mirror of
				https://github.com/jesseduffield/lazygit.git
				synced 2025-10-30 23:57:43 +02:00 
			
		
		
		
	Show divergence from base branch in branches list
This commit is contained in:
		| @@ -130,7 +130,7 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error { | ||||
| 			if self.c.AppState.LocalBranchSortOrder == "recency" { | ||||
| 				refresh("reflog and branches", func() { self.refreshReflogAndBranches(includeWorktreesWithBranches, options.KeepBranchSelectionIndex) }) | ||||
| 			} else { | ||||
| 				refresh("branches", func() { self.refreshBranches(includeWorktreesWithBranches, options.KeepBranchSelectionIndex) }) | ||||
| 				refresh("branches", func() { self.refreshBranches(includeWorktreesWithBranches, options.KeepBranchSelectionIndex, true) }) | ||||
| 				refresh("reflog", func() { _ = self.refreshReflogCommits() }) | ||||
| 			} | ||||
| 		} else if scopeSet.Includes(types.REBASE_COMMITS) { | ||||
| @@ -256,7 +256,7 @@ func (self *RefreshHelper) refreshReflogCommitsConsideringStartup() { | ||||
| 	case types.INITIAL: | ||||
| 		self.c.OnWorker(func(_ gocui.Task) error { | ||||
| 			_ = self.refreshReflogCommits() | ||||
| 			self.refreshBranches(false, true) | ||||
| 			self.refreshBranches(false, true, true) | ||||
| 			self.c.State().GetRepoState().SetStartupStage(types.COMPLETE) | ||||
| 			return nil | ||||
| 		}) | ||||
| @@ -267,9 +267,11 @@ func (self *RefreshHelper) refreshReflogCommitsConsideringStartup() { | ||||
| } | ||||
|  | ||||
| func (self *RefreshHelper) refreshReflogAndBranches(refreshWorktrees bool, keepBranchSelectionIndex bool) { | ||||
| 	loadBehindCounts := self.c.State().GetRepoState().GetStartupStage() == types.COMPLETE | ||||
|  | ||||
| 	self.refreshReflogCommitsConsideringStartup() | ||||
|  | ||||
| 	self.refreshBranches(refreshWorktrees, keepBranchSelectionIndex) | ||||
| 	self.refreshBranches(refreshWorktrees, keepBranchSelectionIndex, loadBehindCounts) | ||||
| } | ||||
|  | ||||
| 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 | ||||
| // 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() | ||||
| 	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 { | ||||
| 		self.c.Log.Error(err) | ||||
| 	} | ||||
|   | ||||
| @@ -155,32 +155,38 @@ 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.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 { | ||||
| 		return style.FgRed.Sprint(tr.UpstreamGone) | ||||
| 	if userConfig.Gui.ShowDivergenceFromBaseBranch != "none" { | ||||
| 		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 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 "" | ||||
| 	return result | ||||
| } | ||||
|  | ||||
| func SetCustomBranches(customBranchColors map[string]string) { | ||||
|   | ||||
| @@ -2,6 +2,7 @@ package presentation | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"sync/atomic" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| @@ -15,6 +16,11 @@ import ( | ||||
| 	"github.com/xo/terminfo" | ||||
| ) | ||||
|  | ||||
| func makeAtomic(v int32) (result atomic.Int32) { | ||||
| 	result.Store(v) | ||||
| 	return //nolint: nakedret | ||||
| } | ||||
|  | ||||
| func Test_getBranchDisplayStrings(t *testing.T) { | ||||
| 	scenarios := []struct { | ||||
| 		branch               *models.Branch | ||||
| @@ -23,6 +29,7 @@ func Test_getBranchDisplayStrings(t *testing.T) { | ||||
| 		viewWidth            int | ||||
| 		useIcons             bool | ||||
| 		checkedOutByWorktree bool | ||||
| 		showDivergenceCfg    string | ||||
| 		expected             []string | ||||
| 	}{ | ||||
| 		// 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, | ||||
| 			useIcons:             false, | ||||
| 			checkedOutByWorktree: false, | ||||
| 			showDivergenceCfg:    "none", | ||||
| 			expected:             []string{"1m", "branch_name"}, | ||||
| 		}, | ||||
| 		{ | ||||
| @@ -42,6 +50,7 @@ func Test_getBranchDisplayStrings(t *testing.T) { | ||||
| 			viewWidth:            100, | ||||
| 			useIcons:             false, | ||||
| 			checkedOutByWorktree: true, | ||||
| 			showDivergenceCfg:    "none", | ||||
| 			expected:             []string{"1m", "branch_name (worktree)"}, | ||||
| 		}, | ||||
| 		{ | ||||
| @@ -51,6 +60,7 @@ func Test_getBranchDisplayStrings(t *testing.T) { | ||||
| 			viewWidth:            100, | ||||
| 			useIcons:             true, | ||||
| 			checkedOutByWorktree: true, | ||||
| 			showDivergenceCfg:    "none", | ||||
| 			expected:             []string{"1m", "", "branch_name "}, | ||||
| 		}, | ||||
| 		{ | ||||
| @@ -66,6 +76,7 @@ func Test_getBranchDisplayStrings(t *testing.T) { | ||||
| 			viewWidth:            100, | ||||
| 			useIcons:             false, | ||||
| 			checkedOutByWorktree: false, | ||||
| 			showDivergenceCfg:    "none", | ||||
| 			expected:             []string{"1m", "branch_name ✓"}, | ||||
| 		}, | ||||
| 		{ | ||||
| @@ -81,8 +92,57 @@ func Test_getBranchDisplayStrings(t *testing.T) { | ||||
| 			viewWidth:            100, | ||||
| 			useIcons:             false, | ||||
| 			checkedOutByWorktree: true, | ||||
| 			showDivergenceCfg:    "none", | ||||
| 			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"}, | ||||
| 			itemOperation:        types.ItemOperationPushing, | ||||
| @@ -90,6 +150,7 @@ func Test_getBranchDisplayStrings(t *testing.T) { | ||||
| 			viewWidth:            100, | ||||
| 			useIcons:             false, | ||||
| 			checkedOutByWorktree: false, | ||||
| 			showDivergenceCfg:    "none", | ||||
| 			expected:             []string{"1m", "branch_name Pushing |"}, | ||||
| 		}, | ||||
| 		{ | ||||
| @@ -108,6 +169,7 @@ func Test_getBranchDisplayStrings(t *testing.T) { | ||||
| 			viewWidth:            100, | ||||
| 			useIcons:             false, | ||||
| 			checkedOutByWorktree: false, | ||||
| 			showDivergenceCfg:    "none", | ||||
| 			expected:             []string{"1m", "12345678", "branch_name ✓", "origin branch_name", "commit title"}, | ||||
| 		}, | ||||
|  | ||||
| @@ -119,6 +181,7 @@ func Test_getBranchDisplayStrings(t *testing.T) { | ||||
| 			viewWidth:            14, | ||||
| 			useIcons:             false, | ||||
| 			checkedOutByWorktree: false, | ||||
| 			showDivergenceCfg:    "none", | ||||
| 			expected:             []string{"1m", "branch_na…"}, | ||||
| 		}, | ||||
| 		{ | ||||
| @@ -128,6 +191,7 @@ func Test_getBranchDisplayStrings(t *testing.T) { | ||||
| 			viewWidth:            14, | ||||
| 			useIcons:             false, | ||||
| 			checkedOutByWorktree: true, | ||||
| 			showDivergenceCfg:    "none", | ||||
| 			expected:             []string{"1m", "bra… (worktree)"}, | ||||
| 		}, | ||||
| 		{ | ||||
| @@ -137,6 +201,7 @@ func Test_getBranchDisplayStrings(t *testing.T) { | ||||
| 			viewWidth:            14, | ||||
| 			useIcons:             true, | ||||
| 			checkedOutByWorktree: true, | ||||
| 			showDivergenceCfg:    "none", | ||||
| 			expected:             []string{"1m", "", "branc… "}, | ||||
| 		}, | ||||
| 		{ | ||||
| @@ -152,6 +217,7 @@ func Test_getBranchDisplayStrings(t *testing.T) { | ||||
| 			viewWidth:            14, | ||||
| 			useIcons:             false, | ||||
| 			checkedOutByWorktree: false, | ||||
| 			showDivergenceCfg:    "none", | ||||
| 			expected:             []string{"1m", "branch_… ✓"}, | ||||
| 		}, | ||||
| 		{ | ||||
| @@ -167,6 +233,7 @@ func Test_getBranchDisplayStrings(t *testing.T) { | ||||
| 			viewWidth:            30, | ||||
| 			useIcons:             false, | ||||
| 			checkedOutByWorktree: true, | ||||
| 			showDivergenceCfg:    "none", | ||||
| 			expected:             []string{"1m", "branch_na… (worktree) ↓5↑3"}, | ||||
| 		}, | ||||
| 		{ | ||||
| @@ -176,6 +243,7 @@ func Test_getBranchDisplayStrings(t *testing.T) { | ||||
| 			viewWidth:            20, | ||||
| 			useIcons:             false, | ||||
| 			checkedOutByWorktree: false, | ||||
| 			showDivergenceCfg:    "none", | ||||
| 			expected:             []string{"1m", "branc… Pushing |"}, | ||||
| 		}, | ||||
| 		{ | ||||
| @@ -185,6 +253,7 @@ func Test_getBranchDisplayStrings(t *testing.T) { | ||||
| 			viewWidth:            -1, | ||||
| 			useIcons:             false, | ||||
| 			checkedOutByWorktree: false, | ||||
| 			showDivergenceCfg:    "none", | ||||
| 			expected:             []string{"1m", "abc Pushing |"}, | ||||
| 		}, | ||||
| 		{ | ||||
| @@ -194,6 +263,7 @@ func Test_getBranchDisplayStrings(t *testing.T) { | ||||
| 			viewWidth:            -1, | ||||
| 			useIcons:             false, | ||||
| 			checkedOutByWorktree: false, | ||||
| 			showDivergenceCfg:    "none", | ||||
| 			expected:             []string{"1m", "ab Pushing |"}, | ||||
| 		}, | ||||
| 		{ | ||||
| @@ -203,6 +273,7 @@ func Test_getBranchDisplayStrings(t *testing.T) { | ||||
| 			viewWidth:            -1, | ||||
| 			useIcons:             false, | ||||
| 			checkedOutByWorktree: false, | ||||
| 			showDivergenceCfg:    "none", | ||||
| 			expected:             []string{"1m", "a Pushing |"}, | ||||
| 		}, | ||||
| 		{ | ||||
| @@ -221,6 +292,7 @@ func Test_getBranchDisplayStrings(t *testing.T) { | ||||
| 			viewWidth:            20, | ||||
| 			useIcons:             false, | ||||
| 			checkedOutByWorktree: false, | ||||
| 			showDivergenceCfg:    "none", | ||||
| 			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 { | ||||
| 		icons.SetNerdFontsVersion(lo.Ternary(s.useIcons, "3", "")) | ||||
| 		c.UserConfig.Gui.ShowDivergenceFromBaseBranch = s.showDivergenceCfg | ||||
|  | ||||
| 		worktrees := []*models.Worktree{} | ||||
| 		if s.checkedOutByWorktree { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user