diff --git a/pkg/commands/git_commands/working_tree.go b/pkg/commands/git_commands/working_tree.go index e74ccf1e5ed..07b52f12dc0 100644 --- a/pkg/commands/git_commands/working_tree.go +++ b/pkg/commands/git_commands/working_tree.go @@ -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)) } diff --git a/pkg/integration/tests/file/gitignore_local.go b/pkg/integration/tests/file/gitignore_local.go new file mode 100644 index 00000000000..a551f4ccffe --- /dev/null +++ b/pkg/integration/tests/file/gitignore_local.go @@ -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") + }) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 26d6d30305f..805b0553013 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -221,6 +221,7 @@ var tests = []*components.IntegrationTest{ file.DiscardVariousChanges, file.DiscardVariousChangesRangeSelect, file.Gitignore, + file.GitignoreLocal, file.GitignoreSpecialCharacters, file.RememberCommitMessageAfterFail, file.RenameSimilarityThresholdChange,