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