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.
3139type 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
0 commit comments