summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Haller <stefan@haller-berlin.de>2023-05-09 21:41:25 +0200
committerStefan Haller <stefan@haller-berlin.de>2023-05-16 13:20:03 +0200
commit46b93bba0eb14f0823cb3571b6b8ad3913ebcc7c (patch)
tree0cb109291599c17c91db2472a3803052798d2168
parentd2d50aedd046f3c32f5106c951647240d2f3a777 (diff)
Add config git.mainBranches
It defaults to {"master", "main"}, but can be set to whatever branch names are used as base branches, e.g. {"master", "devel", "v1.0-hotfixes"}. It is used for color-coding the shas in the commit list, i.e. to decide whether commits are green or yellow.
-rw-r--r--docs/Config.md3
-rw-r--r--pkg/commands/git.go2
-rw-r--r--pkg/commands/git_commands/commit_loader.go83
-rw-r--r--pkg/commands/git_commands/commit_loader_test.go148
-rw-r--r--pkg/config/user_config.go2
5 files changed, 164 insertions, 74 deletions
diff --git a/docs/Config.md b/docs/Config.md
index e3890f9f6..f752e026b 100644
--- a/docs/Config.md
+++ b/docs/Config.md
@@ -88,6 +88,9 @@ git:
# displays the whole git graph by default in the commits panel (equivalent to passing the `--all` argument to `git log`)
showWholeGraph: false
skipHookPrefix: WIP
+ # The main branches. We colour commits green if they belong to one of these branches,
+ # so that you can easily see which commits are unique to your branch (coloured in yellow)
+ mainBranches: [master, main]
autoFetch: true
autoRefresh: true
branchLogCmd: 'git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --'
diff --git a/pkg/commands/git.go b/pkg/commands/git.go
index caf03db75..4facd4f24 100644
--- a/pkg/commands/git.go
+++ b/pkg/commands/git.go
@@ -129,7 +129,7 @@ func NewGitCommandAux(
branchLoader := git_commands.NewBranchLoader(cmn, branchCommands.GetRawBranches, branchCommands.CurrentBranchInfo, configCommands)
commitFileLoader := git_commands.NewCommitFileLoader(cmn, cmd)
- commitLoader := git_commands.NewCommitLoader(cmn, cmd, dotGitDir, branchCommands.CurrentBranchInfo, statusCommands.RebaseMode)
+ commitLoader := git_commands.NewCommitLoader(cmn, cmd, dotGitDir, statusCommands.RebaseMode)
reflogCommitLoader := git_commands.NewReflogCommitLoader(cmn, cmd)
remoteLoader := git_commands.NewRemoteLoader(cmn, cmd, repo.Remotes)
stashLoader := git_commands.NewStashLoader(cmn, cmd)
diff --git a/pkg/commands/git_commands/commit_loader.go b/pkg/commands/git_commands/commit_loader.go
index 0ab3ceee4..e68f56ebb 100644
--- a/pkg/commands/git_commands/commit_loader.go
+++ b/pkg/commands/git_commands/commit_loader.go
@@ -15,6 +15,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/common"
+ "github.com/samber/lo"
)
// context:
@@ -28,11 +29,14 @@ type CommitLoader struct {
*common.Common
cmd oscommands.ICmdObjBuilder
- getCurrentBranchInfo func() (BranchInfo, error)
- getRebaseMode func() (enums.RebaseMode, error)
- readFile func(filename string) ([]byte, error)
- walkFiles func(root string, fn filepath.WalkFunc) error
- dotGitDir string
+ getRebaseMode func() (enums.RebaseMode, error)
+ 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, quoted for direct use in a git command.
+ // We use these to obtain the merge base of the branch.
+ // When nil, we're yet to obtain the list of main branches.
+ quotedMainBranches *string
}
// making our dependencies explicit for the sake of easier testing
@@ -40,17 +44,16 @@ func NewCommitLoader(
cmn *common.Common,
cmd oscommands.ICmdObjBuilder,
dotGitDir string,
- getCurrentBranchInfo func() (BranchInfo, error),
getRebaseMode func() (enums.RebaseMode, error),
) *CommitLoader {
return &CommitLoader{
- Common: cmn,
- cmd: cmd,
- getCurrentBranchInfo: getCurrentBranchInfo,
- getRebaseMode: getRebaseMode,
- readFile: os.ReadFile,
- walkFiles: filepath.Walk,
- dotGitDir: dotGitDir,
+ Common: cmn,
+ cmd: cmd,
+ getRebaseMode: getRebaseMode,
+ readFile: os.ReadFile,
+ walkFiles: filepath.Walk,
+ dotGitDir: dotGitDir,
+ quotedMainBranches: nil,
}
}
@@ -101,10 +104,7 @@ func (self *CommitLoader) GetCommits(opts GetCommitsOptions) ([]*models.Commit,
return commits, nil
}
- commits, err = self.setCommitMergedStatuses(opts.RefName, commits)
- if err != nil {
- return nil, err
- }
+ commits = self.setCommitMergedStatuses(opts.RefName, commits)
return commits, nil
}
@@ -344,13 +344,10 @@ func (self *CommitLoader) commitFromPatch(content string) *models.Commit {
}
}
-func (self *CommitLoader) setCommitMergedStatuses(refName string, commits []*models.Commit) ([]*models.Commit, error) {
- ancestor, err := self.getMergeBase(refName)
- if err != nil {
- return nil, err
- }
+func (self *CommitLoader) setCommitMergedStatuses(refName string, commits []*models.Commit) []*models.Commit {
+ ancestor := self.getMergeBase(refName)
if ancestor == "" {
- return commits, nil
+ return commits
}
passedAncestor := false
for i, commit := range commits {
@@ -364,23 +361,41 @@ func (self *CommitLoader) setCommitMergedStatuses(refName string, commits []*mod
commits[i].Status = models.StatusMerged
}
}
- return commits, nil
+ return commits
}
-func (self *CommitLoader) getMergeBase(refName string) (string, error) {
- info, err := self.getCurrentBranchInfo()
- if err != nil {
- return "", err
+func (self *CommitLoader) getMergeBase(refName string) string {
+ if self.quotedMainBranches == nil {
+ self.quotedMainBranches = lo.ToPtr(self.getExistingMainBranches())
}
- baseBranch := "master"
- if strings.HasPrefix(info.RefName, "feature/") {
- baseBranch = "develop"
+ if *self.quotedMainBranches == "" {
+ return ""
}
- // swallowing error because it's not a big deal; probably because there are no commits yet
- output, _ := self.cmd.New(fmt.Sprintf("git merge-base %s %s", self.cmd.Quote(refName), self.cmd.Quote(baseBranch))).DontLog().RunWithOutput()
- return ignoringWarnings(output), nil
+ // We pass all configured main branches to the merge-base call; git will
+ // return the base commit for the closest one.
+ output, err := self.cmd.New(fmt.Sprintf("git merge-base %s %s",
+ self.cmd.Quote(refName), *self.quotedMainBranches)).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.quotedMainBranches = nil
+ }
+ return ignoringWarnings(output)
+}
+
+func (self *CommitLoader) getExistingMainBranches() string {
+ return strings.Join(
+ lo.FilterMap(self.UserConfig.Git.MainBranches,
+ func(branchName string, _ int) (string, bool) {
+ quotedRef := self.cmd.Quote("refs/heads/" + branchName)
+ if err := self.cmd.New(fmt.Sprintf("git rev-parse --verify --quiet %s", quotedRef)).DontLog().Run(); err != nil {
+ return "", false
+ }
+ return quotedRef, true
+ }), " ")
}
func ignoringWarnings(commandOutput string) string {
diff --git a/pkg/commands/git_commands/commit_loader_test.go b/pkg/commands/git_commands/commit_loader_test.go
index 140541545..0b2df681c 100644
--- a/pkg/commands/git_commands/commit_loader_test.go
+++ b/pkg/commands/git_commands/commit_loader_test.go
@@ -1,6 +1,7 @@
package git_commands
import (
+ "errors"
"path/filepath"
"strings"
"testing"
@@ -21,25 +22,26 @@ d8084cd558925eb7c9c38afeed5725c21653ab90|1640821426|Jesse Duffield|jessedduffiel
3d4470a6c072208722e5ae9a54bcb9634959a1c5|1640748818|Jesse Duffield|jessedduffield@gmail.com||053a66a7be3da43aacdc|WIP
053a66a7be3da43aacdc7aa78e1fe757b82c4dd2|1640739815|Jesse Duffield|jessedduffield@gmail.com||985fe482e806b172aea4|refactoring the config struct`, "|", "\x00", -1)
+var singleCommitOutput = strings.Replace(`0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield|jessedduffield@gmail.com| (HEAD -> better-tests)|b21997d6b4cbdf84b149|better typing for rebase mode`, "|", "\x00", -1)
+
func TestGetCommits(t *testing.T) {
type scenario struct {
- testName string
- runner *oscommands.FakeCmdObjRunner
- expectedCommits []*models.Commit
- expectedError error
- logOrder string
- rebaseMode enums.RebaseMode
- currentBranchName string
- opts GetCommitsOptions
+ testName string
+ runner *oscommands.FakeCmdObjRunner
+ expectedCommits []*models.Commit
+ expectedError error
+ logOrder string
+ rebaseMode enums.RebaseMode
+ opts GetCommitsOptions
+ mainBranches []string
}
scenarios := []scenario{
{
- testName: "should return no commits if there are none",
- logOrder: "topo-order",
- rebaseMode: enums.REBASE_MODE_NONE,
- currentBranchName: "master",
- opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
+ testName: "should return no commits if there are none",
+ logOrder: "topo-order",
+ rebaseMode: enums.REBASE_MODE_NONE,
+ opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
runner: oscommands.NewFakeRunner(t).
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
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) {
expectedError: nil,
},
{
- testName: "should use proper upstream name for branch",
- logOrder: "topo-order",
- rebaseMode: enums.REBASE_MODE_NONE,
- currentBranchName: "mybranch",
- opts: GetCommitsOptions{RefName: "refs/heads/mybranch", IncludeRebaseCommits: false},
+ testName: "should use proper upstream name for branch",
+ logOrder: "topo-order",
+ rebaseMode: enums.REBASE_MODE_NONE,
+ opts: GetCommitsOptions{RefName: "refs/heads/mybranch", IncludeRebaseCommits: false},
runner: oscommands.NewFakeRunner(t).
Expect(`git merge-base "refs/heads/mybranch" "mybranch"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
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) {
expectedError: nil,
},
{
- testName: "should return commits if they are present",
- logOrder: "topo-order",
- rebaseMode: enums.REBASE_MODE_NONE,
- currentBranchName: "master",
- opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
+ testName: "should return commits if they are present",
+ logOrder: "topo-order",
+ rebaseMode: enums.REBASE_MODE_NONE,
+ opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
+ mainBranches: []string{"master", "main"},
runner: oscommands.NewFakeRunner(t).
// here it's seeing which commits are yet to be pushed
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
// here it's actually getting all the commits in a formatted form, one per line
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).
+ // here it's testing which of the configured main branches exist
+ Expect(`git rev-parse --verify --quiet "refs/heads/master"`, "", nil). // this one does
+ Expect(`git rev-parse --verify --quiet "refs/heads/main"`, "", errors.New("error")). // this one doesn't
// here it's seeing where our branch diverged from the master branch so that we can mark that commit and parent commits as 'merged'
- Expect(`git merge-base "HEAD" "master"`, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil),
+ Expect(`git merge-base "HEAD" "refs/heads/master"`, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil),
expectedCommits: []*models.Commit{
{
@@ -191,11 +195,80 @@ func TestGetCommits(t *testing.T) {
expectedError: nil,
},
{
- testName: "should not specify order if `log.order` is `default`",
- logOrder: "default",
- rebaseMode: enums.REBASE_MODE_NONE,
- currentBranchName: "master",
- opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
+ testName: "should not call merge-base for mainBranches if none exist",
+ logOrder: "topo-order",
+ rebaseMode: enums.REBASE_MODE_NONE,
+ opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
+ mainBranches: []string{"master", "main"},
+ runner: oscommands.NewFakeRunner(t).
+ // here it's seeing which commits are yet to be pushed
+ Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
+ // here it's actually getting all the commits in a formatted form, one per line
+ 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).
+ // here it's testing which of the configured main branches exist; neither does
+ Expect(`git rev-parse --verify --quiet "refs/heads/master"`, "", errors.New("error")).
+ Expect(`git rev-parse --verify --quiet "refs/heads/main"`, "", errors.New("error")),
+
+ expectedCommits: []*models.Commit{
+ {
+ Sha: "0eea75e8c631fba6b58135697835d58ba4c18dbc",
+ Name: "better typing for rebase mode",
+ Status: models.StatusUnpushed,
+ Action: models.ActionNone,
+ Tags: []string{},
+ ExtraInfo: "(HEAD -> better-tests)",
+ AuthorName: "Jesse Duffield",
+ AuthorEmail: "jessedduffield@gmail.com",
+ UnixTimestamp: 1640826609,
+ Parents: []string{
+ "b21997d6b4cbdf84b149",
+ },
+ },
+ },
+ expectedError: nil,
+ },
+ {
+ testName: "should call merge-base for all main branches that exist",
+ logOrder: "topo-order",
+ rebaseMode: enums.REBASE_MODE_NONE,
+ opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
+ mainBranches: []string{"master", "main", "develop", "1.0-hotfixes"},
+ runner: oscommands.NewFakeRunner(t).
+ // here it's seeing which commits are yet to be pushed
+ Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
+ // here it's actually getting all the commits in a formatted form, one per line
+ 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).
+ // here it's testing which of the configured main branches exist
+ Expect(`git rev-parse --verify --quiet "refs/heads/master"`, "", nil).
+ Expect(`git rev-parse --verify --quiet "refs/heads/main"`, "", errors.New("error")).
+ Expect(`git rev-parse --verify --quiet "refs/heads/develop"`, "", nil).
+ Expect(`git rev-parse --verify --quiet "refs/heads/1.0-hotfixes"`, "", nil).
+ // here it's seeing where our branch diverged from the master branch so that we can mark that commit and parent commits as 'merged'
+ Expect(`git merge-base "HEAD" "refs/heads/master" "refs/heads/develop" "refs/heads/1.0-hotfixes"`, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil),
+
+ expectedCommits: []*models.Commit{
+ {
+ Sha: "0eea75e8c631fba6b58135697835d58ba4c18dbc",
+ Name: "better typing for rebase mode",
+ Status: models.StatusUnpushed,
+ Action: models.ActionNone,
+ Tags: []string{},
+ ExtraInfo: "(HEAD -> better-tests)",
+ AuthorName: "Jesse Duffield",
+ AuthorEmail: "jessedduffield@gmail.com",
+ UnixTimestamp: 1640826609,
+ Parents: []string{
+ "b21997d6b4cbdf84b149",
+ },
+ },
+ },
+ expectedError: nil,
+ },
+ {
+ testName: "should not specify order if `log.order` is `default`",
+ logOrder: "default",
+ rebaseMode: enums.REBASE_MODE_NONE,
+ opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
runner: oscommands.NewFakeRunner(t).
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
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) {
expectedError: nil,
},
{
- testName: "should set filter path",
- logOrder: "default",
- rebaseMode: enums.REBASE_MODE_NONE,
- currentBranchName: "master",
- opts: GetCommitsOptions{RefName: "HEAD", FilterPath: "src"},
+ testName: "should set filter path",
+ logOrder: "default",
+ rebaseMode: enums.REBASE_MODE_NONE,
+ opts: GetCommitsOptions{RefName: "HEAD", FilterPath: "src"},
runner: oscommands.NewFakeRunner(t).
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
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) {
common.UserConfig.Git.Log.Order = scenario.logOrder
builder := &CommitLoader{
- Common: common,
- cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner),
- getCurrentBranchInfo: func() (BranchInfo, error) {
- return BranchInfo{RefName: scenario.currentBranchName, DisplayName: scenario.currentBranchName, DetachedHead: false}, nil
- },
+ Common: common,
+ cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner),
getRebaseMode: func() (enums.RebaseMode, error) { return scenario.rebaseMode, nil },
dotGitDir: ".git",
readFile: func(filename string) ([]byte, error) {
@@ -240,6 +309,7 @@ func TestGetCommits(t *testing.T) {
},
}
+ common.UserConfig.Git.MainBranches = scenario.mainBranches
commits, err := builder.GetCommits(scenario.opts)
assert.Equal(t, scenario.expectedCommits, commits)
diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go
index 82361ddbd..9212a2de8 100644
--- a/pkg/config/user_config.go
+++ b/pkg/config/user_config.go
@@ -76,6 +76,7 @@ type GitConfig struct {
Paging PagingConfig `yaml:"paging"`
Commit CommitConfig `yaml:"commit"`
Merging MergingConfig `yaml:"merging"`
+ MainBranches []string `yaml:"mainBranches"`
SkipHookPrefix string `yaml:"skipHookPrefix"`
AutoFetch bool `yaml:"autoFetch"`
AutoRefresh bool `yaml:"autoRefresh"`
@@ -443,6 +444,7 @@ func GetDefaultConfig() *UserConfig {
ShowWholeGraph: false,
},
SkipHookPrefix: "WIP",
+ MainBranches: []string{"master", "main"},
AutoFetch: true,
AutoRefresh: true,
BranchLogCmd: "git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --",