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