Skip to content

Commit f6d1333

Browse files
committed
Add custom patch command "Move patch into new commit before the original commit"
This is often useful to extract preparatory refactoring commits from a bigger one.
1 parent 5321101 commit f6d1333

11 files changed

+244
-6
lines changed

pkg/commands/git_commands/patch.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,29 @@ func (self *PatchCommands) PullPatchIntoNewCommit(
314314
return self.rebase.ContinueRebase()
315315
}
316316

317+
func (self *PatchCommands) PullPatchIntoNewCommitBefore(
318+
commits []*models.Commit,
319+
commitIdx int,
320+
commitSummary string,
321+
commitDescription string,
322+
) error {
323+
if err := self.rebase.BeginInteractiveRebaseForCommit(commits, commitIdx+1, true); err != nil {
324+
return err
325+
}
326+
327+
if err := self.ApplyCustomPatch(false, false); err != nil {
328+
_ = self.rebase.AbortRebase()
329+
return err
330+
}
331+
332+
if err := self.commit.CommitCmdObj(commitSummary, commitDescription, false).Run(); err != nil {
333+
return err
334+
}
335+
336+
self.PatchBuilder.Reset()
337+
return self.rebase.ContinueRebase()
338+
}
339+
317340
// We have just applied a patch in reverse to discard it from a commit; if we
318341
// now try to apply the patch again to move it to a later commit, or to the
319342
// index, then this would conflict "with itself" in case the patch contained

pkg/gui/controllers/custom_patch_options_menu_action.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ func (self *CustomPatchOptionsMenuAction) Call() error {
6464
OnPress: self.handlePullPatchIntoNewCommit,
6565
Key: 'n',
6666
},
67+
{
68+
Label: self.c.Tr.MovePatchIntoNewCommitBefore,
69+
Tooltip: self.c.Tr.MovePatchIntoNewCommitBeforeTooltip,
70+
OnPress: self.handlePullPatchIntoNewCommitBefore,
71+
Key: 'N',
72+
},
6773
}...)
6874

6975
if self.c.Context().Current().GetKey() == self.c.Contexts().LocalCommits.GetKey() {
@@ -223,6 +229,41 @@ func (self *CustomPatchOptionsMenuAction) handlePullPatchIntoNewCommit() error {
223229
return nil
224230
}
225231

232+
func (self *CustomPatchOptionsMenuAction) handlePullPatchIntoNewCommitBefore() error {
233+
if ok, err := self.validateNormalWorkingTreeState(); !ok {
234+
return err
235+
}
236+
237+
self.returnFocusFromPatchExplorerIfNecessary()
238+
239+
commitIndex := self.getPatchCommitIndex()
240+
self.c.Helpers().Commits.OpenCommitMessagePanel(
241+
&helpers.OpenCommitMessagePanelOpts{
242+
// Pass a commit index of one less than the moved-from commit, so that
243+
// you can press up arrow once to recall the original commit message:
244+
CommitIndex: commitIndex - 1,
245+
InitialMessage: "",
246+
SummaryTitle: self.c.Tr.CommitSummaryTitle,
247+
DescriptionTitle: self.c.Tr.CommitDescriptionTitle,
248+
PreserveMessage: false,
249+
OnConfirm: func(summary string, description string) error {
250+
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
251+
self.c.Helpers().Commits.CloseCommitMessagePanel()
252+
self.c.LogAction(self.c.Tr.Actions.MovePatchIntoNewCommit)
253+
err := self.c.Git().Patch.PullPatchIntoNewCommitBefore(self.c.Model().Commits, commitIndex, summary, description)
254+
if err := self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err); err != nil {
255+
return err
256+
}
257+
self.c.Context().Push(self.c.Contexts().LocalCommits, types.OnFocusOpts{})
258+
return nil
259+
})
260+
},
261+
},
262+
)
263+
264+
return nil
265+
}
266+
226267
func (self *CustomPatchOptionsMenuAction) handleApplyPatch(reverse bool) error {
227268
self.returnFocusFromPatchExplorerIfNecessary()
228269

pkg/i18n/english.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -813,6 +813,8 @@ type TranslationSet struct {
813813
MovePatchOutIntoIndexTooltip string
814814
MovePatchIntoNewCommit string
815815
MovePatchIntoNewCommitTooltip string
816+
MovePatchIntoNewCommitBefore string
817+
MovePatchIntoNewCommitBeforeTooltip string
816818
MovePatchToSelectedCommit string
817819
MovePatchToSelectedCommitTooltip string
818820
CopyPatchToClipboard string
@@ -1896,8 +1898,10 @@ func EnglishTranslationSet() *TranslationSet {
18961898
RemovePatchFromOriginalCommitTooltip: "Remove the current patch from its commit. This is achieved by starting an interactive rebase at the commit, applying the patch in reverse, and then continuing the rebase. If later commits depend on the patch, you may need to resolve conflicts.",
18971899
MovePatchOutIntoIndex: "Move patch out into index",
18981900
MovePatchOutIntoIndexTooltip: "Move the patch out of its commit and into the index. This is achieved by starting an interactive rebase at the commit, applying the patch in reverse, continuing the rebase to completion, and then applying the patch to the index. If later commits depend on the patch, you may need to resolve conflicts.",
1899-
MovePatchIntoNewCommit: "Move patch into new commit",
1901+
MovePatchIntoNewCommit: "Move patch into new commit after the original commit",
19001902
MovePatchIntoNewCommitTooltip: "Move the patch out of its commit and into a new commit sitting on top of the original commit. This is achieved by starting an interactive rebase at the original commit, applying the patch in reverse, then applying the patch to the index and committing it as a new commit, before continuing the rebase to completion. If later commits depend on the patch, you may need to resolve conflicts.",
1903+
MovePatchIntoNewCommitBefore: "Move patch into new commit before the original commit",
1904+
MovePatchIntoNewCommitBeforeTooltip: "Move the patch out of its commit and into a new commit before the original commit. This works best when the custom patch contains only entire hunks or even entire files; if it contains partial hunks, you are likely to get conflicts.",
19011905
MovePatchToSelectedCommit: "Move patch to selected commit (%s)",
19021906
MovePatchToSelectedCommitTooltip: "Move the patch out of its original commit and into the selected commit. This is achieved by starting an interactive rebase at the original commit, applying the patch in reverse, then continuing the rebase up to the selected commit, before applying the patch forward and amending the selected commit. The rebase is then continued to completion. If commits between the source and destination commit depend on the patch, you may need to resolve conflicts.",
19031907
CopyPatchToClipboard: "Copy patch to clipboard",

pkg/integration/tests/patch_building/move_to_new_commit.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ var MoveToNewCommit = NewIntegrationTest(NewIntegrationTestArgs{
4848

4949
t.Views().Information().Content(Contains("Building patch"))
5050

51-
t.Common().SelectPatchOption(Contains("Move patch into new commit"))
51+
t.Common().SelectPatchOption(Contains("Move patch into new commit after the original commit"))
5252

5353
t.ExpectPopup().CommitMessagePanel().
5454
InitialText(Equals("")).
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package patch_building
2+
3+
import (
4+
"github.com/jesseduffield/lazygit/pkg/config"
5+
. "github.com/jesseduffield/lazygit/pkg/integration/components"
6+
)
7+
8+
var MoveToNewCommitBefore = NewIntegrationTest(NewIntegrationTestArgs{
9+
Description: "Move a patch from a commit to a new commit before the original one",
10+
ExtraCmdArgs: []string{},
11+
Skip: false,
12+
GitVersion: AtLeast("2.26.0"),
13+
SetupConfig: func(config *config.AppConfig) {},
14+
SetupRepo: func(shell *Shell) {
15+
shell.CreateDir("dir")
16+
shell.CreateFileAndAdd("dir/file1", "file1 content")
17+
shell.CreateFileAndAdd("dir/file2", "file2 content")
18+
shell.Commit("first commit")
19+
20+
shell.UpdateFileAndAdd("dir/file1", "file1 content with old changes")
21+
shell.DeleteFileAndAdd("dir/file2")
22+
shell.CreateFileAndAdd("dir/file3", "file3 content")
23+
shell.Commit("commit to move from")
24+
25+
shell.UpdateFileAndAdd("dir/file1", "file1 content with new changes")
26+
shell.Commit("third commit")
27+
},
28+
Run: func(t *TestDriver, keys config.KeybindingConfig) {
29+
t.Views().Commits().
30+
Focus().
31+
Lines(
32+
Contains("third commit").IsSelected(),
33+
Contains("commit to move from"),
34+
Contains("first commit"),
35+
).
36+
SelectNextItem().
37+
PressEnter()
38+
39+
t.Views().CommitFiles().
40+
IsFocused().
41+
Lines(
42+
Contains("dir").IsSelected(),
43+
Contains(" M file1"),
44+
Contains(" D file2"),
45+
Contains(" A file3"),
46+
).
47+
PressPrimaryAction().
48+
PressEscape()
49+
50+
t.Views().Information().Content(Contains("Building patch"))
51+
52+
t.Common().SelectPatchOption(Contains("Move patch into new commit before the original commit"))
53+
54+
t.ExpectPopup().CommitMessagePanel().
55+
InitialText(Equals("")).
56+
Type("new commit").Confirm()
57+
58+
t.Views().Commits().
59+
IsFocused().
60+
Lines(
61+
Contains("third commit"),
62+
Contains("commit to move from").IsSelected(),
63+
Contains("new commit"),
64+
Contains("first commit"),
65+
).
66+
SelectNextItem().
67+
PressEnter()
68+
69+
t.Views().CommitFiles().
70+
IsFocused().
71+
Lines(
72+
Contains("dir").IsSelected(),
73+
Contains(" M file1"),
74+
Contains(" D file2"),
75+
Contains(" A file3"),
76+
).
77+
PressEscape()
78+
79+
t.Views().Commits().
80+
IsFocused().
81+
SelectPreviousItem().
82+
PressEnter()
83+
84+
// the original commit has no more files in it
85+
t.Views().CommitFiles().
86+
IsFocused().
87+
Lines(
88+
Contains("(none)"),
89+
)
90+
},
91+
})
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package patch_building
2+
3+
import (
4+
"github.com/jesseduffield/lazygit/pkg/config"
5+
. "github.com/jesseduffield/lazygit/pkg/integration/components"
6+
)
7+
8+
var MoveToNewCommitBeforeNoKeepEmpty = NewIntegrationTest(NewIntegrationTestArgs{
9+
Description: "Move a patch from a commit to a new commit before the original one, for older git versions that don't keep the empty commit",
10+
ExtraCmdArgs: []string{},
11+
Skip: false,
12+
GitVersion: Before("2.26.0"),
13+
SetupConfig: func(config *config.AppConfig) {},
14+
SetupRepo: func(shell *Shell) {
15+
shell.CreateDir("dir")
16+
shell.CreateFileAndAdd("dir/file1", "file1 content")
17+
shell.CreateFileAndAdd("dir/file2", "file2 content")
18+
shell.Commit("first commit")
19+
20+
shell.UpdateFileAndAdd("dir/file1", "file1 content with old changes")
21+
shell.DeleteFileAndAdd("dir/file2")
22+
shell.CreateFileAndAdd("dir/file3", "file3 content")
23+
shell.Commit("commit to move from")
24+
25+
shell.UpdateFileAndAdd("dir/file1", "file1 content with new changes")
26+
shell.Commit("third commit")
27+
},
28+
Run: func(t *TestDriver, keys config.KeybindingConfig) {
29+
t.Views().Commits().
30+
Focus().
31+
Lines(
32+
Contains("third commit").IsSelected(),
33+
Contains("commit to move from"),
34+
Contains("first commit"),
35+
).
36+
SelectNextItem().
37+
PressEnter()
38+
39+
t.Views().CommitFiles().
40+
IsFocused().
41+
Lines(
42+
Contains("dir").IsSelected(),
43+
Contains(" M file1"),
44+
Contains(" D file2"),
45+
Contains(" A file3"),
46+
).
47+
PressPrimaryAction().
48+
PressEscape()
49+
50+
t.Views().Information().Content(Contains("Building patch"))
51+
52+
t.Common().SelectPatchOption(Contains("Move patch into new commit before the original commit"))
53+
54+
t.ExpectPopup().CommitMessagePanel().
55+
InitialText(Equals("")).
56+
Type("new commit").Confirm()
57+
58+
t.Views().Commits().
59+
IsFocused().
60+
Lines(
61+
Contains("third commit"),
62+
Contains("new commit").IsSelected(),
63+
Contains("first commit"),
64+
).
65+
PressEnter()
66+
67+
t.Views().CommitFiles().
68+
IsFocused().
69+
Lines(
70+
Contains("dir").IsSelected(),
71+
Contains(" M file1"),
72+
Contains(" D file2"),
73+
Contains(" A file3"),
74+
).
75+
PressEscape()
76+
},
77+
})

pkg/integration/tests/patch_building/move_to_new_commit_from_added_file.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ var MoveToNewCommitFromAddedFile = NewIntegrationTest(NewIntegrationTestArgs{
3939

4040
t.Views().Information().Content(Contains("Building patch"))
4141

42-
t.Common().SelectPatchOption(Contains("Move patch into new commit"))
42+
t.Common().SelectPatchOption(Contains("Move patch into new commit after the original commit"))
4343

4444
t.ExpectPopup().CommitMessagePanel().
4545
InitialText(Equals("")).

pkg/integration/tests/patch_building/move_to_new_commit_from_deleted_file.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ var MoveToNewCommitFromDeletedFile = NewIntegrationTest(NewIntegrationTestArgs{
3939

4040
t.Views().Information().Content(Contains("Building patch"))
4141

42-
t.Common().SelectPatchOption(Contains("Move patch into new commit"))
42+
t.Common().SelectPatchOption(Contains("Move patch into new commit after the original commit"))
4343

4444
t.ExpectPopup().CommitMessagePanel().
4545
InitialText(Equals("")).

pkg/integration/tests/patch_building/move_to_new_commit_in_last_commit_of_stacked_branch.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ var MoveToNewCommitInLastCommitOfStackedBranch = NewIntegrationTest(NewIntegrati
5252

5353
t.Views().Information().Content(Contains("Building patch"))
5454

55-
t.Common().SelectPatchOption(Contains("Move patch into new commit"))
55+
t.Common().SelectPatchOption(Contains("Move patch into new commit after the original commit"))
5656

5757
t.ExpectPopup().CommitMessagePanel().
5858
InitialText(Equals("")).

pkg/integration/tests/patch_building/move_to_new_commit_partial_hunk.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ var MoveToNewCommitPartialHunk = NewIntegrationTest(NewIntegrationTestArgs{
4444

4545
t.Views().Information().Content(Contains("Building patch"))
4646

47-
t.Common().SelectPatchOption(Contains("Move patch into new commit"))
47+
t.Common().SelectPatchOption(Contains("Move patch into new commit after the original commit"))
4848

4949
t.ExpectPopup().CommitMessagePanel().
5050
InitialText(Equals("")).

pkg/integration/tests/test_list.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,8 @@ var tests = []*components.IntegrationTest{
310310
patch_building.MoveToLaterCommit,
311311
patch_building.MoveToLaterCommitPartialHunk,
312312
patch_building.MoveToNewCommit,
313+
patch_building.MoveToNewCommitBefore,
314+
patch_building.MoveToNewCommitBeforeNoKeepEmpty,
313315
patch_building.MoveToNewCommitFromAddedFile,
314316
patch_building.MoveToNewCommitFromDeletedFile,
315317
patch_building.MoveToNewCommitInLastCommitOfStackedBranch,

0 commit comments

Comments
 (0)