summaryrefslogtreecommitdiffstats
path: root/pkg/commands/git_commands/branch_loader.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/commands/git_commands/branch_loader.go')
-rw-r--r--pkg/commands/git_commands/branch_loader.go158
1 files changed, 138 insertions, 20 deletions
diff --git a/pkg/commands/git_commands/branch_loader.go b/pkg/commands/git_commands/branch_loader.go
index 198368502..929d5964d 100644
--- a/pkg/commands/git_commands/branch_loader.go
+++ b/pkg/commands/git_commands/branch_loader.go
@@ -5,6 +5,7 @@ import (
"regexp"
"strconv"
"strings"
+ "time"
"github.com/jesseduffield/generics/set"
"github.com/jesseduffield/go-git/v5/config"
@@ -14,6 +15,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
"golang.org/x/exp/slices"
+ "golang.org/x/sync/errgroup"
)
// context:
@@ -40,6 +42,7 @@ type BranchInfo struct {
// BranchLoader returns a list of Branch objects for the current repo
type BranchLoader struct {
*common.Common
+ *GitCommon
cmd oscommands.ICmdObjBuilder
getCurrentBranchInfo func() (BranchInfo, error)
config BranchLoaderConfigCommands
@@ -47,12 +50,14 @@ type BranchLoader struct {
func NewBranchLoader(
cmn *common.Common,
+ gitCommon *GitCommon,
cmd oscommands.ICmdObjBuilder,
getCurrentBranchInfo func() (BranchInfo, error),
config BranchLoaderConfigCommands,
) *BranchLoader {
return &BranchLoader{
Common: cmn,
+ GitCommon: gitCommon,
cmd: cmd,
getCurrentBranchInfo: getCurrentBranchInfo,
config: config,
@@ -60,8 +65,14 @@ func NewBranchLoader(
}
// Load the list of branches for the current repo
-func (self *BranchLoader) Load(reflogCommits []*models.Commit) ([]*models.Branch, error) {
- branches := self.obtainBranches()
+func (self *BranchLoader) Load(reflogCommits []*models.Commit,
+ mainBranches *MainBranches,
+ oldBranches []*models.Branch,
+ loadBehindCounts bool,
+ onWorker func(func() error),
+ renderFunc func(),
+) ([]*models.Branch, error) {
+ branches := self.obtainBranches(self.version.IsAtLeast(2, 22, 0))
if self.AppState.LocalBranchSortOrder == "recency" {
reflogBranches := self.obtainReflogBranches(reflogCommits)
@@ -119,12 +130,109 @@ func (self *BranchLoader) Load(reflogCommits []*models.Commit) ([]*models.Branch
branch.UpstreamRemote = match.Remote
branch.UpstreamBranch = match.Merge.Short()
}
+
+ // If the branch already existed, take over its BehindBaseBranch value
+ // to reduce flicker
+ if oldBranch, found := lo.Find(oldBranches, func(b *models.Branch) bool {
+ return b.Name == branch.Name
+ }); found {
+ branch.BehindBaseBranch.Store(oldBranch.BehindBaseBranch.Load())
+ }
+ }
+
+ if loadBehindCounts && self.UserConfig.Gui.ShowDivergenceFromBaseBranch != "none" {
+ onWorker(func() error {
+ return self.GetBehindBaseBranchValuesForAllBranches(branches, mainBranches, renderFunc)
+ })
}
return branches, nil
}
-func (self *BranchLoader) obtainBranches() []*models.Branch {
+func (self *BranchLoader) GetBehindBaseBranchValuesForAllBranches(
+ branches []*models.Branch,
+ mainBranches *MainBranches,
+ renderFunc func(),
+) error {
+ mainBranchRefs := mainBranches.Get()
+ if len(mainBranchRefs) == 0 {
+ return nil
+ }
+
+ t := time.Now()
+ errg := errgroup.Group{}
+
+ for _, branch := range branches {
+ errg.Go(func() error {
+ baseBranch, err := self.GetBaseBranch(branch, mainBranches)
+ if err != nil {
+ return err
+ }
+ behind := 0 // prime it in case something below fails
+ if baseBranch != "" {
+ 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
+ }
+ // The format of the output is "<ahead>\t<behind>"
+ aheadBehindStr := strings.Split(strings.TrimSpace(output), "\t")
+ if len(aheadBehindStr) == 2 {
+ if value, err := strconv.Atoi(aheadBehindStr[1]); err == nil {
+ behind = value
+ }
+ }
+ }
+ branch.BehindBaseBranch.Store(int32(behind))
+ return nil
+ })
+ }
+
+ err := errg.Wait()
+ self.Log.Debugf("time to get behind base branch values for all branches: %s", time.Since(t))
+ renderFunc()
+ return err
+}
+
+// Find the base branch for the given branch (i.e. the main branch that the
+// given branch was forked off of)
+//
+// Note that this function may return an empty string even if the returned error
+// is nil, e.g. when none of the configured main branches exist. This is not
+// considered an error condition, so callers need to check both the returned
+// error and whether the returned base branch is empty (and possibly react
+// differently in both cases).
+func (self *BranchLoader) GetBaseBranch(branch *models.Branch, mainBranches *MainBranches) (string, error) {
+ mergeBase := mainBranches.GetMergeBase(branch.FullRefName())
+ if mergeBase == "" {
+ return "", nil
+ }
+
+ output, err := self.cmd.New(
+ NewGitCmd("for-each-ref").
+ Arg("--contains").
+ Arg(mergeBase).
+ Arg("--format=%(refname)").
+ Arg(mainBranches.Get()...).
+ ToArgv(),
+ ).DontLog().RunWithOutput()
+ if err != nil {
+ return "", err
+ }
+ trimmedOutput := strings.TrimSpace(output)
+ split := strings.Split(trimmedOutput, "\n")
+ if len(split) == 0 || split[0] == "" {
+ return "", nil
+ }
+ return split[0], nil
+}
+
+func (self *BranchLoader) obtainBranches(canUsePushTrack bool) []*models.Branch {
output, err := self.getRawBranches()
if err != nil {
panic(err)
@@ -147,7 +255,7 @@ func (self *BranchLoader) obtainBranches() []*models.Branch {
}
storeCommitDateAsRecency := self.AppState.LocalBranchSortOrder != "recency"
- return obtainBranch(split, storeCommitDateAsRecency), true
+ return obtainBranch(split, storeCommitDateAsRecency, canUsePushTrack), true
})
}
@@ -183,23 +291,31 @@ var branchFields = []string{
"refname:short",
"upstream:short",
"upstream:track",
+ "push:track",
"subject",
"objectname",
"committerdate:unix",
}
// Obtain branch information from parsed line output of getRawBranches()
-func obtainBranch(split []string, storeCommitDateAsRecency bool) *models.Branch {
+func obtainBranch(split []string, storeCommitDateAsRecency bool, canUsePushTrack bool) *models.Branch {
headMarker := split[0]
fullName := split[1]
upstreamName := split[2]
track := split[3]
- subject := split[4]
- commitHash := split[5]
- commitDate := split[6]
+ pushTrack := split[4]
+ subject := split[5]
+ commitHash := split[6]
+ commitDate := split[7]
name := strings.TrimPrefix(fullName, "heads/")
- pushables, pullables, gone := parseUpstreamInfo(upstreamName, track)
+ aheadForPull, behindForPull, gone := parseUpstreamInfo(upstreamName, track)
+ var aheadForPush, behindForPush string
+ if canUsePushTrack {
+ aheadForPush, behindForPush, _ = parseUpstreamInfo(upstreamName, pushTrack)
+ } else {
+ aheadForPush, behindForPush = aheadForPull, behindForPull
+ }
recency := ""
if storeCommitDateAsRecency {
@@ -209,14 +325,16 @@ func obtainBranch(split []string, storeCommitDateAsRecency bool) *models.Branch
}
return &models.Branch{
- Name: name,
- Recency: recency,
- Pushables: pushables,
- Pullables: pullables,
- UpstreamGone: gone,
- Head: headMarker == "*",
- Subject: subject,
- CommitHash: commitHash,
+ Name: name,
+ Recency: recency,
+ AheadForPull: aheadForPull,
+ BehindForPull: behindForPull,
+ AheadForPush: aheadForPush,
+ BehindForPush: behindForPush,
+ UpstreamGone: gone,
+ Head: headMarker == "*",
+ Subject: subject,
+ CommitHash: commitHash,
}
}
@@ -232,10 +350,10 @@ func parseUpstreamInfo(upstreamName string, track string) (string, string, bool)
return "?", "?", true
}
- pushables := parseDifference(track, `ahead (\d+)`)
- pullables := parseDifference(track, `behind (\d+)`)
+ ahead := parseDifference(track, `ahead (\d+)`)
+ behind := parseDifference(track, `behind (\d+)`)
- return pushables, pullables, false
+ return ahead, behind, false
}
func parseDifference(track string, regexStr string) string {