mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-10-05 21:46:49 -04:00
Act on a whole file's changes as acting on the file
The diff of a deleted file is its content going away, so putting every line of it into the index leaves an empty file there (modified in the index, deleted in the working tree) rather than the deletion the user selected. The reverse case matches: the diff of an added file is its whole content, and taking all of it back out of the index leaves the file tracked and empty rather than untracked again. In the staging view you had to enter such a file deliberately to reach these cases, but stepping through a directory's diff hunk by hunk runs into them routinely. Selecting every change of a file says "this file", so stage or unstage the file itself. This applies to any file, since applying a file's whole diff amounts to the same thing everywhere else. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
8e0a02a3b8
commit
1d5ff34a71
@@ -171,6 +171,19 @@ func (self *WorkingTreeDiffActions) applyDiffLines(
|
||||
}
|
||||
}
|
||||
|
||||
// Acting on every change of a file is acting on the file itself, and saying so is
|
||||
// not the same as applying its diff. The diff of a deleted file is its content
|
||||
// going away, and putting that into the index line by line leaves an empty file
|
||||
// there rather than the deletion; the diff of an added one is its whole content,
|
||||
// and taking that back out leaves an empty file in the index rather than an
|
||||
// untracked one.
|
||||
if opts.Cached && len(patchLineIndices) == changeLineCount(parsedPatch) {
|
||||
if opts.Reverse {
|
||||
return self.c.Git().WorkingTree.UnStageFile(file.Names(), file.Tracked)
|
||||
}
|
||||
return self.c.Git().WorkingTree.StageFile(file.GetPath())
|
||||
}
|
||||
|
||||
patchToApply := parsedPatch.
|
||||
Transform(patch.TransformOpts{
|
||||
Reverse: opts.Reverse,
|
||||
@@ -184,3 +197,11 @@ func (self *WorkingTreeDiffActions) applyDiffLines(
|
||||
|
||||
return self.c.Git().Patch.ApplyPatch(patchToApply, opts)
|
||||
}
|
||||
|
||||
// changeLineCount returns how many of a patch's lines are changes rather than context
|
||||
// or header. A selection of the whole diff covers exactly that many.
|
||||
func changeLineCount(p *patch.Patch) int {
|
||||
return lo.CountBy(p.Lines(), func(line *patch.PatchLine) bool {
|
||||
return line.IsAddition() || line.IsDeletion()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package main_view
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var StageDeletedFile = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Acting on the whole of a file's block in a directory's diff acts on the file: staging a deletion, and unstaging an addition",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(cfg *config.AppConfig) {
|
||||
cfg.GetUserConfig().Gui.UseHunkModeInStagingView = false
|
||||
},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateFileAndAdd("fileA", "a\n")
|
||||
shell.CreateFileAndAdd("fileB", "b1\nb2\n")
|
||||
shell.Commit("one")
|
||||
|
||||
shell.UpdateFile("fileA", "a\nfromA\n")
|
||||
shell.DeleteFile("fileB")
|
||||
shell.CreateFileAndAdd("fileC", "c1\nc2\n")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("▼ /").IsSelected(),
|
||||
Contains(" M fileA"),
|
||||
Contains(" D fileB"),
|
||||
Contains("A fileC"),
|
||||
).
|
||||
Press(keys.Universal.FocusMainView)
|
||||
|
||||
// Select everything the deleted file contributes to the diff.
|
||||
t.Views().Main().
|
||||
IsFocused().
|
||||
NavigateToLine(Contains("-b1")).
|
||||
Press(keys.Universal.ToggleRangeSelect).
|
||||
NavigateToLine(Contains("-b2")).
|
||||
SelectedLines(
|
||||
Contains("-b1"),
|
||||
Contains("-b2"),
|
||||
).
|
||||
PressPrimaryAction()
|
||||
|
||||
// The file is staged as deleted. Applying its lines as a patch would have left
|
||||
// an empty file in the index instead, which is not what deleting a file means.
|
||||
t.Views().Files().Lines(
|
||||
Contains("▼ /"),
|
||||
Contains(" M fileA"),
|
||||
Contains("D fileB"),
|
||||
Contains("A fileC"),
|
||||
)
|
||||
|
||||
// The same the other way round: taking the whole of an added file back out of
|
||||
// the index leaves it untracked, rather than tracked and empty.
|
||||
t.Views().Main().
|
||||
IsFocused().
|
||||
Press(keys.Universal.TogglePanel)
|
||||
|
||||
t.Views().Secondary().
|
||||
IsFocused().
|
||||
NavigateToLine(Contains("+c1")).
|
||||
Press(keys.Universal.ToggleRangeSelect).
|
||||
NavigateToLine(Contains("+c2")).
|
||||
SelectedLines(
|
||||
Contains("+c1"),
|
||||
Contains("+c2"),
|
||||
).
|
||||
PressPrimaryAction()
|
||||
|
||||
t.Views().Files().Lines(
|
||||
Contains("▼ /"),
|
||||
Contains(" M fileA"),
|
||||
Contains("D fileB"),
|
||||
Contains("?? fileC"),
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -409,6 +409,7 @@ var tests = []*components.IntegrationTest{
|
||||
main_view.SelectVisibleHunkOnFocusingMainView,
|
||||
main_view.SelectionCommandsOnlyWhereTheyApply,
|
||||
main_view.SelectionOverTheCustomPatch,
|
||||
main_view.StageDeletedFile,
|
||||
main_view.StageDiffLines,
|
||||
main_view.StageDiffLinesOfAPathWithASpace,
|
||||
main_view.StageRangeSpanningFiles,
|
||||
|
||||
Reference in New Issue
Block a user