fork of go-git with some jj specific features
at main 37 kB view raw
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}