Use local .gitignore when one exists in the same directory

When ignoring a file, check if a .gitignore already exists in the file's
directory. If so, add just the basename to that local .gitignore instead
of adding the full path to the root .gitignore.
This commit is contained in:
dvic 2026-01-14 09:54:46 +01:00
parent 4486c86271
commit 1897453367
No known key found for this signature in database
3 changed files with 70 additions and 1 deletions

View file

@ -239,8 +239,18 @@ func escapeFilename(filename string) string {
return re.ReplaceAllString(filename, `\${0}`)
}
// Ignore adds a file to the gitignore for the repo
// Ignore adds a file to the gitignore for the repo.
// If a .gitignore exists in the same directory as the file, it will be used
// with just the basename. Otherwise, the root .gitignore is used with the full path.
func (self *WorkingTreeCommands) Ignore(filename string) error {
dir := filepath.Dir(filename)
if dir != "." {
localGitignore := filepath.Join(dir, ".gitignore")
if _, err := os.Stat(localGitignore); err == nil {
return self.os.AppendLineToFile(localGitignore, escapeFilename(filepath.Base(filename)))
}
}
return self.os.AppendLineToFile(".gitignore", escapeFilename(filename))
}

View file

@ -0,0 +1,58 @@
package file
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var GitignoreLocal = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Verify that ignoring a file uses the local .gitignore if one exists in the same directory",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {
},
SetupRepo: func(shell *Shell) {
// Create root .gitignore
shell.CreateFile(".gitignore", "")
// Create subdirectory with its own .gitignore
shell.CreateDir("subdir_with_gitignore")
shell.CreateFile("subdir_with_gitignore/.gitignore", "")
shell.CreateFile("subdir_with_gitignore/file_to_ignore", "")
// Create subdirectory without .gitignore
shell.CreateDir("subdir_without_gitignore")
shell.CreateFile("subdir_without_gitignore/another_file", "")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Files().
IsFocused().
Lines(
Equals("▼ /").IsSelected(),
Equals(" ▼ subdir_with_gitignore"),
Equals(" ?? .gitignore"),
Equals(" ?? file_to_ignore"),
Equals(" ▼ subdir_without_gitignore"),
Equals(" ?? another_file"),
Equals(" ?? .gitignore"),
).
// Navigate to subdir_with_gitignore/file_to_ignore
NavigateToLine(Contains("file_to_ignore")).
Press(keys.Files.IgnoreFile).
Tap(func() {
t.ExpectPopup().Menu().Title(Equals("Ignore or exclude file")).Select(Contains("Add to .gitignore")).Confirm()
// Should be added to the local .gitignore with just the basename
t.FileSystem().FileContent("subdir_with_gitignore/.gitignore", Equals("file_to_ignore\n"))
// Root .gitignore should remain empty
t.FileSystem().FileContent(".gitignore", Equals(""))
}).
// Navigate to subdir_without_gitignore/another_file
NavigateToLine(Contains("another_file")).
Press(keys.Files.IgnoreFile).
Tap(func() {
t.ExpectPopup().Menu().Title(Equals("Ignore or exclude file")).Select(Contains("Add to .gitignore")).Confirm()
// Should be added to root .gitignore with full path since no local .gitignore exists
t.FileSystem().FileContent(".gitignore", Equals("subdir_without_gitignore/another_file\n"))
// Local .gitignore should not have been created
t.FileSystem().PathNotPresent("subdir_without_gitignore/.gitignore")
})
},
})

View file

@ -221,6 +221,7 @@ var tests = []*components.IntegrationTest{
file.DiscardVariousChanges,
file.DiscardVariousChangesRangeSelect,
file.Gitignore,
file.GitignoreLocal,
file.GitignoreSpecialCharacters,
file.RememberCommitMessageAfterFail,
file.RenameSimilarityThresholdChange,