1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-06-02 23:27:32 +02:00

Support range selection for reverting commits

This commit is contained in:
Stefan Haller 2025-03-30 13:16:07 +02:00
parent 1c91999f61
commit 945b023eb5
6 changed files with 71 additions and 65 deletions

View File

@ -285,14 +285,14 @@ func (self *CommitCommands) ShowFileContentCmdObj(hash string, filePath string)
return self.cmd.New(cmdArgs).DontLog() return self.cmd.New(cmdArgs).DontLog()
} }
// Revert reverts the selected commit by hash. If isMerge is true, we'll pass -m 1 // Revert reverts the selected commits by hash. If isMerge is true, we'll pass -m 1
// to say we want to revert the first parent of the merge commit, which is the one // to say we want to revert the first parent of the merge commit, which is the one
// people want in 99.9% of cases. In current git versions we could unconditionally // people want in 99.9% of cases. In current git versions we could unconditionally
// pass -m 1 even for non-merge commits, but older versions of git choke on it. // pass -m 1 even for non-merge commits, but older versions of git choke on it.
func (self *CommitCommands) Revert(hash string, isMerge bool) error { func (self *CommitCommands) Revert(hashes []string, isMerge bool) error {
cmdArgs := NewGitCmd("revert"). cmdArgs := NewGitCmd("revert").
Arg(hash).
ArgIf(isMerge, "-m", "1"). ArgIf(isMerge, "-m", "1").
Arg(hashes...).
ToArgv() ToArgv()
return self.cmd.New(cmdArgs).Run() return self.cmd.New(cmdArgs).Run()

View File

@ -248,8 +248,8 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
}, },
{ {
Key: opts.GetKey(opts.Config.Commits.RevertCommit), Key: opts.GetKey(opts.Config.Commits.RevertCommit),
Handler: self.withItem(self.revert), Handler: self.withItemsRange(self.revert),
GetDisabledReason: self.require(self.singleItemSelected()), GetDisabledReason: self.require(self.itemRangeSelected()),
Description: self.c.Tr.Revert, Description: self.c.Tr.Revert,
Tooltip: self.c.Tr.RevertCommitTooltip, Tooltip: self.c.Tr.RevertCommitTooltip,
}, },
@ -857,22 +857,31 @@ func (self *LocalCommitsController) addCoAuthor(start, end int) error {
return nil return nil
} }
func (self *LocalCommitsController) revert(commit *models.Commit) error { func (self *LocalCommitsController) revert(commits []*models.Commit, start, end int) error {
self.c.Confirm(types.ConfirmOpts{ var promptText string
Title: self.c.Tr.Actions.RevertCommit, if len(commits) == 1 {
Prompt: utils.ResolvePlaceholderString( promptText = utils.ResolvePlaceholderString(
self.c.Tr.ConfirmRevertCommit, self.c.Tr.ConfirmRevertCommit,
map[string]string{ map[string]string{
"selectedCommit": commit.ShortHash(), "selectedCommit": commits[0].ShortHash(),
}), })
} else {
promptText = self.c.Tr.ConfirmRevertCommitRange
}
hashes := lo.Map(commits, func(c *models.Commit, _ int) string { return c.Hash })
isMerge := lo.SomeBy(commits, func(c *models.Commit) bool { return c.IsMerge() })
self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.Actions.RevertCommit,
Prompt: promptText,
HandleConfirm: func() error { HandleConfirm: func() error {
self.c.LogAction(self.c.Tr.Actions.RevertCommit) self.c.LogAction(self.c.Tr.Actions.RevertCommit)
return self.c.WithWaitingStatusSync(self.c.Tr.RevertingStatus, func() error { return self.c.WithWaitingStatusSync(self.c.Tr.RevertingStatus, func() error {
result := self.c.Git().Commit.Revert(commit.Hash, commit.IsMerge()) result := self.c.Git().Commit.Revert(hashes, isMerge)
if err := self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(result); err != nil { if err := self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(result); err != nil {
return err return err
} }
self.context().MoveSelection(1) self.context().MoveSelection(len(commits))
return self.c.Refresh(types.RefreshOptions{ return self.c.Refresh(types.RefreshOptions{
Mode: types.SYNC, Scope: []types.RefreshableView{types.COMMITS, types.BRANCHES}, Mode: types.SYNC, Scope: []types.RefreshableView{types.COMMITS, types.BRANCHES},
}) })

View File

@ -771,6 +771,7 @@ type TranslationSet struct {
OpenCommitInBrowser string OpenCommitInBrowser string
ViewBisectOptions string ViewBisectOptions string
ConfirmRevertCommit string ConfirmRevertCommit string
ConfirmRevertCommitRange string
RewordInEditorTitle string RewordInEditorTitle string
RewordInEditorPrompt string RewordInEditorPrompt string
CheckoutAutostashPrompt string CheckoutAutostashPrompt string
@ -1837,6 +1838,7 @@ func EnglishTranslationSet() *TranslationSet {
OpenCommitInBrowser: "Open commit in browser", OpenCommitInBrowser: "Open commit in browser",
ViewBisectOptions: "View bisect options", ViewBisectOptions: "View bisect options",
ConfirmRevertCommit: "Are you sure you want to revert {{.selectedCommit}}?", ConfirmRevertCommit: "Are you sure you want to revert {{.selectedCommit}}?",
ConfirmRevertCommitRange: "Are you sure you want to revert the selected commits?",
RewordInEditorTitle: "Reword in editor", RewordInEditorTitle: "Reword in editor",
RewordInEditorPrompt: "Are you sure you want to reword this commit in your editor?", RewordInEditorPrompt: "Are you sure you want to reword this commit in your editor?",
HardResetAutostashPrompt: "Are you sure you want to hard reset to '%s'? An auto-stash will be performed if necessary.", HardResetAutostashPrompt: "Are you sure you want to hard reset to '%s'? An auto-stash will be performed if necessary.",

View File

@ -9,16 +9,7 @@ var RevertWithConflictMultipleCommits = NewIntegrationTest(NewIntegrationTestArg
Description: "Reverts a range of commits, the first of which conflicts", Description: "Reverts a range of commits, the first of which conflicts",
ExtraCmdArgs: []string{}, ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(cfg *config.AppConfig) { SetupConfig: func(cfg *config.AppConfig) {},
// TODO: use our revert UI once we support range-select for reverts
cfg.GetUserConfig().CustomCommands = []config.CustomCommand{
{
Key: "X",
Context: "commits",
Command: "git -c core.editor=: revert HEAD^ HEAD^^",
},
}
},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {
shell.CreateFileAndAdd("myfile", "") shell.CreateFileAndAdd("myfile", "")
shell.Commit("add empty file") shell.Commit("add empty file")
@ -38,13 +29,18 @@ var RevertWithConflictMultipleCommits = NewIntegrationTest(NewIntegrationTestArg
Contains("CI ◯ unrelated change"), Contains("CI ◯ unrelated change"),
Contains("CI ◯ add empty file"), Contains("CI ◯ add empty file"),
). ).
Press("X"). SelectNextItem().
Press(keys.Universal.RangeSelectDown).
Press(keys.Commits.RevertCommit).
Tap(func() { Tap(func() {
t.ExpectPopup().Alert(). t.ExpectPopup().Confirmation().
Title(Equals("Error")). Title(Equals("Revert commit")).
// The exact error message is different on different git versions, Content(Equals("Are you sure you want to revert the selected commits?")).
// but they all contain the word 'conflict' somewhere. Confirm()
Content(Contains("conflict")).
t.ExpectPopup().Menu().
Title(Equals("Conflicts!")).
Select(Contains("View conflicts")).
Confirm() Confirm()
}). }).
Lines( Lines(
@ -59,7 +55,7 @@ var RevertWithConflictMultipleCommits = NewIntegrationTest(NewIntegrationTestArg
t.Views().Options().Content(Contains("View revert options: m")) t.Views().Options().Content(Contains("View revert options: m"))
t.Views().Information().Content(Contains("Reverting (Reset)")) t.Views().Information().Content(Contains("Reverting (Reset)"))
t.Views().Files().Focus(). t.Views().Files().IsFocused().
Lines( Lines(
Contains("UU myfile").IsSelected(), Contains("UU myfile").IsSelected(),
). ).

View File

@ -9,18 +9,10 @@ var RevertDuringRebaseWhenStoppedOnEdit = NewIntegrationTest(NewIntegrationTestA
Description: "Revert a series of commits while stopped in a rebase", Description: "Revert a series of commits while stopped in a rebase",
ExtraCmdArgs: []string{}, ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(cfg *config.AppConfig) { SetupConfig: func(cfg *config.AppConfig) {},
// TODO: use our revert UI once we support range-select for reverts
cfg.GetUserConfig().CustomCommands = []config.CustomCommand{
{
Key: "X",
Context: "commits",
Command: "git -c core.editor=: revert HEAD^ HEAD^^",
},
}
},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {
shell.EmptyCommit("master commit") shell.EmptyCommit("master commit 1")
shell.EmptyCommit("master commit 2")
shell.NewBranch("branch") shell.NewBranch("branch")
shell.CreateNCommits(4) shell.CreateNCommits(4)
}, },
@ -32,7 +24,8 @@ var RevertDuringRebaseWhenStoppedOnEdit = NewIntegrationTest(NewIntegrationTestA
Contains("commit 03"), Contains("commit 03"),
Contains("commit 02"), Contains("commit 02"),
Contains("commit 01"), Contains("commit 01"),
Contains("master commit"), Contains("master commit 2"),
Contains("master commit 1"),
). ).
NavigateToLine(Contains("commit 03")). NavigateToLine(Contains("commit 03")).
Press(keys.Universal.Edit). Press(keys.Universal.Edit).
@ -41,17 +34,27 @@ var RevertDuringRebaseWhenStoppedOnEdit = NewIntegrationTest(NewIntegrationTestA
Contains("<-- YOU ARE HERE --- commit 03").IsSelected(), Contains("<-- YOU ARE HERE --- commit 03").IsSelected(),
Contains("commit 02"), Contains("commit 02"),
Contains("commit 01"), Contains("commit 01"),
Contains("master commit"), Contains("master commit 2"),
Contains("master commit 1"),
). ).
Press("X"). SelectNextItem().
Press(keys.Universal.RangeSelectDown).
Press(keys.Commits.RevertCommit).
Tap(func() {
t.ExpectPopup().Confirmation().
Title(Equals("Revert commit")).
Content(MatchesRegexp(`Are you sure you want to revert \w+?`)).
Confirm()
}).
Lines( Lines(
Contains("pick").Contains("commit 04"), Contains("pick").Contains("commit 04"),
Contains(`<-- YOU ARE HERE --- Revert "commit 01"`).IsSelected(), Contains(`<-- YOU ARE HERE --- Revert "commit 01"`),
Contains(`Revert "commit 02"`), Contains(`Revert "commit 02"`),
Contains("commit 03"), Contains("commit 03"),
Contains("commit 02"), Contains("commit 02").IsSelected(),
Contains("commit 01"), Contains("commit 01").IsSelected(),
Contains("master commit"), Contains("master commit 2"),
Contains("master commit 1"),
) )
}, },
}) })

View File

@ -9,16 +9,7 @@ var RevertMultipleCommitsInInteractiveRebase = NewIntegrationTest(NewIntegration
Description: "Reverts a range of commits, the first of which conflicts, in the middle of an interactive rebase", Description: "Reverts a range of commits, the first of which conflicts, in the middle of an interactive rebase",
ExtraCmdArgs: []string{}, ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(cfg *config.AppConfig) { SetupConfig: func(cfg *config.AppConfig) {},
// TODO: use our revert UI once we support range-select for reverts
cfg.GetUserConfig().CustomCommands = []config.CustomCommand{
{
Key: "X",
Context: "commits",
Command: "git -c core.editor=: revert HEAD^ HEAD^^",
},
}
},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {
shell.CreateFileAndAdd("myfile", "") shell.CreateFileAndAdd("myfile", "")
shell.Commit("add empty file") shell.Commit("add empty file")
@ -44,13 +35,18 @@ var RevertMultipleCommitsInInteractiveRebase = NewIntegrationTest(NewIntegration
). ).
NavigateToLine(Contains("add second line")). NavigateToLine(Contains("add second line")).
Press(keys.Universal.Edit). Press(keys.Universal.Edit).
Press("X"). SelectNextItem().
Press(keys.Universal.RangeSelectDown).
Press(keys.Commits.RevertCommit).
Tap(func() { Tap(func() {
t.ExpectPopup().Alert(). t.ExpectPopup().Confirmation().
Title(Equals("Error")). Title(Equals("Revert commit")).
// The exact error message is different on different git versions, Content(Equals("Are you sure you want to revert the selected commits?")).
// but they all contain the word 'conflict' somewhere. Confirm()
Content(Contains("conflict")).
t.ExpectPopup().Menu().
Title(Equals("Conflicts!")).
Select(Contains("View conflicts")).
Confirm() Confirm()
}). }).
Lines( Lines(
@ -67,7 +63,7 @@ var RevertMultipleCommitsInInteractiveRebase = NewIntegrationTest(NewIntegration
t.Views().Options().Content(Contains("View revert options: m")) t.Views().Options().Content(Contains("View revert options: m"))
t.Views().Information().Content(Contains("Reverting (Reset)")) t.Views().Information().Content(Contains("Reverting (Reset)"))
t.Views().Files().Focus(). t.Views().Files().IsFocused().
Lines( Lines(
Contains("UU myfile").IsSelected(), Contains("UU myfile").IsSelected(),
). ).