diff options
author | Jesse Duffield <jessedduffield@gmail.com> | 2023-10-05 19:25:58 +1100 |
---|---|---|
committer | Jesse Duffield <jessedduffield@gmail.com> | 2023-10-11 15:51:58 +1100 |
commit | 017367f9402c9c8513dda7cf1129866af77b6bcf (patch) | |
tree | 590086f741c8bfea8eade50b78fe386bc2fb0ec8 | |
parent | 16369dc3c2c2a422373fff8edfb25a8bf289c9db (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.go | 3 | ||||
-rw-r--r-- | pkg/commands/git_commands/merged_branch_loader.go | 59 | ||||
-rw-r--r-- | pkg/gui/context/branches_context.go | 1 | ||||
-rw-r--r-- | pkg/gui/controllers/helpers/refresh_helper.go | 48 | ||||
-rw-r--r-- | pkg/gui/gui.go | 2 | ||||
-rw-r--r-- | pkg/gui/presentation/branches.go | 15 | ||||
-rw-r--r-- | pkg/gui/presentation/commits.go | 33 | ||||
-rw-r--r-- | pkg/gui/types/common.go | 21 |
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 |