mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-05-11 12:25:47 +02:00
Show divergence from base branch in branches list
This commit is contained in:
parent
5b613f5bc7
commit
373b1970ca
11 changed files with 281 additions and 51 deletions
|
@ -187,6 +187,10 @@ gui:
|
||||||
# If true, show commit hashes alongside branch names in the branches view.
|
# If true, show commit hashes alongside branch names in the branches view.
|
||||||
showBranchCommitHash: false
|
showBranchCommitHash: false
|
||||||
|
|
||||||
|
# Whether to show the divergence from the base branch in the branches view.
|
||||||
|
# One of: 'none' | 'onlyArrow' | 'arrowAndNumber'
|
||||||
|
showDivergenceFromBaseBranch: none
|
||||||
|
|
||||||
# Height of the command log view
|
# Height of the command log view
|
||||||
commandLogSize: 8
|
commandLogSize: 8
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/jesseduffield/generics/set"
|
"github.com/jesseduffield/generics/set"
|
||||||
"github.com/jesseduffield/go-git/v5/config"
|
"github.com/jesseduffield/go-git/v5/config"
|
||||||
|
@ -14,6 +15,7 @@ import (
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
// context:
|
// context:
|
||||||
|
@ -63,7 +65,13 @@ func NewBranchLoader(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the list of branches for the current repo
|
// 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,
|
||||||
|
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))
|
branches := self.obtainBranches(self.version.IsAtLeast(2, 22, 0))
|
||||||
|
|
||||||
if self.AppState.LocalBranchSortOrder == "recency" {
|
if self.AppState.LocalBranchSortOrder == "recency" {
|
||||||
|
@ -122,11 +130,75 @@ func (self *BranchLoader) Load(reflogCommits []*models.Commit) ([]*models.Branch
|
||||||
branch.UpstreamRemote = match.Remote
|
branch.UpstreamRemote = match.Remote
|
||||||
branch.UpstreamBranch = match.Merge.Short()
|
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
|
return branches, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// Find the base branch for the given branch (i.e. the main branch that the
|
||||||
// given branch was forked off of)
|
// given branch was forked off of)
|
||||||
//
|
//
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
// Branch : A git branch
|
// Branch : A git branch
|
||||||
// duplicating this for now
|
// duplicating this for now
|
||||||
|
@ -32,6 +35,11 @@ type Branch struct {
|
||||||
Subject string
|
Subject string
|
||||||
// commit hash
|
// commit hash
|
||||||
CommitHash string
|
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 {
|
func (b *Branch) FullRefName() string {
|
||||||
|
|
|
@ -129,6 +129,9 @@ type GuiConfig struct {
|
||||||
CommitHashLength int `yaml:"commitHashLength" jsonschema:"minimum=0"`
|
CommitHashLength int `yaml:"commitHashLength" jsonschema:"minimum=0"`
|
||||||
// If true, show commit hashes alongside branch names in the branches view.
|
// If true, show commit hashes alongside branch names in the branches view.
|
||||||
ShowBranchCommitHash bool `yaml:"showBranchCommitHash"`
|
ShowBranchCommitHash bool `yaml:"showBranchCommitHash"`
|
||||||
|
// Whether to show the divergence from the base branch in the branches view.
|
||||||
|
// One of: 'none' | 'onlyArrow' | 'arrowAndNumber'
|
||||||
|
ShowDivergenceFromBaseBranch string `yaml:"showDivergenceFromBaseBranch" jsonschema:"enum=none,enum=onlyArrow,enum=arrowAndNumber"`
|
||||||
// Height of the command log view
|
// Height of the command log view
|
||||||
CommandLogSize int `yaml:"commandLogSize" jsonschema:"minimum=0"`
|
CommandLogSize int `yaml:"commandLogSize" jsonschema:"minimum=0"`
|
||||||
// Whether to split the main window when viewing file changes.
|
// Whether to split the main window when viewing file changes.
|
||||||
|
@ -673,27 +676,28 @@ func GetDefaultConfig() *UserConfig {
|
||||||
UnstagedChangesColor: []string{"red"},
|
UnstagedChangesColor: []string{"red"},
|
||||||
DefaultFgColor: []string{"default"},
|
DefaultFgColor: []string{"default"},
|
||||||
},
|
},
|
||||||
CommitLength: CommitLengthConfig{Show: true},
|
CommitLength: CommitLengthConfig{Show: true},
|
||||||
SkipNoStagedFilesWarning: false,
|
SkipNoStagedFilesWarning: false,
|
||||||
ShowListFooter: true,
|
ShowListFooter: true,
|
||||||
ShowCommandLog: true,
|
ShowCommandLog: true,
|
||||||
ShowBottomLine: true,
|
ShowBottomLine: true,
|
||||||
ShowPanelJumps: true,
|
ShowPanelJumps: true,
|
||||||
ShowFileTree: true,
|
ShowFileTree: true,
|
||||||
ShowRandomTip: true,
|
ShowRandomTip: true,
|
||||||
ShowIcons: false,
|
ShowIcons: false,
|
||||||
NerdFontsVersion: "",
|
NerdFontsVersion: "",
|
||||||
ShowFileIcons: true,
|
ShowFileIcons: true,
|
||||||
CommitHashLength: 8,
|
CommitHashLength: 8,
|
||||||
ShowBranchCommitHash: false,
|
ShowBranchCommitHash: false,
|
||||||
CommandLogSize: 8,
|
ShowDivergenceFromBaseBranch: "none",
|
||||||
SplitDiff: "auto",
|
CommandLogSize: 8,
|
||||||
SkipRewordInEditorWarning: false,
|
SplitDiff: "auto",
|
||||||
WindowSize: "normal",
|
SkipRewordInEditorWarning: false,
|
||||||
Border: "rounded",
|
WindowSize: "normal",
|
||||||
AnimateExplosion: true,
|
Border: "rounded",
|
||||||
PortraitMode: "auto",
|
AnimateExplosion: true,
|
||||||
FilterMode: "substring",
|
PortraitMode: "auto",
|
||||||
|
FilterMode: "substring",
|
||||||
Spinner: SpinnerConfig{
|
Spinner: SpinnerConfig{
|
||||||
Frames: []string{"|", "/", "-", "\\"},
|
Frames: []string{"|", "/", "-", "\\"},
|
||||||
Rate: 50,
|
Rate: 50,
|
||||||
|
|
|
@ -7,7 +7,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (config *UserConfig) Validate() error {
|
func (config *UserConfig) Validate() error {
|
||||||
if err := validateEnum("gui.statusPanelView", config.Gui.StatusPanelView, []string{"dashboard", "allBranchesLog"}); err != nil {
|
if err := validateEnum("gui.statusPanelView", config.Gui.StatusPanelView,
|
||||||
|
[]string{"dashboard", "allBranchesLog"}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := validateEnum("gui.showDivergenceFromBaseBranch", config.Gui.ShowDivergenceFromBaseBranch,
|
||||||
|
[]string{"none", "onlyArrow", "arrowAndNumber"}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -130,7 +130,7 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
|
||||||
if self.c.AppState.LocalBranchSortOrder == "recency" {
|
if self.c.AppState.LocalBranchSortOrder == "recency" {
|
||||||
refresh("reflog and branches", func() { self.refreshReflogAndBranches(includeWorktreesWithBranches, options.KeepBranchSelectionIndex) })
|
refresh("reflog and branches", func() { self.refreshReflogAndBranches(includeWorktreesWithBranches, options.KeepBranchSelectionIndex) })
|
||||||
} else {
|
} else {
|
||||||
refresh("branches", func() { self.refreshBranches(includeWorktreesWithBranches, options.KeepBranchSelectionIndex) })
|
refresh("branches", func() { self.refreshBranches(includeWorktreesWithBranches, options.KeepBranchSelectionIndex, true) })
|
||||||
refresh("reflog", func() { _ = self.refreshReflogCommits() })
|
refresh("reflog", func() { _ = self.refreshReflogCommits() })
|
||||||
}
|
}
|
||||||
} else if scopeSet.Includes(types.REBASE_COMMITS) {
|
} else if scopeSet.Includes(types.REBASE_COMMITS) {
|
||||||
|
@ -256,7 +256,7 @@ func (self *RefreshHelper) refreshReflogCommitsConsideringStartup() {
|
||||||
case types.INITIAL:
|
case types.INITIAL:
|
||||||
self.c.OnWorker(func(_ gocui.Task) error {
|
self.c.OnWorker(func(_ gocui.Task) error {
|
||||||
_ = self.refreshReflogCommits()
|
_ = self.refreshReflogCommits()
|
||||||
self.refreshBranches(false, true)
|
self.refreshBranches(false, true, true)
|
||||||
self.c.State().GetRepoState().SetStartupStage(types.COMPLETE)
|
self.c.State().GetRepoState().SetStartupStage(types.COMPLETE)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
@ -267,9 +267,11 @@ func (self *RefreshHelper) refreshReflogCommitsConsideringStartup() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *RefreshHelper) refreshReflogAndBranches(refreshWorktrees bool, keepBranchSelectionIndex bool) {
|
func (self *RefreshHelper) refreshReflogAndBranches(refreshWorktrees bool, keepBranchSelectionIndex bool) {
|
||||||
|
loadBehindCounts := self.c.State().GetRepoState().GetStartupStage() == types.COMPLETE
|
||||||
|
|
||||||
self.refreshReflogCommitsConsideringStartup()
|
self.refreshReflogCommitsConsideringStartup()
|
||||||
|
|
||||||
self.refreshBranches(refreshWorktrees, keepBranchSelectionIndex)
|
self.refreshBranches(refreshWorktrees, keepBranchSelectionIndex, loadBehindCounts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *RefreshHelper) refreshCommitsAndCommitFiles() {
|
func (self *RefreshHelper) refreshCommitsAndCommitFiles() {
|
||||||
|
@ -438,7 +440,7 @@ func (self *RefreshHelper) refreshStateSubmoduleConfigs() error {
|
||||||
|
|
||||||
// self.refreshStatus is called at the end of this because that's when we can
|
// self.refreshStatus is called at the end of this because that's when we can
|
||||||
// be sure there is a State.Model.Branches array to pick the current branch from
|
// be sure there is a State.Model.Branches array to pick the current branch from
|
||||||
func (self *RefreshHelper) refreshBranches(refreshWorktrees bool, keepBranchSelectionIndex bool) {
|
func (self *RefreshHelper) refreshBranches(refreshWorktrees bool, keepBranchSelectionIndex bool, loadBehindCounts bool) {
|
||||||
self.c.Mutexes().RefreshingBranchesMutex.Lock()
|
self.c.Mutexes().RefreshingBranchesMutex.Lock()
|
||||||
defer self.c.Mutexes().RefreshingBranchesMutex.Unlock()
|
defer self.c.Mutexes().RefreshingBranchesMutex.Unlock()
|
||||||
|
|
||||||
|
@ -457,7 +459,25 @@ 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().MainBranches,
|
||||||
|
self.c.Model().Branches,
|
||||||
|
loadBehindCounts,
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
self.refreshStatus()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
self.c.Log.Error(err)
|
self.c.Log.Error(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,32 +155,38 @@ func BranchStatus(
|
||||||
return style.FgCyan.Sprintf("%s %s", itemOperationStr, utils.Loader(now, userConfig.Gui.Spinner))
|
return style.FgCyan.Sprintf("%s %s", itemOperationStr, utils.Loader(now, userConfig.Gui.Spinner))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !branch.IsTrackingRemote() {
|
result := ""
|
||||||
return ""
|
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.IsBehindForPull() && branch.IsAheadForPull() {
|
||||||
|
result = style.FgYellow.Sprintf("↓%s↑%s", branch.BehindForPull, branch.AheadForPull)
|
||||||
|
} else if branch.IsBehindForPull() {
|
||||||
|
result = style.FgYellow.Sprintf("↓%s", branch.BehindForPull)
|
||||||
|
} else if branch.IsAheadForPull() {
|
||||||
|
result = style.FgYellow.Sprintf("↑%s", branch.AheadForPull)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if branch.UpstreamGone {
|
if userConfig.Gui.ShowDivergenceFromBaseBranch != "none" {
|
||||||
return style.FgRed.Sprint(tr.UpstreamGone)
|
behind := branch.BehindBaseBranch.Load()
|
||||||
|
if behind != 0 {
|
||||||
|
if result != "" {
|
||||||
|
result += " "
|
||||||
|
}
|
||||||
|
if userConfig.Gui.ShowDivergenceFromBaseBranch == "arrowAndNumber" {
|
||||||
|
result += style.FgCyan.Sprintf("↓%d", behind)
|
||||||
|
} else {
|
||||||
|
result += style.FgCyan.Sprintf("↓")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if branch.MatchesUpstream() {
|
return result
|
||||||
return style.FgGreen.Sprint("✓")
|
|
||||||
}
|
|
||||||
if branch.RemoteBranchNotStoredLocally() {
|
|
||||||
return style.FgMagenta.Sprint("?")
|
|
||||||
}
|
|
||||||
|
|
||||||
if branch.IsBehindForPull() && branch.IsAheadForPull() {
|
|
||||||
return style.FgYellow.Sprintf("↓%s↑%s", branch.BehindForPull, branch.AheadForPull)
|
|
||||||
}
|
|
||||||
if branch.IsBehindForPull() {
|
|
||||||
return style.FgYellow.Sprintf("↓%s", branch.BehindForPull)
|
|
||||||
}
|
|
||||||
if branch.IsAheadForPull() {
|
|
||||||
return style.FgYellow.Sprintf("↑%s", branch.AheadForPull)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetCustomBranches(customBranchColors map[string]string) {
|
func SetCustomBranches(customBranchColors map[string]string) {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package presentation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -15,6 +16,11 @@ import (
|
||||||
"github.com/xo/terminfo"
|
"github.com/xo/terminfo"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func makeAtomic(v int32) (result atomic.Int32) {
|
||||||
|
result.Store(v)
|
||||||
|
return //nolint: nakedret
|
||||||
|
}
|
||||||
|
|
||||||
func Test_getBranchDisplayStrings(t *testing.T) {
|
func Test_getBranchDisplayStrings(t *testing.T) {
|
||||||
scenarios := []struct {
|
scenarios := []struct {
|
||||||
branch *models.Branch
|
branch *models.Branch
|
||||||
|
@ -23,6 +29,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||||
viewWidth int
|
viewWidth int
|
||||||
useIcons bool
|
useIcons bool
|
||||||
checkedOutByWorktree bool
|
checkedOutByWorktree bool
|
||||||
|
showDivergenceCfg string
|
||||||
expected []string
|
expected []string
|
||||||
}{
|
}{
|
||||||
// First some tests for when the view is wide enough so that everything fits:
|
// First some tests for when the view is wide enough so that everything fits:
|
||||||
|
@ -33,6 +40,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||||
viewWidth: 100,
|
viewWidth: 100,
|
||||||
useIcons: false,
|
useIcons: false,
|
||||||
checkedOutByWorktree: false,
|
checkedOutByWorktree: false,
|
||||||
|
showDivergenceCfg: "none",
|
||||||
expected: []string{"1m", "branch_name"},
|
expected: []string{"1m", "branch_name"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -42,6 +50,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||||
viewWidth: 100,
|
viewWidth: 100,
|
||||||
useIcons: false,
|
useIcons: false,
|
||||||
checkedOutByWorktree: true,
|
checkedOutByWorktree: true,
|
||||||
|
showDivergenceCfg: "none",
|
||||||
expected: []string{"1m", "branch_name (worktree)"},
|
expected: []string{"1m", "branch_name (worktree)"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -51,6 +60,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||||
viewWidth: 100,
|
viewWidth: 100,
|
||||||
useIcons: true,
|
useIcons: true,
|
||||||
checkedOutByWorktree: true,
|
checkedOutByWorktree: true,
|
||||||
|
showDivergenceCfg: "none",
|
||||||
expected: []string{"1m", "", "branch_name "},
|
expected: []string{"1m", "", "branch_name "},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -66,6 +76,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||||
viewWidth: 100,
|
viewWidth: 100,
|
||||||
useIcons: false,
|
useIcons: false,
|
||||||
checkedOutByWorktree: false,
|
checkedOutByWorktree: false,
|
||||||
|
showDivergenceCfg: "none",
|
||||||
expected: []string{"1m", "branch_name ✓"},
|
expected: []string{"1m", "branch_name ✓"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -81,8 +92,57 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||||
viewWidth: 100,
|
viewWidth: 100,
|
||||||
useIcons: false,
|
useIcons: false,
|
||||||
checkedOutByWorktree: true,
|
checkedOutByWorktree: true,
|
||||||
|
showDivergenceCfg: "none",
|
||||||
expected: []string{"1m", "branch_name (worktree) ↓5↑3"},
|
expected: []string{"1m", "branch_name (worktree) ↓5↑3"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
branch: &models.Branch{
|
||||||
|
Name: "branch_name",
|
||||||
|
Recency: "1m",
|
||||||
|
BehindBaseBranch: makeAtomic(2),
|
||||||
|
},
|
||||||
|
itemOperation: types.ItemOperationNone,
|
||||||
|
fullDescription: false,
|
||||||
|
viewWidth: 100,
|
||||||
|
useIcons: false,
|
||||||
|
checkedOutByWorktree: false,
|
||||||
|
showDivergenceCfg: "onlyArrow",
|
||||||
|
expected: []string{"1m", "branch_name ↓"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
branch: &models.Branch{
|
||||||
|
Name: "branch_name",
|
||||||
|
Recency: "1m",
|
||||||
|
UpstreamRemote: "origin",
|
||||||
|
AheadForPull: "0",
|
||||||
|
BehindForPull: "0",
|
||||||
|
BehindBaseBranch: makeAtomic(2),
|
||||||
|
},
|
||||||
|
itemOperation: types.ItemOperationNone,
|
||||||
|
fullDescription: false,
|
||||||
|
viewWidth: 100,
|
||||||
|
useIcons: false,
|
||||||
|
checkedOutByWorktree: false,
|
||||||
|
showDivergenceCfg: "arrowAndNumber",
|
||||||
|
expected: []string{"1m", "branch_name ✓ ↓2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
branch: &models.Branch{
|
||||||
|
Name: "branch_name",
|
||||||
|
Recency: "1m",
|
||||||
|
UpstreamRemote: "origin",
|
||||||
|
AheadForPull: "3",
|
||||||
|
BehindForPull: "5",
|
||||||
|
BehindBaseBranch: makeAtomic(2),
|
||||||
|
},
|
||||||
|
itemOperation: types.ItemOperationNone,
|
||||||
|
fullDescription: false,
|
||||||
|
viewWidth: 100,
|
||||||
|
useIcons: false,
|
||||||
|
checkedOutByWorktree: false,
|
||||||
|
showDivergenceCfg: "arrowAndNumber",
|
||||||
|
expected: []string{"1m", "branch_name ↓5↑3 ↓2"},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
|
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
|
||||||
itemOperation: types.ItemOperationPushing,
|
itemOperation: types.ItemOperationPushing,
|
||||||
|
@ -90,6 +150,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||||
viewWidth: 100,
|
viewWidth: 100,
|
||||||
useIcons: false,
|
useIcons: false,
|
||||||
checkedOutByWorktree: false,
|
checkedOutByWorktree: false,
|
||||||
|
showDivergenceCfg: "none",
|
||||||
expected: []string{"1m", "branch_name Pushing |"},
|
expected: []string{"1m", "branch_name Pushing |"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -108,6 +169,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||||
viewWidth: 100,
|
viewWidth: 100,
|
||||||
useIcons: false,
|
useIcons: false,
|
||||||
checkedOutByWorktree: false,
|
checkedOutByWorktree: false,
|
||||||
|
showDivergenceCfg: "none",
|
||||||
expected: []string{"1m", "12345678", "branch_name ✓", "origin branch_name", "commit title"},
|
expected: []string{"1m", "12345678", "branch_name ✓", "origin branch_name", "commit title"},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -119,6 +181,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||||
viewWidth: 14,
|
viewWidth: 14,
|
||||||
useIcons: false,
|
useIcons: false,
|
||||||
checkedOutByWorktree: false,
|
checkedOutByWorktree: false,
|
||||||
|
showDivergenceCfg: "none",
|
||||||
expected: []string{"1m", "branch_na…"},
|
expected: []string{"1m", "branch_na…"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -128,6 +191,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||||
viewWidth: 14,
|
viewWidth: 14,
|
||||||
useIcons: false,
|
useIcons: false,
|
||||||
checkedOutByWorktree: true,
|
checkedOutByWorktree: true,
|
||||||
|
showDivergenceCfg: "none",
|
||||||
expected: []string{"1m", "bra… (worktree)"},
|
expected: []string{"1m", "bra… (worktree)"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -137,6 +201,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||||
viewWidth: 14,
|
viewWidth: 14,
|
||||||
useIcons: true,
|
useIcons: true,
|
||||||
checkedOutByWorktree: true,
|
checkedOutByWorktree: true,
|
||||||
|
showDivergenceCfg: "none",
|
||||||
expected: []string{"1m", "", "branc… "},
|
expected: []string{"1m", "", "branc… "},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -152,6 +217,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||||
viewWidth: 14,
|
viewWidth: 14,
|
||||||
useIcons: false,
|
useIcons: false,
|
||||||
checkedOutByWorktree: false,
|
checkedOutByWorktree: false,
|
||||||
|
showDivergenceCfg: "none",
|
||||||
expected: []string{"1m", "branch_… ✓"},
|
expected: []string{"1m", "branch_… ✓"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -167,6 +233,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||||
viewWidth: 30,
|
viewWidth: 30,
|
||||||
useIcons: false,
|
useIcons: false,
|
||||||
checkedOutByWorktree: true,
|
checkedOutByWorktree: true,
|
||||||
|
showDivergenceCfg: "none",
|
||||||
expected: []string{"1m", "branch_na… (worktree) ↓5↑3"},
|
expected: []string{"1m", "branch_na… (worktree) ↓5↑3"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -176,6 +243,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||||
viewWidth: 20,
|
viewWidth: 20,
|
||||||
useIcons: false,
|
useIcons: false,
|
||||||
checkedOutByWorktree: false,
|
checkedOutByWorktree: false,
|
||||||
|
showDivergenceCfg: "none",
|
||||||
expected: []string{"1m", "branc… Pushing |"},
|
expected: []string{"1m", "branc… Pushing |"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -185,6 +253,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||||
viewWidth: -1,
|
viewWidth: -1,
|
||||||
useIcons: false,
|
useIcons: false,
|
||||||
checkedOutByWorktree: false,
|
checkedOutByWorktree: false,
|
||||||
|
showDivergenceCfg: "none",
|
||||||
expected: []string{"1m", "abc Pushing |"},
|
expected: []string{"1m", "abc Pushing |"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -194,6 +263,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||||
viewWidth: -1,
|
viewWidth: -1,
|
||||||
useIcons: false,
|
useIcons: false,
|
||||||
checkedOutByWorktree: false,
|
checkedOutByWorktree: false,
|
||||||
|
showDivergenceCfg: "none",
|
||||||
expected: []string{"1m", "ab Pushing |"},
|
expected: []string{"1m", "ab Pushing |"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -203,6 +273,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||||
viewWidth: -1,
|
viewWidth: -1,
|
||||||
useIcons: false,
|
useIcons: false,
|
||||||
checkedOutByWorktree: false,
|
checkedOutByWorktree: false,
|
||||||
|
showDivergenceCfg: "none",
|
||||||
expected: []string{"1m", "a Pushing |"},
|
expected: []string{"1m", "a Pushing |"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -221,6 +292,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||||
viewWidth: 20,
|
viewWidth: 20,
|
||||||
useIcons: false,
|
useIcons: false,
|
||||||
checkedOutByWorktree: false,
|
checkedOutByWorktree: false,
|
||||||
|
showDivergenceCfg: "none",
|
||||||
expected: []string{"1m", "12345678", "bran… ✓", "origin branch_name", "commit title"},
|
expected: []string{"1m", "12345678", "bran… ✓", "origin branch_name", "commit title"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -232,6 +304,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||||
|
|
||||||
for i, s := range scenarios {
|
for i, s := range scenarios {
|
||||||
icons.SetNerdFontsVersion(lo.Ternary(s.useIcons, "3", ""))
|
icons.SetNerdFontsVersion(lo.Ternary(s.useIcons, "3", ""))
|
||||||
|
c.UserConfig.Gui.ShowDivergenceFromBaseBranch = s.showDivergenceCfg
|
||||||
|
|
||||||
worktrees := []*models.Worktree{}
|
worktrees := []*models.Worktree{}
|
||||||
if s.checkedOutByWorktree {
|
if s.checkedOutByWorktree {
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package status
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/config"
|
||||||
|
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ShowDivergenceFromBaseBranch = NewIntegrationTest(NewIntegrationTestArgs{
|
||||||
|
Description: "Show divergence from base branch in the status panel",
|
||||||
|
ExtraCmdArgs: []string{},
|
||||||
|
Skip: false,
|
||||||
|
SetupConfig: func(config *config.AppConfig) {
|
||||||
|
config.UserConfig.Gui.ShowDivergenceFromBaseBranch = "arrowAndNumber"
|
||||||
|
},
|
||||||
|
SetupRepo: func(shell *Shell) {
|
||||||
|
shell.CreateNCommits(2)
|
||||||
|
shell.CloneIntoRemote("origin")
|
||||||
|
shell.NewBranch("feature")
|
||||||
|
shell.HardReset("HEAD^")
|
||||||
|
},
|
||||||
|
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||||
|
t.GlobalPress(keys.Universal.NextBlock)
|
||||||
|
|
||||||
|
t.Views().Status().
|
||||||
|
Content(Equals("↓1 repo → feature"))
|
||||||
|
},
|
||||||
|
})
|
|
@ -266,6 +266,7 @@ var tests = []*components.IntegrationTest{
|
||||||
status.ClickRepoNameToOpenReposMenu,
|
status.ClickRepoNameToOpenReposMenu,
|
||||||
status.ClickToFocus,
|
status.ClickToFocus,
|
||||||
status.ClickWorkingTreeStateToOpenRebaseOptionsMenu,
|
status.ClickWorkingTreeStateToOpenRebaseOptionsMenu,
|
||||||
|
status.ShowDivergenceFromBaseBranch,
|
||||||
submodule.Add,
|
submodule.Add,
|
||||||
submodule.Enter,
|
submodule.Enter,
|
||||||
submodule.EnterNested,
|
submodule.EnterNested,
|
||||||
|
|
|
@ -331,6 +331,16 @@
|
||||||
"description": "If true, show commit hashes alongside branch names in the branches view.",
|
"description": "If true, show commit hashes alongside branch names in the branches view.",
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
|
"showDivergenceFromBaseBranch": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"none",
|
||||||
|
"onlyArrow",
|
||||||
|
"arrowAndNumber"
|
||||||
|
],
|
||||||
|
"description": "Whether to show the divergence from the base branch in the branches view.\nOne of: 'none' | 'onlyArrow' | 'arrowAndNumber'",
|
||||||
|
"default": "none"
|
||||||
|
},
|
||||||
"commandLogSize": {
|
"commandLogSize": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"minimum": 0,
|
"minimum": 0,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue