Skip to content

Commit 33da56e

Browse files
authored
Merge pull request #2619 from stefanhaller/config-for-base-branches
Add config for main branches
2 parents d2d50ae + 46b93bb commit 33da56e

File tree

5 files changed

+164
-74
lines changed

5 files changed

+164
-74
lines changed

docs/Config.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ git:
8888
# displays the whole git graph by default in the commits panel (equivalent to passing the `--all` argument to `git log`)
8989
showWholeGraph: false
9090
skipHookPrefix: WIP
91+
# The main branches. We colour commits green if they belong to one of these branches,
92+
# so that you can easily see which commits are unique to your branch (coloured in yellow)
93+
mainBranches: [master, main]
9194
autoFetch: true
9295
autoRefresh: true
9396
branchLogCmd: 'git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --'

pkg/commands/git.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ func NewGitCommandAux(
129129

130130
branchLoader := git_commands.NewBranchLoader(cmn, branchCommands.GetRawBranches, branchCommands.CurrentBranchInfo, configCommands)
131131
commitFileLoader := git_commands.NewCommitFileLoader(cmn, cmd)
132-
commitLoader := git_commands.NewCommitLoader(cmn, cmd, dotGitDir, branchCommands.CurrentBranchInfo, statusCommands.RebaseMode)
132+
commitLoader := git_commands.NewCommitLoader(cmn, cmd, dotGitDir, statusCommands.RebaseMode)
133133
reflogCommitLoader := git_commands.NewReflogCommitLoader(cmn, cmd)
134134
remoteLoader := git_commands.NewRemoteLoader(cmn, cmd, repo.Remotes)
135135
stashLoader := git_commands.NewStashLoader(cmn, cmd)

pkg/commands/git_commands/commit_loader.go

Lines changed: 49 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
1616
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
1717
"github.com/jesseduffield/lazygit/pkg/common"
18+
"github.com/samber/lo"
1819
)
1920

2021
// context:
@@ -28,29 +29,31 @@ type CommitLoader struct {
2829
*common.Common
2930
cmd oscommands.ICmdObjBuilder
3031

31-
getCurrentBranchInfo func() (BranchInfo, error)
32-
getRebaseMode func() (enums.RebaseMode, error)
33-
readFile func(filename string) ([]byte, error)
34-
walkFiles func(root string, fn filepath.WalkFunc) error
35-
dotGitDir string
32+
getRebaseMode func() (enums.RebaseMode, error)
33+
readFile func(filename string) ([]byte, error)
34+
walkFiles func(root string, fn filepath.WalkFunc) error
35+
dotGitDir string
36+
// List of main branches that exist in the repo, quoted for direct use in a git command.
37+
// We use these to obtain the merge base of the branch.
38+
// When nil, we're yet to obtain the list of main branches.
39+
quotedMainBranches *string
3640
}
3741

3842
// making our dependencies explicit for the sake of easier testing
3943
func NewCommitLoader(
4044
cmn *common.Common,
4145
cmd oscommands.ICmdObjBuilder,
4246
dotGitDir string,
43-
getCurrentBranchInfo func() (BranchInfo, error),
4447
getRebaseMode func() (enums.RebaseMode, error),
4548
) *CommitLoader {
4649
return &CommitLoader{
47-
Common: cmn,
48-
cmd: cmd,
49-
getCurrentBranchInfo: getCurrentBranchInfo,
50-
getRebaseMode: getRebaseMode,
51-
readFile: os.ReadFile,
52-
walkFiles: filepath.Walk,
53-
dotGitDir: dotGitDir,
50+
Common: cmn,
51+
cmd: cmd,
52+
getRebaseMode: getRebaseMode,
53+
readFile: os.ReadFile,
54+
walkFiles: filepath.Walk,
55+
dotGitDir: dotGitDir,
56+
quotedMainBranches: nil,
5457
}
5558
}
5659

@@ -101,10 +104,7 @@ func (self *CommitLoader) GetCommits(opts GetCommitsOptions) ([]*models.Commit,
101104
return commits, nil
102105
}
103106

104-
commits, err = self.setCommitMergedStatuses(opts.RefName, commits)
105-
if err != nil {
106-
return nil, err
107-
}
107+
commits = self.setCommitMergedStatuses(opts.RefName, commits)
108108

109109
return commits, nil
110110
}
@@ -344,13 +344,10 @@ func (self *CommitLoader) commitFromPatch(content string) *models.Commit {
344344
}
345345
}
346346

347-
func (self *CommitLoader) setCommitMergedStatuses(refName string, commits []*models.Commit) ([]*models.Commit, error) {
348-
ancestor, err := self.getMergeBase(refName)
349-
if err != nil {
350-
return nil, err
351-
}
347+
func (self *CommitLoader) setCommitMergedStatuses(refName string, commits []*models.Commit) []*models.Commit {
348+
ancestor := self.getMergeBase(refName)
352349
if ancestor == "" {
353-
return commits, nil
350+
return commits
354351
}
355352
passedAncestor := false
356353
for i, commit := range commits {
@@ -364,23 +361,41 @@ func (self *CommitLoader) setCommitMergedStatuses(refName string, commits []*mod
364361
commits[i].Status = models.StatusMerged
365362
}
366363
}
367-
return commits, nil
364+
return commits
368365
}
369366

370-
func (self *CommitLoader) getMergeBase(refName string) (string, error) {
371-
info, err := self.getCurrentBranchInfo()
372-
if err != nil {
373-
return "", err
367+
func (self *CommitLoader) getMergeBase(refName string) string {
368+
if self.quotedMainBranches == nil {
369+
self.quotedMainBranches = lo.ToPtr(self.getExistingMainBranches())
374370
}
375371

376-
baseBranch := "master"
377-
if strings.HasPrefix(info.RefName, "feature/") {
378-
baseBranch = "develop"
372+
if *self.quotedMainBranches == "" {
373+
return ""
379374
}
380375

381-
// swallowing error because it's not a big deal; probably because there are no commits yet
382-
output, _ := self.cmd.New(fmt.Sprintf("git merge-base %s %s", self.cmd.Quote(refName), self.cmd.Quote(baseBranch))).DontLog().RunWithOutput()
383-
return ignoringWarnings(output), nil
376+
// We pass all configured main branches to the merge-base call; git will
377+
// return the base commit for the closest one.
378+
output, err := self.cmd.New(fmt.Sprintf("git merge-base %s %s",
379+
self.cmd.Quote(refName), *self.quotedMainBranches)).DontLog().RunWithOutput()
380+
if err != nil {
381+
// If there's an error, it must be because one of the main branches that
382+
// used to exist when we called getExistingMainBranches() was deleted
383+
// meanwhile. To fix this for next time, throw away our cache.
384+
self.quotedMainBranches = nil
385+
}
386+
return ignoringWarnings(output)
387+
}
388+
389+
func (self *CommitLoader) getExistingMainBranches() string {
390+
return strings.Join(
391+
lo.FilterMap(self.UserConfig.Git.MainBranches,
392+
func(branchName string, _ int) (string, bool) {
393+
quotedRef := self.cmd.Quote("refs/heads/" + branchName)
394+
if err := self.cmd.New(fmt.Sprintf("git rev-parse --verify --quiet %s", quotedRef)).DontLog().Run(); err != nil {
395+
return "", false
396+
}
397+
return quotedRef, true
398+
}), " ")
384399
}
385400

386401
func ignoringWarnings(commandOutput string) string {

pkg/commands/git_commands/commit_loader_test.go

Lines changed: 109 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package git_commands
22

33
import (
4+
"errors"
45
"path/filepath"
56
"strings"
67
"testing"
@@ -21,25 +22,26 @@ d8084cd558925eb7c9c38afeed5725c21653ab90|1640821426|Jesse Duffield|jessedduffiel
2122
3d4470a6c072208722e5ae9a54bcb9634959a1c5|1640748818|Jesse Duffield|[email protected]||053a66a7be3da43aacdc|WIP
2223
053a66a7be3da43aacdc7aa78e1fe757b82c4dd2|1640739815|Jesse Duffield|[email protected]||985fe482e806b172aea4|refactoring the config struct`, "|", "\x00", -1)
2324

25+
var singleCommitOutput = strings.Replace(`0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield|[email protected]| (HEAD -> better-tests)|b21997d6b4cbdf84b149|better typing for rebase mode`, "|", "\x00", -1)
26+
2427
func TestGetCommits(t *testing.T) {
2528
type scenario struct {
26-
testName string
27-
runner *oscommands.FakeCmdObjRunner
28-
expectedCommits []*models.Commit
29-
expectedError error
30-
logOrder string
31-
rebaseMode enums.RebaseMode
32-
currentBranchName string
33-
opts GetCommitsOptions
29+
testName string
30+
runner *oscommands.FakeCmdObjRunner
31+
expectedCommits []*models.Commit
32+
expectedError error
33+
logOrder string
34+
rebaseMode enums.RebaseMode
35+
opts GetCommitsOptions
36+
mainBranches []string
3437
}
3538

3639
scenarios := []scenario{
3740
{
38-
testName: "should return no commits if there are none",
39-
logOrder: "topo-order",
40-
rebaseMode: enums.REBASE_MODE_NONE,
41-
currentBranchName: "master",
42-
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
41+
testName: "should return no commits if there are none",
42+
logOrder: "topo-order",
43+
rebaseMode: enums.REBASE_MODE_NONE,
44+
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
4345
runner: oscommands.NewFakeRunner(t).
4446
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
4547
Expect(`git log "HEAD" --topo-order --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --no-show-signature --`, "", nil),
@@ -48,11 +50,10 @@ func TestGetCommits(t *testing.T) {
4850
expectedError: nil,
4951
},
5052
{
51-
testName: "should use proper upstream name for branch",
52-
logOrder: "topo-order",
53-
rebaseMode: enums.REBASE_MODE_NONE,
54-
currentBranchName: "mybranch",
55-
opts: GetCommitsOptions{RefName: "refs/heads/mybranch", IncludeRebaseCommits: false},
53+
testName: "should use proper upstream name for branch",
54+
logOrder: "topo-order",
55+
rebaseMode: enums.REBASE_MODE_NONE,
56+
opts: GetCommitsOptions{RefName: "refs/heads/mybranch", IncludeRebaseCommits: false},
5657
runner: oscommands.NewFakeRunner(t).
5758
Expect(`git merge-base "refs/heads/mybranch" "mybranch"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
5859
Expect(`git log "refs/heads/mybranch" --topo-order --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --no-show-signature --`, "", nil),
@@ -61,18 +62,21 @@ func TestGetCommits(t *testing.T) {
6162
expectedError: nil,
6263
},
6364
{
64-
testName: "should return commits if they are present",
65-
logOrder: "topo-order",
66-
rebaseMode: enums.REBASE_MODE_NONE,
67-
currentBranchName: "master",
68-
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
65+
testName: "should return commits if they are present",
66+
logOrder: "topo-order",
67+
rebaseMode: enums.REBASE_MODE_NONE,
68+
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
69+
mainBranches: []string{"master", "main"},
6970
runner: oscommands.NewFakeRunner(t).
7071
// here it's seeing which commits are yet to be pushed
7172
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
7273
// here it's actually getting all the commits in a formatted form, one per line
7374
Expect(`git log "HEAD" --topo-order --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --no-show-signature --`, commitsOutput, nil).
75+
// here it's testing which of the configured main branches exist
76+
Expect(`git rev-parse --verify --quiet "refs/heads/master"`, "", nil). // this one does
77+
Expect(`git rev-parse --verify --quiet "refs/heads/main"`, "", errors.New("error")). // this one doesn't
7478
// here it's seeing where our branch diverged from the master branch so that we can mark that commit and parent commits as 'merged'
75-
Expect(`git merge-base "HEAD" "master"`, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil),
79+
Expect(`git merge-base "HEAD" "refs/heads/master"`, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil),
7680

7781
expectedCommits: []*models.Commit{
7882
{
@@ -191,11 +195,80 @@ func TestGetCommits(t *testing.T) {
191195
expectedError: nil,
192196
},
193197
{
194-
testName: "should not specify order if `log.order` is `default`",
195-
logOrder: "default",
196-
rebaseMode: enums.REBASE_MODE_NONE,
197-
currentBranchName: "master",
198-
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
198+
testName: "should not call merge-base for mainBranches if none exist",
199+
logOrder: "topo-order",
200+
rebaseMode: enums.REBASE_MODE_NONE,
201+
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
202+
mainBranches: []string{"master", "main"},
203+
runner: oscommands.NewFakeRunner(t).
204+
// here it's seeing which commits are yet to be pushed
205+
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
206+
// here it's actually getting all the commits in a formatted form, one per line
207+
Expect(`git log "HEAD" --topo-order --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --no-show-signature --`, singleCommitOutput, nil).
208+
// here it's testing which of the configured main branches exist; neither does
209+
Expect(`git rev-parse --verify --quiet "refs/heads/master"`, "", errors.New("error")).
210+
Expect(`git rev-parse --verify --quiet "refs/heads/main"`, "", errors.New("error")),
211+
212+
expectedCommits: []*models.Commit{
213+
{
214+
Sha: "0eea75e8c631fba6b58135697835d58ba4c18dbc",
215+
Name: "better typing for rebase mode",
216+
Status: models.StatusUnpushed,
217+
Action: models.ActionNone,
218+
Tags: []string{},
219+
ExtraInfo: "(HEAD -> better-tests)",
220+
AuthorName: "Jesse Duffield",
221+
AuthorEmail: "[email protected]",
222+
UnixTimestamp: 1640826609,
223+
Parents: []string{
224+
"b21997d6b4cbdf84b149",
225+
},
226+
},
227+
},
228+
expectedError: nil,
229+
},
230+
{
231+
testName: "should call merge-base for all main branches that exist",
232+
logOrder: "topo-order",
233+
rebaseMode: enums.REBASE_MODE_NONE,
234+
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
235+
mainBranches: []string{"master", "main", "develop", "1.0-hotfixes"},
236+
runner: oscommands.NewFakeRunner(t).
237+
// here it's seeing which commits are yet to be pushed
238+
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
239+
// here it's actually getting all the commits in a formatted form, one per line
240+
Expect(`git log "HEAD" --topo-order --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --no-show-signature --`, singleCommitOutput, nil).
241+
// here it's testing which of the configured main branches exist
242+
Expect(`git rev-parse --verify --quiet "refs/heads/master"`, "", nil).
243+
Expect(`git rev-parse --verify --quiet "refs/heads/main"`, "", errors.New("error")).
244+
Expect(`git rev-parse --verify --quiet "refs/heads/develop"`, "", nil).
245+
Expect(`git rev-parse --verify --quiet "refs/heads/1.0-hotfixes"`, "", nil).
246+
// here it's seeing where our branch diverged from the master branch so that we can mark that commit and parent commits as 'merged'
247+
Expect(`git merge-base "HEAD" "refs/heads/master" "refs/heads/develop" "refs/heads/1.0-hotfixes"`, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil),
248+
249+
expectedCommits: []*models.Commit{
250+
{
251+
Sha: "0eea75e8c631fba6b58135697835d58ba4c18dbc",
252+
Name: "better typing for rebase mode",
253+
Status: models.StatusUnpushed,
254+
Action: models.ActionNone,
255+
Tags: []string{},
256+
ExtraInfo: "(HEAD -> better-tests)",
257+
AuthorName: "Jesse Duffield",
258+
AuthorEmail: "[email protected]",
259+
UnixTimestamp: 1640826609,
260+
Parents: []string{
261+
"b21997d6b4cbdf84b149",
262+
},
263+
},
264+
},
265+
expectedError: nil,
266+
},
267+
{
268+
testName: "should not specify order if `log.order` is `default`",
269+
logOrder: "default",
270+
rebaseMode: enums.REBASE_MODE_NONE,
271+
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
199272
runner: oscommands.NewFakeRunner(t).
200273
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
201274
Expect(`git log "HEAD" --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --no-show-signature --`, "", nil),
@@ -204,11 +277,10 @@ func TestGetCommits(t *testing.T) {
204277
expectedError: nil,
205278
},
206279
{
207-
testName: "should set filter path",
208-
logOrder: "default",
209-
rebaseMode: enums.REBASE_MODE_NONE,
210-
currentBranchName: "master",
211-
opts: GetCommitsOptions{RefName: "HEAD", FilterPath: "src"},
280+
testName: "should set filter path",
281+
logOrder: "default",
282+
rebaseMode: enums.REBASE_MODE_NONE,
283+
opts: GetCommitsOptions{RefName: "HEAD", FilterPath: "src"},
212284
runner: oscommands.NewFakeRunner(t).
213285
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
214286
Expect(`git log "HEAD" --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --follow --no-show-signature -- "src"`, "", nil),
@@ -225,11 +297,8 @@ func TestGetCommits(t *testing.T) {
225297
common.UserConfig.Git.Log.Order = scenario.logOrder
226298

227299
builder := &CommitLoader{
228-
Common: common,
229-
cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner),
230-
getCurrentBranchInfo: func() (BranchInfo, error) {
231-
return BranchInfo{RefName: scenario.currentBranchName, DisplayName: scenario.currentBranchName, DetachedHead: false}, nil
232-
},
300+
Common: common,
301+
cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner),
233302
getRebaseMode: func() (enums.RebaseMode, error) { return scenario.rebaseMode, nil },
234303
dotGitDir: ".git",
235304
readFile: func(filename string) ([]byte, error) {
@@ -240,6 +309,7 @@ func TestGetCommits(t *testing.T) {
240309
},
241310
}
242311

312+
common.UserConfig.Git.MainBranches = scenario.mainBranches
243313
commits, err := builder.GetCommits(scenario.opts)
244314

245315
assert.Equal(t, scenario.expectedCommits, commits)

pkg/config/user_config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ type GitConfig struct {
7676
Paging PagingConfig `yaml:"paging"`
7777
Commit CommitConfig `yaml:"commit"`
7878
Merging MergingConfig `yaml:"merging"`
79+
MainBranches []string `yaml:"mainBranches"`
7980
SkipHookPrefix string `yaml:"skipHookPrefix"`
8081
AutoFetch bool `yaml:"autoFetch"`
8182
AutoRefresh bool `yaml:"autoRefresh"`
@@ -443,6 +444,7 @@ func GetDefaultConfig() *UserConfig {
443444
ShowWholeGraph: false,
444445
},
445446
SkipHookPrefix: "WIP",
447+
MainBranches: []string{"master", "main"},
446448
AutoFetch: true,
447449
AutoRefresh: true,
448450
BranchLogCmd: "git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --",

0 commit comments

Comments
 (0)