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}