Build applied patches from the focused diff

Applying and reverse-applying custom patches must keep their staging,
dirty-worktree, and conflict behavior after the patch-building panel is
removed. Select the source lines in commit diffs while retaining every
assertion about the resulting working tree and conflict resolution.

Co-Authored-By: GitHub Copilot <copilot@github.com>
This commit is contained in:
Stefan Haller
2026-10-01 17:28:30 +02:00
co-authored by GitHub Copilot
parent c966efe1f3
commit 115e25e43c
6 changed files with 73 additions and 63 deletions
@@ -1,15 +1,17 @@
package patch_building
package main_view
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var Apply = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Apply a custom patch",
var ApplyCustomPatch = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Apply a custom patch built from a branch commit's focused diff",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupConfig: func(config *config.AppConfig) {
config.GetUserConfig().Gui.UseHunkModeInStagingView = false
},
SetupRepo: func(shell *Shell) {
shell.NewBranch("branch-a")
shell.CreateFileAndAdd("file1", "first line\n")
@@ -18,7 +20,6 @@ var Apply = NewIntegrationTest(NewIntegrationTestArgs{
shell.NewBranch("branch-b")
shell.UpdateFileAndAdd("file1", "first line\nsecond line\n")
shell.Commit("update")
shell.Checkout("branch-a")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
@@ -44,12 +45,15 @@ var Apply = NewIntegrationTest(NewIntegrationTestArgs{
Lines(
Contains("M file1").IsSelected(),
).
Press(keys.Universal.FocusMainView)
t.Views().Main().
IsFocused().
SelectedLines(Contains("+second line")).
PressPrimaryAction()
t.Views().Information().Content(Contains("Building patch"))
t.Views().Secondary().Content(Contains("second line"))
t.Common().SelectPatchOption(MatchesRegexp(`Apply patch$`))
t.Views().Files().
@@ -57,8 +61,6 @@ var Apply = NewIntegrationTest(NewIntegrationTestArgs{
Lines(
Contains("file1").IsSelected(),
)
t.Views().Secondary().
Content(Contains("second line"))
t.Views().Secondary().Content(Contains("second line"))
},
})
@@ -1,15 +1,17 @@
package patch_building
package main_view
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var ApplyInReverse = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Apply a custom patch in reverse",
var ApplyCustomPatchInReverse = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Apply a custom patch built from a focused commit diff in reverse",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupConfig: func(config *config.AppConfig) {
config.GetUserConfig().Gui.UseHunkModeInStagingView = false
},
SetupRepo: func(shell *Shell) {
shell.CreateFileAndAdd("file1", "file1 content\n")
shell.CreateFileAndAdd("file2", "file2 content\n")
@@ -30,13 +32,15 @@ var ApplyInReverse = NewIntegrationTest(NewIntegrationTestArgs{
Equals(" A file1"),
Equals(" A file2"),
).
SelectNextItem().
Press(keys.Universal.FocusMainView)
t.Views().Main().
IsFocused().
SelectedLines(Contains("+file1 content")).
PressPrimaryAction()
t.Views().Information().Content(Contains("Building patch"))
t.Views().Secondary().Content(Contains("+file1 content"))
t.Common().SelectPatchOption(Contains("Apply patch in reverse"))
t.Views().Files().
@@ -44,8 +48,6 @@ var ApplyInReverse = NewIntegrationTest(NewIntegrationTestArgs{
Lines(
Contains("D").Contains("file1").IsSelected(),
)
t.Views().Secondary().
Content(Contains("-file1 content"))
t.Views().Secondary().Content(Contains("-file1 content"))
},
})
@@ -1,15 +1,17 @@
package patch_building
package main_view
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var ApplyInReverseWithConflict = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Apply a custom patch in reverse, resulting in a conflict",
var ApplyCustomPatchInReverseWithConflict = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Apply a multi-file custom patch in reverse when one file conflicts",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupConfig: func(config *config.AppConfig) {
config.GetUserConfig().Gui.UseHunkModeInStagingView = false
},
SetupRepo: func(shell *Shell) {
shell.CreateFileAndAdd("file1", "file1 content\n")
shell.CreateFileAndAdd("file2", "file2 content\n")
@@ -38,21 +40,17 @@ var ApplyInReverseWithConflict = NewIntegrationTest(NewIntegrationTestArgs{
Equals(" M file1"),
Equals(" M file2"),
).
SelectNextItem().
// Add both files to the patch; the first will conflict, the second won't
PressPrimaryAction().
Tap(func() {
t.Views().Information().Content(Contains("Building patch"))
Press(keys.Universal.FocusMainView)
t.Views().Secondary().Content(
Contains("+more file1 content"))
}).
SelectNextItem().
t.Views().Main().
IsFocused().
SelectedLines(Contains("+more file1 content")).
Press(keys.Universal.ToggleRangeSelect).
NavigateToLine(Contains("+more file2 content")).
PressPrimaryAction()
t.Views().Secondary().Content(
Contains("+more file1 content").Contains("+more file2 content"))
t.Views().Information().Content(Contains("Building patch"))
t.Views().Secondary().Content(Contains("+more file1 content").Contains("+more file2 content"))
t.Common().SelectPatchOption(Contains("Apply patch in reverse"))
t.ExpectPopup().Alert().
@@ -87,12 +85,10 @@ var ApplyInReverseWithConflict = NewIntegrationTest(NewIntegrationTestArgs{
Equals(" M file1").IsSelected(),
Equals(" M file2"),
)
t.Views().Secondary().
ContainsLines(
Contains(" file1 content"),
Contains("-more file1 content"),
Contains("-even more file1"),
)
t.Views().Secondary().ContainsLines(
Contains(" file1 content"),
Contains("-more file1 content"),
Contains("-even more file1"),
)
},
})
@@ -1,15 +1,17 @@
package patch_building
package main_view
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var ApplyWithModifiedFileNoConflict = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Apply a custom patch, with a modified file in the working tree that does not conflict with the patch",
var ApplyCustomPatchWithModifiedFile = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Apply a custom patch alongside a non-conflicting working-tree change",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupConfig: func(config *config.AppConfig) {
config.GetUserConfig().Gui.UseHunkModeInStagingView = false
},
SetupRepo: func(shell *Shell) {
shell.NewBranch("branch-a")
shell.CreateFileAndAdd("file1", "1\n2\n3\n")
@@ -45,12 +47,15 @@ var ApplyWithModifiedFileNoConflict = NewIntegrationTest(NewIntegrationTestArgs{
Lines(
Equals("M file1").IsSelected(),
).
Press(keys.Universal.FocusMainView)
t.Views().Main().
IsFocused().
SelectedLines(Contains("+4")).
PressPrimaryAction()
t.Views().Information().Content(Contains("Building patch"))
t.Views().Secondary().Content(Contains("3\n+4"))
t.Common().SelectPatchOption(MatchesRegexp(`Apply patch$`))
t.ExpectPopup().Confirmation().Title(Equals("Must stage files")).
@@ -62,8 +67,6 @@ var ApplyWithModifiedFileNoConflict = NewIntegrationTest(NewIntegrationTestArgs{
Lines(
Equals("M file1").IsSelected(),
)
t.Views().Secondary().
Content(Contains("-1\n+11\n 2\n 3\n+4"))
t.Views().Secondary().Content(Contains("-1\n+11\n 2\n 3\n+4"))
},
})
@@ -1,15 +1,17 @@
package patch_building
package main_view
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var ApplyWithModifiedFileConflict = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Apply a custom patch, with a modified file in the working tree that conflicts with the patch",
var ApplyCustomPatchWithModifiedFileConflict = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Apply a custom patch that conflicts with a working-tree change",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupConfig: func(config *config.AppConfig) {
config.GetUserConfig().Gui.UseHunkModeInStagingView = false
},
SetupRepo: func(shell *Shell) {
shell.NewBranch("branch-a")
shell.CreateFileAndAdd("file1", "1\n2\n3\n")
@@ -45,12 +47,17 @@ var ApplyWithModifiedFileConflict = NewIntegrationTest(NewIntegrationTestArgs{
Lines(
Equals("M file1").IsSelected(),
).
Press(keys.Universal.FocusMainView)
t.Views().Main().
IsFocused().
SelectedLines(Contains("-1")).
Press(keys.Universal.ToggleRangeSelect).
Press(keys.Universal.NextItem).
PressPrimaryAction()
t.Views().Information().Content(Contains("Building patch"))
t.Views().Secondary().Content(Contains("-1\n+11\n"))
t.Common().SelectPatchOption(MatchesRegexp(`Apply patch$`))
t.ExpectPopup().Confirmation().Title(Equals("Must stage files")).
+5 -5
View File
@@ -368,6 +368,11 @@ var tests = []*components.IntegrationTest{
interactive_rebase.SwapWithConflict,
interactive_rebase.ViewFilesOfTodoEntries,
main_view.AdvanceAfterStagingShiftsLineNumbers,
main_view.ApplyCustomPatch,
main_view.ApplyCustomPatchInReverse,
main_view.ApplyCustomPatchInReverseWithConflict,
main_view.ApplyCustomPatchWithModifiedFile,
main_view.ApplyCustomPatchWithModifiedFileConflict,
main_view.BuildPatchFromACommitsDiff,
main_view.BuildPatchFromAReflogEntry,
main_view.BuildPatchFromAWholeCommitsDiff,
@@ -487,11 +492,6 @@ var tests = []*components.IntegrationTest{
misc.RecentReposReftableRepo,
misc.RecentReposWithLongNames,
misc.StartInGitDir,
patch_building.Apply,
patch_building.ApplyInReverse,
patch_building.ApplyInReverseWithConflict,
patch_building.ApplyWithModifiedFileConflict,
patch_building.ApplyWithModifiedFileNoConflict,
patch_building.CopyRenamedFileDiff,
patch_building.RemoveFromCommit,
patch_building.RemovePartsOfAddedFile,