The Git repository that vanished by Mark Seemann
A pair of simple operations resurrected it.
The other day I had an 'interesting' experience. I was about to create a small pull request, so I checked out a new branch in Git and switched to my editor in order to start coding when the battery on my laptop died.
Clearly, when this happens, the computer immediately stops, without any graceful shutdown.
I plugged in the laptop and booted it. When I navigated to the source code folder I was working on, the files where there, but it was no longer a Git repository!
Git is fixable #
Git is more complex, and more powerful, than most developers care to deal with. Over the years, I've observed hundreds of people interact with Git in various ways, and most tend to give up at the first sign of trouble.
The point of this article isn't to point fingers at anyone, but rather to serve as a gentle reminder that Git tends to be eminently fixable.
Often, when people run into problems with Git, their only recourse is to delete the repository and clone it again. I've seen people do that enough times to realise that it might be helpful to point out: You may not have to do that.
Since I use Git tactically I have many repositories on my machine that have no remotes. In those cases, deleting the entire directory and cloning it from the remote isn't an option. I do take backups, though.
Still, in this story, the repository I was working with did have a remote. Even so, I was reluctant to delete everything and start over, since I had multiple branches and stashes I'd used for various experiments. Many of those I'd never pushed to the remote, so starting over would mean that I'd lose all of that. It was, perhaps, not a catastrophe, but I would certainly prefer to restore my local repository, if possible.
The symptoms were these: When you work with Git in Git Bash, the prompt will indicate which branch you're on. That information was absent, so I was already worried. A quick query confirmed my fears:
$ git status fatal: not a git repository (or any of the parent directories): .git
All the source code was there, but it looked as though the Git repository was gone. The code still compiled, but there was no source history.
Since all code files were there, I had hope. It helps knowing that Git, too, is file-based, and all files are in a hidden directory called
.git. If all the source code was still there, perhaps the
.git files were there, too. Why wouldn't they be?
$ ls .git COMMIT_EDITMSG description gitk.cache hooks/ info/ modules/ objects/ packed-refs config FETCH_HEAD HEAD index logs/ ms-persist.xml ORIG_HEAD refs/
Jolly good! The
.git files were still there.
I now had a hypothesis: The unexpected shutdown of my machine had left some 'dangling pointers' in
.git. A modern operating system may delay writes to disk, so perhaps my
git checkout command had never made it all the way to disk - or, at least, not all of it.
If the repository was 'merely' corrupted in the sense that a few of the reference pointers had gone missing, perhaps it was fixable.
A few web searches indicated that the problem might be with the
HEAD file, so I investigated its contents:
$ cat .git/HEAD
That was all. No output. The
HEAD file was empty.
That file is not supposed to be empty. It's supposed to contain a commit ID or a reference that tells the Git CLI what the current head is - that is, which commit is currently checked out.
While I had checked out a new branch when my computer shut down, I hadn't written any code yet. Thus, the easiest remedy would be to restore the head to
master. So I opened the
HEAD file in Vim and added this to it:
And just like that, the entire Git repository returned!
Bad object #
The branches, the history, everything looked as though it was restored. A little more investigation, however, revealed one more problem:
$ git log --oneline --all fatal: bad object refs/heads/some-branch
While a normal
git log command worked fine, as soon as I added the
--all switch, I got that
bad object error message, with the name of the branch I had just created before the computer shut down. (The name of that branch wasn't
some-branch - that's just a surrogate I'm using for this article.)
Perhaps this was the same kind of problem, so I explored the
.git directory further and soon discovered a
some-branch file in
.git/refs/heads/. What did the contents look like?
$ cat .git/refs/heads/some-branch
Another empty file!
Since I had never committed any work to that branch, the easiest fix was to simply delete the file:
$ rm .git/refs/heads/some-branch
That solved that problem as well. No more
fatal: bad object error when using the
--all switch with
No more problems have shown up since then.
My experience with Git is that it's so powerful that you can often run into trouble. On the other hand, it's also so powerful that you can also use it to extricate yourself from trouble. Learning how to do that will teach you how to use Git to your advantage.
The problem that I ran into here wasn't fixable with the Git CLI itself, but turned out to still be easily remedied. A Git guru like Enrico Campidoglio could most likely have solved my problems without even searching the web. The details of how to solve the problems were new to me, but it took me a few web searches and perhaps five-ten minutes to fix them.
The point of this article, then, isn't in the details. It's that it pays to do a little investigation when you run into problems with Git. I already knew that, but I thought that this little story was a good occasion to share that knowledge.