diff options
Diffstat (limited to 'pkg/commands/git_commands/branch_loader.go')
-rw-r--r-- | pkg/commands/git_commands/branch_loader.go | 158 |
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 { |