Show errors for unresolvable diff lines (#6100)

If lazygit can't work out what diff lines it is supposed to act on when
staging a hunk (for example because of bugs like the one in #6099, or
maybe because of a misbehaving OSC-1717 diff renderer), it used to do
nothing and silently ignore the error. Now it shows an error popup to
the user in such a case, so that they can report the bug more easily.
This commit is contained in:
Stefan Haller
2026-10-10 13:07:29 +02:00
committed by GitHub
10 changed files with 408 additions and 86 deletions
+77 -37
View File
@@ -1,6 +1,7 @@
package controllers
import (
"errors"
"fmt"
"github.com/jesseduffield/generics/set"
@@ -9,6 +10,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/jesseduffield/lazygit/pkg/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
@@ -104,6 +106,10 @@ func (self *CommitDiffActions) PrimaryAction(pane types.DiffPaneContext, firstBu
if len(lines) == 0 {
return nil
}
selection, err := self.selectionByFile(lines)
if err != nil {
return err
}
patchBuilder := self.c.Git().Patch.PatchBuilder
from, reverse := self.patchEndpoints(target)
@@ -121,14 +127,16 @@ func (self *CommitDiffActions) PrimaryAction(pane types.DiffPaneContext, firstBu
patchBuilder.Start(from, target.to, reverse, target.canRebase)
}
if err := self.togglePatchLines(lines); err != nil {
return err
}
err := self.togglePatchLines(selection)
// Taking the last line back out ends the patch rather than leaving an empty
// one, so that the pane previewing it and the marks over the diff go with it.
// The same goes for a patch just started that no line could be put into.
if patchBuilder.IsEmpty() {
patchBuilder.Reset()
}
if err != nil {
return err
}
// The diff on screen is the one the marks belong to, so they can be brought up
// to date at once rather than waiting for the render below.
@@ -167,11 +175,15 @@ func (self *CommitDiffActions) removePatchLines(
}
patchBuilder := self.c.Git().Patch.PatchBuilder
files := self.filesInDiff()
files, err := self.filesInDiff()
if err != nil {
return err
}
indicesByFilename := map[string][]int{}
for path, ordinals := range self.c.Helpers().DiffLine.ChangeLineOrdinals(self.customPatchDiff(), lines) {
filename := self.patchBuilderPath(path)
if filename == "" {
continue
return diffLinesFileNotFoundError(self.c, path)
}
included := patchBuilder.IncludedChangeLineIndices(filename)
indices := []int{}
@@ -181,8 +193,11 @@ func (self *CommitDiffActions) removePatchLines(
}
}
if len(indices) == 0 {
continue
return diffLinesNotFoundError(self.c, filename)
}
indicesByFilename[filename] = indices
}
for filename, indices := range indicesByFilename {
if err := patchBuilder.RemoveFileLineRange(filename, files.previousPath(filename), indices); err != nil {
return err
}
@@ -223,7 +238,12 @@ func (self *CommitDiffActions) DiscardSelection(pane types.DiffPaneContext, firs
}
commitIndex := self.indexOfTargetCommit(target)
if commitIndex == -1 {
return nil
return errors.New(utils.ResolvePlaceholderString(self.c.Tr.CommitNotInCommitsPanel,
map[string]string{"hash": utils.ShortHash(target.to)}))
}
selection, err := self.selectionByFile(lines)
if err != nil {
return err
}
patchBuilder := self.c.Git().Patch.PatchBuilder
@@ -238,7 +258,10 @@ func (self *CommitDiffActions) DiscardSelection(pane types.DiffPaneContext, firs
from, reverse := self.patchEndpoints(target)
patchBuilder.Reset()
patchBuilder.Start(from, target.to, reverse, target.canRebase)
if err := self.togglePatchLines(lines); err != nil {
if err := self.togglePatchLines(selection); err != nil {
// This patch exists only to be removed from the commit, so don't leave it
// behind.
patchBuilder.Reset()
return err
}
if patchBuilder.IsEmpty() {
@@ -318,41 +341,61 @@ func (self *CommitDiffActions) PatchInclusion() func(types.DiffLineInfo) bool {
}
}
// togglePatchLines takes the given lines of the commit's diff into the custom patch, or
// out of it. The first line of the selection decides which of the two happens, once for
// the whole selection: pointing at a line that is already in the patch takes the whole
// selection out of it, as toggling a selection of files in the commit files panel does.
func (self *CommitDiffActions) togglePatchLines(lines []types.DiffLineInfo) error {
patchBuilder := self.c.Git().Patch.PatchBuilder
// commitDiffSelection is the selected lines of a commit's diff, by the file they belong
// to: a patch is built a file at a time, while a selection can span several of them.
type commitDiffSelection struct {
// What the diff says about each of the files it covers.
files commitDiffFiles
// The files the selection covers, in the order the diff shows them, and per file the
// lines of it that are selected: a patch is built a file at a time, while a selection
// can span several of them.
paths := []string{}
linesByPath := map[string][]patch.LineIdentity{}
// The files the selection covers, in the order the diff shows them.
paths []string
// Per file, the lines of it that are selected.
linesByPath map[string][]patch.LineIdentity
}
// selectionByFile groups the given lines of the commit's diff by the file they belong
// to. If a line belongs to no file of the diff, the diff on screen and the one the
// patch is built from disagree, so this fails and we build nothing.
func (self *CommitDiffActions) selectionByFile(lines []types.DiffLineInfo) (commitDiffSelection, error) {
files, err := self.filesInDiff()
if err != nil {
return commitDiffSelection{}, err
}
selection := commitDiffSelection{files: files, linesByPath: map[string][]patch.LineIdentity{}}
for _, line := range lines {
path := self.patchBuilderPath(line.Path)
if path == "" {
continue
if _, ok := files[path]; !ok {
return commitDiffSelection{}, diffLinesFileNotFoundError(self.c, line.Path)
}
if _, seen := linesByPath[path]; !seen {
paths = append(paths, path)
if _, seen := selection.linesByPath[path]; !seen {
selection.paths = append(selection.paths, path)
}
linesByPath[path] = append(linesByPath[path], line.PatchLineIdentity())
}
if len(paths) == 0 {
return nil
selection.linesByPath[path] = append(selection.linesByPath[path], line.PatchLineIdentity())
}
return selection, nil
}
// togglePatchLines takes the selected lines of the commit's diff into the custom patch,
// or out of it. The first line of the selection decides which of the two happens, once
// for the whole selection: pointing at a line that is already in the patch takes the
// whole selection out of it, as toggling a selection of files in the commit files panel
// does.
func (self *CommitDiffActions) togglePatchLines(selection commitDiffSelection) error {
patchBuilder := self.c.Git().Patch.PatchBuilder
files, paths := selection.files, selection.paths
files := self.filesInDiff()
indicesByPath := map[string][]int{}
wholeFileByPath := map[string]bool{}
for _, path := range paths {
indices, everyChange, err := patchBuilder.PatchLineIndicesForLines(
path, files.previousPath(path), linesByPath[path])
path, files.previousPath(path), selection.linesByPath[path])
if err != nil {
return err
}
if len(indices) == 0 {
return diffLinesNotFoundError(self.c, path)
}
indicesByPath[path] = indices
// Selecting every change of a file the commit adds or deletes is selecting the
@@ -365,12 +408,9 @@ func (self *CommitDiffActions) togglePatchLines(lines []types.DiffLineInfo) erro
if err != nil {
return err
}
removing := len(indicesByPath[paths[0]]) > 0 && lo.Contains(included, indicesByPath[paths[0]][0])
removing := lo.Contains(included, indicesByPath[paths[0]][0])
for _, path := range paths {
if len(indicesByPath[path]) == 0 {
continue
}
previousPath := files.previousPath(path)
var err error
switch {
@@ -415,20 +455,20 @@ func (self commitDiffFiles) isWholeFileOperation(path string) bool {
// filesInDiff asks git which files the diff a patch is being built from covers, and
// what it does to each.
func (self *CommitDiffActions) filesInDiff() commitDiffFiles {
func (self *CommitDiffActions) filesInDiff() (commitDiffFiles, error) {
target := self.target()
if target == nil {
return nil
return nil, nil
}
from, reverse := self.patchEndpoints(target)
files, err := self.c.Git().Loaders.CommitFileLoader.GetFilesInDiff(from, target.to, reverse)
if err != nil {
return nil
return nil, err
}
return lo.SliceToMap(files, func(file *models.CommitFile) (string, *models.CommitFile) {
return file.Path, file
})
}), nil
}
// patchEndpoints gives the two ends of the diff a patch is built from. They are the ends
+21
View File
@@ -1,11 +1,13 @@
package controllers
import (
"errors"
"path"
"path/filepath"
"strings"
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
@@ -132,6 +134,25 @@ func isInDir(path string, dir string) bool {
return dir == "." || strings.HasPrefix(path, dir+"/")
}
// diffLinesFileNotFoundError reports that the file selected diff lines belong to can't
// be found. It names the file by its path in the repo, or by the path the diff line
// carries for one outside of it.
func diffLinesFileNotFoundError(c *ControllerCommon, absolutePath string) error {
path := repoRelativePath(c.Git().RepoPaths.WorktreePath(), absolutePath)
if path == "" {
path = absolutePath
}
return errors.New(utils.ResolvePlaceholderString(c.Tr.DiffLinesFileNotFound,
map[string]string{"path": path}))
}
// diffLinesNotFoundError reports that selected diff lines can't be found in the diff of
// the file at the given path in the repo.
func diffLinesNotFoundError(c *ControllerCommon, path string) error {
return errors.New(utils.ResolvePlaceholderString(c.Tr.DiffLinesNotFound,
map[string]string{"path": path}))
}
// repoRelativePath turns the absolute path a diff line carries into the one git speaks
// of the file: relative to the worktree, with forward slashes. It is "" for a path that
// is no file of this repo, which a diff renderer's own naming of a line can produce.
+1 -1
View File
@@ -1105,7 +1105,7 @@ func (self *MainViewController) openPullRequestAtSelectedLine() error {
}
relativePath := repoRelativePath(self.c.Git().RepoPaths.WorktreePath(), info.Path)
if relativePath == "" {
return nil
return diffLinesFileNotFoundError(self.c, info.Path)
}
self.c.LogAction(self.c.Tr.Actions.OpenPullRequest)
+100 -48
View File
@@ -59,14 +59,17 @@ func (self *WorkingTreeDiffActions) PrimaryAction(pane types.DiffPaneContext, fi
self.c.UserConfig().Keybinding.Universal.IncreaseContextInDiffView)
}
infos, onStagedSide, ok := self.diffLineSelection(pane, firstBufferLine, lastBufferLine)
if !ok {
linesByFile, onStagedSide, err := self.diffLineSelection(pane, firstBufferLine, lastBufferLine)
if err != nil {
return err
}
if len(linesByFile) == 0 {
return nil
}
// Either way the patch goes to the index: forwards from the unstaged side to stage
// it, backwards from the staged side to take it back out.
return self.applyDiffLineSelection(pane, firstBufferLine, infos, onStagedSide,
return self.applyDiffLineSelection(pane, firstBufferLine, linesByFile, onStagedSide,
git_commands.ApplyPatchOpts{Reverse: onStagedSide, Cached: true})
}
@@ -79,8 +82,11 @@ func (self *WorkingTreeDiffActions) DiscardSelection(pane types.DiffPaneContext,
self.c.UserConfig().Keybinding.Universal.IncreaseContextInDiffView)
}
infos, onStagedSide, ok := self.diffLineSelection(pane, firstBufferLine, lastBufferLine)
if !ok {
linesByFile, onStagedSide, err := self.diffLineSelection(pane, firstBufferLine, lastBufferLine)
if err != nil {
return err
}
if len(linesByFile) == 0 {
return nil
}
@@ -93,7 +99,7 @@ func (self *WorkingTreeDiffActions) DiscardSelection(pane types.DiffPaneContext,
Title: self.c.Tr.DiscardChangeTitle,
Prompt: self.c.Tr.DiscardChangePrompt,
HandleConfirm: func() error {
return self.applyDiffLineSelection(pane, firstBufferLine, infos, onStagedSide,
return self.applyDiffLineSelection(pane, firstBufferLine, linesByFile, onStagedSide,
git_commands.ApplyPatchOpts{Reverse: true, Cached: onStagedSide})
},
})
@@ -117,20 +123,20 @@ func (self *WorkingTreeDiffActions) DiscardSelectionDisabledReason(types.DiffPan
func (self *WorkingTreeDiffActions) EditHunk(
pane types.DiffPaneContext, firstBufferLine int, lastBufferLine int,
) error {
infos, onStagedSide, ok := self.diffLineSelection(pane, firstBufferLine, lastBufferLine)
if !ok {
return nil
linesByFile, onStagedSide, err := self.diffLineSelection(pane, firstBufferLine, lastBufferLine)
if err != nil {
return err
}
file := self.fileForDiffLinePath(infos[0].Path)
if file == nil {
if len(linesByFile) == 0 {
return nil
}
file := linesByFile[0].file
parsedPatch := patch.Parse(self.c.Git().WorkingTree.WorktreeFileDiff(file, git_commands.DiffModePlain, onStagedSide))
lineIndices := patch.ChangeLineIndicesForLines(parsedPatch,
[]patch.LineIdentity{infos[0].PatchLineIdentity()})
[]patch.LineIdentity{linesByFile[0].lines[0].PatchLineIdentity()})
if len(lineIndices) == 0 {
return nil
return diffLinesNotFoundError(self.c, file.GetPath())
}
hunkIdx := parsedPatch.HunkContainingLine(lineIndices[0])
@@ -193,19 +199,36 @@ func (self *WorkingTreeDiffActions) PatchInclusion() func(types.DiffLineInfo) bo
return nil
}
// fileDiffLines is the selected change lines of one file of the diff.
type fileDiffLines struct {
file *models.File
lines []types.DiffLineInfo
}
// diffLineSelection resolves what the user has selected in a pane of the focused main
// view to the change lines to act on, and reports whether they are the staged side of
// the diff — which is a question about the pane, so it is the same for every file of a
// directory's diff. ok is false when the selection holds no change line, in which case
// directory's diff. When the selection holds no change line, it returns no file, and
// there is nothing to act on.
//
// A directory's diff spans several files, and a patch is of one file, so the lines are
// grouped by the file they belong to, in the order the diff shows the files. If a line
// belongs to no file of the files panel, the diff and the panel disagree. Acting on the
// other lines would do only part of what the user asked for, so this fails instead.
func (self *WorkingTreeDiffActions) diffLineSelection(
pane types.DiffPaneContext, firstBufferLine int, lastBufferLine int,
) (infos []types.DiffLineInfo, onStagedSide bool, ok bool) {
infos = self.c.Helpers().DiffLine.ChangeLinesInBufferRange(pane.GetView(), firstBufferLine, lastBufferLine)
if len(infos) == 0 {
return nil, false, false
) (linesByFile []fileDiffLines, onStagedSide bool, err error) {
infos := self.c.Helpers().DiffLine.ChangeLinesInBufferRange(pane.GetView(), firstBufferLine, lastBufferLine)
infosByPath := lo.GroupBy(infos, func(info types.DiffLineInfo) string { return info.Path })
paths := lo.Uniq(lo.Map(infos, func(info types.DiffLineInfo, _ int) string { return info.Path }))
for _, path := range paths {
file := self.fileForDiffLinePath(path)
if file == nil {
return nil, false, diffLinesFileNotFoundError(self.c, path)
}
linesByFile = append(linesByFile, fileDiffLines{file: file, lines: infosByPath[path]})
}
return infos, self.showsStagedSide(pane), true
return linesByFile, self.showsStagedSide(pane), nil
}
// applyDiffLineSelection applies the selected change lines, a patch per file, and
@@ -215,26 +238,29 @@ func (self *WorkingTreeDiffActions) diffLineSelection(
// from once the diff has changed under it.
func (self *WorkingTreeDiffActions) applyDiffLineSelection(
pane types.DiffPaneContext, firstBufferLine int,
infos []types.DiffLineInfo, onStagedSide bool, opts git_commands.ApplyPatchOpts,
linesByFile []fileDiffLines, onStagedSide bool, opts git_commands.ApplyPatchOpts,
) error {
self.c.LogAction(self.c.Tr.Actions.ApplyPatch)
// A directory's diff spans several files, and a patch is of one file, so the
// selected lines are grouped by the file they belong to and applied file by file.
infosByFile := lo.GroupBy(infos, func(info types.DiffLineInfo) string { return info.Path })
acted := set.New[string]()
actedSideRemains := false
for path, fileInfos := range infosByFile {
file := self.fileForDiffLinePath(path)
if file == nil {
continue
}
changesLeft, err := self.applyDiffLines(file, fileInfos, onStagedSide, opts)
// If the lines of one file can't be found, no file is acted on, so every file's
// patch is built before any is applied.
patches := make([]diffLinesPatch, 0, len(linesByFile))
for _, fileLines := range linesByFile {
p, err := self.buildDiffLinesPatch(fileLines.file, fileLines.lines, onStagedSide, opts)
if err != nil {
return err
}
acted.Add(file.GetPath())
actedSideRemains = actedSideRemains || changesLeft
patches = append(patches, p)
}
self.c.LogAction(self.c.Tr.Actions.ApplyPatch)
acted := set.New[string]()
actedSideRemains := false
for _, p := range patches {
if err := self.applyDiffLinesPatch(p, opts); err != nil {
return err
}
acted.Add(p.file.GetPath())
actedSideRemains = actedSideRemains || p.changesLeft
}
if !actedSideRemains {
actedSideRemains = self.anyFileHasChangesOnSide(acted, onStagedSide)
@@ -268,8 +294,23 @@ func (self *WorkingTreeDiffActions) fileForDiffLinePath(path string) *models.Fil
return self.context().FileTreeViewModel.GetFile(relativePath)
}
// applyDiffLines applies the given change lines of one file — a line, a hunk, a range —
// as a patch built from that file's own diff:
// diffLinesPatch is how the selected change lines of one file are applied.
type diffLinesPatch struct {
file *models.File
// Whether the lines are every change of the file's diff. Acting on all of them is
// acting on the file itself, which is staged or unstaged as a whole.
wholeFile bool
// The patch of the lines, where they are not the whole file.
patch string
// Whether the diff the lines were found in holds changes they don't cover.
changesLeft bool
}
// buildDiffLinesPatch builds what applies the given change lines of one file — a line,
// a hunk, a range — as a patch built from that file's own diff:
//
// - stage: read the unstaged diff, apply it to the index
// - unstage: read the staged diff, apply it to the index backwards
@@ -284,18 +325,25 @@ func (self *WorkingTreeDiffActions) fileForDiffLinePath(path string) *models.Fil
// selected: a patch of the lines you picked keeps whatever context it needs around
// them by itself.
//
// It reports whether the diff it read holds changes the selection didn't cover. The
// It records whether the diff it read holds changes the selection didn't cover. The
// caller uses this to tell whether the side acted on still has anything of this file
// in it once we are done.
func (self *WorkingTreeDiffActions) applyDiffLines(
//
// It fails if none of the lines are in the diff. The diff on screen is then not the
// one it read; either the file changed since the diff was shown, or we read the
// lines wrongly.
func (self *WorkingTreeDiffActions) buildDiffLinesPatch(
file *models.File, infos []types.DiffLineInfo, sourceCached bool, opts git_commands.ApplyPatchOpts,
) (bool, error) {
) (diffLinesPatch, error) {
parsedPatch := patch.Parse(self.c.Git().WorkingTree.WorktreeFileDiff(file, git_commands.DiffModePlain, sourceCached))
patchLineIndices := patch.ChangeLineIndicesForLines(parsedPatch,
lo.Map(infos, func(info types.DiffLineInfo, _ int) patch.LineIdentity {
return info.PatchLineIdentity()
}))
if len(patchLineIndices) == 0 {
return diffLinesPatch{}, diffLinesNotFoundError(self.c, file.GetPath())
}
changesLeft := len(patchLineIndices) < changeLineCount(parsedPatch)
@@ -306,10 +354,7 @@ func (self *WorkingTreeDiffActions) applyDiffLines(
// and taking that back out leaves an empty file in the index rather than an
// untracked one.
if !changesLeft && opts.Cached {
if opts.Reverse {
return false, self.c.Git().WorkingTree.UnStageFile(file.Names(), file.Tracked)
}
return false, self.c.Git().WorkingTree.StageFile(file.GetPath())
return diffLinesPatch{file: file, wholeFile: true}, nil
}
patchToApply := parsedPatch.
@@ -319,11 +364,18 @@ func (self *WorkingTreeDiffActions) applyDiffLines(
FileNameOverride: file.GetPath(),
}).
FormatPlain()
if patchToApply == "" {
return changesLeft, nil
}
return diffLinesPatch{file: file, patch: patchToApply, changesLeft: changesLeft}, nil
}
return changesLeft, self.c.Git().Patch.ApplyPatch(patchToApply, opts)
// applyDiffLinesPatch applies what buildDiffLinesPatch built, as opts says.
func (self *WorkingTreeDiffActions) applyDiffLinesPatch(p diffLinesPatch, opts git_commands.ApplyPatchOpts) error {
if p.wholeFile {
if opts.Reverse {
return self.c.Git().WorkingTree.UnStageFile(p.file.Names(), p.file.Tracked)
}
return self.c.Git().WorkingTree.StageFile(p.file.GetPath())
}
return self.c.Git().Patch.ApplyPatch(p.patch, opts)
}
// changeLineCount returns how many of a patch's lines are changes rather than context
+6
View File
@@ -748,6 +748,9 @@ type TranslationSet struct {
CopySelectedDiffLinesToClipboard string
SelectedDiffLinesCopiedToast string
SelectionNotFoundInDiffToast string
DiffLinesFileNotFound string
DiffLinesNotFound string
CommitNotInCommitsPanel string
NoFilesStagedTitle string
NoFilesStagedPrompt string
BranchNotFoundTitle string
@@ -1938,6 +1941,9 @@ func EnglishTranslationSet() *TranslationSet {
CopySelectedDiffLinesToClipboard: "Copy selected diff lines to clipboard",
SelectedDiffLinesCopiedToast: "Selected diff lines copied to clipboard",
SelectionNotFoundInDiffToast: "Nothing in the selection could be found in the diff",
DiffLinesFileNotFound: "Can't find the file '{{.path}}' that the selected lines belong to",
DiffLinesNotFound: "Can't find the selected lines in the diff of '{{.path}}'",
CommitNotInCommitsPanel: "Can't find commit {{.hash}} in the commits panel",
CommitPrefixPatternError: "Error in commitPrefix pattern",
NoFilesStagedTitle: "No files staged",
NoFilesStagedPrompt: "You have not staged any files. Commit all files?",
@@ -0,0 +1,51 @@
package main_view
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var ReportLinesMissingFromACommitsDiff = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Adding lines to a custom patch that the diff puts where the commit's diff has none shows an error",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(cfg *config.AppConfig) {
cfg.GetUserConfig().Gui.UseHunkModeInDiffView = false
// A renderer that announces the metadata protocol and passes the diff through,
// except that its hunk header puts the hunk ten lines further down.
cfg.GetUserConfig().Git.DiffRenderers = []config.DiffRendererConfig{
{Command: `printf '\033]1717;1\007'; sed 's#@@ -1,3 +1,4 @@#@@ -11,3 +11,4 @@#'`},
}
},
SetupRepo: func(shell *Shell) {
shell.CreateFileAndAdd("file1", "one\ntwo\nthree\n")
shell.Commit("one")
shell.UpdateFileAndAdd("file1", "one\nADD1\ntwo\nthree\n")
shell.Commit("two")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Commits().
Focus().
Lines(
Contains("two").IsSelected(),
Contains("one"),
).
Press(keys.Universal.FocusMainView)
t.Views().Main().
IsFocused().
SelectedLines(
Contains("+ADD1"),
).
PressPrimaryAction()
t.ExpectPopup().Alert().
Title(Equals("Error")).
Content(Equals("Can't find the selected lines in the diff of 'file1'")).
Confirm()
// No patch is left behind.
t.Views().Information().Content(DoesNotContain("Building patch"))
},
})
@@ -0,0 +1,47 @@
package main_view
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var ReportLinesMissingFromAFileChangedSince = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Staging lines that are gone from the file since its diff was shown shows an error",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(cfg *config.AppConfig) {
cfg.GetUserConfig().Gui.UseHunkModeInDiffView = false
},
SetupRepo: func(shell *Shell) {
shell.CreateFileAndAdd("file1", "one\ntwo\nthree\n")
shell.Commit("one")
shell.UpdateFile("file1", "one\nADD1\ntwo\nthree\n")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Files().
IsFocused().
Lines(
Contains(" M file1").IsSelected(),
).
Press(keys.Universal.FocusMainView)
t.Views().Main().
IsFocused().
SelectedLines(
Contains("+ADD1"),
)
// The change is undone outside of lazygit, and nothing has refreshed the diff
// since.
t.Shell().UpdateFile("file1", "one\ntwo\nthree\n")
t.Views().Main().
PressPrimaryAction()
t.ExpectPopup().Alert().
Title(Equals("Error")).
Content(Equals("Can't find the selected lines in the diff of 'file1'")).
Confirm()
},
})
@@ -0,0 +1,51 @@
package main_view
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var ReportLinesOfAFileMissingFromTheCommitsDiff = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Adding lines to a custom patch that the diff puts in a file the commit doesn't change shows an error",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(cfg *config.AppConfig) {
cfg.GetUserConfig().Gui.UseHunkModeInDiffView = false
// A renderer that announces the metadata protocol and passes the diff through,
// except that its +++ line names a file in another directory.
cfg.GetUserConfig().Git.DiffRenderers = []config.DiffRendererConfig{
{Command: `printf '\033]1717;1\007'; sed 's#+++ b/#+++ b/elsewhere/#'`},
}
},
SetupRepo: func(shell *Shell) {
shell.CreateFileAndAdd("file1", "one\ntwo\nthree\n")
shell.Commit("one")
shell.UpdateFileAndAdd("file1", "one\nADD1\ntwo\nthree\n")
shell.Commit("two")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Commits().
Focus().
Lines(
Contains("two").IsSelected(),
Contains("one"),
).
Press(keys.Universal.FocusMainView)
t.Views().Main().
IsFocused().
SelectedLines(
Contains("+ADD1"),
).
PressPrimaryAction()
t.ExpectPopup().Alert().
Title(Equals("Error")).
Content(Equals("Can't find the file 'elsewhere/file1' that the selected lines belong to")).
Confirm()
// No patch is left behind.
t.Views().Information().Content(DoesNotContain("Building patch"))
},
})
@@ -0,0 +1,50 @@
package main_view
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var ReportLinesOfAFileMissingFromTheFilesPanel = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Staging lines that the diff puts in a file the files panel doesn't have shows an error",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(cfg *config.AppConfig) {
cfg.GetUserConfig().Gui.UseHunkModeInDiffView = false
// A renderer that announces the metadata protocol and passes the diff through,
// except that its +++ line names a file in another directory.
cfg.GetUserConfig().Git.DiffRenderers = []config.DiffRendererConfig{
{Command: `printf '\033]1717;1\007'; sed 's#+++ b/#+++ b/elsewhere/#'`},
}
},
SetupRepo: func(shell *Shell) {
shell.CreateFileAndAdd("file1", "one\ntwo\nthree\n")
shell.Commit("one")
shell.UpdateFile("file1", "one\nADD1\ntwo\nthree\n")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Files().
IsFocused().
Lines(
Contains(" M file1").IsSelected(),
).
Press(keys.Universal.FocusMainView)
t.Views().Main().
IsFocused().
SelectedLines(
Contains("+ADD1"),
).
PressPrimaryAction()
t.ExpectPopup().Alert().
Title(Equals("Error")).
Content(Equals("Can't find the file 'elsewhere/file1' that the selected lines belong to")).
Confirm()
t.Views().Files().Lines(
Contains(" M file1"),
)
},
})
+4
View File
@@ -463,6 +463,10 @@ var tests = []*components.IntegrationTest{
main_view.RemovePartOfAddedFileFromCommit,
main_view.RemovePatchFromCommit,
main_view.RenderTheDiffBesideThePatchMarks,
main_view.ReportLinesMissingFromACommitsDiff,
main_view.ReportLinesMissingFromAFileChangedSince,
main_view.ReportLinesOfAFileMissingFromTheCommitsDiff,
main_view.ReportLinesOfAFileMissingFromTheFilesPanel,
main_view.ResetAPatchBuiltFromACommitsDiff,
main_view.ResetPatchWithEscape,
main_view.ResetThePatchFromThePaneShowingIt,