Show divergence from base branch in branches list

This commit is contained in:
Stefan Haller 2024-04-30 12:34:05 +02:00 committed by Jesse Duffield
parent 316ea99d67
commit 6eaece3696
15 changed files with 98 additions and 36 deletions

View file

@ -1,6 +1,7 @@
package git_commands
import (
"errors"
"fmt"
"regexp"
"strconv"
@ -60,7 +61,7 @@ func NewBranchLoader(
}
// Load the list of branches for the current repo
func (self *BranchLoader) Load(reflogCommits []*models.Commit) ([]*models.Branch, error) {
func (self *BranchLoader) Load(reflogCommits []*models.Commit, existingMainBranches *ExistingMainBranches, oldBranches []*models.Branch, onWorker func(func() error), renderFunc func()) ([]*models.Branch, error) {
branches := self.obtainBranches()
if self.AppState.LocalBranchSortOrder == "recency" {
@ -119,8 +120,49 @@ func (self *BranchLoader) Load(reflogCommits []*models.Commit) ([]*models.Branch
branch.UpstreamRemote = match.Remote
branch.UpstreamBranch = match.Merge.Short()
}
if oldBranch, found := lo.Find(oldBranches, func(b *models.Branch) bool {
return b.Name == branch.Name
}); found {
branch.BehindBaseBranch.Store(oldBranch.BehindBaseBranch.Load())
}
}
onWorker(func() error {
mainBranches := existingMainBranches.Get()
if len(mainBranches) > 0 {
for _, branch := range branches {
baseBranch, err := self.GetBaseBranch(branch, existingMainBranches)
if err != nil {
return err
}
if baseBranch == "" {
continue
}
output, err := self.cmd.New(
NewGitCmd("rev-list").
Arg("--left-right").
Arg("--count").
Arg(fmt.Sprintf("%s...%s", branch.FullRefName(), baseBranch)).
ToArgv(),
).DontLog().RunWithOutput()
if err != nil {
return err
}
aheadBehindStr := strings.Split(strings.TrimSpace(output), "\t")
if len(aheadBehindStr) != 2 {
return errors.New("unexpected output from git rev-list")
}
if behind, err := strconv.Atoi(aheadBehindStr[1]); err == nil {
branch.BehindBaseBranch.Store(int32(behind))
renderFunc()
}
}
}
return nil
})
return branches, nil
}

View file

@ -1,6 +1,9 @@
package models
import "fmt"
import (
"fmt"
"sync/atomic"
)
// Branch : A git branch
// duplicating this for now
@ -28,6 +31,11 @@ type Branch struct {
Subject string
// commit hash
CommitHash string
// How far we have fallen behind our base branch. 0 means either not
// determined yet, or up to date with base branch. (We don't need to
// distinguish the two, as we don't draw anything in both cases.)
BehindBaseBranch atomic.Int32
}
func (b *Branch) FullRefName() string {

View file

@ -452,7 +452,20 @@ func (self *RefreshHelper) refreshBranches(refreshWorktrees bool, keepBranchSele
}
}
branches, err := self.c.Git().Loaders.BranchLoader.Load(reflogCommits)
branches, err := self.c.Git().Loaders.BranchLoader.Load(reflogCommits, self.c.Model().ExistingMainBranches, self.c.Model().Branches,
func(f func() error) {
self.c.OnWorker(func(_ gocui.Task) error {
return f()
})
},
func() {
self.c.OnUIThread(func() error {
if err := self.c.Contexts().Branches.HandleRender(); err != nil {
self.c.Log.Error(err)
}
return nil
})
})
if err != nil {
self.c.Log.Error(err)
}

View file

@ -155,32 +155,31 @@ func BranchStatus(
return style.FgCyan.Sprintf("%s %s", itemOperationStr, utils.Loader(now, userConfig.Gui.Spinner))
}
if !branch.IsTrackingRemote() {
return ""
result := ""
if branch.IsTrackingRemote() {
if branch.UpstreamGone {
result = style.FgRed.Sprint(tr.UpstreamGone)
} else if branch.MatchesUpstream() {
result = style.FgGreen.Sprint("✓")
} else if branch.RemoteBranchNotStoredLocally() {
result = style.FgMagenta.Sprint("?")
} else if branch.HasCommitsToPull() && branch.HasCommitsToPush() {
result = style.FgYellow.Sprintf("↓%s↑%s", branch.Pullables, branch.Pushables)
} else if branch.HasCommitsToPull() {
result = style.FgYellow.Sprintf("↓%s", branch.Pullables)
} else if branch.HasCommitsToPush() {
result = style.FgYellow.Sprintf("↑%s", branch.Pushables)
}
}
if branch.UpstreamGone {
return style.FgRed.Sprint(tr.UpstreamGone)
if v := branch.BehindBaseBranch.Load(); v != 0 {
if result != "" {
result += " "
}
result += style.FgCyan.Sprintf("↓%d", v)
}
if branch.MatchesUpstream() {
return style.FgGreen.Sprint("✓")
}
if branch.RemoteBranchNotStoredLocally() {
return style.FgMagenta.Sprint("?")
}
if branch.HasCommitsToPull() && branch.HasCommitsToPush() {
return style.FgYellow.Sprintf("↓%s↑%s", branch.Pullables, branch.Pushables)
}
if branch.HasCommitsToPull() {
return style.FgYellow.Sprintf("↓%s", branch.Pullables)
}
if branch.HasCommitsToPush() {
return style.FgYellow.Sprintf("↑%s", branch.Pushables)
}
return ""
return result
}
func SetCustomBranches(customBranchColors map[string]string) {

View file

@ -40,7 +40,7 @@ var ForcePush = NewIntegrationTest(NewIntegrationTestArgs{
Contains("one"),
)
t.Views().Status().Content(Equals("✓ repo → master"))
t.Views().Status().Content(Equals("✓ ↓1 repo → master"))
t.Views().Remotes().Focus().
Lines(Contains("origin")).

View file

@ -42,7 +42,7 @@ var ForcePushMultipleMatching = NewIntegrationTest(NewIntegrationTestArgs{
Contains("one"),
)
t.Views().Status().Content(Equals("✓ repo → master"))
t.Views().Status().Content(Equals("✓ ↓1 repo → master"))
t.Views().Branches().
Lines(

View file

@ -41,7 +41,7 @@ var ForcePushMultipleUpstream = NewIntegrationTest(NewIntegrationTestArgs{
Contains("one"),
)
t.Views().Status().Content(Equals("✓ repo → master"))
t.Views().Status().Content(Equals("✓ ↓1 repo → master"))
t.Views().Branches().
Lines(

View file

@ -36,6 +36,6 @@ var Pull = NewIntegrationTest(NewIntegrationTestArgs{
Contains("one"),
)
t.Views().Status().Content(Equals("✓ repo → master"))
t.Views().Status().Content(Equals("✓ ↓1 repo → master"))
},
})

View file

@ -40,6 +40,6 @@ var PullAndSetUpstream = NewIntegrationTest(NewIntegrationTestArgs{
Contains("one"),
)
t.Views().Status().Content(Equals("✓ repo → master"))
t.Views().Status().Content(Equals("✓ ↓1 repo → master"))
},
})

View file

@ -39,7 +39,7 @@ var PullMerge = NewIntegrationTest(NewIntegrationTestArgs{
IsFocused().
Press(keys.Universal.Pull)
t.Views().Status().Content(Equals("↑2 repo → master"))
t.Views().Status().Content(Equals("↑2 ↓2 repo → master"))
t.Views().Commits().
Lines(

View file

@ -62,7 +62,7 @@ var PullMergeConflict = NewIntegrationTest(NewIntegrationTestArgs{
t.Common().ContinueOnConflictsResolved()
t.Views().Status().Content(Equals("↑2 repo → master"))
t.Views().Status().Content(Equals("↑2 ↓2 repo → master"))
t.Views().Commits().
Focus().

View file

@ -41,7 +41,7 @@ var PullRebase = NewIntegrationTest(NewIntegrationTestArgs{
IsFocused().
Press(keys.Universal.Pull)
t.Views().Status().Content(Equals("↑1 repo → master"))
t.Views().Status().Content(Equals("↑1 ↓2 repo → master"))
t.Views().Commits().
Lines(

View file

@ -63,7 +63,7 @@ var PullRebaseConflict = NewIntegrationTest(NewIntegrationTestArgs{
t.Common().ContinueOnConflictsResolved()
t.Views().Status().Content(Equals("↑1 repo → master"))
t.Views().Status().Content(Equals("↑1 ↓2 repo → master"))
t.Views().Commits().
Focus().

View file

@ -76,7 +76,7 @@ var PullRebaseInteractiveConflict = NewIntegrationTest(NewIntegrationTestArgs{
t.Common().ContinueOnConflictsResolved()
t.Views().Status().Content(Equals("↑2 repo → master"))
t.Views().Status().Content(Equals("↑2 ↓2 repo → master"))
t.Views().Commits().
Focus().

View file

@ -85,7 +85,7 @@ var PullRebaseInteractiveConflictDrop = NewIntegrationTest(NewIntegrationTestArg
t.Common().ContinueOnConflictsResolved()
t.Views().Status().Content(Equals("↑1 repo → master"))
t.Views().Status().Content(Equals("↑1 ↓2 repo → master"))
t.Views().Commits().
Focus().