Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

Commit 39853aa

Browse files
committed
remote: add the last 100 commits for each ref in haves list
If the local ref is not an ancestor of the remote ref being fetched, then when we send an UploadPack request with that local ref as one of the Haves, the remote will not recognize it, and will think we are asking for the entire history of the repo, even if there's a common ancestor. To do this right, we need to support the multi-ack protocol so we can negotiate a common commit. That's hard though; this is a quick fix just to include the previous 100 commits for each local ref in the Haves list, and hope that one of them is the common commit.
1 parent a99c129 commit 39853aa

File tree

2 files changed

+97
-4
lines changed

2 files changed

+97
-4
lines changed

remote.go

Lines changed: 92 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ var (
2727
ErrDeleteRefNotSupported = errors.New("server does not support delete-refs")
2828
)
2929

30+
const (
31+
// This describes the maximum number of commits to walk when
32+
// computing the haves to send to a server, for each ref in the
33+
// repo containing this remote, when not using the multi-ack
34+
// protocol. Setting this to 0 means there is no limit.
35+
maxHavesToVisitPerRef = 100
36+
)
37+
3038
// Remote represents a connection to a remote repository.
3139
type Remote struct {
3240
c *config.RemoteConfig
@@ -284,7 +292,7 @@ func (r *Remote) fetch(ctx context.Context, o *FetchOptions) (storer.ReferenceSt
284292

285293
req.Wants, err = getWants(r.s, refs)
286294
if len(req.Wants) > 0 {
287-
req.Haves, err = getHaves(localRefs)
295+
req.Haves, err = getHaves(localRefs, remoteRefs, r.s)
288296
if err != nil {
289297
return nil, err
290298
}
@@ -490,8 +498,86 @@ func (r *Remote) references() ([]*plumbing.Reference, error) {
490498
return localRefs, nil
491499
}
492500

493-
func getHaves(localRefs []*plumbing.Reference) ([]plumbing.Hash, error) {
501+
func getRemoteRefsFromStorer(remoteRefStorer storer.ReferenceStorer) (
502+
map[plumbing.Hash]bool, error) {
503+
remoteRefs := map[plumbing.Hash]bool{}
504+
iter, err := remoteRefStorer.IterReferences()
505+
if err != nil {
506+
return nil, err
507+
}
508+
err = iter.ForEach(func(ref *plumbing.Reference) error {
509+
if ref.Type() != plumbing.HashReference {
510+
return nil
511+
}
512+
remoteRefs[ref.Hash()] = true
513+
return nil
514+
})
515+
if err != nil {
516+
return nil, err
517+
}
518+
return remoteRefs, nil
519+
}
520+
521+
// getHavesFromRef populates the given `haves` map with the given
522+
// reference, and up to `maxHavesToVisitPerRef` ancestor commits.
523+
func getHavesFromRef(
524+
ref *plumbing.Reference,
525+
remoteRefs map[plumbing.Hash]bool,
526+
s storage.Storer,
527+
haves map[plumbing.Hash]bool,
528+
) error {
529+
h := ref.Hash()
530+
if haves[h] {
531+
return nil
532+
}
533+
534+
// No need to load the commit if we know the remote already
535+
// has this hash.
536+
if remoteRefs[h] {
537+
haves[h] = true
538+
return nil
539+
}
540+
541+
commit, err := object.GetCommit(s, h)
542+
if err != nil {
543+
// Ignore the error if this isn't a commit.
544+
haves[ref.Hash()] = true
545+
return nil
546+
}
547+
548+
// Until go-git supports proper commit negotiation during an
549+
// upload pack request, include up to `maxHavesToVisitPerRef`
550+
// commits from the history of each ref.
551+
walker := object.NewCommitPreorderIter(commit, haves, nil)
552+
toVisit := maxHavesToVisitPerRef
553+
return walker.ForEach(func(c *object.Commit) error {
554+
haves[c.Hash] = true
555+
toVisit--
556+
// If toVisit starts out at 0 (indicating there is no
557+
// max), then it will be negative here and we won't stop
558+
// early.
559+
if toVisit == 0 || remoteRefs[c.Hash] {
560+
return storer.ErrStop
561+
}
562+
return nil
563+
})
564+
}
565+
566+
func getHaves(
567+
localRefs []*plumbing.Reference,
568+
remoteRefStorer storer.ReferenceStorer,
569+
s storage.Storer,
570+
) ([]plumbing.Hash, error) {
494571
haves := map[plumbing.Hash]bool{}
572+
573+
// Build a map of all the remote references, to avoid loading too
574+
// many parent commits for references we know don't need to be
575+
// transferred.
576+
remoteRefs, err := getRemoteRefsFromStorer(remoteRefStorer)
577+
if err != nil {
578+
return nil, err
579+
}
580+
495581
for _, ref := range localRefs {
496582
if haves[ref.Hash()] == true {
497583
continue
@@ -501,7 +587,10 @@ func getHaves(localRefs []*plumbing.Reference) ([]plumbing.Hash, error) {
501587
continue
502588
}
503589

504-
haves[ref.Hash()] = true
590+
err = getHavesFromRef(ref, remoteRefs, s, haves)
591+
if err != nil {
592+
return nil, err
593+
}
505594
}
506595

507596
var result []plumbing.Hash

remote_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,10 @@ func (s *RemoteSuite) TestPushWrongRemoteName(c *C) {
625625
}
626626

627627
func (s *RemoteSuite) TestGetHaves(c *C) {
628+
f := fixtures.Basic().One()
629+
sto, err := filesystem.NewStorage(f.DotGit())
630+
c.Assert(err, IsNil)
631+
628632
var localRefs = []*plumbing.Reference{
629633
plumbing.NewReferenceFromStrings(
630634
"foo",
@@ -640,7 +644,7 @@ func (s *RemoteSuite) TestGetHaves(c *C) {
640644
),
641645
}
642646

643-
l, err := getHaves(localRefs)
647+
l, err := getHaves(localRefs, memory.NewStorage(), sto)
644648
c.Assert(err, IsNil)
645649
c.Assert(l, HasLen, 2)
646650
}

0 commit comments

Comments
 (0)