`worktree.Status()` not respecting .gitignore
When we have multi nested directory and the .gitignore file is inside that. The wroktree.Status() is not respecting patterns inside .gitignore that can cause the git status to dirty.
How to reproduce
Check this repo https://github.com/acumino/git-status. When we run $ go run ./test.go which is basically checking the status and returning an error if status is not clean.
wt, err := repo.Worktree()
if err != nil {
fmt.Printf("failed to get git worktree: %v", err)
}
if stat, err := wt.Status(); err != nil {
fmt.Printf("failed to get git status: %v", err)
} else if !stat.IsClean() {
fmt.Printf("%v\n%v", err, stat)
}
It throws an error and ideally it should not.
?? pp/hh/hack1/configs/authwebhook.yaml
Explanation
The issue is happening because append() is being used for the slice in recursion at line 64.
https://github.com/go-git/go-git/blob/c785af3f4559ebac52c42f12d17cb118aac383ad/plumbing/format/gitignore/dir.go#L49-L76
The passed value of fs is being changed with appends and is being used as domain here https://github.com/go-git/go-git/blob/c785af3f4559ebac52c42f12d17cb118aac383ad/plumbing/format/gitignore/pattern.go#L42
So when this fs is updated next time with append it is causing overwrite to previously written domain values leading to previously added pattern's domain to overwritten. So git status basically at the end store wrong pattern to ignore, leading to dirty git status.
An explanation of how slices are passed in function and are used internally golang https://stackoverflow.com/questions/39993688/are-slices-passed-by-value.
Solution
Instead to passing append(path, fi.Name()) in ReadPatterns() every times a new copy of slice should be passed.
cc - @mcuadros
I was trying to follow along here because I had a similar theory. https://go.dev/play/p/pjtX_coRiQ8
package main
import "fmt"
func recurse(a []string) []string {
if len(a) == 3 {
return a
}
return recurse(append(a, "test"))
}
func main() {
a := []string{"a"}
b := recurse(a)
fmt.Println(a, b)
}
My assumption was that because Go slices are pass-by-value that a and b would have the same contents.
They do not in this case. This program evaluated to.
[a] [a test test]
Program exited.
The return value of append will be a slice that points to the newly allocated array. I could be widely misunderstanding the issue though.
Please check this issue https://github.com/golang/go/issues/52152 for examples.
As suggested in the issue https://github.com/golang/go/issues/52152 there is nothing that can be done from the go side. If needed I can open a PR with a possible workaround.
@acumino , I have no association with this repo, but I think a PR would be welcome. Do you have a fork with your copy-based workaround?
I've run into the issue where there's a large directory (~300M of contents) that is in the .gitignore file. The worktree.Status() call takes anywhere from 1.6s (with repeated calls so that the kernel is caching the directory contents I'm guessing), to 15s, whereas if I delete that directory, the call takes ~300ms (with repeated calls).
For comparison, the git status command returns in <20ms
I'm guessing respecting the .gitignore would be a non-trivial performance improvement.
@acumino another issue I spot on .gitignore is that a global .gitignore doesn't take effect.
I think I'm seeing the same as @tisonkun, with my attempts at debugging reported here. Copying my gitignore file into the directory that I've specified as the Path in my Add/AddOptions doesn't seem to have any impact though.
as @tisonkun mentioned, we're seeing the same issue in #597