From 30154aa9c5417a300fc7465fd22536182aa50e20 Mon Sep 17 00:00:00 2001 From: cobyfrombrooklyn-bot Date: Thu, 26 Feb 2026 10:42:24 -0500 Subject: [PATCH] Fix #5302: Create .git/info directory before writing exclude file When .git/info directory does not exist (can happen with bare clones or manual deletion), the Exclude function failed with 'no such file or directory'. Added os.MkdirAll to create the directory before opening the exclude file. Added integration test exclude_without_info_dir that removes .git/info before attempting to exclude a file. --- pkg/commands/git_commands/working_tree.go | 6 +++- .../tests/file/exclude_without_info_dir.go | 32 +++++++++++++++++++ pkg/integration/tests/test_list.go | 1 + 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 pkg/integration/tests/file/exclude_without_info_dir.go diff --git a/pkg/commands/git_commands/working_tree.go b/pkg/commands/git_commands/working_tree.go index 664436066..7aafe3655 100644 --- a/pkg/commands/git_commands/working_tree.go +++ b/pkg/commands/git_commands/working_tree.go @@ -246,7 +246,11 @@ func (self *WorkingTreeCommands) Ignore(filename string) error { // Exclude adds a file to the .git/info/exclude for the repo func (self *WorkingTreeCommands) Exclude(filename string) error { - excludeFile := filepath.Join(self.repoPaths.repoGitDirPath, "info", "exclude") + 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)) } diff --git a/pkg/integration/tests/file/exclude_without_info_dir.go b/pkg/integration/tests/file/exclude_without_info_dir.go new file mode 100644 index 000000000..6e88de747 --- /dev/null +++ b/pkg/integration/tests/file/exclude_without_info_dir.go @@ -0,0 +1,32 @@ +package file + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var ExcludeWithoutInfoDir = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Exclude a file when .git/info directory does not exist", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) { + }, + SetupRepo: func(shell *Shell) { + // Remove .git/info directory to reproduce #5302 + shell.RunCommand([]string{"rm", "-rf", ".git/info"}) + shell.CreateFile("toExclude", "") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Files(). + IsFocused(). + Focus(). + NavigateToLine(Contains("toExclude")). + Press(keys.Files.IgnoreFile). + Tap(func() { + t.ExpectPopup().Menu().Title(Equals("Ignore or exclude file")).Select(Contains("Add to .git/info/exclude")).Confirm() + + // Should succeed without error, creating .git/info/ directory automatically + t.FileSystem().FileContent(".git/info/exclude", Contains("/toExclude")) + }) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 1fdd4c161..c336cce1f 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -221,6 +221,7 @@ var tests = []*components.IntegrationTest{ file.DiscardUnstagedRangeSelect, file.DiscardVariousChanges, file.DiscardVariousChangesRangeSelect, + file.ExcludeWithoutInfoDir, file.Gitignore, file.GitignoreSpecialCharacters, file.RememberCommitMessageAfterFail,