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/storage"
26 "github.com/go-git/go-git/v5/storage/filesystem"
27 "github.com/go-git/go-git/v5/storage/memory"
28 "github.com/go-git/go-git/v5/utils/ioutil"
29)
30
31var (
32 NoErrAlreadyUpToDate = errors.New("already up-to-date")
33 ErrDeleteRefNotSupported = errors.New("server does not support delete-refs")
34 ErrForceNeeded = errors.New("some refs were not updated")
35 ErrExactSHA1NotSupported = errors.New("server does not support exact SHA1 refspec")
36 ErrEmptyUrls = errors.New("URLs cannot be empty")
37 ErrFilterNotSupported = errors.New("server does not support filters")
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.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, osfs.WithBoundOS()), 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 return nil, err
339 }
340
341 if o.FollowTags {
342 if err := r.addReachableTags(localRefs, remoteRefs, req); err != nil {
343 return nil, err
344 }
345 }
346
347 return req, nil
348}
349
350func (r *Remote) updateRemoteReferenceStorage(
351 req *packp.ReferenceUpdateRequest,
352) error {
353 for _, spec := range r.c.Fetch {
354 for _, c := range req.Commands {
355 if !spec.Match(c.Name) {
356 continue
357 }
358
359 local := spec.Dst(c.Name)
360 ref := plumbing.NewHashReference(local, c.New)
361 switch c.Action() {
362 case packp.Create, packp.Update:
363 if err := r.s.SetReference(ref); err != nil {
364 return err
365 }
366 case packp.Delete:
367 if err := r.s.RemoveReference(local); err != nil {
368 return err
369 }
370 }
371 }
372 }
373
374 return nil
375}
376
377// FetchContext fetches references along with the objects necessary to complete
378// their histories.
379//
380// Returns nil if the operation is successful, NoErrAlreadyUpToDate if there are
381// no changes to be fetched, or an error.
382//
383// The provided Context must be non-nil. If the context expires before the
384// operation is complete, an error is returned. The context only affects the
385// transport operations.
386func (r *Remote) FetchContext(ctx context.Context, o *FetchOptions) error {
387 _, err := r.fetch(ctx, o)
388 return err
389}
390
391// Fetch fetches references along with the objects necessary to complete their
392// histories.
393//
394// Returns nil if the operation is successful, NoErrAlreadyUpToDate if there are
395// no changes to be fetched, or an error.
396func (r *Remote) Fetch(o *FetchOptions) error {
397 return r.FetchContext(context.Background(), o)
398}
399
400func (r *Remote) fetch(ctx context.Context, o *FetchOptions) (sto storer.ReferenceStorer, err error) {
401 if o.RemoteName == "" {
402 o.RemoteName = r.c.Name
403 }
404
405 if err = o.Validate(); err != nil {
406 return nil, err
407 }
408
409 if len(o.RefSpecs) == 0 {
410 o.RefSpecs = r.c.Fetch
411 }
412
413 if o.RemoteURL == "" {
414 o.RemoteURL = r.c.URLs[0]
415 }
416
417 s, err := newUploadPackSession(o.RemoteURL, o.Auth, o.InsecureSkipTLS, o.CABundle, o.ProxyOptions)
418 if err != nil {
419 return nil, err
420 }
421
422 defer ioutil.CheckClose(s, &err)
423
424 ar, err := s.AdvertisedReferencesContext(ctx)
425 if err != nil {
426 return nil, err
427 }
428
429 req, err := r.newUploadPackRequest(o, ar)
430 if err != nil {
431 return nil, err
432 }
433
434 if err := r.isSupportedRefSpec(o.RefSpecs, ar); err != nil {
435 return nil, err
436 }
437
438 remoteRefs, err := ar.AllReferences()
439 if err != nil {
440 return nil, err
441 }
442
443 localRefs, err := r.references()
444 if err != nil {
445 return nil, err
446 }
447
448 refs, specToRefs, err := calculateRefs(o.RefSpecs, remoteRefs, o.Tags)
449 if err != nil {
450 return nil, err
451 }
452
453 if !req.Depth.IsZero() {
454 req.Shallows, err = r.s.Shallow()
455 if err != nil {
456 return nil, fmt.Errorf("existing checkout is not shallow")
457 }
458 }
459
460 req.Wants, err = getWants(r.s, refs, o.Depth)
461 if len(req.Wants) > 0 {
462 req.Haves, err = getHaves(localRefs, remoteRefs, r.s, o.Depth)
463 if err != nil {
464 return nil, err
465 }
466
467 if err = r.fetchPack(ctx, o, s, req); err != nil {
468 return nil, err
469 }
470 }
471
472 var updatedPrune bool
473 if o.Prune {
474 updatedPrune, err = r.pruneRemotes(o.RefSpecs, localRefs, remoteRefs)
475 if err != nil {
476 return nil, err
477 }
478 }
479
480 updated, err := r.updateLocalReferenceStorage(o.RefSpecs, refs, remoteRefs, specToRefs, o.Tags, o.Force)
481 if err != nil {
482 return nil, err
483 }
484
485 if !updated {
486 updated, err = depthChanged(req.Shallows, r.s)
487 if err != nil {
488 return nil, fmt.Errorf("error checking depth change: %v", err)
489 }
490 }
491
492 if !updated && !updatedPrune {
493 // No references updated, but may have fetched new objects, check if we now have any of our wants
494 for _, hash := range req.Wants {
495 exists, _ := objectExists(r.s, hash)
496 if exists {
497 updated = true
498 break
499 }
500 }
501
502 if !updated {
503 return remoteRefs, NoErrAlreadyUpToDate
504 }
505 }
506
507 return remoteRefs, nil
508}
509
510func depthChanged(before []plumbing.Hash, s storage.Storer) (bool, error) {
511 after, err := s.Shallow()
512 if err != nil {
513 return false, err
514 }
515
516 if len(before) != len(after) {
517 return true, nil
518 }
519
520 bm := make(map[plumbing.Hash]bool, len(before))
521 for _, b := range before {
522 bm[b] = true
523 }
524 for _, a := range after {
525 if _, ok := bm[a]; !ok {
526 return true, nil
527 }
528 }
529
530 return false, nil
531}
532
533func newUploadPackSession(url string, auth transport.AuthMethod, insecure bool, cabundle []byte, proxyOpts transport.ProxyOptions) (transport.UploadPackSession, error) {
534 c, ep, err := newClient(url, insecure, cabundle, proxyOpts)
535 if err != nil {
536 return nil, err
537 }
538
539 return c.NewUploadPackSession(ep, auth)
540}
541
542func newSendPackSession(url string, auth transport.AuthMethod, insecure bool, cabundle []byte, proxyOpts transport.ProxyOptions) (transport.ReceivePackSession, error) {
543 c, ep, err := newClient(url, insecure, cabundle, proxyOpts)
544 if err != nil {
545 return nil, err
546 }
547
548 return c.NewReceivePackSession(ep, auth)
549}
550
551func newClient(url string, insecure bool, cabundle []byte, proxyOpts transport.ProxyOptions) (transport.Transport, *transport.Endpoint, error) {
552 ep, err := transport.NewEndpoint(url)
553 if err != nil {
554 return nil, nil, err
555 }
556 ep.InsecureSkipTLS = insecure
557 ep.CaBundle = cabundle
558 ep.Proxy = proxyOpts
559
560 c, err := transport.Get(ep.Protocol)
561 if err != nil {
562 return nil, nil, err
563 }
564
565 return c, ep, err
566}
567
568func (r *Remote) fetchPack(ctx context.Context, o *FetchOptions, s transport.UploadPackSession,
569 req *packp.UploadPackRequest,
570) (err error) {
571 reader, err := s.UploadPack(ctx, req)
572 if err != nil {
573 if errors.Is(err, transport.ErrEmptyUploadPackRequest) {
574 // XXX: no packfile provided, everything is up-to-date.
575 return nil
576 }
577 return err
578 }
579
580 defer ioutil.CheckClose(reader, &err)
581
582 if err = r.updateShallow(o, reader); err != nil {
583 return err
584 }
585
586 if err = packfile.UpdateObjectStorage(r.s,
587 buildSidebandIfSupported(req.Capabilities, reader, o.Progress),
588 ); err != nil {
589 return err
590 }
591
592 return err
593}
594
595func (r *Remote) pruneRemotes(specs []config.RefSpec, localRefs []*plumbing.Reference, remoteRefs memory.ReferenceStorage) (bool, error) {
596 var updatedPrune bool
597 for _, spec := range specs {
598 rev := spec.Reverse()
599 for _, ref := range localRefs {
600 if !rev.Match(ref.Name()) {
601 continue
602 }
603 _, err := remoteRefs.Reference(rev.Dst(ref.Name()))
604 if errors.Is(err, plumbing.ErrReferenceNotFound) {
605 updatedPrune = true
606 err := r.s.RemoveReference(ref.Name())
607 if err != nil {
608 return false, err
609 }
610 }
611 }
612 }
613 return updatedPrune, nil
614}
615
616func (r *Remote) addReferencesToUpdate(
617 refspecs []config.RefSpec,
618 localRefs []*plumbing.Reference,
619 remoteRefs storer.ReferenceStorer,
620 req *packp.ReferenceUpdateRequest,
621 prune bool,
622 forceWithLease *ForceWithLease,
623) error {
624 // This references dictionary will be used to search references by name.
625 refsDict := make(map[string]*plumbing.Reference)
626 for _, ref := range localRefs {
627 refsDict[ref.Name().String()] = ref
628 }
629
630 for _, rs := range refspecs {
631 if rs.IsDelete() {
632 if err := r.deleteReferences(rs, remoteRefs, refsDict, req, false); err != nil {
633 return err
634 }
635 } else {
636 err := r.addOrUpdateReferences(rs, localRefs, refsDict, remoteRefs, req, forceWithLease)
637 if err != nil {
638 return err
639 }
640
641 if prune {
642 if err := r.deleteReferences(rs, remoteRefs, refsDict, req, true); err != nil {
643 return err
644 }
645 }
646 }
647 }
648
649 return nil
650}
651
652func (r *Remote) addOrUpdateReferences(
653 rs config.RefSpec,
654 localRefs []*plumbing.Reference,
655 refsDict map[string]*plumbing.Reference,
656 remoteRefs storer.ReferenceStorer,
657 req *packp.ReferenceUpdateRequest,
658 forceWithLease *ForceWithLease,
659) error {
660 // If it is not a wildcard refspec we can directly search for the reference
661 // in the references dictionary.
662 if !rs.IsWildcard() {
663 ref, ok := refsDict[rs.Src()]
664 if !ok {
665 commit, err := object.GetCommit(r.s, plumbing.NewHash(rs.Src()))
666 if err == nil {
667 return r.addCommit(rs, remoteRefs, commit.Hash, req)
668 }
669 return nil
670 }
671
672 return r.addReferenceIfRefSpecMatches(rs, remoteRefs, ref, req, forceWithLease)
673 }
674
675 for _, ref := range localRefs {
676 err := r.addReferenceIfRefSpecMatches(rs, remoteRefs, ref, req, forceWithLease)
677 if err != nil {
678 return err
679 }
680 }
681
682 return nil
683}
684
685func (r *Remote) deleteReferences(rs config.RefSpec,
686 remoteRefs storer.ReferenceStorer,
687 refsDict map[string]*plumbing.Reference,
688 req *packp.ReferenceUpdateRequest,
689 prune bool,
690) error {
691 iter, err := remoteRefs.IterReferences()
692 if err != nil {
693 return err
694 }
695
696 return iter.ForEach(func(ref *plumbing.Reference) error {
697 if ref.Type() != plumbing.HashReference {
698 return nil
699 }
700
701 if prune {
702 rs := rs.Reverse()
703 if !rs.Match(ref.Name()) {
704 return nil
705 }
706
707 if _, ok := refsDict[rs.Dst(ref.Name()).String()]; ok {
708 return nil
709 }
710 } else if rs.Dst("") != ref.Name() {
711 return nil
712 }
713
714 cmd := &packp.Command{
715 Name: ref.Name(),
716 Old: ref.Hash(),
717 New: plumbing.ZeroHash,
718 }
719 req.Commands = append(req.Commands, cmd)
720 return nil
721 })
722}
723
724func (r *Remote) addCommit(rs config.RefSpec,
725 remoteRefs storer.ReferenceStorer, localCommit plumbing.Hash,
726 req *packp.ReferenceUpdateRequest,
727) error {
728 if rs.IsWildcard() {
729 return errors.New("can't use wildcard together with hash refspecs")
730 }
731
732 cmd := &packp.Command{
733 Name: rs.Dst(""),
734 Old: plumbing.ZeroHash,
735 New: localCommit,
736 }
737 remoteRef, err := remoteRefs.Reference(cmd.Name)
738 if err == nil {
739 if remoteRef.Type() != plumbing.HashReference {
740 // TODO: check actual git behavior here
741 return nil
742 }
743
744 cmd.Old = remoteRef.Hash()
745 } else if err != plumbing.ErrReferenceNotFound {
746 return err
747 }
748 if cmd.Old == cmd.New {
749 return nil
750 }
751 if !rs.IsForceUpdate() {
752 if err := checkFastForwardUpdate(r.s, remoteRefs, cmd); err != nil {
753 return err
754 }
755 }
756
757 req.Commands = append(req.Commands, cmd)
758 return nil
759}
760
761func (r *Remote) addReferenceIfRefSpecMatches(rs config.RefSpec,
762 remoteRefs storer.ReferenceStorer, localRef *plumbing.Reference,
763 req *packp.ReferenceUpdateRequest, forceWithLease *ForceWithLease,
764) error {
765 if localRef.Type() != plumbing.HashReference {
766 return nil
767 }
768
769 if !rs.Match(localRef.Name()) {
770 return nil
771 }
772
773 cmd := &packp.Command{
774 Name: rs.Dst(localRef.Name()),
775 Old: plumbing.ZeroHash,
776 New: localRef.Hash(),
777 }
778
779 remoteRef, err := remoteRefs.Reference(cmd.Name)
780 if err == nil {
781 if remoteRef.Type() != plumbing.HashReference {
782 // TODO: check actual git behavior here
783 return nil
784 }
785
786 cmd.Old = remoteRef.Hash()
787 } else if err != plumbing.ErrReferenceNotFound {
788 return err
789 }
790
791 if cmd.Old == cmd.New {
792 return nil
793 }
794
795 if forceWithLease != nil {
796 if err = r.checkForceWithLease(localRef, cmd, forceWithLease); err != nil {
797 return err
798 }
799 } else if !rs.IsForceUpdate() {
800 if err := checkFastForwardUpdate(r.s, remoteRefs, cmd); err != nil {
801 return err
802 }
803 }
804
805 req.Commands = append(req.Commands, cmd)
806 return nil
807}
808
809func (r *Remote) checkForceWithLease(localRef *plumbing.Reference, cmd *packp.Command, forceWithLease *ForceWithLease) error {
810 remotePrefix := fmt.Sprintf("refs/remotes/%s/", r.Config().Name)
811
812 ref, err := storer.ResolveReference(
813 r.s,
814 plumbing.ReferenceName(remotePrefix+strings.Replace(localRef.Name().String(), "refs/heads/", "", -1)))
815 if err != nil {
816 return err
817 }
818
819 if forceWithLease.RefName.String() == "" || (forceWithLease.RefName == cmd.Name) {
820 expectedOID := ref.Hash()
821
822 if !forceWithLease.Hash.IsZero() {
823 expectedOID = forceWithLease.Hash
824 }
825
826 if cmd.Old != expectedOID {
827 return fmt.Errorf("non-fast-forward update: %s", cmd.Name.String())
828 }
829 }
830
831 return nil
832}
833
834func (r *Remote) references() ([]*plumbing.Reference, error) {
835 var localRefs []*plumbing.Reference
836
837 iter, err := r.s.IterReferences()
838 if err != nil {
839 return nil, err
840 }
841
842 for {
843 ref, err := iter.Next()
844 if err == io.EOF {
845 break
846 }
847
848 if err != nil {
849 return nil, err
850 }
851
852 localRefs = append(localRefs, ref)
853 }
854
855 return localRefs, nil
856}
857
858func getRemoteRefsFromStorer(remoteRefStorer storer.ReferenceStorer) (
859 map[plumbing.Hash]bool, error,
860) {
861 remoteRefs := map[plumbing.Hash]bool{}
862 iter, err := remoteRefStorer.IterReferences()
863 if err != nil {
864 return nil, err
865 }
866 err = iter.ForEach(func(ref *plumbing.Reference) error {
867 if ref.Type() != plumbing.HashReference {
868 return nil
869 }
870 remoteRefs[ref.Hash()] = true
871 return nil
872 })
873 if err != nil {
874 return nil, err
875 }
876 return remoteRefs, nil
877}
878
879// getHavesFromRef populates the given `haves` map with the given
880// reference, and up to `maxHavesToVisitPerRef` ancestor commits.
881func getHavesFromRef(
882 ref *plumbing.Reference,
883 remoteRefs map[plumbing.Hash]bool,
884 s storage.Storer,
885 haves map[plumbing.Hash]bool,
886 depth int,
887) error {
888 h := ref.Hash()
889 if haves[h] {
890 return nil
891 }
892
893 commit, err := object.GetCommit(s, h)
894 if err != nil {
895 if !errors.Is(err, plumbing.ErrObjectNotFound) {
896 // Ignore the error if this isn't a commit.
897 haves[ref.Hash()] = true
898 }
899 return nil
900 }
901
902 // Until go-git supports proper commit negotiation during an
903 // upload pack request, include up to `maxHavesToVisitPerRef`
904 // commits from the history of each ref.
905 walker := object.NewCommitPreorderIter(commit, haves, nil)
906 toVisit := maxHavesToVisitPerRef
907 // But only need up to the requested depth
908 if depth > 0 && depth < maxHavesToVisitPerRef {
909 toVisit = depth
910 }
911 // It is safe to ignore any error here as we are just trying to find the references that we already have
912 // An example of a legitimate failure is we have a shallow clone and don't have the previous commit(s)
913 _ = walker.ForEach(func(c *object.Commit) error {
914 haves[c.Hash] = true
915 toVisit--
916 // If toVisit starts out at 0 (indicating there is no
917 // max), then it will be negative here and we won't stop
918 // early.
919 if toVisit == 0 || remoteRefs[c.Hash] {
920 return storer.ErrStop
921 }
922 return nil
923 })
924
925 return nil
926}
927
928func getHaves(
929 localRefs []*plumbing.Reference,
930 remoteRefStorer storer.ReferenceStorer,
931 s storage.Storer,
932 depth int,
933) ([]plumbing.Hash, error) {
934 haves := map[plumbing.Hash]bool{}
935
936 // Build a map of all the remote references, to avoid loading too
937 // many parent commits for references we know don't need to be
938 // transferred.
939 remoteRefs, err := getRemoteRefsFromStorer(remoteRefStorer)
940 if err != nil {
941 return nil, err
942 }
943
944 for _, ref := range localRefs {
945 if haves[ref.Hash()] {
946 continue
947 }
948
949 if ref.Type() != plumbing.HashReference {
950 continue
951 }
952
953 err = getHavesFromRef(ref, remoteRefs, s, haves, depth)
954 if err != nil {
955 return nil, err
956 }
957 }
958
959 var result []plumbing.Hash
960 for h := range haves {
961 result = append(result, h)
962 }
963
964 return result, nil
965}
966
967const refspecAllTags = "+refs/tags/*:refs/tags/*"
968
969func calculateRefs(
970 spec []config.RefSpec,
971 remoteRefs storer.ReferenceStorer,
972 tagMode plumbing.TagMode,
973) (memory.ReferenceStorage, [][]*plumbing.Reference, error) {
974 if tagMode == plumbing.AllTags {
975 spec = append(spec, refspecAllTags)
976 }
977
978 refs := make(memory.ReferenceStorage)
979 // list of references matched for each spec
980 specToRefs := make([][]*plumbing.Reference, len(spec))
981 for i := range spec {
982 var err error
983 specToRefs[i], err = doCalculateRefs(spec[i], remoteRefs, refs)
984 if err != nil {
985 return nil, nil, err
986 }
987 }
988
989 return refs, specToRefs, nil
990}
991
992func doCalculateRefs(
993 s config.RefSpec,
994 remoteRefs storer.ReferenceStorer,
995 refs memory.ReferenceStorage,
996) ([]*plumbing.Reference, error) {
997 var refList []*plumbing.Reference
998
999 if s.IsExactSHA1() {
1000 ref := plumbing.NewHashReference(s.Dst(""), plumbing.NewHash(s.Src()))
1001
1002 refList = append(refList, ref)
1003 return refList, refs.SetReference(ref)
1004 }
1005
1006 var matched bool
1007 onMatched := func(ref *plumbing.Reference) error {
1008 if ref.Type() == plumbing.SymbolicReference {
1009 target, err := storer.ResolveReference(remoteRefs, ref.Name())
1010 if err != nil {
1011 return err
1012 }
1013
1014 ref = plumbing.NewHashReference(ref.Name(), target.Hash())
1015 }
1016
1017 if ref.Type() != plumbing.HashReference {
1018 return nil
1019 }
1020
1021 matched = true
1022 refList = append(refList, ref)
1023 return refs.SetReference(ref)
1024 }
1025
1026 var ret error
1027 if s.IsWildcard() {
1028 iter, err := remoteRefs.IterReferences()
1029 if err != nil {
1030 return nil, err
1031 }
1032 ret = iter.ForEach(func(ref *plumbing.Reference) error {
1033 if !s.Match(ref.Name()) {
1034 return nil
1035 }
1036
1037 return onMatched(ref)
1038 })
1039 } else {
1040 var resolvedRef *plumbing.Reference
1041 src := s.Src()
1042 resolvedRef, ret = expand_ref(remoteRefs, plumbing.ReferenceName(src))
1043 if ret == nil {
1044 ret = onMatched(resolvedRef)
1045 }
1046 }
1047
1048 if !matched && !s.IsWildcard() {
1049 return nil, NoMatchingRefSpecError{refSpec: s}
1050 }
1051
1052 return refList, ret
1053}
1054
1055func getWants(localStorer storage.Storer, refs memory.ReferenceStorage, depth int) ([]plumbing.Hash, error) {
1056 // If depth is anything other than 1 and the repo has shallow commits then just because we have the commit
1057 // at the reference doesn't mean that we don't still need to fetch the parents
1058 shallow := false
1059 if depth != 1 {
1060 if s, _ := localStorer.Shallow(); len(s) > 0 {
1061 shallow = true
1062 }
1063 }
1064
1065 wants := map[plumbing.Hash]bool{}
1066 for _, ref := range refs {
1067 hash := ref.Hash()
1068 exists, err := objectExists(localStorer, ref.Hash())
1069 if err != nil {
1070 return nil, err
1071 }
1072
1073 if !exists || shallow {
1074 wants[hash] = true
1075 }
1076 }
1077
1078 var result []plumbing.Hash
1079 for h := range wants {
1080 result = append(result, h)
1081 }
1082
1083 return result, nil
1084}
1085
1086func objectExists(s storer.EncodedObjectStorer, h plumbing.Hash) (bool, error) {
1087 _, err := s.EncodedObject(plumbing.AnyObject, h)
1088 if err == plumbing.ErrObjectNotFound {
1089 return false, nil
1090 }
1091
1092 return true, err
1093}
1094
1095func checkFastForwardUpdate(s storer.EncodedObjectStorer, remoteRefs storer.ReferenceStorer, cmd *packp.Command) error {
1096 if cmd.Old == plumbing.ZeroHash {
1097 _, err := remoteRefs.Reference(cmd.Name)
1098 if err == plumbing.ErrReferenceNotFound {
1099 return nil
1100 }
1101
1102 if err != nil {
1103 return err
1104 }
1105
1106 return fmt.Errorf("non-fast-forward update: %s", cmd.Name.String())
1107 }
1108
1109 ff, err := isFastForward(s, cmd.Old, cmd.New, nil)
1110 if err != nil {
1111 return err
1112 }
1113
1114 if !ff {
1115 return fmt.Errorf("non-fast-forward update: %s", cmd.Name.String())
1116 }
1117
1118 return nil
1119}
1120
1121func isFastForward(s storer.EncodedObjectStorer, old, new plumbing.Hash, earliestShallow *plumbing.Hash) (bool, error) {
1122 c, err := object.GetCommit(s, new)
1123 if err != nil {
1124 return false, err
1125 }
1126
1127 parentsToIgnore := []plumbing.Hash{}
1128 if earliestShallow != nil {
1129 earliestCommit, err := object.GetCommit(s, *earliestShallow)
1130 if err != nil {
1131 return false, err
1132 }
1133
1134 parentsToIgnore = earliestCommit.ParentHashes
1135 }
1136
1137 found := false
1138 // stop iterating at the earliest shallow commit, ignoring its parents
1139 // note: when pull depth is smaller than the number of new changes on the remote, this fails due to missing parents.
1140 // as far as i can tell, without the commits in-between the shallow pull and the earliest shallow, there's no
1141 // real way of telling whether it will be a fast-forward merge.
1142 iter := object.NewCommitPreorderIter(c, nil, parentsToIgnore)
1143 err = iter.ForEach(func(c *object.Commit) error {
1144 if c.Hash != old {
1145 return nil
1146 }
1147
1148 found = true
1149 return storer.ErrStop
1150 })
1151 return found, err
1152}
1153
1154func (r *Remote) newUploadPackRequest(o *FetchOptions,
1155 ar *packp.AdvRefs,
1156) (*packp.UploadPackRequest, error) {
1157 req := packp.NewUploadPackRequestFromCapabilities(ar.Capabilities)
1158
1159 if o.Depth != 0 {
1160 req.Depth = packp.DepthCommits(o.Depth)
1161 if err := req.Capabilities.Set(capability.Shallow); err != nil {
1162 return nil, err
1163 }
1164 }
1165
1166 if o.Progress == nil && ar.Capabilities.Supports(capability.NoProgress) {
1167 if err := req.Capabilities.Set(capability.NoProgress); err != nil {
1168 return nil, err
1169 }
1170 }
1171
1172 if o.Filter != "" {
1173 if ar.Capabilities.Supports(capability.Filter) {
1174 req.Filter = o.Filter
1175 if err := req.Capabilities.Set(capability.Filter); err != nil {
1176 return nil, err
1177 }
1178 } else {
1179 return nil, ErrFilterNotSupported
1180 }
1181 }
1182 isWildcard := true
1183 for _, s := range o.RefSpecs {
1184 if !s.IsWildcard() {
1185 isWildcard = false
1186 break
1187 }
1188 }
1189
1190 if isWildcard && o.Tags == plumbing.TagFollowing && ar.Capabilities.Supports(capability.IncludeTag) {
1191 if err := req.Capabilities.Set(capability.IncludeTag); err != nil {
1192 return nil, err
1193 }
1194 }
1195
1196 return req, nil
1197}
1198
1199func (r *Remote) isSupportedRefSpec(refs []config.RefSpec, ar *packp.AdvRefs) error {
1200 var containsIsExact bool
1201 for _, ref := range refs {
1202 if ref.IsExactSHA1() {
1203 containsIsExact = true
1204 }
1205 }
1206
1207 if !containsIsExact {
1208 return nil
1209 }
1210
1211 if ar.Capabilities.Supports(capability.AllowReachableSHA1InWant) ||
1212 ar.Capabilities.Supports(capability.AllowTipSHA1InWant) {
1213 return nil
1214 }
1215
1216 return ErrExactSHA1NotSupported
1217}
1218
1219func buildSidebandIfSupported(l *capability.List, reader io.Reader, p sideband.Progress) io.Reader {
1220 var t sideband.Type
1221
1222 switch {
1223 case l.Supports(capability.Sideband):
1224 t = sideband.Sideband
1225 case l.Supports(capability.Sideband64k):
1226 t = sideband.Sideband64k
1227 default:
1228 return reader
1229 }
1230
1231 d := sideband.NewDemuxer(t, reader)
1232 d.Progress = p
1233
1234 return d
1235}
1236
1237func (r *Remote) updateLocalReferenceStorage(
1238 specs []config.RefSpec,
1239 fetchedRefs, remoteRefs memory.ReferenceStorage,
1240 specToRefs [][]*plumbing.Reference,
1241 tagMode plumbing.TagMode,
1242 force bool,
1243) (updated bool, err error) {
1244 isWildcard := true
1245 forceNeeded := false
1246
1247 for i, spec := range specs {
1248 if !spec.IsWildcard() {
1249 isWildcard = false
1250 }
1251
1252 for _, ref := range specToRefs[i] {
1253 if ref.Type() != plumbing.HashReference {
1254 continue
1255 }
1256
1257 localName := spec.Dst(ref.Name())
1258 // If localName doesn't start with "refs/" then treat as a branch.
1259 if !strings.HasPrefix(localName.String(), "refs/") {
1260 localName = plumbing.NewBranchReferenceName(localName.String())
1261 }
1262 old, _ := storer.ResolveReference(r.s, localName)
1263 new := plumbing.NewHashReference(localName, ref.Hash())
1264
1265 // If the ref exists locally as a non-tag and force is not
1266 // specified, only update if the new ref is an ancestor of the old
1267 if old != nil && !old.Name().IsTag() && !force && !spec.IsForceUpdate() {
1268 ff, err := isFastForward(r.s, old.Hash(), new.Hash(), nil)
1269 if err != nil {
1270 return updated, err
1271 }
1272
1273 if !ff {
1274 forceNeeded = true
1275 continue
1276 }
1277 }
1278
1279 refUpdated, err := checkAndUpdateReferenceStorerIfNeeded(r.s, new, old)
1280 if err != nil {
1281 return updated, err
1282 }
1283
1284 if refUpdated {
1285 updated = true
1286 }
1287 }
1288 }
1289
1290 if tagMode == plumbing.NoTags {
1291 return updated, nil
1292 }
1293
1294 tags := fetchedRefs
1295 if isWildcard {
1296 tags = remoteRefs
1297 }
1298 tagUpdated, err := r.buildFetchedTags(tags)
1299 if err != nil {
1300 return updated, err
1301 }
1302
1303 if tagUpdated {
1304 updated = true
1305 }
1306
1307 if forceNeeded {
1308 err = ErrForceNeeded
1309 }
1310
1311 return
1312}
1313
1314func (r *Remote) buildFetchedTags(refs memory.ReferenceStorage) (updated bool, err error) {
1315 for _, ref := range refs {
1316 if !ref.Name().IsTag() {
1317 continue
1318 }
1319
1320 _, err := r.s.EncodedObject(plumbing.AnyObject, ref.Hash())
1321 if err == plumbing.ErrObjectNotFound {
1322 continue
1323 }
1324
1325 if err != nil {
1326 return false, err
1327 }
1328
1329 refUpdated, err := updateReferenceStorerIfNeeded(r.s, ref)
1330 if err != nil {
1331 return updated, err
1332 }
1333
1334 if refUpdated {
1335 updated = true
1336 }
1337 }
1338
1339 return
1340}
1341
1342// List the references on the remote repository.
1343// The provided Context must be non-nil. If the context expires before the
1344// operation is complete, an error is returned. The context only affects to the
1345// transport operations.
1346func (r *Remote) ListContext(ctx context.Context, o *ListOptions) (rfs []*plumbing.Reference, err error) {
1347 return r.list(ctx, o)
1348}
1349
1350func (r *Remote) List(o *ListOptions) (rfs []*plumbing.Reference, err error) {
1351 timeout := o.Timeout
1352 // Default to the old hardcoded 10s value if a timeout is not explicitly set.
1353 if timeout == 0 {
1354 timeout = 10
1355 }
1356 if timeout < 0 {
1357 return nil, fmt.Errorf("invalid timeout: %d", timeout)
1358 }
1359 ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
1360 defer cancel()
1361 return r.ListContext(ctx, o)
1362}
1363
1364func (r *Remote) list(ctx context.Context, o *ListOptions) (rfs []*plumbing.Reference, err error) {
1365 if r.c == nil || len(r.c.URLs) == 0 {
1366 return nil, ErrEmptyUrls
1367 }
1368
1369 s, err := newUploadPackSession(r.c.URLs[0], o.Auth, o.InsecureSkipTLS, o.CABundle, o.ProxyOptions)
1370 if err != nil {
1371 return nil, err
1372 }
1373
1374 defer ioutil.CheckClose(s, &err)
1375
1376 ar, err := s.AdvertisedReferencesContext(ctx)
1377 if err != nil {
1378 return nil, err
1379 }
1380
1381 allRefs, err := ar.AllReferences()
1382 if err != nil {
1383 return nil, err
1384 }
1385
1386 refs, err := allRefs.IterReferences()
1387 if err != nil {
1388 return nil, err
1389 }
1390
1391 var resultRefs []*plumbing.Reference
1392 if o.PeelingOption == AppendPeeled || o.PeelingOption == IgnorePeeled {
1393 err = refs.ForEach(func(ref *plumbing.Reference) error {
1394 resultRefs = append(resultRefs, ref)
1395 return nil
1396 })
1397 if err != nil {
1398 return nil, err
1399 }
1400 }
1401
1402 if o.PeelingOption == AppendPeeled || o.PeelingOption == OnlyPeeled {
1403 for k, v := range ar.Peeled {
1404 resultRefs = append(resultRefs, plumbing.NewReferenceFromStrings(k+"^{}", v.String()))
1405 }
1406 }
1407
1408 return resultRefs, nil
1409}
1410
1411func objectsToPush(commands []*packp.Command) []plumbing.Hash {
1412 objects := make([]plumbing.Hash, 0, len(commands))
1413 for _, cmd := range commands {
1414 if cmd.New == plumbing.ZeroHash {
1415 continue
1416 }
1417 objects = append(objects, cmd.New)
1418 }
1419 return objects
1420}
1421
1422func referencesToHashes(refs storer.ReferenceStorer) ([]plumbing.Hash, error) {
1423 iter, err := refs.IterReferences()
1424 if err != nil {
1425 return nil, err
1426 }
1427
1428 var hs []plumbing.Hash
1429 err = iter.ForEach(func(ref *plumbing.Reference) error {
1430 if ref.Type() != plumbing.HashReference {
1431 return nil
1432 }
1433
1434 hs = append(hs, ref.Hash())
1435 return nil
1436 })
1437 if err != nil {
1438 return nil, err
1439 }
1440
1441 return hs, nil
1442}
1443
1444func pushHashes(
1445 ctx context.Context,
1446 sess transport.ReceivePackSession,
1447 s storage.Storer,
1448 req *packp.ReferenceUpdateRequest,
1449 hs []plumbing.Hash,
1450 useRefDeltas bool,
1451 allDelete bool,
1452) (*packp.ReportStatus, error) {
1453 rd, wr := io.Pipe()
1454
1455 config, err := s.Config()
1456 if err != nil {
1457 return nil, err
1458 }
1459
1460 // Set buffer size to 1 so the error message can be written when
1461 // ReceivePack fails. Otherwise the goroutine will be blocked writing
1462 // to the channel.
1463 done := make(chan error, 1)
1464
1465 if !allDelete {
1466 req.Packfile = rd
1467 go func() {
1468 e := packfile.NewEncoder(wr, s, useRefDeltas)
1469 if _, err := e.Encode(hs, config.Pack.Window); err != nil {
1470 done <- wr.CloseWithError(err)
1471 return
1472 }
1473
1474 done <- wr.Close()
1475 }()
1476 } else {
1477 close(done)
1478 }
1479
1480 rs, err := sess.ReceivePack(ctx, req)
1481 if err != nil {
1482 // close the pipe to unlock encode write
1483 _ = rd.Close()
1484 return nil, err
1485 }
1486
1487 if err := <-done; err != nil {
1488 return nil, err
1489 }
1490
1491 return rs, nil
1492}
1493
1494func (r *Remote) updateShallow(o *FetchOptions, resp *packp.UploadPackResponse) error {
1495 if o.Depth == 0 || len(resp.Shallows) == 0 {
1496 return nil
1497 }
1498
1499 shallows, err := r.s.Shallow()
1500 if err != nil {
1501 return err
1502 }
1503
1504outer:
1505 for _, s := range resp.Shallows {
1506 for _, oldS := range shallows {
1507 if s == oldS {
1508 continue outer
1509 }
1510 }
1511 shallows = append(shallows, s)
1512 }
1513
1514 return r.s.SetShallow(shallows)
1515}
1516
1517func (r *Remote) checkRequireRemoteRefs(requires []config.RefSpec, remoteRefs storer.ReferenceStorer) error {
1518 for _, require := range requires {
1519 if require.IsWildcard() {
1520 return fmt.Errorf("wildcards not supported in RequireRemoteRefs, got %s", require.String())
1521 }
1522
1523 name := require.Dst("")
1524 remote, err := remoteRefs.Reference(name)
1525 if err != nil {
1526 return fmt.Errorf("remote ref %s required to be %s but is absent", name.String(), require.Src())
1527 }
1528
1529 var requireHash string
1530 if require.IsExactSHA1() {
1531 requireHash = require.Src()
1532 } else {
1533 target, err := storer.ResolveReference(remoteRefs, plumbing.ReferenceName(require.Src()))
1534 if err != nil {
1535 return fmt.Errorf("could not resolve ref %s in RequireRemoteRefs", require.Src())
1536 }
1537 requireHash = target.Hash().String()
1538 }
1539
1540 if remote.Hash().String() != requireHash {
1541 return fmt.Errorf("remote ref %s required to be %s but is %s", name.String(), requireHash, remote.Hash().String())
1542 }
1543 }
1544 return nil
1545}