| package git |
| |
| import ( |
| "bytes" |
| "context" |
| "errors" |
| "fmt" |
| "io" |
| stdioutil "io/ioutil" |
| "os" |
| "path" |
| "path/filepath" |
| "strings" |
| "time" |
| |
| "golang.org/x/crypto/openpgp" |
| "gopkg.in/src-d/go-git.v4/config" |
| "gopkg.in/src-d/go-git.v4/internal/revision" |
| "gopkg.in/src-d/go-git.v4/plumbing" |
| "gopkg.in/src-d/go-git.v4/plumbing/cache" |
| "gopkg.in/src-d/go-git.v4/plumbing/format/packfile" |
| "gopkg.in/src-d/go-git.v4/plumbing/object" |
| "gopkg.in/src-d/go-git.v4/plumbing/storer" |
| "gopkg.in/src-d/go-git.v4/storage" |
| "gopkg.in/src-d/go-git.v4/storage/filesystem" |
| "gopkg.in/src-d/go-git.v4/utils/ioutil" |
| |
| "gopkg.in/src-d/go-billy.v4" |
| "gopkg.in/src-d/go-billy.v4/osfs" |
| ) |
| |
| // GitDirName this is a special folder where all the git stuff is. |
| const GitDirName = ".git" |
| |
| var ( |
| // ErrBranchExists an error stating the specified branch already exists |
| ErrBranchExists = errors.New("branch already exists") |
| // ErrBranchNotFound an error stating the specified branch does not exist |
| ErrBranchNotFound = errors.New("branch not found") |
| // ErrTagExists an error stating the specified tag already exists |
| ErrTagExists = errors.New("tag already exists") |
| // ErrTagNotFound an error stating the specified tag does not exist |
| ErrTagNotFound = errors.New("tag not found") |
| // ErrFetching is returned when the packfile could not be downloaded |
| ErrFetching = errors.New("unable to fetch packfile") |
| |
| ErrInvalidReference = errors.New("invalid reference, should be a tag or a branch") |
| ErrRepositoryNotExists = errors.New("repository does not exist") |
| ErrRepositoryAlreadyExists = errors.New("repository already exists") |
| ErrRemoteNotFound = errors.New("remote not found") |
| ErrRemoteExists = errors.New("remote already exists") |
| ErrAnonymousRemoteName = errors.New("anonymous remote name must be 'anonymous'") |
| ErrWorktreeNotProvided = errors.New("worktree should be provided") |
| ErrIsBareRepository = errors.New("worktree not available in a bare repository") |
| ErrUnableToResolveCommit = errors.New("unable to resolve commit") |
| ErrPackedObjectsNotSupported = errors.New("Packed objects not supported") |
| ) |
| |
| // Repository represents a git repository |
| type Repository struct { |
| Storer storage.Storer |
| |
| r map[string]*Remote |
| wt billy.Filesystem |
| } |
| |
| // 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) { |
| if err := initStorer(s); err != nil { |
| return nil, err |
| } |
| |
| r := newRepository(s, worktree) |
| _, err := r.Reference(plumbing.HEAD, false) |
| switch err { |
| case plumbing.ErrReferenceNotFound: |
| case nil: |
| return nil, ErrRepositoryAlreadyExists |
| default: |
| return nil, err |
| } |
| |
| h := plumbing.NewSymbolicReference(plumbing.HEAD, plumbing.Master) |
| if err := s.SetReference(h); err != nil { |
| return nil, err |
| } |
| |
| if worktree == nil { |
| r.setIsBare(true) |
| return r, nil |
| } |
| |
| return r, setWorktreeAndStoragePaths(r, worktree) |
| } |
| |
| func initStorer(s storer.Storer) error { |
| i, ok := s.(storer.Initializer) |
| if !ok { |
| return nil |
| } |
| |
| return i.Init() |
| } |
| |
| func setWorktreeAndStoragePaths(r *Repository, worktree billy.Filesystem) error { |
| type fsBased interface { |
| Filesystem() billy.Filesystem |
| } |
| |
| // .git file is only created if the storage is file based and the file |
| // system is osfs.OS |
| fs, isFSBased := r.Storer.(fsBased) |
| if !isFSBased { |
| return nil |
| } |
| |
| if err := createDotGitFile(worktree, fs.Filesystem()); err != nil { |
| return err |
| } |
| |
| return setConfigWorktree(r, worktree, fs.Filesystem()) |
| } |
| |
| func createDotGitFile(worktree, storage billy.Filesystem) error { |
| path, err := filepath.Rel(worktree.Root(), storage.Root()) |
| if err != nil { |
| path = storage.Root() |
| } |
| |
| if path == GitDirName { |
| // not needed, since the folder is the default place |
| return nil |
| } |
| |
| f, err := worktree.Create(GitDirName) |
| if err != nil { |
| return err |
| } |
| |
| defer f.Close() |
| _, err = fmt.Fprintf(f, "gitdir: %s\n", path) |
| return err |
| } |
| |
| func setConfigWorktree(r *Repository, worktree, storage billy.Filesystem) error { |
| path, err := filepath.Rel(storage.Root(), worktree.Root()) |
| if err != nil { |
| path = worktree.Root() |
| } |
| |
| if path == ".." { |
| // not needed, since the folder is the default place |
| return nil |
| } |
| |
| cfg, err := r.Storer.Config() |
| if err != nil { |
| return err |
| } |
| |
| cfg.Core.Worktree = path |
| return r.Storer.SetConfig(cfg) |
| } |
| |
| // Open opens a git repository using the given Storer and worktree filesystem, |
| // if the given storer is complete empty ErrRepositoryNotExists is returned. |
| // The worktree can be nil when the repository being opened is bare, if the |
| // repository is a normal one (not bare) and worktree is nil the err |
| // ErrWorktreeNotProvided is returned |
| func Open(s storage.Storer, worktree billy.Filesystem) (*Repository, error) { |
| _, err := s.Reference(plumbing.HEAD) |
| if err == plumbing.ErrReferenceNotFound { |
| return nil, ErrRepositoryNotExists |
| } |
| |
| if err != nil { |
| return nil, err |
| } |
| |
| return newRepository(s, worktree), nil |
| } |
| |
| // Clone a repository into the given Storer and worktree Filesystem with the |
| // given options, if worktree is nil a bare repository is created. If the given |
| // storer is not empty ErrRepositoryAlreadyExists is returned. |
| // |
| // The provided Context must be non-nil. If the context expires before the |
| // operation is complete, an error is returned. The context only affects to the |
| // transport operations. |
| func Clone(s storage.Storer, worktree billy.Filesystem, o *CloneOptions) (*Repository, error) { |
| return CloneContext(context.Background(), s, worktree, o) |
| } |
| |
| // CloneContext a repository into the given Storer and worktree Filesystem with |
| // the given options, if worktree is nil a bare repository is created. If the |
| // given storer is not empty ErrRepositoryAlreadyExists is returned. |
| // |
| // The provided Context must be non-nil. If the context expires before the |
| // operation is complete, an error is returned. The context only affects to the |
| // transport operations. |
| func CloneContext( |
| ctx context.Context, s storage.Storer, worktree billy.Filesystem, o *CloneOptions, |
| ) (*Repository, error) { |
| r, err := Init(s, worktree) |
| if err != nil { |
| return nil, err |
| } |
| |
| return r, r.clone(ctx, o) |
| } |
| |
| // PlainInit create an empty git repository at the given path. isBare defines |
| // if the repository will have worktree (non-bare) or not (bare), if the path |
| // is not empty ErrRepositoryAlreadyExists is returned. |
| func PlainInit(path string, isBare bool) (*Repository, error) { |
| var wt, dot billy.Filesystem |
| |
| if isBare { |
| dot = osfs.New(path) |
| } else { |
| wt = osfs.New(path) |
| dot, _ = wt.Chroot(GitDirName) |
| } |
| |
| s := filesystem.NewStorage(dot, cache.NewObjectLRUDefault()) |
| |
| return Init(s, wt) |
| } |
| |
| // 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 |
| func PlainOpen(path string) (*Repository, error) { |
| return PlainOpenWithOptions(path, &PlainOpenOptions{}) |
| } |
| |
| // PlainOpenWithOptions opens a git repository from the given path with specific |
| // options. See PlainOpen for more info. |
| func PlainOpenWithOptions(path string, o *PlainOpenOptions) (*Repository, error) { |
| dot, wt, err := dotGitToOSFilesystems(path, o.DetectDotGit) |
| if err != nil { |
| return nil, err |
| } |
| |
| if _, err := dot.Stat(""); err != nil { |
| if os.IsNotExist(err) { |
| return nil, ErrRepositoryNotExists |
| } |
| |
| return nil, err |
| } |
| |
| s := filesystem.NewStorage(dot, cache.NewObjectLRUDefault()) |
| |
| return Open(s, wt) |
| } |
| |
| func dotGitToOSFilesystems(path string, detect bool) (dot, wt billy.Filesystem, err error) { |
| if path, err = filepath.Abs(path); err != nil { |
| return nil, nil, err |
| } |
| var fs billy.Filesystem |
| var fi os.FileInfo |
| for { |
| fs = osfs.New(path) |
| fi, err = fs.Stat(GitDirName) |
| if err == nil { |
| // no error; stop |
| break |
| } |
| if !os.IsNotExist(err) { |
| // unknown error; stop |
| return nil, nil, err |
| } |
| if detect { |
| // try its parent as long as we haven't reached |
| // the root dir |
| if dir := filepath.Dir(path); dir != path { |
| path = dir |
| continue |
| } |
| } |
| // not detecting via parent dirs and the dir does not exist; |
| // stop |
| return fs, nil, nil |
| } |
| |
| if fi.IsDir() { |
| dot, err = fs.Chroot(GitDirName) |
| return dot, fs, err |
| } |
| |
| dot, err = dotGitFileToOSFilesystem(path, fs) |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| return dot, fs, nil |
| } |
| |
| func dotGitFileToOSFilesystem(path string, fs billy.Filesystem) (bfs billy.Filesystem, err error) { |
| f, err := fs.Open(GitDirName) |
| if err != nil { |
| return nil, err |
| } |
| defer ioutil.CheckClose(f, &err) |
| |
| b, err := stdioutil.ReadAll(f) |
| if err != nil { |
| return nil, err |
| } |
| |
| line := string(b) |
| const prefix = "gitdir: " |
| if !strings.HasPrefix(line, prefix) { |
| return nil, fmt.Errorf(".git file has no %s prefix", prefix) |
| } |
| |
| gitdir := strings.Split(line[len(prefix):], "\n")[0] |
| gitdir = strings.TrimSpace(gitdir) |
| if filepath.IsAbs(gitdir) { |
| return osfs.New(gitdir), nil |
| } |
| |
| return osfs.New(fs.Join(path, gitdir)), nil |
| } |
| |
| // PlainClone a repository into the path with the given options, isBare defines |
| // if the new repository will be bare or normal. If the path is not empty |
| // ErrRepositoryAlreadyExists is returned. |
| // |
| // TODO(mcuadros): move isBare to CloneOptions in v5 |
| func PlainClone(path string, isBare bool, o *CloneOptions) (*Repository, error) { |
| return PlainCloneContext(context.Background(), path, isBare, o) |
| } |
| |
| // PlainCloneContext a repository into the path with the given options, isBare |
| // defines if the new repository will be bare or normal. If the path is not empty |
| // ErrRepositoryAlreadyExists is returned. |
| // |
| // The provided Context must be non-nil. If the context expires before the |
| // operation is complete, an error is returned. The context only affects to the |
| // transport operations. |
| // |
| // TODO(mcuadros): move isBare to CloneOptions in v5 |
| // TODO(smola): refuse upfront to clone on a non-empty directory in v5, see #1027 |
| func PlainCloneContext(ctx context.Context, path string, isBare bool, o *CloneOptions) (*Repository, error) { |
| cleanup, cleanupParent, err := checkIfCleanupIsNeeded(path) |
| if err != nil { |
| return nil, err |
| } |
| |
| r, err := PlainInit(path, isBare) |
| if err != nil { |
| return nil, err |
| } |
| |
| err = r.clone(ctx, o) |
| if err != nil && err != ErrRepositoryAlreadyExists { |
| if cleanup { |
| cleanUpDir(path, cleanupParent) |
| } |
| } |
| |
| return r, err |
| } |
| |
| func newRepository(s storage.Storer, worktree billy.Filesystem) *Repository { |
| return &Repository{ |
| Storer: s, |
| wt: worktree, |
| r: make(map[string]*Remote), |
| } |
| } |
| |
| func checkIfCleanupIsNeeded(path string) (cleanup bool, cleanParent bool, err error) { |
| fi, err := os.Stat(path) |
| if err != nil { |
| if os.IsNotExist(err) { |
| return true, true, nil |
| } |
| |
| return false, false, err |
| } |
| |
| if !fi.IsDir() { |
| return false, false, fmt.Errorf("path is not a directory: %s", path) |
| } |
| |
| f, err := os.Open(path) |
| if err != nil { |
| return false, false, err |
| } |
| |
| defer ioutil.CheckClose(f, &err) |
| |
| _, err = f.Readdirnames(1) |
| if err == io.EOF { |
| return true, false, nil |
| } |
| |
| if err != nil { |
| return false, false, err |
| } |
| |
| return false, false, nil |
| } |
| |
| func cleanUpDir(path string, all bool) error { |
| if all { |
| return os.RemoveAll(path) |
| } |
| |
| f, err := os.Open(path) |
| if err != nil { |
| return err |
| } |
| |
| defer ioutil.CheckClose(f, &err) |
| |
| names, err := f.Readdirnames(-1) |
| if err != nil { |
| return err |
| } |
| |
| for _, name := range names { |
| if err := os.RemoveAll(filepath.Join(path, name)); err != nil { |
| return err |
| } |
| } |
| |
| return err |
| } |
| |
| // Config return the repository config |
| func (r *Repository) Config() (*config.Config, error) { |
| return r.Storer.Config() |
| } |
| |
| // Remote return a remote if exists |
| func (r *Repository) Remote(name string) (*Remote, error) { |
| cfg, err := r.Storer.Config() |
| if err != nil { |
| return nil, err |
| } |
| |
| c, ok := cfg.Remotes[name] |
| if !ok { |
| return nil, ErrRemoteNotFound |
| } |
| |
| return NewRemote(r.Storer, c), nil |
| } |
| |
| // Remotes returns a list with all the remotes |
| func (r *Repository) Remotes() ([]*Remote, error) { |
| cfg, err := r.Storer.Config() |
| if err != nil { |
| return nil, err |
| } |
| |
| remotes := make([]*Remote, len(cfg.Remotes)) |
| |
| var i int |
| for _, c := range cfg.Remotes { |
| remotes[i] = NewRemote(r.Storer, c) |
| i++ |
| } |
| |
| return remotes, nil |
| } |
| |
| // CreateRemote creates a new remote |
| func (r *Repository) CreateRemote(c *config.RemoteConfig) (*Remote, error) { |
| if err := c.Validate(); err != nil { |
| return nil, err |
| } |
| |
| remote := NewRemote(r.Storer, c) |
| |
| cfg, err := r.Storer.Config() |
| if err != nil { |
| return nil, err |
| } |
| |
| if _, ok := cfg.Remotes[c.Name]; ok { |
| return nil, ErrRemoteExists |
| } |
| |
| cfg.Remotes[c.Name] = c |
| return remote, r.Storer.SetConfig(cfg) |
| } |
| |
| // CreateRemoteAnonymous creates a new anonymous remote. c.Name must be "anonymous". |
| // It's used like 'git fetch git@github.com:src-d/go-git.git master:master'. |
| func (r *Repository) CreateRemoteAnonymous(c *config.RemoteConfig) (*Remote, error) { |
| if err := c.Validate(); err != nil { |
| return nil, err |
| } |
| |
| if c.Name != "anonymous" { |
| return nil, ErrAnonymousRemoteName |
| } |
| |
| remote := NewRemote(r.Storer, c) |
| |
| return remote, nil |
| } |
| |
| // DeleteRemote delete a remote from the repository and delete the config |
| func (r *Repository) DeleteRemote(name string) error { |
| cfg, err := r.Storer.Config() |
| if err != nil { |
| return err |
| } |
| |
| if _, ok := cfg.Remotes[name]; !ok { |
| return ErrRemoteNotFound |
| } |
| |
| delete(cfg.Remotes, name) |
| return r.Storer.SetConfig(cfg) |
| } |
| |
| // Branch return a Branch if exists |
| func (r *Repository) Branch(name string) (*config.Branch, error) { |
| cfg, err := r.Storer.Config() |
| if err != nil { |
| return nil, err |
| } |
| |
| b, ok := cfg.Branches[name] |
| if !ok { |
| return nil, ErrBranchNotFound |
| } |
| |
| return b, nil |
| } |
| |
| // CreateBranch creates a new Branch |
| func (r *Repository) CreateBranch(c *config.Branch) error { |
| if err := c.Validate(); err != nil { |
| return err |
| } |
| |
| cfg, err := r.Storer.Config() |
| if err != nil { |
| return err |
| } |
| |
| if _, ok := cfg.Branches[c.Name]; ok { |
| return ErrBranchExists |
| } |
| |
| cfg.Branches[c.Name] = c |
| return r.Storer.SetConfig(cfg) |
| } |
| |
| // DeleteBranch delete a Branch from the repository and delete the config |
| func (r *Repository) DeleteBranch(name string) error { |
| cfg, err := r.Storer.Config() |
| if err != nil { |
| return err |
| } |
| |
| if _, ok := cfg.Branches[name]; !ok { |
| return ErrBranchNotFound |
| } |
| |
| delete(cfg.Branches, name) |
| return r.Storer.SetConfig(cfg) |
| } |
| |
| // CreateTag creates a tag. If opts is included, the tag is an annotated tag, |
| // otherwise a lightweight tag is created. |
| func (r *Repository) CreateTag(name string, hash plumbing.Hash, opts *CreateTagOptions) (*plumbing.Reference, error) { |
| rname := plumbing.ReferenceName(path.Join("refs", "tags", name)) |
| |
| _, err := r.Storer.Reference(rname) |
| switch err { |
| case nil: |
| // Tag exists, this is an error |
| return nil, ErrTagExists |
| case plumbing.ErrReferenceNotFound: |
| // Tag missing, available for creation, pass this |
| default: |
| // Some other error |
| return nil, err |
| } |
| |
| var target plumbing.Hash |
| if opts != nil { |
| target, err = r.createTagObject(name, hash, opts) |
| if err != nil { |
| return nil, err |
| } |
| } else { |
| target = hash |
| } |
| |
| ref := plumbing.NewHashReference(rname, target) |
| if err = r.Storer.SetReference(ref); err != nil { |
| return nil, err |
| } |
| |
| return ref, nil |
| } |
| |
| func (r *Repository) createTagObject(name string, hash plumbing.Hash, opts *CreateTagOptions) (plumbing.Hash, error) { |
| if err := opts.Validate(r, hash); err != nil { |
| return plumbing.ZeroHash, err |
| } |
| |
| rawobj, err := object.GetObject(r.Storer, hash) |
| if err != nil { |
| return plumbing.ZeroHash, err |
| } |
| |
| tag := &object.Tag{ |
| Name: name, |
| Tagger: *opts.Tagger, |
| Message: opts.Message, |
| TargetType: rawobj.Type(), |
| Target: hash, |
| } |
| |
| if opts.SignKey != nil { |
| sig, err := r.buildTagSignature(tag, opts.SignKey) |
| if err != nil { |
| return plumbing.ZeroHash, err |
| } |
| |
| tag.PGPSignature = sig |
| } |
| |
| obj := r.Storer.NewEncodedObject() |
| if err := tag.Encode(obj); err != nil { |
| return plumbing.ZeroHash, err |
| } |
| |
| return r.Storer.SetEncodedObject(obj) |
| } |
| |
| func (r *Repository) buildTagSignature(tag *object.Tag, signKey *openpgp.Entity) (string, error) { |
| encoded := &plumbing.MemoryObject{} |
| if err := tag.Encode(encoded); err != nil { |
| return "", err |
| } |
| |
| rdr, err := encoded.Reader() |
| if err != nil { |
| return "", err |
| } |
| |
| var b bytes.Buffer |
| if err := openpgp.ArmoredDetachSign(&b, signKey, rdr, nil); err != nil { |
| return "", err |
| } |
| |
| return b.String(), nil |
| } |
| |
| // Tag returns a tag from the repository. |
| // |
| // 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 |
| // } |
| // |
| func (r *Repository) Tag(name string) (*plumbing.Reference, error) { |
| ref, err := r.Reference(plumbing.ReferenceName(path.Join("refs", "tags", name)), false) |
| if err != nil { |
| if err == plumbing.ErrReferenceNotFound { |
| // Return a friendly error for this one, versus just ReferenceNotFound. |
| return nil, ErrTagNotFound |
| } |
| |
| return nil, err |
| } |
| |
| return ref, nil |
| } |
| |
| // DeleteTag deletes a tag from the repository. |
| func (r *Repository) DeleteTag(name string) error { |
| _, err := r.Tag(name) |
| if err != nil { |
| return err |
| } |
| |
| return r.Storer.RemoveReference(plumbing.ReferenceName(path.Join("refs", "tags", name))) |
| } |
| |
| func (r *Repository) resolveToCommitHash(h plumbing.Hash) (plumbing.Hash, error) { |
| obj, err := r.Storer.EncodedObject(plumbing.AnyObject, h) |
| if err != nil { |
| return plumbing.ZeroHash, err |
| } |
| switch obj.Type() { |
| case plumbing.TagObject: |
| t, err := object.DecodeTag(r.Storer, obj) |
| if err != nil { |
| return plumbing.ZeroHash, err |
| } |
| return r.resolveToCommitHash(t.Target) |
| case plumbing.CommitObject: |
| return h, nil |
| default: |
| return plumbing.ZeroHash, ErrUnableToResolveCommit |
| } |
| } |
| |
| // Clone clones a remote repository |
| func (r *Repository) clone(ctx context.Context, o *CloneOptions) error { |
| if err := o.Validate(); err != nil { |
| return err |
| } |
| |
| c := &config.RemoteConfig{ |
| Name: o.RemoteName, |
| URLs: []string{o.URL}, |
| Fetch: r.cloneRefSpec(o), |
| } |
| |
| if _, err := r.CreateRemote(c); err != nil { |
| return err |
| } |
| |
| ref, err := r.fetchAndUpdateReferences(ctx, &FetchOptions{ |
| RefSpecs: c.Fetch, |
| Depth: o.Depth, |
| Auth: o.Auth, |
| Progress: o.Progress, |
| Tags: o.Tags, |
| RemoteName: o.RemoteName, |
| }, o.ReferenceName) |
| if err != nil { |
| return err |
| } |
| |
| if r.wt != nil && !o.NoCheckout { |
| w, err := r.Worktree() |
| if err != nil { |
| return err |
| } |
| |
| head, err := r.Head() |
| if err != nil { |
| return err |
| } |
| |
| if err := w.Reset(&ResetOptions{ |
| Mode: MergeReset, |
| Commit: head.Hash(), |
| }); err != nil { |
| return err |
| } |
| |
| if o.RecurseSubmodules != NoRecurseSubmodules { |
| if err := w.updateSubmodules(&SubmoduleUpdateOptions{ |
| RecurseSubmodules: o.RecurseSubmodules, |
| Auth: o.Auth, |
| }); err != nil { |
| return err |
| } |
| } |
| } |
| |
| if err := r.updateRemoteConfigIfNeeded(o, c, ref); err != nil { |
| return err |
| } |
| |
| if ref.Name().IsBranch() { |
| branchRef := ref.Name() |
| branchName := strings.Split(string(branchRef), "refs/heads/")[1] |
| |
| b := &config.Branch{ |
| Name: branchName, |
| Merge: branchRef, |
| } |
| if o.RemoteName == "" { |
| b.Remote = "origin" |
| } else { |
| b.Remote = o.RemoteName |
| } |
| if err := r.CreateBranch(b); err != nil { |
| return err |
| } |
| } |
| |
| return nil |
| } |
| |
| const ( |
| refspecTag = "+refs/tags/%s:refs/tags/%[1]s" |
| refspecSingleBranch = "+refs/heads/%s:refs/remotes/%s/%[1]s" |
| refspecSingleBranchHEAD = "+HEAD:refs/remotes/%s/HEAD" |
| ) |
| |
| func (r *Repository) cloneRefSpec(o *CloneOptions) []config.RefSpec { |
| switch { |
| case o.ReferenceName.IsTag(): |
| return []config.RefSpec{ |
| config.RefSpec(fmt.Sprintf(refspecTag, o.ReferenceName.Short())), |
| } |
| 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{ |
| config.RefSpec(fmt.Sprintf(refspecSingleBranch, o.ReferenceName.Short(), o.RemoteName)), |
| } |
| default: |
| return []config.RefSpec{ |
| config.RefSpec(fmt.Sprintf(config.DefaultFetchRefSpec, o.RemoteName)), |
| } |
| } |
| } |
| |
| func (r *Repository) setIsBare(isBare bool) error { |
| cfg, err := r.Storer.Config() |
| if err != nil { |
| return err |
| } |
| |
| cfg.Core.IsBare = isBare |
| return r.Storer.SetConfig(cfg) |
| } |
| |
| func (r *Repository) updateRemoteConfigIfNeeded(o *CloneOptions, c *config.RemoteConfig, head *plumbing.Reference) error { |
| if !o.SingleBranch { |
| return nil |
| } |
| |
| c.Fetch = r.cloneRefSpec(o) |
| |
| cfg, err := r.Storer.Config() |
| if err != nil { |
| return err |
| } |
| |
| cfg.Remotes[c.Name] = c |
| return r.Storer.SetConfig(cfg) |
| } |
| |
| func (r *Repository) fetchAndUpdateReferences( |
| ctx context.Context, o *FetchOptions, ref plumbing.ReferenceName, |
| ) (*plumbing.Reference, error) { |
| |
| if err := o.Validate(); err != nil { |
| return nil, err |
| } |
| |
| remote, err := r.Remote(o.RemoteName) |
| if err != nil { |
| return nil, err |
| } |
| |
| objsUpdated := true |
| remoteRefs, err := remote.fetch(ctx, o) |
| if err == NoErrAlreadyUpToDate { |
| objsUpdated = false |
| } else if err == packfile.ErrEmptyPackfile { |
| return nil, ErrFetching |
| } else if err != nil { |
| return nil, err |
| } |
| |
| resolvedRef, err := storer.ResolveReference(remoteRefs, ref) |
| if err != nil { |
| return nil, err |
| } |
| |
| refsUpdated, err := r.updateReferences(remote.c.Fetch, resolvedRef) |
| if err != nil { |
| return nil, err |
| } |
| |
| if !objsUpdated && !refsUpdated { |
| return nil, NoErrAlreadyUpToDate |
| } |
| |
| return resolvedRef, nil |
| } |
| |
| func (r *Repository) updateReferences(spec []config.RefSpec, |
| resolvedRef *plumbing.Reference) (updated bool, err error) { |
| |
| if !resolvedRef.Name().IsBranch() { |
| // Detached HEAD mode |
| h, err := r.resolveToCommitHash(resolvedRef.Hash()) |
| if err != nil { |
| return false, err |
| } |
| head := plumbing.NewHashReference(plumbing.HEAD, h) |
| return updateReferenceStorerIfNeeded(r.Storer, head) |
| } |
| |
| refs := []*plumbing.Reference{ |
| // Create local reference for the resolved ref |
| resolvedRef, |
| // Create local symbolic HEAD |
| plumbing.NewSymbolicReference(plumbing.HEAD, resolvedRef.Name()), |
| } |
| |
| refs = append(refs, r.calculateRemoteHeadReference(spec, resolvedRef)...) |
| |
| for _, ref := range refs { |
| u, err := updateReferenceStorerIfNeeded(r.Storer, ref) |
| if err != nil { |
| return updated, err |
| } |
| |
| if u { |
| updated = true |
| } |
| } |
| |
| return |
| } |
| |
| func (r *Repository) calculateRemoteHeadReference(spec []config.RefSpec, |
| resolvedHead *plumbing.Reference) []*plumbing.Reference { |
| |
| var refs []*plumbing.Reference |
| |
| // Create resolved HEAD reference with remote prefix if it does not |
| // exist. This is needed when using single branch and HEAD. |
| for _, rs := range spec { |
| name := resolvedHead.Name() |
| if !rs.Match(name) { |
| continue |
| } |
| |
| name = rs.Dst(name) |
| _, err := r.Storer.Reference(name) |
| if err == plumbing.ErrReferenceNotFound { |
| refs = append(refs, plumbing.NewHashReference(name, resolvedHead.Hash())) |
| } |
| } |
| |
| return refs |
| } |
| |
| func checkAndUpdateReferenceStorerIfNeeded( |
| s storer.ReferenceStorer, r, old *plumbing.Reference) ( |
| updated bool, err error) { |
| p, err := s.Reference(r.Name()) |
| if err != nil && err != plumbing.ErrReferenceNotFound { |
| return false, err |
| } |
| |
| // we use the string method to compare references, is the easiest way |
| if err == plumbing.ErrReferenceNotFound || r.String() != p.String() { |
| if err := s.CheckAndSetReference(r, old); err != nil { |
| return false, err |
| } |
| |
| return true, nil |
| } |
| |
| return false, nil |
| } |
| |
| func updateReferenceStorerIfNeeded( |
| s storer.ReferenceStorer, r *plumbing.Reference) (updated bool, err error) { |
| return checkAndUpdateReferenceStorerIfNeeded(s, r, nil) |
| } |
| |
| // Fetch fetches references along with the objects necessary to complete |
| // their histories, from the remote named as FetchOptions.RemoteName. |
| // |
| // Returns nil if the operation is successful, NoErrAlreadyUpToDate if there are |
| // no changes to be fetched, or an error. |
| func (r *Repository) Fetch(o *FetchOptions) error { |
| return r.FetchContext(context.Background(), o) |
| } |
| |
| // FetchContext fetches references along with the objects necessary to complete |
| // their histories, from the remote named as FetchOptions.RemoteName. |
| // |
| // Returns nil if the operation is successful, NoErrAlreadyUpToDate if there are |
| // no changes to be fetched, or an error. |
| // |
| // The provided Context must be non-nil. If the context expires before the |
| // operation is complete, an error is returned. The context only affects to the |
| // transport operations. |
| func (r *Repository) FetchContext(ctx context.Context, o *FetchOptions) error { |
| if err := o.Validate(); err != nil { |
| return err |
| } |
| |
| remote, err := r.Remote(o.RemoteName) |
| if err != nil { |
| return err |
| } |
| |
| return remote.FetchContext(ctx, o) |
| } |
| |
| // Push performs a push to the remote. Returns NoErrAlreadyUpToDate if |
| // the remote was already up-to-date, from the remote named as |
| // FetchOptions.RemoteName. |
| func (r *Repository) Push(o *PushOptions) error { |
| return r.PushContext(context.Background(), o) |
| } |
| |
| // PushContext performs a push to the remote. Returns NoErrAlreadyUpToDate if |
| // the remote was already up-to-date, from the remote named as |
| // FetchOptions.RemoteName. |
| // |
| // The provided Context must be non-nil. If the context expires before the |
| // operation is complete, an error is returned. The context only affects to the |
| // transport operations. |
| func (r *Repository) PushContext(ctx context.Context, o *PushOptions) error { |
| if err := o.Validate(); err != nil { |
| return err |
| } |
| |
| remote, err := r.Remote(o.RemoteName) |
| if err != nil { |
| return err |
| } |
| |
| return remote.PushContext(ctx, o) |
| } |
| |
| // Log returns the commit history from the given LogOptions. |
| func (r *Repository) Log(o *LogOptions) (object.CommitIter, error) { |
| fn := commitIterFunc(o.Order) |
| if fn == nil { |
| return nil, fmt.Errorf("invalid Order=%v", o.Order) |
| } |
| |
| var ( |
| it object.CommitIter |
| err error |
| ) |
| if o.All { |
| it, err = r.logAll(fn) |
| } else { |
| it, err = r.log(o.From, fn) |
| } |
| |
| if err != nil { |
| return nil, err |
| } |
| |
| if o.FileName != nil { |
| // for `git log --all` also check parent (if the next commit comes from the real parent) |
| it = r.logWithFile(*o.FileName, it, o.All) |
| } |
| |
| return it, nil |
| } |
| |
| func (r *Repository) log(from plumbing.Hash, commitIterFunc func(*object.Commit) object.CommitIter) (object.CommitIter, error) { |
| h := from |
| if from == plumbing.ZeroHash { |
| head, err := r.Head() |
| if err != nil { |
| return nil, err |
| } |
| |
| h = head.Hash() |
| } |
| |
| commit, err := r.CommitObject(h) |
| if err != nil { |
| return nil, err |
| } |
| return commitIterFunc(commit), nil |
| } |
| |
| func (r *Repository) logAll(commitIterFunc func(*object.Commit) object.CommitIter) (object.CommitIter, error) { |
| return object.NewCommitAllIter(r.Storer, commitIterFunc) |
| } |
| |
| func (*Repository) logWithFile(fileName string, commitIter object.CommitIter, checkParent bool) object.CommitIter { |
| return object.NewCommitFileIterFromIter(fileName, commitIter, checkParent) |
| } |
| |
| func commitIterFunc(order LogOrder) func(c *object.Commit) object.CommitIter { |
| switch order { |
| case LogOrderDefault: |
| return func(c *object.Commit) object.CommitIter { |
| return object.NewCommitPreorderIter(c, nil, nil) |
| } |
| case LogOrderDFS: |
| return func(c *object.Commit) object.CommitIter { |
| return object.NewCommitPreorderIter(c, nil, nil) |
| } |
| case LogOrderDFSPost: |
| return func(c *object.Commit) object.CommitIter { |
| return object.NewCommitPostorderIter(c, nil) |
| } |
| case LogOrderBSF: |
| return func(c *object.Commit) object.CommitIter { |
| return object.NewCommitIterBSF(c, nil, nil) |
| } |
| case LogOrderCommitterTime: |
| return func(c *object.Commit) object.CommitIter { |
| return object.NewCommitIterCTime(c, nil, nil) |
| } |
| } |
| return nil |
| } |
| |
| // Tags returns all the tag References in a repository. |
| // |
| // 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 |
| // } |
| // |
| func (r *Repository) Tags() (storer.ReferenceIter, error) { |
| refIter, err := r.Storer.IterReferences() |
| if err != nil { |
| return nil, err |
| } |
| |
| return storer.NewReferenceFilteredIter( |
| func(r *plumbing.Reference) bool { |
| return r.Name().IsTag() |
| }, refIter), nil |
| } |
| |
| // Branches returns all the References that are Branches. |
| func (r *Repository) Branches() (storer.ReferenceIter, error) { |
| refIter, err := r.Storer.IterReferences() |
| if err != nil { |
| return nil, err |
| } |
| |
| return storer.NewReferenceFilteredIter( |
| func(r *plumbing.Reference) bool { |
| return r.Name().IsBranch() |
| }, refIter), nil |
| } |
| |
| // Notes returns all the References that are notes. For more information: |
| // https://git-scm.com/docs/git-notes |
| func (r *Repository) Notes() (storer.ReferenceIter, error) { |
| refIter, err := r.Storer.IterReferences() |
| if err != nil { |
| return nil, err |
| } |
| |
| return storer.NewReferenceFilteredIter( |
| func(r *plumbing.Reference) bool { |
| return r.Name().IsNote() |
| }, refIter), nil |
| } |
| |
| // TreeObject return a Tree with the given hash. If not found |
| // plumbing.ErrObjectNotFound is returned |
| func (r *Repository) TreeObject(h plumbing.Hash) (*object.Tree, error) { |
| return object.GetTree(r.Storer, h) |
| } |
| |
| // TreeObjects returns an unsorted TreeIter with all the trees in the repository |
| func (r *Repository) TreeObjects() (*object.TreeIter, error) { |
| iter, err := r.Storer.IterEncodedObjects(plumbing.TreeObject) |
| if err != nil { |
| return nil, err |
| } |
| |
| return object.NewTreeIter(r.Storer, iter), nil |
| } |
| |
| // CommitObject return a Commit with the given hash. If not found |
| // plumbing.ErrObjectNotFound is returned. |
| func (r *Repository) CommitObject(h plumbing.Hash) (*object.Commit, error) { |
| return object.GetCommit(r.Storer, h) |
| } |
| |
| // CommitObjects returns an unsorted CommitIter with all the commits in the repository. |
| func (r *Repository) CommitObjects() (object.CommitIter, error) { |
| iter, err := r.Storer.IterEncodedObjects(plumbing.CommitObject) |
| if err != nil { |
| return nil, err |
| } |
| |
| return object.NewCommitIter(r.Storer, iter), nil |
| } |
| |
| // BlobObject returns a Blob with the given hash. If not found |
| // plumbing.ErrObjectNotFound is returned. |
| func (r *Repository) BlobObject(h plumbing.Hash) (*object.Blob, error) { |
| return object.GetBlob(r.Storer, h) |
| } |
| |
| // BlobObjects returns an unsorted BlobIter with all the blobs in the repository. |
| func (r *Repository) BlobObjects() (*object.BlobIter, error) { |
| iter, err := r.Storer.IterEncodedObjects(plumbing.BlobObject) |
| if err != nil { |
| return nil, err |
| } |
| |
| return object.NewBlobIter(r.Storer, iter), nil |
| } |
| |
| // TagObject returns a Tag with the given hash. If not found |
| // plumbing.ErrObjectNotFound is returned. This method only returns |
| // annotated Tags, no lightweight Tags. |
| func (r *Repository) TagObject(h plumbing.Hash) (*object.Tag, error) { |
| return object.GetTag(r.Storer, h) |
| } |
| |
| // TagObjects returns a unsorted TagIter that can step through all of the annotated |
| // tags in the repository. |
| func (r *Repository) TagObjects() (*object.TagIter, error) { |
| iter, err := r.Storer.IterEncodedObjects(plumbing.TagObject) |
| if err != nil { |
| return nil, err |
| } |
| |
| return object.NewTagIter(r.Storer, iter), nil |
| } |
| |
| // Object returns an Object with the given hash. If not found |
| // plumbing.ErrObjectNotFound is returned. |
| func (r *Repository) Object(t plumbing.ObjectType, h plumbing.Hash) (object.Object, error) { |
| obj, err := r.Storer.EncodedObject(t, h) |
| if err != nil { |
| return nil, err |
| } |
| |
| return object.DecodeObject(r.Storer, obj) |
| } |
| |
| // Objects returns an unsorted ObjectIter with all the objects in the repository. |
| func (r *Repository) Objects() (*object.ObjectIter, error) { |
| iter, err := r.Storer.IterEncodedObjects(plumbing.AnyObject) |
| if err != nil { |
| return nil, err |
| } |
| |
| return object.NewObjectIter(r.Storer, iter), nil |
| } |
| |
| // Head returns the reference where HEAD is pointing to. |
| func (r *Repository) Head() (*plumbing.Reference, error) { |
| return storer.ResolveReference(r.Storer, plumbing.HEAD) |
| } |
| |
| // Reference returns the reference for a given reference name. If resolved is |
| // true, any symbolic reference will be resolved. |
| func (r *Repository) Reference(name plumbing.ReferenceName, resolved bool) ( |
| *plumbing.Reference, error) { |
| |
| if resolved { |
| return storer.ResolveReference(r.Storer, name) |
| } |
| |
| return r.Storer.Reference(name) |
| } |
| |
| // References returns an unsorted ReferenceIter for all references. |
| func (r *Repository) References() (storer.ReferenceIter, error) { |
| return r.Storer.IterReferences() |
| } |
| |
| // Worktree returns a worktree based on the given fs, if nil the default |
| // worktree will be used. |
| func (r *Repository) Worktree() (*Worktree, error) { |
| if r.wt == nil { |
| return nil, ErrIsBareRepository |
| } |
| |
| return &Worktree{r: r, Filesystem: r.wt}, nil |
| } |
| |
| // 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}) |
| func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, error) { |
| p := revision.NewParserFromString(string(rev)) |
| |
| items, err := p.Parse() |
| |
| if err != nil { |
| return nil, err |
| } |
| |
| var commit *object.Commit |
| |
| for _, item := range items { |
| switch item.(type) { |
| case revision.Ref: |
| revisionRef := item.(revision.Ref) |
| |
| var tryHashes []plumbing.Hash |
| |
| maybeHash := plumbing.NewHash(string(revisionRef)) |
| |
| if !maybeHash.IsZero() { |
| tryHashes = append(tryHashes, maybeHash) |
| } |
| |
| 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 |
| } |
| } |
| |
| // in ambiguous cases, `git rev-parse` will emit a warning, but |
| // will always return the oid in preference to a ref; we don't have |
| // the ability to emit a warning here, so (for speed purposes) |
| // don't bother to detect the ambiguity either, just return in the |
| // priority that git would. |
| gotOne := false |
| for _, hash := range tryHashes { |
| commitObj, err := r.CommitObject(hash) |
| if err == nil { |
| commit = commitObj |
| gotOne = true |
| break |
| } |
| |
| tagObj, err := r.TagObject(hash) |
| if err == nil { |
| // If the tag target lookup fails here, this most likely |
| // represents some sort of repo corruption, so let the |
| // error bubble up. |
| tagCommit, err := tagObj.Commit() |
| if err != nil { |
| return &plumbing.ZeroHash, err |
| } |
| commit = tagCommit |
| gotOne = true |
| break |
| } |
| } |
| |
| if !gotOne { |
| return &plumbing.ZeroHash, plumbing.ErrReferenceNotFound |
| } |
| |
| case revision.CaretPath: |
| depth := item.(revision.CaretPath).Depth |
| |
| if depth == 0 { |
| break |
| } |
| |
| iter := commit.Parents() |
| |
| c, err := iter.Next() |
| |
| if err != nil { |
| return &plumbing.ZeroHash, err |
| } |
| |
| if depth == 1 { |
| commit = c |
| |
| break |
| } |
| |
| c, err = iter.Next() |
| |
| if err != nil { |
| return &plumbing.ZeroHash, err |
| } |
| |
| commit = c |
| case revision.TildePath: |
| for i := 0; i < item.(revision.TildePath).Depth; i++ { |
| c, err := commit.Parents().Next() |
| |
| if err != nil { |
| return &plumbing.ZeroHash, err |
| } |
| |
| commit = c |
| } |
| case revision.CaretReg: |
| history := object.NewCommitPreorderIter(commit, nil, nil) |
| |
| re := item.(revision.CaretReg).Regexp |
| negate := item.(revision.CaretReg).Negate |
| |
| var c *object.Commit |
| |
| err := history.ForEach(func(hc *object.Commit) error { |
| if !negate && re.MatchString(hc.Message) { |
| c = hc |
| return storer.ErrStop |
| } |
| |
| if negate && !re.MatchString(hc.Message) { |
| c = hc |
| return storer.ErrStop |
| } |
| |
| return nil |
| }) |
| if err != nil { |
| return &plumbing.ZeroHash, err |
| } |
| |
| if c == nil { |
| return &plumbing.ZeroHash, fmt.Errorf(`No commit message match regexp : "%s"`, re.String()) |
| } |
| |
| commit = c |
| } |
| } |
| |
| return &commit.Hash, nil |
| } |
| |
| type RepackConfig struct { |
| // UseRefDeltas configures whether packfile encoder will use reference deltas. |
| // By default OFSDeltaObject is used. |
| UseRefDeltas bool |
| // OnlyDeletePacksOlderThan if set to non-zero value |
| // selects only objects older than the time provided. |
| OnlyDeletePacksOlderThan time.Time |
| } |
| |
| func (r *Repository) RepackObjects(cfg *RepackConfig) (err error) { |
| pos, ok := r.Storer.(storer.PackedObjectStorer) |
| if !ok { |
| return ErrPackedObjectsNotSupported |
| } |
| |
| // Get the existing object packs. |
| hs, err := pos.ObjectPacks() |
| if err != nil { |
| return err |
| } |
| |
| // Create a new pack. |
| nh, err := r.createNewObjectPack(cfg) |
| if err != nil { |
| return err |
| } |
| |
| // Delete old packs. |
| for _, h := range hs { |
| // Skip if new hash is the same as an old one. |
| if h == nh { |
| continue |
| } |
| err = pos.DeleteOldObjectPackAndIndex(h, cfg.OnlyDeletePacksOlderThan) |
| if err != nil { |
| return err |
| } |
| } |
| |
| return nil |
| } |
| |
| // createNewObjectPack is a helper for RepackObjects taking care |
| // of creating a new pack. It is used so the the PackfileWriter |
| // deferred close has the right scope. |
| func (r *Repository) createNewObjectPack(cfg *RepackConfig) (h plumbing.Hash, err error) { |
| ow := newObjectWalker(r.Storer) |
| err = ow.walkAllRefs() |
| if err != nil { |
| return h, err |
| } |
| objs := make([]plumbing.Hash, 0, len(ow.seen)) |
| for h := range ow.seen { |
| objs = append(objs, h) |
| } |
| pfw, ok := r.Storer.(storer.PackfileWriter) |
| if !ok { |
| return h, fmt.Errorf("Repository storer is not a storer.PackfileWriter") |
| } |
| wc, err := pfw.PackfileWriter() |
| if err != nil { |
| return h, err |
| } |
| defer ioutil.CheckClose(wc, &err) |
| scfg, err := r.Storer.Config() |
| if err != nil { |
| return h, err |
| } |
| enc := packfile.NewEncoder(wc, r.Storer, cfg.UseRefDeltas) |
| h, err = enc.Encode(objs, scfg.Pack.Window) |
| if err != nil { |
| return h, err |
| } |
| |
| // Delete the packed, loose objects. |
| if los, ok := r.Storer.(storer.LooseObjectStorer); ok { |
| err = los.ForEachObjectHash(func(hash plumbing.Hash) error { |
| if ow.isSeen(hash) { |
| err = los.DeleteLooseObject(hash) |
| if err != nil { |
| return err |
| } |
| } |
| return nil |
| }) |
| if err != nil { |
| return h, err |
| } |
| } |
| |
| return h, err |
| } |