This commit is contained in:
phanium 2026-08-30 13:24:38 -07:00 committed by GitHub
commit 7add03f050
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 190 additions and 34 deletions

View file

@ -367,19 +367,29 @@ func escapeFilename(filename string) string {
return "/" + re.ReplaceAllString(filename, `\${0}`)
}
// Ignore adds a file to the gitignore for the repo
func (self *WorkingTreeCommands) Ignore(filename string) error {
return self.os.AppendLineToFile(".gitignore", escapeFilename(filename))
// Ignore adds the given files to the gitignore for the repo
func (self *WorkingTreeCommands) Ignore(filenames []string) error {
for _, filename := range filenames {
if err := self.os.AppendLineToFile(".gitignore", escapeFilename(filename)); err != nil {
return err
}
}
return nil
}
// Exclude adds a file to the .git/info/exclude for the repo
func (self *WorkingTreeCommands) Exclude(filename string) error {
// Exclude adds the given files to the .git/info/exclude for the repo
func (self *WorkingTreeCommands) Exclude(filenames []string) error {
infoDir := filepath.Join(self.repoPaths.repoGitDirPath, "info")
if err := os.MkdirAll(infoDir, 0o755); err != nil {
return err
}
excludeFile := filepath.Join(infoDir, "exclude")
return self.os.AppendLineToFile(excludeFile, escapeFilename(filename))
for _, filename := range filenames {
if err := self.os.AppendLineToFile(excludeFile, escapeFilename(filename)); err != nil {
return err
}
}
return nil
}
// WorktreeFileDiff returns the diff of a file

View file

@ -105,8 +105,8 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
},
{
Keys: opts.GetKeys(opts.Config.Files.IgnoreFile),
Handler: self.withItem(self.ignoreOrExcludeMenu),
GetDisabledReason: self.require(self.singleItemSelected()),
Handler: self.withItems(self.ignoreOrExcludeMenu),
GetDisabledReason: self.require(self.itemsSelected()),
Description: self.c.Tr.Actions.IgnoreExcludeFile,
OpensMenu: true,
},
@ -973,18 +973,28 @@ func (self *FilesController) unstageFiles(node *filetree.FileNode) error {
})
}
func (self *FilesController) ignoreOrExcludeTracked(node *filetree.FileNode, trAction string, f func(string) error) error {
func (self *FilesController) ignoreOrExcludeTracked(nodes []*filetree.FileNode, trAction string, f func([]string) error) error {
self.c.LogAction(trAction)
// not 100% sure if this is necessary but I'll assume it is
if err := self.unstageFiles(node); err != nil {
return err
nodes = normalisedSelectedNodes(nodes)
paths := make([]string, 0, len(nodes))
for _, node := range nodes {
if node.GetIsTracked() {
// not 100% sure if this is necessary but I'll assume it is
if err := self.unstageFiles(node); err != nil {
return err
}
if err := self.c.Git().WorkingTree.RemoveTrackedFiles(node.GetPath()); err != nil {
return err
}
}
paths = append(paths, node.GetPath())
}
if err := self.c.Git().WorkingTree.RemoveTrackedFiles(node.GetPath()); err != nil {
return err
}
if err := f(node.GetPath()); err != nil {
if err := f(paths); err != nil {
return err
}
@ -992,10 +1002,17 @@ func (self *FilesController) ignoreOrExcludeTracked(node *filetree.FileNode, trA
return nil
}
func (self *FilesController) ignoreOrExcludeUntracked(node *filetree.FileNode, trAction string, f func(string) error) error {
func (self *FilesController) ignoreOrExcludeUntracked(nodes []*filetree.FileNode, trAction string, f func([]string) error) error {
self.c.LogAction(trAction)
if err := f(node.GetPath()); err != nil {
nodes = normalisedSelectedNodes(nodes)
paths := make([]string, 0, len(nodes))
for _, node := range nodes {
paths = append(paths, node.GetPath())
}
if err := f(paths); err != nil {
return err
}
@ -1003,44 +1020,56 @@ func (self *FilesController) ignoreOrExcludeUntracked(node *filetree.FileNode, t
return nil
}
func (self *FilesController) ignoreOrExcludeFile(node *filetree.FileNode, trText string, trPrompt string, trAction string, f func(string) error) error {
if node.GetIsTracked() {
func (self *FilesController) ignoreOrExcludeFiles(nodes []*filetree.FileNode, trText string, trPrompt string, trAction string, f func([]string) error) error {
hasTracked := false
for _, node := range nodes {
if node.GetIsTracked() {
hasTracked = true
break
}
}
if hasTracked {
self.c.Confirm(types.ConfirmOpts{
Title: trText,
Prompt: trPrompt,
HandleConfirm: func() error {
return self.ignoreOrExcludeTracked(node, trAction, f)
return self.ignoreOrExcludeTracked(nodes, trAction, f)
},
})
return nil
}
return self.ignoreOrExcludeUntracked(node, trAction, f)
return self.ignoreOrExcludeUntracked(nodes, trAction, f)
}
func (self *FilesController) ignore(node *filetree.FileNode) error {
if node.GetPath() == ".gitignore" {
return errors.New(self.c.Tr.Actions.IgnoreFileErr)
func (self *FilesController) ignore(nodes []*filetree.FileNode) error {
for _, node := range nodes {
if node.GetPath() == ".gitignore" {
return errors.New(self.c.Tr.Actions.IgnoreFileErr)
}
}
return self.ignoreOrExcludeFile(node, self.c.Tr.IgnoreTracked, self.c.Tr.IgnoreTrackedPrompt, self.c.Tr.Actions.IgnoreExcludeFile, self.c.Git().WorkingTree.Ignore)
return self.ignoreOrExcludeFiles(nodes, self.c.Tr.IgnoreTracked, self.c.Tr.IgnoreTrackedPrompt, self.c.Tr.Actions.IgnoreExcludeFile, self.c.Git().WorkingTree.Ignore)
}
func (self *FilesController) exclude(node *filetree.FileNode) error {
if node.GetPath() == ".gitignore" {
return errors.New(self.c.Tr.Actions.ExcludeGitIgnoreErr)
func (self *FilesController) exclude(nodes []*filetree.FileNode) error {
for _, node := range nodes {
if node.GetPath() == ".gitignore" {
return errors.New(self.c.Tr.Actions.ExcludeGitIgnoreErr)
}
}
return self.ignoreOrExcludeFile(node, self.c.Tr.ExcludeTracked, self.c.Tr.ExcludeTrackedPrompt, self.c.Tr.Actions.ExcludeFile, self.c.Git().WorkingTree.Exclude)
return self.ignoreOrExcludeFiles(nodes, self.c.Tr.ExcludeTracked, self.c.Tr.ExcludeTrackedPrompt, self.c.Tr.Actions.ExcludeFile, self.c.Git().WorkingTree.Exclude)
}
func (self *FilesController) ignoreOrExcludeMenu(node *filetree.FileNode) error {
func (self *FilesController) ignoreOrExcludeMenu(nodes []*filetree.FileNode) error {
return self.c.Menu(types.CreateMenuOptions{
Title: self.c.Tr.Actions.IgnoreExcludeFile,
Items: []*types.MenuItem{
{
LabelColumns: []string{self.c.Tr.IgnoreFile},
OnPress: func() error {
if err := self.ignore(node); err != nil {
if err := self.ignore(nodes); err != nil {
return err
}
return nil
@ -1050,7 +1079,7 @@ func (self *FilesController) ignoreOrExcludeMenu(node *filetree.FileNode) error
{
LabelColumns: []string{self.c.Tr.ExcludeFile},
OnPress: func() error {
if err := self.exclude(node); err != nil {
if err := self.exclude(nodes); err != nil {
return err
}
return nil

View file

@ -0,0 +1,65 @@
package file
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var GitignoreRangeSelect = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Ignore and exclude multiple files at once via range select",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {
},
SetupRepo: func(shell *Shell) {
shell.CreateFile(".gitignore", "")
shell.CreateFile("toIgnore1", "")
shell.CreateFile("toIgnore2", "")
shell.CreateFile("toIgnore3", "")
shell.CreateFile("toExclude1", "")
shell.CreateFile("toExclude2", "")
shell.CreateFile("toExclude3", "")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Files().
IsFocused().
Lines(
Equals("▼ /").IsSelected(),
Equals(" ?? .gitignore"),
Equals(" ?? toExclude1"),
Equals(" ?? toExclude2"),
Equals(" ?? toExclude3"),
Equals(" ?? toIgnore1"),
Equals(" ?? toIgnore2"),
Equals(" ?? toIgnore3"),
).
// Select range from toIgnore1 to toIgnore3
NavigateToLine(Contains("toIgnore1")).
Press(keys.Universal.ToggleRangeSelect).
NavigateToLine(Contains("toIgnore3")).
Press(keys.Files.IgnoreFile).
// Ignore all selected files
Tap(func() {
t.ExpectPopup().Menu().Title(Equals("Ignore or exclude file")).
Select(Contains("Add to .gitignore")).
Confirm()
t.FileSystem().FileContent(".gitignore", Equals("/toIgnore1\n/toIgnore2\n/toIgnore3\n"))
}).
// Dismiss the range select mode for the next set of steps
Press(keys.Universal.ToggleRangeSelect).
// Select range from toExclude1 to toExclude3
NavigateToLine(Contains("toExclude1")).
Press(keys.Universal.ToggleRangeSelect).
NavigateToLine(Contains("toExclude3")).
Press(keys.Files.IgnoreFile).
// Exclude all selected files
Tap(func() {
t.ExpectPopup().Menu().Title(Equals("Ignore or exclude file")).
Select(Contains("Add to .git/info/exclude")).
Confirm()
t.FileSystem().FileContent(".git/info/exclude", Contains("/toExclude1\n/toExclude2\n/toExclude3\n"))
})
},
})

View file

@ -0,0 +1,50 @@
package file
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var GitignoreTrackedRangeSelect = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Range-select across a tracked directory and its children (parent+child case)",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {
},
SetupRepo: func(shell *Shell) {
shell.CreateFileAndAdd("dir-tracked/file-a", "x")
shell.CreateFileAndAdd("dir-tracked/file-b", "x")
shell.CreateFileAndAdd("tracked1", "x")
shell.Commit("initial")
shell.UpdateFile("dir-tracked/file-a", "y")
shell.UpdateFile("dir-tracked/file-b", "y")
shell.UpdateFile("tracked1", "y")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Files().
IsFocused().
Lines(
Equals("▼ /").IsSelected(),
Equals(" ▼ dir-tracked"),
Equals(" M file-a"),
Equals(" M file-b"),
Equals(" M tracked1"),
).
NavigateToLine(Contains("dir-tracked")).
Press(keys.Universal.ToggleRangeSelect).
NavigateToLine(Contains("tracked1")).
Press(keys.Files.IgnoreFile).
Tap(func() {
t.ExpectPopup().Menu().Title(Equals("Ignore or exclude file")).
Select(Contains("Add to .gitignore")).
Confirm()
t.ExpectPopup().Confirmation().
Title(Equals("Ignore tracked file")).
Content(Contains("tracked file")).
Confirm()
t.FileSystem().FileContent(".gitignore", Equals("/dir-tracked\n/tracked1\n"))
})
},
})

View file

@ -245,7 +245,9 @@ var tests = []*components.IntegrationTest{
file.DiscardVariousChangesRangeSelect,
file.ExcludeWithoutInfoDir,
file.Gitignore,
file.GitignoreRangeSelect,
file.GitignoreSpecialCharacters,
file.GitignoreTrackedRangeSelect,
file.RememberCommitMessageAfterFail,
file.RenameSimilarityThresholdChange,
file.RenamedFiles,