mirror of
https://github.com/jesseduffield/lazygit.git
synced 2024-12-02 09:21:40 +02:00
Add command to show divergence from upstream (#2871)
This commit is contained in:
commit
a63d5891e2
@ -6,6 +6,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@ -68,6 +69,8 @@ type GetCommitsOptions struct {
|
||||
RefForPushedStatus string // the ref to use for determining pushed/unpushed status
|
||||
// determines if we show the whole git graph i.e. pass the '--all' flag
|
||||
All bool
|
||||
// If non-empty, show divergence from this ref (left-right log)
|
||||
RefToShowDivergenceFrom string
|
||||
}
|
||||
|
||||
// GetCommits obtains the commits of the current branch
|
||||
@ -93,17 +96,21 @@ func (self *CommitLoader) GetCommits(opts GetCommitsOptions) ([]*models.Commit,
|
||||
defer wg.Done()
|
||||
|
||||
logErr = self.getLogCmd(opts).RunAndProcessLines(func(line string) (bool, error) {
|
||||
commit := self.extractCommitFromLine(line)
|
||||
commit := self.extractCommitFromLine(line, opts.RefToShowDivergenceFrom != "")
|
||||
commits = append(commits, commit)
|
||||
return false, nil
|
||||
})
|
||||
})
|
||||
|
||||
var ancestor string
|
||||
var remoteAncestor string
|
||||
go utils.Safe(func() {
|
||||
defer wg.Done()
|
||||
|
||||
ancestor = self.getMergeBase(opts.RefName)
|
||||
if opts.RefToShowDivergenceFrom != "" {
|
||||
remoteAncestor = self.getMergeBase(opts.RefToShowDivergenceFrom)
|
||||
}
|
||||
})
|
||||
|
||||
passedFirstPushedCommit := false
|
||||
@ -137,8 +144,23 @@ func (self *CommitLoader) GetCommits(opts GetCommitsOptions) ([]*models.Commit,
|
||||
return commits, nil
|
||||
}
|
||||
|
||||
if ancestor != "" {
|
||||
commits = setCommitMergedStatuses(ancestor, commits)
|
||||
if opts.RefToShowDivergenceFrom != "" {
|
||||
sort.SliceStable(commits, func(i, j int) bool {
|
||||
// In the divergence view we want incoming commits to come first
|
||||
return commits[i].Divergence > commits[j].Divergence
|
||||
})
|
||||
|
||||
_, localSectionStart, found := lo.FindIndexOf(commits, func(commit *models.Commit) bool {
|
||||
return commit.Divergence == models.DivergenceLeft
|
||||
})
|
||||
if !found {
|
||||
localSectionStart = len(commits)
|
||||
}
|
||||
|
||||
setCommitMergedStatuses(remoteAncestor, commits[:localSectionStart])
|
||||
setCommitMergedStatuses(ancestor, commits[localSectionStart:])
|
||||
} else {
|
||||
setCommitMergedStatuses(ancestor, commits)
|
||||
}
|
||||
|
||||
return commits, nil
|
||||
@ -179,8 +201,8 @@ func (self *CommitLoader) MergeRebasingCommits(commits []*models.Commit) ([]*mod
|
||||
// then puts them into a commit object
|
||||
// example input:
|
||||
// 8ad01fe32fcc20f07bc6693f87aa4977c327f1e1|10 hours ago|Jesse Duffield| (HEAD -> master, tag: v0.15.2)|refresh commits when adding a tag
|
||||
func (self *CommitLoader) extractCommitFromLine(line string) *models.Commit {
|
||||
split := strings.SplitN(line, "\x00", 7)
|
||||
func (self *CommitLoader) extractCommitFromLine(line string, showDivergence bool) *models.Commit {
|
||||
split := strings.SplitN(line, "\x00", 8)
|
||||
|
||||
sha := split[0]
|
||||
unixTimestamp := split[1]
|
||||
@ -189,6 +211,10 @@ func (self *CommitLoader) extractCommitFromLine(line string) *models.Commit {
|
||||
extraInfo := strings.TrimSpace(split[4])
|
||||
parentHashes := split[5]
|
||||
message := split[6]
|
||||
divergence := models.DivergenceNone
|
||||
if showDivergence {
|
||||
divergence = lo.Ternary(split[7] == "<", models.DivergenceLeft, models.DivergenceRight)
|
||||
}
|
||||
|
||||
tags := []string{}
|
||||
|
||||
@ -222,6 +248,7 @@ func (self *CommitLoader) extractCommitFromLine(line string) *models.Commit {
|
||||
AuthorName: authorName,
|
||||
AuthorEmail: authorEmail,
|
||||
Parents: parents,
|
||||
Divergence: divergence,
|
||||
}
|
||||
}
|
||||
|
||||
@ -251,7 +278,7 @@ func (self *CommitLoader) getHydratedRebasingCommits(rebaseMode enums.RebaseMode
|
||||
|
||||
fullCommits := map[string]*models.Commit{}
|
||||
err = cmdObj.RunAndProcessLines(func(line string) (bool, error) {
|
||||
commit := self.extractCommitFromLine(line)
|
||||
commit := self.extractCommitFromLine(line, false)
|
||||
fullCommits[commit.Sha] = commit
|
||||
return false, nil
|
||||
})
|
||||
@ -495,7 +522,11 @@ func (self *CommitLoader) commitFromPatch(content string) *models.Commit {
|
||||
}
|
||||
}
|
||||
|
||||
func setCommitMergedStatuses(ancestor string, commits []*models.Commit) []*models.Commit {
|
||||
func setCommitMergedStatuses(ancestor string, commits []*models.Commit) {
|
||||
if ancestor == "" {
|
||||
return
|
||||
}
|
||||
|
||||
passedAncestor := false
|
||||
for i, commit := range commits {
|
||||
// some commits aren't really commits and don't have sha's, such as the update-ref todo
|
||||
@ -509,7 +540,6 @@ func setCommitMergedStatuses(ancestor string, commits []*models.Commit) []*model
|
||||
commits[i].Status = models.StatusMerged
|
||||
}
|
||||
}
|
||||
return commits
|
||||
}
|
||||
|
||||
func (self *CommitLoader) getMergeBase(refName string) string {
|
||||
@ -622,8 +652,13 @@ func (self *CommitLoader) getFirstPushedCommit(refName string) (string, error) {
|
||||
func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj {
|
||||
config := self.UserConfig.Git.Log
|
||||
|
||||
refSpec := opts.RefName
|
||||
if opts.RefToShowDivergenceFrom != "" {
|
||||
refSpec += "..." + opts.RefToShowDivergenceFrom
|
||||
}
|
||||
|
||||
cmdArgs := NewGitCmd("log").
|
||||
Arg(opts.RefName).
|
||||
Arg(refSpec).
|
||||
ArgIf(config.Order != "default", "--"+config.Order).
|
||||
ArgIf(opts.All, "--all").
|
||||
Arg("--oneline").
|
||||
@ -632,6 +667,7 @@ func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj {
|
||||
ArgIf(opts.Limit, "-300").
|
||||
ArgIf(opts.FilterPath != "", "--follow").
|
||||
Arg("--no-show-signature").
|
||||
ArgIf(opts.RefToShowDivergenceFrom != "", "--left-right").
|
||||
Arg("--").
|
||||
ArgIf(opts.FilterPath != "", opts.FilterPath).
|
||||
ToArgv()
|
||||
@ -639,4 +675,4 @@ func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj {
|
||||
return self.cmd.New(cmdArgs).DontLog()
|
||||
}
|
||||
|
||||
const prettyFormat = `--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s`
|
||||
const prettyFormat = `--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m`
|
||||
|
@ -45,7 +45,7 @@ func TestGetCommits(t *testing.T) {
|
||||
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
ExpectGitArgs([]string{"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),
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
|
||||
|
||||
expectedCommits: []*models.Commit{},
|
||||
expectedError: nil,
|
||||
@ -57,7 +57,7 @@ func TestGetCommits(t *testing.T) {
|
||||
opts: GetCommitsOptions{RefName: "refs/heads/mybranch", RefForPushedStatus: "refs/heads/mybranch", IncludeRebaseCommits: false},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"merge-base", "refs/heads/mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
ExpectGitArgs([]string{"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),
|
||||
ExpectGitArgs([]string{"log", "refs/heads/mybranch", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
|
||||
|
||||
expectedCommits: []*models.Commit{},
|
||||
expectedError: nil,
|
||||
@ -72,7 +72,7 @@ func TestGetCommits(t *testing.T) {
|
||||
// here it's seeing which commits are yet to be pushed
|
||||
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
// here it's actually getting all the commits in a formatted form, one per line
|
||||
ExpectGitArgs([]string{"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).
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, commitsOutput, nil).
|
||||
// here it's testing which of the configured main branches have an upstream
|
||||
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "refs/remotes/origin/master", nil). // this one does
|
||||
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "", errors.New("error")). // this one doesn't, so it checks origin instead
|
||||
@ -209,7 +209,7 @@ func TestGetCommits(t *testing.T) {
|
||||
// here it's seeing which commits are yet to be pushed
|
||||
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
// here it's actually getting all the commits in a formatted form, one per line
|
||||
ExpectGitArgs([]string{"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).
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, singleCommitOutput, nil).
|
||||
// here it's testing which of the configured main branches exist; neither does
|
||||
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "", errors.New("error")).
|
||||
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/master"}, "", errors.New("error")).
|
||||
@ -246,7 +246,7 @@ func TestGetCommits(t *testing.T) {
|
||||
// here it's seeing which commits are yet to be pushed
|
||||
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
// here it's actually getting all the commits in a formatted form, one per line
|
||||
ExpectGitArgs([]string{"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).
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, singleCommitOutput, nil).
|
||||
// here it's testing which of the configured main branches exist
|
||||
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "refs/remotes/origin/master", nil).
|
||||
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "", errors.New("error")).
|
||||
@ -282,7 +282,7 @@ func TestGetCommits(t *testing.T) {
|
||||
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
|
||||
|
||||
expectedCommits: []*models.Commit{},
|
||||
expectedError: nil,
|
||||
@ -294,7 +294,7 @@ func TestGetCommits(t *testing.T) {
|
||||
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", FilterPath: "src"},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
ExpectGitArgs([]string{"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),
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--follow", "--no-show-signature", "--", "src"}, "", nil),
|
||||
|
||||
expectedCommits: []*models.Commit{},
|
||||
expectedError: nil,
|
||||
@ -548,7 +548,8 @@ func TestCommitLoader_setCommitMergedStatuses(t *testing.T) {
|
||||
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.testName, func(t *testing.T) {
|
||||
expectedCommits := setCommitMergedStatuses(scenario.ancestor, scenario.commits)
|
||||
expectedCommits := scenario.commits
|
||||
setCommitMergedStatuses(scenario.ancestor, expectedCommits)
|
||||
assert.Equal(t, scenario.expectedCommits, expectedCommits)
|
||||
})
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package models
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Branch : A git branch
|
||||
// duplicating this for now
|
||||
type Branch struct {
|
||||
@ -43,6 +45,22 @@ func (b *Branch) ParentRefName() string {
|
||||
return b.RefName() + "^"
|
||||
}
|
||||
|
||||
func (b *Branch) FullUpstreamRefName() string {
|
||||
if b.UpstreamRemote == "" || b.UpstreamBranch == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
return fmt.Sprintf("refs/remotes/%s/%s", b.UpstreamRemote, b.UpstreamBranch)
|
||||
}
|
||||
|
||||
func (b *Branch) ShortUpstreamRefName() string {
|
||||
if b.UpstreamRemote == "" || b.UpstreamBranch == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s", b.UpstreamRemote, b.UpstreamBranch)
|
||||
}
|
||||
|
||||
func (b *Branch) ID() string {
|
||||
return b.RefName()
|
||||
}
|
||||
|
@ -30,6 +30,17 @@ const (
|
||||
ActionConflict = todo.Comment + 1
|
||||
)
|
||||
|
||||
type Divergence int
|
||||
|
||||
// For a divergence log (left/right comparison of two refs) this is set to
|
||||
// either DivergenceLeft or DivergenceRight for each commit; for normal
|
||||
// commit views it is always DivergenceNone.
|
||||
const (
|
||||
DivergenceNone Divergence = iota
|
||||
DivergenceLeft
|
||||
DivergenceRight
|
||||
)
|
||||
|
||||
// Commit : A git commit
|
||||
type Commit struct {
|
||||
Sha string
|
||||
@ -41,6 +52,7 @@ type Commit struct {
|
||||
AuthorName string // something like 'Jesse Duffield'
|
||||
AuthorEmail string // something like 'jessedduffield@gmail.com'
|
||||
UnixTimestamp int64
|
||||
Divergence Divergence // set to DivergenceNone unless we are showing the divergence view
|
||||
|
||||
// SHAs of parent commits (will be multiple if it's a merge commit)
|
||||
Parents []string
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type SubCommitsContext struct {
|
||||
@ -73,12 +73,41 @@ func NewSubCommitsContext(
|
||||
selectedCommitSha,
|
||||
startIdx,
|
||||
endIdx,
|
||||
shouldShowGraph(c),
|
||||
// Don't show the graph in the left/right view; we'd like to, but
|
||||
// it's too complicated:
|
||||
shouldShowGraph(c) && viewModel.GetRefToShowDivergenceFrom() == "",
|
||||
git_commands.NewNullBisectInfo(),
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
getNonModelItems := func() []*NonModelItem {
|
||||
result := []*NonModelItem{}
|
||||
if viewModel.GetRefToShowDivergenceFrom() != "" {
|
||||
_, upstreamIdx, found := lo.FindIndexOf(
|
||||
c.Model().SubCommits, func(c *models.Commit) bool { return c.Divergence == models.DivergenceRight })
|
||||
if !found {
|
||||
upstreamIdx = 0
|
||||
}
|
||||
result = append(result, &NonModelItem{
|
||||
Index: upstreamIdx,
|
||||
Content: fmt.Sprintf("--- %s ---", c.Tr.DivergenceSectionHeaderRemote),
|
||||
})
|
||||
|
||||
_, localIdx, found := lo.FindIndexOf(
|
||||
c.Model().SubCommits, func(c *models.Commit) bool { return c.Divergence == models.DivergenceLeft })
|
||||
if !found {
|
||||
localIdx = len(c.Model().SubCommits)
|
||||
}
|
||||
result = append(result, &NonModelItem{
|
||||
Index: localIdx,
|
||||
Content: fmt.Sprintf("--- %s ---", c.Tr.DivergenceSectionHeaderLocal),
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
ctx := &SubCommitsContext{
|
||||
c: c,
|
||||
SubCommitsViewModel: viewModel,
|
||||
@ -96,6 +125,7 @@ func NewSubCommitsContext(
|
||||
ListRenderer: ListRenderer{
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
getNonModelItems: getNonModelItems,
|
||||
},
|
||||
c: c,
|
||||
refreshViewportOnChange: true,
|
||||
@ -112,7 +142,8 @@ func NewSubCommitsContext(
|
||||
|
||||
type SubCommitsViewModel struct {
|
||||
// name of the ref that the sub-commits are shown for
|
||||
ref types.Ref
|
||||
ref types.Ref
|
||||
refToShowDivergenceFrom string
|
||||
*ListViewModel[*models.Commit]
|
||||
|
||||
limitCommits bool
|
||||
@ -127,6 +158,14 @@ func (self *SubCommitsViewModel) GetRef() types.Ref {
|
||||
return self.ref
|
||||
}
|
||||
|
||||
func (self *SubCommitsViewModel) SetRefToShowDivergenceFrom(ref string) {
|
||||
self.refToShowDivergenceFrom = ref
|
||||
}
|
||||
|
||||
func (self *SubCommitsViewModel) GetRefToShowDivergenceFrom() string {
|
||||
return self.refToShowDivergenceFrom
|
||||
}
|
||||
|
||||
func (self *SubCommitsViewModel) SetShowBranchHeads(value bool) {
|
||||
self.showBranchHeads = value
|
||||
}
|
||||
@ -160,10 +199,6 @@ func (self *SubCommitsContext) GetCommits() []*models.Commit {
|
||||
return self.getModel()
|
||||
}
|
||||
|
||||
func (self *SubCommitsContext) Title() string {
|
||||
return fmt.Sprintf(self.c.Tr.SubCommitsDynamicTitle, utils.TruncateWithEllipsis(self.ref.RefName(), 50))
|
||||
}
|
||||
|
||||
func (self *SubCommitsContext) SetLimitCommits(value bool) {
|
||||
self.limitCommits = value
|
||||
}
|
||||
|
@ -76,6 +76,13 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
helperCommon,
|
||||
func() *status.StatusManager { return gui.statusManager },
|
||||
)
|
||||
|
||||
setSubCommits := func(commits []*models.Commit) {
|
||||
gui.Mutexes.SubCommitsMutex.Lock()
|
||||
defer gui.Mutexes.SubCommitsMutex.Unlock()
|
||||
|
||||
gui.State.Model.SubCommits = commits
|
||||
}
|
||||
gui.helpers = &helpers.Helpers{
|
||||
Refs: refsHelper,
|
||||
Host: helpers.NewHostHelper(helperCommon),
|
||||
@ -111,8 +118,9 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
modeHelper,
|
||||
appStatusHelper,
|
||||
),
|
||||
Search: helpers.NewSearchHelper(helperCommon),
|
||||
Worktree: worktreeHelper,
|
||||
Search: helpers.NewSearchHelper(helperCommon),
|
||||
Worktree: worktreeHelper,
|
||||
SubCommits: helpers.NewSubCommitsHelper(helperCommon, refreshHelper, setSubCommits),
|
||||
}
|
||||
|
||||
gui.CustomCommandsClient = custom_commands.NewClient(
|
||||
@ -206,13 +214,6 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
controllers.AttachControllers(context, sideWindowControllerFactory.Create(context))
|
||||
}
|
||||
|
||||
setSubCommits := func(commits []*models.Commit) {
|
||||
gui.Mutexes.SubCommitsMutex.Lock()
|
||||
defer gui.Mutexes.SubCommitsMutex.Unlock()
|
||||
|
||||
gui.State.Model.SubCommits = commits
|
||||
}
|
||||
|
||||
for _, context := range []controllers.CanSwitchToSubCommits{
|
||||
gui.State.Contexts.Branches,
|
||||
gui.State.Contexts.RemoteBranches,
|
||||
@ -220,7 +221,7 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
gui.State.Contexts.ReflogCommits,
|
||||
} {
|
||||
controllers.AttachControllers(context, controllers.NewSwitchToSubCommitsController(
|
||||
common, setSubCommits, context,
|
||||
common, context,
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
@ -141,6 +142,27 @@ func (self *BranchesController) setUpstream(selectedBranch *models.Branch) error
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: self.c.Tr.Actions.SetUnsetUpstream,
|
||||
Items: []*types.MenuItem{
|
||||
{
|
||||
LabelColumns: []string{self.c.Tr.ViewDivergenceFromUpstream},
|
||||
OnPress: func() error {
|
||||
branch := self.context().GetSelected()
|
||||
if branch == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !branch.RemoteBranchStoredLocally() {
|
||||
return self.c.ErrorMsg(self.c.Tr.DivergenceNoUpstream)
|
||||
}
|
||||
return self.c.Helpers().SubCommits.ViewSubCommits(helpers.ViewSubCommitsOpts{
|
||||
Ref: branch,
|
||||
TitleRef: fmt.Sprintf("%s <-> %s", branch.RefName(), branch.ShortUpstreamRefName()),
|
||||
RefToShowDivergenceFrom: branch.FullUpstreamRefName(),
|
||||
Context: self.context(),
|
||||
ShowBranchHeads: false,
|
||||
})
|
||||
},
|
||||
Key: 'v',
|
||||
},
|
||||
{
|
||||
LabelColumns: []string{self.c.Tr.UnsetUpstream},
|
||||
OnPress: func() error {
|
||||
|
@ -49,6 +49,7 @@ type Helpers struct {
|
||||
WindowArrangement *WindowArrangementHelper
|
||||
Search *SearchHelper
|
||||
Worktree *WorktreeHelper
|
||||
SubCommits *SubCommitsHelper
|
||||
}
|
||||
|
||||
func NewStubHelpers() *Helpers {
|
||||
@ -83,5 +84,6 @@ func NewStubHelpers() *Helpers {
|
||||
WindowArrangement: &WindowArrangementHelper{},
|
||||
Search: &SearchHelper{},
|
||||
Worktree: &WorktreeHelper{},
|
||||
SubCommits: &SubCommitsHelper{},
|
||||
}
|
||||
}
|
||||
|
@ -334,11 +334,12 @@ func (self *RefreshHelper) refreshSubCommitsWithLimit() error {
|
||||
|
||||
commits, err := self.c.Git().Loaders.CommitLoader.GetCommits(
|
||||
git_commands.GetCommitsOptions{
|
||||
Limit: self.c.Contexts().SubCommits.GetLimitCommits(),
|
||||
FilterPath: self.c.Modes().Filtering.GetPath(),
|
||||
IncludeRebaseCommits: false,
|
||||
RefName: self.c.Contexts().SubCommits.GetRef().FullRefName(),
|
||||
RefForPushedStatus: self.c.Contexts().SubCommits.GetRef().FullRefName(),
|
||||
Limit: self.c.Contexts().SubCommits.GetLimitCommits(),
|
||||
FilterPath: self.c.Modes().Filtering.GetPath(),
|
||||
IncludeRebaseCommits: false,
|
||||
RefName: self.c.Contexts().SubCommits.GetRef().FullRefName(),
|
||||
RefToShowDivergenceFrom: self.c.Contexts().SubCommits.GetRefToShowDivergenceFrom(),
|
||||
RefForPushedStatus: self.c.Contexts().SubCommits.GetRef().FullRefName(),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
|
73
pkg/gui/controllers/helpers/sub_commits_helper.go
Normal file
73
pkg/gui/controllers/helpers/sub_commits_helper.go
Normal file
@ -0,0 +1,73 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
type SubCommitsHelper struct {
|
||||
c *HelperCommon
|
||||
|
||||
refreshHelper *RefreshHelper
|
||||
setSubCommits func([]*models.Commit)
|
||||
}
|
||||
|
||||
func NewSubCommitsHelper(
|
||||
c *HelperCommon,
|
||||
refreshHelper *RefreshHelper,
|
||||
setSubCommits func([]*models.Commit),
|
||||
) *SubCommitsHelper {
|
||||
return &SubCommitsHelper{
|
||||
c: c,
|
||||
refreshHelper: refreshHelper,
|
||||
setSubCommits: setSubCommits,
|
||||
}
|
||||
}
|
||||
|
||||
type ViewSubCommitsOpts struct {
|
||||
Ref types.Ref
|
||||
RefToShowDivergenceFrom string
|
||||
TitleRef string
|
||||
Context types.Context
|
||||
ShowBranchHeads bool
|
||||
}
|
||||
|
||||
func (self *SubCommitsHelper) ViewSubCommits(opts ViewSubCommitsOpts) error {
|
||||
commits, err := self.c.Git().Loaders.CommitLoader.GetCommits(
|
||||
git_commands.GetCommitsOptions{
|
||||
Limit: true,
|
||||
FilterPath: self.c.Modes().Filtering.GetPath(),
|
||||
IncludeRebaseCommits: false,
|
||||
RefName: opts.Ref.FullRefName(),
|
||||
RefForPushedStatus: opts.Ref.FullRefName(),
|
||||
RefToShowDivergenceFrom: opts.RefToShowDivergenceFrom,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.setSubCommits(commits)
|
||||
self.refreshHelper.RefreshAuthors(commits)
|
||||
|
||||
subCommitsContext := self.c.Contexts().SubCommits
|
||||
subCommitsContext.SetSelectedLineIdx(0)
|
||||
subCommitsContext.SetParentContext(opts.Context)
|
||||
subCommitsContext.SetWindowName(opts.Context.GetWindowName())
|
||||
subCommitsContext.SetTitleRef(utils.TruncateWithEllipsis(opts.TitleRef, 50))
|
||||
subCommitsContext.SetRef(opts.Ref)
|
||||
subCommitsContext.SetRefToShowDivergenceFrom(opts.RefToShowDivergenceFrom)
|
||||
subCommitsContext.SetLimitCommits(true)
|
||||
subCommitsContext.SetShowBranchHeads(opts.ShowBranchHeads)
|
||||
subCommitsContext.ClearSearchString()
|
||||
subCommitsContext.GetView().ClearSearch()
|
||||
|
||||
err = self.c.PostRefreshUpdate(self.c.Contexts().SubCommits)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.c.PushContext(self.c.Contexts().SubCommits)
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@ -18,20 +17,16 @@ type SwitchToSubCommitsController struct {
|
||||
baseController
|
||||
c *ControllerCommon
|
||||
context CanSwitchToSubCommits
|
||||
|
||||
setSubCommits func([]*models.Commit)
|
||||
}
|
||||
|
||||
func NewSwitchToSubCommitsController(
|
||||
controllerCommon *ControllerCommon,
|
||||
setSubCommits func([]*models.Commit),
|
||||
context CanSwitchToSubCommits,
|
||||
) *SwitchToSubCommitsController {
|
||||
return &SwitchToSubCommitsController{
|
||||
baseController: baseController{},
|
||||
c: controllerCommon,
|
||||
context: context,
|
||||
setSubCommits: setSubCommits,
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,40 +52,12 @@ func (self *SwitchToSubCommitsController) viewCommits() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// need to populate my sub commits
|
||||
commits, err := self.c.Git().Loaders.CommitLoader.GetCommits(
|
||||
git_commands.GetCommitsOptions{
|
||||
Limit: true,
|
||||
FilterPath: self.c.Modes().Filtering.GetPath(),
|
||||
IncludeRebaseCommits: false,
|
||||
RefName: ref.FullRefName(),
|
||||
RefForPushedStatus: ref.FullRefName(),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.setSubCommits(commits)
|
||||
self.c.Helpers().Refresh.RefreshAuthors(commits)
|
||||
|
||||
subCommitsContext := self.c.Contexts().SubCommits
|
||||
subCommitsContext.SetSelectedLineIdx(0)
|
||||
subCommitsContext.SetParentContext(self.context)
|
||||
subCommitsContext.SetWindowName(self.context.GetWindowName())
|
||||
subCommitsContext.SetTitleRef(ref.Description())
|
||||
subCommitsContext.SetRef(ref)
|
||||
subCommitsContext.SetLimitCommits(true)
|
||||
subCommitsContext.SetShowBranchHeads(self.context.ShowBranchHeadsInSubCommits())
|
||||
subCommitsContext.ClearSearchString()
|
||||
subCommitsContext.GetView().ClearSearch()
|
||||
|
||||
err = self.c.PostRefreshUpdate(self.c.Contexts().SubCommits)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.c.PushContext(self.c.Contexts().SubCommits)
|
||||
return self.c.Helpers().SubCommits.ViewSubCommits(helpers.ViewSubCommitsOpts{
|
||||
Ref: ref,
|
||||
TitleRef: ref.RefName(),
|
||||
Context: self.context,
|
||||
ShowBranchHeads: self.context.ShowBranchHeadsInSubCommits(),
|
||||
})
|
||||
}
|
||||
|
||||
func (self *SwitchToSubCommitsController) Context() types.Context {
|
||||
|
@ -359,7 +359,9 @@ func displayCommit(
|
||||
}
|
||||
|
||||
cols := make([]string, 0, 7)
|
||||
if icons.IsIconEnabled() {
|
||||
if commit.Divergence != models.DivergenceNone {
|
||||
cols = append(cols, shaColor.Sprint(lo.Ternary(commit.Divergence == models.DivergenceLeft, "↑", "↓")))
|
||||
} else if icons.IsIconEnabled() {
|
||||
cols = append(cols, shaColor.Sprint(icons.IconForCommit(commit)))
|
||||
}
|
||||
cols = append(cols, shaColor.Sprint(commit.ShortSha()))
|
||||
@ -430,6 +432,8 @@ func getShaColor(
|
||||
shaColor = theme.DiffTerminalColor
|
||||
} else if cherryPickedCommitShaSet.Includes(commit.Sha) {
|
||||
shaColor = theme.CherryPickedCommitTextStyle
|
||||
} else if commit.Divergence == models.DivergenceRight && commit.Status != models.StatusMerged {
|
||||
shaColor = style.FgBlue
|
||||
}
|
||||
|
||||
return shaColor
|
||||
|
@ -348,6 +348,10 @@ type TranslationSet struct {
|
||||
SetAsUpstream string
|
||||
SetUpstream string
|
||||
UnsetUpstream string
|
||||
ViewDivergenceFromUpstream string
|
||||
DivergenceNoUpstream string
|
||||
DivergenceSectionHeaderLocal string
|
||||
DivergenceSectionHeaderRemote string
|
||||
SetUpstreamTitle string
|
||||
SetUpstreamMessage string
|
||||
EditRemote string
|
||||
@ -1128,6 +1132,10 @@ func EnglishTranslationSet() TranslationSet {
|
||||
SetAsUpstream: "Set as upstream of checked-out branch",
|
||||
SetUpstream: "Set upstream of selected branch",
|
||||
UnsetUpstream: "Unset upstream of selected branch",
|
||||
ViewDivergenceFromUpstream: "View divergence from upstream",
|
||||
DivergenceNoUpstream: "Cannot show divergence of a branch that has no (locally tracked) upstream",
|
||||
DivergenceSectionHeaderLocal: "Local",
|
||||
DivergenceSectionHeaderRemote: "Remote",
|
||||
SetUpstreamTitle: "Set upstream branch",
|
||||
SetUpstreamMessage: "Are you sure you want to set the upstream branch of '{{.checkedOut}}' to '{{.selected}}'",
|
||||
EditRemote: "Edit remote",
|
||||
|
@ -39,6 +39,18 @@ func (self *TextMatcher) DoesNotContain(target string) *TextMatcher {
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *TextMatcher) DoesNotContainAnyOf(targets []string) *TextMatcher {
|
||||
self.appendRule(matcherRule[string]{
|
||||
name: fmt.Sprintf("does not contain any of '%s'", targets),
|
||||
testFn: func(value string) (bool, string) {
|
||||
return lo.NoneBy(targets, func(target string) bool { return strings.Contains(value, target) }),
|
||||
fmt.Sprintf("Expected none of '%s' to be found in '%s'", targets, value)
|
||||
},
|
||||
})
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *TextMatcher) MatchesRegexp(target string) *TextMatcher {
|
||||
self.appendRule(matcherRule[string]{
|
||||
name: fmt.Sprintf("matches regular expression '%s'", target),
|
||||
@ -107,6 +119,10 @@ func DoesNotContain(target string) *TextMatcher {
|
||||
return AnyString().DoesNotContain(target)
|
||||
}
|
||||
|
||||
func DoesNotContainAnyOf(targets ...string) *TextMatcher {
|
||||
return AnyString().DoesNotContainAnyOf(targets)
|
||||
}
|
||||
|
||||
func MatchesRegexp(target string) *TextMatcher {
|
||||
return AnyString().MatchesRegexp(target)
|
||||
}
|
||||
|
@ -0,0 +1,54 @@
|
||||
package branch
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var ShowDivergenceFromUpstream = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Show divergence from upstream",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateFileAndAdd("file", "content1")
|
||||
shell.Commit("one")
|
||||
shell.UpdateFileAndAdd("file", "content2")
|
||||
shell.Commit("two")
|
||||
shell.CreateFileAndAdd("file3", "content3")
|
||||
shell.Commit("three")
|
||||
|
||||
shell.CloneIntoRemote("origin")
|
||||
|
||||
shell.SetBranchUpstream("master", "origin/master")
|
||||
|
||||
shell.HardReset("HEAD^^")
|
||||
shell.CreateFileAndAdd("file4", "content4")
|
||||
shell.Commit("four")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Lines(
|
||||
Contains("four"),
|
||||
Contains("one"),
|
||||
)
|
||||
|
||||
t.Views().Branches().
|
||||
Focus().
|
||||
Lines(Contains("master")).
|
||||
Press(keys.Branches.SetUpstream)
|
||||
|
||||
t.ExpectPopup().Menu().Title(Contains("upstream")).Select(Contains("View divergence from upstream")).Confirm()
|
||||
|
||||
t.Views().SubCommits().
|
||||
IsFocused().
|
||||
Title(Contains("Commits (master <-> origin/master)")).
|
||||
Lines(
|
||||
DoesNotContainAnyOf("↓", "↑").Contains("--- Remote ---"),
|
||||
Contains("↓").Contains("three"),
|
||||
Contains("↓").Contains("two"),
|
||||
DoesNotContainAnyOf("↓", "↑").Contains("--- Local ---"),
|
||||
Contains("↑").Contains("four"),
|
||||
)
|
||||
},
|
||||
})
|
@ -49,6 +49,7 @@ var tests = []*components.IntegrationTest{
|
||||
branch.Reset,
|
||||
branch.ResetUpstream,
|
||||
branch.SetUpstream,
|
||||
branch.ShowDivergenceFromUpstream,
|
||||
branch.Suggestions,
|
||||
cherry_pick.CherryPick,
|
||||
cherry_pick.CherryPickConflicts,
|
||||
|
Loading…
Reference in New Issue
Block a user