mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-11-06 13:00:59 -04:00
Show an error when editing a file of a diff that doesn't exist
If you press `e` on a file of a commit, and a later commit deleted that file, the editor is opened on a path that doesn't exist. Most editors then create it as a new, empty file. This is confusing. The same happens with `o`, and with any other file that the worktree it is opened from doesn't have. An example is a file that is added in a branch that isn't checked out anywhere, since it is opened from the current worktree. Show an error instead of opening the editor. For a range selection in the commit files panel, show it only if none of the selected files exist. Otherwise open the existing ones and leave out the others silently; an error shown only after returning from the editor would come too late to be useful. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
6cd6e4532d
commit
3742ac6066
@@ -1,6 +1,7 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/modes/diffing"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
@@ -229,27 +231,87 @@ func (self *DiffHelper) OpenDiffToolForRef(selectedRef models.Ref) error {
|
||||
|
||||
// EditFilesOfDiff opens files of the diff that the current side panel shows in the
|
||||
// editor. They are opened from the worktree of the diff (see otherWorktreeOfDiff).
|
||||
// Files that don't exist there are left out, and if none of them exist, it returns an
|
||||
// error (see existingPathsInWorktreeOfDiff).
|
||||
func (self *DiffHelper) EditFilesOfDiff(paths []string) error {
|
||||
return self.filesHelper.EditFiles(lo.Map(paths, func(path string, _ int) string {
|
||||
return self.pathInWorktreeOfDiff(path)
|
||||
}))
|
||||
existingPaths, err := self.existingPathsInWorktreeOfDiff(paths)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return self.filesHelper.EditFiles(existingPaths)
|
||||
}
|
||||
|
||||
// EditFileOfDiffAtLine opens a file of the diff shown in the given view in the editor,
|
||||
// at a line of that diff. The line is carried forward to where it is in the file now
|
||||
// (see AdjustLineNumber). Like EditFilesOfDiff, it opens the file from the worktree of
|
||||
// the diff.
|
||||
// the diff, and returns an error if the file doesn't exist there.
|
||||
func (self *DiffHelper) EditFileOfDiffAtLine(path string, lineNumber int, viewName string) error {
|
||||
path = self.pathInWorktreeOfDiff(path)
|
||||
path, err := self.existingPathInWorktreeOfDiff(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lineNumber = self.AdjustLineNumber(path, lineNumber, viewName)
|
||||
return self.filesHelper.EditFileAtLine(path, lineNumber)
|
||||
}
|
||||
|
||||
// OpenFileOfDiff opens a file of the diff that the current side panel shows with the
|
||||
// default application for it. Like EditFilesOfDiff, it opens the file from the
|
||||
// worktree of the diff.
|
||||
// worktree of the diff, and returns an error if the file doesn't exist there.
|
||||
func (self *DiffHelper) OpenFileOfDiff(path string) error {
|
||||
return self.filesHelper.OpenFile(self.pathInWorktreeOfDiff(path))
|
||||
path, err := self.existingPathInWorktreeOfDiff(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return self.filesHelper.OpenFile(path)
|
||||
}
|
||||
|
||||
// existingPathsInWorktreeOfDiff returns the absolute paths of those of the given files
|
||||
// of the diff that exist in the worktree of the diff. A file of a diff may be missing
|
||||
// there, for example if a later commit deleted it, or if the worktree has a branch
|
||||
// checked out that doesn't have it. An editor would create such a file as a new, empty
|
||||
// one, so it is left out.
|
||||
//
|
||||
// If none of the files exist, it returns an error. If only some of them are missing, it
|
||||
// leaves them out silently. The user would see such an error only after returning from
|
||||
// the editor.
|
||||
func (self *DiffHelper) existingPathsInWorktreeOfDiff(paths []string) ([]string, error) {
|
||||
absPaths := make([]string, 0, len(paths))
|
||||
for _, path := range paths {
|
||||
absPath, err := self.AbsolutePathOfFileOfDiff(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
absPaths = append(absPaths, absPath)
|
||||
}
|
||||
|
||||
existingPaths := lo.Filter(absPaths, func(path string, _ int) bool {
|
||||
exists, err := self.c.OS().FileExists(path)
|
||||
// If we can't tell, the editor reports the problem
|
||||
return exists || err != nil
|
||||
})
|
||||
if len(existingPaths) > 0 {
|
||||
return existingPaths, nil
|
||||
}
|
||||
|
||||
if len(absPaths) == 1 {
|
||||
return nil, errors.New(utils.ResolvePlaceholderString(
|
||||
self.c.Tr.FileOfDiffDoesNotExist, map[string]string{"path": absPaths[0]}))
|
||||
}
|
||||
worktreePath, ok := self.otherWorktreeOfDiff()
|
||||
if !ok {
|
||||
worktreePath = self.c.Git().RepoPaths.WorktreePath()
|
||||
}
|
||||
return nil, errors.New(utils.ResolvePlaceholderString(
|
||||
self.c.Tr.NoFileOfDiffExists, map[string]string{"worktreePath": worktreePath}))
|
||||
}
|
||||
|
||||
// existingPathInWorktreeOfDiff is existingPathsInWorktreeOfDiff for a single file.
|
||||
func (self *DiffHelper) existingPathInWorktreeOfDiff(path string) (string, error) {
|
||||
existingPaths, err := self.existingPathsInWorktreeOfDiff([]string{path})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return existingPaths[0], nil
|
||||
}
|
||||
|
||||
// AbsolutePathOfFileOfDiff returns the absolute path of a file of the diff that the
|
||||
|
||||
@@ -422,6 +422,8 @@ type TranslationSet struct {
|
||||
JumpToFileInDiffTooltip string
|
||||
OnlyOneFileInDiff string
|
||||
NoFileInDiffNamed string
|
||||
FileOfDiffDoesNotExist string
|
||||
NoFileOfDiffExists string
|
||||
PrevConflict string
|
||||
NextConflict string
|
||||
SelectPrevHunk string
|
||||
@@ -1617,6 +1619,8 @@ func EnglishTranslationSet() *TranslationSet {
|
||||
JumpToFileInDiffTooltip: "Pick one of the files of the diff shown in the main view, and scroll the main view to it. The focus stays in this panel.",
|
||||
OnlyOneFileInDiff: "There is only one file in this diff",
|
||||
NoFileInDiffNamed: "This diff has no file named '{{.path}}'",
|
||||
FileOfDiffDoesNotExist: "File '{{.path}}' doesn't exist in the working tree",
|
||||
NoFileOfDiffExists: "None of the selected files exist in the working tree at '{{.worktreePath}}'",
|
||||
PrevConflict: "Previous conflict",
|
||||
NextConflict: "Next conflict",
|
||||
SelectPrevHunk: "Previous hunk",
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
package commit
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var EditFilesMissingFromWorkingTree = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Edit or open files of a commit that a later commit deleted",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {
|
||||
config.GetUserConfig().Gui.UseHunkModeInDiffView = false
|
||||
config.GetUserConfig().OS.Edit = "echo {{filename}} > edit-command"
|
||||
config.GetUserConfig().OS.EditAtLine = "echo {{filename}}:{{line}} > edit-command"
|
||||
config.GetUserConfig().OS.Open = "echo {{filename}} > open-command"
|
||||
},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateFileAndAdd("file1", "one\n")
|
||||
shell.CreateFileAndAdd("file2", "two\n")
|
||||
shell.CreateFileAndAdd("file3", "three\n")
|
||||
shell.Commit("add files")
|
||||
shell.DeleteFileAndAdd("file2")
|
||||
shell.DeleteFileAndAdd("file3")
|
||||
shell.Commit("delete files")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("delete files").IsSelected(),
|
||||
Contains("add files"),
|
||||
).
|
||||
NavigateToLine(Contains("add files")).
|
||||
PressEnter()
|
||||
|
||||
t.Views().CommitFiles().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Equals("▼ /").IsSelected(),
|
||||
Contains("A file1"),
|
||||
Contains("A file2"),
|
||||
Contains("A file3"),
|
||||
).
|
||||
NavigateToLine(Contains("file2")).
|
||||
Press(keys.Universal.Edit).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Alert().
|
||||
Title(Equals("Error")).
|
||||
Content(Contains("/repo/file2' doesn't exist in the working tree")).
|
||||
Confirm()
|
||||
}).
|
||||
Press(keys.Universal.OpenFile).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Alert().
|
||||
Title(Equals("Error")).
|
||||
Content(Contains("/repo/file2' doesn't exist in the working tree")).
|
||||
Confirm()
|
||||
}).
|
||||
Press(keys.Universal.FocusMainView)
|
||||
|
||||
t.Views().Main().
|
||||
IsFocused().
|
||||
NavigateToLine(Contains("+two")).
|
||||
Press(keys.Universal.Edit).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Alert().
|
||||
Title(Equals("Error")).
|
||||
Content(Contains("/repo/file2' doesn't exist in the working tree")).
|
||||
Confirm()
|
||||
}).
|
||||
PressEscape()
|
||||
|
||||
t.Views().CommitFiles().
|
||||
IsFocused().
|
||||
Press(keys.Universal.ToggleRangeSelect).
|
||||
SelectNextItem().
|
||||
SelectedLines(
|
||||
Contains("A file2"),
|
||||
Contains("A file3"),
|
||||
).
|
||||
Press(keys.Universal.Edit).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Alert().
|
||||
Title(Equals("Error")).
|
||||
Content(Contains("None of the selected files exist in the working tree at '")).
|
||||
Confirm()
|
||||
}).
|
||||
// If only some of the files are missing, the others are opened without
|
||||
// complaint
|
||||
NavigateToLine(Contains("file1")).
|
||||
SelectedLines(
|
||||
Contains("A file1"),
|
||||
Contains("A file2"),
|
||||
).
|
||||
Press(keys.Universal.Edit).
|
||||
Tap(func() {
|
||||
t.FileSystem().FileContent("edit-command",
|
||||
Contains("/repo/file1\n").DoesNotContain("file2"))
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -135,6 +135,7 @@ var tests = []*components.IntegrationTest{
|
||||
commit.DiscardRenamedFile,
|
||||
commit.DiscardSubmoduleChanges,
|
||||
commit.DoNotShowBranchMarkerForHeadCommit,
|
||||
commit.EditFilesMissingFromWorkingTree,
|
||||
commit.FailHooksThenCommitNoHooks,
|
||||
commit.FindBaseCommitForFixup,
|
||||
commit.FindBaseCommitForFixupDisregardFixupsForSameBaseCommit,
|
||||
|
||||
Reference in New Issue
Block a user