From 3bf833aea72559ae5e2a67dd44325953e20624d5 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 28 Jan 2026 09:35:00 +0100 Subject: [PATCH 1/5] Remove the "old cherry-pick key" warning It's been ages since we changed the key, users should hopefully be used to it by now, and we want to reuse the key for something else later in the branch. Co-authored-by: Stefan Haller --- .../controllers/basic_commits_controller.go | 19 ------------------- pkg/i18n/english.go | 2 -- 2 files changed, 21 deletions(-) diff --git a/pkg/gui/controllers/basic_commits_controller.go b/pkg/gui/controllers/basic_commits_controller.go index b3a5bf38c..be856a9e9 100644 --- a/pkg/gui/controllers/basic_commits_controller.go +++ b/pkg/gui/controllers/basic_commits_controller.go @@ -1,7 +1,6 @@ package controllers import ( - "errors" "fmt" "strings" @@ -130,14 +129,6 @@ func (self *BasicCommitsController) GetKeybindings(opts types.KeybindingsOpts) [ GetDisabledReason: self.require(self.canSelectCommitsOfCurrentBranch), Description: self.c.Tr.SelectCommitsOfCurrentBranch, }, - // Putting this at the bottom of the list so that it has the lowest priority, - // meaning that if the user has configured another keybinding to the same key - // then that will take precedence. - { - // Hardcoding this key because it's not configurable - Key: opts.GetKey("c"), - Handler: self.handleOldCherryPickKey, - }, } return bindings @@ -382,16 +373,6 @@ func (self *BasicCommitsController) canCopyCommits(selectedCommits []*models.Com return nil } -func (self *BasicCommitsController) handleOldCherryPickKey() error { - msg := utils.ResolvePlaceholderString(self.c.Tr.OldCherryPickKeyWarning, - map[string]string{ - "copy": keybindings.Label(self.c.UserConfig().Keybinding.Commits.CherryPickCopy), - "paste": keybindings.Label(self.c.UserConfig().Keybinding.Commits.PasteCommits), - }) - - return errors.New(msg) -} - func (self *BasicCommitsController) openDiffTool(commit *models.Commit) error { to := commit.RefName() from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(commit.ParentRefName()) diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 970afd498..1be4898b1 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -897,7 +897,6 @@ type TranslationSet struct { SelectedItemIsNotABranch string SelectedItemDoesNotHaveFiles string MultiSelectNotSupportedForSubmodules string - OldCherryPickKeyWarning string CommandDoesNotSupportOpeningInEditor string CustomCommands string NoApplicableCommandsInThisContext string @@ -1993,7 +1992,6 @@ func EnglishTranslationSet() *TranslationSet { SelectedItemIsNotABranch: "Selected item is not a branch", SelectedItemDoesNotHaveFiles: "Selected item does not have files to view", MultiSelectNotSupportedForSubmodules: "Multiselection not supported for submodules", - OldCherryPickKeyWarning: "The 'c' key is no longer the default key for copying commits to cherry pick. Please use `{{.copy}}` instead (and `{{.paste}}` to paste). The reason for this change is that the 'v' key for selecting a range of lines when staging is now also used for selecting a range of lines in any list view, meaning that we needed to find a new key for pasting commits, and if we're going to now use `{{.paste}}` for pasting commits, we may as well use `{{.copy}}` for copying them. If you want to configure the keybindings to get the old behaviour, set the following in your config:\n\nkeybinding:\n universal:\n toggleRangeSelect: \n commits:\n cherryPickCopy: 'c'\n pasteCommits: 'v'", CommandDoesNotSupportOpeningInEditor: "This command doesn't support switching to the editor", CustomCommands: "Custom commands", NoApplicableCommandsInThisContext: "(No applicable commands in this context)", From de98258e394c595ba36137309efe2d4290089cad Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 28 Jan 2026 09:29:45 +0100 Subject: [PATCH 2/5] Add ActionFlag field to Commit Co-authored-by: Stefan Haller --- pkg/commands/git_commands/commit_loader.go | 10 ++++++---- pkg/commands/models/commit.go | 3 +++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/pkg/commands/git_commands/commit_loader.go b/pkg/commands/git_commands/commit_loader.go index da4bcb7ff..8b79bd8cd 100644 --- a/pkg/commands/git_commands/commit_loader.go +++ b/pkg/commands/git_commands/commit_loader.go @@ -325,6 +325,7 @@ func (self *CommitLoader) getHydratedTodoCommits(hashPool *utils.StringPool, tod hydratedCommits = append(hydratedCommits, rebasingCommit) } else if commit := findFullCommit(rebasingCommit.Hash()); commit != nil { commit.Action = rebasingCommit.Action + commit.ActionFlag = rebasingCommit.ActionFlag commit.Status = rebasingCommit.Status hydratedCommits = append(hydratedCommits, commit) } @@ -371,10 +372,11 @@ func (self *CommitLoader) getRebasingCommits(hashPool *utils.StringPool, addConf continue } commits = utils.Prepend(commits, models.NewCommit(hashPool, models.NewCommitOpts{ - Hash: t.Commit, - Name: t.Msg, - Status: models.StatusRebasing, - Action: t.Command, + Hash: t.Commit, + Name: t.Msg, + Status: models.StatusRebasing, + Action: t.Command, + ActionFlag: t.Flag, })) } diff --git a/pkg/commands/models/commit.go b/pkg/commands/models/commit.go index 8dfec4d4b..137528ee6 100644 --- a/pkg/commands/models/commit.go +++ b/pkg/commands/models/commit.go @@ -60,6 +60,7 @@ type Commit struct { Status CommitStatus Action todo.TodoCommand + ActionFlag string // e.g. "-C" for fixup -C Divergence Divergence // set to DivergenceNone unless we are showing the divergence view } @@ -68,6 +69,7 @@ type NewCommitOpts struct { Name string Status CommitStatus Action todo.TodoCommand + ActionFlag string Tags []string ExtraInfo string AuthorName string @@ -83,6 +85,7 @@ func NewCommit(hashPool *utils.StringPool, opts NewCommitOpts) *Commit { Name: opts.Name, Status: opts.Status, Action: opts.Action, + ActionFlag: opts.ActionFlag, Tags: opts.Tags, ExtraInfo: opts.ExtraInfo, AuthorName: opts.AuthorName, From 99e26be3d9753dc28dbfb537a2c0b3f710b51f59 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 28 Jan 2026 09:31:34 +0100 Subject: [PATCH 3/5] Show -C for fixup todos in rebase if present Co-authored-by: Stefan Haller --- pkg/gui/presentation/commits.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/gui/presentation/commits.go b/pkg/gui/presentation/commits.go index a5799bcb3..67fa62ac8 100644 --- a/pkg/gui/presentation/commits.go +++ b/pkg/gui/presentation/commits.go @@ -385,7 +385,12 @@ func displayCommit( actionString := "" if commit.Action != models.ActionNone { - actionString = actionColorMap(commit.Action, commit.Status).Sprint(commit.Action.String()) + actionStr := commit.Action.String() + // Only show the flag for fixup commands (where -C changes the meaning) + if commit.ActionFlag != "" && commit.Action == todo.Fixup { + actionStr += " " + commit.ActionFlag + } + actionString = actionColorMap(commit.Action, commit.Status).Sprint(actionStr) } tagString := "" From 6f6545e44f9702aa0e9a237711679b12df236215 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 28 Jan 2026 09:34:07 +0100 Subject: [PATCH 4/5] Pass todo flag to EditRebaseTodo Not used yet, we pass an empty string everywhere, to match the previous behavior. Just extracting this into a separate commit to make the next one smaller. Co-authored-by: Stefan Haller --- pkg/app/daemon/daemon.go | 1 + pkg/app/daemon/rebase.go | 1 + pkg/commands/git_commands/rebase.go | 6 ++++-- pkg/gui/controllers/local_commits_controller.go | 14 +++++++++++--- pkg/utils/rebase_todo.go | 2 ++ 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/pkg/app/daemon/daemon.go b/pkg/app/daemon/daemon.go index 6b29fd62d..df0e4bb49 100644 --- a/pkg/app/daemon/daemon.go +++ b/pkg/app/daemon/daemon.go @@ -199,6 +199,7 @@ func (self *ChangeTodoActionsInstruction) run(common *common.Common) error { return utils.TodoChange{ Hash: c.Hash, NewAction: c.NewAction, + Flag: c.Flag, } }) diff --git a/pkg/app/daemon/rebase.go b/pkg/app/daemon/rebase.go index e7cf1f57b..fdb58f23c 100644 --- a/pkg/app/daemon/rebase.go +++ b/pkg/app/daemon/rebase.go @@ -14,6 +14,7 @@ import ( type ChangeTodoAction struct { Hash string NewAction todo.TodoCommand + Flag string } func handleInteractiveRebase(common *common.Common, f func(path string) error) error { diff --git a/pkg/commands/git_commands/rebase.go b/pkg/commands/git_commands/rebase.go index 152c88bbc..97d48a1a0 100644 --- a/pkg/commands/git_commands/rebase.go +++ b/pkg/commands/git_commands/rebase.go @@ -139,7 +139,7 @@ func (self *RebaseCommands) MoveCommitsUp(commits []*models.Commit, startIdx int }).Run() } -func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, startIdx int, endIdx int, action todo.TodoCommand) error { +func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, startIdx int, endIdx int, action todo.TodoCommand, flag string) error { baseIndex := endIdx + 1 if action == todo.Squash || action == todo.Fixup { baseIndex++ @@ -151,6 +151,7 @@ func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, startIdx return daemon.ChangeTodoAction{ Hash: commit.Hash(), NewAction: action, + Flag: flag, }, !commit.IsMerge() }) @@ -334,11 +335,12 @@ func todoFromCommit(commit *models.Commit) utils.Todo { } // Sets the action for the given commits in the git-rebase-todo file -func (self *RebaseCommands) EditRebaseTodo(commits []*models.Commit, action todo.TodoCommand) error { +func (self *RebaseCommands) EditRebaseTodo(commits []*models.Commit, action todo.TodoCommand, flag string) error { commitsWithAction := lo.Map(commits, func(commit *models.Commit, _ int) utils.TodoChange { return utils.TodoChange{ Hash: commit.Hash(), NewAction: action, + Flag: flag, } }) diff --git a/pkg/gui/controllers/local_commits_controller.go b/pkg/gui/controllers/local_commits_controller.go index 2c8932f33..a2dc55994 100644 --- a/pkg/gui/controllers/local_commits_controller.go +++ b/pkg/gui/controllers/local_commits_controller.go @@ -505,7 +505,7 @@ func (self *LocalCommitsController) edit(selectedCommits []*models.Commit, start commits := self.c.Model().Commits if !commits[endIdx].IsMerge() { selectionRangeAndMode := self.getSelectionRangeAndMode() - err := self.c.Git().Rebase.InteractiveRebase(commits, startIdx, endIdx, todo.Edit) + err := self.c.Git().Rebase.InteractiveRebase(commits, startIdx, endIdx, todo.Edit, "") return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions( err, types.RefreshOptions{ @@ -611,13 +611,17 @@ func (self *LocalCommitsController) pick(selectedCommits []*models.Commit) error } func (self *LocalCommitsController) interactiveRebase(action todo.TodoCommand, startIdx int, endIdx int) error { + return self.interactiveRebaseWithFlag(action, startIdx, endIdx, "") +} + +func (self *LocalCommitsController) interactiveRebaseWithFlag(action todo.TodoCommand, startIdx int, endIdx int, flag string) error { // When performing an action that will remove the selected commits, we need to select the // next commit down (which will end up at the start index after the action is performed) if action == todo.Drop || action == todo.Fixup || action == todo.Squash { self.context().SetSelection(startIdx) } - err := self.c.Git().Rebase.InteractiveRebase(self.c.Model().Commits, startIdx, endIdx, action) + err := self.c.Git().Rebase.InteractiveRebase(self.c.Model().Commits, startIdx, endIdx, action, flag) return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err) } @@ -626,7 +630,11 @@ func (self *LocalCommitsController) interactiveRebase(action todo.TodoCommand, s // commit meaning you are trying to edit the todo file rather than actually // begin a rebase. It then updates the todo file with that action func (self *LocalCommitsController) updateTodos(action todo.TodoCommand, selectedCommits []*models.Commit) error { - if err := self.c.Git().Rebase.EditRebaseTodo(selectedCommits, action); err != nil { + return self.updateTodosWithFlag(action, selectedCommits, "") +} + +func (self *LocalCommitsController) updateTodosWithFlag(action todo.TodoCommand, selectedCommits []*models.Commit, flag string) error { + if err := self.c.Git().Rebase.EditRebaseTodo(selectedCommits, action, flag); err != nil { return err } diff --git a/pkg/utils/rebase_todo.go b/pkg/utils/rebase_todo.go index e2c9dc442..fe04cbc60 100644 --- a/pkg/utils/rebase_todo.go +++ b/pkg/utils/rebase_todo.go @@ -19,6 +19,7 @@ type Todo struct { type TodoChange struct { Hash string NewAction todo.TodoCommand + Flag string } // Read a git-rebase-todo file, change the actions for the given commits, @@ -37,6 +38,7 @@ func EditRebaseTodo(filePath string, changes []TodoChange, commentChar byte) err if equalHash(t.Commit, change.Hash) { matchCount++ t.Command = change.NewAction + t.Flag = change.Flag } } } From f317a97ac15f724e645ea8daac01b93be038cbb9 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sun, 25 Jan 2026 08:59:39 +1100 Subject: [PATCH 5/5] Support using the selected commit's message in a fixup I've optimised for muscle memory backwards compatibility here: - Outside interactive rebase: press 'f' then instead of a confirmation panel, a menu appears where you can choose to keep the selected commit's message - Inside interactive rebase: press 'f' then press 'c' to see the menu for keeping the message, where if you press 'c' again it will retain the current message. so 'fcc' is the chord to press. We're also now showing the -C flag (which is what enables the behaviour) against the todo. I've picked the 'c' keybinding because 'C' was taken and it corresponds to the flag. Previously that showed a warning about a change in keybinding for cherry picking but it's been ages since we've made that change so I'm happy to retire it. --- docs-master/Config.md | 1 + docs-master/keybindings/Keybindings_en.md | 1 + docs-master/keybindings/Keybindings_ja.md | 1 + docs-master/keybindings/Keybindings_ko.md | 1 + docs-master/keybindings/Keybindings_nl.md | 1 + docs-master/keybindings/Keybindings_pl.md | 1 + docs-master/keybindings/Keybindings_pt.md | 1 + docs-master/keybindings/Keybindings_ru.md | 1 + docs-master/keybindings/Keybindings_zh-CN.md | 1 + docs-master/keybindings/Keybindings_zh-TW.md | 1 + pkg/config/user_config.go | 2 + .../controllers/local_commits_controller.go | 76 ++++++++++++++++-- pkg/i18n/english.go | 20 ++++- .../interactive_rebase/fixup_keep_message.go | 48 ++++++++++++ .../fixup_keep_message_rebase.go | 78 +++++++++++++++++++ .../interactive_rebase/fixup_second_commit.go | 3 +- .../outside_rebase_range_select.go | 3 +- pkg/integration/tests/test_list.go | 2 + schema-master/config.json | 4 + schema/config.json | 4 + 20 files changed, 236 insertions(+), 14 deletions(-) create mode 100644 pkg/integration/tests/interactive_rebase/fixup_keep_message.go create mode 100644 pkg/integration/tests/interactive_rebase/fixup_keep_message_rebase.go diff --git a/docs-master/Config.md b/docs-master/Config.md index 21709da75..38887b3e2 100644 --- a/docs-master/Config.md +++ b/docs-master/Config.md @@ -713,6 +713,7 @@ keybinding: renameCommitWithEditor: R viewResetOptions: g markCommitAsFixup: f + setFixupMessage: c createFixupCommit: F squashAboveCommits: S moveDownCommit: diff --git a/docs-master/keybindings/Keybindings_en.md b/docs-master/keybindings/Keybindings_en.md index bbbd3e2e8..76b02512c 100644 --- a/docs-master/keybindings/Keybindings_en.md +++ b/docs-master/keybindings/Keybindings_en.md @@ -89,6 +89,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ | `` b `` | View bisect options | | | `` s `` | Squash | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. | | `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. | +| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. | | `` r `` | Reword | Reword the selected commit's message. | | `` R `` | Reword with editor | | | `` d `` | Drop | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. | diff --git a/docs-master/keybindings/Keybindings_ja.md b/docs-master/keybindings/Keybindings_ja.md index 79cf196fe..39758d3ed 100644 --- a/docs-master/keybindings/Keybindings_ja.md +++ b/docs-master/keybindings/Keybindings_ja.md @@ -69,6 +69,7 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味 | `` b `` | bisectオプションを表示 | | | `` s `` | スカッシュ | 選択したコミットをその下のコミットにスカッシュします。スカッシュとは複数のコミットを1つにまとめる操作です。選択したコミットのメッセージが下のコミットに追加されます。 | | `` f `` | フィックスアップ | 選択したコミットをその下のコミットにマージします。フィックスアップはスカッシュと似ていますが、選択したコミットのメッセージは破棄され、下のコミットのメッセージのみが保持されます。 | +| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. | | `` r `` | メッセージ変更 | 選択したコミットのメッセージを変更します。 | | `` R `` | エディタでメッセージ変更 | | | `` d `` | 削除 | 選択したコミットを削除します。これはリベースを通じてブランチからコミットを削除します。コミットが後続のコミットが依存する変更を行っている場合、マージコンフリクトを解決する必要があるかもしれません。 | diff --git a/docs-master/keybindings/Keybindings_ko.md b/docs-master/keybindings/Keybindings_ko.md index 330c080ee..0860c8571 100644 --- a/docs-master/keybindings/Keybindings_ko.md +++ b/docs-master/keybindings/Keybindings_ko.md @@ -298,6 +298,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ | `` b `` | Bisect 옵션 보기 | | | `` s `` | 스쿼시 | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. | | `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. | +| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. | | `` r `` | 커밋메시지 변경 | Reword the selected commit's message. | | `` R `` | 에디터에서 커밋메시지 수정 | | | `` d `` | 커밋 삭제 | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. | diff --git a/docs-master/keybindings/Keybindings_nl.md b/docs-master/keybindings/Keybindings_nl.md index f738be1b2..0bfffcb3d 100644 --- a/docs-master/keybindings/Keybindings_nl.md +++ b/docs-master/keybindings/Keybindings_nl.md @@ -160,6 +160,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ | `` b `` | View bisect options | | | `` s `` | Squash | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. | | `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. | +| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. | | `` r `` | Hernoem commit | Reword the selected commit's message. | | `` R `` | Hernoem commit met editor | | | `` d `` | Verwijder commit | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. | diff --git a/docs-master/keybindings/Keybindings_pl.md b/docs-master/keybindings/Keybindings_pl.md index 7bf17f18b..656f16a76 100644 --- a/docs-master/keybindings/Keybindings_pl.md +++ b/docs-master/keybindings/Keybindings_pl.md @@ -62,6 +62,7 @@ _Legenda: `` oznacza ctrl+b, `` oznacza alt+b, `B` oznacza shift+b_ | `` b `` | Zobacz opcje bisect | | | `` s `` | Scal | Scal wybrany commit z commitami poniżej. Wiadomość wybranego commita zostanie dołączona do commita poniżej. | | `` f `` | Poprawka | Włącz wybrany commit do commita poniżej. Podobnie do fixup, ale wiadomość wybranego commita zostanie odrzucona. | +| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. | | `` r `` | Przeformułuj | Przeformułuj wiadomość wybranego commita. | | `` R `` | Przeformułuj za pomocą edytora | | | `` d `` | Usuń | Usuń wybrany commit. To usunie commit z gałęzi za pomocą rebazowania. Jeśli commit wprowadza zmiany, od których zależą późniejsze commity, być może będziesz musiał rozwiązać konflikty scalania. | diff --git a/docs-master/keybindings/Keybindings_pt.md b/docs-master/keybindings/Keybindings_pt.md index 54b1f1871..c9e5d3bf4 100644 --- a/docs-master/keybindings/Keybindings_pt.md +++ b/docs-master/keybindings/Keybindings_pt.md @@ -164,6 +164,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ | `` b `` | View bisect options | | | `` s `` | Squash | Squash o commit selecionado no commit abaixo dele. A mensagem do commit selecionado será anexada ao commit abaixo dele. | | `` f `` | Fixup | Faça o commit selecionado no commit abaixo dele. Semelhante para o squash, mas a mensagem do commit selecionado será descartada. | +| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. | | `` r `` | Reword | Repetir a mensagem de submissão selecionada. | | `` R `` | Republicar com o editor | | | `` d `` | Descartar | Solte o commit selecionado. Isso irá remover o commit do branch através de uma rebase. Se o commit faz com que as alterações em commits posteriores dependem, você pode precisar resolver conflitos de merge. | diff --git a/docs-master/keybindings/Keybindings_ru.md b/docs-master/keybindings/Keybindings_ru.md index cee2366b4..df0a4ce1f 100644 --- a/docs-master/keybindings/Keybindings_ru.md +++ b/docs-master/keybindings/Keybindings_ru.md @@ -170,6 +170,7 @@ _Связки клавиш_ | `` b `` | Просмотреть параметры бинарного поиска | | | `` s `` | Объединить коммиты (Squash) | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. | | `` f `` | Объединить несколько коммитов в один отбросив сообщение коммита (Fixup) | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. | +| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. | | `` r `` | Перефразировать коммит | Reword the selected commit's message. | | `` R `` | Переписать коммит с помощью редактора | | | `` d `` | Удалить коммит | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. | diff --git a/docs-master/keybindings/Keybindings_zh-CN.md b/docs-master/keybindings/Keybindings_zh-CN.md index 21b75ee29..37480d3c3 100644 --- a/docs-master/keybindings/Keybindings_zh-CN.md +++ b/docs-master/keybindings/Keybindings_zh-CN.md @@ -126,6 +126,7 @@ _图例:`` 意味着ctrl+b, `意味着Alt+b, `B` 意味着shift+b_ | `` b `` | 查看二分查找选项 | | | `` s `` | 压缩(Squash) | 将已选提交压缩到该提交之下。这些选定的提交的消息会附加到该提交的消息之下。 | | `` f `` | 修正 (fixup) | 将选定的提交合并到其下面的提交中。与压缩类似,但所选提交的消息将被丢弃。 | +| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. | | `` r `` | 改写提交 | 重写所选提交的消息。 | | `` R `` | 使用编辑器重命名提交 | | | `` d `` | 删除提交 | 删除选中的提交。这将通过变基从分支中删除该提交,如果该提交修改的内容依赖于后续的提交,则需要解决合并冲突。 | diff --git a/docs-master/keybindings/Keybindings_zh-TW.md b/docs-master/keybindings/Keybindings_zh-TW.md index 59d6d74c9..107976c26 100644 --- a/docs-master/keybindings/Keybindings_zh-TW.md +++ b/docs-master/keybindings/Keybindings_zh-TW.md @@ -184,6 +184,7 @@ _說明:`` 表示 Ctrl+B、`` 表示 Alt+B,`B`表示 Shift+B | `` b `` | 查看二分選項 | | | `` s `` | 壓縮 (Squash) | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. | | `` f `` | 修復 (Fixup) | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. | +| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. | | `` r `` | 改寫提交 | 改寫選中的提交訊息 | | `` R `` | 使用編輯器改寫提交 | | | `` d `` | 刪除提交 | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. | diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go index bcb5c2f6f..abf591801 100644 --- a/pkg/config/user_config.go +++ b/pkg/config/user_config.go @@ -551,6 +551,7 @@ type KeybindingCommitsConfig struct { RenameCommitWithEditor string `yaml:"renameCommitWithEditor"` ViewResetOptions string `yaml:"viewResetOptions"` MarkCommitAsFixup string `yaml:"markCommitAsFixup"` + SetFixupMessage string `yaml:"setFixupMessage"` CreateFixupCommit string `yaml:"createFixupCommit"` SquashAboveCommits string `yaml:"squashAboveCommits"` MoveDownCommit string `yaml:"moveDownCommit"` @@ -1008,6 +1009,7 @@ func GetDefaultConfig() *UserConfig { RenameCommitWithEditor: "R", ViewResetOptions: "g", MarkCommitAsFixup: "f", + SetFixupMessage: "c", CreateFixupCommit: "F", SquashAboveCommits: "S", MoveDownCommit: "", diff --git a/pkg/gui/controllers/local_commits_controller.go b/pkg/gui/controllers/local_commits_controller.go index a2dc55994..42fb1135e 100644 --- a/pkg/gui/controllers/local_commits_controller.go +++ b/pkg/gui/controllers/local_commits_controller.go @@ -82,6 +82,15 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [ Tooltip: self.c.Tr.FixupTooltip, DisplayOnScreen: true, }, + { + Key: opts.GetKey(opts.Config.Commits.SetFixupMessage), + Handler: self.withItem(self.setFixupMessage), + GetDisabledReason: self.require( + self.singleItemSelected(self.canSetFixupMessage), + ), + Description: self.c.Tr.SetFixupMessage, + Tooltip: self.c.Tr.SetFixupMessageTooltip, + }, { Key: opts.GetKey(opts.Config.Commits.RenameCommit), Handler: self.withItem(self.reword), @@ -321,20 +330,71 @@ func (self *LocalCommitsController) fixup(selectedCommits []*models.Commit, star return self.updateTodos(todo.Fixup, selectedCommits) } - self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.Fixup, - Prompt: self.c.Tr.SureFixupThisCommit, - HandleConfirm: func() error { - return self.c.WithWaitingStatus(self.c.Tr.FixingStatus, func(gocui.Task) error { - self.c.LogAction(self.c.Tr.Actions.FixupCommit) - return self.interactiveRebase(todo.Fixup, startIdx, endIdx) - }) + return self.c.Menu(types.CreateMenuOptions{ + Title: self.c.Tr.Fixup, + Items: []*types.MenuItem{ + { + Label: self.c.Tr.Fixup, + Key: 'f', + OnPress: func() error { + return self.c.WithWaitingStatus(self.c.Tr.FixingStatus, func(gocui.Task) error { + self.c.LogAction(self.c.Tr.Actions.FixupCommit) + return self.interactiveRebase(todo.Fixup, startIdx, endIdx) + }) + }, + Tooltip: self.c.Tr.FixupTooltip, + }, + { + Label: self.c.Tr.FixupKeepMessage, + Key: 'c', + OnPress: func() error { + return self.c.WithWaitingStatus(self.c.Tr.FixingStatus, func(gocui.Task) error { + self.c.LogAction(self.c.Tr.Actions.FixupCommitKeepMessage) + return self.interactiveRebaseWithFlag(todo.Fixup, startIdx, endIdx, "-C") + }) + }, + Tooltip: self.c.Tr.FixupKeepMessageTooltip, + }, }, }) +} + +func (self *LocalCommitsController) canSetFixupMessage(commit *models.Commit) *types.DisabledReason { + if !self.isRebasing() { + return &types.DisabledReason{Text: self.c.Tr.NotMidRebase} + } + + if commit.Action != todo.Fixup { + return &types.DisabledReason{Text: self.c.Tr.MustSelectFixupCommit} + } return nil } +func (self *LocalCommitsController) setFixupMessage(commit *models.Commit) error { + return self.c.Menu(types.CreateMenuOptions{ + Title: self.c.Tr.SetFixupMessage, + Items: []*types.MenuItem{ + { + Label: self.c.Tr.FixupDiscardMessage, + Key: 'f', + OnPress: func() error { + return self.updateTodosWithFlag(todo.Fixup, []*models.Commit{commit}, "") + }, + Tooltip: self.c.Tr.FixupDiscardMessageTooltip, + }, + { + Label: self.c.Tr.FixupKeepMessage, + Key: 'c', + OnPress: func() error { + return self.updateTodosWithFlag(todo.Fixup, []*models.Commit{commit}, "-C") + }, + Tooltip: self.c.Tr.FixupKeepMessageTooltip, + }, + }, + }) +} + func (self *LocalCommitsController) reword(commit *models.Commit) error { commitIdx := self.context().GetSelectedLineIdx() if self.c.Git().Config.NeedsGpgSubprocessForCommit() && !self.isHeadCommit(commitIdx) { diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 1be4898b1..ce87693a2 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -164,7 +164,12 @@ type TranslationSet struct { CannotSquashOrFixupMergeCommit string Fixup string FixupTooltip string - SureFixupThisCommit string + FixupKeepMessage string + FixupKeepMessageTooltip string + SetFixupMessage string + SetFixupMessageTooltip string + FixupDiscardMessage string + FixupDiscardMessageTooltip string SureSquashThisCommit string Squash string PickCommitTooltip string @@ -316,6 +321,8 @@ type TranslationSet struct { ViewRevertOptions string NotMergingOrRebasing string AlreadyRebasing string + NotMidRebase string + MustSelectFixupCommit string RecentRepos string MergeOptionsTitle string RebaseOptionsTitle string @@ -965,6 +972,7 @@ type Actions struct { CheckoutFile string SquashCommitDown string FixupCommit string + FixupCommitKeepMessage string RewordCommit string DropCommit string EditCommit string @@ -1259,7 +1267,12 @@ func EnglishTranslationSet() *TranslationSet { CannotSquashOrFixupFirstCommit: "There's no commit below to squash into", CannotSquashOrFixupMergeCommit: "Cannot squash or fixup a merge commit", Fixup: "Fixup", - SureFixupThisCommit: "Are you sure you want to 'fixup' the selected commit(s) into the commit below?", + FixupKeepMessage: "Fixup and use this commit's message", + FixupKeepMessageTooltip: "Squash the selected commit into the commit below, using this commit's message, discarding the message of the commit below.", + SetFixupMessage: "Set fixup message", + SetFixupMessageTooltip: "Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message.", + FixupDiscardMessage: "Fixup and discard this commit's message", + FixupDiscardMessageTooltip: "Squash the selected commit into the commit below, discarding this commit's message.", SureSquashThisCommit: "Are you sure you want to squash the selected commit(s) into the commit below?", Squash: "Squash", PickCommitTooltip: "Mark the selected commit to be picked (when mid-rebase). This means that the commit will be retained upon continuing the rebase.", @@ -1411,6 +1424,8 @@ func EnglishTranslationSet() *TranslationSet { ViewRevertOptions: "View revert options", NotMergingOrRebasing: "You are currently neither rebasing nor merging", AlreadyRebasing: "Can't perform this action during a rebase", + NotMidRebase: "This action only works during an interactive rebase", + MustSelectFixupCommit: "This action only works on fixup commits", RecentRepos: "Recent repositories", MergeOptionsTitle: "Merge options", RebaseOptionsTitle: "Rebase options", @@ -2023,6 +2038,7 @@ func EnglishTranslationSet() *TranslationSet { CheckoutFile: "Checkout file", SquashCommitDown: "Squash commit down", FixupCommit: "Fixup commit", + FixupCommitKeepMessage: "Fixup commit (keep message)", RewordCommit: "Reword commit", DropCommit: "Drop commit", EditCommit: "Edit commit", diff --git a/pkg/integration/tests/interactive_rebase/fixup_keep_message.go b/pkg/integration/tests/interactive_rebase/fixup_keep_message.go new file mode 100644 index 000000000..524066f92 --- /dev/null +++ b/pkg/integration/tests/interactive_rebase/fixup_keep_message.go @@ -0,0 +1,48 @@ +package interactive_rebase + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var FixupKeepMessage = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Fixup a commit, keeping its commit message", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell. + CreateFileAndAdd("file1.txt", "File1 Content\n").Commit("First Commit"). + CreateFileAndAdd("file2.txt", "File2 Content\n").Commit("Second Commit"). + CreateFileAndAdd("file3.txt", "File3 Content\n").Commit("Third Commit") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Commits(). + Focus(). + Lines( + Contains("Third Commit"), + Contains("Second Commit"), + Contains("First Commit"), + ). + NavigateToLine(Contains("Second Commit")). + Press(keys.Commits.MarkCommitAsFixup). + Tap(func() { + t.ExpectPopup().Menu(). + Title(Equals("Fixup")). + Select(Contains("use this commit's message")). + Confirm() + }). + Lines( + Contains("Third Commit"), + Contains("Second Commit").IsSelected(), + ) + + t.Views().Main(). + // The resulting commit should have the message from the fixup commit, + // not the target commit + Content(Contains("Second Commit")). + Content(DoesNotContain("First Commit")). + Content(Contains("+File1 Content")). + Content(Contains("+File2 Content")) + }, +}) diff --git a/pkg/integration/tests/interactive_rebase/fixup_keep_message_rebase.go b/pkg/integration/tests/interactive_rebase/fixup_keep_message_rebase.go new file mode 100644 index 000000000..595968c17 --- /dev/null +++ b/pkg/integration/tests/interactive_rebase/fixup_keep_message_rebase.go @@ -0,0 +1,78 @@ +package interactive_rebase + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var FixupKeepMessageRebase = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Set fixup -C flag on a fixup commit during interactive rebase", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell. + CreateFileAndAdd("file1.txt", "File1 Content\n").Commit("First Commit"). + CreateFileAndAdd("file2.txt", "File2 Content\n").Commit("Second Commit"). + CreateFileAndAdd("file3.txt", "File3 Content\n").Commit("Third Commit") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Commits(). + Focus(). + Lines( + Contains("Third Commit"), + Contains("Second Commit"), + Contains("First Commit"), + ). + // Start interactive rebase from the first commit + NavigateToLine(Contains("First Commit")). + Press(keys.Universal.Edit). + Lines( + Contains("--- Pending rebase todos ---"), + Contains("pick CI Third Commit"), + Contains("pick CI Second Commit"), + Contains("--- Commits ---"), + Contains("First Commit").IsSelected(), + ). + // Mark second commit as fixup + NavigateToLine(Contains("Second Commit")). + Press(keys.Commits.MarkCommitAsFixup). + Lines( + Contains("--- Pending rebase todos ---"), + Contains("pick CI Third Commit"), + Contains("fixup CI Second Commit").IsSelected(), + Contains("--- Commits ---"), + Contains("First Commit"), + ). + // Now set the -C flag using the SetFixupMessage keybinding + Press(keys.Commits.SetFixupMessage). + Tap(func() { + t.ExpectPopup().Menu(). + Title(Equals("Set fixup message")). + Select(Contains("use this commit's message")). + Confirm() + }). + Lines( + Contains("--- Pending rebase todos ---"), + Contains("pick CI Third Commit"), + Contains("fixup -C CI Second Commit").IsSelected(), + Contains("--- Commits ---"), + Contains("First Commit"), + ). + // Continue the rebase + Tap(func() { + t.Common().ContinueRebase() + }). + Lines( + Contains("Third Commit"), + Contains("Second Commit").IsSelected(), + ) + + t.Views().Main(). + // The resulting commit should have the message from the fixup commit + Content(Contains("Second Commit")). + Content(DoesNotContain("First Commit")). + Content(Contains("+File1 Content")). + Content(Contains("+File2 Content")) + }, +}) diff --git a/pkg/integration/tests/interactive_rebase/fixup_second_commit.go b/pkg/integration/tests/interactive_rebase/fixup_second_commit.go index c5eec4a82..33cc93215 100644 --- a/pkg/integration/tests/interactive_rebase/fixup_second_commit.go +++ b/pkg/integration/tests/interactive_rebase/fixup_second_commit.go @@ -27,9 +27,8 @@ var FixupSecondCommit = NewIntegrationTest(NewIntegrationTestArgs{ NavigateToLine(Contains("Fixup Commit Message")). Press(keys.Commits.MarkCommitAsFixup). Tap(func() { - t.ExpectPopup().Confirmation(). + t.ExpectPopup().Menu(). Title(Equals("Fixup")). - Content(Equals("Are you sure you want to 'fixup' the selected commit(s) into the commit below?")). Confirm() }). Lines( diff --git a/pkg/integration/tests/interactive_rebase/outside_rebase_range_select.go b/pkg/integration/tests/interactive_rebase/outside_rebase_range_select.go index d30ae0d64..4aeb28b28 100644 --- a/pkg/integration/tests/interactive_rebase/outside_rebase_range_select.go +++ b/pkg/integration/tests/interactive_rebase/outside_rebase_range_select.go @@ -77,9 +77,8 @@ var OutsideRebaseRangeSelect = NewIntegrationTest(NewIntegrationTestArgs{ ). Press(keys.Commits.MarkCommitAsFixup). Tap(func() { - t.ExpectPopup().Confirmation(). + t.ExpectPopup().Menu(). Title(Equals("Fixup")). - Content(Contains("Are you sure you want to 'fixup' the selected commit(s) into the commit below?")). Confirm() }). TopLines( diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index faf35a601..4d9edaa70 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -279,6 +279,8 @@ var tests = []*components.IntegrationTest{ interactive_rebase.EditRangeSelectOutsideRebase, interactive_rebase.EditTheConflCommit, interactive_rebase.FixupFirstCommit, + interactive_rebase.FixupKeepMessage, + interactive_rebase.FixupKeepMessageRebase, interactive_rebase.FixupSecondCommit, interactive_rebase.InteractiveRebaseOfCopiedBranch, interactive_rebase.InteractiveRebaseWithConflictForEditCommand, diff --git a/schema-master/config.json b/schema-master/config.json index c371f14e9..0e880b066 100644 --- a/schema-master/config.json +++ b/schema-master/config.json @@ -946,6 +946,10 @@ "type": "string", "default": "f" }, + "setFixupMessage": { + "type": "string", + "default": "c" + }, "createFixupCommit": { "type": "string", "default": "F" diff --git a/schema/config.json b/schema/config.json index c371f14e9..0e880b066 100644 --- a/schema/config.json +++ b/schema/config.json @@ -946,6 +946,10 @@ "type": "string", "default": "f" }, + "setFixupMessage": { + "type": "string", + "default": "c" + }, "createFixupCommit": { "type": "string", "default": "F"