blob: 0c8dc98b8f1329bcfab7a4a49c507d86b131fe1f [file] [log] [blame]
// 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
}