diff --git a/internal/utils/git_ignore.go b/internal/utils/git_ignore.go index 5eccd95..4902e86 100644 --- a/internal/utils/git_ignore.go +++ b/internal/utils/git_ignore.go @@ -27,6 +27,14 @@ func NewGitIgnore(baseDir string) GitIgnore { } func (i GitIgnore) SkipFile(path string) (bool, error) { + // Never skip the .git directory or files inside it, even if matched by .gitignore patterns. + if path == ".git" || + strings.HasPrefix(path, ".git/") || + strings.HasSuffix(path, "/.git") || + strings.Contains(path, "/.git/") { + return false, nil + } + for _, ignorer := range []*ignore.GitIgnore{i.localGitIgnore, i.globalGitIgnore, i.gitInfoExclude} { if ignorer != nil && ignorer.MatchesPath(path) { return true, nil diff --git a/internal/utils/git_ignore_test.go b/internal/utils/git_ignore_test.go index febc8bc..a1f3635 100644 --- a/internal/utils/git_ignore_test.go +++ b/internal/utils/git_ignore_test.go @@ -47,6 +47,28 @@ func TestGitIgnore(t *testing.T) { assertFileSkipped(t, &gitIgnore, "ignore/this/file.txt") assertFileNotSkipped(t, &gitIgnore, "some/other/file.txt") }) + + t.Run("never skip .git directory or files inside it", func(t *testing.T) { + tmpRepoDir := t.TempDir() + writeFile(t, filepath.Join(tmpRepoDir, ".gitignore"), ".git\n.git/*\n") // would match .git if we didn't special-case it + gitIgnore := NewGitIgnore(tmpRepoDir) + + pathsThatMustNotBeSkipped := []string{ + ".git", + ".git/HEAD", + ".git/config", + ".git/refs/heads/main", + "/Users/example/repo/.git", + "repo/.git", + "subdir/repo/.git/refs", + } + for _, path := range pathsThatMustNotBeSkipped { + path := path + t.Run(path, func(t *testing.T) { + assertFileNotSkipped(t, &gitIgnore, path) + }) + } + }) } func assertFileSkipped(t *testing.T, gitIgnore *GitIgnore, path string) {