diff --git a/docs-master/keybindings/Keybindings_en.md b/docs-master/keybindings/Keybindings_en.md index 76b02512c85..06ff049a02c 100644 --- a/docs-master/keybindings/Keybindings_en.md +++ b/docs-master/keybindings/Keybindings_en.md @@ -237,6 +237,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ | `` o `` | Open file | Open file in default application. | | `` e `` | Edit file | Open file in external editor. | | `` `` | Toggle lines in patch | | +| `` d `` | Remove lines from commit | Remove the selected lines from this commit. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes these lines. | | `` `` | Exit custom patch builder | | | `` / `` | Search the current view by text | | diff --git a/docs-master/keybindings/Keybindings_ja.md b/docs-master/keybindings/Keybindings_ja.md index b657aa794f7..74873ed2ef5 100644 --- a/docs-master/keybindings/Keybindings_ja.md +++ b/docs-master/keybindings/Keybindings_ja.md @@ -278,6 +278,7 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味 | `` o `` | ファイルを開く | デフォルトのアプリケーションでファイルを開きます。 | | `` e `` | ファイルを編集 | 外部エディタでファイルを開きます。 | | `` `` | パッチ内の行を切り替え | | +| `` d `` | Remove lines from commit | Remove the selected lines from this commit. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes these lines. | | `` `` | カスタムパッチビルダーを終了 | | | `` / `` | 現在のビューをテキストで検索 | | diff --git a/docs-master/keybindings/Keybindings_ko.md b/docs-master/keybindings/Keybindings_ko.md index 0860c85719c..7627822b281 100644 --- a/docs-master/keybindings/Keybindings_ko.md +++ b/docs-master/keybindings/Keybindings_ko.md @@ -178,6 +178,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ | `` o `` | 파일 닫기 | Open file in default application. | | `` e `` | 파일 편집 | Open file in external editor. | | `` `` | Line(s)을 패치에 추가/삭제 | | +| `` d `` | Remove lines from commit | Remove the selected lines from this commit. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes these lines. | | `` `` | Exit custom patch builder | | | `` / `` | 검색 시작 | | diff --git a/docs-master/keybindings/Keybindings_nl.md b/docs-master/keybindings/Keybindings_nl.md index 0bfffcb3dea..ceec409049a 100644 --- a/docs-master/keybindings/Keybindings_nl.md +++ b/docs-master/keybindings/Keybindings_nl.md @@ -245,6 +245,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ | `` o `` | Open bestand | Open file in default application. | | `` e `` | Verander bestand | Open file in external editor. | | `` `` | Voeg toe/verwijder lijn(en) in patch | | +| `` d `` | Remove lines from commit | Remove the selected lines from this commit. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes these lines. | | `` `` | Sluit lijn-bij-lijn modus | | | `` / `` | Start met zoeken | | diff --git a/docs-master/keybindings/Keybindings_pl.md b/docs-master/keybindings/Keybindings_pl.md index 656f16a76ed..5bb91ec795b 100644 --- a/docs-master/keybindings/Keybindings_pl.md +++ b/docs-master/keybindings/Keybindings_pl.md @@ -124,6 +124,7 @@ _Legenda: `` oznacza ctrl+b, `` oznacza alt+b, `B` oznacza shift+b_ | `` o `` | Otwórz plik | Otwórz plik w domyślnej aplikacji. | | `` e `` | Edytuj plik | Otwórz plik w zewnętrznym edytorze. | | `` `` | Przełącz linie w łatce | | +| `` d `` | Remove lines from commit | Remove the selected lines from this commit. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes these lines. | | `` `` | Wyjdź z budowniczego niestandardowej łatki | | | `` / `` | Szukaj w bieżącym widoku po tekście | | diff --git a/docs-master/keybindings/Keybindings_pt.md b/docs-master/keybindings/Keybindings_pt.md index c9e5d3bf47b..ed34b0ae8b9 100644 --- a/docs-master/keybindings/Keybindings_pt.md +++ b/docs-master/keybindings/Keybindings_pt.md @@ -295,6 +295,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ | `` o `` | Abrir arquivo | Abrir arquivo no aplicativo padrão. | | `` e `` | Editar arquivo | Abrir arquivo no editor externo. | | `` `` | Alternar linhas no caminho | | +| `` d `` | Remove lines from commit | Remove the selected lines from this commit. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes these lines. | | `` `` | Sair do construtor de patch personalizado | | | `` / `` | Search the current view by text | | diff --git a/docs-master/keybindings/Keybindings_ru.md b/docs-master/keybindings/Keybindings_ru.md index df0a4ce1f1a..d01ea24e28c 100644 --- a/docs-master/keybindings/Keybindings_ru.md +++ b/docs-master/keybindings/Keybindings_ru.md @@ -138,6 +138,7 @@ _Связки клавиш_ | `` o `` | Открыть файл | Open file in default application. | | `` e `` | Редактировать файл | Open file in external editor. | | `` `` | Добавить/удалить строку(и) для патча | | +| `` d `` | Remove lines from commit | Remove the selected lines from this commit. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes these lines. | | `` `` | Выйти из сборщика пользовательских патчей | | | `` / `` | Найти | | diff --git a/docs-master/keybindings/Keybindings_zh-CN.md b/docs-master/keybindings/Keybindings_zh-CN.md index 169dbb9c8c0..b533f649582 100644 --- a/docs-master/keybindings/Keybindings_zh-CN.md +++ b/docs-master/keybindings/Keybindings_zh-CN.md @@ -260,6 +260,7 @@ _图例:`` 意味着ctrl+b, `意味着Alt+b, `B` 意味着shift+b_ | `` o `` | 打开文件 | 使用默认程序打开该文件 | | `` e `` | 编辑文件 | 使用外部编辑器打开文件 | | `` `` | 添加/移除 行到补丁 | | +| `` d `` | Remove lines from commit | Remove the selected lines from this commit. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes these lines. | | `` `` | 退出逐行模式 | | | `` / `` | 开始搜索 | | diff --git a/docs-master/keybindings/Keybindings_zh-TW.md b/docs-master/keybindings/Keybindings_zh-TW.md index 107976c26f0..3bb311897bf 100644 --- a/docs-master/keybindings/Keybindings_zh-TW.md +++ b/docs-master/keybindings/Keybindings_zh-TW.md @@ -72,6 +72,7 @@ _說明:`` 表示 Ctrl+B、`` 表示 Alt+B,`B`表示 Shift+B | `` o `` | 開啟檔案 | 使用預設軟體開啟 | | `` e `` | 編輯檔案 | 使用外部編輯器開啟 | | `` `` | 向 (或從) 補丁中添加/刪除行 | | +| `` d `` | Remove lines from commit | Remove the selected lines from this commit. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes these lines. | | `` `` | 退出自訂補丁建立器 | | | `` / `` | 搜尋 | | diff --git a/pkg/gui/controllers/patch_building_controller.go b/pkg/gui/controllers/patch_building_controller.go index c4f8390f6e8..aaca6430aa2 100644 --- a/pkg/gui/controllers/patch_building_controller.go +++ b/pkg/gui/controllers/patch_building_controller.go @@ -1,7 +1,10 @@ package controllers import ( + "fmt" + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/gui/keybindings" "github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/samber/lo" ) @@ -42,6 +45,14 @@ func (self *PatchBuildingController) GetKeybindings(opts types.KeybindingsOpts) Description: self.c.Tr.ToggleSelectionForPatch, DisplayOnScreen: true, }, + { + Key: opts.GetKey(opts.Config.Universal.Remove), + Handler: self.DiscardSelection, + GetDisabledReason: self.getDisabledReasonForDiscard, + Description: self.c.Tr.RemoveSelectionFromPatch, + Tooltip: self.c.Tr.RemoveSelectionFromPatchTooltip, + DisplayOnScreen: true, + }, { Key: opts.GetKey(opts.Config.Universal.Return), Handler: self.Escape, @@ -168,6 +179,83 @@ func (self *PatchBuildingController) toggleSelection() error { return nil } +func (self *PatchBuildingController) getDisabledReasonForDiscard() *types.DisabledReason { + if !self.c.Git().Patch.PatchBuilder.CanRebase { + return &types.DisabledReason{Text: self.c.Tr.CanOnlyRemoveLinesFromLocalCommits} + } + if !self.c.Git().Patch.PatchBuilder.IsEmpty() { + return &types.DisabledReason{Text: self.c.Tr.MustClearPatchBeforeRemovingLines} + } + return nil +} + +func (self *PatchBuildingController) DiscardSelection() error { + if self.c.UserConfig().Git.DiffContextSize == 0 { + return fmt.Errorf(self.c.Tr.Actions.NotEnoughContextToRemoveLines, + keybindings.Label(self.c.UserConfig().Keybinding.Universal.IncreaseContextInDiffView)) + } + + if ok, err := self.c.Helpers().PatchBuilding.ValidateNormalWorkingTreeState(); !ok { + return err + } + + self.c.Confirm(types.ConfirmOpts{ + Title: self.c.Tr.RemoveLinesFromCommitTitle, + Prompt: self.c.Tr.RemoveLinesFromCommitPrompt, + HandleConfirm: func() error { + return self.removeSelectionFromCommit() + }, + }) + + return nil +} + +func (self *PatchBuildingController) addSelectionToPatch() error { + self.context().GetMutex().Lock() + defer self.context().GetMutex().Unlock() + + filename := self.c.Contexts().CommitFiles.GetSelectedPath() + if filename == "" { + return nil + } + + state := self.context().GetState() + lineIndicesToToggle := state.LineIndicesOfAddedOrDeletedLinesInSelectedPatchRange() + if len(lineIndicesToToggle) == 0 { + return nil + } + + return self.c.Git().Patch.PatchBuilder.AddFileLineRange(filename, lineIndicesToToggle) +} + +func (self *PatchBuildingController) removeSelectionFromCommit() error { + if err := self.addSelectionToPatch(); err != nil { + return err + } + + if self.c.Git().Patch.PatchBuilder.IsEmpty() { + return nil + } + + self.c.Helpers().PatchBuilding.Escape() + + return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error { + commitIndex := self.getPatchCommitIndex() + self.c.LogAction(self.c.Tr.Actions.RemovePatchFromCommit) + err := self.c.Git().Patch.DeletePatchesFromCommit(self.c.Model().Commits, commitIndex) + return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err) + }) +} + +func (self *PatchBuildingController) getPatchCommitIndex() int { + for index, commit := range self.c.Model().Commits { + if commit.Hash() == self.c.Git().Patch.PatchBuilder.To { + return index + } + } + return -1 +} + func (self *PatchBuildingController) Escape() error { context := self.c.Contexts().CustomPatchBuilder state := context.GetState() diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index ce87693a24d..96443fcddad 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -300,6 +300,8 @@ type TranslationSet struct { ToggleSelectHunkTooltip string HunkStagingHint string ToggleSelectionForPatch string + RemoveSelectionFromPatch string + RemoveSelectionFromPatchTooltip string EditHunk string EditHunkTooltip string ToggleStagingView string @@ -434,6 +436,8 @@ type TranslationSet struct { CheckoutCommitFileTooltip string CannotCheckoutWithModifiedFilesErr string CanOnlyDiscardFromLocalCommits string + CanOnlyRemoveLinesFromLocalCommits string + MustClearPatchBeforeRemovingLines string Remove string DiscardOldFileChangeTooltip string DiscardFileChangesTitle string @@ -686,6 +690,8 @@ type TranslationSet struct { BranchUnknown string DiscardChangeTitle string DiscardChangePrompt string + RemoveLinesFromCommitTitle string + RemoveLinesFromCommitPrompt string CreateNewBranchFromCommit string BuildingPatch string ViewCommits string @@ -1007,6 +1013,7 @@ type Actions struct { ResolveConflictByDeletingFile string NotEnoughContextToStage string NotEnoughContextToDiscard string + NotEnoughContextToRemoveLines string NotEnoughContextForCustomPatch string IgnoreExcludeFile string IgnoreFileErr string @@ -1405,6 +1412,8 @@ func EnglishTranslationSet() *TranslationSet { ToggleSelectHunkTooltip: "Toggle line-by-line vs. hunk selection mode.", HunkStagingHint: englishHunkStagingHint, ToggleSelectionForPatch: `Toggle lines in patch`, + RemoveSelectionFromPatch: `Remove lines from commit`, + RemoveSelectionFromPatchTooltip: "Remove the selected lines from this commit. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes these lines.", EditHunk: `Edit hunk`, EditHunkTooltip: "Edit selected hunk in external editor.", ToggleStagingView: "Switch view", @@ -1542,6 +1551,8 @@ func EnglishTranslationSet() *TranslationSet { CheckoutCommitFileTooltip: "Checkout file. This replaces the file in your working tree with the version from the selected commit.", CannotCheckoutWithModifiedFilesErr: "You have local modifications for the file(s) you are trying to check out. You need to stash or discard these first.", CanOnlyDiscardFromLocalCommits: "Changes can only be discarded from local commits", + CanOnlyRemoveLinesFromLocalCommits: "Lines can only be removed from local commits", + MustClearPatchBeforeRemovingLines: "Clear the current custom patch first before removing lines from the commit", Remove: "Remove", DiscardOldFileChangeTooltip: "Discard this commit's changes to this file. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes this file.", DiscardFileChangesTitle: "Discard file changes", @@ -1794,6 +1805,8 @@ func EnglishTranslationSet() *TranslationSet { BranchUnknown: "Branch unknown", DiscardChangeTitle: "Discard change", DiscardChangePrompt: "Are you sure you want to discard this change (git reset)? It is irreversible.\nTo disable this dialogue set the config key of 'gui.skipDiscardChangeWarning' to true", + RemoveLinesFromCommitTitle: "Remove lines from commit", + RemoveLinesFromCommitPrompt: "Are you sure you want to remove the selected lines from this commit?", CreateNewBranchFromCommit: "Create new branch off of commit", BuildingPatch: "Building patch", ViewCommits: "View commits", @@ -2075,6 +2088,7 @@ func EnglishTranslationSet() *TranslationSet { ResolveConflictByDeletingFile: "Resolve by deleting file", NotEnoughContextToStage: "Staging or unstaging changes is not possible with a diff context size of 0. Increase the context using '%s'.", NotEnoughContextToDiscard: "Discarding changes is not possible with a diff context size of 0. Increase the context using '%s'.", + NotEnoughContextToRemoveLines: "Removing lines from a commit is not possible with a diff context size of 0. Increase the context using '%s'.", NotEnoughContextForCustomPatch: "Creating custom patches is not possible with a diff context size of 0. Increase the context using '%s'.", IgnoreExcludeFile: "Ignore or exclude file", IgnoreFileErr: "Cannot ignore .gitignore", diff --git a/pkg/integration/tests/patch_building/remove_lines_from_commit.go b/pkg/integration/tests/patch_building/remove_lines_from_commit.go new file mode 100644 index 00000000000..636bcdf283f --- /dev/null +++ b/pkg/integration/tests/patch_building/remove_lines_from_commit.go @@ -0,0 +1,63 @@ +package patch_building + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var RemoveLinesFromCommit = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Remove specific lines from a commit using the 'd' shortcut in the patch building view", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell.EmptyCommit("first commit") + + shell.CreateFileAndAdd("file1", "1st line\n2nd line\n3rd line\n") + shell.Commit("commit to remove from") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Commits(). + Focus(). + Lines( + Contains("commit to remove from").IsSelected(), + Contains("first commit"), + ). + PressEnter() + + t.Views().CommitFiles(). + IsFocused(). + Lines( + Contains("A file1").IsSelected(), + ). + PressEnter() + + // Select the second line (+2nd line) and press 'd' to remove it + t.Views().PatchBuilding(). + IsFocused(). + SelectNextItem(). + SelectedLines( + Contains("+2nd line"), + ). + Press(keys.Universal.Remove) + + t.ExpectPopup().Confirmation(). + Title(Equals("Remove lines from commit")). + Content(Equals("Are you sure you want to remove the selected lines from this commit?")). + Confirm() + + // After the rebase, we should be back at the commit files view + // and the commit should now only contain the 1st and 3rd lines + t.Views().CommitFiles(). + IsFocused(). + Lines( + Contains("A file1").IsSelected(), + ). + PressEscape() + + t.Views().Main().ContainsLines( + Equals("+1st line"), + Equals("+3rd line"), + ) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 4d9edaa70ab..d098d1185c5 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -348,6 +348,7 @@ var tests = []*components.IntegrationTest{ patch_building.MoveToNewCommitInLastCommitOfStackedBranch, patch_building.MoveToNewCommitPartialHunk, patch_building.RemoveFromCommit, + patch_building.RemoveLinesFromCommit, patch_building.RemovePartsOfAddedFile, patch_building.ResetWithEscape, patch_building.SelectAllFiles,