| package git |
| |
| import ( |
| "errors" |
| "fmt" |
| "os" |
| |
| "srcd.works/go-git.v4/config" |
| "srcd.works/go-git.v4/plumbing" |
| "srcd.works/go-git.v4/plumbing/object" |
| "srcd.works/go-git.v4/plumbing/storer" |
| "srcd.works/go-git.v4/storage/filesystem" |
| |
| "srcd.works/go-billy.v1" |
| "srcd.works/go-billy.v1/osfs" |
| ) |
| |
| var ( |
| ErrObjectNotFound = errors.New("object not found") |
| ErrInvalidReference = errors.New("invalid reference, should be a tag or a branch") |
| ErrRepositoryNotExists = errors.New("repository not exists") |
| ErrRepositoryAlreadyExists = errors.New("repository already exists") |
| ErrRemoteNotFound = errors.New("remote not found") |
| ErrRemoteExists = errors.New("remote already exists") |
| ErrWorktreeNotProvided = errors.New("worktree should be provided") |
| ErrIsBareRepository = errors.New("worktree not available in a bare repository") |
| ) |
| |
| // Repository represents a git repository |
| type Repository struct { |
| r map[string]*Remote |
| s Storer |
| 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 Storer, worktree billy.Filesystem) (*Repository, error) { |
| 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 |
| } |
| |
| // 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 Storer, worktree billy.Filesystem) (*Repository, error) { |
| _, err := s.Reference(plumbing.HEAD) |
| if err == plumbing.ErrReferenceNotFound { |
| return nil, ErrRepositoryNotExists |
| } |
| |
| if err != nil { |
| return nil, err |
| } |
| |
| cfg, err := s.Config() |
| if err != nil { |
| return nil, err |
| } |
| |
| if !cfg.Core.IsBare && worktree == nil { |
| return nil, ErrWorktreeNotProvided |
| } |
| |
| 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 |
| func Clone(s Storer, worktree billy.Filesystem, o *CloneOptions) (*Repository, error) { |
| r, err := Init(s, worktree) |
| if err != nil { |
| return nil, err |
| } |
| |
| return r, r.clone(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.Dir(".git") |
| } |
| |
| s, err := filesystem.NewStorage(dot) |
| if err != nil { |
| return nil, err |
| } |
| |
| 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) { |
| var wt, dot billy.Filesystem |
| |
| fs := osfs.New(path) |
| if _, err := fs.Stat(".git"); err != nil { |
| if !os.IsNotExist(err) { |
| return nil, err |
| } |
| |
| dot = fs |
| } else { |
| wt = fs |
| dot = fs.Dir(".git") |
| } |
| |
| s, err := filesystem.NewStorage(dot) |
| if err != nil { |
| return nil, err |
| } |
| |
| return Open(s, wt) |
| } |
| |
| // 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 |
| func PlainClone(path string, isBare bool, o *CloneOptions) (*Repository, error) { |
| r, err := PlainInit(path, isBare) |
| if err != nil { |
| return nil, err |
| } |
| |
| return r, r.clone(o) |
| } |
| |
| func newRepository(s Storer, worktree billy.Filesystem) *Repository { |
| return &Repository{ |
| s: s, |
| wt: worktree, |
| r: make(map[string]*Remote, 0), |
| } |
| } |
| |
| // Config return the repository config |
| func (r *Repository) Config() (*config.Config, error) { |
| return r.s.Config() |
| } |
| |
| // Remote return a remote if exists |
| func (r *Repository) Remote(name string) (*Remote, error) { |
| cfg, err := r.s.Config() |
| if err != nil { |
| return nil, err |
| } |
| |
| c, ok := cfg.Remotes[name] |
| if !ok { |
| return nil, ErrRemoteNotFound |
| } |
| |
| return newRemote(r.s, c), nil |
| } |
| |
| // Remotes returns a list with all the remotes |
| func (r *Repository) Remotes() ([]*Remote, error) { |
| cfg, err := r.s.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.s, 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.s, c) |
| |
| cfg, err := r.s.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.s.SetConfig(cfg) |
| } |
| |
| // DeleteRemote delete a remote from the repository and delete the config |
| func (r *Repository) DeleteRemote(name string) error { |
| cfg, err := r.s.Config() |
| if err != nil { |
| return err |
| } |
| |
| if _, ok := cfg.Remotes[name]; !ok { |
| return ErrRemoteNotFound |
| } |
| |
| delete(cfg.Remotes, name) |
| return r.s.SetConfig(cfg) |
| } |
| |
| // Clone clones a remote repository |
| func (r *Repository) clone(o *CloneOptions) error { |
| if err := o.Validate(); err != nil { |
| return err |
| } |
| |
| // marks the repository as bare in the config, until we have Worktree, all |
| // the repository are bare |
| if err := r.setIsBare(true); err != nil { |
| return err |
| } |
| |
| c := &config.RemoteConfig{ |
| Name: o.RemoteName, |
| URL: o.URL, |
| } |
| |
| remote, err := r.CreateRemote(c) |
| if err != nil { |
| return err |
| } |
| |
| remoteRefs, err := remote.fetch(&FetchOptions{ |
| RefSpecs: r.cloneRefSpec(o, c), |
| Depth: o.Depth, |
| Auth: o.Auth, |
| Progress: o.Progress, |
| }) |
| if err != nil { |
| return err |
| } |
| |
| head, err := storer.ResolveReference(remoteRefs, o.ReferenceName) |
| if err != nil { |
| return err |
| } |
| |
| if _, err := r.updateReferences(c.Fetch, o.ReferenceName, head); err != nil { |
| return err |
| } |
| |
| if err := r.updateWorktree(); err != nil { |
| return err |
| } |
| |
| return r.updateRemoteConfig(remote, o, c, head) |
| } |
| |
| func (r *Repository) cloneRefSpec(o *CloneOptions, |
| c *config.RemoteConfig) []config.RefSpec { |
| |
| if !o.SingleBranch { |
| return c.Fetch |
| } |
| |
| var rs string |
| |
| if o.ReferenceName == plumbing.HEAD { |
| rs = fmt.Sprintf(refspecSingleBranchHEAD, c.Name) |
| } else { |
| rs = fmt.Sprintf(refspecSingleBranch, |
| o.ReferenceName.Short(), c.Name) |
| } |
| |
| return []config.RefSpec{config.RefSpec(rs)} |
| } |
| |
| func (r *Repository) setIsBare(isBare bool) error { |
| cfg, err := r.s.Config() |
| if err != nil { |
| return err |
| } |
| |
| cfg.Core.IsBare = isBare |
| return r.s.SetConfig(cfg) |
| } |
| |
| const ( |
| refspecSingleBranch = "+refs/heads/%s:refs/remotes/%s/%[1]s" |
| refspecSingleBranchHEAD = "+HEAD:refs/remotes/%s/HEAD" |
| ) |
| |
| func (r *Repository) updateRemoteConfig(remote *Remote, o *CloneOptions, |
| c *config.RemoteConfig, head *plumbing.Reference) error { |
| |
| if !o.SingleBranch { |
| return nil |
| } |
| |
| c.Fetch = []config.RefSpec{config.RefSpec(fmt.Sprintf( |
| refspecSingleBranch, head.Name().Short(), c.Name, |
| ))} |
| |
| cfg, err := r.s.Config() |
| if err != nil { |
| return err |
| } |
| |
| cfg.Remotes[c.Name] = c |
| return r.s.SetConfig(cfg) |
| } |
| |
| func (r *Repository) updateReferences(spec []config.RefSpec, |
| headName plumbing.ReferenceName, resolvedHead *plumbing.Reference) (updated bool, err error) { |
| |
| if !resolvedHead.IsBranch() { |
| // Detached HEAD mode |
| head := plumbing.NewHashReference(plumbing.HEAD, resolvedHead.Hash()) |
| return updateReferenceStorerIfNeeded(r.s, head) |
| } |
| |
| refs := []*plumbing.Reference{ |
| // Create local reference for the resolved head |
| resolvedHead, |
| // Create local symbolic HEAD |
| plumbing.NewSymbolicReference(plumbing.HEAD, resolvedHead.Name()), |
| } |
| |
| refs = append(refs, r.calculateRemoteHeadReference(spec, resolvedHead)...) |
| |
| for _, ref := range refs { |
| u, err := updateReferenceStorerIfNeeded(r.s, 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.s.Reference(name) |
| if err == plumbing.ErrReferenceNotFound { |
| refs = append(refs, plumbing.NewHashReference(name, resolvedHead.Hash())) |
| } |
| } |
| |
| return refs |
| } |
| |
| func updateReferenceStorerIfNeeded( |
| s storer.ReferenceStorer, r *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.SetReference(r); err != nil { |
| return false, err |
| } |
| |
| return true, nil |
| } |
| |
| return false, nil |
| } |
| |
| // Pull incorporates changes from a remote repository into the current branch. |
| // Returns nil if the operation is successful, NoErrAlreadyUpToDate if there are |
| // no changes to be fetched, or an error. |
| func (r *Repository) Pull(o *PullOptions) error { |
| if err := o.Validate(); err != nil { |
| return err |
| } |
| |
| remote, err := r.Remote(o.RemoteName) |
| if err != nil { |
| return err |
| } |
| |
| remoteRefs, err := remote.fetch(&FetchOptions{ |
| Depth: o.Depth, |
| Auth: o.Auth, |
| Progress: o.Progress, |
| }) |
| |
| updated := true |
| if err == NoErrAlreadyUpToDate { |
| updated = false |
| } else if err != nil { |
| return err |
| } |
| |
| head, err := storer.ResolveReference(remoteRefs, o.ReferenceName) |
| if err != nil { |
| return err |
| } |
| |
| refsUpdated, err := r.updateReferences(remote.c.Fetch, o.ReferenceName, head) |
| if err != nil { |
| return err |
| } |
| |
| if refsUpdated { |
| updated = refsUpdated |
| } |
| |
| if !updated { |
| return NoErrAlreadyUpToDate |
| } |
| |
| return r.updateWorktree() |
| } |
| |
| func (r *Repository) updateWorktree() error { |
| if r.wt == nil { |
| return nil |
| } |
| |
| w, err := r.Worktree() |
| if err != nil { |
| return err |
| } |
| |
| h, err := r.Head() |
| if err != nil { |
| return err |
| } |
| |
| return w.Checkout(h.Hash()) |
| } |
| |
| // Fetch fetches changes from a remote repository. |
| // 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 { |
| if err := o.Validate(); err != nil { |
| return err |
| } |
| |
| remote, err := r.Remote(o.RemoteName) |
| if err != nil { |
| return err |
| } |
| |
| return remote.Fetch(o) |
| } |
| |
| // Push pushes changes to a remote. |
| func (r *Repository) Push(o *PushOptions) error { |
| if err := o.Validate(); err != nil { |
| return err |
| } |
| |
| remote, err := r.Remote(o.RemoteName) |
| if err != nil { |
| return err |
| } |
| |
| return remote.Push(o) |
| } |
| |
| // Commit return a Commit with the given hash. If not found |
| // plumbing.ErrObjectNotFound is returned |
| func (r *Repository) Commit(h plumbing.Hash) (*object.Commit, error) { |
| return object.GetCommit(r.s, h) |
| } |
| |
| // Commits returns an unsorted CommitIter with all the commits in the repository |
| func (r *Repository) Commits() (*object.CommitIter, error) { |
| iter, err := r.s.IterEncodedObjects(plumbing.CommitObject) |
| if err != nil { |
| return nil, err |
| } |
| |
| return object.NewCommitIter(r.s, iter), nil |
| } |
| |
| // Tree return a Tree with the given hash. If not found |
| // plumbing.ErrObjectNotFound is returned |
| func (r *Repository) Tree(h plumbing.Hash) (*object.Tree, error) { |
| return object.GetTree(r.s, h) |
| } |
| |
| // Trees returns an unsorted TreeIter with all the trees in the repository |
| func (r *Repository) Trees() (*object.TreeIter, error) { |
| iter, err := r.s.IterEncodedObjects(plumbing.TreeObject) |
| if err != nil { |
| return nil, err |
| } |
| |
| return object.NewTreeIter(r.s, iter), nil |
| } |
| |
| // Blob returns a Blob with the given hash. If not found |
| // plumbing.ErrObjectNotFound is returne |
| func (r *Repository) Blob(h plumbing.Hash) (*object.Blob, error) { |
| return object.GetBlob(r.s, h) |
| } |
| |
| // Blobs returns an unsorted BlobIter with all the blobs in the repository |
| func (r *Repository) Blobs() (*object.BlobIter, error) { |
| iter, err := r.s.IterEncodedObjects(plumbing.BlobObject) |
| if err != nil { |
| return nil, err |
| } |
| |
| return object.NewBlobIter(r.s, iter), nil |
| } |
| |
| // Tag returns a Tag with the given hash. If not found |
| // plumbing.ErrObjectNotFound is returned |
| func (r *Repository) Tag(h plumbing.Hash) (*object.Tag, error) { |
| return object.GetTag(r.s, h) |
| } |
| |
| // Tags returns a unsorted TagIter that can step through all of the annotated |
| // tags in the repository. |
| func (r *Repository) Tags() (*object.TagIter, error) { |
| iter, err := r.s.IterEncodedObjects(plumbing.TagObject) |
| if err != nil { |
| return nil, err |
| } |
| |
| return object.NewTagIter(r.s, 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.s.EncodedObject(t, h) |
| if err != nil { |
| if err == plumbing.ErrObjectNotFound { |
| return nil, ErrObjectNotFound |
| } |
| |
| return nil, err |
| } |
| |
| return object.DecodeObject(r.s, obj) |
| } |
| |
| // Objects returns an unsorted BlobIter with all the objects in the repository |
| func (r *Repository) Objects() (*object.ObjectIter, error) { |
| iter, err := r.s.IterEncodedObjects(plumbing.AnyObject) |
| if err != nil { |
| return nil, err |
| } |
| |
| return object.NewObjectIter(r.s, iter), nil |
| } |
| |
| // Head returns the reference where HEAD is pointing to. |
| func (r *Repository) Head() (*plumbing.Reference, error) { |
| return storer.ResolveReference(r.s, 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.s, name) |
| } |
| |
| return r.s.Reference(name) |
| } |
| |
| // References returns an unsorted ReferenceIter for all references. |
| func (r *Repository) References() (storer.ReferenceIter, error) { |
| return r.s.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, fs: r.wt}, nil |
| } |