Skip to content

Commit

Permalink
git: Fix fetching after shallow clone. Fixes go-git#305
Browse files Browse the repository at this point in the history
Signed-off-by: Arieh Schneier <15041913+AriehSchneier@users.noreply.github.com>
  • Loading branch information
AriehSchneier authored and durandj committed Jul 1, 2023
1 parent 337f72a commit 0d55948
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 44 deletions.
35 changes: 35 additions & 0 deletions common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package git
import (
"os"
"testing"
"time"

"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/cache"
"github.com/go-git/go-git/v5/plumbing/format/packfile"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/storage/filesystem"
"github.com/go-git/go-git/v5/storage/memory"

Expand Down Expand Up @@ -212,3 +214,36 @@ func AssertReferencesMissing(c *C, r *Repository, expected []string) {
c.Assert(err, Equals, plumbing.ErrReferenceNotFound)
}
}

func CommitNewFile(c *C, repo *Repository, fileName string) plumbing.Hash {
wt, err := repo.Worktree()
c.Assert(err, IsNil)

fd, err := wt.Filesystem.Create(fileName)
c.Assert(err, IsNil)

_, err = fd.Write([]byte("# test file"))
c.Assert(err, IsNil)

err = fd.Close()
c.Assert(err, IsNil)

_, err = wt.Add(fileName)
c.Assert(err, IsNil)

sha, err := wt.Commit("test commit", &CommitOptions{
Author: &object.Signature{
Name: "test",
Email: "test@example.com",
When: time.Now(),
},
Committer: &object.Signature{
Name: "test",
Email: "test@example.com",
When: time.Now(),
},
})
c.Assert(err, IsNil)

return sha
}
16 changes: 13 additions & 3 deletions remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ func (r *Remote) fetch(ctx context.Context, o *FetchOptions) (sto storer.Referen

req.Wants, err = getWants(r.s, refs)
if len(req.Wants) > 0 {
req.Haves, err = getHaves(localRefs, remoteRefs, r.s)
req.Haves, err = getHaves(localRefs, remoteRefs, r.s, o.Depth)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -837,6 +837,7 @@ func getHavesFromRef(
remoteRefs map[plumbing.Hash]bool,
s storage.Storer,
haves map[plumbing.Hash]bool,
depth int,
) error {
h := ref.Hash()
if haves[h] {
Expand All @@ -862,7 +863,13 @@ func getHavesFromRef(
// commits from the history of each ref.
walker := object.NewCommitPreorderIter(commit, haves, nil)
toVisit := maxHavesToVisitPerRef
return walker.ForEach(func(c *object.Commit) error {
// But only need up to the requested depth
if depth > 0 && depth < maxHavesToVisitPerRef {
toVisit = depth
}
// It is safe to ignore any error here as we are just trying to find the references that we already have
// An example of a legitimate failure is we have a shallow clone and don't have the previous commit(s)
_ = walker.ForEach(func(c *object.Commit) error {
haves[c.Hash] = true
toVisit--
// If toVisit starts out at 0 (indicating there is no
Expand All @@ -873,12 +880,15 @@ func getHavesFromRef(
}
return nil
})

return nil
}

func getHaves(
localRefs []*plumbing.Reference,
remoteRefStorer storer.ReferenceStorer,
s storage.Storer,
depth int,
) ([]plumbing.Hash, error) {
haves := map[plumbing.Hash]bool{}

Expand All @@ -899,7 +909,7 @@ func getHaves(
continue
}

err = getHavesFromRef(ref, remoteRefs, s, haves)
err = getHavesFromRef(ref, remoteRefs, s, haves, depth)
if err != nil {
return nil, err
}
Expand Down
109 changes: 68 additions & 41 deletions remote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/cache"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/plumbing/protocol/packp"
"github.com/go-git/go-git/v5/plumbing/protocol/packp/capability"
"github.com/go-git/go-git/v5/plumbing/storer"
Expand Down Expand Up @@ -1175,7 +1174,7 @@ func (s *RemoteSuite) TestGetHaves(c *C) {
),
}

l, err := getHaves(localRefs, memory.NewStorage(), sto)
l, err := getHaves(localRefs, memory.NewStorage(), sto, 0)
c.Assert(err, IsNil)
c.Assert(l, HasLen, 2)
}
Expand Down Expand Up @@ -1404,45 +1403,7 @@ func (s *RemoteSuite) TestCanPushShasToReference(c *C) {
c.Assert(err, IsNil)
c.Assert(repo, NotNil)

fd, err := os.Create(filepath.Join(d, "repo", "README.md"))
c.Assert(err, IsNil)
if err != nil {
return
}
_, err = fd.WriteString("# test repo")
c.Assert(err, IsNil)
if err != nil {
return
}
err = fd.Close()
c.Assert(err, IsNil)
if err != nil {
return
}

wt, err := repo.Worktree()
c.Assert(err, IsNil)
if err != nil {
return
}

wt.Add("README.md")
sha, err := wt.Commit("test commit", &CommitOptions{
Author: &object.Signature{
Name: "test",
Email: "test@example.com",
When: time.Now(),
},
Committer: &object.Signature{
Name: "test",
Email: "test@example.com",
When: time.Now(),
},
})
c.Assert(err, IsNil)
if err != nil {
return
}
sha := CommitNewFile(c, repo, "README.md")

gitremote, err := repo.CreateRemote(&config.RemoteConfig{
Name: "local",
Expand Down Expand Up @@ -1472,3 +1433,69 @@ func (s *RemoteSuite) TestCanPushShasToReference(c *C) {
}
c.Assert(ref.Hash().String(), Equals, sha.String())
}

func (s *RemoteSuite) TestFetchAfterShallowClone(c *C) {
tempDir, clean := s.TemporalDir()
defer clean()
remoteUrl := filepath.Join(tempDir, "remote")
repoDir := filepath.Join(tempDir, "repo")

// Create a new repo and add more than 1 commit (so we can have a shallow commit)
remote, err := PlainInit(remoteUrl, false)
c.Assert(err, IsNil)
c.Assert(remote, NotNil)

_ = CommitNewFile(c, remote, "File1")
_ = CommitNewFile(c, remote, "File2")

// Clone the repo with a depth of 1
repo, err := PlainClone(repoDir, false, &CloneOptions{
URL: remoteUrl,
Depth: 1,
Tags: NoTags,
SingleBranch: true,
ReferenceName: "master",
})
c.Assert(err, IsNil)

// Add new commits to the origin (more than 1 so that our next test hits a missing commit)
_ = CommitNewFile(c, remote, "File3")
sha4 := CommitNewFile(c, remote, "File4")

// Try fetch with depth of 1 again (note, we need to ensure no remote branch remains pointing at the old commit)
r, err := repo.Remote(DefaultRemoteName)
c.Assert(err, IsNil)
s.testFetch(c, r, &FetchOptions{
Depth: 2,
Tags: NoTags,

RefSpecs: []config.RefSpec{
"+refs/heads/master:refs/heads/master",
"+refs/heads/master:refs/remotes/origin/master",
},
}, []*plumbing.Reference{
plumbing.NewReferenceFromStrings("refs/heads/master", sha4.String()),
plumbing.NewReferenceFromStrings("refs/remotes/origin/master", sha4.String()),
plumbing.NewSymbolicReference("HEAD", "refs/heads/master"),
})

// Add another commit to the origin
sha5 := CommitNewFile(c, remote, "File5")

// Try fetch with depth of 2 this time (to reach a commit that we don't have locally)
r, err = repo.Remote(DefaultRemoteName)
c.Assert(err, IsNil)
s.testFetch(c, r, &FetchOptions{
Depth: 1,
Tags: NoTags,

RefSpecs: []config.RefSpec{
"+refs/heads/master:refs/heads/master",
"+refs/heads/master:refs/remotes/origin/master",
},
}, []*plumbing.Reference{
plumbing.NewReferenceFromStrings("refs/heads/master", sha5.String()),
plumbing.NewReferenceFromStrings("refs/remotes/origin/master", sha5.String()),
plumbing.NewSymbolicReference("HEAD", "refs/heads/master"),
})
}

0 comments on commit 0d55948

Please sign in to comment.