Show original todo action instead of "conflict", and show <-- CONFLICT instead

It is useful to see if the conflicted commit was a "pick" or an "edit". What's
more, we're about to add support for showing cherry-picks and reverts, and
seeing that a conflicted commit was a revert is important because its diff is
backwards compared to the diff of the conflicting files in the Files panel.
This commit is contained in:
Stefan Haller 2024-06-13 16:55:20 +02:00
parent 9c8f987934
commit ff465e2581
19 changed files with 81 additions and 63 deletions

View file

@ -322,13 +322,8 @@ func (self *CommitLoader) getRebasingCommits() []*models.Commit {
// See if the current commit couldn't be applied because it conflicted; if
// so, add a fake entry for it
if conflictedCommitHash := self.getConflictedCommit(todos); conflictedCommitHash != "" {
commits = append(commits, &models.Commit{
Hash: conflictedCommitHash,
Name: "",
Status: models.StatusRebasing,
Action: models.ActionConflict,
})
if conflictedCommit := self.getConflictedCommit(todos); conflictedCommit != nil {
commits = append(commits, conflictedCommit)
}
for _, t := range todos {
@ -351,17 +346,17 @@ func (self *CommitLoader) getRebasingCommits() []*models.Commit {
return commits
}
func (self *CommitLoader) getConflictedCommit(todos []todo.Todo) string {
func (self *CommitLoader) getConflictedCommit(todos []todo.Todo) *models.Commit {
bytesContent, err := self.readFile(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/done"))
if err != nil {
self.Log.Error(fmt.Sprintf("error occurred reading rebase-merge/done: %s", err.Error()))
return ""
return nil
}
doneTodos, err := todo.Parse(bytes.NewBuffer(bytesContent), self.config.GetCoreCommentChar())
if err != nil {
self.Log.Error(fmt.Sprintf("error occurred while parsing rebase-merge/done file: %s", err.Error()))
return ""
return nil
}
amendFileExists := false
@ -372,15 +367,15 @@ func (self *CommitLoader) getConflictedCommit(todos []todo.Todo) string {
return self.getConflictedCommitImpl(todos, doneTodos, amendFileExists)
}
func (self *CommitLoader) getConflictedCommitImpl(todos []todo.Todo, doneTodos []todo.Todo, amendFileExists bool) string {
func (self *CommitLoader) getConflictedCommitImpl(todos []todo.Todo, doneTodos []todo.Todo, amendFileExists bool) *models.Commit {
// Should never be possible, but just to be safe:
if len(doneTodos) == 0 {
self.Log.Error("no done entries in rebase-merge/done file")
return ""
return nil
}
lastTodo := doneTodos[len(doneTodos)-1]
if lastTodo.Command == todo.Break || lastTodo.Command == todo.Exec || lastTodo.Command == todo.Reword {
return ""
return nil
}
// In certain cases, git reschedules commands that failed. One example is if
@ -391,7 +386,7 @@ func (self *CommitLoader) getConflictedCommitImpl(todos []todo.Todo, doneTodos [
// same, the command was rescheduled.
if len(doneTodos) > 0 && len(todos) > 0 && doneTodos[len(doneTodos)-1] == todos[0] {
// Command was rescheduled, no need to display it
return ""
return nil
}
// Older versions of git have a bug whereby, if a command is rescheduled,
@ -416,26 +411,30 @@ func (self *CommitLoader) getConflictedCommitImpl(todos []todo.Todo, doneTodos [
if len(doneTodos) >= 3 && len(todos) > 0 && doneTodos[len(doneTodos)-2] == todos[0] &&
doneTodos[len(doneTodos)-1] == doneTodos[len(doneTodos)-3] {
// Command was rescheduled, no need to display it
return ""
return nil
}
if lastTodo.Command == todo.Edit {
if amendFileExists {
// Special case for "edit": if the "amend" file exists, the "edit"
// command was successful, otherwise it wasn't
return ""
return nil
}
}
// I don't think this is ever possible, but again, just to be safe:
if lastTodo.Commit == "" {
self.Log.Error("last command in rebase-merge/done file doesn't have a commit")
return ""
return nil
}
// Any other todo that has a commit associated with it must have failed with
// a conflict, otherwise we wouldn't have stopped the rebase:
return lastTodo.Commit
return &models.Commit{
Hash: lastTodo.Commit,
Action: lastTodo.Command,
Status: models.StatusConflicted,
}
}
func setCommitMergedStatuses(ancestor string, commits []*models.Commit) {

View file

@ -332,14 +332,14 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
todos []todo.Todo
doneTodos []todo.Todo
amendFileExists bool
expectedHash string
expectedResult *models.Commit
}{
{
testName: "no done todos",
todos: []todo.Todo{},
doneTodos: []todo.Todo{},
amendFileExists: false,
expectedHash: "",
expectedResult: nil,
},
{
testName: "common case (conflict)",
@ -355,7 +355,11 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
},
},
amendFileExists: false,
expectedHash: "fa1afe1",
expectedResult: &models.Commit{
Hash: "fa1afe1",
Action: todo.Pick,
Status: models.StatusConflicted,
},
},
{
testName: "last command was 'break'",
@ -364,7 +368,7 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
{Command: todo.Break},
},
amendFileExists: false,
expectedHash: "",
expectedResult: nil,
},
{
testName: "last command was 'exec'",
@ -376,7 +380,7 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
},
},
amendFileExists: false,
expectedHash: "",
expectedResult: nil,
},
{
testName: "last command was 'reword'",
@ -385,7 +389,7 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
{Command: todo.Reword},
},
amendFileExists: false,
expectedHash: "",
expectedResult: nil,
},
{
testName: "'pick' was rescheduled",
@ -402,7 +406,7 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
},
},
amendFileExists: false,
expectedHash: "",
expectedResult: nil,
},
{
testName: "'pick' was rescheduled, buggy git version",
@ -427,7 +431,7 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
},
},
amendFileExists: false,
expectedHash: "",
expectedResult: nil,
},
{
testName: "conflicting 'pick' after 'exec'",
@ -452,7 +456,11 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
},
},
amendFileExists: false,
expectedHash: "fa1afe1",
expectedResult: &models.Commit{
Hash: "fa1afe1",
Action: todo.Pick,
Status: models.StatusConflicted,
},
},
{
testName: "'edit' with amend file",
@ -464,7 +472,7 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
},
},
amendFileExists: true,
expectedHash: "",
expectedResult: nil,
},
{
testName: "'edit' without amend file",
@ -476,7 +484,11 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
},
},
amendFileExists: false,
expectedHash: "fa1afe1",
expectedResult: &models.Commit{
Hash: "fa1afe1",
Action: todo.Edit,
Status: models.StatusConflicted,
},
},
}
for _, scenario := range scenarios {
@ -497,7 +509,7 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
}
hash := builder.getConflictedCommitImpl(scenario.todos, scenario.doneTodos, scenario.amendFileExists)
assert.Equal(t, scenario.expectedHash, hash)
assert.Equal(t, scenario.expectedResult, hash)
})
}
}

View file

@ -18,6 +18,7 @@ const (
StatusPushed
StatusMerged
StatusRebasing
StatusConflicted
StatusReflog
)
@ -25,8 +26,6 @@ const (
// Conveniently for us, the todo package starts the enum at 1, and given
// that it doesn't have a "none" value, we're setting ours to 0
ActionNone todo.TodoCommand = 0
// "Comment" is the last one of the todo package's enum entries
ActionConflict = todo.Comment + 1
)
type Divergence int

View file

@ -829,12 +829,12 @@ func (self *FilesController) handleAmendCommitPress() error {
func (self *FilesController) isResolvingConflicts() bool {
commits := self.c.Model().Commits
for _, c := range commits {
if c.Status == models.StatusConflicted {
return true
}
if !c.IsTODO() {
break
}
if c.Action == models.ActionConflict {
return true
}
}
return false
}

View file

@ -1389,7 +1389,7 @@ func (self *LocalCommitsController) canMoveDown(selectedCommits []*models.Commit
if self.isRebasing() {
commits := self.c.Model().Commits
if !commits[endIdx+1].IsTODO() || commits[endIdx+1].Action == models.ActionConflict {
if !commits[endIdx+1].IsTODO() || commits[endIdx+1].Status == models.StatusConflicted {
return &types.DisabledReason{Text: self.c.Tr.CannotMoveAnyFurther}
}
}
@ -1405,7 +1405,7 @@ func (self *LocalCommitsController) canMoveUp(selectedCommits []*models.Commit,
if self.isRebasing() {
commits := self.c.Model().Commits
if !commits[startIdx-1].IsTODO() || commits[startIdx-1].Action == models.ActionConflict {
if !commits[startIdx-1].IsTODO() || commits[startIdx-1].Status == models.StatusConflicted {
return &types.DisabledReason{Text: self.c.Tr.CannotMoveAnyFurther}
}
}

View file

@ -186,7 +186,7 @@ func GetCommitListDisplayStrings(
unfilteredIdx := i + startIdx
bisectStatus = getBisectStatus(unfilteredIdx, commit.Hash, bisectInfo, bisectBounds)
isYouAreHereCommit := false
if showYouAreHereLabel && (commit.Action == models.ActionConflict || unfilteredIdx == rebaseOffset) {
if showYouAreHereLabel && (commit.Status == models.StatusConflicted || unfilteredIdx == rebaseOffset) {
isYouAreHereCommit = true
showYouAreHereLabel = false
}
@ -395,8 +395,7 @@ func displayCommit(
actionString := ""
if commit.Action != models.ActionNone {
todoString := lo.Ternary(commit.Action == models.ActionConflict, "conflict", commit.Action.String())
actionString = actionColorMap(commit.Action).Sprint(todoString) + " "
actionString = actionColorMap(commit.Action, commit.Status).Sprint(commit.Action.String()) + " "
}
tagString := ""
@ -429,8 +428,13 @@ func displayCommit(
mark := ""
if isYouAreHereCommit {
color := lo.Ternary(commit.Action == models.ActionConflict, style.FgRed, style.FgYellow)
youAreHere := color.Sprintf("<-- %s ---", common.Tr.YouAreHere)
color := style.FgYellow
text := common.Tr.YouAreHere
if commit.Status == models.StatusConflicted {
color = style.FgRed
text = common.Tr.ConflictLabel
}
youAreHere := color.Sprintf("<-- %s ---", text)
mark = fmt.Sprintf("%s ", youAreHere)
} else if isMarkedBaseCommit {
rebaseFromHere := style.FgYellow.Sprint(common.Tr.MarkedCommitMarker)
@ -501,7 +505,7 @@ func getHashColor(
hashColor = style.FgYellow
case models.StatusMerged:
hashColor = style.FgGreen
case models.StatusRebasing:
case models.StatusRebasing, models.StatusConflicted:
hashColor = style.FgBlue
case models.StatusReflog:
hashColor = style.FgBlue
@ -519,7 +523,11 @@ func getHashColor(
return hashColor
}
func actionColorMap(action todo.TodoCommand) style.TextStyle {
func actionColorMap(action todo.TodoCommand, status models.CommitStatus) style.TextStyle {
if status == models.StatusConflicted {
return style.FgRed
}
switch action {
case todo.Pick:
return style.FgCyan
@ -529,8 +537,6 @@ func actionColorMap(action todo.TodoCommand) style.TextStyle {
return style.FgGreen
case todo.Fixup:
return style.FgMagenta
case models.ActionConflict:
return style.FgRed
default:
return style.FgYellow
}

View file

@ -349,6 +349,7 @@ type TranslationSet struct {
ErrorOccurred string
NoRoom string
YouAreHere string
ConflictLabel string
YouDied string
RewordNotSupported string
ChangingThisActionIsNotAllowed string
@ -1416,6 +1417,7 @@ func EnglishTranslationSet() *TranslationSet {
ErrorOccurred: "An error occurred! Please create an issue at",
NoRoom: "Not enough room",
YouAreHere: "YOU ARE HERE",
ConflictLabel: "CONFLICT",
YouDied: "YOU DIED!",
RewordNotSupported: "Rewording commits while interactively rebasing is not currently supported",
ChangingThisActionIsNotAllowed: "Changing this kind of rebase todo entry is not allowed",

View file

@ -53,7 +53,7 @@ var RebaseAndDrop = NewIntegrationTest(NewIntegrationTestArgs{
TopLines(
MatchesRegexp(`pick.*to keep`).IsSelected(),
MatchesRegexp(`pick.*to remove`),
MatchesRegexp(`conflict.*YOU ARE HERE.*first change`),
MatchesRegexp(`pick.*CONFLICT.*first change`),
MatchesRegexp("second-change-branch unrelated change"),
MatchesRegexp("second change"),
MatchesRegexp("original"),
@ -63,7 +63,7 @@ var RebaseAndDrop = NewIntegrationTest(NewIntegrationTestArgs{
TopLines(
MatchesRegexp(`pick.*to keep`),
MatchesRegexp(`drop.*to remove`).IsSelected(),
MatchesRegexp(`conflict.*YOU ARE HERE.*first change`),
MatchesRegexp(`pick.*CONFLICT.*first change`),
MatchesRegexp("second-change-branch unrelated change"),
MatchesRegexp("second change"),
MatchesRegexp("original"),

View file

@ -30,7 +30,7 @@ var AmendWhenThereAreConflictsAndAmend = NewIntegrationTest(NewIntegrationTestAr
Focus().
Lines(
Contains("pick").Contains("commit three"),
Contains("conflict").Contains("<-- YOU ARE HERE --- file1 changed in branch"),
Contains("pick").Contains("<-- CONFLICT --- file1 changed in branch"),
Contains("commit two"),
Contains("file1 changed in master"),
Contains("base commit"),

View file

@ -34,7 +34,7 @@ var AmendWhenThereAreConflictsAndCancel = NewIntegrationTest(NewIntegrationTestA
Focus().
Lines(
Contains("pick").Contains("commit three"),
Contains("conflict").Contains("<-- YOU ARE HERE --- file1 changed in branch"),
Contains("pick").Contains("<-- CONFLICT --- file1 changed in branch"),
Contains("commit two"),
Contains("file1 changed in master"),
Contains("base commit"),

View file

@ -44,7 +44,7 @@ func doTheRebaseForAmendTests(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Commits().
Lines(
Contains("pick").Contains("commit three"),
Contains("conflict").Contains("<-- YOU ARE HERE --- file1 changed in branch"),
Contains("pick").Contains("<-- CONFLICT --- file1 changed in branch"),
Contains("commit two"),
Contains("file1 changed in master"),
Contains("base commit"),

View file

@ -35,7 +35,7 @@ var AmendCommitWithConflict = NewIntegrationTest(NewIntegrationTestArgs{
}).
Lines(
Contains("pick").Contains("three"),
Contains("conflict").Contains("<-- YOU ARE HERE --- fixup! two"),
Contains("fixup").Contains("<-- CONFLICT --- fixup! two"),
Contains("two"),
Contains("one"),
)
@ -66,7 +66,7 @@ var AmendCommitWithConflict = NewIntegrationTest(NewIntegrationTestArgs{
t.Views().Commits().
Lines(
Contains("<-- YOU ARE HERE --- three"),
Contains("<-- CONFLICT --- three"),
Contains("two"),
Contains("one"),
)

View file

@ -33,10 +33,10 @@ var EditTheConflCommit = NewIntegrationTest(NewIntegrationTestArgs{
Focus().
Lines(
Contains("pick").Contains("commit two"),
Contains("conflict").Contains("<-- YOU ARE HERE --- commit three"),
Contains("pick").Contains("<-- CONFLICT --- commit three"),
Contains("commit one"),
).
NavigateToLine(Contains("<-- YOU ARE HERE --- commit three")).
NavigateToLine(Contains("<-- CONFLICT --- commit three")).
Press(keys.Commits.RenameCommit)
t.ExpectToast(Contains("Disabled: Rewording commits while interactively rebasing is not currently supported"))

View file

@ -4,13 +4,13 @@ import (
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
func handleConflictsFromSwap(t *TestDriver) {
func handleConflictsFromSwap(t *TestDriver, expectedCommand string) {
t.Common().AcknowledgeConflicts()
t.Views().Commits().
Lines(
Contains("pick").Contains("commit two"),
Contains("conflict").Contains("<-- YOU ARE HERE --- commit three"),
Contains(expectedCommand).Contains("<-- CONFLICT --- commit three"),
Contains("commit one"),
)

View file

@ -44,6 +44,6 @@ var SwapInRebaseWithConflict = NewIntegrationTest(NewIntegrationTestArgs{
t.Common().ContinueRebase()
})
handleConflictsFromSwap(t)
handleConflictsFromSwap(t, "pick")
},
})

View file

@ -47,6 +47,6 @@ var SwapInRebaseWithConflictAndEdit = NewIntegrationTest(NewIntegrationTestArgs{
t.Common().ContinueRebase()
})
handleConflictsFromSwap(t)
handleConflictsFromSwap(t, "edit")
},
})

View file

@ -28,6 +28,6 @@ var SwapWithConflict = NewIntegrationTest(NewIntegrationTestArgs{
).
Press(keys.Commits.MoveDownCommit)
handleConflictsFromSwap(t)
handleConflictsFromSwap(t, "pick")
},
})

View file

@ -49,7 +49,7 @@ var PullRebaseInteractiveConflict = NewIntegrationTest(NewIntegrationTestArgs{
t.Views().Commits().
Lines(
Contains("pick").Contains("five"),
Contains("conflict").Contains("YOU ARE HERE").Contains("four"),
Contains("pick").Contains("CONFLICT").Contains("four"),
Contains("three"),
Contains("two"),
Contains("one"),

View file

@ -50,7 +50,7 @@ var PullRebaseInteractiveConflictDrop = NewIntegrationTest(NewIntegrationTestArg
Focus().
Lines(
Contains("pick").Contains("five").IsSelected(),
Contains("conflict").Contains("YOU ARE HERE").Contains("four"),
Contains("pick").Contains("CONFLICT").Contains("four"),
Contains("three"),
Contains("two"),
Contains("one"),
@ -58,7 +58,7 @@ var PullRebaseInteractiveConflictDrop = NewIntegrationTest(NewIntegrationTestArg
Press(keys.Universal.Remove).
Lines(
Contains("drop").Contains("five").IsSelected(),
Contains("conflict").Contains("YOU ARE HERE").Contains("four"),
Contains("pick").Contains("CONFLICT").Contains("four"),
Contains("three"),
Contains("two"),
Contains("one"),