diff options
Diffstat (limited to 'repository.go')
-rw-r--r-- | repository.go | 201 |
1 files changed, 147 insertions, 54 deletions
diff --git a/repository.go b/repository.go index e8eb53f..3154ac0 100644 --- a/repository.go +++ b/repository.go @@ -3,32 +3,36 @@ package git import ( "bytes" "context" + "crypto" "encoding/hex" "errors" "fmt" - stdioutil "io/ioutil" + "io" "os" "path" "path/filepath" "strings" "time" + "dario.cat/mergo" "github.com/ProtonMail/go-crypto/openpgp" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/osfs" "github.com/go-git/go-billy/v5/util" "github.com/go-git/go-git/v5/config" + "github.com/go-git/go-git/v5/internal/path_util" "github.com/go-git/go-git/v5/internal/revision" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/cache" + formatcfg "github.com/go-git/go-git/v5/plumbing/format/config" "github.com/go-git/go-git/v5/plumbing/format/packfile" + "github.com/go-git/go-git/v5/plumbing/hash" "github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/plumbing/storer" "github.com/go-git/go-git/v5/storage" "github.com/go-git/go-git/v5/storage/filesystem" "github.com/go-git/go-git/v5/storage/filesystem/dotgit" "github.com/go-git/go-git/v5/utils/ioutil" - "github.com/imdario/mergo" ) // GitDirName this is a special folder where all the git stuff is. @@ -57,6 +61,7 @@ var ( ErrIsBareRepository = errors.New("worktree not available in a bare repository") ErrUnableToResolveCommit = errors.New("unable to resolve commit") ErrPackedObjectsNotSupported = errors.New("packed objects not supported") + ErrSHA256NotSupported = errors.New("go-git was not compiled with SHA256 support") ) // Repository represents a git repository @@ -67,14 +72,30 @@ type Repository struct { wt billy.Filesystem } +type InitOptions struct { + // The default branch (e.g. "refs/heads/master") + DefaultBranch plumbing.ReferenceName +} + // Init creates an empty git repository, based on the given Storer and worktree. // The worktree Filesystem is optional, if nil a bare repository is created. If // the given storer is not empty ErrRepositoryAlreadyExists is returned func Init(s storage.Storer, worktree billy.Filesystem) (*Repository, error) { + options := InitOptions{ + DefaultBranch: plumbing.Master, + } + return InitWithOptions(s, worktree, options) +} + +func InitWithOptions(s storage.Storer, worktree billy.Filesystem, options InitOptions) (*Repository, error) { if err := initStorer(s); err != nil { return nil, err } + if options.DefaultBranch == "" { + options.DefaultBranch = plumbing.Master + } + r := newRepository(s, worktree) _, err := r.Reference(plumbing.HEAD, false) switch err { @@ -85,7 +106,7 @@ func Init(s storage.Storer, worktree billy.Filesystem) (*Repository, error) { return nil, err } - h := plumbing.NewSymbolicReference(plumbing.HEAD, plumbing.Master) + h := plumbing.NewSymbolicReference(plumbing.HEAD, options.DefaultBranch) if err := s.SetReference(h); err != nil { return nil, err } @@ -228,6 +249,39 @@ func PlainInit(path string, isBare bool) (*Repository, error) { return Init(s, wt) } +func PlainInitWithOptions(path string, opts *PlainInitOptions) (*Repository, error) { + wt := osfs.New(path) + dot, _ := wt.Chroot(GitDirName) + + s := filesystem.NewStorage(dot, cache.NewObjectLRUDefault()) + + r, err := Init(s, wt) + if err != nil { + return nil, err + } + + cfg, err := r.Config() + if err != nil { + return nil, err + } + + if opts != nil { + if opts.ObjectFormat == formatcfg.SHA256 && hash.CryptoType != crypto.SHA256 { + return nil, ErrSHA256NotSupported + } + + cfg.Core.RepositoryFormatVersion = formatcfg.Version_1 + cfg.Extensions.ObjectFormat = opts.ObjectFormat + } + + err = r.Storer.SetConfig(cfg) + if err != nil { + return nil, err + } + + return r, err +} + // PlainOpen opens a git repository from the given path. It detects if the // repository is bare or a normal one. If the path doesn't contain a valid // repository ErrRepositoryNotExists is returned @@ -269,6 +323,11 @@ func PlainOpenWithOptions(path string, o *PlainOpenOptions) (*Repository, error) } func dotGitToOSFilesystems(path string, detect bool) (dot, wt billy.Filesystem, err error) { + path, err = path_util.ReplaceTildeWithHome(path) + if err != nil { + return nil, nil, err + } + if path, err = filepath.Abs(path); err != nil { return nil, nil, err } @@ -280,6 +339,9 @@ func dotGitToOSFilesystems(path string, detect bool) (dot, wt billy.Filesystem, pathinfo, err := fs.Stat("/") if !os.IsNotExist(err) { + if pathinfo == nil { + return nil, nil, err + } if !pathinfo.IsDir() && detect { fs = osfs.New(filepath.Dir(path)) } @@ -327,7 +389,7 @@ func dotGitFileToOSFilesystem(path string, fs billy.Filesystem) (bfs billy.Files } defer ioutil.CheckClose(f, &err) - b, err := stdioutil.ReadAll(f) + b, err := io.ReadAll(f) if err != nil { return nil, err } @@ -356,7 +418,7 @@ func dotGitCommonDirectory(fs billy.Filesystem) (commonDir billy.Filesystem, err return nil, err } - b, err := stdioutil.ReadAll(f) + b, err := io.ReadAll(f) if err != nil { return nil, err } @@ -404,6 +466,9 @@ func PlainCloneContext(ctx context.Context, path string, isBare bool, o *CloneOp return nil, err } + if o.Mirror { + isBare = true + } r, err := PlainInit(path, isBare) if err != nil { return nil, err @@ -747,21 +812,20 @@ func (r *Repository) buildTagSignature(tag *object.Tag, signKey *openpgp.Entity) // If you want to check to see if the tag is an annotated tag, you can call // TagObject on the hash of the reference in ForEach: // -// ref, err := r.Tag("v0.1.0") -// if err != nil { -// // Handle error -// } -// -// obj, err := r.TagObject(ref.Hash()) -// switch err { -// case nil: -// // Tag object present -// case plumbing.ErrObjectNotFound: -// // Not a tag object -// default: -// // Some other error -// } +// ref, err := r.Tag("v0.1.0") +// if err != nil { +// // Handle error +// } // +// obj, err := r.TagObject(ref.Hash()) +// switch err { +// case nil: +// // Tag object present +// case plumbing.ErrObjectNotFound: +// // Not a tag object +// default: +// // Some other error +// } func (r *Repository) Tag(name string) (*plumbing.Reference, error) { ref, err := r.Reference(plumbing.ReferenceName(path.Join("refs", "tags", name)), false) if err != nil { @@ -812,9 +876,10 @@ func (r *Repository) clone(ctx context.Context, o *CloneOptions) error { } c := &config.RemoteConfig{ - Name: o.RemoteName, - URLs: []string{o.URL}, - Fetch: r.cloneRefSpec(o), + Name: o.RemoteName, + URLs: []string{o.URL}, + Fetch: r.cloneRefSpec(o), + Mirror: o.Mirror, } if _, err := r.CreateRemote(c); err != nil { @@ -830,6 +895,7 @@ func (r *Repository) clone(ctx context.Context, o *CloneOptions) error { RemoteName: o.RemoteName, InsecureSkipTLS: o.InsecureSkipTLS, CABundle: o.CABundle, + ProxyOptions: o.ProxyOptions, }, o.ReferenceName) if err != nil { return err @@ -856,7 +922,13 @@ func (r *Repository) clone(ctx context.Context, o *CloneOptions) error { if o.RecurseSubmodules != NoRecurseSubmodules { if err := w.updateSubmodules(&SubmoduleUpdateOptions{ RecurseSubmodules: o.RecurseSubmodules, - Auth: o.Auth, + Depth: func() int { + if o.ShallowSubmodules { + return 1 + } + return 0 + }(), + Auth: o.Auth, }); err != nil { return err } @@ -867,7 +939,7 @@ func (r *Repository) clone(ctx context.Context, o *CloneOptions) error { return err } - if ref.Name().IsBranch() { + if !o.Mirror && ref.Name().IsBranch() { branchRef := ref.Name() branchName := strings.Split(string(branchRef), "refs/heads/")[1] @@ -898,6 +970,8 @@ const ( func (r *Repository) cloneRefSpec(o *CloneOptions) []config.RefSpec { switch { + case o.Mirror: + return []config.RefSpec{"+refs/*:refs/*"} case o.ReferenceName.IsTag(): return []config.RefSpec{ config.RefSpec(fmt.Sprintf(refspecTag, o.ReferenceName.Short())), @@ -905,7 +979,6 @@ func (r *Repository) cloneRefSpec(o *CloneOptions) []config.RefSpec { case o.SingleBranch && o.ReferenceName == plumbing.HEAD: return []config.RefSpec{ config.RefSpec(fmt.Sprintf(refspecSingleBranchHEAD, o.RemoteName)), - config.RefSpec(fmt.Sprintf(refspecSingleBranch, plumbing.Master.Short(), o.RemoteName)), } case o.SingleBranch: return []config.RefSpec{ @@ -967,7 +1040,7 @@ func (r *Repository) fetchAndUpdateReferences( return nil, err } - resolvedRef, err := storer.ResolveReference(remoteRefs, ref) + resolvedRef, err := expand_ref(remoteRefs, ref) if err != nil { return nil, err } @@ -1238,26 +1311,25 @@ func commitIterFunc(order LogOrder) func(c *object.Commit) object.CommitIter { // If you want to check to see if the tag is an annotated tag, you can call // TagObject on the hash Reference passed in through ForEach: // -// iter, err := r.Tags() -// if err != nil { -// // Handle error -// } -// -// if err := iter.ForEach(func (ref *plumbing.Reference) error { -// obj, err := r.TagObject(ref.Hash()) -// switch err { -// case nil: -// // Tag object present -// case plumbing.ErrObjectNotFound: -// // Not a tag object -// default: -// // Some other error -// return err -// } -// }); err != nil { -// // Handle outer iterator error -// } +// iter, err := r.Tags() +// if err != nil { +// // Handle error +// } // +// if err := iter.ForEach(func (ref *plumbing.Reference) error { +// obj, err := r.TagObject(ref.Hash()) +// switch err { +// case nil: +// // Tag object present +// case plumbing.ErrObjectNotFound: +// // Not a tag object +// default: +// // Some other error +// return err +// } +// }); err != nil { +// // Handle outer iterator error +// } func (r *Repository) Tags() (storer.ReferenceIter, error) { refIter, err := r.Storer.IterReferences() if err != nil { @@ -1416,14 +1488,35 @@ func (r *Repository) Worktree() (*Worktree, error) { return &Worktree{r: r, Filesystem: r.wt}, nil } +func expand_ref(s storer.ReferenceStorer, ref plumbing.ReferenceName) (*plumbing.Reference, error) { + // For improving troubleshooting, this preserves the error for the provided `ref`, + // and returns the error for that specific ref in case all parse rules fails. + var ret error + for _, rule := range plumbing.RefRevParseRules { + resolvedRef, err := storer.ResolveReference(s, plumbing.ReferenceName(fmt.Sprintf(rule, ref))) + + if err == nil { + return resolvedRef, nil + } else if ret == nil { + ret = err + } + } + + return nil, ret +} + // ResolveRevision resolves revision to corresponding hash. It will always // resolve to a commit hash, not a tree or annotated tag. // // Implemented resolvers : HEAD, branch, tag, heads/branch, refs/heads/branch, // 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) -func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, error) { - p := revision.NewParserFromString(string(rev)) +func (r *Repository) ResolveRevision(in plumbing.Revision) (*plumbing.Hash, error) { + rev := in.String() + if rev == "" { + return &plumbing.ZeroHash, plumbing.ErrReferenceNotFound + } + p := revision.NewParserFromString(rev) items, err := p.Parse() if err != nil { @@ -1441,13 +1534,9 @@ func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, err tryHashes = append(tryHashes, r.resolveHashPrefix(string(revisionRef))...) - for _, rule := range append([]string{"%s"}, plumbing.RefRevParseRules...) { - ref, err := storer.ResolveReference(r.Storer, plumbing.ReferenceName(fmt.Sprintf(rule, revisionRef))) - - if err == nil { - tryHashes = append(tryHashes, ref.Hash()) - break - } + ref, err := expand_ref(r.Storer, plumbing.ReferenceName(revisionRef)) + if err == nil { + tryHashes = append(tryHashes, ref.Hash()) } // in ambiguous cases, `git rev-parse` will emit a warning, but @@ -1554,6 +1643,10 @@ func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, err } } + if commit == nil { + return &plumbing.ZeroHash, plumbing.ErrReferenceNotFound + } + return &commit.Hash, nil } |