summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2023-10-05 19:25:58 +1100
committerJesse Duffield <jessedduffield@gmail.com>2023-10-11 15:51:58 +1100
commit017367f9402c9c8513dda7cf1129866af77b6bcf (patch)
tree590086f741c8bfea8eade50b78fe386bc2fb0ec8
parent16369dc3c2c2a422373fff8edfb25a8bf289c9db (diff)
Show commit merged/push statuses against branchesshow-merged-status
When viewing branches in an enlargened window, we now colour the commit shas based on whether the branch has been merged to a main branch and whether it's been pushed to the upstream. This isn't using the exact same code as we use for commits, so it's possible we'll get a colour mismatch when the user checks out a branch and views the commits in the commits view, but if we come across that situation we'll just need to fix it.
-rw-r--r--pkg/commands/git.go3
-rw-r--r--pkg/commands/git_commands/merged_branch_loader.go59
-rw-r--r--pkg/gui/context/branches_context.go1
-rw-r--r--pkg/gui/controllers/helpers/refresh_helper.go48
-rw-r--r--pkg/gui/gui.go2
-rw-r--r--pkg/gui/presentation/branches.go15
-rw-r--r--pkg/gui/presentation/commits.go33
-rw-r--r--pkg/gui/types/common.go21
8 files changed, 142 insertions, 40 deletions
diff --git a/pkg/commands/git.go b/pkg/commands/git.go
index 510661034..1710e547b 100644
--- a/pkg/commands/git.go
+++ b/pkg/commands/git.go
@@ -47,6 +47,7 @@ type GitCommand struct {
type Loaders struct {
BranchLoader *git_commands.BranchLoader
+ MergedBranchLoader *git_commands.MergedBranchLoader
CommitFileLoader *git_commands.CommitFileLoader
CommitLoader *git_commands.CommitLoader
FileLoader *git_commands.FileLoader
@@ -162,6 +163,7 @@ func NewGitCommandAux(
worktreeCommands := git_commands.NewWorktreeCommands(gitCommon)
branchLoader := git_commands.NewBranchLoader(cmn, cmd, branchCommands.CurrentBranchInfo, configCommands)
+ mergedBranchLoader := git_commands.NewMergedBranchLoader(gitCommon)
commitFileLoader := git_commands.NewCommitFileLoader(cmn, cmd)
commitLoader := git_commands.NewCommitLoader(cmn, cmd, statusCommands.RebaseMode, gitCommon)
reflogCommitLoader := git_commands.NewReflogCommitLoader(cmn, cmd)
@@ -200,6 +202,7 @@ func NewGitCommandAux(
Worktrees: worktreeLoader,
StashLoader: stashLoader,
TagLoader: tagLoader,
+ MergedBranchLoader: mergedBranchLoader,
},
RepoPaths: repoPaths,
}
diff --git a/pkg/commands/git_commands/merged_branch_loader.go b/pkg/commands/git_commands/merged_branch_loader.go
new file mode 100644
index 000000000..a3b4843b6
--- /dev/null
+++ b/pkg/commands/git_commands/merged_branch_loader.go
@@ -0,0 +1,59 @@
+package git_commands
+
+import (
+ "github.com/jesseduffield/generics/set"
+ "github.com/jesseduffield/lazygit/pkg/utils"
+)
+
+// Returns a set of branch names which are merged into a main branch.
+// This
+type MergedBranchLoader struct {
+ c *GitCommon
+}
+
+func NewMergedBranchLoader(c *GitCommon) *MergedBranchLoader {
+ return &MergedBranchLoader{c: c}
+}
+
+// TODO: check against upstreams, and share code with commit loader that determines main branches
+func (self *MergedBranchLoader) Load() *set.Set[string] {
+ set := set.New[string]()
+
+ mainBranches := self.c.UserConfig.Git.MainBranches
+ results := utils.ConcurrentMap(mainBranches, func(mainBranch string) []string {
+ mergedBranches, err := self.GetMergedBranches(mainBranch)
+ if err != nil {
+ self.c.Log.Warnf("Failed to get merged branches for %s: %s", mainBranch, err)
+ return nil
+ }
+ return mergedBranches
+ })
+
+ for i := 0; i < len(mainBranches); i++ {
+ for _, mergedBranch := range results[i] {
+ set.Add(mergedBranch)
+ }
+ }
+
+ return set
+}
+
+func (self *MergedBranchLoader) GetMergedBranches(mainBranch string) ([]string, error) {
+ // git for-each-ref --merged master --format '%(refname)' refs/heads/
+ cmdArgs := NewGitCmd("for-each-ref").Arg(
+ "--merged",
+ mainBranch,
+ "--format",
+ "%(refname)",
+ "refs/heads/",
+ ).ToArgv()
+
+ output, err := self.c.cmd.New(cmdArgs).DontLog().RunWithOutput()
+ if err != nil {
+ return nil, err
+ }
+
+ branches := utils.SplitLines(output)
+
+ return branches, nil
+}
diff --git a/pkg/gui/context/branches_context.go b/pkg/gui/context/branches_context.go
index 68324d020..0ec9fb5cf 100644
--- a/pkg/gui/context/branches_context.go
+++ b/pkg/gui/context/branches_context.go
@@ -33,6 +33,7 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext {
c.Tr,
c.UserConfig,
c.Model().Worktrees,
+ c.Model().MergedBranches,
)
}
diff --git a/pkg/gui/controllers/helpers/refresh_helper.go b/pkg/gui/controllers/helpers/refresh_helper.go
index dc43844e2..8927e3610 100644
--- a/pkg/gui/controllers/helpers/refresh_helper.go
+++ b/pkg/gui/controllers/helpers/refresh_helper.go
@@ -429,25 +429,43 @@ func (self *RefreshHelper) refreshBranches(refreshWorktrees bool) {
self.c.Mutexes().RefreshingBranchesMutex.Lock()
defer self.c.Mutexes().RefreshingBranchesMutex.Unlock()
- reflogCommits := self.c.Model().FilteredReflogCommits
- if self.c.Modes().Filtering.Active() {
- // in filter mode we filter our reflog commits to just those containing the path
- // however we need all the reflog entries to populate the recencies of our branches
- // which allows us to order them correctly. So if we're filtering we'll just
- // manually load all the reflog commits here
- var err error
- reflogCommits, _, err = self.c.Git().Loaders.ReflogCommitLoader.GetReflogCommits(nil, "")
+ var wg sync.WaitGroup
+
+ wg.Add(2)
+
+ go func() {
+ defer wg.Done()
+
+ reflogCommits := self.c.Model().FilteredReflogCommits
+ if self.c.Modes().Filtering.Active() {
+ // in filter mode we filter our reflog commits to just those containing the path
+ // however we need all the reflog entries to populate the recencies of our branches
+ // which allows us to order them correctly. So if we're filtering we'll just
+ // manually load all the reflog commits here
+ var err error
+ reflogCommits, _, err = self.c.Git().Loaders.ReflogCommitLoader.GetReflogCommits(nil, "")
+ if err != nil {
+ self.c.Log.Error(err)
+ }
+ }
+
+ branches, err := self.c.Git().Loaders.BranchLoader.Load(reflogCommits)
if err != nil {
- self.c.Log.Error(err)
+ _ = self.c.Error(err)
}
- }
- branches, err := self.c.Git().Loaders.BranchLoader.Load(reflogCommits)
- if err != nil {
- _ = self.c.Error(err)
- }
+ self.c.Model().Branches = branches
+ }()
+
+ go func() {
+ defer wg.Done()
+
+ mergedBranches := self.c.Git().Loaders.MergedBranchLoader.Load()
+
+ self.c.Model().MergedBranches = mergedBranches
+ }()
- self.c.Model().Branches = branches
+ wg.Wait()
if refreshWorktrees {
self.loadWorktrees()
diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go
index 787bdd169..55d78adb2 100644
--- a/pkg/gui/gui.go
+++ b/pkg/gui/gui.go
@@ -8,6 +8,7 @@ import (
"strings"
"sync"
+ "github.com/jesseduffield/generics/set"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazycore/pkg/boxlayout"
appTypes "github.com/jesseduffield/lazygit/pkg/app/types"
@@ -384,6 +385,7 @@ func (gui *Gui) resetState(startArgs appTypes.StartArgs) types.Context {
BisectInfo: git_commands.NewNullBisectInfo(),
FilesTrie: patricia.NewTrie(),
Authors: map[string]*models.Author{},
+ MergedBranches: set.New[string](),
},
Modes: &types.Modes{
Filtering: filtering.New(startArgs.FilterPath),
diff --git a/pkg/gui/presentation/branches.go b/pkg/gui/presentation/branches.go
index bd027ca62..dbbb7aa8e 100644
--- a/pkg/gui/presentation/branches.go
+++ b/pkg/gui/presentation/branches.go
@@ -4,6 +4,7 @@ import (
"fmt"
"strings"
+ "github.com/jesseduffield/generics/set"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/config"
@@ -26,10 +27,11 @@ func GetBranchListDisplayStrings(
tr *i18n.TranslationSet,
userConfig *config.UserConfig,
worktrees []*models.Worktree,
+ mergedBranches *set.Set[string],
) [][]string {
return lo.Map(branches, func(branch *models.Branch, _ int) []string {
diffed := branch.Name == diffName
- return getBranchDisplayStrings(branch, getItemOperation(branch), fullDescription, diffed, tr, userConfig, worktrees)
+ return getBranchDisplayStrings(branch, getItemOperation(branch), fullDescription, diffed, tr, userConfig, worktrees, mergedBranches)
})
}
@@ -42,6 +44,7 @@ func getBranchDisplayStrings(
tr *i18n.TranslationSet,
userConfig *config.UserConfig,
worktrees []*models.Worktree,
+ mergedBranches *set.Set[string],
) []string {
displayName := b.Name
if b.DisplayName != "" {
@@ -74,7 +77,15 @@ func getBranchDisplayStrings(
}
if fullDescription || userConfig.Gui.ShowBranchCommitHash {
- res = append(res, utils.ShortSha(b.CommitHash))
+ var commitStyle style.TextStyle
+ if b.HasCommitsToPush() {
+ commitStyle = getShaStyleFromStatus(models.StatusUnpushed)
+ } else if mergedBranches.Includes(b.FullRefName()) {
+ commitStyle = getShaStyleFromStatus(models.StatusMerged)
+ } else {
+ commitStyle = getShaStyleFromStatus(models.StatusPushed)
+ }
+ res = append(res, commitStyle.Sprint(utils.ShortSha(b.CommitHash)))
}
res = append(res, coloredName)
diff --git a/pkg/gui/presentation/commits.go b/pkg/gui/presentation/commits.go
index b4297f6ed..93f9d6d94 100644
--- a/pkg/gui/presentation/commits.go
+++ b/pkg/gui/presentation/commits.go
@@ -413,20 +413,8 @@ func getShaColor(
}
diffed := commit.Sha != "" && commit.Sha == diffName
- shaColor := theme.DefaultTextColor
- switch commit.Status {
- case models.StatusUnpushed:
- shaColor = style.FgRed
- case models.StatusPushed:
- shaColor = style.FgYellow
- case models.StatusMerged:
- shaColor = style.FgGreen
- case models.StatusRebasing:
- shaColor = style.FgBlue
- case models.StatusReflog:
- shaColor = style.FgBlue
- default:
- }
+
+ shaColor := getShaStyleFromStatus(commit.Status)
if diffed {
shaColor = theme.DiffTerminalColor
@@ -439,6 +427,23 @@ func getShaColor(
return shaColor
}
+func getShaStyleFromStatus(status models.CommitStatus) style.TextStyle {
+ switch status {
+ case models.StatusUnpushed:
+ return style.FgRed
+ case models.StatusPushed:
+ return style.FgYellow
+ case models.StatusMerged:
+ return style.FgGreen
+ case models.StatusRebasing:
+ return style.FgBlue
+ case models.StatusReflog:
+ return style.FgBlue
+ default:
+ return theme.DefaultTextColor
+ }
+}
+
func actionColorMap(action todo.TodoCommand) style.TextStyle {
switch action {
case todo.Pick:
diff --git a/pkg/gui/types/common.go b/pkg/gui/types/common.go
index 84ad874f3..41438b0ea 100644
--- a/pkg/gui/types/common.go
+++ b/pkg/gui/types/common.go
@@ -1,6 +1,7 @@
package types
import (
+ "github.com/jesseduffield/generics/set"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
@@ -223,15 +224,17 @@ type MenuItem struct {
}
type Model struct {
- CommitFiles []*models.CommitFile
- Files []*models.File
- Submodules []*models.SubmoduleConfig
- Branches []*models.Branch
- Commits []*models.Commit
- StashEntries []*models.StashEntry
- SubCommits []*models.Commit
- Remotes []*models.Remote
- Worktrees []*models.Worktree
+ CommitFiles []*models.CommitFile
+ Files []*models.File
+ Submodules []*models.SubmoduleConfig
+ Branches []*models.Branch
+ // set of branch names of the form 'refs/heads/mybranch'
+ MergedBranches *set.Set[string]
+ Commits []*models.Commit
+ StashEntries []*models.StashEntry
+ SubCommits []*models.Commit
+ Remotes []*models.Remote
+ Worktrees []*models.Worktree
// FilteredReflogCommits are the ones that appear in the reflog panel.
// when in filtering mode we only include the ones that match the given path