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

Commit b345ed7

Browse files
authored
Merge pull request #610 from keybase/strib/gh-master-list-extra-haves
remote: add the last 100 commits for each ref in haves list
2 parents e4ce8aa + 39853aa commit b345ed7

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)