mirror of
https://github.com/grdl/git-get.git
synced 2026-02-05 07:23:46 +00:00
Count commits ahead and behind the upstream branch
This commit is contained in:
@@ -126,17 +126,30 @@ func newRepoWithBranchBehind(t *testing.T) *Repo {
|
||||
return r
|
||||
}
|
||||
|
||||
// generate repo with 2 commits ahead and 3 behind the origin
|
||||
func newRepoWithBranchAheadAndBehind(t *testing.T) *Repo {
|
||||
origin := newRepoWithCommit(t)
|
||||
|
||||
r := origin.clone(t)
|
||||
r.writeFile(t, "local.new", "I'm a new file on local")
|
||||
r.writeFile(t, "local.new", "local 1")
|
||||
r.addFile(t, "local.new")
|
||||
r.newCommit(t, "new local commit")
|
||||
r.newCommit(t, "1st local commit")
|
||||
|
||||
origin.writeFile(t, "origin.new", "I'm a new file on origin")
|
||||
r.writeFile(t, "local.new", "local 2")
|
||||
r.addFile(t, "local.new")
|
||||
r.newCommit(t, "2nd local commit")
|
||||
|
||||
origin.writeFile(t, "origin.new", "origin 1")
|
||||
origin.addFile(t, "origin.new")
|
||||
origin.newCommit(t, "new origin commit")
|
||||
origin.newCommit(t, "1st origin commit")
|
||||
|
||||
origin.writeFile(t, "origin.new", "origin 2")
|
||||
origin.addFile(t, "origin.new")
|
||||
origin.newCommit(t, "2nd origin commit")
|
||||
|
||||
origin.writeFile(t, "origin.new", "origin 3")
|
||||
origin.addFile(t, "origin.new")
|
||||
origin.newCommit(t, "3rd origin commit")
|
||||
|
||||
r.fetch(t)
|
||||
return r
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/go-git/go-git/v5/plumbing/revlist"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/go-git/go-billy/v5/osfs"
|
||||
@@ -35,10 +37,10 @@ type RepoStatus struct {
|
||||
}
|
||||
|
||||
type BranchStatus struct {
|
||||
Name string
|
||||
Upstream string
|
||||
NeedsPull bool
|
||||
NeedsPush bool
|
||||
Name string
|
||||
Upstream string
|
||||
Ahead int
|
||||
Behind int
|
||||
}
|
||||
|
||||
func (r *Repo) LoadStatus() error {
|
||||
@@ -172,14 +174,14 @@ func (r *Repo) newBranchStatus(branch string) (*BranchStatus, error) {
|
||||
return bs, nil
|
||||
}
|
||||
|
||||
needsPull, needsPush, err := r.needsPullOrPush(branch, upstream)
|
||||
ahead, behind, err := r.aheadBehind(branch, upstream)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bs.Upstream = upstream
|
||||
bs.NeedsPush = needsPush
|
||||
bs.NeedsPull = needsPull
|
||||
bs.Ahead = ahead
|
||||
bs.Behind = behind
|
||||
|
||||
return bs, nil
|
||||
}
|
||||
@@ -215,61 +217,50 @@ func (r *Repo) upstream(branch string) (string, error) {
|
||||
return remote + "/" + merge, nil
|
||||
}
|
||||
|
||||
func (r *Repo) needsPullOrPush(localBranch string, upstreamBranch string) (needsPull bool, needsPush bool, err error) {
|
||||
func (r *Repo) aheadBehind(localBranch string, upstreamBranch string) (ahead int, behind int, err error) {
|
||||
localHash, err := r.ResolveRevision(plumbing.Revision(localBranch))
|
||||
if err != nil {
|
||||
return false, false, errors.Wrapf(err, "Failed resolving revision %s", localBranch)
|
||||
return 0, 0, errors.Wrapf(err, "Failed resolving revision %s", localBranch)
|
||||
}
|
||||
|
||||
upstreamHash, err := r.ResolveRevision(plumbing.Revision(upstreamBranch))
|
||||
if err != nil {
|
||||
return false, false, errors.Wrapf(err, "Failed resolving revision %s", upstreamBranch)
|
||||
return 0, 0, errors.Wrapf(err, "Failed resolving revision %s", upstreamBranch)
|
||||
}
|
||||
|
||||
localCommit, err := r.CommitObject(*localHash)
|
||||
behind, err = r.revlistCount(*localHash, *upstreamHash)
|
||||
if err != nil {
|
||||
return false, false, errors.Wrapf(err, "Failed finding a commit for hash %s", localHash.String())
|
||||
return 0, 0, errors.Wrapf(err, "Failed counting commits behind %s", upstreamBranch)
|
||||
}
|
||||
|
||||
upstreamCommit, err := r.CommitObject(*upstreamHash)
|
||||
ahead, err = r.revlistCount(*upstreamHash, *localHash)
|
||||
if err != nil {
|
||||
return false, false, errors.Wrapf(err, "Failed finding a commit for hash %s", upstreamHash.String())
|
||||
return 0, 0, errors.Wrapf(err, "Failed counting commits ahead of %s", upstreamBranch)
|
||||
}
|
||||
|
||||
// If local branch hash is the same as upstream, it means there is no difference between local and upstream
|
||||
if *localHash == *upstreamHash {
|
||||
return false, false, nil
|
||||
}
|
||||
|
||||
commons, err := localCommit.MergeBase(upstreamCommit)
|
||||
if err != nil {
|
||||
return false, false, errors.Wrapf(err, "Failed finding common ancestors for branches %s & %s", localBranch, upstreamBranch)
|
||||
}
|
||||
|
||||
if len(commons) == 0 {
|
||||
// TODO: No common ancestors. This should be an error
|
||||
return false, false, nil
|
||||
}
|
||||
|
||||
if len(commons) > 1 {
|
||||
// TODO: multiple best ancestors. How to handle this?
|
||||
return false, false, nil
|
||||
}
|
||||
|
||||
mergeBase := commons[0]
|
||||
|
||||
// If merge base is the same as upstream branch, local branch is ahead and push is needed
|
||||
// If merge base is the same as local branch, local branch is behind and pull is needed
|
||||
// If merge base is something else, branches have diverged and merge is needed (both pull and push)
|
||||
// ref: https://stackoverflow.com/a/17723781/1085632
|
||||
|
||||
if mergeBase.Hash == *upstreamHash {
|
||||
return false, true, nil
|
||||
}
|
||||
|
||||
if mergeBase.Hash == *localHash {
|
||||
return true, false, nil
|
||||
}
|
||||
|
||||
return true, true, nil
|
||||
return ahead, behind, nil
|
||||
}
|
||||
|
||||
// revlistCount counts the number of commits between two hashes.
|
||||
// https://github.com/src-d/go-git/issues/757#issuecomment-452697701
|
||||
// TODO: See if this can be optimized. Running the loop twice feels wrong.
|
||||
func (r *Repo) revlistCount(hash1, hash2 plumbing.Hash) (int, error) {
|
||||
ref1hist, err := revlist.Objects(r.Storer, []plumbing.Hash{hash1}, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
ref2hist, err := revlist.Objects(r.Storer, []plumbing.Hash{hash2}, ref1hist)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
count := 0
|
||||
for _, h := range ref2hist {
|
||||
if _, err = r.CommitObject(h); err == nil {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
@@ -34,10 +34,10 @@ func TestStatus(t *testing.T) {
|
||||
CurrentBranch: "master",
|
||||
Branches: []*BranchStatus{
|
||||
{
|
||||
Name: "master",
|
||||
Upstream: "",
|
||||
NeedsPull: false,
|
||||
NeedsPush: false,
|
||||
Name: "master",
|
||||
Upstream: "",
|
||||
Behind: 0,
|
||||
Ahead: 0,
|
||||
},
|
||||
},
|
||||
}},
|
||||
@@ -47,10 +47,10 @@ func TestStatus(t *testing.T) {
|
||||
CurrentBranch: "master",
|
||||
Branches: []*BranchStatus{
|
||||
{
|
||||
Name: "master",
|
||||
Upstream: "",
|
||||
NeedsPull: false,
|
||||
NeedsPush: false,
|
||||
Name: "master",
|
||||
Upstream: "",
|
||||
Behind: 0,
|
||||
Ahead: 0,
|
||||
},
|
||||
},
|
||||
}},
|
||||
@@ -60,10 +60,10 @@ func TestStatus(t *testing.T) {
|
||||
CurrentBranch: "master",
|
||||
Branches: []*BranchStatus{
|
||||
{
|
||||
Name: "master",
|
||||
Upstream: "",
|
||||
NeedsPull: false,
|
||||
NeedsPush: false,
|
||||
Name: "master",
|
||||
Upstream: "",
|
||||
Behind: 0,
|
||||
Ahead: 0,
|
||||
},
|
||||
},
|
||||
}},
|
||||
@@ -73,15 +73,15 @@ func TestStatus(t *testing.T) {
|
||||
CurrentBranch: "master",
|
||||
Branches: []*BranchStatus{
|
||||
{
|
||||
Name: "master",
|
||||
Upstream: "",
|
||||
NeedsPull: false,
|
||||
NeedsPush: false,
|
||||
Name: "master",
|
||||
Upstream: "",
|
||||
Behind: 0,
|
||||
Ahead: 0,
|
||||
}, {
|
||||
Name: "local",
|
||||
Upstream: "",
|
||||
NeedsPull: false,
|
||||
NeedsPush: false,
|
||||
Name: "local",
|
||||
Upstream: "",
|
||||
Behind: 0,
|
||||
Ahead: 0,
|
||||
},
|
||||
},
|
||||
}},
|
||||
@@ -91,15 +91,15 @@ func TestStatus(t *testing.T) {
|
||||
CurrentBranch: "local",
|
||||
Branches: []*BranchStatus{
|
||||
{
|
||||
Name: "master",
|
||||
Upstream: "origin/master",
|
||||
NeedsPull: false,
|
||||
NeedsPush: false,
|
||||
Name: "master",
|
||||
Upstream: "origin/master",
|
||||
Behind: 0,
|
||||
Ahead: 0,
|
||||
}, {
|
||||
Name: "local",
|
||||
Upstream: "",
|
||||
NeedsPull: false,
|
||||
NeedsPush: false,
|
||||
Name: "local",
|
||||
Upstream: "",
|
||||
Behind: 0,
|
||||
Ahead: 0,
|
||||
},
|
||||
},
|
||||
}},
|
||||
@@ -109,10 +109,10 @@ func TestStatus(t *testing.T) {
|
||||
CurrentBranch: StatusDetached,
|
||||
Branches: []*BranchStatus{
|
||||
{
|
||||
Name: "master",
|
||||
Upstream: "",
|
||||
NeedsPull: false,
|
||||
NeedsPush: false,
|
||||
Name: "master",
|
||||
Upstream: "",
|
||||
Behind: 0,
|
||||
Ahead: 0,
|
||||
},
|
||||
},
|
||||
}},
|
||||
@@ -122,10 +122,10 @@ func TestStatus(t *testing.T) {
|
||||
CurrentBranch: "master",
|
||||
Branches: []*BranchStatus{
|
||||
{
|
||||
Name: "master",
|
||||
Upstream: "origin/master",
|
||||
NeedsPull: false,
|
||||
NeedsPush: true,
|
||||
Name: "master",
|
||||
Upstream: "origin/master",
|
||||
Behind: 0,
|
||||
Ahead: 1,
|
||||
},
|
||||
},
|
||||
}},
|
||||
@@ -135,10 +135,10 @@ func TestStatus(t *testing.T) {
|
||||
CurrentBranch: "master",
|
||||
Branches: []*BranchStatus{
|
||||
{
|
||||
Name: "master",
|
||||
Upstream: "origin/master",
|
||||
NeedsPull: true,
|
||||
NeedsPush: false,
|
||||
Name: "master",
|
||||
Upstream: "origin/master",
|
||||
Behind: 1,
|
||||
Ahead: 0,
|
||||
},
|
||||
},
|
||||
}},
|
||||
@@ -148,10 +148,10 @@ func TestStatus(t *testing.T) {
|
||||
CurrentBranch: "master",
|
||||
Branches: []*BranchStatus{
|
||||
{
|
||||
Name: "master",
|
||||
Upstream: "origin/master",
|
||||
NeedsPull: true,
|
||||
NeedsPush: true,
|
||||
Name: "master",
|
||||
Upstream: "origin/master",
|
||||
Behind: 3,
|
||||
Ahead: 2,
|
||||
},
|
||||
},
|
||||
}},
|
||||
|
||||
Reference in New Issue
Block a user