mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-10-12 02:16:50 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
},
|
||||
})
|
||||
+51
@@ -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"))
|
||||
},
|
||||
})
|
||||
+50
@@ -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"),
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user