Skip to content

Commit b98b813

Browse files
authored
Merge pull request #778 from AriehSchneier/fix-fetch-after-shallow-v2
git: Fix fetching after shallow clone. Fixes #305
2 parents 84d9be2 + cdba533 commit b98b813

File tree

3 files changed

+116
-44
lines changed

3 files changed

+116
-44
lines changed

common_test.go

+35
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ package git
33
import (
44
"os"
55
"testing"
6+
"time"
67

78
"github.com/go-git/go-git/v5/plumbing"
89
"github.com/go-git/go-git/v5/plumbing/cache"
910
"github.com/go-git/go-git/v5/plumbing/format/packfile"
11+
"github.com/go-git/go-git/v5/plumbing/object"
1012
"github.com/go-git/go-git/v5/storage/filesystem"
1113
"github.com/go-git/go-git/v5/storage/memory"
1214

@@ -212,3 +214,36 @@ func AssertReferencesMissing(c *C, r *Repository, expected []string) {
212214
c.Assert(err, Equals, plumbing.ErrReferenceNotFound)
213215
}
214216
}
217+
218+
func CommitNewFile(c *C, repo *Repository, fileName string) plumbing.Hash {
219+
wt, err := repo.Worktree()
220+
c.Assert(err, IsNil)
221+
222+
fd, err := wt.Filesystem.Create(fileName)
223+
c.Assert(err, IsNil)
224+
225+
_, err = fd.Write([]byte("# test file"))
226+
c.Assert(err, IsNil)
227+
228+
err = fd.Close()
229+
c.Assert(err, IsNil)
230+
231+
_, err = wt.Add(fileName)
232+
c.Assert(err, IsNil)
233+
234+
sha, err := wt.Commit("test commit", &CommitOptions{
235+
Author: &object.Signature{
236+
Name: "test",
237+
Email: "test@example.com",
238+
When: time.Now(),
239+
},
240+
Committer: &object.Signature{
241+
Name: "test",
242+
Email: "test@example.com",
243+
When: time.Now(),
244+
},
245+
})
246+
c.Assert(err, IsNil)
247+
248+
return sha
249+
}

remote.go

+13-3
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ func (r *Remote) fetch(ctx context.Context, o *FetchOptions) (sto storer.Referen
459459

460460
req.Wants, err = getWants(r.s, refs)
461461
if len(req.Wants) > 0 {
462-
req.Haves, err = getHaves(localRefs, remoteRefs, r.s)
462+
req.Haves, err = getHaves(localRefs, remoteRefs, r.s, o.Depth)
463463
if err != nil {
464464
return nil, err
465465
}
@@ -837,6 +837,7 @@ func getHavesFromRef(
837837
remoteRefs map[plumbing.Hash]bool,
838838
s storage.Storer,
839839
haves map[plumbing.Hash]bool,
840+
depth int,
840841
) error {
841842
h := ref.Hash()
842843
if haves[h] {
@@ -862,7 +863,13 @@ func getHavesFromRef(
862863
// commits from the history of each ref.
863864
walker := object.NewCommitPreorderIter(commit, haves, nil)
864865
toVisit := maxHavesToVisitPerRef
865-
return walker.ForEach(func(c *object.Commit) error {
866+
// But only need up to the requested depth
867+
if depth > 0 && depth < maxHavesToVisitPerRef {
868+
toVisit = depth
869+
}
870+
// It is safe to ignore any error here as we are just trying to find the references that we already have
871+
// An example of a legitimate failure is we have a shallow clone and don't have the previous commit(s)
872+
_ = walker.ForEach(func(c *object.Commit) error {
866873
haves[c.Hash] = true
867874
toVisit--
868875
// If toVisit starts out at 0 (indicating there is no
@@ -873,12 +880,15 @@ func getHavesFromRef(
873880
}
874881
return nil
875882
})
883+
884+
return nil
876885
}
877886

878887
func getHaves(
879888
localRefs []*plumbing.Reference,
880889
remoteRefStorer storer.ReferenceStorer,
881890
s storage.Storer,
891+
depth int,
882892
) ([]plumbing.Hash, error) {
883893
haves := map[plumbing.Hash]bool{}
884894

@@ -899,7 +909,7 @@ func getHaves(
899909
continue
900910
}
901911

902-
err = getHavesFromRef(ref, remoteRefs, s, haves)
912+
err = getHavesFromRef(ref, remoteRefs, s, haves, depth)
903913
if err != nil {
904914
return nil, err
905915
}

remote_test.go

+68-41
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414
"github.com/go-git/go-git/v5/config"
1515
"github.com/go-git/go-git/v5/plumbing"
1616
"github.com/go-git/go-git/v5/plumbing/cache"
17-
"github.com/go-git/go-git/v5/plumbing/object"
1817
"github.com/go-git/go-git/v5/plumbing/protocol/packp"
1918
"github.com/go-git/go-git/v5/plumbing/protocol/packp/capability"
2019
"github.com/go-git/go-git/v5/plumbing/storer"
@@ -1175,7 +1174,7 @@ func (s *RemoteSuite) TestGetHaves(c *C) {
11751174
),
11761175
}
11771176

1178-
l, err := getHaves(localRefs, memory.NewStorage(), sto)
1177+
l, err := getHaves(localRefs, memory.NewStorage(), sto, 0)
11791178
c.Assert(err, IsNil)
11801179
c.Assert(l, HasLen, 2)
11811180
}
@@ -1404,45 +1403,7 @@ func (s *RemoteSuite) TestCanPushShasToReference(c *C) {
14041403
c.Assert(err, IsNil)
14051404
c.Assert(repo, NotNil)
14061405

1407-
fd, err := os.Create(filepath.Join(d, "repo", "README.md"))
1408-
c.Assert(err, IsNil)
1409-
if err != nil {
1410-
return
1411-
}
1412-
_, err = fd.WriteString("# test repo")
1413-
c.Assert(err, IsNil)
1414-
if err != nil {
1415-
return
1416-
}
1417-
err = fd.Close()
1418-
c.Assert(err, IsNil)
1419-
if err != nil {
1420-
return
1421-
}
1422-
1423-
wt, err := repo.Worktree()
1424-
c.Assert(err, IsNil)
1425-
if err != nil {
1426-
return
1427-
}
1428-
1429-
wt.Add("README.md")
1430-
sha, err := wt.Commit("test commit", &CommitOptions{
1431-
Author: &object.Signature{
1432-
Name: "test",
1433-
Email: "test@example.com",
1434-
When: time.Now(),
1435-
},
1436-
Committer: &object.Signature{
1437-
Name: "test",
1438-
Email: "test@example.com",
1439-
When: time.Now(),
1440-
},
1441-
})
1442-
c.Assert(err, IsNil)
1443-
if err != nil {
1444-
return
1445-
}
1406+
sha := CommitNewFile(c, repo, "README.md")
14461407

14471408
gitremote, err := repo.CreateRemote(&config.RemoteConfig{
14481409
Name: "local",
@@ -1472,3 +1433,69 @@ func (s *RemoteSuite) TestCanPushShasToReference(c *C) {
14721433
}
14731434
c.Assert(ref.Hash().String(), Equals, sha.String())
14741435
}
1436+
1437+
func (s *RemoteSuite) TestFetchAfterShallowClone(c *C) {
1438+
tempDir, clean := s.TemporalDir()
1439+
defer clean()
1440+
remoteUrl := filepath.Join(tempDir, "remote")
1441+
repoDir := filepath.Join(tempDir, "repo")
1442+
1443+
// Create a new repo and add more than 1 commit (so we can have a shallow commit)
1444+
remote, err := PlainInit(remoteUrl, false)
1445+
c.Assert(err, IsNil)
1446+
c.Assert(remote, NotNil)
1447+
1448+
_ = CommitNewFile(c, remote, "File1")
1449+
_ = CommitNewFile(c, remote, "File2")
1450+
1451+
// Clone the repo with a depth of 1
1452+
repo, err := PlainClone(repoDir, false, &CloneOptions{
1453+
URL: remoteUrl,
1454+
Depth: 1,
1455+
Tags: NoTags,
1456+
SingleBranch: true,
1457+
ReferenceName: "master",
1458+
})
1459+
c.Assert(err, IsNil)
1460+
1461+
// Add new commits to the origin (more than 1 so that our next test hits a missing commit)
1462+
_ = CommitNewFile(c, remote, "File3")
1463+
sha4 := CommitNewFile(c, remote, "File4")
1464+
1465+
// Try fetch with depth of 1 again (note, we need to ensure no remote branch remains pointing at the old commit)
1466+
r, err := repo.Remote(DefaultRemoteName)
1467+
c.Assert(err, IsNil)
1468+
s.testFetch(c, r, &FetchOptions{
1469+
Depth: 2,
1470+
Tags: NoTags,
1471+
1472+
RefSpecs: []config.RefSpec{
1473+
"+refs/heads/master:refs/heads/master",
1474+
"+refs/heads/master:refs/remotes/origin/master",
1475+
},
1476+
}, []*plumbing.Reference{
1477+
plumbing.NewReferenceFromStrings("refs/heads/master", sha4.String()),
1478+
plumbing.NewReferenceFromStrings("refs/remotes/origin/master", sha4.String()),
1479+
plumbing.NewSymbolicReference("HEAD", "refs/heads/master"),
1480+
})
1481+
1482+
// Add another commit to the origin
1483+
sha5 := CommitNewFile(c, remote, "File5")
1484+
1485+
// Try fetch with depth of 2 this time (to reach a commit that we don't have locally)
1486+
r, err = repo.Remote(DefaultRemoteName)
1487+
c.Assert(err, IsNil)
1488+
s.testFetch(c, r, &FetchOptions{
1489+
Depth: 1,
1490+
Tags: NoTags,
1491+
1492+
RefSpecs: []config.RefSpec{
1493+
"+refs/heads/master:refs/heads/master",
1494+
"+refs/heads/master:refs/remotes/origin/master",
1495+
},
1496+
}, []*plumbing.Reference{
1497+
plumbing.NewReferenceFromStrings("refs/heads/master", sha5.String()),
1498+
plumbing.NewReferenceFromStrings("refs/remotes/origin/master", sha5.String()),
1499+
plumbing.NewSymbolicReference("HEAD", "refs/heads/master"),
1500+
})
1501+
}

0 commit comments

Comments
 (0)