src-d / go-git

Project has been moved to: https://github.com/go-git/go-git
https://github.com/go-git/go-git
Apache License 2.0
4.91k stars 541 forks source link

repo.Head() fails when referenced from a linked worktree after being garbage collected #1252

Open nicerobot opened 4 years ago

nicerobot commented 4 years ago

When a linked worktree's HEAD ref is a tracking-branch, this code will attempt to open the ref using the path in the .git/worktree which is not where the refs are stored.

https://github.com/src-d/go-git/blob/0d1a009cbb604db18be960db5f1525b99a55d727/storage/filesystem/dotgit/dotgit.go#L927-L931

Example

Demo

This example doesn't technically git gc the worktree but the scenario is the same.

Output

$ make
git clone git@github.com:src-d/go-git.git
Cloning into 'go-git'...
cd go-git; git fetch --all
go-git-bug/go-git
Fetching origin
cd go-git; git worktree add -b new-ref ../go-git-linked/
go-git-bug/go-git
Preparing worktree (new branch 'new-ref')
HEAD is now at 1a7db85bca70 Merge pull request #1231 from alexandear/fix-typos
go build -o bugged

---- Works as expected
go-git-bug/go-git
git rev-parse --git-dir
.git
git rev-parse HEAD
1a7db85bca7027d90afdb5ce711622aaac9feaed
2019/12/01 11:39:36 worktree: go-git-bug/go-git
2019/12/01 11:39:36 1a7db85bca7027d90afdb5ce711622aaac9feaed refs/heads/master
2019/12/01 11:39:36 1a7db85bca7027d90afdb5ce711622aaac9feaed refs/heads/new-ref
2019/12/01 11:39:36 head: 1a7db85bca7027d90afdb5ce711622aaac9feaed refs/heads/master

---- Bug this does not work as expected
go-git-bug/go-git-linked
git rev-parse --git-dir
go-git-bug/go-git/.git/worktrees/go-git-linked
git rev-parse HEAD
1a7db85bca7027d90afdb5ce711622aaac9feaed
2019/12/01 11:39:36 worktree: go-git-bug/go-git-linked
2019/12/01 11:39:36 head: reference not found
make: *** [bug] Error 1

Call-stack

repo, err := git.PlainOpenWithOptions(".", &git.PlainOpenOptions{DetectDotGit: true})
repo.Head()
storer.ResolveReference(r.Storer, plumbing.HEAD)
resolveReference(s, r, 0)
s.Reference(r.Target())
r.dir.Ref(n) // <- `n` (i.e. `r.Target()`) is `ref/heads/new-ref`
d.readReferenceFile(".", name.String())
d.fs.Open(path) // <- this `path` is wrong: go-git/.git/worktrees/go-git-linked/refs/heads/new-ref

Additionally

There is also a scenario for linked worktrees when the worktree's .git/ref/heads file can be garbage collected (git gc in the worktree, because it is the same as a ref/remotes?) so the above will always fail.

nicerobot commented 4 years ago

The problem appears to be:

https://github.com/src-d/go-git/blob/0d1a009cbb604db18be960db5f1525b99a55d727/storage/filesystem/storage.go#L52-L62

In this linked worktree example, the ObjectStorage and ReferenceStorage should be based on the contents of the go-git/.git/worktrees/go-git-linked/gitdir file.

AlexandreCarlton commented 4 years ago

It looks like this would be solved by parsing commondir in the worktree folder (i.e. .git/worktrees/<worktree>/commondir) which points back to the main .git folder. While #1098 parses this, it hasn't been touched in almost a year.