fork of go-git with some jj specific features
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v5.14.0 1886 lines 48 kB view raw
1package git 2 3import ( 4 "bytes" 5 "context" 6 "crypto" 7 "encoding/hex" 8 "errors" 9 "fmt" 10 "io" 11 "os" 12 "path" 13 "path/filepath" 14 "strings" 15 "time" 16 17 "dario.cat/mergo" 18 "github.com/ProtonMail/go-crypto/openpgp" 19 "github.com/go-git/go-billy/v5" 20 "github.com/go-git/go-billy/v5/osfs" 21 "github.com/go-git/go-billy/v5/util" 22 "github.com/go-git/go-git/v5/config" 23 "github.com/go-git/go-git/v5/internal/path_util" 24 "github.com/go-git/go-git/v5/internal/revision" 25 "github.com/go-git/go-git/v5/internal/url" 26 "github.com/go-git/go-git/v5/plumbing" 27 "github.com/go-git/go-git/v5/plumbing/cache" 28 formatcfg "github.com/go-git/go-git/v5/plumbing/format/config" 29 "github.com/go-git/go-git/v5/plumbing/format/packfile" 30 "github.com/go-git/go-git/v5/plumbing/hash" 31 "github.com/go-git/go-git/v5/plumbing/object" 32 "github.com/go-git/go-git/v5/plumbing/storer" 33 "github.com/go-git/go-git/v5/storage" 34 "github.com/go-git/go-git/v5/storage/filesystem" 35 "github.com/go-git/go-git/v5/storage/filesystem/dotgit" 36 "github.com/go-git/go-git/v5/utils/ioutil" 37) 38 39// GitDirName this is a special folder where all the git stuff is. 40const GitDirName = ".git" 41 42var ( 43 // ErrBranchExists an error stating the specified branch already exists 44 ErrBranchExists = errors.New("branch already exists") 45 // ErrBranchNotFound an error stating the specified branch does not exist 46 ErrBranchNotFound = errors.New("branch not found") 47 // ErrTagExists an error stating the specified tag already exists 48 ErrTagExists = errors.New("tag already exists") 49 // ErrTagNotFound an error stating the specified tag does not exist 50 ErrTagNotFound = errors.New("tag not found") 51 // ErrFetching is returned when the packfile could not be downloaded 52 ErrFetching = errors.New("unable to fetch packfile") 53 54 ErrInvalidReference = errors.New("invalid reference, should be a tag or a branch") 55 ErrRepositoryNotExists = errors.New("repository does not exist") 56 ErrRepositoryIncomplete = errors.New("repository's commondir path does not exist") 57 ErrRepositoryAlreadyExists = errors.New("repository already exists") 58 ErrRemoteNotFound = errors.New("remote not found") 59 ErrRemoteExists = errors.New("remote already exists") 60 ErrAnonymousRemoteName = errors.New("anonymous remote name must be 'anonymous'") 61 ErrWorktreeNotProvided = errors.New("worktree should be provided") 62 ErrIsBareRepository = errors.New("worktree not available in a bare repository") 63 ErrUnableToResolveCommit = errors.New("unable to resolve commit") 64 ErrPackedObjectsNotSupported = errors.New("packed objects not supported") 65 ErrSHA256NotSupported = errors.New("go-git was not compiled with SHA256 support") 66 ErrAlternatePathNotSupported = errors.New("alternate path must use the file scheme") 67 ErrUnsupportedMergeStrategy = errors.New("unsupported merge strategy") 68 ErrFastForwardMergeNotPossible = errors.New("not possible to fast-forward merge changes") 69) 70 71// Repository represents a git repository 72type Repository struct { 73 Storer storage.Storer 74 75 r map[string]*Remote 76 wt billy.Filesystem 77} 78 79type InitOptions struct { 80 // The default branch (e.g. "refs/heads/master") 81 DefaultBranch plumbing.ReferenceName 82} 83 84// Init creates an empty git repository, based on the given Storer and worktree. 85// The worktree Filesystem is optional, if nil a bare repository is created. If 86// the given storer is not empty ErrRepositoryAlreadyExists is returned 87func Init(s storage.Storer, worktree billy.Filesystem) (*Repository, error) { 88 options := InitOptions{ 89 DefaultBranch: plumbing.Master, 90 } 91 return InitWithOptions(s, worktree, options) 92} 93 94func InitWithOptions(s storage.Storer, worktree billy.Filesystem, options InitOptions) (*Repository, error) { 95 if err := initStorer(s); err != nil { 96 return nil, err 97 } 98 99 if options.DefaultBranch == "" { 100 options.DefaultBranch = plumbing.Master 101 } 102 103 if err := options.DefaultBranch.Validate(); err != nil { 104 return nil, err 105 } 106 107 r := newRepository(s, worktree) 108 _, err := r.Reference(plumbing.HEAD, false) 109 switch err { 110 case plumbing.ErrReferenceNotFound: 111 case nil: 112 return nil, ErrRepositoryAlreadyExists 113 default: 114 return nil, err 115 } 116 117 h := plumbing.NewSymbolicReference(plumbing.HEAD, options.DefaultBranch) 118 if err := s.SetReference(h); err != nil { 119 return nil, err 120 } 121 122 if worktree == nil { 123 _ = r.setIsBare(true) 124 return r, nil 125 } 126 127 return r, setWorktreeAndStoragePaths(r, worktree) 128} 129 130func initStorer(s storer.Storer) error { 131 i, ok := s.(storer.Initializer) 132 if !ok { 133 return nil 134 } 135 136 return i.Init() 137} 138 139func setWorktreeAndStoragePaths(r *Repository, worktree billy.Filesystem) error { 140 type fsBased interface { 141 Filesystem() billy.Filesystem 142 } 143 144 // .git file is only created if the storage is file based and the file 145 // system is osfs.OS 146 fs, isFSBased := r.Storer.(fsBased) 147 if !isFSBased { 148 return nil 149 } 150 151 if err := createDotGitFile(worktree, fs.Filesystem()); err != nil { 152 return err 153 } 154 155 return setConfigWorktree(r, worktree, fs.Filesystem()) 156} 157 158func createDotGitFile(worktree, storage billy.Filesystem) error { 159 path, err := filepath.Rel(worktree.Root(), storage.Root()) 160 if err != nil { 161 path = storage.Root() 162 } 163 164 if path == GitDirName { 165 // not needed, since the folder is the default place 166 return nil 167 } 168 169 f, err := worktree.Create(GitDirName) 170 if err != nil { 171 return err 172 } 173 174 defer f.Close() 175 _, err = fmt.Fprintf(f, "gitdir: %s\n", path) 176 return err 177} 178 179func setConfigWorktree(r *Repository, worktree, storage billy.Filesystem) error { 180 path, err := filepath.Rel(storage.Root(), worktree.Root()) 181 if err != nil { 182 path = worktree.Root() 183 } 184 185 if path == ".." { 186 // not needed, since the folder is the default place 187 return nil 188 } 189 190 cfg, err := r.Config() 191 if err != nil { 192 return err 193 } 194 195 cfg.Core.Worktree = path 196 return r.Storer.SetConfig(cfg) 197} 198 199// Open opens a git repository using the given Storer and worktree filesystem, 200// if the given storer is complete empty ErrRepositoryNotExists is returned. 201// The worktree can be nil when the repository being opened is bare, if the 202// repository is a normal one (not bare) and worktree is nil the err 203// ErrWorktreeNotProvided is returned 204func Open(s storage.Storer, worktree billy.Filesystem) (*Repository, error) { 205 _, err := s.Reference(plumbing.HEAD) 206 if err == plumbing.ErrReferenceNotFound { 207 return nil, ErrRepositoryNotExists 208 } 209 210 if err != nil { 211 return nil, err 212 } 213 214 return newRepository(s, worktree), nil 215} 216 217// Clone a repository into the given Storer and worktree Filesystem with the 218// given options, if worktree is nil a bare repository is created. If the given 219// storer is not empty ErrRepositoryAlreadyExists is returned. 220func Clone(s storage.Storer, worktree billy.Filesystem, o *CloneOptions) (*Repository, error) { 221 return CloneContext(context.Background(), s, worktree, o) 222} 223 224// CloneContext a repository into the given Storer and worktree Filesystem with 225// the given options, if worktree is nil a bare repository is created. If the 226// given storer is not empty ErrRepositoryAlreadyExists is returned. 227// 228// The provided Context must be non-nil. If the context expires before the 229// operation is complete, an error is returned. The context only affects the 230// transport operations. 231func CloneContext( 232 ctx context.Context, s storage.Storer, worktree billy.Filesystem, o *CloneOptions, 233) (*Repository, error) { 234 r, err := Init(s, worktree) 235 if err != nil { 236 return nil, err 237 } 238 239 return r, r.clone(ctx, o) 240} 241 242// PlainInit create an empty git repository at the given path. isBare defines 243// if the repository will have worktree (non-bare) or not (bare), if the path 244// is not empty ErrRepositoryAlreadyExists is returned. 245func PlainInit(path string, isBare bool) (*Repository, error) { 246 return PlainInitWithOptions(path, &PlainInitOptions{ 247 Bare: isBare, 248 }) 249} 250 251func PlainInitWithOptions(path string, opts *PlainInitOptions) (*Repository, error) { 252 if opts == nil { 253 opts = &PlainInitOptions{} 254 } 255 256 var wt, dot billy.Filesystem 257 258 if opts.Bare { 259 dot = osfs.New(path) 260 } else { 261 wt = osfs.New(path) 262 dot, _ = wt.Chroot(GitDirName) 263 } 264 265 s := filesystem.NewStorage(dot, cache.NewObjectLRUDefault()) 266 267 r, err := InitWithOptions(s, wt, opts.InitOptions) 268 if err != nil { 269 return nil, err 270 } 271 272 cfg, err := r.Config() 273 if err != nil { 274 return nil, err 275 } 276 277 if opts.ObjectFormat != "" { 278 if opts.ObjectFormat == formatcfg.SHA256 && hash.CryptoType != crypto.SHA256 { 279 return nil, ErrSHA256NotSupported 280 } 281 282 cfg.Core.RepositoryFormatVersion = formatcfg.Version_1 283 cfg.Extensions.ObjectFormat = opts.ObjectFormat 284 } 285 286 err = r.Storer.SetConfig(cfg) 287 if err != nil { 288 return nil, err 289 } 290 291 return r, err 292} 293 294// PlainOpen opens a git repository from the given path. It detects if the 295// repository is bare or a normal one. If the path doesn't contain a valid 296// repository ErrRepositoryNotExists is returned 297func PlainOpen(path string) (*Repository, error) { 298 return PlainOpenWithOptions(path, &PlainOpenOptions{}) 299} 300 301// PlainOpenWithOptions opens a git repository from the given path with specific 302// options. See PlainOpen for more info. 303func PlainOpenWithOptions(path string, o *PlainOpenOptions) (*Repository, error) { 304 dot, wt, err := dotGitToOSFilesystems(path, o.DetectDotGit) 305 if err != nil { 306 return nil, err 307 } 308 309 if _, err := dot.Stat(""); err != nil { 310 if os.IsNotExist(err) { 311 return nil, ErrRepositoryNotExists 312 } 313 314 return nil, err 315 } 316 317 var repositoryFs billy.Filesystem 318 319 if o.EnableDotGitCommonDir { 320 dotGitCommon, err := dotGitCommonDirectory(dot) 321 if err != nil { 322 return nil, err 323 } 324 repositoryFs = dotgit.NewRepositoryFilesystem(dot, dotGitCommon) 325 } else { 326 repositoryFs = dot 327 } 328 329 s := filesystem.NewStorage(repositoryFs, cache.NewObjectLRUDefault()) 330 331 return Open(s, wt) 332} 333 334func dotGitToOSFilesystems(path string, detect bool) (dot, wt billy.Filesystem, err error) { 335 path, err = path_util.ReplaceTildeWithHome(path) 336 if err != nil { 337 return nil, nil, err 338 } 339 340 if path, err = filepath.Abs(path); err != nil { 341 return nil, nil, err 342 } 343 344 var fs billy.Filesystem 345 var fi os.FileInfo 346 for { 347 fs = osfs.New(path) 348 349 pathinfo, err := fs.Stat("/") 350 if !os.IsNotExist(err) { 351 if pathinfo == nil { 352 return nil, nil, err 353 } 354 if !pathinfo.IsDir() && detect { 355 fs = osfs.New(filepath.Dir(path)) 356 } 357 } 358 359 fi, err = fs.Stat(GitDirName) 360 if err == nil { 361 // no error; stop 362 break 363 } 364 if !os.IsNotExist(err) { 365 // unknown error; stop 366 return nil, nil, err 367 } 368 if detect { 369 // try its parent as long as we haven't reached 370 // the root dir 371 if dir := filepath.Dir(path); dir != path { 372 path = dir 373 continue 374 } 375 } 376 // not detecting via parent dirs and the dir does not exist; 377 // stop 378 return fs, nil, nil 379 } 380 381 if fi.IsDir() { 382 dot, err = fs.Chroot(GitDirName) 383 return dot, fs, err 384 } 385 386 dot, err = dotGitFileToOSFilesystem(path, fs) 387 if err != nil { 388 return nil, nil, err 389 } 390 391 return dot, fs, nil 392} 393 394func dotGitFileToOSFilesystem(path string, fs billy.Filesystem) (bfs billy.Filesystem, err error) { 395 f, err := fs.Open(GitDirName) 396 if err != nil { 397 return nil, err 398 } 399 defer ioutil.CheckClose(f, &err) 400 401 b, err := io.ReadAll(f) 402 if err != nil { 403 return nil, err 404 } 405 406 line := string(b) 407 const prefix = "gitdir: " 408 if !strings.HasPrefix(line, prefix) { 409 return nil, fmt.Errorf(".git file has no %s prefix", prefix) 410 } 411 412 gitdir := strings.Split(line[len(prefix):], "\n")[0] 413 gitdir = strings.TrimSpace(gitdir) 414 if filepath.IsAbs(gitdir) { 415 return osfs.New(gitdir), nil 416 } 417 418 return osfs.New(fs.Join(path, gitdir)), nil 419} 420 421func dotGitCommonDirectory(fs billy.Filesystem) (commonDir billy.Filesystem, err error) { 422 f, err := fs.Open("commondir") 423 if os.IsNotExist(err) { 424 return nil, nil 425 } 426 if err != nil { 427 return nil, err 428 } 429 430 b, err := io.ReadAll(f) 431 if err != nil { 432 return nil, err 433 } 434 if len(b) > 0 { 435 path := strings.TrimSpace(string(b)) 436 if filepath.IsAbs(path) { 437 commonDir = osfs.New(path) 438 } else { 439 commonDir = osfs.New(filepath.Join(fs.Root(), path)) 440 } 441 if _, err := commonDir.Stat(""); err != nil { 442 if os.IsNotExist(err) { 443 return nil, ErrRepositoryIncomplete 444 } 445 446 return nil, err 447 } 448 } 449 450 return commonDir, nil 451} 452 453// PlainClone a repository into the path with the given options, isBare defines 454// if the new repository will be bare or normal. If the path is not empty 455// ErrRepositoryAlreadyExists is returned. 456// 457// TODO(mcuadros): move isBare to CloneOptions in v5 458func PlainClone(path string, isBare bool, o *CloneOptions) (*Repository, error) { 459 return PlainCloneContext(context.Background(), path, isBare, o) 460} 461 462// PlainCloneContext a repository into the path with the given options, isBare 463// defines if the new repository will be bare or normal. If the path is not empty 464// ErrRepositoryAlreadyExists is returned. 465// 466// The provided Context must be non-nil. If the context expires before the 467// operation is complete, an error is returned. The context only affects the 468// transport operations. 469// 470// TODO(mcuadros): move isBare to CloneOptions in v5 471// TODO(smola): refuse upfront to clone on a non-empty directory in v5, see #1027 472func PlainCloneContext(ctx context.Context, path string, isBare bool, o *CloneOptions) (*Repository, error) { 473 cleanup, cleanupParent, err := checkIfCleanupIsNeeded(path) 474 if err != nil { 475 return nil, err 476 } 477 478 if o.Mirror { 479 isBare = true 480 } 481 r, err := PlainInit(path, isBare) 482 if err != nil { 483 return nil, err 484 } 485 486 err = r.clone(ctx, o) 487 if err != nil && err != ErrRepositoryAlreadyExists { 488 if cleanup { 489 _ = cleanUpDir(path, cleanupParent) 490 } 491 } 492 493 return r, err 494} 495 496func newRepository(s storage.Storer, worktree billy.Filesystem) *Repository { 497 return &Repository{ 498 Storer: s, 499 wt: worktree, 500 r: make(map[string]*Remote), 501 } 502} 503 504func checkIfCleanupIsNeeded(path string) (cleanup bool, cleanParent bool, err error) { 505 fi, err := osfs.Default.Stat(path) 506 if err != nil { 507 if os.IsNotExist(err) { 508 return true, true, nil 509 } 510 511 return false, false, err 512 } 513 514 if !fi.IsDir() { 515 return false, false, fmt.Errorf("path is not a directory: %s", path) 516 } 517 518 files, err := osfs.Default.ReadDir(path) 519 if err != nil { 520 return false, false, err 521 } 522 523 if len(files) == 0 { 524 return true, false, nil 525 } 526 527 return false, false, nil 528} 529 530func cleanUpDir(path string, all bool) error { 531 if all { 532 return util.RemoveAll(osfs.Default, path) 533 } 534 535 files, err := osfs.Default.ReadDir(path) 536 if err != nil { 537 return err 538 } 539 540 for _, fi := range files { 541 if err := util.RemoveAll(osfs.Default, osfs.Default.Join(path, fi.Name())); err != nil { 542 return err 543 } 544 } 545 546 return err 547} 548 549// Config return the repository config. In a filesystem backed repository this 550// means read the `.git/config`. 551func (r *Repository) Config() (*config.Config, error) { 552 return r.Storer.Config() 553} 554 555// SetConfig marshall and writes the repository config. In a filesystem backed 556// repository this means write the `.git/config`. This function should be called 557// with the result of `Repository.Config` and never with the output of 558// `Repository.ConfigScoped`. 559func (r *Repository) SetConfig(cfg *config.Config) error { 560 return r.Storer.SetConfig(cfg) 561} 562 563// ConfigScoped returns the repository config, merged with requested scope and 564// lower. For example if, config.GlobalScope is given the local and global config 565// are returned merged in one config value. 566func (r *Repository) ConfigScoped(scope config.Scope) (*config.Config, error) { 567 // TODO(mcuadros): v6, add this as ConfigOptions.Scoped 568 569 var err error 570 system := config.NewConfig() 571 if scope >= config.SystemScope { 572 system, err = config.LoadConfig(config.SystemScope) 573 if err != nil { 574 return nil, err 575 } 576 } 577 578 global := config.NewConfig() 579 if scope >= config.GlobalScope { 580 global, err = config.LoadConfig(config.GlobalScope) 581 if err != nil { 582 return nil, err 583 } 584 } 585 586 local, err := r.Storer.Config() 587 if err != nil { 588 return nil, err 589 } 590 591 _ = mergo.Merge(global, system) 592 _ = mergo.Merge(local, global) 593 return local, nil 594} 595 596// Remote return a remote if exists 597func (r *Repository) Remote(name string) (*Remote, error) { 598 cfg, err := r.Config() 599 if err != nil { 600 return nil, err 601 } 602 603 c, ok := cfg.Remotes[name] 604 if !ok { 605 return nil, ErrRemoteNotFound 606 } 607 608 return NewRemote(r.Storer, c), nil 609} 610 611// Remotes returns a list with all the remotes 612func (r *Repository) Remotes() ([]*Remote, error) { 613 cfg, err := r.Config() 614 if err != nil { 615 return nil, err 616 } 617 618 remotes := make([]*Remote, len(cfg.Remotes)) 619 620 var i int 621 for _, c := range cfg.Remotes { 622 remotes[i] = NewRemote(r.Storer, c) 623 i++ 624 } 625 626 return remotes, nil 627} 628 629// CreateRemote creates a new remote 630func (r *Repository) CreateRemote(c *config.RemoteConfig) (*Remote, error) { 631 if err := c.Validate(); err != nil { 632 return nil, err 633 } 634 635 remote := NewRemote(r.Storer, c) 636 637 cfg, err := r.Config() 638 if err != nil { 639 return nil, err 640 } 641 642 if _, ok := cfg.Remotes[c.Name]; ok { 643 return nil, ErrRemoteExists 644 } 645 646 cfg.Remotes[c.Name] = c 647 return remote, r.Storer.SetConfig(cfg) 648} 649 650// CreateRemoteAnonymous creates a new anonymous remote. c.Name must be "anonymous". 651// It's used like 'git fetch git@github.com:src-d/go-git.git master:master'. 652func (r *Repository) CreateRemoteAnonymous(c *config.RemoteConfig) (*Remote, error) { 653 if err := c.Validate(); err != nil { 654 return nil, err 655 } 656 657 if c.Name != "anonymous" { 658 return nil, ErrAnonymousRemoteName 659 } 660 661 remote := NewRemote(r.Storer, c) 662 663 return remote, nil 664} 665 666// DeleteRemote delete a remote from the repository and delete the config 667func (r *Repository) DeleteRemote(name string) error { 668 cfg, err := r.Config() 669 if err != nil { 670 return err 671 } 672 673 if _, ok := cfg.Remotes[name]; !ok { 674 return ErrRemoteNotFound 675 } 676 677 delete(cfg.Remotes, name) 678 return r.Storer.SetConfig(cfg) 679} 680 681// Branch return a Branch if exists 682func (r *Repository) Branch(name string) (*config.Branch, error) { 683 cfg, err := r.Config() 684 if err != nil { 685 return nil, err 686 } 687 688 b, ok := cfg.Branches[name] 689 if !ok { 690 return nil, ErrBranchNotFound 691 } 692 693 return b, nil 694} 695 696// CreateBranch creates a new Branch 697func (r *Repository) CreateBranch(c *config.Branch) error { 698 if err := c.Validate(); err != nil { 699 return err 700 } 701 702 cfg, err := r.Config() 703 if err != nil { 704 return err 705 } 706 707 if _, ok := cfg.Branches[c.Name]; ok { 708 return ErrBranchExists 709 } 710 711 cfg.Branches[c.Name] = c 712 return r.Storer.SetConfig(cfg) 713} 714 715// DeleteBranch delete a Branch from the repository and delete the config 716func (r *Repository) DeleteBranch(name string) error { 717 cfg, err := r.Config() 718 if err != nil { 719 return err 720 } 721 722 if _, ok := cfg.Branches[name]; !ok { 723 return ErrBranchNotFound 724 } 725 726 delete(cfg.Branches, name) 727 return r.Storer.SetConfig(cfg) 728} 729 730// CreateTag creates a tag. If opts is included, the tag is an annotated tag, 731// otherwise a lightweight tag is created. 732func (r *Repository) CreateTag(name string, hash plumbing.Hash, opts *CreateTagOptions) (*plumbing.Reference, error) { 733 rname := plumbing.NewTagReferenceName(name) 734 if err := rname.Validate(); err != nil { 735 return nil, err 736 } 737 738 _, err := r.Storer.Reference(rname) 739 switch err { 740 case nil: 741 // Tag exists, this is an error 742 return nil, ErrTagExists 743 case plumbing.ErrReferenceNotFound: 744 // Tag missing, available for creation, pass this 745 default: 746 // Some other error 747 return nil, err 748 } 749 750 var target plumbing.Hash 751 if opts != nil { 752 target, err = r.createTagObject(name, hash, opts) 753 if err != nil { 754 return nil, err 755 } 756 } else { 757 target = hash 758 } 759 760 ref := plumbing.NewHashReference(rname, target) 761 if err = r.Storer.SetReference(ref); err != nil { 762 return nil, err 763 } 764 765 return ref, nil 766} 767 768func (r *Repository) createTagObject(name string, hash plumbing.Hash, opts *CreateTagOptions) (plumbing.Hash, error) { 769 if err := opts.Validate(r, hash); err != nil { 770 return plumbing.ZeroHash, err 771 } 772 773 rawobj, err := object.GetObject(r.Storer, hash) 774 if err != nil { 775 return plumbing.ZeroHash, err 776 } 777 778 tag := &object.Tag{ 779 Name: name, 780 Tagger: *opts.Tagger, 781 Message: opts.Message, 782 TargetType: rawobj.Type(), 783 Target: hash, 784 } 785 786 if opts.SignKey != nil { 787 sig, err := r.buildTagSignature(tag, opts.SignKey) 788 if err != nil { 789 return plumbing.ZeroHash, err 790 } 791 792 tag.PGPSignature = sig 793 } 794 795 obj := r.Storer.NewEncodedObject() 796 if err := tag.Encode(obj); err != nil { 797 return plumbing.ZeroHash, err 798 } 799 800 return r.Storer.SetEncodedObject(obj) 801} 802 803func (r *Repository) buildTagSignature(tag *object.Tag, signKey *openpgp.Entity) (string, error) { 804 encoded := &plumbing.MemoryObject{} 805 if err := tag.Encode(encoded); err != nil { 806 return "", err 807 } 808 809 rdr, err := encoded.Reader() 810 if err != nil { 811 return "", err 812 } 813 814 var b bytes.Buffer 815 if err := openpgp.ArmoredDetachSign(&b, signKey, rdr, nil); err != nil { 816 return "", err 817 } 818 819 return b.String(), nil 820} 821 822// Tag returns a tag from the repository. 823// 824// If you want to check to see if the tag is an annotated tag, you can call 825// TagObject on the hash of the reference in ForEach: 826// 827// ref, err := r.Tag("v0.1.0") 828// if err != nil { 829// // Handle error 830// } 831// 832// obj, err := r.TagObject(ref.Hash()) 833// switch err { 834// case nil: 835// // Tag object present 836// case plumbing.ErrObjectNotFound: 837// // Not a tag object 838// default: 839// // Some other error 840// } 841func (r *Repository) Tag(name string) (*plumbing.Reference, error) { 842 ref, err := r.Reference(plumbing.ReferenceName(path.Join("refs", "tags", name)), false) 843 if err != nil { 844 if err == plumbing.ErrReferenceNotFound { 845 // Return a friendly error for this one, versus just ReferenceNotFound. 846 return nil, ErrTagNotFound 847 } 848 849 return nil, err 850 } 851 852 return ref, nil 853} 854 855// DeleteTag deletes a tag from the repository. 856func (r *Repository) DeleteTag(name string) error { 857 _, err := r.Tag(name) 858 if err != nil { 859 return err 860 } 861 862 return r.Storer.RemoveReference(plumbing.ReferenceName(path.Join("refs", "tags", name))) 863} 864 865func (r *Repository) resolveToCommitHash(h plumbing.Hash) (plumbing.Hash, error) { 866 obj, err := r.Storer.EncodedObject(plumbing.AnyObject, h) 867 if err != nil { 868 return plumbing.ZeroHash, err 869 } 870 switch obj.Type() { 871 case plumbing.TagObject: 872 t, err := object.DecodeTag(r.Storer, obj) 873 if err != nil { 874 return plumbing.ZeroHash, err 875 } 876 return r.resolveToCommitHash(t.Target) 877 case plumbing.CommitObject: 878 return h, nil 879 default: 880 return plumbing.ZeroHash, ErrUnableToResolveCommit 881 } 882} 883 884// Clone clones a remote repository 885func (r *Repository) clone(ctx context.Context, o *CloneOptions) error { 886 if err := o.Validate(); err != nil { 887 return err 888 } 889 890 c := &config.RemoteConfig{ 891 Name: o.RemoteName, 892 URLs: []string{o.URL}, 893 Fetch: r.cloneRefSpec(o), 894 Mirror: o.Mirror, 895 } 896 897 if _, err := r.CreateRemote(c); err != nil { 898 return err 899 } 900 901 // When the repository to clone is on the local machine, 902 // instead of using hard links, automatically setup .git/objects/info/alternates 903 // to share the objects with the source repository 904 if o.Shared { 905 if !url.IsLocalEndpoint(o.URL) { 906 return ErrAlternatePathNotSupported 907 } 908 altpath := o.URL 909 remoteRepo, err := PlainOpen(o.URL) 910 if err != nil { 911 return fmt.Errorf("failed to open remote repository: %w", err) 912 } 913 conf, err := remoteRepo.Config() 914 if err != nil { 915 return fmt.Errorf("failed to read remote repository configuration: %w", err) 916 } 917 if !conf.Core.IsBare { 918 altpath = path.Join(altpath, GitDirName) 919 } 920 if err := r.Storer.AddAlternate(altpath); err != nil { 921 return fmt.Errorf("failed to add alternate file to git objects dir: %w", err) 922 } 923 } 924 925 ref, err := r.fetchAndUpdateReferences(ctx, &FetchOptions{ 926 RefSpecs: c.Fetch, 927 Depth: o.Depth, 928 Auth: o.Auth, 929 Progress: o.Progress, 930 Tags: o.Tags, 931 RemoteName: o.RemoteName, 932 InsecureSkipTLS: o.InsecureSkipTLS, 933 CABundle: o.CABundle, 934 ProxyOptions: o.ProxyOptions, 935 }, o.ReferenceName) 936 if err != nil { 937 return err 938 } 939 940 if r.wt != nil && !o.NoCheckout { 941 w, err := r.Worktree() 942 if err != nil { 943 return err 944 } 945 946 head, err := r.Head() 947 if err != nil { 948 return err 949 } 950 951 if err := w.Reset(&ResetOptions{ 952 Mode: MergeReset, 953 Commit: head.Hash(), 954 }); err != nil { 955 return err 956 } 957 958 if o.RecurseSubmodules != NoRecurseSubmodules { 959 if err := w.updateSubmodules(ctx, &SubmoduleUpdateOptions{ 960 RecurseSubmodules: o.RecurseSubmodules, 961 Depth: func() int { 962 if o.ShallowSubmodules { 963 return 1 964 } 965 return 0 966 }(), 967 Auth: o.Auth, 968 }); err != nil { 969 return err 970 } 971 } 972 } 973 974 if err := r.updateRemoteConfigIfNeeded(o, c, ref); err != nil { 975 return err 976 } 977 978 if !o.Mirror && ref.Name().IsBranch() { 979 branchRef := ref.Name() 980 branchName := strings.Split(string(branchRef), "refs/heads/")[1] 981 982 b := &config.Branch{ 983 Name: branchName, 984 Merge: branchRef, 985 } 986 987 if o.RemoteName == "" { 988 b.Remote = "origin" 989 } else { 990 b.Remote = o.RemoteName 991 } 992 993 if err := r.CreateBranch(b); err != nil { 994 return err 995 } 996 } 997 998 return nil 999} 1000 1001const ( 1002 refspecTag = "+refs/tags/%s:refs/tags/%[1]s" 1003 refspecSingleBranch = "+refs/heads/%s:refs/remotes/%s/%[1]s" 1004 refspecSingleBranchHEAD = "+HEAD:refs/remotes/%s/HEAD" 1005) 1006 1007func (r *Repository) cloneRefSpec(o *CloneOptions) []config.RefSpec { 1008 switch { 1009 case o.Mirror: 1010 return []config.RefSpec{"+refs/*:refs/*"} 1011 case o.ReferenceName.IsTag(): 1012 return []config.RefSpec{ 1013 config.RefSpec(fmt.Sprintf(refspecTag, o.ReferenceName.Short())), 1014 } 1015 case o.SingleBranch && o.ReferenceName == plumbing.HEAD: 1016 return []config.RefSpec{ 1017 config.RefSpec(fmt.Sprintf(refspecSingleBranchHEAD, o.RemoteName)), 1018 } 1019 case o.SingleBranch: 1020 return []config.RefSpec{ 1021 config.RefSpec(fmt.Sprintf(refspecSingleBranch, o.ReferenceName.Short(), o.RemoteName)), 1022 } 1023 default: 1024 return []config.RefSpec{ 1025 config.RefSpec(fmt.Sprintf(config.DefaultFetchRefSpec, o.RemoteName)), 1026 } 1027 } 1028} 1029 1030func (r *Repository) setIsBare(isBare bool) error { 1031 cfg, err := r.Config() 1032 if err != nil { 1033 return err 1034 } 1035 1036 cfg.Core.IsBare = isBare 1037 return r.Storer.SetConfig(cfg) 1038} 1039 1040func (r *Repository) updateRemoteConfigIfNeeded(o *CloneOptions, c *config.RemoteConfig, _ *plumbing.Reference) error { 1041 if !o.SingleBranch { 1042 return nil 1043 } 1044 1045 c.Fetch = r.cloneRefSpec(o) 1046 1047 cfg, err := r.Config() 1048 if err != nil { 1049 return err 1050 } 1051 1052 cfg.Remotes[c.Name] = c 1053 return r.Storer.SetConfig(cfg) 1054} 1055 1056func (r *Repository) fetchAndUpdateReferences( 1057 ctx context.Context, o *FetchOptions, ref plumbing.ReferenceName, 1058) (*plumbing.Reference, error) { 1059 1060 if err := o.Validate(); err != nil { 1061 return nil, err 1062 } 1063 1064 remote, err := r.Remote(o.RemoteName) 1065 if err != nil { 1066 return nil, err 1067 } 1068 1069 objsUpdated := true 1070 remoteRefs, err := remote.fetch(ctx, o) 1071 if err == NoErrAlreadyUpToDate { 1072 objsUpdated = false 1073 } else if err == packfile.ErrEmptyPackfile { 1074 return nil, ErrFetching 1075 } else if err != nil { 1076 return nil, err 1077 } 1078 1079 resolvedRef, err := expand_ref(remoteRefs, ref) 1080 if err != nil { 1081 return nil, err 1082 } 1083 1084 refsUpdated, err := r.updateReferences(remote.c.Fetch, resolvedRef) 1085 if err != nil { 1086 return nil, err 1087 } 1088 1089 if !objsUpdated && !refsUpdated { 1090 return nil, NoErrAlreadyUpToDate 1091 } 1092 1093 return resolvedRef, nil 1094} 1095 1096func (r *Repository) updateReferences(spec []config.RefSpec, 1097 resolvedRef *plumbing.Reference) (updated bool, err error) { 1098 1099 if !resolvedRef.Name().IsBranch() { 1100 // Detached HEAD mode 1101 h, err := r.resolveToCommitHash(resolvedRef.Hash()) 1102 if err != nil { 1103 return false, err 1104 } 1105 head := plumbing.NewHashReference(plumbing.HEAD, h) 1106 return updateReferenceStorerIfNeeded(r.Storer, head) 1107 } 1108 1109 refs := []*plumbing.Reference{ 1110 // Create local reference for the resolved ref 1111 resolvedRef, 1112 // Create local symbolic HEAD 1113 plumbing.NewSymbolicReference(plumbing.HEAD, resolvedRef.Name()), 1114 } 1115 1116 refs = append(refs, r.calculateRemoteHeadReference(spec, resolvedRef)...) 1117 1118 for _, ref := range refs { 1119 u, err := updateReferenceStorerIfNeeded(r.Storer, ref) 1120 if err != nil { 1121 return updated, err 1122 } 1123 1124 if u { 1125 updated = true 1126 } 1127 } 1128 1129 return 1130} 1131 1132func (r *Repository) calculateRemoteHeadReference(spec []config.RefSpec, 1133 resolvedHead *plumbing.Reference) []*plumbing.Reference { 1134 1135 var refs []*plumbing.Reference 1136 1137 // Create resolved HEAD reference with remote prefix if it does not 1138 // exist. This is needed when using single branch and HEAD. 1139 for _, rs := range spec { 1140 name := resolvedHead.Name() 1141 if !rs.Match(name) { 1142 continue 1143 } 1144 1145 name = rs.Dst(name) 1146 _, err := r.Storer.Reference(name) 1147 if err == plumbing.ErrReferenceNotFound { 1148 refs = append(refs, plumbing.NewHashReference(name, resolvedHead.Hash())) 1149 } 1150 } 1151 1152 return refs 1153} 1154 1155func checkAndUpdateReferenceStorerIfNeeded( 1156 s storer.ReferenceStorer, r, old *plumbing.Reference) ( 1157 updated bool, err error) { 1158 p, err := s.Reference(r.Name()) 1159 if err != nil && err != plumbing.ErrReferenceNotFound { 1160 return false, err 1161 } 1162 1163 // we use the string method to compare references, is the easiest way 1164 if err == plumbing.ErrReferenceNotFound || r.String() != p.String() { 1165 if err := s.CheckAndSetReference(r, old); err != nil { 1166 return false, err 1167 } 1168 1169 return true, nil 1170 } 1171 1172 return false, nil 1173} 1174 1175func updateReferenceStorerIfNeeded( 1176 s storer.ReferenceStorer, r *plumbing.Reference) (updated bool, err error) { 1177 return checkAndUpdateReferenceStorerIfNeeded(s, r, nil) 1178} 1179 1180// Fetch fetches references along with the objects necessary to complete 1181// their histories, from the remote named as FetchOptions.RemoteName. 1182// 1183// Returns nil if the operation is successful, NoErrAlreadyUpToDate if there are 1184// no changes to be fetched, or an error. 1185func (r *Repository) Fetch(o *FetchOptions) error { 1186 return r.FetchContext(context.Background(), o) 1187} 1188 1189// FetchContext fetches references along with the objects necessary to complete 1190// their histories, from the remote named as FetchOptions.RemoteName. 1191// 1192// Returns nil if the operation is successful, NoErrAlreadyUpToDate if there are 1193// no changes to be fetched, or an error. 1194// 1195// The provided Context must be non-nil. If the context expires before the 1196// operation is complete, an error is returned. The context only affects the 1197// transport operations. 1198func (r *Repository) FetchContext(ctx context.Context, o *FetchOptions) error { 1199 if err := o.Validate(); err != nil { 1200 return err 1201 } 1202 1203 remote, err := r.Remote(o.RemoteName) 1204 if err != nil { 1205 return err 1206 } 1207 1208 return remote.FetchContext(ctx, o) 1209} 1210 1211// Push performs a push to the remote. Returns NoErrAlreadyUpToDate if 1212// the remote was already up-to-date, from the remote named as 1213// FetchOptions.RemoteName. 1214func (r *Repository) Push(o *PushOptions) error { 1215 return r.PushContext(context.Background(), o) 1216} 1217 1218// PushContext performs a push to the remote. Returns NoErrAlreadyUpToDate if 1219// the remote was already up-to-date, from the remote named as 1220// FetchOptions.RemoteName. 1221// 1222// The provided Context must be non-nil. If the context expires before the 1223// operation is complete, an error is returned. The context only affects the 1224// transport operations. 1225func (r *Repository) PushContext(ctx context.Context, o *PushOptions) error { 1226 if err := o.Validate(); err != nil { 1227 return err 1228 } 1229 1230 remote, err := r.Remote(o.RemoteName) 1231 if err != nil { 1232 return err 1233 } 1234 1235 return remote.PushContext(ctx, o) 1236} 1237 1238// Log returns the commit history from the given LogOptions. 1239func (r *Repository) Log(o *LogOptions) (object.CommitIter, error) { 1240 fn := commitIterFunc(o.Order) 1241 if fn == nil { 1242 return nil, fmt.Errorf("invalid Order=%v", o.Order) 1243 } 1244 1245 var ( 1246 it object.CommitIter 1247 err error 1248 ) 1249 if o.All { 1250 it, err = r.logAll(fn) 1251 } else { 1252 it, err = r.log(o.From, fn) 1253 } 1254 1255 if err != nil { 1256 return nil, err 1257 } 1258 1259 if o.FileName != nil { 1260 // for `git log --all` also check parent (if the next commit comes from the real parent) 1261 it = r.logWithFile(*o.FileName, it, o.All) 1262 } 1263 if o.PathFilter != nil { 1264 it = r.logWithPathFilter(o.PathFilter, it, o.All) 1265 } 1266 1267 if o.Since != nil || o.Until != nil { 1268 limitOptions := object.LogLimitOptions{Since: o.Since, Until: o.Until} 1269 it = r.logWithLimit(it, limitOptions) 1270 } 1271 1272 return it, nil 1273} 1274 1275func (r *Repository) log(from plumbing.Hash, commitIterFunc func(*object.Commit) object.CommitIter) (object.CommitIter, error) { 1276 h := from 1277 if from == plumbing.ZeroHash { 1278 head, err := r.Head() 1279 if err != nil { 1280 return nil, err 1281 } 1282 1283 h = head.Hash() 1284 } 1285 1286 commit, err := r.CommitObject(h) 1287 if err != nil { 1288 return nil, err 1289 } 1290 return commitIterFunc(commit), nil 1291} 1292 1293func (r *Repository) logAll(commitIterFunc func(*object.Commit) object.CommitIter) (object.CommitIter, error) { 1294 return object.NewCommitAllIter(r.Storer, commitIterFunc) 1295} 1296 1297func (*Repository) logWithFile(fileName string, commitIter object.CommitIter, checkParent bool) object.CommitIter { 1298 return object.NewCommitPathIterFromIter( 1299 func(path string) bool { 1300 return path == fileName 1301 }, 1302 commitIter, 1303 checkParent, 1304 ) 1305} 1306 1307func (*Repository) logWithPathFilter(pathFilter func(string) bool, commitIter object.CommitIter, checkParent bool) object.CommitIter { 1308 return object.NewCommitPathIterFromIter( 1309 pathFilter, 1310 commitIter, 1311 checkParent, 1312 ) 1313} 1314 1315func (*Repository) logWithLimit(commitIter object.CommitIter, limitOptions object.LogLimitOptions) object.CommitIter { 1316 return object.NewCommitLimitIterFromIter(commitIter, limitOptions) 1317} 1318 1319func commitIterFunc(order LogOrder) func(c *object.Commit) object.CommitIter { 1320 switch order { 1321 case LogOrderDefault: 1322 return func(c *object.Commit) object.CommitIter { 1323 return object.NewCommitPreorderIter(c, nil, nil) 1324 } 1325 case LogOrderDFS: 1326 return func(c *object.Commit) object.CommitIter { 1327 return object.NewCommitPreorderIter(c, nil, nil) 1328 } 1329 case LogOrderDFSPost: 1330 return func(c *object.Commit) object.CommitIter { 1331 return object.NewCommitPostorderIter(c, nil) 1332 } 1333 case LogOrderBSF: 1334 return func(c *object.Commit) object.CommitIter { 1335 return object.NewCommitIterBSF(c, nil, nil) 1336 } 1337 case LogOrderCommitterTime: 1338 return func(c *object.Commit) object.CommitIter { 1339 return object.NewCommitIterCTime(c, nil, nil) 1340 } 1341 } 1342 return nil 1343} 1344 1345// Tags returns all the tag References in a repository. 1346// 1347// If you want to check to see if the tag is an annotated tag, you can call 1348// TagObject on the hash Reference passed in through ForEach: 1349// 1350// iter, err := r.Tags() 1351// if err != nil { 1352// // Handle error 1353// } 1354// 1355// if err := iter.ForEach(func (ref *plumbing.Reference) error { 1356// obj, err := r.TagObject(ref.Hash()) 1357// switch err { 1358// case nil: 1359// // Tag object present 1360// case plumbing.ErrObjectNotFound: 1361// // Not a tag object 1362// default: 1363// // Some other error 1364// return err 1365// } 1366// }); err != nil { 1367// // Handle outer iterator error 1368// } 1369func (r *Repository) Tags() (storer.ReferenceIter, error) { 1370 refIter, err := r.Storer.IterReferences() 1371 if err != nil { 1372 return nil, err 1373 } 1374 1375 return storer.NewReferenceFilteredIter( 1376 func(r *plumbing.Reference) bool { 1377 return r.Name().IsTag() 1378 }, refIter), nil 1379} 1380 1381// Branches returns all the References that are Branches. 1382func (r *Repository) Branches() (storer.ReferenceIter, error) { 1383 refIter, err := r.Storer.IterReferences() 1384 if err != nil { 1385 return nil, err 1386 } 1387 1388 return storer.NewReferenceFilteredIter( 1389 func(r *plumbing.Reference) bool { 1390 return r.Name().IsBranch() 1391 }, refIter), nil 1392} 1393 1394// Notes returns all the References that are notes. For more information: 1395// https://git-scm.com/docs/git-notes 1396func (r *Repository) Notes() (storer.ReferenceIter, error) { 1397 refIter, err := r.Storer.IterReferences() 1398 if err != nil { 1399 return nil, err 1400 } 1401 1402 return storer.NewReferenceFilteredIter( 1403 func(r *plumbing.Reference) bool { 1404 return r.Name().IsNote() 1405 }, refIter), nil 1406} 1407 1408// TreeObject return a Tree with the given hash. If not found 1409// plumbing.ErrObjectNotFound is returned 1410func (r *Repository) TreeObject(h plumbing.Hash) (*object.Tree, error) { 1411 return object.GetTree(r.Storer, h) 1412} 1413 1414// TreeObjects returns an unsorted TreeIter with all the trees in the repository 1415func (r *Repository) TreeObjects() (*object.TreeIter, error) { 1416 iter, err := r.Storer.IterEncodedObjects(plumbing.TreeObject) 1417 if err != nil { 1418 return nil, err 1419 } 1420 1421 return object.NewTreeIter(r.Storer, iter), nil 1422} 1423 1424// CommitObject return a Commit with the given hash. If not found 1425// plumbing.ErrObjectNotFound is returned. 1426func (r *Repository) CommitObject(h plumbing.Hash) (*object.Commit, error) { 1427 return object.GetCommit(r.Storer, h) 1428} 1429 1430// CommitObjects returns an unsorted CommitIter with all the commits in the repository. 1431func (r *Repository) CommitObjects() (object.CommitIter, error) { 1432 iter, err := r.Storer.IterEncodedObjects(plumbing.CommitObject) 1433 if err != nil { 1434 return nil, err 1435 } 1436 1437 return object.NewCommitIter(r.Storer, iter), nil 1438} 1439 1440// BlobObject returns a Blob with the given hash. If not found 1441// plumbing.ErrObjectNotFound is returned. 1442func (r *Repository) BlobObject(h plumbing.Hash) (*object.Blob, error) { 1443 return object.GetBlob(r.Storer, h) 1444} 1445 1446// BlobObjects returns an unsorted BlobIter with all the blobs in the repository. 1447func (r *Repository) BlobObjects() (*object.BlobIter, error) { 1448 iter, err := r.Storer.IterEncodedObjects(plumbing.BlobObject) 1449 if err != nil { 1450 return nil, err 1451 } 1452 1453 return object.NewBlobIter(r.Storer, iter), nil 1454} 1455 1456// TagObject returns a Tag with the given hash. If not found 1457// plumbing.ErrObjectNotFound is returned. This method only returns 1458// annotated Tags, no lightweight Tags. 1459func (r *Repository) TagObject(h plumbing.Hash) (*object.Tag, error) { 1460 return object.GetTag(r.Storer, h) 1461} 1462 1463// TagObjects returns a unsorted TagIter that can step through all of the annotated 1464// tags in the repository. 1465func (r *Repository) TagObjects() (*object.TagIter, error) { 1466 iter, err := r.Storer.IterEncodedObjects(plumbing.TagObject) 1467 if err != nil { 1468 return nil, err 1469 } 1470 1471 return object.NewTagIter(r.Storer, iter), nil 1472} 1473 1474// Object returns an Object with the given hash. If not found 1475// plumbing.ErrObjectNotFound is returned. 1476func (r *Repository) Object(t plumbing.ObjectType, h plumbing.Hash) (object.Object, error) { 1477 obj, err := r.Storer.EncodedObject(t, h) 1478 if err != nil { 1479 return nil, err 1480 } 1481 1482 return object.DecodeObject(r.Storer, obj) 1483} 1484 1485// Objects returns an unsorted ObjectIter with all the objects in the repository. 1486func (r *Repository) Objects() (*object.ObjectIter, error) { 1487 iter, err := r.Storer.IterEncodedObjects(plumbing.AnyObject) 1488 if err != nil { 1489 return nil, err 1490 } 1491 1492 return object.NewObjectIter(r.Storer, iter), nil 1493} 1494 1495// Head returns the reference where HEAD is pointing to. 1496func (r *Repository) Head() (*plumbing.Reference, error) { 1497 return storer.ResolveReference(r.Storer, plumbing.HEAD) 1498} 1499 1500// Reference returns the reference for a given reference name. If resolved is 1501// true, any symbolic reference will be resolved. 1502func (r *Repository) Reference(name plumbing.ReferenceName, resolved bool) ( 1503 *plumbing.Reference, error) { 1504 1505 if resolved { 1506 return storer.ResolveReference(r.Storer, name) 1507 } 1508 1509 return r.Storer.Reference(name) 1510} 1511 1512// References returns an unsorted ReferenceIter for all references. 1513func (r *Repository) References() (storer.ReferenceIter, error) { 1514 return r.Storer.IterReferences() 1515} 1516 1517// Worktree returns a worktree based on the given fs, if nil the default 1518// worktree will be used. 1519func (r *Repository) Worktree() (*Worktree, error) { 1520 if r.wt == nil { 1521 return nil, ErrIsBareRepository 1522 } 1523 1524 return &Worktree{r: r, Filesystem: r.wt}, nil 1525} 1526 1527func expand_ref(s storer.ReferenceStorer, ref plumbing.ReferenceName) (*plumbing.Reference, error) { 1528 // For improving troubleshooting, this preserves the error for the provided `ref`, 1529 // and returns the error for that specific ref in case all parse rules fails. 1530 var ret error 1531 for _, rule := range plumbing.RefRevParseRules { 1532 resolvedRef, err := storer.ResolveReference(s, plumbing.ReferenceName(fmt.Sprintf(rule, ref))) 1533 1534 if err == nil { 1535 return resolvedRef, nil 1536 } else if ret == nil { 1537 ret = err 1538 } 1539 } 1540 1541 return nil, ret 1542} 1543 1544// ResolveRevision resolves revision to corresponding hash. It will always 1545// resolve to a commit hash, not a tree or annotated tag. 1546// 1547// Implemented resolvers : HEAD, branch, tag, heads/branch, refs/heads/branch, 1548// refs/tags/tag, refs/remotes/origin/branch, refs/remotes/origin/HEAD, tilde and caret (HEAD~1, master~^, tag~2, ref/heads/master~1, ...), selection by text (HEAD^{/fix nasty bug}), hash (prefix and full) 1549func (r *Repository) ResolveRevision(in plumbing.Revision) (*plumbing.Hash, error) { 1550 rev := in.String() 1551 if rev == "" { 1552 return &plumbing.ZeroHash, plumbing.ErrReferenceNotFound 1553 } 1554 1555 p := revision.NewParserFromString(rev) 1556 items, err := p.Parse() 1557 1558 if err != nil { 1559 return nil, err 1560 } 1561 1562 var commit *object.Commit 1563 1564 for _, item := range items { 1565 switch item := item.(type) { 1566 case revision.Ref: 1567 revisionRef := item 1568 1569 var tryHashes []plumbing.Hash 1570 1571 tryHashes = append(tryHashes, r.resolveHashPrefix(string(revisionRef))...) 1572 1573 ref, err := expand_ref(r.Storer, plumbing.ReferenceName(revisionRef)) 1574 if err == nil { 1575 tryHashes = append(tryHashes, ref.Hash()) 1576 } 1577 1578 // in ambiguous cases, `git rev-parse` will emit a warning, but 1579 // will always return the oid in preference to a ref; we don't have 1580 // the ability to emit a warning here, so (for speed purposes) 1581 // don't bother to detect the ambiguity either, just return in the 1582 // priority that git would. 1583 gotOne := false 1584 for _, hash := range tryHashes { 1585 commitObj, err := r.CommitObject(hash) 1586 if err == nil { 1587 commit = commitObj 1588 gotOne = true 1589 break 1590 } 1591 1592 tagObj, err := r.TagObject(hash) 1593 if err == nil { 1594 // If the tag target lookup fails here, this most likely 1595 // represents some sort of repo corruption, so let the 1596 // error bubble up. 1597 tagCommit, err := tagObj.Commit() 1598 if err != nil { 1599 return &plumbing.ZeroHash, err 1600 } 1601 commit = tagCommit 1602 gotOne = true 1603 break 1604 } 1605 } 1606 1607 if !gotOne { 1608 return &plumbing.ZeroHash, plumbing.ErrReferenceNotFound 1609 } 1610 1611 case revision.CaretPath: 1612 depth := item.Depth 1613 1614 if depth == 0 { 1615 break 1616 } 1617 1618 iter := commit.Parents() 1619 1620 c, err := iter.Next() 1621 1622 if err != nil { 1623 return &plumbing.ZeroHash, err 1624 } 1625 1626 if depth == 1 { 1627 commit = c 1628 1629 break 1630 } 1631 1632 c, err = iter.Next() 1633 1634 if err != nil { 1635 return &plumbing.ZeroHash, err 1636 } 1637 1638 commit = c 1639 case revision.TildePath: 1640 for i := 0; i < item.Depth; i++ { 1641 c, err := commit.Parents().Next() 1642 1643 if err != nil { 1644 return &plumbing.ZeroHash, err 1645 } 1646 1647 commit = c 1648 } 1649 case revision.CaretReg: 1650 history := object.NewCommitPreorderIter(commit, nil, nil) 1651 1652 re := item.Regexp 1653 negate := item.Negate 1654 1655 var c *object.Commit 1656 1657 err := history.ForEach(func(hc *object.Commit) error { 1658 if !negate && re.MatchString(hc.Message) { 1659 c = hc 1660 return storer.ErrStop 1661 } 1662 1663 if negate && !re.MatchString(hc.Message) { 1664 c = hc 1665 return storer.ErrStop 1666 } 1667 1668 return nil 1669 }) 1670 if err != nil { 1671 return &plumbing.ZeroHash, err 1672 } 1673 1674 if c == nil { 1675 return &plumbing.ZeroHash, fmt.Errorf("no commit message match regexp: %q", re.String()) 1676 } 1677 1678 commit = c 1679 } 1680 } 1681 1682 if commit == nil { 1683 return &plumbing.ZeroHash, plumbing.ErrReferenceNotFound 1684 } 1685 1686 return &commit.Hash, nil 1687} 1688 1689// resolveHashPrefix returns a list of potential hashes that the given string 1690// is a prefix of. It quietly swallows errors, returning nil. 1691func (r *Repository) resolveHashPrefix(hashStr string) []plumbing.Hash { 1692 // Handle complete and partial hashes. 1693 // plumbing.NewHash forces args into a full 20 byte hash, which isn't suitable 1694 // for partial hashes since they will become zero-filled. 1695 1696 if hashStr == "" { 1697 return nil 1698 } 1699 if len(hashStr) == len(plumbing.ZeroHash)*2 { 1700 // Only a full hash is possible. 1701 hexb, err := hex.DecodeString(hashStr) 1702 if err != nil { 1703 return nil 1704 } 1705 var h plumbing.Hash 1706 copy(h[:], hexb) 1707 return []plumbing.Hash{h} 1708 } 1709 1710 // Partial hash. 1711 // hex.DecodeString only decodes to complete bytes, so only works with pairs of hex digits. 1712 evenHex := hashStr[:len(hashStr)&^1] 1713 hexb, err := hex.DecodeString(evenHex) 1714 if err != nil { 1715 return nil 1716 } 1717 candidates := expandPartialHash(r.Storer, hexb) 1718 if len(evenHex) == len(hashStr) { 1719 // The prefix was an exact number of bytes. 1720 return candidates 1721 } 1722 // Do another prefix check to ensure the dangling nybble is correct. 1723 var hashes []plumbing.Hash 1724 for _, h := range candidates { 1725 if strings.HasPrefix(h.String(), hashStr) { 1726 hashes = append(hashes, h) 1727 } 1728 } 1729 return hashes 1730} 1731 1732type RepackConfig struct { 1733 // UseRefDeltas configures whether packfile encoder will use reference deltas. 1734 // By default OFSDeltaObject is used. 1735 UseRefDeltas bool 1736 // OnlyDeletePacksOlderThan if set to non-zero value 1737 // selects only objects older than the time provided. 1738 OnlyDeletePacksOlderThan time.Time 1739} 1740 1741func (r *Repository) RepackObjects(cfg *RepackConfig) (err error) { 1742 pos, ok := r.Storer.(storer.PackedObjectStorer) 1743 if !ok { 1744 return ErrPackedObjectsNotSupported 1745 } 1746 1747 // Get the existing object packs. 1748 hs, err := pos.ObjectPacks() 1749 if err != nil { 1750 return err 1751 } 1752 1753 // Create a new pack. 1754 nh, err := r.createNewObjectPack(cfg) 1755 if err != nil { 1756 return err 1757 } 1758 1759 // Delete old packs. 1760 for _, h := range hs { 1761 // Skip if new hash is the same as an old one. 1762 if h == nh { 1763 continue 1764 } 1765 err = pos.DeleteOldObjectPackAndIndex(h, cfg.OnlyDeletePacksOlderThan) 1766 if err != nil { 1767 return err 1768 } 1769 } 1770 1771 return nil 1772} 1773 1774// Merge merges the reference branch into the current branch. 1775// 1776// If the merge is not possible (or supported) returns an error without changing 1777// the HEAD for the current branch. Possible errors include: 1778// - The merge strategy is not supported. 1779// - The specific strategy cannot be used (e.g. using FastForwardMerge when one is not possible). 1780func (r *Repository) Merge(ref plumbing.Reference, opts MergeOptions) error { 1781 if opts.Strategy != FastForwardMerge { 1782 return ErrUnsupportedMergeStrategy 1783 } 1784 1785 // Ignore error as not having a shallow list is optional here. 1786 shallowList, _ := r.Storer.Shallow() 1787 var earliestShallow *plumbing.Hash 1788 if len(shallowList) > 0 { 1789 earliestShallow = &shallowList[0] 1790 } 1791 1792 head, err := r.Head() 1793 if err != nil { 1794 return err 1795 } 1796 1797 ff, err := isFastForward(r.Storer, head.Hash(), ref.Hash(), earliestShallow) 1798 if err != nil { 1799 return err 1800 } 1801 1802 if !ff { 1803 return ErrFastForwardMergeNotPossible 1804 } 1805 1806 return r.Storer.SetReference(plumbing.NewHashReference(head.Name(), ref.Hash())) 1807} 1808 1809// createNewObjectPack is a helper for RepackObjects taking care 1810// of creating a new pack. It is used so the PackfileWriter 1811// deferred close has the right scope. 1812func (r *Repository) createNewObjectPack(cfg *RepackConfig) (h plumbing.Hash, err error) { 1813 ow := newObjectWalker(r.Storer) 1814 err = ow.walkAllRefs() 1815 if err != nil { 1816 return h, err 1817 } 1818 objs := make([]plumbing.Hash, 0, len(ow.seen)) 1819 for h := range ow.seen { 1820 objs = append(objs, h) 1821 } 1822 pfw, ok := r.Storer.(storer.PackfileWriter) 1823 if !ok { 1824 return h, fmt.Errorf("Repository storer is not a storer.PackfileWriter") 1825 } 1826 wc, err := pfw.PackfileWriter() 1827 if err != nil { 1828 return h, err 1829 } 1830 defer ioutil.CheckClose(wc, &err) 1831 scfg, err := r.Config() 1832 if err != nil { 1833 return h, err 1834 } 1835 enc := packfile.NewEncoder(wc, r.Storer, cfg.UseRefDeltas) 1836 h, err = enc.Encode(objs, scfg.Pack.Window) 1837 if err != nil { 1838 return h, err 1839 } 1840 1841 // Delete the packed, loose objects. 1842 if los, ok := r.Storer.(storer.LooseObjectStorer); ok { 1843 err = los.ForEachObjectHash(func(hash plumbing.Hash) error { 1844 if ow.isSeen(hash) { 1845 err = los.DeleteLooseObject(hash) 1846 if err != nil { 1847 return err 1848 } 1849 } 1850 return nil 1851 }) 1852 if err != nil { 1853 return h, err 1854 } 1855 } 1856 1857 return h, err 1858} 1859 1860func expandPartialHash(st storer.EncodedObjectStorer, prefix []byte) (hashes []plumbing.Hash) { 1861 // The fast version is implemented by storage/filesystem.ObjectStorage. 1862 type fastIter interface { 1863 HashesWithPrefix(prefix []byte) ([]plumbing.Hash, error) 1864 } 1865 if fi, ok := st.(fastIter); ok { 1866 h, err := fi.HashesWithPrefix(prefix) 1867 if err != nil { 1868 return nil 1869 } 1870 return h 1871 } 1872 1873 // Slow path. 1874 iter, err := st.IterEncodedObjects(plumbing.AnyObject) 1875 if err != nil { 1876 return nil 1877 } 1878 iter.ForEach(func(obj plumbing.EncodedObject) error { 1879 h := obj.Hash() 1880 if bytes.HasPrefix(h[:], prefix) { 1881 hashes = append(hashes, h) 1882 } 1883 return nil 1884 }) 1885 return 1886}