diff --git a/branch.go b/branch.go index ee7504c..9e0ef84 100644 --- a/branch.go +++ b/branch.go @@ -15,7 +15,7 @@ type BranchStatus struct { Behind int } -func Branches(repo *git.Repository) ([]BranchStatus, error) { +func Branches(repo *git.Repository) (map[string]BranchStatus, error) { iter, err := repo.NewBranchIterator(git.BranchAll) if err != nil { return nil, errors.Wrap(err, "Failed creating branch iterator") @@ -31,14 +31,14 @@ func Branches(repo *git.Repository) ([]BranchStatus, error) { return nil, errors.Wrap(err, "Failed iterating over branches") } - var statuses []BranchStatus + statuses := make(map[string]BranchStatus) for _, branch := range branches { status, err := NewBranchStatus(repo, branch) if err != nil { - // TODO: handle error + // TODO: Handle error. We should tell user that we couldn't read status of that branch but probably shouldn't exit continue } - statuses = append(statuses, status) + statuses[status.Name] = status } return statuses, nil @@ -53,31 +53,38 @@ func NewBranchStatus(repo *git.Repository, branch *git.Branch) (BranchStatus, er } status.Name = name - status.IsRemote = branch.IsRemote() + // If branch is a remote one, return immediately. Upstream can only be found for local branches. + if branch.IsRemote() { + status.IsRemote = true + return status, nil + } upstream, err := branch.Upstream() if err != nil && !git.IsErrorCode(err, git.ErrNotFound) { return status, errors.Wrap(err, "Failed getting branch upstream") } - if upstream != nil { - status.HasUpstream = true + // If there's no upstream, return immediately. Ahead/Behind can only be found when upstream exists. + if upstream == nil { + return status, nil + } - ahead, behind, err := repo.AheadBehind(branch.Target(), upstream.Target()) - if err != nil { - return status, errors.Wrap(err, "Failed getting ahead/behind information") - } + status.HasUpstream = true - status.Ahead = ahead - status.Behind = behind + ahead, behind, err := repo.AheadBehind(branch.Target(), upstream.Target()) + if err != nil { + return status, errors.Wrap(err, "Failed getting ahead/behind information") + } - if ahead > 0 { - status.NeedsPush = true - } + status.Ahead = ahead + status.Behind = behind - if behind > 0 { - status.NeedsPull = true - } + if ahead > 0 { + status.NeedsPush = true + } + + if behind > 0 { + status.NeedsPull = true } return status, nil diff --git a/branch_test.go b/branch_test.go index 9eb2d99..8861d70 100644 --- a/branch_test.go +++ b/branch_test.go @@ -1,6 +1,7 @@ package main import ( + "reflect" "testing" ) @@ -29,3 +30,49 @@ func TestNewLocalBranch(t *testing.T) { t.Errorf("Wrong branch status, got %+v; want %+v", status, want) } } + +func TestClonedBranches(t *testing.T) { + origin := newTestRepo(t) + createFile(t, origin, "file") + stageFile(t, origin, "file") + createCommit(t, origin, "Initial commit") + + repo, err := CloneRepo(origin.Path(), newTempDir(t)) + checkFatal(t, err) + + createBranch(t, repo, "branch") + + branches, err := Branches(repo) + checkFatal(t, err) + + master := branches["master"] + wantMaster := BranchStatus{ + Name: "master", + IsRemote: false, + HasUpstream: true, + } + + originMaster := branches["origin/master"] + wantOriginMaster := BranchStatus{ + Name: "origin/master", + IsRemote: true, + HasUpstream: false, + } + + branch := branches["branch"] + wantBranch := BranchStatus{ + Name: "branch", + IsRemote: false, + HasUpstream: false, + } + + if !reflect.DeepEqual(master, wantMaster) { + t.Errorf("Wrong branch status, got %+v; want %+v", master, wantMaster) + } + if !reflect.DeepEqual(originMaster, wantOriginMaster) { + t.Errorf("Wrong branch status, got %+v; want %+v", originMaster, wantOriginMaster) + } + if !reflect.DeepEqual(branch, wantBranch) { + t.Errorf("Wrong branch status, got %+v; want %+v", branch, wantBranch) + } +} diff --git a/helpers_test.go b/helpers_test.go index 5c4edd5..1ef375e 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -18,24 +18,26 @@ func checkFatal(t *testing.T, err error) { } } -func cleanupRepo(t *testing.T, repo *git.Repository) { - err := os.RemoveAll(repo.Workdir()) - if err != nil { - t.Errorf("failed cleaning up repo") - } -} - -func newTestRepo(t *testing.T) *git.Repository { - dir, err := ioutil.TempDir("", "test-repo-") +func newTempDir(t *testing.T) string { + dir, err := ioutil.TempDir("", "git-get-repo-") checkFatal(t, errors.Wrap(err, "Failed creating test repo directory")) - repo, err := git.InitRepository(dir, false) - checkFatal(t, errors.Wrap(err, "Failed initializing a temp repo")) - // Automatically remove repo when test is over t.Cleanup(func() { - cleanupRepo(t, repo) + err := os.RemoveAll(dir) + if err != nil { + t.Errorf("failed cleaning up repo") + } }) + + return dir +} + +func newTestRepo(t *testing.T) *git.Repository { + dir := newTempDir(t) + repo, err := git.InitRepository(dir, false) + checkFatal(t, errors.Wrap(err, "Failed initializing a temp repo")) + return repo } diff --git a/repo.go b/repo.go new file mode 100644 index 0000000..36098e3 --- /dev/null +++ b/repo.go @@ -0,0 +1,20 @@ +package main + +import ( + "github.com/pkg/errors" + + git "github.com/libgit2/git2go/v30" +) + +func CloneRepo(url string, path string) (*git.Repository, error) { + options := &git.CloneOptions{ + CheckoutOpts: nil, + FetchOptions: nil, + Bare: false, + CheckoutBranch: "", + RemoteCreateCallback: nil, + } + + repo, err := git.Clone(url, path, options) + return repo, errors.Wrap(err, "Failed cloning repo") +} diff --git a/status_test.go b/status_test.go index c1accb8..3774f3a 100644 --- a/status_test.go +++ b/status_test.go @@ -140,6 +140,28 @@ func TestStatusWithMultipleCommits(t *testing.T) { if len(entries) != 0 { t.Errorf("Repo with no uncommitted files should have no status entries") } + + status, err := NewRepoStatus(repo.Workdir()) + checkFatal(t, err) + + want := RepoStatus{ + HasUntrackedFiles: false, + HasUncommittedChanges: false, + BranchStatuses: nil, + } + + if !reflect.DeepEqual(status, want) { + t.Errorf("Wrong repo status, got %+v; want %+v", status, want) + } +} + +func TestStatusCloned(t *testing.T) { + origin := newTestRepo(t) + dir := newTempDir(t) + + repo, err := CloneRepo(origin.Path(), dir) + checkFatal(t, err) + status, err := NewRepoStatus(repo.Workdir()) checkFatal(t, err)