gitgit-rebasegit-stashgit-fsckgit-dangling

git fsck combining --lost-found and --unreachable


I found many interesting posts about git fsck, so I wanted to experiment a little on them. First of all the sources I read before this question:

I started with this repo:

* 9c7d1ea (HEAD -> test) f
* cd28884 e
| * 7b7bac0 (master) d
| * cab074f c
|/  
* d35af2c b
| * f907f39 r # unreferenced commit
|/
* 81d6675 a

Where r has been created from a detached HEAD from a. Then I wanted to rebase master on test, but I had some unstaged changes, so I did:

git rebase --autostash test

Obtaining (I am not showing r but it is still there):

* caee68c (HEAD -> master) d
* 2e1cb7d c
* 9c7d1ea (test) f
* cd28884 e
* d35af2c b
* 81d6675 a

Next I run:

$ git fsck
#...
dangling commit 6387b70fe14f1ecb90e650faba5270128694613d # stash
#...
$ git fsck --unreachable
#...
unreachable commit 6387b70fe14f1ecb90e650faba5270128694613d # stash
unreachable commit d8bb677ce0f6602f4ccad46123ee50f2bf6b5819 # stash index
#...
$ git fsck --lost-found
#...
dangling commit 6387b70fe14f1ecb90e650faba5270128694613d # stash
dangling commit f907f39d41763accf6d64f4c736642c0120d5ae2 # r
#...

First question

Why does only the --lost-found version return the r commit? And why are not the c and d before the rebase shown among the unreachables? I thought I understood the difference reading the linked questions, but I am clearly missing something. I still have the complete reflog, but I guess you do not need it, since all commits (except those related to the stash) are referenced.


I know I should create another post but the second question is partially related. I tried out of curiosity:

$ git fsck --lost-found --unreachable
#...
unreachable commit 6387b70fe14f1ecb90e650faba5270128694613d # stash
unreachable commit d8bb677ce0f6602f4ccad46123ee50f2bf6b5819 # stash index
unreachable commit f907f39d41763accf6d64f4c736642c0120d5ae2 # r
unreachable commit 7b7bac0608936a0bcc29267f68091de3466de1cf # c before rebase
unreachable commit cab074f2c9d63919c3fa59a2dd63ec874b0f0891 # d before rebase
#...

Second question

Combining both options I get all the unreachable commits (and not just the union of --lost-found and --unreachable), this is very unexpected. Why does it behave like this?


Solution

  • Some of this is indeed puzzling, and appears not to be properly documented, but a quick look at builtin/fsck.c shows that using --lost-found:

    1. turns on --full;
    2. turns on --no-reflogs.

    Item 1 isn't particularly interesting since --full is now on by default anyway, but the documentation really should call out that --lost-found disables --no-full. Item 2 explains most of the rest; I have a guess at the last part [Edit: the rest].

    Note that when you ran:

    git checkout master && git rebase --autostash test
    

    this made Git run git stash push, which made a new stash consisting of two new commits. Git then did the rebase as usual, which copied the cab074f and 7b7bac0 commits, visible in the original git log --all --decorate --oneline --graph output, to the new 2e1cb7d and caee68c commits visible in the second output.

    Why does only the --lost-found version return the r commit? And why are not the c and d before the rebase shown among the unreachables?

    Presumably that commit is still in the HEAD reflog. That makes it reachable from a reference—but since --lost-found implies --no-reflogs, it becomes unreachable this time. The same goes for the originals of c and d: they're reachable via multiple reflog entries, from both HEAD's reflog and master's.

    Combining both options I get all the unreachable commits (and not just the union of --lost-found and --unreachable), this is very unexpected. Why does it behave like this?

    That's more puzzling. [Edit: solved; see below.] Let's run these in order of your git fsck commands:

    If you would like to report a Git documentation bug, that the fsck documentation does not note that --lost-found implies --no-reflogs, you should do that.