fix(git): detect when upstream no longer exists

Currently the git segment does not detect when an upstream is removed
(e.g., a PR is merged and the remote branch is deleted).

This change adds functionality to detect when upstream is removed;
if the status reports "branch.upstream" but no "branch.ab", it is gone.

The UI has also been changed to reflect this third state;
it now matches posh-git's behavior, which only shows the Gone icon
if an upstream branch is set but does not report ahead/behind counts.
This commit is contained in:
Chris Donnelly 2022-01-25 16:59:27 -06:00 committed by Jan De Dobbeleer
parent 7dab387a11
commit 06e08074fe
2 changed files with 45 additions and 21 deletions

View file

@ -45,6 +45,7 @@ type Git struct {
Upstream string
UpstreamIcon string
UpstreamURL string
UpstreamGone bool
StashCount int
WorktreeCount int
IsWorkTree bool
@ -205,12 +206,12 @@ func (g *Git) setBranchStatus() {
if g.Behind > 0 {
return fmt.Sprintf(" %s%d", g.props.GetString(BranchBehindIcon, "\u2193"), g.Behind)
}
if g.UpstreamGone {
return fmt.Sprintf(" %s", g.props.GetString(BranchGoneIcon, "\u2262"))
}
if g.Behind == 0 && g.Ahead == 0 && g.Upstream != "" {
return fmt.Sprintf(" %s", g.props.GetString(BranchIdenticalIcon, "\u2261"))
}
if g.Upstream == "" {
return fmt.Sprintf(" %s", g.props.GetString(BranchGoneIcon, "\u2262"))
}
return ""
}
g.BranchStatus = getBranchStatus()
@ -268,7 +269,9 @@ func (g *Git) setGitStatus() {
continue
}
if strings.HasPrefix(line, UPSTREAM) && len(line) > len(UPSTREAM) {
// status reports upstream, but upstream may be gone (must check BRANCHSTATUS)
g.Upstream = line[len(UPSTREAM):]
g.UpstreamGone = true
continue
}
if strings.HasPrefix(line, BRANCHSTATUS) && len(line) > len(BRANCHSTATUS) {
@ -279,6 +282,8 @@ func (g *Git) setGitStatus() {
behind, _ := strconv.Atoi(splitted[1])
g.Behind = -behind
}
// confirmed: upstream exists
g.UpstreamGone = false
continue
}
addToStatus(line)

View file

@ -321,15 +321,16 @@ func TestSetPrettyHEADName(t *testing.T) {
func TestSetGitStatus(t *testing.T) {
cases := []struct {
Case string
Output string
ExpectedWorking *GitStatus
ExpectedStaging *GitStatus
ExpectedHash string
ExpectedRef string
ExpectedUpstream string
ExpectedAhead int
ExpectedBehind int
Case string
Output string
ExpectedWorking *GitStatus
ExpectedStaging *GitStatus
ExpectedHash string
ExpectedRef string
ExpectedUpstream string
ExpectedUpstreamGone bool
ExpectedAhead int
ExpectedBehind int
}{
{
Case: "all different options on working and staging, no remote",
@ -357,6 +358,7 @@ func TestSetGitStatus(t *testing.T) {
# branch.oid 1234567891011121314
# branch.head rework-git-status
# branch.upstream origin/rework-git-status
# branch.ab +0 -0
1 .R N...
1 .C N...
1 .M N...
@ -379,6 +381,7 @@ func TestSetGitStatus(t *testing.T) {
# branch.oid 1234567891011121314
# branch.head rework-git-status
# branch.upstream origin/rework-git-status
# branch.ab +0 -0
`,
ExpectedUpstream: "origin/rework-git-status",
ExpectedHash: "1234567",
@ -414,6 +417,18 @@ func TestSetGitStatus(t *testing.T) {
ExpectedRef: "main",
ExpectedWorking: &GitStatus{ScmStatus: ScmStatus{Added: 3}},
},
{
Case: "remote branch was deleted",
Output: `
# branch.oid 1234567891011121314
# branch.head branch-is-gone
# branch.upstream origin/branch-is-gone
`,
ExpectedUpstream: "origin/branch-is-gone",
ExpectedHash: "1234567",
ExpectedRef: "branch-is-gone",
ExpectedUpstreamGone: true,
},
}
for _, tc := range cases {
env := new(mock.MockedEnvironment)
@ -437,6 +452,7 @@ func TestSetGitStatus(t *testing.T) {
assert.Equal(t, tc.ExpectedHash, g.Hash, tc.Case)
assert.Equal(t, tc.ExpectedRef, g.Ref, tc.Case)
assert.Equal(t, tc.ExpectedUpstream, g.Upstream, tc.Case)
assert.Equal(t, tc.ExpectedUpstreamGone, g.UpstreamGone, tc.Case)
assert.Equal(t, tc.ExpectedAhead, g.Ahead, tc.Case)
assert.Equal(t, tc.ExpectedBehind, g.Behind, tc.Case)
}
@ -505,17 +521,19 @@ func TestGitUpstream(t *testing.T) {
func TestGetBranchStatus(t *testing.T) {
cases := []struct {
Case string
Expected string
Ahead int
Behind int
Upstream string
Case string
Expected string
Ahead int
Behind int
Upstream string
UpstreamGone bool
}{
{Case: "Equal with remote", Expected: " equal", Upstream: branchName},
{Case: "Ahead", Expected: " up2", Ahead: 2},
{Case: "Behind", Expected: " down8", Behind: 8},
{Case: "Behind and ahead", Expected: " up7 down8", Behind: 8, Ahead: 7},
{Case: "Gone", Expected: " gone"},
{Case: "Gone", Expected: " gone", Upstream: branchName, UpstreamGone: true},
{Case: "No remote", Expected: "", Upstream: ""},
{Case: "Default (bug)", Expected: "", Behind: -8, Upstream: "wonky"},
}
@ -530,9 +548,10 @@ func TestGetBranchStatus(t *testing.T) {
scm: scm{
props: props,
},
Ahead: tc.Ahead,
Behind: tc.Behind,
Upstream: tc.Upstream,
Ahead: tc.Ahead,
Behind: tc.Behind,
Upstream: tc.Upstream,
UpstreamGone: tc.UpstreamGone,
}
g.setBranchStatus()
assert.Equal(t, tc.Expected, g.BranchStatus, tc.Case)