fork of go-git with some jj specific features
1package git
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "io"
8 "strings"
9 "time"
10
11 "github.com/go-git/go-billy/v5/osfs"
12
13 "github.com/go-git/go-git/v5/config"
14 "github.com/go-git/go-git/v5/internal/url"
15 "github.com/go-git/go-git/v5/plumbing"
16 "github.com/go-git/go-git/v5/plumbing/cache"
17 "github.com/go-git/go-git/v5/plumbing/format/packfile"
18 "github.com/go-git/go-git/v5/plumbing/object"
19 "github.com/go-git/go-git/v5/plumbing/protocol/packp"
20 "github.com/go-git/go-git/v5/plumbing/protocol/packp/capability"
21 "github.com/go-git/go-git/v5/plumbing/protocol/packp/sideband"
22 "github.com/go-git/go-git/v5/plumbing/revlist"
23 "github.com/go-git/go-git/v5/plumbing/storer"
24 "github.com/go-git/go-git/v5/plumbing/transport"
25 "github.com/go-git/go-git/v5/plumbing/transport/client"
26 "github.com/go-git/go-git/v5/storage"
27 "github.com/go-git/go-git/v5/storage/filesystem"
28 "github.com/go-git/go-git/v5/storage/memory"
29 "github.com/go-git/go-git/v5/utils/ioutil"
30)
31
32var (
33 NoErrAlreadyUpToDate = errors.New("already up-to-date")
34 ErrDeleteRefNotSupported = errors.New("server does not support delete-refs")
35 ErrForceNeeded = errors.New("some refs were not updated")
36 ErrExactSHA1NotSupported = errors.New("server does not support exact SHA1 refspec")
37 ErrEmptyUrls = errors.New("URLs cannot be empty")
38)
39
40type NoMatchingRefSpecError struct {
41 refSpec config.RefSpec
42}
43
44func (e NoMatchingRefSpecError) Error() string {
45 return fmt.Sprintf("couldn't find remote ref %q", e.refSpec.Src())
46}
47
48func (e NoMatchingRefSpecError) Is(target error) bool {
49 _, ok := target.(NoMatchingRefSpecError)
50 return ok
51}
52
53const (
54 // This describes the maximum number of commits to walk when
55 // computing the haves to send to a server, for each ref in the
56 // repo containing this remote, when not using the multi-ack
57 // protocol. Setting this to 0 means there is no limit.
58 maxHavesToVisitPerRef = 100
59
60 // peeledSuffix is the suffix used to build peeled reference names.
61 peeledSuffix = "^{}"
62)
63
64// Remote represents a connection to a remote repository.
65type Remote struct {
66 c *config.RemoteConfig
67 s storage.Storer
68}
69
70// NewRemote creates a new Remote.
71// The intended purpose is to use the Remote for tasks such as listing remote references (like using git ls-remote).
72// Otherwise Remotes should be created via the use of a Repository.
73func NewRemote(s storage.Storer, c *config.RemoteConfig) *Remote {
74 return &Remote{s: s, c: c}
75}
76
77// Config returns the RemoteConfig object used to instantiate this Remote.
78func (r *Remote) Config() *config.RemoteConfig {
79 return r.c
80}
81
82func (r *Remote) String() string {
83 var fetch, push string
84 if len(r.c.URLs) > 0 {
85 fetch = r.c.URLs[0]
86 push = r.c.URLs[len(r.c.URLs)-1]
87 }
88
89 return fmt.Sprintf("%s\t%s (fetch)\n%[1]s\t%[3]s (push)", r.c.Name, fetch, push)
90}
91
92// Push performs a push to the remote. Returns NoErrAlreadyUpToDate if the
93// remote was already up-to-date.
94func (r *Remote) Push(o *PushOptions) error {
95 return r.PushContext(context.Background(), o)
96}
97
98// PushContext performs a push to the remote. Returns NoErrAlreadyUpToDate if
99// the remote was already up-to-date.
100//
101// The provided Context must be non-nil. If the context expires before the
102// operation is complete, an error is returned. The context only affects the
103// transport operations.
104func (r *Remote) PushContext(ctx context.Context, o *PushOptions) (err error) {
105 if err := o.Validate(); err != nil {
106 return err
107 }
108
109 if o.RemoteName != r.c.Name {
110 return fmt.Errorf("remote names don't match: %s != %s", o.RemoteName, r.c.Name)
111 }
112
113 if o.RemoteURL == "" && len(r.c.URLs) > 0 {
114 o.RemoteURL = r.c.URLs[len(r.c.URLs)-1]
115 }
116
117 s, err := newSendPackSession(o.RemoteURL, o.Auth, o.InsecureSkipTLS, o.ClientCert, o.ClientKey, o.CABundle, o.ProxyOptions)
118 if err != nil {
119 return err
120 }
121
122 defer ioutil.CheckClose(s, &err)
123
124 ar, err := s.AdvertisedReferencesContext(ctx)
125 if err != nil {
126 return err
127 }
128
129 remoteRefs, err := ar.AllReferences()
130 if err != nil {
131 return err
132 }
133
134 if err := r.checkRequireRemoteRefs(o.RequireRemoteRefs, remoteRefs); err != nil {
135 return err
136 }
137
138 isDelete := false
139 allDelete := true
140 for _, rs := range o.RefSpecs {
141 if rs.IsDelete() {
142 isDelete = true
143 } else {
144 allDelete = false
145 }
146 if isDelete && !allDelete {
147 break
148 }
149 }
150
151 if isDelete && !ar.Capabilities.Supports(capability.DeleteRefs) {
152 return ErrDeleteRefNotSupported
153 }
154
155 if o.Force {
156 for i := 0; i < len(o.RefSpecs); i++ {
157 rs := &o.RefSpecs[i]
158 if !rs.IsForceUpdate() && !rs.IsDelete() {
159 o.RefSpecs[i] = config.RefSpec("+" + rs.String())
160 }
161 }
162 }
163
164 localRefs, err := r.references()
165 if err != nil {
166 return err
167 }
168
169 req, err := r.newReferenceUpdateRequest(o, localRefs, remoteRefs, ar)
170 if err != nil {
171 return err
172 }
173
174 if len(req.Commands) == 0 {
175 return NoErrAlreadyUpToDate
176 }
177
178 objects := objectsToPush(req.Commands)
179
180 haves, err := referencesToHashes(remoteRefs)
181 if err != nil {
182 return err
183 }
184
185 stop, err := r.s.Shallow()
186 if err != nil {
187 return err
188 }
189
190 // if we have shallow we should include this as part of the objects that
191 // we are aware.
192 haves = append(haves, stop...)
193
194 var hashesToPush []plumbing.Hash
195 // Avoid the expensive revlist operation if we're only doing deletes.
196 if !allDelete {
197 if url.IsLocalEndpoint(o.RemoteURL) {
198 // If we're are pushing to a local repo, it might be much
199 // faster to use a local storage layer to get the commits
200 // to ignore, when calculating the object revlist.
201 localStorer := filesystem.NewStorage(
202 osfs.New(o.RemoteURL), cache.NewObjectLRUDefault())
203 hashesToPush, err = revlist.ObjectsWithStorageForIgnores(
204 r.s, localStorer, objects, haves)
205 } else {
206 hashesToPush, err = revlist.Objects(r.s, objects, haves)
207 }
208 if err != nil {
209 return err
210 }
211 }
212
213 if len(hashesToPush) == 0 {
214 allDelete = true
215 for _, command := range req.Commands {
216 if command.Action() != packp.Delete {
217 allDelete = false
218 break
219 }
220 }
221 }
222
223 rs, err := pushHashes(ctx, s, r.s, req, hashesToPush, r.useRefDeltas(ar), allDelete)
224 if err != nil {
225 return err
226 }
227
228 if rs != nil {
229 if err = rs.Error(); err != nil {
230 return err
231 }
232 }
233
234 return r.updateRemoteReferenceStorage(req)
235}
236
237func (r *Remote) useRefDeltas(ar *packp.AdvRefs) bool {
238 return !ar.Capabilities.Supports(capability.OFSDelta)
239}
240
241func (r *Remote) addReachableTags(localRefs []*plumbing.Reference, remoteRefs storer.ReferenceStorer, req *packp.ReferenceUpdateRequest) error {
242 tags := make(map[plumbing.Reference]struct{})
243 // get a list of all tags locally
244 for _, ref := range localRefs {
245 if strings.HasPrefix(string(ref.Name()), "refs/tags") {
246 tags[*ref] = struct{}{}
247 }
248 }
249
250 remoteRefIter, err := remoteRefs.IterReferences()
251 if err != nil {
252 return err
253 }
254
255 // remove any that are already on the remote
256 if err := remoteRefIter.ForEach(func(reference *plumbing.Reference) error {
257 delete(tags, *reference)
258 return nil
259 }); err != nil {
260 return err
261 }
262
263 for tag := range tags {
264 tagObject, err := object.GetObject(r.s, tag.Hash())
265 var tagCommit *object.Commit
266 if err != nil {
267 return fmt.Errorf("get tag object: %w", err)
268 }
269
270 if tagObject.Type() != plumbing.TagObject {
271 continue
272 }
273
274 annotatedTag, ok := tagObject.(*object.Tag)
275 if !ok {
276 return errors.New("could not get annotated tag object")
277 }
278
279 tagCommit, err = object.GetCommit(r.s, annotatedTag.Target)
280 if err != nil {
281 return fmt.Errorf("get annotated tag commit: %w", err)
282 }
283
284 // only include tags that are reachable from one of the refs
285 // already being pushed
286 for _, cmd := range req.Commands {
287 if tag.Name() == cmd.Name {
288 continue
289 }
290
291 if strings.HasPrefix(cmd.Name.String(), "refs/tags") {
292 continue
293 }
294
295 c, err := object.GetCommit(r.s, cmd.New)
296 if err != nil {
297 return fmt.Errorf("get commit %v: %w", cmd.Name, err)
298 }
299
300 if isAncestor, err := tagCommit.IsAncestor(c); err == nil && isAncestor {
301 req.Commands = append(req.Commands, &packp.Command{Name: tag.Name(), New: tag.Hash()})
302 }
303 }
304 }
305
306 return nil
307}
308
309func (r *Remote) newReferenceUpdateRequest(
310 o *PushOptions,
311 localRefs []*plumbing.Reference,
312 remoteRefs storer.ReferenceStorer,
313 ar *packp.AdvRefs,
314) (*packp.ReferenceUpdateRequest, error) {
315 req := packp.NewReferenceUpdateRequestFromCapabilities(ar.Capabilities)
316
317 if o.Progress != nil {
318 req.Progress = o.Progress
319 if ar.Capabilities.Supports(capability.Sideband64k) {
320 _ = req.Capabilities.Set(capability.Sideband64k)
321 } else if ar.Capabilities.Supports(capability.Sideband) {
322 _ = req.Capabilities.Set(capability.Sideband)
323 }
324 }
325
326 if ar.Capabilities.Supports(capability.PushOptions) {
327 _ = req.Capabilities.Set(capability.PushOptions)
328 for k, v := range o.Options {
329 req.Options = append(req.Options, &packp.Option{Key: k, Value: v})
330 }
331 }
332
333 if o.Atomic && ar.Capabilities.Supports(capability.Atomic) {
334 _ = req.Capabilities.Set(capability.Atomic)
335 }
336
337 if err := r.addReferencesToUpdate(o.RefSpecs, localRefs, remoteRefs, req, o.Prune, o.ForceWithLease); err != nil {
338
339 return nil, err
340 }
341
342 if o.FollowTags {
343 if err := r.addReachableTags(localRefs, remoteRefs, req); err != nil {
344 return nil, err
345 }
346 }
347
348 return req, nil
349}
350
351func (r *Remote) updateRemoteReferenceStorage(
352 req *packp.ReferenceUpdateRequest,
353) error {
354
355 for _, spec := range r.c.Fetch {
356 for _, c := range req.Commands {
357 if !spec.Match(c.Name) {
358 continue
359 }
360
361 local := spec.Dst(c.Name)
362 ref := plumbing.NewHashReference(local, c.New)
363 switch c.Action() {
364 case packp.Create, packp.Update:
365 if err := r.s.SetReference(ref); err != nil {
366 return err
367 }
368 case packp.Delete:
369 if err := r.s.RemoveReference(local); err != nil {
370 return err
371 }
372 }
373 }
374 }
375
376 return nil
377}
378
379// FetchContext fetches references along with the objects necessary to complete
380// their histories.
381//
382// Returns nil if the operation is successful, NoErrAlreadyUpToDate if there are
383// no changes to be fetched, or an error.
384//
385// The provided Context must be non-nil. If the context expires before the
386// operation is complete, an error is returned. The context only affects the
387// transport operations.
388func (r *Remote) FetchContext(ctx context.Context, o *FetchOptions) error {
389 _, err := r.fetch(ctx, o)
390 return err
391}
392
393// Fetch fetches references along with the objects necessary to complete their
394// histories.
395//
396// Returns nil if the operation is successful, NoErrAlreadyUpToDate if there are
397// no changes to be fetched, or an error.
398func (r *Remote) Fetch(o *FetchOptions) error {
399 return r.FetchContext(context.Background(), o)
400}
401
402func (r *Remote) fetch(ctx context.Context, o *FetchOptions) (sto storer.ReferenceStorer, err error) {
403 if o.RemoteName == "" {
404 o.RemoteName = r.c.Name
405 }
406
407 if err = o.Validate(); err != nil {
408 return nil, err
409 }
410
411 if len(o.RefSpecs) == 0 {
412 o.RefSpecs = r.c.Fetch
413 }
414
415 if o.RemoteURL == "" {
416 o.RemoteURL = r.c.URLs[0]
417 }
418
419 s, err := newUploadPackSession(o.RemoteURL, o.Auth, o.InsecureSkipTLS, o.ClientCert, o.ClientKey, o.CABundle, o.ProxyOptions)
420 if err != nil {
421 return nil, err
422 }
423
424 defer ioutil.CheckClose(s, &err)
425
426 ar, err := s.AdvertisedReferencesContext(ctx)
427 if err != nil {
428 return nil, err
429 }
430
431 req, err := r.newUploadPackRequest(o, ar)
432 if err != nil {
433 return nil, err
434 }
435
436 if err := r.isSupportedRefSpec(o.RefSpecs, ar); err != nil {
437 return nil, err
438 }
439
440 remoteRefs, err := ar.AllReferences()
441 if err != nil {
442 return nil, err
443 }
444
445 localRefs, err := r.references()
446 if err != nil {
447 return nil, err
448 }
449
450 refs, specToRefs, err := calculateRefs(o.RefSpecs, remoteRefs, o.Tags)
451 if err != nil {
452 return nil, err
453 }
454
455 if !req.Depth.IsZero() {
456 req.Shallows, err = r.s.Shallow()
457 if err != nil {
458 return nil, fmt.Errorf("existing checkout is not shallow")
459 }
460 }
461
462 req.Wants, err = getWants(r.s, refs, o.Depth)
463 if len(req.Wants) > 0 {
464 req.Haves, err = getHaves(localRefs, remoteRefs, r.s, o.Depth)
465 if err != nil {
466 return nil, err
467 }
468
469 if err = r.fetchPack(ctx, o, s, req); err != nil {
470 return nil, err
471 }
472 }
473
474 var updatedPrune bool
475 if o.Prune {
476 updatedPrune, err = r.pruneRemotes(o.RefSpecs, localRefs, remoteRefs)
477 if err != nil {
478 return nil, err
479 }
480 }
481
482 updated, err := r.updateLocalReferenceStorage(o.RefSpecs, refs, remoteRefs, specToRefs, o.Tags, o.Force)
483 if err != nil {
484 return nil, err
485 }
486
487 if !updated {
488 updated, err = depthChanged(req.Shallows, r.s)
489 if err != nil {
490 return nil, fmt.Errorf("error checking depth change: %v", err)
491 }
492 }
493
494 if !updated && !updatedPrune {
495 // No references updated, but may have fetched new objects, check if we now have any of our wants
496 for _, hash := range req.Wants {
497 exists, _ := objectExists(r.s, hash)
498 if exists {
499 updated = true
500 break
501 }
502 }
503
504 if !updated {
505 return remoteRefs, NoErrAlreadyUpToDate
506 }
507 }
508
509 return remoteRefs, nil
510}
511
512func depthChanged(before []plumbing.Hash, s storage.Storer) (bool, error) {
513 after, err := s.Shallow()
514 if err != nil {
515 return false, err
516 }
517
518 if len(before) != len(after) {
519 return true, nil
520 }
521
522 bm := make(map[plumbing.Hash]bool, len(before))
523 for _, b := range before {
524 bm[b] = true
525 }
526 for _, a := range after {
527 if _, ok := bm[a]; !ok {
528 return true, nil
529 }
530 }
531
532 return false, nil
533}
534
535func newUploadPackSession(url string, auth transport.AuthMethod, insecure bool, clientCert, clientKey, caBundle []byte, proxyOpts transport.ProxyOptions) (transport.UploadPackSession, error) {
536 c, ep, err := newClient(url, insecure, clientCert, clientKey, caBundle, proxyOpts)
537 if err != nil {
538 return nil, err
539 }
540
541 return c.NewUploadPackSession(ep, auth)
542}
543
544func newSendPackSession(url string, auth transport.AuthMethod, insecure bool, clientCert, clientKey, caBundle []byte, proxyOpts transport.ProxyOptions) (transport.ReceivePackSession, error) {
545 c, ep, err := newClient(url, insecure, clientCert, clientKey, caBundle, proxyOpts)
546 if err != nil {
547 return nil, err
548 }
549
550 return c.NewReceivePackSession(ep, auth)
551}
552
553func newClient(url string, insecure bool, clientCert, clientKey, caBundle []byte, proxyOpts transport.ProxyOptions) (transport.Transport, *transport.Endpoint, error) {
554 ep, err := transport.NewEndpoint(url)
555 if err != nil {
556 return nil, nil, err
557 }
558 ep.InsecureSkipTLS = insecure
559 ep.ClientCert = clientCert
560 ep.ClientKey = clientKey
561 ep.CaBundle = caBundle
562 ep.Proxy = proxyOpts
563
564 c, err := client.NewClient(ep)
565 if err != nil {
566 return nil, nil, err
567 }
568
569 return c, ep, err
570}
571
572func (r *Remote) fetchPack(ctx context.Context, o *FetchOptions, s transport.UploadPackSession,
573 req *packp.UploadPackRequest) (err error) {
574
575 reader, err := s.UploadPack(ctx, req)
576 if err != nil {
577 if errors.Is(err, transport.ErrEmptyUploadPackRequest) {
578 // XXX: no packfile provided, everything is up-to-date.
579 return nil
580 }
581 return err
582 }
583
584 defer ioutil.CheckClose(reader, &err)
585
586 if err = r.updateShallow(o, reader); err != nil {
587 return err
588 }
589
590 if err = packfile.UpdateObjectStorage(r.s,
591 buildSidebandIfSupported(req.Capabilities, reader, o.Progress),
592 ); err != nil {
593 return err
594 }
595
596 return err
597}
598
599func (r *Remote) pruneRemotes(specs []config.RefSpec, localRefs []*plumbing.Reference, remoteRefs memory.ReferenceStorage) (bool, error) {
600 var updatedPrune bool
601 for _, spec := range specs {
602 rev := spec.Reverse()
603 for _, ref := range localRefs {
604 if !rev.Match(ref.Name()) {
605 continue
606 }
607 _, err := remoteRefs.Reference(rev.Dst(ref.Name()))
608 if errors.Is(err, plumbing.ErrReferenceNotFound) {
609 updatedPrune = true
610 err := r.s.RemoveReference(ref.Name())
611 if err != nil {
612 return false, err
613 }
614 }
615 }
616 }
617 return updatedPrune, nil
618}
619
620func (r *Remote) addReferencesToUpdate(
621 refspecs []config.RefSpec,
622 localRefs []*plumbing.Reference,
623 remoteRefs storer.ReferenceStorer,
624 req *packp.ReferenceUpdateRequest,
625 prune bool,
626 forceWithLease *ForceWithLease,
627) error {
628 // This references dictionary will be used to search references by name.
629 refsDict := make(map[string]*plumbing.Reference)
630 for _, ref := range localRefs {
631 refsDict[ref.Name().String()] = ref
632 }
633
634 for _, rs := range refspecs {
635 if rs.IsDelete() {
636 if err := r.deleteReferences(rs, remoteRefs, refsDict, req, false); err != nil {
637 return err
638 }
639 } else {
640 err := r.addOrUpdateReferences(rs, localRefs, refsDict, remoteRefs, req, forceWithLease)
641 if err != nil {
642 return err
643 }
644
645 if prune {
646 if err := r.deleteReferences(rs, remoteRefs, refsDict, req, true); err != nil {
647 return err
648 }
649 }
650 }
651 }
652
653 return nil
654}
655
656func (r *Remote) addOrUpdateReferences(
657 rs config.RefSpec,
658 localRefs []*plumbing.Reference,
659 refsDict map[string]*plumbing.Reference,
660 remoteRefs storer.ReferenceStorer,
661 req *packp.ReferenceUpdateRequest,
662 forceWithLease *ForceWithLease,
663) error {
664 // If it is not a wildcard refspec we can directly search for the reference
665 // in the references dictionary.
666 if !rs.IsWildcard() {
667 ref, ok := refsDict[rs.Src()]
668 if !ok {
669 commit, err := object.GetCommit(r.s, plumbing.NewHash(rs.Src()))
670 if err == nil {
671 return r.addCommit(rs, remoteRefs, commit.Hash, req)
672 }
673 return nil
674 }
675
676 return r.addReferenceIfRefSpecMatches(rs, remoteRefs, ref, req, forceWithLease)
677 }
678
679 for _, ref := range localRefs {
680 err := r.addReferenceIfRefSpecMatches(rs, remoteRefs, ref, req, forceWithLease)
681 if err != nil {
682 return err
683 }
684 }
685
686 return nil
687}
688
689func (r *Remote) deleteReferences(rs config.RefSpec,
690 remoteRefs storer.ReferenceStorer,
691 refsDict map[string]*plumbing.Reference,
692 req *packp.ReferenceUpdateRequest,
693 prune bool) error {
694 iter, err := remoteRefs.IterReferences()
695 if err != nil {
696 return err
697 }
698
699 return iter.ForEach(func(ref *plumbing.Reference) error {
700 if ref.Type() != plumbing.HashReference {
701 return nil
702 }
703
704 if prune {
705 rs := rs.Reverse()
706 if !rs.Match(ref.Name()) {
707 return nil
708 }
709
710 if _, ok := refsDict[rs.Dst(ref.Name()).String()]; ok {
711 return nil
712 }
713 } else if rs.Dst("") != ref.Name() {
714 return nil
715 }
716
717 cmd := &packp.Command{
718 Name: ref.Name(),
719 Old: ref.Hash(),
720 New: plumbing.ZeroHash,
721 }
722 req.Commands = append(req.Commands, cmd)
723 return nil
724 })
725}
726
727func (r *Remote) addCommit(rs config.RefSpec,
728 remoteRefs storer.ReferenceStorer, localCommit plumbing.Hash,
729 req *packp.ReferenceUpdateRequest) error {
730
731 if rs.IsWildcard() {
732 return errors.New("can't use wildcard together with hash refspecs")
733 }
734
735 cmd := &packp.Command{
736 Name: rs.Dst(""),
737 Old: plumbing.ZeroHash,
738 New: localCommit,
739 }
740 remoteRef, err := remoteRefs.Reference(cmd.Name)
741 if err == nil {
742 if remoteRef.Type() != plumbing.HashReference {
743 // TODO: check actual git behavior here
744 return nil
745 }
746
747 cmd.Old = remoteRef.Hash()
748 } else if err != plumbing.ErrReferenceNotFound {
749 return err
750 }
751 if cmd.Old == cmd.New {
752 return nil
753 }
754 if !rs.IsForceUpdate() {
755 if err := checkFastForwardUpdate(r.s, remoteRefs, cmd); err != nil {
756 return err
757 }
758 }
759
760 req.Commands = append(req.Commands, cmd)
761 return nil
762}
763
764func (r *Remote) addReferenceIfRefSpecMatches(rs config.RefSpec,
765 remoteRefs storer.ReferenceStorer, localRef *plumbing.Reference,
766 req *packp.ReferenceUpdateRequest, forceWithLease *ForceWithLease) error {
767
768 if localRef.Type() != plumbing.HashReference {
769 return nil
770 }
771
772 if !rs.Match(localRef.Name()) {
773 return nil
774 }
775
776 cmd := &packp.Command{
777 Name: rs.Dst(localRef.Name()),
778 Old: plumbing.ZeroHash,
779 New: localRef.Hash(),
780 }
781
782 remoteRef, err := remoteRefs.Reference(cmd.Name)
783 if err == nil {
784 if remoteRef.Type() != plumbing.HashReference {
785 // TODO: check actual git behavior here
786 return nil
787 }
788
789 cmd.Old = remoteRef.Hash()
790 } else if err != plumbing.ErrReferenceNotFound {
791 return err
792 }
793
794 if cmd.Old == cmd.New {
795 return nil
796 }
797
798 if forceWithLease != nil {
799 if err = r.checkForceWithLease(localRef, cmd, forceWithLease); err != nil {
800 return err
801 }
802 } else if !rs.IsForceUpdate() {
803 if err := checkFastForwardUpdate(r.s, remoteRefs, cmd); err != nil {
804 return err
805 }
806 }
807
808 req.Commands = append(req.Commands, cmd)
809 return nil
810}
811
812func (r *Remote) checkForceWithLease(localRef *plumbing.Reference, cmd *packp.Command, forceWithLease *ForceWithLease) error {
813 remotePrefix := fmt.Sprintf("refs/remotes/%s/", r.Config().Name)
814
815 ref, err := storer.ResolveReference(
816 r.s,
817 plumbing.ReferenceName(remotePrefix+strings.Replace(localRef.Name().String(), "refs/heads/", "", -1)))
818 if err != nil {
819 return err
820 }
821
822 if forceWithLease.RefName.String() == "" || (forceWithLease.RefName == cmd.Name) {
823 expectedOID := ref.Hash()
824
825 if !forceWithLease.Hash.IsZero() {
826 expectedOID = forceWithLease.Hash
827 }
828
829 if cmd.Old != expectedOID {
830 return fmt.Errorf("non-fast-forward update: %s", cmd.Name.String())
831 }
832 }
833
834 return nil
835}
836
837func (r *Remote) references() ([]*plumbing.Reference, error) {
838 var localRefs []*plumbing.Reference
839
840 iter, err := r.s.IterReferences()
841 if err != nil {
842 return nil, err
843 }
844
845 for {
846 ref, err := iter.Next()
847 if err == io.EOF {
848 break
849 }
850
851 if err != nil {
852 return nil, err
853 }
854
855 localRefs = append(localRefs, ref)
856 }
857
858 return localRefs, nil
859}
860
861func getRemoteRefsFromStorer(remoteRefStorer storer.ReferenceStorer) (
862 map[plumbing.Hash]bool, error) {
863 remoteRefs := map[plumbing.Hash]bool{}
864 iter, err := remoteRefStorer.IterReferences()
865 if err != nil {
866 return nil, err
867 }
868 err = iter.ForEach(func(ref *plumbing.Reference) error {
869 if ref.Type() != plumbing.HashReference {
870 return nil
871 }
872 remoteRefs[ref.Hash()] = true
873 return nil
874 })
875 if err != nil {
876 return nil, err
877 }
878 return remoteRefs, nil
879}
880
881// getHavesFromRef populates the given `haves` map with the given
882// reference, and up to `maxHavesToVisitPerRef` ancestor commits.
883func getHavesFromRef(
884 ref *plumbing.Reference,
885 remoteRefs map[plumbing.Hash]bool,
886 s storage.Storer,
887 haves map[plumbing.Hash]bool,
888 depth int,
889) error {
890 h := ref.Hash()
891 if haves[h] {
892 return nil
893 }
894
895 commit, err := object.GetCommit(s, h)
896 if err != nil {
897 if !errors.Is(err, plumbing.ErrObjectNotFound) {
898 // Ignore the error if this isn't a commit.
899 haves[ref.Hash()] = true
900 }
901 return nil
902 }
903
904 // Until go-git supports proper commit negotiation during an
905 // upload pack request, include up to `maxHavesToVisitPerRef`
906 // commits from the history of each ref.
907 walker := object.NewCommitPreorderIter(commit, haves, nil)
908 toVisit := maxHavesToVisitPerRef
909 // But only need up to the requested depth
910 if depth > 0 && depth < maxHavesToVisitPerRef {
911 toVisit = depth
912 }
913 // It is safe to ignore any error here as we are just trying to find the references that we already have
914 // An example of a legitimate failure is we have a shallow clone and don't have the previous commit(s)
915 _ = walker.ForEach(func(c *object.Commit) error {
916 haves[c.Hash] = true
917 toVisit--
918 // If toVisit starts out at 0 (indicating there is no
919 // max), then it will be negative here and we won't stop
920 // early.
921 if toVisit == 0 || remoteRefs[c.Hash] {
922 return storer.ErrStop
923 }
924 return nil
925 })
926
927 return nil
928}
929
930func getHaves(
931 localRefs []*plumbing.Reference,
932 remoteRefStorer storer.ReferenceStorer,
933 s storage.Storer,
934 depth int,
935) ([]plumbing.Hash, error) {
936 haves := map[plumbing.Hash]bool{}
937
938 // Build a map of all the remote references, to avoid loading too
939 // many parent commits for references we know don't need to be
940 // transferred.
941 remoteRefs, err := getRemoteRefsFromStorer(remoteRefStorer)
942 if err != nil {
943 return nil, err
944 }
945
946 for _, ref := range localRefs {
947 if haves[ref.Hash()] {
948 continue
949 }
950
951 if ref.Type() != plumbing.HashReference {
952 continue
953 }
954
955 err = getHavesFromRef(ref, remoteRefs, s, haves, depth)
956 if err != nil {
957 return nil, err
958 }
959 }
960
961 var result []plumbing.Hash
962 for h := range haves {
963 result = append(result, h)
964 }
965
966 return result, nil
967}
968
969const refspecAllTags = "+refs/tags/*:refs/tags/*"
970
971func calculateRefs(
972 spec []config.RefSpec,
973 remoteRefs storer.ReferenceStorer,
974 tagMode TagMode,
975) (memory.ReferenceStorage, [][]*plumbing.Reference, error) {
976 if tagMode == AllTags {
977 spec = append(spec, refspecAllTags)
978 }
979
980 refs := make(memory.ReferenceStorage)
981 // list of references matched for each spec
982 specToRefs := make([][]*plumbing.Reference, len(spec))
983 for i := range spec {
984 var err error
985 specToRefs[i], err = doCalculateRefs(spec[i], remoteRefs, refs)
986 if err != nil {
987 return nil, nil, err
988 }
989 }
990
991 return refs, specToRefs, nil
992}
993
994func doCalculateRefs(
995 s config.RefSpec,
996 remoteRefs storer.ReferenceStorer,
997 refs memory.ReferenceStorage,
998) ([]*plumbing.Reference, error) {
999 var refList []*plumbing.Reference
1000
1001 if s.IsExactSHA1() {
1002 ref := plumbing.NewHashReference(s.Dst(""), plumbing.NewHash(s.Src()))
1003
1004 refList = append(refList, ref)
1005 return refList, refs.SetReference(ref)
1006 }
1007
1008 var matched bool
1009 onMatched := func(ref *plumbing.Reference) error {
1010 if ref.Type() == plumbing.SymbolicReference {
1011 target, err := storer.ResolveReference(remoteRefs, ref.Name())
1012 if err != nil {
1013 return err
1014 }
1015
1016 ref = plumbing.NewHashReference(ref.Name(), target.Hash())
1017 }
1018
1019 if ref.Type() != plumbing.HashReference {
1020 return nil
1021 }
1022
1023 matched = true
1024 refList = append(refList, ref)
1025 return refs.SetReference(ref)
1026 }
1027
1028 var ret error
1029 if s.IsWildcard() {
1030 iter, err := remoteRefs.IterReferences()
1031 if err != nil {
1032 return nil, err
1033 }
1034 ret = iter.ForEach(func(ref *plumbing.Reference) error {
1035 if !s.Match(ref.Name()) {
1036 return nil
1037 }
1038
1039 return onMatched(ref)
1040 })
1041 } else {
1042 var resolvedRef *plumbing.Reference
1043 src := s.Src()
1044 resolvedRef, ret = expand_ref(remoteRefs, plumbing.ReferenceName(src))
1045 if ret == nil {
1046 ret = onMatched(resolvedRef)
1047 }
1048 }
1049
1050 if !matched && !s.IsWildcard() {
1051 return nil, NoMatchingRefSpecError{refSpec: s}
1052 }
1053
1054 return refList, ret
1055}
1056
1057func getWants(localStorer storage.Storer, refs memory.ReferenceStorage, depth int) ([]plumbing.Hash, error) {
1058 // If depth is anything other than 1 and the repo has shallow commits then just because we have the commit
1059 // at the reference doesn't mean that we don't still need to fetch the parents
1060 shallow := false
1061 if depth != 1 {
1062 if s, _ := localStorer.Shallow(); len(s) > 0 {
1063 shallow = true
1064 }
1065 }
1066
1067 wants := map[plumbing.Hash]bool{}
1068 for _, ref := range refs {
1069 hash := ref.Hash()
1070 exists, err := objectExists(localStorer, ref.Hash())
1071 if err != nil {
1072 return nil, err
1073 }
1074
1075 if !exists || shallow {
1076 wants[hash] = true
1077 }
1078 }
1079
1080 var result []plumbing.Hash
1081 for h := range wants {
1082 result = append(result, h)
1083 }
1084
1085 return result, nil
1086}
1087
1088func objectExists(s storer.EncodedObjectStorer, h plumbing.Hash) (bool, error) {
1089 _, err := s.EncodedObject(plumbing.AnyObject, h)
1090 if err == plumbing.ErrObjectNotFound {
1091 return false, nil
1092 }
1093
1094 return true, err
1095}
1096
1097func checkFastForwardUpdate(s storer.EncodedObjectStorer, remoteRefs storer.ReferenceStorer, cmd *packp.Command) error {
1098 if cmd.Old == plumbing.ZeroHash {
1099 _, err := remoteRefs.Reference(cmd.Name)
1100 if err == plumbing.ErrReferenceNotFound {
1101 return nil
1102 }
1103
1104 if err != nil {
1105 return err
1106 }
1107
1108 return fmt.Errorf("non-fast-forward update: %s", cmd.Name.String())
1109 }
1110
1111 ff, err := isFastForward(s, cmd.Old, cmd.New, nil)
1112 if err != nil {
1113 return err
1114 }
1115
1116 if !ff {
1117 return fmt.Errorf("non-fast-forward update: %s", cmd.Name.String())
1118 }
1119
1120 return nil
1121}
1122
1123func isFastForward(s storer.EncodedObjectStorer, old, new plumbing.Hash, earliestShallow *plumbing.Hash) (bool, error) {
1124 c, err := object.GetCommit(s, new)
1125 if err != nil {
1126 return false, err
1127 }
1128
1129 parentsToIgnore := []plumbing.Hash{}
1130 if earliestShallow != nil {
1131 earliestCommit, err := object.GetCommit(s, *earliestShallow)
1132 if err != nil {
1133 return false, err
1134 }
1135
1136 parentsToIgnore = earliestCommit.ParentHashes
1137 }
1138
1139 found := false
1140 // stop iterating at the earliest shallow commit, ignoring its parents
1141 // note: when pull depth is smaller than the number of new changes on the remote, this fails due to missing parents.
1142 // as far as i can tell, without the commits in-between the shallow pull and the earliest shallow, there's no
1143 // real way of telling whether it will be a fast-forward merge.
1144 iter := object.NewCommitPreorderIter(c, nil, parentsToIgnore)
1145 err = iter.ForEach(func(c *object.Commit) error {
1146 if c.Hash != old {
1147 return nil
1148 }
1149
1150 found = true
1151 return storer.ErrStop
1152 })
1153 return found, err
1154}
1155
1156func (r *Remote) newUploadPackRequest(o *FetchOptions,
1157 ar *packp.AdvRefs) (*packp.UploadPackRequest, error) {
1158
1159 req := packp.NewUploadPackRequestFromCapabilities(ar.Capabilities)
1160
1161 if o.Depth != 0 {
1162 req.Depth = packp.DepthCommits(o.Depth)
1163 if err := req.Capabilities.Set(capability.Shallow); err != nil {
1164 return nil, err
1165 }
1166 }
1167
1168 if o.Progress == nil && ar.Capabilities.Supports(capability.NoProgress) {
1169 if err := req.Capabilities.Set(capability.NoProgress); err != nil {
1170 return nil, err
1171 }
1172 }
1173
1174 isWildcard := true
1175 for _, s := range o.RefSpecs {
1176 if !s.IsWildcard() {
1177 isWildcard = false
1178 break
1179 }
1180 }
1181
1182 if isWildcard && o.Tags == TagFollowing && ar.Capabilities.Supports(capability.IncludeTag) {
1183 if err := req.Capabilities.Set(capability.IncludeTag); err != nil {
1184 return nil, err
1185 }
1186 }
1187
1188 return req, nil
1189}
1190
1191func (r *Remote) isSupportedRefSpec(refs []config.RefSpec, ar *packp.AdvRefs) error {
1192 var containsIsExact bool
1193 for _, ref := range refs {
1194 if ref.IsExactSHA1() {
1195 containsIsExact = true
1196 }
1197 }
1198
1199 if !containsIsExact {
1200 return nil
1201 }
1202
1203 if ar.Capabilities.Supports(capability.AllowReachableSHA1InWant) ||
1204 ar.Capabilities.Supports(capability.AllowTipSHA1InWant) {
1205 return nil
1206 }
1207
1208 return ErrExactSHA1NotSupported
1209}
1210
1211func buildSidebandIfSupported(l *capability.List, reader io.Reader, p sideband.Progress) io.Reader {
1212 var t sideband.Type
1213
1214 switch {
1215 case l.Supports(capability.Sideband):
1216 t = sideband.Sideband
1217 case l.Supports(capability.Sideband64k):
1218 t = sideband.Sideband64k
1219 default:
1220 return reader
1221 }
1222
1223 d := sideband.NewDemuxer(t, reader)
1224 d.Progress = p
1225
1226 return d
1227}
1228
1229func (r *Remote) updateLocalReferenceStorage(
1230 specs []config.RefSpec,
1231 fetchedRefs, remoteRefs memory.ReferenceStorage,
1232 specToRefs [][]*plumbing.Reference,
1233 tagMode TagMode,
1234 force bool,
1235) (updated bool, err error) {
1236 isWildcard := true
1237 forceNeeded := false
1238
1239 for i, spec := range specs {
1240 if !spec.IsWildcard() {
1241 isWildcard = false
1242 }
1243
1244 for _, ref := range specToRefs[i] {
1245 if ref.Type() != plumbing.HashReference {
1246 continue
1247 }
1248
1249 localName := spec.Dst(ref.Name())
1250 // If localName doesn't start with "refs/" then treat as a branch.
1251 if !strings.HasPrefix(localName.String(), "refs/") {
1252 localName = plumbing.NewBranchReferenceName(localName.String())
1253 }
1254 old, _ := storer.ResolveReference(r.s, localName)
1255 new := plumbing.NewHashReference(localName, ref.Hash())
1256
1257 // If the ref exists locally as a non-tag and force is not
1258 // specified, only update if the new ref is an ancestor of the old
1259 if old != nil && !old.Name().IsTag() && !force && !spec.IsForceUpdate() {
1260 ff, err := isFastForward(r.s, old.Hash(), new.Hash(), nil)
1261 if err != nil {
1262 return updated, err
1263 }
1264
1265 if !ff {
1266 forceNeeded = true
1267 continue
1268 }
1269 }
1270
1271 refUpdated, err := checkAndUpdateReferenceStorerIfNeeded(r.s, new, old)
1272 if err != nil {
1273 return updated, err
1274 }
1275
1276 if refUpdated {
1277 updated = true
1278 }
1279 }
1280 }
1281
1282 if tagMode == NoTags {
1283 return updated, nil
1284 }
1285
1286 tags := fetchedRefs
1287 if isWildcard {
1288 tags = remoteRefs
1289 }
1290 tagUpdated, err := r.buildFetchedTags(tags)
1291 if err != nil {
1292 return updated, err
1293 }
1294
1295 if tagUpdated {
1296 updated = true
1297 }
1298
1299 if forceNeeded {
1300 err = ErrForceNeeded
1301 }
1302
1303 return
1304}
1305
1306func (r *Remote) buildFetchedTags(refs memory.ReferenceStorage) (updated bool, err error) {
1307 for _, ref := range refs {
1308 if !ref.Name().IsTag() {
1309 continue
1310 }
1311
1312 _, err := r.s.EncodedObject(plumbing.AnyObject, ref.Hash())
1313 if err == plumbing.ErrObjectNotFound {
1314 continue
1315 }
1316
1317 if err != nil {
1318 return false, err
1319 }
1320
1321 refUpdated, err := updateReferenceStorerIfNeeded(r.s, ref)
1322 if err != nil {
1323 return updated, err
1324 }
1325
1326 if refUpdated {
1327 updated = true
1328 }
1329 }
1330
1331 return
1332}
1333
1334// List the references on the remote repository.
1335// The provided Context must be non-nil. If the context expires before the
1336// operation is complete, an error is returned. The context only affects to the
1337// transport operations.
1338func (r *Remote) ListContext(ctx context.Context, o *ListOptions) (rfs []*plumbing.Reference, err error) {
1339 return r.list(ctx, o)
1340}
1341
1342func (r *Remote) List(o *ListOptions) (rfs []*plumbing.Reference, err error) {
1343 timeout := o.Timeout
1344 // Default to the old hardcoded 10s value if a timeout is not explicitly set.
1345 if timeout == 0 {
1346 timeout = 10
1347 }
1348 if timeout < 0 {
1349 return nil, fmt.Errorf("invalid timeout: %d", timeout)
1350 }
1351 ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
1352 defer cancel()
1353 return r.ListContext(ctx, o)
1354}
1355
1356func (r *Remote) list(ctx context.Context, o *ListOptions) (rfs []*plumbing.Reference, err error) {
1357 if r.c == nil || len(r.c.URLs) == 0 {
1358 return nil, ErrEmptyUrls
1359 }
1360
1361 s, err := newUploadPackSession(r.c.URLs[0], o.Auth, o.InsecureSkipTLS, o.ClientCert, o.ClientKey, o.CABundle, o.ProxyOptions)
1362 if err != nil {
1363 return nil, err
1364 }
1365
1366 defer ioutil.CheckClose(s, &err)
1367
1368 ar, err := s.AdvertisedReferencesContext(ctx)
1369 if err != nil {
1370 return nil, err
1371 }
1372
1373 allRefs, err := ar.AllReferences()
1374 if err != nil {
1375 return nil, err
1376 }
1377
1378 refs, err := allRefs.IterReferences()
1379 if err != nil {
1380 return nil, err
1381 }
1382
1383 var resultRefs []*plumbing.Reference
1384 if o.PeelingOption == AppendPeeled || o.PeelingOption == IgnorePeeled {
1385 err = refs.ForEach(func(ref *plumbing.Reference) error {
1386 resultRefs = append(resultRefs, ref)
1387 return nil
1388 })
1389 if err != nil {
1390 return nil, err
1391 }
1392 }
1393
1394 if o.PeelingOption == AppendPeeled || o.PeelingOption == OnlyPeeled {
1395 for k, v := range ar.Peeled {
1396 resultRefs = append(resultRefs, plumbing.NewReferenceFromStrings(k+"^{}", v.String()))
1397 }
1398 }
1399
1400 return resultRefs, nil
1401}
1402
1403func objectsToPush(commands []*packp.Command) []plumbing.Hash {
1404 objects := make([]plumbing.Hash, 0, len(commands))
1405 for _, cmd := range commands {
1406 if cmd.New == plumbing.ZeroHash {
1407 continue
1408 }
1409 objects = append(objects, cmd.New)
1410 }
1411 return objects
1412}
1413
1414func referencesToHashes(refs storer.ReferenceStorer) ([]plumbing.Hash, error) {
1415 iter, err := refs.IterReferences()
1416 if err != nil {
1417 return nil, err
1418 }
1419
1420 var hs []plumbing.Hash
1421 err = iter.ForEach(func(ref *plumbing.Reference) error {
1422 if ref.Type() != plumbing.HashReference {
1423 return nil
1424 }
1425
1426 hs = append(hs, ref.Hash())
1427 return nil
1428 })
1429 if err != nil {
1430 return nil, err
1431 }
1432
1433 return hs, nil
1434}
1435
1436func pushHashes(
1437 ctx context.Context,
1438 sess transport.ReceivePackSession,
1439 s storage.Storer,
1440 req *packp.ReferenceUpdateRequest,
1441 hs []plumbing.Hash,
1442 useRefDeltas bool,
1443 allDelete bool,
1444) (*packp.ReportStatus, error) {
1445 rd, wr := io.Pipe()
1446
1447 config, err := s.Config()
1448 if err != nil {
1449 return nil, err
1450 }
1451
1452 // Set buffer size to 1 so the error message can be written when
1453 // ReceivePack fails. Otherwise the goroutine will be blocked writing
1454 // to the channel.
1455 done := make(chan error, 1)
1456
1457 if !allDelete {
1458 req.Packfile = rd
1459 go func() {
1460 e := packfile.NewEncoder(wr, s, useRefDeltas)
1461 if _, err := e.Encode(hs, config.Pack.Window); err != nil {
1462 done <- wr.CloseWithError(err)
1463 return
1464 }
1465
1466 done <- wr.Close()
1467 }()
1468 } else {
1469 close(done)
1470 }
1471
1472 rs, err := sess.ReceivePack(ctx, req)
1473 if err != nil {
1474 // close the pipe to unlock encode write
1475 _ = rd.Close()
1476 return nil, err
1477 }
1478
1479 if err := <-done; err != nil {
1480 return nil, err
1481 }
1482
1483 return rs, nil
1484}
1485
1486func (r *Remote) updateShallow(o *FetchOptions, resp *packp.UploadPackResponse) error {
1487 if o.Depth == 0 || len(resp.Shallows) == 0 {
1488 return nil
1489 }
1490
1491 shallows, err := r.s.Shallow()
1492 if err != nil {
1493 return err
1494 }
1495
1496outer:
1497 for _, s := range resp.Shallows {
1498 for _, oldS := range shallows {
1499 if s == oldS {
1500 continue outer
1501 }
1502 }
1503 shallows = append(shallows, s)
1504 }
1505
1506 return r.s.SetShallow(shallows)
1507}
1508
1509func (r *Remote) checkRequireRemoteRefs(requires []config.RefSpec, remoteRefs storer.ReferenceStorer) error {
1510 for _, require := range requires {
1511 if require.IsWildcard() {
1512 return fmt.Errorf("wildcards not supported in RequireRemoteRefs, got %s", require.String())
1513 }
1514
1515 name := require.Dst("")
1516 remote, err := remoteRefs.Reference(name)
1517 if err != nil {
1518 return fmt.Errorf("remote ref %s required to be %s but is absent", name.String(), require.Src())
1519 }
1520
1521 var requireHash string
1522 if require.IsExactSHA1() {
1523 requireHash = require.Src()
1524 } else {
1525 target, err := storer.ResolveReference(remoteRefs, plumbing.ReferenceName(require.Src()))
1526 if err != nil {
1527 return fmt.Errorf("could not resolve ref %s in RequireRemoteRefs", require.Src())
1528 }
1529 requireHash = target.Hash().String()
1530 }
1531
1532 if remote.Hash().String() != requireHash {
1533 return fmt.Errorf("remote ref %s required to be %s but is %s", name.String(), requireHash, remote.Hash().String())
1534 }
1535 }
1536 return nil
1537}