#2112·go-git

go-git ignores files inside un-ignored directory with wildcard pattern

Author: aktauCreated May 12, 2026Updated Sep 18, 2026
Labelsbug

Bug Description

When using a .gitignore file with a wildcard ignore * followed by a directory-only inclusion like !dir/, go-git fails to ignore files inside dir/, whereas standard Git ignores them.

In plumbing/format/gitignore/pattern.go, the simpleNameMatch function iterates over all components of a path. If any component matches the pattern, it returns true (matched), unless it's the last component and it's checking dirOnly.

For a path ["dir", "file.txt"] and pattern !dir/:

  1. Component dir matches dir.
  2. It is not the last component (i == 0, len(path)-1 == 1).
  3. The function returns true (matched).
  4. This causes the file to be included, overriding the * pattern.

Effectively, !dir/ acts as !dir/** in go-git.

(I did use AI to generate a bug report with identifiers that avoid the non-public parts of my project, I edited it myself and ran the reproducer myself.)

go-git Version

488798e28afdf2a31fc6ce32fd3ec8023025f449

Steps to Reproduce

To reproduce:

Standard Git:

bash
mkdir git-repro
cd git-repro
git init
echo '*' > .gitignore
echo '!my-dir/' >> .gitignore
mkdir -p my-dir/sub
echo 'test' > my-dir/sub/file.txt
git status

This would say: nothing to commit (the file inside my-dir is ignored).

git based on go-git:

Run these commands to set up the environment and run the reproducer:

bash
mkdir gogit-repro
cd gogit-repro
# Save the Go code below as repro.go
go mod init repro
go get github.com/go-git/go-git/v5
go run repro.go
go
// repro.go
package main

import (
	"fmt"
	"os"

	"github.com/go-git/go-git/v5"
)

func main() {
	// Setup environment
	os.MkdirAll("my-dir/sub", 0755)
	os.WriteFile(".gitignore", []byte("*\n!my-dir/\n"), 0644)
	os.WriteFile("my-dir/sub/file.txt", []byte("test"), 0644)

	r, err := git.PlainInit(".", false)
	if err != nil {
		panic(err)
	}
	w, err := r.Worktree()
	if err != nil {
		panic(err)
	}

	status, err := w.Status()
	if err != nil {
		panic(err)
	}

	fmt.Println("Status:")
	for path, s := range status {
		fmt.Printf("  %s: %c\n", path, s.Worktree)
	}
}

Actual Output:

Status:
  my-dir/sub/file.txt: ?

(? indicates Untracked, meaning it was not ignored).

Additional Information

No response