Compare commits

...

16 Commits

Author SHA1 Message Date
Jesse Duffield
3dc960b475 fixup! Factor out CommitLoader.mainBranches into its own class, and store it in Model 2024-05-19 17:24:19 +10:00
Jesse Duffield
de7ee9af21 Use one worker per branch to speed things up 2024-05-19 17:23:39 +10:00
Stefan Haller
eded3ccf54 Add command "Rebase onto base branch" to rebase menu 2024-05-19 17:23:39 +10:00
Stefan Haller
b3252ee7c8 Remove target branch from title of rebase menu
Put it into the individual menu items instead.

Again, this is necessary because we are going to add another entry to the menu
that is independent of the selected branch.
2024-05-19 17:23:39 +10:00
Stefan Haller
f2e8e3fcc1 Always show rebase menu, even when rebasing is not possible
Instead, disable the individual entries in the menu.

This is necessary because we are going to add another entry to the menu that is
independent of the selected branch.
2024-05-19 17:23:39 +10:00
Stefan Haller
7acf96bd0d Make "Rebase" show up with "..." in the keybindings menu 2024-05-19 17:23:39 +10:00
Stefan Haller
9ede2f025e Fix typo 2024-05-19 17:23:39 +10:00
Stefan Haller
f58b7da7dc Add command "View divergence from base branch" 2024-05-19 17:23:39 +10:00
Stefan Haller
b23ea3b190 fixup! WIP Add showDivergenceFromBaseBranch config 2024-05-19 17:23:39 +10:00
Stefan Haller
4d2177725f WIP Add showDivergenceFromBaseBranch config 2024-05-19 17:23:39 +10:00
Stefan Haller
6d2ec43596 WIP Show both ahead and behind 2024-05-19 17:23:39 +10:00
Stefan Haller
6eaece3696 Show divergence from base branch in branches list 2024-05-19 17:23:39 +10:00
Stefan Haller
316ea99d67 Add GetBaseBranch function 2024-05-19 17:23:39 +10:00
Stefan Haller
e7ac70c415 Remove ColoredBranchStatus and branchStatusColor
Previously the entire status was colored in a single color, so the API made
sense. This is going to change later in this branch, so now we must include the
color in the string returned from BranchStatus(), which means that callers who
need to do hit detection or measure the length need to decolorize it.

While we're at it, switch the order of ↑3↓7 to ↓7↑3. For some reason that I
can't really explain I find it more logical this way. The software out there is
pretty undecided about it, it seems: VS Code puts ↓7 first, and so does the
shell prompt that comes with git; git status and git branch -v put "ahead" first
though. Shrug.
2024-05-19 17:23:39 +10:00
Stefan Haller
aba0290e51 Factor out CommitLoader.mainBranches into its own class, and store it in Model 2024-05-19 17:23:39 +10:00
Stefan Haller
2ae1bea20a More explicit test of status panel content
Use Equals instead of Contains for asserting the status view content. This
solves the problem that we might assert Contains("↓2 repo"), but what it really
shows is "↑1↓2 repo", and the test still succeeds. At best this is confusing.

Also, this way we don't have to use the awkward DoesNotContain to check that it
really doesn't show a checkmark.

To do this, we need to fix two whitespace problems:
- there was always a space at the end for no reason. Simply remove it. It was
  added in efb51eee96, but from looking at that diff it seems it was added
  accidentally.
- there was a space at the beginning if the branch status was empty. This is
  actually a cosmetic problem, for branches without a status the text was
  indented by once space. Change this so that the space is added conditionally.
  It's a bit awkward that we have to use Decolorise here, but this will go away
  again later in this branch.
2024-05-19 17:23:07 +10:00
50 changed files with 526 additions and 233 deletions

View File

@@ -74,6 +74,7 @@ gui:
showListFooter: true # for seeing the '5 of 20' message in list panels
showRandomTip: true
showBranchCommitHash: false # show commit hashes alongside branch names
showDivergenceFromBaseBranch: onlyBehind # one of 'off' | 'onlyBehind' | 'behindAndAhead'
showBottomLine: true # for hiding the bottom information line (unless it has important information to tell you)
showPanelJumps: true # for showing the jump-to-panel keybindings as panel subtitles
showCommandLog: true

View File

@@ -5,7 +5,10 @@ import (
"regexp"
"strconv"
"strings"
"sync"
"time"
"github.com/go-errors/errors"
"github.com/jesseduffield/generics/set"
"github.com/jesseduffield/go-git/v5/config"
"github.com/jesseduffield/lazygit/pkg/commands/models"
@@ -60,7 +63,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,11 +122,109 @@ 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())
branch.AheadOfBaseBranch.Store(oldBranch.AheadOfBaseBranch.Load())
}
}
onWorker(func() error {
mainBranches := existingMainBranches.Get()
if len(mainBranches) == 0 {
return nil
}
t := time.Now()
wg := sync.WaitGroup{}
for _, branch := range branches {
branch := branch
wg.Add(1)
onWorker(func() error {
defer wg.Done()
baseBranch, err := self.GetBaseBranch(branch, existingMainBranches)
if err != nil {
return err
}
if baseBranch == "" {
return nil
}
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 ahead, err := strconv.Atoi(aheadBehindStr[0]); err == nil {
if behind, err := strconv.Atoi(aheadBehindStr[1]); err == nil {
branch.AheadOfBaseBranch.Store(int32(ahead))
branch.BehindBaseBranch.Store(int32(behind))
}
}
return nil
})
}
wg.Wait()
self.Log.Infof("time to get ahead/behind base branch for all branches: %s", time.Since(t))
renderFunc()
return nil
})
return branches, nil
}
func (self *BranchLoader) GetBaseBranch(branch *models.Branch, existingMainBranches *ExistingMainBranches) (string, error) {
mainBranches := existingMainBranches.Get()
if len(mainBranches) == 0 {
return "", nil
}
output, err := self.cmd.New(
NewGitCmd("merge-base").Arg(branch.FullRefName()).Arg(mainBranches...).
ToArgv(),
).DontLog().RunWithOutput()
if err != nil {
// If there's an error, it must be because one of the main branches that
// used to exist when we called getExistingMainBranches() was deleted
// meanwhile. To fix this for next time, throw away our cache.
existingMainBranches.Clear()
return "", nil
}
mergeBase := ignoringWarnings(output)
output, err = self.cmd.New(
NewGitCmd("for-each-ref").
Arg("--contains").
Arg(mergeBase).
Arg("--format=%(refname)").
Arg(mainBranches...).
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
}
baseBranch := split[0]
return baseBranch, nil
}
func (self *BranchLoader) obtainBranches() []*models.Branch {
output, err := self.getRawBranches()
if err != nil {

View File

@@ -35,11 +35,6 @@ type CommitLoader struct {
readFile func(filename string) ([]byte, error)
walkFiles func(root string, fn filepath.WalkFunc) error
dotGitDir string
// List of main branches that exist in the repo.
// We use these to obtain the merge base of the branch.
// When nil, we're yet to obtain the list of existing main branches.
// When an empty slice, we've obtained the list and it's empty.
mainBranches []string
*GitCommon
}
@@ -56,7 +51,6 @@ func NewCommitLoader(
getRebaseMode: getRebaseMode,
readFile: os.ReadFile,
walkFiles: filepath.Walk,
mainBranches: nil,
GitCommon: gitCommon,
}
}
@@ -72,6 +66,7 @@ type GetCommitsOptions struct {
All bool
// If non-empty, show divergence from this ref (left-right log)
RefToShowDivergenceFrom string
ExistingMainBranches *ExistingMainBranches
}
// GetCommits obtains the commits of the current branch
@@ -108,9 +103,9 @@ func (self *CommitLoader) GetCommits(opts GetCommitsOptions) ([]*models.Commit,
go utils.Safe(func() {
defer wg.Done()
ancestor = self.getMergeBase(opts.RefName)
ancestor = self.getMergeBase(opts.RefName, opts.ExistingMainBranches)
if opts.RefToShowDivergenceFrom != "" {
remoteAncestor = self.getMergeBase(opts.RefToShowDivergenceFrom)
remoteAncestor = self.getMergeBase(opts.RefToShowDivergenceFrom, opts.ExistingMainBranches)
}
})
@@ -471,12 +466,9 @@ func setCommitMergedStatuses(ancestor string, commits []*models.Commit) {
}
}
func (self *CommitLoader) getMergeBase(refName string) string {
if self.mainBranches == nil {
self.mainBranches = self.getExistingMainBranches()
}
if len(self.mainBranches) == 0 {
func (self *CommitLoader) getMergeBase(refName string, existingMainBranches *ExistingMainBranches) string {
mainBranches := existingMainBranches.Get()
if len(mainBranches) == 0 {
return ""
}
@@ -484,69 +476,18 @@ func (self *CommitLoader) getMergeBase(refName string) string {
// return the base commit for the closest one.
output, err := self.cmd.New(
NewGitCmd("merge-base").Arg(refName).Arg(self.mainBranches...).
NewGitCmd("merge-base").Arg(refName).Arg(mainBranches...).
ToArgv(),
).DontLog().RunWithOutput()
if err != nil {
// If there's an error, it must be because one of the main branches that
// used to exist when we called getExistingMainBranches() was deleted
// meanwhile. To fix this for next time, throw away our cache.
self.mainBranches = nil
existingMainBranches.Clear()
}
return ignoringWarnings(output)
}
func (self *CommitLoader) getExistingMainBranches() []string {
var existingBranches []string
var wg sync.WaitGroup
mainBranches := self.UserConfig.Git.MainBranches
existingBranches = make([]string, len(mainBranches))
for i, branchName := range mainBranches {
wg.Add(1)
go utils.Safe(func() {
defer wg.Done()
// Try to determine upstream of local main branch
if ref, err := self.cmd.New(
NewGitCmd("rev-parse").Arg("--symbolic-full-name", branchName+"@{u}").ToArgv(),
).DontLog().RunWithOutput(); err == nil {
existingBranches[i] = strings.TrimSpace(ref)
return
}
// If this failed, a local branch for this main branch doesn't exist or it
// has no upstream configured. Try looking for one in the "origin" remote.
ref := "refs/remotes/origin/" + branchName
if err := self.cmd.New(
NewGitCmd("rev-parse").Arg("--verify", "--quiet", ref).ToArgv(),
).DontLog().Run(); err == nil {
existingBranches[i] = ref
return
}
// If this failed as well, try if we have the main branch as a local
// branch. This covers the case where somebody is using git locally
// for something, but never pushing anywhere.
ref = "refs/heads/" + branchName
if err := self.cmd.New(
NewGitCmd("rev-parse").Arg("--verify", "--quiet", ref).ToArgv(),
).DontLog().Run(); err == nil {
existingBranches[i] = ref
}
})
}
wg.Wait()
existingBranches = lo.Filter(existingBranches, func(branch string, _ int) bool {
return branch != ""
})
return existingBranches
}
func ignoringWarnings(commandOutput string) string {
trimmedOutput := strings.TrimSpace(commandOutput)
split := strings.Split(trimmedOutput, "\n")

View File

@@ -307,10 +307,11 @@ func TestGetCommits(t *testing.T) {
common := utils.NewDummyCommon()
common.AppState = &config.AppState{}
common.AppState.GitLogOrder = scenario.logOrder
cmd := oscommands.NewDummyCmdObjBuilder(scenario.runner)
builder := &CommitLoader{
Common: common,
cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner),
cmd: cmd,
getRebaseMode: func() (enums.RebaseMode, error) { return scenario.rebaseMode, nil },
dotGitDir: ".git",
readFile: func(filename string) ([]byte, error) {
@@ -322,7 +323,9 @@ func TestGetCommits(t *testing.T) {
}
common.UserConfig.Git.MainBranches = scenario.mainBranches
commits, err := builder.GetCommits(scenario.opts)
opts := scenario.opts
opts.ExistingMainBranches = NewExistingMainBranches(scenario.mainBranches, cmd)
commits, err := builder.GetCommits(opts)
assert.Equal(t, scenario.expectedCommits, commits)
assert.Equal(t, scenario.expectedError, err)

View File

@@ -0,0 +1,99 @@
package git_commands
import (
"strings"
"sync"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
"github.com/sasha-s/go-deadlock"
)
type ExistingMainBranches struct {
configuredMainBranches []string
existingBranches []string
cmd oscommands.ICmdObjBuilder
mutex *deadlock.Mutex
}
func NewExistingMainBranches(
configuredMainBranches []string,
cmd oscommands.ICmdObjBuilder,
) *ExistingMainBranches {
return &ExistingMainBranches{
configuredMainBranches: configuredMainBranches,
existingBranches: nil,
cmd: cmd,
mutex: &deadlock.Mutex{},
}
}
func (self *ExistingMainBranches) Get() []string {
self.mutex.Lock()
defer self.mutex.Unlock()
if self.existingBranches == nil {
self.existingBranches = self.determineMainBranches()
}
return self.existingBranches
}
func (self *ExistingMainBranches) Clear() {
self.mutex.Lock()
defer self.mutex.Unlock()
self.existingBranches = nil
}
func (self *ExistingMainBranches) determineMainBranches() []string {
var existingBranches []string
var wg sync.WaitGroup
existingBranches = make([]string, len(self.configuredMainBranches))
for i, branchName := range self.configuredMainBranches {
wg.Add(1)
go utils.Safe(func() {
defer wg.Done()
// Try to determine upstream of local main branch
if ref, err := self.cmd.New(
NewGitCmd("rev-parse").Arg("--symbolic-full-name", branchName+"@{u}").ToArgv(),
).DontLog().RunWithOutput(); err == nil {
existingBranches[i] = strings.TrimSpace(ref)
return
}
// If this failed, a local branch for this main branch doesn't exist or it
// has no upstream configured. Try looking for one in the "origin" remote.
ref := "refs/remotes/origin/" + branchName
if err := self.cmd.New(
NewGitCmd("rev-parse").Arg("--verify", "--quiet", ref).ToArgv(),
).DontLog().Run(); err == nil {
existingBranches[i] = ref
return
}
// If this failed as well, try if we have the main branch as a local
// branch. This covers the case where somebody is using git locally
// for something, but never pushing anywhere.
ref = "refs/heads/" + branchName
if err := self.cmd.New(
NewGitCmd("rev-parse").Arg("--verify", "--quiet", ref).ToArgv(),
).DontLog().Run(); err == nil {
existingBranches[i] = ref
}
})
}
wg.Wait()
existingBranches = lo.Filter(existingBranches, func(branch string, _ int) bool {
return branch != ""
})
return existingBranches
}

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,17 @@ 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
// How far our branch is ahead of its base branch. 0 means either not
// determined yet, or there are no commits on this branch yet, or the branch
// is already merged. (We don't need to distinguish these, as we don't draw
// anything in all these cases.)
AheadOfBaseBranch atomic.Int32
}
func (b *Branch) FullRefName() string {

View File

@@ -127,6 +127,8 @@ type GuiConfig struct {
CommitHashLength int `yaml:"commitHashLength" jsonschema:"minimum=0"`
// If true, show commit hashes alongside branch names in the branches view.
ShowBranchCommitHash bool `yaml:"showBranchCommitHash"`
// Whether to show the divergence from the base branch in the branches view.
ShowDivergenceFromBaseBranch string `yaml:"showDivergenceFromBaseBranch" jsonschema:"enum=off,enum=onlyBehindArrow,enum=onlyBehindArrowAndNumber,enum=behindAndAheadArrows,enum=behindAndAheadArrowsAndNumbers"`
// Height of the command log view
CommandLogSize int `yaml:"commandLogSize" jsonschema:"minimum=0"`
// Whether to split the main window when viewing file changes.
@@ -668,26 +670,27 @@ func GetDefaultConfig() *UserConfig {
UnstagedChangesColor: []string{"red"},
DefaultFgColor: []string{"default"},
},
CommitLength: CommitLengthConfig{Show: true},
SkipNoStagedFilesWarning: false,
ShowListFooter: true,
ShowCommandLog: true,
ShowBottomLine: true,
ShowPanelJumps: true,
ShowFileTree: true,
ShowRandomTip: true,
ShowIcons: false,
NerdFontsVersion: "",
ShowFileIcons: true,
CommitHashLength: 8,
ShowBranchCommitHash: false,
CommandLogSize: 8,
SplitDiff: "auto",
SkipRewordInEditorWarning: false,
Border: "rounded",
AnimateExplosion: true,
PortraitMode: "auto",
FilterMode: "substring",
CommitLength: CommitLengthConfig{Show: true},
SkipNoStagedFilesWarning: false,
ShowListFooter: true,
ShowCommandLog: true,
ShowBottomLine: true,
ShowPanelJumps: true,
ShowFileTree: true,
ShowRandomTip: true,
ShowIcons: false,
NerdFontsVersion: "",
ShowFileIcons: true,
CommitHashLength: 8,
ShowBranchCommitHash: false,
ShowDivergenceFromBaseBranch: "onlyBehindArrowAndNumber",
CommandLogSize: 8,
SplitDiff: "auto",
SkipRewordInEditorWarning: false,
Border: "rounded",
AnimateExplosion: true,
PortraitMode: "auto",
FilterMode: "substring",
Spinner: SpinnerConfig{
Frames: []string{"|", "/", "-", "\\"},
Rate: 50,

View File

@@ -100,14 +100,13 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Branches.RebaseBranch),
Handler: opts.Guards.OutsideFilterMode(self.rebase),
GetDisabledReason: self.require(
self.singleItemSelected(self.notRebasingOntoSelf),
),
Description: self.c.Tr.RebaseBranch,
Tooltip: self.c.Tr.RebaseBranchTooltip,
DisplayOnScreen: true,
Key: opts.GetKey(opts.Config.Branches.RebaseBranch),
Handler: opts.Guards.OutsideFilterMode(self.withItem(self.rebase)),
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.RebaseBranch,
Tooltip: self.c.Tr.RebaseBranchTooltip,
OpensMenu: true,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Branches.MergeIntoCurrentBranch),
@@ -205,6 +204,40 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc
},
}
var disabledReason *types.DisabledReason
baseBranch, err := self.c.Git().Loaders.BranchLoader.GetBaseBranch(selectedBranch, self.c.Model().ExistingMainBranches)
if err != nil {
return err
}
if baseBranch == "" {
baseBranch = self.c.Tr.BaseBranch
disabledReason = &types.DisabledReason{Text: self.c.Tr.NoMainBranches}
}
shortBaseBranchName := strings.TrimPrefix(baseBranch, "refs/remotes/")
label := utils.ResolvePlaceholderString(
self.c.Tr.ViewDivergenceFromBaseBranch,
map[string]string{"baseBranch": shortBaseBranchName},
)
viewDivergenceFromBaseBranchItem := &types.MenuItem{
LabelColumns: []string{label},
Key: 'b',
OnPress: func() error {
branch := self.context().GetSelected()
if branch == nil {
return nil
}
return self.c.Helpers().SubCommits.ViewSubCommits(helpers.ViewSubCommitsOpts{
Ref: branch,
TitleRef: fmt.Sprintf("%s <-> %s", branch.RefName(), shortBaseBranchName),
RefToShowDivergenceFrom: baseBranch,
Context: self.context(),
ShowBranchHeads: false,
})
},
DisabledReason: disabledReason,
}
unsetUpstreamItem := &types.MenuItem{
LabelColumns: []string{self.c.Tr.UnsetUpstream},
OnPress: func() error {
@@ -312,6 +345,7 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc
options := []*types.MenuItem{
viewDivergenceItem,
viewDivergenceFromBaseBranchItem,
unsetUpstreamItem,
setUpstreamItem,
upstreamResetItem,
@@ -598,19 +632,8 @@ func (self *BranchesController) merge() error {
return self.c.Helpers().MergeAndRebase.MergeRefIntoCheckedOutBranch(selectedBranchName)
}
func (self *BranchesController) rebase() error {
selectedBranchName := self.context().GetSelected().Name
return self.c.Helpers().MergeAndRebase.RebaseOntoRef(selectedBranchName)
}
func (self *BranchesController) notRebasingOntoSelf(branch *models.Branch) *types.DisabledReason {
selectedBranchName := branch.Name
checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef().Name
if selectedBranchName == checkedOutBranch {
return &types.DisabledReason{Text: self.c.Tr.CantRebaseOntoSelf}
}
return nil
func (self *BranchesController) rebase(branch *models.Branch) error {
return self.c.Helpers().MergeAndRebase.RebaseOntoRef(branch.Name)
}
func (self *BranchesController) fastForward(branch *models.Branch) error {

View File

@@ -234,11 +234,29 @@ func (self *MergeAndRebaseHelper) PromptToContinueRebase() error {
}
func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error {
checkedOutBranch := self.refsHelper.GetCheckedOutRef().Name
checkedOutBranch := self.refsHelper.GetCheckedOutRef()
checkedOutBranchName := self.refsHelper.GetCheckedOutRef().Name
var disabledReason, baseBranchDisabledReason *types.DisabledReason
if checkedOutBranchName == ref {
disabledReason = &types.DisabledReason{Text: self.c.Tr.CantRebaseOntoSelf}
}
baseBranch, err := self.c.Git().Loaders.BranchLoader.GetBaseBranch(checkedOutBranch, self.refsHelper.c.Model().ExistingMainBranches)
if err != nil {
return err
}
if baseBranch == "" {
baseBranch = self.c.Tr.CouldNotDetermineBaseBranch
baseBranchDisabledReason = &types.DisabledReason{Text: self.c.Tr.CouldNotDetermineBaseBranch}
}
menuItems := []*types.MenuItem{
{
Label: self.c.Tr.SimpleRebase,
Key: 's',
Label: utils.ResolvePlaceholderString(self.c.Tr.SimpleRebase,
map[string]string{"ref": ref},
),
Key: 's',
DisabledReason: disabledReason,
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.RebaseBranch)
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(task gocui.Task) error {
@@ -258,9 +276,12 @@ func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error {
},
},
{
Label: self.c.Tr.InteractiveRebase,
Key: 'i',
Tooltip: self.c.Tr.InteractiveRebaseTooltip,
Label: utils.ResolvePlaceholderString(self.c.Tr.InteractiveRebase,
map[string]string{"ref": ref},
),
Key: 'i',
DisabledReason: disabledReason,
Tooltip: self.c.Tr.InteractiveRebaseTooltip,
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.RebaseBranch)
baseCommit := self.c.Modes().MarkedBaseCommit.GetHash()
@@ -279,6 +300,31 @@ func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error {
return self.c.PushContext(self.c.Contexts().LocalCommits)
},
},
{
Label: utils.ResolvePlaceholderString(self.c.Tr.RebaseOntoBaseBranch,
map[string]string{"baseBranch": strings.TrimPrefix(baseBranch, "refs/remotes/")},
),
Key: 'b',
DisabledReason: baseBranchDisabledReason,
Tooltip: self.c.Tr.RebaseOntoBaseBranchTooltip,
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.RebaseBranch)
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(task gocui.Task) error {
baseCommit := self.c.Modes().MarkedBaseCommit.GetHash()
var err error
if baseCommit != "" {
err = self.c.Git().Rebase.RebaseBranchFromBaseCommit(baseBranch, baseCommit)
} else {
err = self.c.Git().Rebase.RebaseBranch(baseBranch)
}
err = self.CheckMergeOrRebase(err)
if err == nil {
return self.ResetMarkedBaseCommit()
}
return err
})
},
},
}
title := utils.ResolvePlaceholderString(
@@ -286,8 +332,7 @@ func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error {
self.c.Tr.RebasingFromBaseCommitTitle,
self.c.Tr.RebasingTitle),
map[string]string{
"checkedOutBranch": checkedOutBranch,
"ref": ref,
"checkedOutBranch": checkedOutBranchName,
},
)

View File

@@ -326,6 +326,7 @@ func (self *RefreshHelper) refreshCommitsWithLimit() error {
RefName: self.refForLog(),
RefForPushedStatus: checkedOutBranchName,
All: self.c.Contexts().LocalCommits.GetShowWholeGitGraph(),
ExistingMainBranches: self.c.Model().ExistingMainBranches,
},
)
if err != nil {
@@ -352,6 +353,7 @@ func (self *RefreshHelper) refreshSubCommitsWithLimit() error {
RefName: self.c.Contexts().SubCommits.GetRef().FullRefName(),
RefToShowDivergenceFrom: self.c.Contexts().SubCommits.GetRefToShowDivergenceFrom(),
RefForPushedStatus: self.c.Contexts().SubCommits.GetRef().FullRefName(),
ExistingMainBranches: self.c.Model().ExistingMainBranches,
},
)
if err != nil {
@@ -450,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

@@ -44,6 +44,7 @@ func (self *SubCommitsHelper) ViewSubCommits(opts ViewSubCommitsOpts) error {
RefName: opts.Ref.FullRefName(),
RefForPushedStatus: opts.Ref.FullRefName(),
RefToShowDivergenceFrom: opts.RefToShowDivergenceFrom,
ExistingMainBranches: self.c.Model().ExistingMainBranches,
},
)
if err != nil {

View File

@@ -12,6 +12,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
@@ -116,7 +117,7 @@ func (self *StatusController) onClick(opts gocui.ViewMouseBindingOpts) error {
return err
}
upstreamStatus := presentation.BranchStatus(currentBranch, types.ItemOperationNone, self.c.Tr, time.Now(), self.c.UserConfig)
upstreamStatus := utils.Decolorise(presentation.BranchStatus(currentBranch, types.ItemOperationNone, self.c.Tr, time.Now(), self.c.UserConfig))
repoName := self.c.Git().RepoPaths.RepoName()
workingTreeState := self.c.Git().Status.WorkingTreeState()
switch workingTreeState {

View File

@@ -386,6 +386,7 @@ func (gui *Gui) resetState(startArgs appTypes.StartArgs) types.Context {
BisectInfo: git_commands.NewNullBisectInfo(),
FilesTrie: patricia.NewTrie(),
Authors: map[string]*models.Author{},
ExistingMainBranches: git_commands.NewExistingMainBranches(gui.UserConfig.Git.MainBranches, gui.os.Cmd),
},
Modes: &types.Modes{
Filtering: filtering.New(startArgs.FilterPath, ""),

View File

@@ -56,7 +56,7 @@ func getBranchDisplayStrings(
// Recency is always three characters, plus one for the space
availableWidth := viewWidth - 4
if len(branchStatus) > 0 {
availableWidth -= runewidth.StringWidth(branchStatus) + 1
availableWidth -= runewidth.StringWidth(utils.Decolorise(branchStatus)) + 1
}
if icons.IsIconEnabled() {
availableWidth -= 2 // one for the icon, one for the space
@@ -89,8 +89,7 @@ func getBranchDisplayStrings(
coloredName = fmt.Sprintf("%s %s", coloredName, style.FgDefault.Sprint(worktreeIcon))
}
if len(branchStatus) > 0 {
coloredStatus := branchStatusColor(b, itemOperation).Sprint(branchStatus)
coloredName = fmt.Sprintf("%s %s", coloredName, coloredStatus)
coloredName = fmt.Sprintf("%s %s", coloredName, branchStatus)
}
recencyColor := style.FgCyan
@@ -144,30 +143,6 @@ func GetBranchTextStyle(name string) style.TextStyle {
}
}
func branchStatusColor(branch *models.Branch, itemOperation types.ItemOperation) style.TextStyle {
colour := style.FgYellow
if itemOperation != types.ItemOperationNone {
colour = style.FgCyan
} else if branch.UpstreamGone {
colour = style.FgRed
} else if branch.MatchesUpstream() {
colour = style.FgGreen
} else if branch.RemoteBranchNotStoredLocally() {
colour = style.FgMagenta
}
return colour
}
func ColoredBranchStatus(
branch *models.Branch,
itemOperation types.ItemOperation,
tr *i18n.TranslationSet,
userConfig *config.UserConfig,
) string {
return branchStatusColor(branch, itemOperation).Sprint(BranchStatus(branch, itemOperation, tr, time.Now(), userConfig))
}
func BranchStatus(
branch *models.Branch,
itemOperation types.ItemOperation,
@@ -177,30 +152,70 @@ func BranchStatus(
) string {
itemOperationStr := ItemOperationToString(itemOperation, tr)
if itemOperationStr != "" {
return itemOperationStr + " " + utils.Loader(now, userConfig.Gui.Spinner)
}
if !branch.IsTrackingRemote() {
return ""
}
if branch.UpstreamGone {
return tr.UpstreamGone
}
if branch.MatchesUpstream() {
return "✓"
}
if branch.RemoteBranchNotStoredLocally() {
return "?"
return style.FgCyan.Sprintf("%s %s", itemOperationStr, utils.Loader(now, userConfig.Gui.Spinner))
}
result := ""
if branch.HasCommitsToPush() {
result = fmt.Sprintf("↑%s", branch.Pushables)
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.HasCommitsToPull() {
result = fmt.Sprintf("%s↓%s", result, branch.Pullables)
if userConfig.Gui.ShowDivergenceFromBaseBranch != "off" {
showNumbers := userConfig.Gui.ShowDivergenceFromBaseBranch == "onlyBehindArrowAndNumber" ||
userConfig.Gui.ShowDivergenceFromBaseBranch == "behindAndAheadArrowsAndNumbers"
behind := branch.BehindBaseBranch.Load()
if userConfig.Gui.ShowDivergenceFromBaseBranch == "onlyBehindArrow" ||
userConfig.Gui.ShowDivergenceFromBaseBranch == "onlyBehindArrowAndNumber" {
if behind != 0 {
if result != "" {
result += " "
}
if showNumbers {
result += style.FgCyan.Sprintf("↓%d", behind)
} else {
result += style.FgCyan.Sprintf("↓")
}
}
} else {
ahead := branch.AheadOfBaseBranch.Load()
if ahead != 0 || behind != 0 {
if result != "" {
result += " "
}
if ahead != 0 && behind != 0 {
if showNumbers {
result += style.FgCyan.Sprintf("↓%d↑%d", behind, ahead)
} else {
result += style.FgCyan.Sprint("↕")
}
} else if behind != 0 {
if showNumbers {
result += style.FgCyan.Sprintf("↓%d", behind)
} else {
result += style.FgCyan.Sprint("↓")
}
} else {
if showNumbers {
result += style.FgCyan.Sprintf("↑%d", ahead)
} else {
result += style.FgCyan.Sprint("↑")
}
}
}
}
}
return result

View File

@@ -81,7 +81,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
viewWidth: 100,
useIcons: false,
checkedOutByWorktree: true,
expected: []string{"1m", "branch_name (worktree) ↑3↓5"},
expected: []string{"1m", "branch_name (worktree) ↓5↑3"},
},
{
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
@@ -167,7 +167,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
viewWidth: 30,
useIcons: false,
checkedOutByWorktree: true,
expected: []string{"1m", "branch_na… (worktree) ↑3↓5"},
expected: []string{"1m", "branch_na… (worktree) ↓5↑3"},
},
{
branch: &models.Branch{Name: "branch_name", Recency: "1m"},

View File

@@ -2,6 +2,7 @@ package presentation
import (
"fmt"
"time"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
@@ -24,7 +25,10 @@ func FormatStatus(
status := ""
if currentBranch.IsRealBranch() {
status += ColoredBranchStatus(currentBranch, itemOperation, tr, userConfig) + " "
status += BranchStatus(currentBranch, itemOperation, tr, time.Now(), userConfig)
if status != "" {
status += " "
}
}
if workingTreeState != enums.REBASE_MODE_NONE {
@@ -40,7 +44,7 @@ func FormatStatus(
}
repoName = fmt.Sprintf("%s(%s%s)", repoName, icon, style.FgCyan.Sprint(linkedWorktreeName))
}
status += fmt.Sprintf("%s → %s ", repoName, name)
status += fmt.Sprintf("%s → %s", repoName, name)
return status
}

View File

@@ -281,6 +281,8 @@ type Model struct {
// we're on a detached head because we're rebasing or bisecting.
CheckedOutBranch string
ExistingMainBranches *git_commands.ExistingMainBranches
// for displaying suggestions while typing in a file name
FilesTrie *patricia.Trie

View File

@@ -289,7 +289,9 @@ type TranslationSet struct {
RebasingFromBaseCommitTitle string
SimpleRebase string
InteractiveRebase string
RebaseOntoBaseBranch string
InteractiveRebaseTooltip string
RebaseOntoBaseBranchTooltip string
MustSelectTodoCommits string
ConfirmMerge string
FwdNoUpstream string
@@ -468,6 +470,10 @@ type TranslationSet struct {
SetUpstream string
UnsetUpstream string
ViewDivergenceFromUpstream string
ViewDivergenceFromBaseBranch string
NoMainBranches string
BaseBranch string
CouldNotDetermineBaseBranch string
DivergenceSectionHeaderLocal string
DivergenceSectionHeaderRemote string
ViewUpstreamResetOptions string
@@ -1251,11 +1257,13 @@ func EnglishTranslationSet() TranslationSet {
KeybindingsMenuSectionLocal: "Local",
KeybindingsMenuSectionGlobal: "Global",
KeybindingsMenuSectionNavigation: "Navigation",
RebasingTitle: "Rebase '{{.checkedOutBranch}}' onto '{{.ref}}'",
RebasingFromBaseCommitTitle: "Rebase '{{.checkedOutBranch}}' from marked base onto '{{.ref}}'",
SimpleRebase: "Simple rebase",
InteractiveRebase: "Interactive rebase",
RebasingTitle: "Rebase '{{.checkedOutBranch}}'",
RebasingFromBaseCommitTitle: "Rebase '{{.checkedOutBranch}}' from marked base",
SimpleRebase: "Simple rebase onto '{{.ref}}'",
InteractiveRebase: "Interactive rebase onto '{{.ref}}'",
RebaseOntoBaseBranch: "Rebase onto base branch ({{.baseBranch}})",
InteractiveRebaseTooltip: "Begin an interactive rebase with a break at the start, so you can update the TODO commits before continuing.",
RebaseOntoBaseBranchTooltip: "Rebase the checked out branch onto its base branch (i.e. the closest main branch).",
MustSelectTodoCommits: "When rebasing, this action only works on a selection of TODO commits.",
ConfirmMerge: "Are you sure you want to merge '{{.selectedBranch}}' into '{{.checkedOutBranch}}'?",
FwdNoUpstream: "Cannot fast-forward a branch with no upstream",
@@ -1434,12 +1442,16 @@ func EnglishTranslationSet() TranslationSet {
SetUpstream: "Set upstream of selected branch",
UnsetUpstream: "Unset upstream of selected branch",
ViewDivergenceFromUpstream: "View divergence from upstream",
ViewDivergenceFromBaseBranch: "View divergence from base branch ({{.baseBranch}})",
NoMainBranches: "There are no main branches",
BaseBranch: "base branch",
CouldNotDetermineBaseBranch: "couldn't determine base branch",
DivergenceSectionHeaderLocal: "Local",
DivergenceSectionHeaderRemote: "Remote",
ViewUpstreamResetOptions: "Reset checked-out branch onto {{.upstream}}",
ViewUpstreamResetOptionsTooltip: "View options for resetting the checked-out branch onto {{upstream}}. Note: this will not reset the selected branch onto the upstream, it will reset the checked-out branch onto the upstream.",
ViewUpstreamRebaseOptions: "Rebase checked-out branch onto {{.upstream}}",
ViewUpstreamRebaseOptionsTooltip: "View options for rebasing the checked-out branch onto {{upstream}}. Note: this will not rebase the selected branch onto the upstream, it will rebased the checked-out branch onto the upstream.",
ViewUpstreamRebaseOptionsTooltip: "View options for rebasing the checked-out branch onto {{upstream}}. Note: this will not rebase the selected branch onto the upstream, it will rebase the checked-out branch onto the upstream.",
UpstreamGenericName: "upstream of selected branch",
SetUpstreamTitle: "Set upstream branch",
SetUpstreamMessage: "Are you sure you want to set the upstream branch of '{{.checkedOut}}' to '{{.selected}}'",

View File

@@ -274,10 +274,10 @@ func polishTranslationSet() TranslationSet {
KeybindingsMenuSectionLocal: "Lokalne",
KeybindingsMenuSectionGlobal: "Globalne",
KeybindingsMenuSectionNavigation: "Nawigacja",
RebasingTitle: "Rebase '{{.checkedOutBranch}}' na '{{.ref}}'",
RebasingFromBaseCommitTitle: "Rebase '{{.checkedOutBranch}}' od oznaczonego commita bazowego na '{{.ref}}'",
SimpleRebase: "Prosty rebase",
InteractiveRebase: "Interaktywny rebase",
RebasingTitle: "Rebase '{{.checkedOutBranch}}'",
RebasingFromBaseCommitTitle: "Rebase '{{.checkedOutBranch}}' od oznaczonego commita bazowego",
SimpleRebase: "Prosty rebase na '{{.ref}}'",
InteractiveRebase: "Interaktywny rebase na '{{.ref}}'",
InteractiveRebaseTooltip: "Rozpocznij interaktywny rebase z przerwaniem na początku, abyś mógł zaktualizować commity TODO przed kontynuacją.",
MustSelectTodoCommits: "Podczas rebase ta akcja działa tylko na zaznaczonych commitach TODO.",
ConfirmMerge: "Czy na pewno chcesz scalić '{{.selectedBranch}}' z '{{.checkedOutBranch}}'?",

View File

@@ -225,9 +225,9 @@ func RussianTranslationSet() TranslationSet {
ConflictsResolved: "Все конфликты слияния разрешены. Продолжить?",
Continue: "Продолжить",
Keybindings: "Связки клавиш",
RebasingTitle: "Перебазировать '{{.checkedOutBranch}}' на '{{.ref}}'",
SimpleRebase: "Простая перебазировка",
InteractiveRebase: "Интерактивная перебазировка",
RebasingTitle: "Перебазировать '{{.checkedOutBranch}}'",
SimpleRebase: "Простая перебазировка на '{{.ref}}'",
InteractiveRebase: "Интерактивная перебазировка на '{{.ref}}'",
InteractiveRebaseTooltip: "Начать интерактивную перебазировку с перерыва в начале, чтобы можно было обновить TODO коммиты, прежде чем продолжить.",
ConfirmMerge: "Вы уверены, что хотите to merge '{{.selectedBranch}}' into '{{.checkedOutBranch}}'?",
FwdNoUpstream: "Невозможно перемотать ветку без upstream-ветки",

View File

@@ -255,9 +255,9 @@ func traditionalChineseTranslationSet() TranslationSet {
ConflictsResolved: "所有合併衝突都已解決。是否繼續?",
Continue: "確認",
Keybindings: "鍵盤快捷鍵",
RebasingTitle: "將 '{{.checkedOutBranch}}' 變基至 '{{.ref}}'",
SimpleRebase: "簡單變基",
InteractiveRebase: "互動變基",
RebasingTitle: "將 '{{.checkedOutBranch}}'",
SimpleRebase: "簡單變基 變基至 '{{.ref}}'",
InteractiveRebase: "互動變基 變基至 '{{.ref}}'",
InteractiveRebaseTooltip: "開始一個互動變基以中斷開始這樣你可以在繼續之前更新TODO提交",
ConfirmMerge: "是否將 '{{.selectedBranch}}' 合併至 '{{.checkedOutBranch}}' ",
FwdNoUpstream: "無法快進無上游分支",

View File

@@ -44,7 +44,7 @@ var DeleteRemoteBranchWithCredentialPrompt = NewIntegrationTest(NewIntegrationTe
Confirm()
}
t.Views().Status().Content(Contains("✓ repo → mybranch"))
t.Views().Status().Content(Equals("✓ repo → mybranch"))
deleteBranch()
@@ -66,7 +66,7 @@ var DeleteRemoteBranchWithCredentialPrompt = NewIntegrationTest(NewIntegrationTe
Content(Contains("incorrect username/password")).
Confirm()
t.Views().Status().Content(Contains("✓ repo → mybranch"))
t.Views().Status().Content(Equals("✓ repo → mybranch"))
// try again with correct password
deleteBranch()
@@ -81,7 +81,7 @@ var DeleteRemoteBranchWithCredentialPrompt = NewIntegrationTest(NewIntegrationTe
Type("password").
Confirm()
t.Views().Status().Content(Contains("repo → mybranch").DoesNotContain("✓"))
t.Views().Status().Content(Equals("(upstream gone) repo → mybranch"))
t.Views().Branches().TopLines(Contains("mybranch (upstream gone)"))
},
})

View File

@@ -31,7 +31,7 @@ var Rebase = NewIntegrationTest(NewIntegrationTestArgs{
Press(keys.Branches.RebaseBranch)
t.ExpectPopup().Menu().
Title(Equals("Rebase 'first-change-branch' onto 'second-change-branch'")).
Title(Equals("Rebase 'first-change-branch'")).
Select(Contains("Simple rebase")).
Confirm()

View File

@@ -31,7 +31,7 @@ var RebaseAbortOnConflict = NewIntegrationTest(NewIntegrationTestArgs{
Press(keys.Branches.RebaseBranch)
t.ExpectPopup().Menu().
Title(Equals("Rebase 'first-change-branch' onto 'second-change-branch'")).
Title(Equals("Rebase 'first-change-branch'")).
Select(Contains("Simple rebase")).
Confirm()

View File

@@ -37,7 +37,7 @@ var RebaseAndDrop = NewIntegrationTest(NewIntegrationTestArgs{
Press(keys.Branches.RebaseBranch)
t.ExpectPopup().Menu().
Title(Equals("Rebase 'first-change-branch' onto 'second-change-branch'")).
Title(Equals("Rebase 'first-change-branch'")).
Select(Contains("Simple rebase")).
Confirm()

View File

@@ -31,7 +31,7 @@ var RebaseCancelOnConflict = NewIntegrationTest(NewIntegrationTestArgs{
Press(keys.Branches.RebaseBranch)
t.ExpectPopup().Menu().
Title(Equals("Rebase 'first-change-branch' onto 'second-change-branch'")).
Title(Equals("Rebase 'first-change-branch'")).
Select(Contains("Simple rebase")).
Confirm()

View File

@@ -42,7 +42,7 @@ var RebaseCopiedBranch = NewIntegrationTest(NewIntegrationTestArgs{
Press(keys.Branches.RebaseBranch).
Tap(func() {
t.ExpectPopup().Menu().
Title(Equals("Rebase 'branch2' onto 'master'")).
Title(Equals("Rebase 'branch2'")).
Select(Contains("Simple rebase")).
Confirm()
})

View File

@@ -40,7 +40,7 @@ var RebaseDoesNotAutosquash = NewIntegrationTest(NewIntegrationTestArgs{
Press(keys.Branches.RebaseBranch)
t.ExpectPopup().Menu().
Title(Equals("Rebase 'my-branch' onto 'master'")).
Title(Equals("Rebase 'my-branch'")).
Select(Contains("Simple rebase")).
Confirm()

View File

@@ -61,7 +61,7 @@ var RebaseFromMarkedBase = NewIntegrationTest(NewIntegrationTestArgs{
Press(keys.Branches.RebaseBranch)
t.ExpectPopup().Menu().
Title(Equals("Rebase 'active-branch' from marked base onto 'target-branch'")).
Title(Equals("Rebase 'active-branch' from marked base")).
Select(Contains("Simple rebase")).
Confirm()

View File

@@ -67,7 +67,7 @@ var RebaseToUpstream = NewIntegrationTest(NewIntegrationTestArgs{
Select(Contains("Rebase checked-out branch onto origin/master...")).
Confirm()
t.ExpectPopup().Menu().
Title(Equals("Rebase 'target' onto 'origin/master'")).
Title(Equals("Rebase 'target'")).
Select(Contains("Simple rebase")).
Confirm()
})

View File

@@ -39,7 +39,7 @@ var AdvancedInteractiveRebase = NewIntegrationTest(NewIntegrationTestArgs{
Press(keys.Branches.RebaseBranch)
t.ExpectPopup().Menu().
Title(Equals(fmt.Sprintf("Rebase '%s' onto '%s'", TOP_BRANCH, BASE_BRANCH))).
Title(Equals(fmt.Sprintf("Rebase '%s'", TOP_BRANCH))).
Select(Contains("Interactive rebase")).
Confirm()
t.Views().Commits().

View File

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

View File

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

View File

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

View File

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

View File

@@ -25,7 +25,7 @@ var PullAndSetUpstream = NewIntegrationTest(NewIntegrationTestArgs{
Contains("one"),
)
t.Views().Status().Content(Contains("repo → master"))
t.Views().Status().Content(Equals("repo → master"))
t.Views().Files().IsFocused().Press(keys.Universal.Pull)
@@ -40,6 +40,6 @@ var PullAndSetUpstream = NewIntegrationTest(NewIntegrationTestArgs{
Contains("one"),
)
t.Views().Status().Content(Contains("✓ repo → master"))
t.Views().Status().Content(Equals("✓ ↓1 repo → master"))
},
})

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,7 +21,7 @@ var Push = NewIntegrationTest(NewIntegrationTestArgs{
shell.EmptyCommit("two")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Status().Content(Contains("↑1 repo → master"))
t.Views().Status().Content(Equals("↑1 repo → master"))
t.Views().Files().
IsFocused().

View File

@@ -22,7 +22,7 @@ var PushAndAutoSetUpstream = NewIntegrationTest(NewIntegrationTestArgs{
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
// assert no mention of upstream/downstream changes
t.Views().Status().Content(MatchesRegexp(`^\s+repo → master`))
t.Views().Status().Content(Equals("repo → master"))
t.Views().Files().
IsFocused().

View File

@@ -19,7 +19,7 @@ var PushAndSetUpstream = NewIntegrationTest(NewIntegrationTestArgs{
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
// assert no mention of upstream/downstream changes
t.Views().Status().Content(MatchesRegexp(`^\s+repo → master`))
t.Views().Status().Content(Equals("repo → master"))
t.Views().Files().
IsFocused().

View File

@@ -24,13 +24,13 @@ var PushFollowTags = NewIntegrationTest(NewIntegrationTestArgs{
shell.SetConfig("push.followTags", "true")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Status().Content(Contains("↑1 repo → master"))
t.Views().Status().Content(Equals("↑1 repo → master"))
t.Views().Files().
IsFocused().
Press(keys.Universal.Push)
t.Views().Status().Content(Contains("✓ repo → master"))
t.Views().Status().Content(Equals("✓ repo → master"))
t.Views().Remotes().
Focus().

View File

@@ -22,13 +22,13 @@ var PushNoFollowTags = NewIntegrationTest(NewIntegrationTestArgs{
shell.CreateAnnotatedTag("mytag", "message", "HEAD")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Status().Content(Contains("✓ repo → master"))
t.Views().Status().Content(Equals("✓ repo → master"))
t.Views().Files().
IsFocused().
Press(keys.Universal.Push)
t.Views().Status().Content(Contains("✓ repo → master"))
t.Views().Status().Content(Equals("✓ repo → master"))
t.Views().Remotes().
Focus().

View File

@@ -26,7 +26,7 @@ var PushWithCredentialPrompt = NewIntegrationTest(NewIntegrationTestArgs{
shell.CopyHelpFile("pre-push", ".git/hooks/pre-push")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Status().Content(Contains("↑1 repo → master"))
t.Views().Status().Content(Equals("↑1 repo → master"))
t.Views().Files().
IsFocused().
@@ -50,7 +50,7 @@ var PushWithCredentialPrompt = NewIntegrationTest(NewIntegrationTestArgs{
Content(Contains("incorrect username/password")).
Confirm()
t.Views().Status().Content(Contains("↑1 repo → master"))
t.Views().Status().Content(Equals("↑1 repo → master"))
// try again with correct password
t.Views().Files().
@@ -67,7 +67,7 @@ var PushWithCredentialPrompt = NewIntegrationTest(NewIntegrationTestArgs{
Type("password").
Confirm()
t.Views().Status().Content(Contains("✓ repo → master"))
t.Views().Status().Content(Equals("✓ repo → master"))
assertSuccessfullyPushed(t)
},

View File

@@ -24,7 +24,7 @@ func createTwoBranchesReadyToForcePush(shell *Shell) {
}
func assertSuccessfullyPushed(t *TestDriver) {
t.Views().Status().Content(Contains("✓ repo → master"))
t.Views().Status().Content(Equals("✓ repo → master"))
t.Views().Remotes().
Focus().

View File

@@ -319,6 +319,18 @@
"type": "boolean",
"description": "If true, show commit hashes alongside branch names in the branches view."
},
"showDivergenceFromBaseBranch": {
"type": "string",
"enum": [
"off",
"onlyBehindArrow",
"onlyBehindArrowAndNumber",
"behindAndAheadArrows",
"behindAndAheadArrowsAndNumbers"
],
"description": "Whether to show the divergence from the base branch in the branches view.",
"default": "onlyBehind"
},
"commandLogSize": {
"type": "integer",
"minimum": 0,