| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package git |
| |
| import ( |
| "fmt" |
| git2go "github.com/libgit2/git2go" |
| ) |
| |
| type Git struct { |
| rootDir string |
| } |
| |
| func NewGit(path string) *Git { |
| return &Git{ |
| rootDir: path, |
| } |
| } |
| |
| func (g *Git) CurrentRevision() (string, error) { |
| repo, err := git2go.OpenRepository(g.rootDir) |
| if err != nil { |
| return "", err |
| } |
| defer repo.Free() |
| head, err := repo.Head() |
| if err != nil { |
| return "", err |
| } |
| defer head.Free() |
| return head.Target().String(), nil |
| } |
| |
| // Fetch fetches refs and tags from the given remote. |
| func (g *Git) Fetch(remote string, opts ...FetchOpt) error { |
| return g.FetchRefspec(remote, "", opts...) |
| } |
| |
| // FetchRefspec fetches refs and tags from the given remote for a particular refspec. |
| func (g *Git) FetchRefspec(remoteName, refspec string, opts ...FetchOpt) error { |
| repo, err := git2go.OpenRepository(g.rootDir) |
| if err != nil { |
| return err |
| } |
| defer repo.Free() |
| if remoteName == "" { |
| return fmt.Errorf("No remote passed") |
| } |
| remote, err := repo.Remotes.Lookup(remoteName) |
| if err != nil { |
| return err |
| } |
| defer remote.Free() |
| fetchOptions := &git2go.FetchOptions{} |
| tags := false |
| prune := false |
| for _, opt := range opts { |
| switch typedOpt := opt.(type) { |
| case TagsOpt: |
| tags = bool(typedOpt) |
| case PruneOpt: |
| prune = bool(typedOpt) |
| } |
| } |
| refspecList := []string{} |
| if refspec != "" { |
| refspecList = []string{refspec} |
| } |
| if prune { |
| fetchOptions.Prune = git2go.FetchPruneOn |
| } |
| if tags { |
| fetchOptions.DownloadTags = git2go.DownloadTagsAll |
| } |
| return remote.Fetch(refspecList, fetchOptions, "") |
| } |
| |
| func (g *Git) SetRemoteUrl(remote, url string) error { |
| repo, err := git2go.OpenRepository(g.rootDir) |
| if err != nil { |
| return err |
| } |
| defer repo.Free() |
| return repo.Remotes.SetUrl(remote, url) |
| } |
| |
| type Reference struct { |
| Name string |
| Revision string |
| IsHead bool |
| } |
| |
| type Branch struct { |
| *Reference |
| Tracking *Reference |
| } |
| |
| // CurrentRevisionForRef gets current rev for ref/branch/tags |
| func (g *Git) CurrentRevisionForRef(ref string) (string, error) { |
| repo, err := git2go.OpenRepository(g.rootDir) |
| if err != nil { |
| return "", err |
| } |
| defer repo.Free() |
| if obj, err := repo.RevparseSingle(ref); err != nil { |
| return "", err |
| } else { |
| defer obj.Free() |
| if obj.Type() == git2go.ObjectTag { |
| tag, err := obj.AsTag() |
| if err != nil { |
| return "", err |
| } |
| defer tag.Free() |
| return tag.TargetId().String(), nil |
| } |
| return obj.Id().String(), nil |
| } |
| } |
| |
| func (g *Git) SetUpstream(branch, upstream string) error { |
| repo, err := git2go.OpenRepository(g.rootDir) |
| if err != nil { |
| return err |
| } |
| defer repo.Free() |
| b, err := repo.LookupBranch(branch, git2go.BranchLocal) |
| if err != nil { |
| return err |
| } |
| return b.SetUpstream(upstream) |
| } |
| |
| func (g *Git) CreateBranchFromRef(branch, ref string) error { |
| repo, err := git2go.OpenRepository(g.rootDir) |
| if err != nil { |
| return err |
| } |
| defer repo.Free() |
| obj, err := repo.RevparseSingle(ref) |
| if err != nil { |
| return err |
| } |
| defer obj.Free() |
| c, err := obj.Peel(git2go.ObjectCommit) |
| if err != nil { |
| return err |
| } |
| defer c.Free() |
| commit, err := c.AsCommit() |
| if err != nil { |
| return err |
| } |
| defer commit.Free() |
| _, err = repo.CreateBranch(branch, commit, false) |
| return err |
| } |
| |
| func (g *Git) HasUntrackedFiles() (bool, error) { |
| repo, err := git2go.OpenRepository(g.rootDir) |
| if err != nil { |
| return false, err |
| } |
| defer repo.Free() |
| opts := &git2go.StatusOptions{} |
| opts.Show = git2go.StatusShowIndexAndWorkdir |
| opts.Flags = git2go.StatusOptIncludeUntracked |
| |
| statusList, err := repo.StatusList(opts) |
| if err != nil { |
| return false, err |
| } |
| |
| defer statusList.Free() |
| entryCount, err := statusList.EntryCount() |
| if err != nil { |
| return false, err |
| } |
| for i := 0; i < entryCount; i++ { |
| entry, err := statusList.ByIndex(i) |
| if err != nil { |
| return false, err |
| } |
| if (entry.Status & git2go.StatusWtNew) > 0 { |
| return true, nil |
| } |
| } |
| return false, nil |
| } |
| func (g *Git) HasUncommittedChanges() (bool, error) { |
| repo, err := git2go.OpenRepository(g.rootDir) |
| if err != nil { |
| return false, err |
| } |
| defer repo.Free() |
| opts := &git2go.StatusOptions{} |
| opts.Show = git2go.StatusShowIndexAndWorkdir |
| |
| statusList, err := repo.StatusList(opts) |
| if err != nil { |
| return false, err |
| } |
| |
| defer statusList.Free() |
| entryCount, err := statusList.EntryCount() |
| if err != nil { |
| return false, err |
| } |
| uncommitedFlag := git2go.StatusWtModified | git2go.StatusWtDeleted | |
| git2go.StatusWtTypeChange | git2go.StatusIndexModified | |
| git2go.StatusIndexNew | git2go.StatusIndexDeleted | |
| git2go.StatusIndexTypeChange | git2go.StatusConflicted |
| |
| for i := 0; i < entryCount; i++ { |
| entry, err := statusList.ByIndex(i) |
| if err != nil { |
| return false, err |
| } |
| if (entry.Status & uncommitedFlag) > 0 { |
| return true, nil |
| } |
| } |
| return false, nil |
| } |
| |
| // GetBranches returns a slice of the local branches of the current |
| // repository, followed by the name of the current branch. |
| func (g *Git) GetBranches() ([]string, string, error) { |
| branches, current := []string{}, "" |
| repo, err := git2go.OpenRepository(g.rootDir) |
| if err != nil { |
| return nil, "", err |
| } |
| defer repo.Free() |
| bi, err := repo.NewBranchIterator(git2go.BranchLocal) |
| if err != nil { |
| return nil, "", err |
| } |
| err = bi.ForEach(func(b *git2go.Branch, bt git2go.BranchType) error { |
| isHead, err := b.IsHead() |
| if err != nil { |
| return err |
| } |
| name, err := b.Name() |
| if err != nil { |
| return err |
| } |
| branches = append(branches, name) |
| if isHead { |
| current = name |
| } |
| return nil |
| }) |
| return branches, current, nil |
| } |
| |
| func (g *Git) GetAllBranchesInfo() ([]Branch, error) { |
| var branches []Branch |
| repo, err := git2go.OpenRepository(g.rootDir) |
| if err != nil { |
| return nil, err |
| } |
| defer repo.Free() |
| bi, err := repo.NewBranchIterator(git2go.BranchLocal) |
| if err != nil { |
| return nil, err |
| } |
| err = bi.ForEach(func(b *git2go.Branch, bt git2go.BranchType) error { |
| isHead, err := b.IsHead() |
| if err != nil { |
| return err |
| } |
| name, err := b.Name() |
| if err != nil { |
| return err |
| } |
| revision := "" |
| if t := b.Target(); t != nil { |
| revision = t.String() |
| } |
| branch := Branch{ |
| &Reference{ |
| Name: name, |
| Revision: revision, |
| IsHead: isHead, |
| }, nil, |
| } |
| if u, err := b.Upstream(); err != nil && !git2go.IsErrorCode(err, git2go.ErrNotFound) { |
| return err |
| } else if u != nil { |
| defer u.Free() |
| branch.Tracking = &Reference{ |
| Name: u.Shorthand(), |
| Revision: u.Target().String(), |
| } |
| } |
| branches = append(branches, branch) |
| return nil |
| }) |
| return branches, err |
| } |