diff options
Diffstat (limited to 'pkg')
10 files changed, 297 insertions, 28 deletions
diff --git a/pkg/commands/git_commands/patch.go b/pkg/commands/git_commands/patch.go index 3d18bf3e2..bceaf9943 100644 --- a/pkg/commands/git_commands/patch.go +++ b/pkg/commands/git_commands/patch.go @@ -47,8 +47,8 @@ type ApplyPatchOpts struct { Reverse bool } -func (self *PatchCommands) ApplyCustomPatch(reverse bool) error { - patch := self.PatchBuilder.PatchToApply(reverse) +func (self *PatchCommands) ApplyCustomPatch(reverse bool, turnAddedFilesIntoDiffAgainstEmptyFile bool) error { + patch := self.PatchBuilder.PatchToApply(reverse, turnAddedFilesIntoDiffAgainstEmptyFile) return self.ApplyPatch(patch, ApplyPatchOpts{ Index: true, @@ -94,7 +94,7 @@ func (self *PatchCommands) DeletePatchesFromCommit(commits []*models.Commit, com } // apply each patch in reverse - if err := self.ApplyCustomPatch(true); err != nil { + if err := self.ApplyCustomPatch(true, true); err != nil { _ = self.rebase.AbortRebase() return err } @@ -123,7 +123,7 @@ func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, s } // apply each patch forward - if err := self.ApplyCustomPatch(false); err != nil { + if err := self.ApplyCustomPatch(false, false); err != nil { // Don't abort the rebase here; this might cause conflicts, so give // the user a chance to resolve them return err @@ -172,7 +172,7 @@ func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, s } // apply each patch in reverse - if err := self.ApplyCustomPatch(true); err != nil { + if err := self.ApplyCustomPatch(true, true); err != nil { _ = self.rebase.AbortRebase() return err } @@ -228,7 +228,7 @@ func (self *PatchCommands) MovePatchIntoIndex(commits []*models.Commit, commitId return err } - if err := self.ApplyCustomPatch(true); err != nil { + if err := self.ApplyCustomPatch(true, true); err != nil { if self.status.WorkingTreeState() == enums.REBASE_MODE_REBASING { _ = self.rebase.AbortRebase() } @@ -282,7 +282,7 @@ func (self *PatchCommands) PullPatchIntoNewCommit( return err } - if err := self.ApplyCustomPatch(true); err != nil { + if err := self.ApplyCustomPatch(true, true); err != nil { _ = self.rebase.AbortRebase() return err } diff --git a/pkg/commands/patch/patch_builder.go b/pkg/commands/patch/patch_builder.go index e18c7d1bb..5ef81c72e 100644 --- a/pkg/commands/patch/patch_builder.go +++ b/pkg/commands/patch/patch_builder.go @@ -65,7 +65,7 @@ func (p *PatchBuilder) Start(from, to string, reverse bool, canRebase bool) { p.fileInfoMap = map[string]*fileInfo{} } -func (p *PatchBuilder) PatchToApply(reverse bool) string { +func (p *PatchBuilder) PatchToApply(reverse bool, turnAddedFilesIntoDiffAgainstEmptyFile bool) string { patch := "" for filename, info := range p.fileInfoMap { @@ -74,9 +74,10 @@ func (p *PatchBuilder) PatchToApply(reverse bool) string { } patch += p.RenderPatchForFile(RenderPatchForFileOpts{ - Filename: filename, - Plain: true, - Reverse: reverse, + Filename: filename, + Plain: true, + Reverse: reverse, + TurnAddedFilesIntoDiffAgainstEmptyFile: turnAddedFilesIntoDiffAgainstEmptyFile, }) } @@ -177,9 +178,10 @@ func (p *PatchBuilder) RemoveFileLineRange(filename string, firstLineIdx, lastLi } type RenderPatchForFileOpts struct { - Filename string - Plain bool - Reverse bool + Filename string + Plain bool + Reverse bool + TurnAddedFilesIntoDiffAgainstEmptyFile bool } func (p *PatchBuilder) RenderPatchForFile(opts RenderPatchForFileOpts) string { @@ -202,8 +204,9 @@ func (p *PatchBuilder) RenderPatchForFile(opts RenderPatchForFileOpts) string { patch := Parse(info.diff). Transform(TransformOpts{ - Reverse: opts.Reverse, - IncludedLineIndices: info.includedLineIndices, + Reverse: opts.Reverse, + TurnAddedFilesIntoDiffAgainstEmptyFile: opts.TurnAddedFilesIntoDiffAgainstEmptyFile, + IncludedLineIndices: info.includedLineIndices, }) if opts.Plain { @@ -220,9 +223,10 @@ func (p *PatchBuilder) renderEachFilePatch(plain bool) []string { sort.Strings(filenames) patches := lo.Map(filenames, func(filename string, _ int) string { return p.RenderPatchForFile(RenderPatchForFileOpts{ - Filename: filename, - Plain: plain, - Reverse: false, + Filename: filename, + Plain: plain, + Reverse: false, + TurnAddedFilesIntoDiffAgainstEmptyFile: true, }) }) output := lo.Filter(patches, func(patch string, _ int) bool { diff --git a/pkg/commands/patch/transform.go b/pkg/commands/patch/transform.go index f861a6540..db35bb4a1 100644 --- a/pkg/commands/patch/transform.go +++ b/pkg/commands/patch/transform.go @@ -1,6 +1,10 @@ package patch -import "github.com/samber/lo" +import ( + "strings" + + "github.com/samber/lo" +) type patchTransformer struct { patch *Patch @@ -22,6 +26,13 @@ type TransformOpts struct { // information it needs to cleanly apply patches FileNameOverride string + // Custom patches tend to work better when treating new files as diffs + // against an empty file. The only case where we need this to be false is + // when moving a custom patch to an earlier commit; in that case the patch + // command would fail with the error "file does not exist in index" if we + // treat it as a diff against an empty file. + TurnAddedFilesIntoDiffAgainstEmptyFile bool + // The indices of lines that should be included in the patch. IncludedLineIndices []int } @@ -61,6 +72,18 @@ func (self *patchTransformer) transformHeader() []string { "--- a/" + self.opts.FileNameOverride, "+++ b/" + self.opts.FileNameOverride, } + } else if self.opts.TurnAddedFilesIntoDiffAgainstEmptyFile { + result := make([]string, 0, len(self.patch.header)) + for idx, line := range self.patch.header { + if strings.HasPrefix(line, "new file mode") { + continue + } + if line == "--- /dev/null" && strings.HasPrefix(self.patch.header[idx+1], "+++ b/") { + line = "--- a/" + self.patch.header[idx+1][6:] + } + result = append(result, line) + } + return result } else { return self.patch.header } diff --git a/pkg/gui/controllers/custom_patch_options_menu_action.go b/pkg/gui/controllers/custom_patch_options_menu_action.go index f5099ae2e..f24607597 100644 --- a/pkg/gui/controllers/custom_patch_options_menu_action.go +++ b/pkg/gui/controllers/custom_patch_options_menu_action.go @@ -237,7 +237,7 @@ func (self *CustomPatchOptionsMenuAction) handleApplyPatch(reverse bool) error { action = "Apply patch in reverse" } self.c.LogAction(action) - if err := self.c.Git().Patch.ApplyCustomPatch(reverse); err != nil { + if err := self.c.Git().Patch.ApplyCustomPatch(reverse, true); err != nil { return err } return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) diff --git a/pkg/gui/controllers/helpers/patch_building_helper.go b/pkg/gui/controllers/helpers/patch_building_helper.go index 423c9f814..dd4c3515a 100644 --- a/pkg/gui/controllers/helpers/patch_building_helper.go +++ b/pkg/gui/controllers/helpers/patch_building_helper.go @@ -82,9 +82,10 @@ func (self *PatchBuildingHelper) RefreshPatchBuildingPanel(opts types.OnFocusOpt } secondaryDiff := self.c.Git().Patch.PatchBuilder.RenderPatchForFile(patch.RenderPatchForFileOpts{ - Filename: path, - Plain: false, - Reverse: false, + Filename: path, + Plain: false, + Reverse: false, + TurnAddedFilesIntoDiffAgainstEmptyFile: true, }) context := self.c.Contexts().CustomPatchBuilder diff --git a/pkg/integration/tests/patch_building/move_to_index_from_added_file_with_conflict.go b/pkg/integration/tests/patch_building/move_to_index_from_added_file_with_conflict.go new file mode 100644 index 000000000..c7acfcd50 --- /dev/null +++ b/pkg/integration/tests/patch_building/move_to_index_from_added_file_with_conflict.go @@ -0,0 +1,96 @@ +package patch_building + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var MoveToIndexFromAddedFileWithConflict = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Move a patch from a file that was added in a commit to the index, causing a conflict", + 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 move from") + shell.UpdateFileAndAdd("file1", "1st line\n2nd line changed\n3rd line\n") + shell.Commit("conflicting change") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Commits(). + Focus(). + Lines( + Contains("conflicting change").IsSelected(), + Contains("commit to move from"), + Contains("first commit"), + ). + SelectNextItem(). + PressEnter() + + t.Views().CommitFiles(). + IsFocused(). + Lines( + Contains("file1").IsSelected(), + ). + PressEnter() + + t.Views().PatchBuilding(). + IsFocused(). + SelectNextItem(). + PressPrimaryAction() + + t.Views().Information().Content(Contains("Building patch")) + + t.Common().SelectPatchOption(Contains("Move patch out into index")) + + t.Common().AcknowledgeConflicts() + + t.Views().Files(). + IsFocused(). + Lines( + Contains("UU").Contains("file1"), + ). + PressEnter() + + t.Views().MergeConflicts(). + IsFocused(). + ContainsLines( + Contains("1st line"), + Contains("<<<<<<< HEAD"), + Contains("======="), + Contains("2nd line changed"), + Contains(">>>>>>>"), + Contains("3rd line"), + ). + SelectNextItem(). + PressPrimaryAction() + + t.Common().ContinueOnConflictsResolved() + + t.ExpectPopup().Alert(). + Title(Equals("Error")). + Content(Contains("Applied patch to 'file1' with conflicts")). + Confirm() + + t.Views().Files(). + IsFocused(). + Lines( + Contains("UU").Contains("file1"), + ). + PressEnter() + + t.Views().MergeConflicts(). + TopLines( + Contains("1st line"), + Contains("<<<<<<< ours"), + Contains("2nd line changed"), + Contains("======="), + Contains("2nd line"), + Contains(">>>>>>> theirs"), + Contains("3rd line"), + ). + IsFocused() + }, +}) diff --git a/pkg/integration/tests/patch_building/move_to_new_commit_from_added_file.go b/pkg/integration/tests/patch_building/move_to_new_commit_from_added_file.go new file mode 100644 index 000000000..97aaea2db --- /dev/null +++ b/pkg/integration/tests/patch_building/move_to_new_commit_from_added_file.go @@ -0,0 +1,88 @@ +package patch_building + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var MoveToNewCommitFromAddedFile = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Move a patch from a file that was added in a commit to a new commit", + 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 move from") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Commits(). + Focus(). + Lines( + Contains("commit to move from").IsSelected(), + Contains("first commit"), + ). + PressEnter() + + t.Views().CommitFiles(). + IsFocused(). + Lines( + Contains("file1").IsSelected(), + ). + PressEnter() + + t.Views().PatchBuilding(). + IsFocused(). + SelectNextItem(). + PressPrimaryAction() + + t.Views().Information().Content(Contains("Building patch")) + + t.Common().SelectPatchOption(Contains("Move patch into new commit")) + + t.ExpectPopup().CommitMessagePanel(). + InitialText(Equals("")). + Type("new commit").Confirm() + + t.Views().Commits(). + IsFocused(). + Lines( + Contains("new commit").IsSelected(), + Contains("commit to move from"), + Contains("first commit"), + ). + PressEnter() + + t.Views().CommitFiles(). + IsFocused(). + Lines( + Contains("M file1").IsSelected(), + ). + Tap(func() { + t.Views().Main().ContainsLines( + Equals(" 1st line"), + Equals("+2nd line"), + Equals(" 3rd line"), + ) + }). + PressEscape() + + t.Views().Commits(). + IsFocused(). + NavigateToLine(Contains("commit to move from")). + PressEnter() + + t.Views().CommitFiles(). + IsFocused(). + Lines( + Contains("A file1").IsSelected(), + ). + Tap(func() { + t.Views().Main().ContainsLines( + Equals("+1st line"), + Equals("+3rd line"), + ) + }) + }, +}) diff --git a/pkg/integration/tests/patch_building/remove_parts_of_added_file.go b/pkg/integration/tests/patch_building/remove_parts_of_added_file.go new file mode 100644 index 000000000..9a0b9a951 --- /dev/null +++ b/pkg/integration/tests/patch_building/remove_parts_of_added_file.go @@ -0,0 +1,56 @@ +package patch_building + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var RemovePartsOfAddedFile = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Remove a custom patch from a file that was added in a commit", + 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() + + t.Views().PatchBuilding(). + IsFocused(). + SelectNextItem(). + PressPrimaryAction() + + t.Views().Information().Content(Contains("Building patch")) + + t.Common().SelectPatchOption(Contains("Remove patch from original commit")) + + 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/patch_building/specific_selection.go b/pkg/integration/tests/patch_building/specific_selection.go index a9dbf9f11..b59b62ccb 100644 --- a/pkg/integration/tests/patch_building/specific_selection.go +++ b/pkg/integration/tests/patch_building/specific_selection.go @@ -126,9 +126,8 @@ var SpecificSelection = NewIntegrationTest(NewIntegrationTestArgs{ t.Views().Secondary().ContainsLines( // direct-file patch Contains(`diff --git a/direct-file b/direct-file`), - Contains(`new file mode 100644`), Contains(`index`), - Contains(`--- /dev/null`), + Contains(`--- a/direct-file`), Contains(`+++ b/direct-file`), Contains(`@@ -0,0 +1 @@`), Contains(`+direct file content`), @@ -149,9 +148,8 @@ var SpecificSelection = NewIntegrationTest(NewIntegrationTestArgs{ Contains(` 1f`), // line-file patch Contains(`diff --git a/line-file b/line-file`), - Contains(`new file mode 100644`), Contains(`index`), - Contains(`--- /dev/null`), + Contains(`--- a/line-file`), Contains(`+++ b/line-file`), Contains(`@@ -0,0 +1,5 @@`), Contains(`+2a`), diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index e2ca3721f..c879b8638 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -232,6 +232,7 @@ var tests = []*components.IntegrationTest{ patch_building.MoveToEarlierCommitFromAddedFile, patch_building.MoveToEarlierCommitNoKeepEmpty, patch_building.MoveToIndex, + patch_building.MoveToIndexFromAddedFileWithConflict, patch_building.MoveToIndexPartOfAdjacentAddedLines, patch_building.MoveToIndexPartial, patch_building.MoveToIndexWithConflict, @@ -239,9 +240,11 @@ var tests = []*components.IntegrationTest{ patch_building.MoveToLaterCommit, patch_building.MoveToLaterCommitPartialHunk, patch_building.MoveToNewCommit, + patch_building.MoveToNewCommitFromAddedFile, patch_building.MoveToNewCommitFromDeletedFile, patch_building.MoveToNewCommitPartialHunk, patch_building.RemoveFromCommit, + patch_building.RemovePartsOfAddedFile, patch_building.ResetWithEscape, patch_building.SelectAllFiles, patch_building.SpecificSelection, |