Reputation: 54949
I have a lot of unstaged changes in my repo. When attempting to cherry pick from a remote branch I got a conflict. I hit abort and this deleted all the unstaged changes I made to my working directory and I lost lots of code.
How do I get it back?
This seems like the problem:
I tried: git checkout @{-1}
That got me an empty HEAD.
Upvotes: 0
Views: 1125
Reputation: 487993
Except for recovery via (e.g.) MacOS Time Machine or backups, you are probably out of luck.
"Unstaged changes" is misleading in Git. There aren't any such things. There are just work-tree files. When Git says that the work-tree file is different, that's a true statement: the work-tree copy is different from some other copy. When Git shows the difference, that's constructed by comparing the work-tree copy to the other copy.
If the difference is gone, one (or both!) of two things must have happened:
The other copy matches now: so the work-tree copy is still the same, but the other copy matches. That's what happens, for instance, when you add and commit: the git add
copies the work-tree copy of each file to replace the stale index copy, and then the commit makes a new commit from the index copy.
Or, the work-tree copy matches now: the old work-tree copy has been thrown out and replaced with the copy from whatever it was you were comparing against (either the index or the HEAD
commit, most likely).
It's worth remembering, when using Git, that each commit has a full and complete copy of every file (well, every file that's in the commit, but that's kind of redundant), frozen in that form forever.1 But these copies are in special, Git-only, compressed form. They're not useful to anything except Git.
Therefore, Git provides a way to extract the files from a commit, turning them back into their useful form. That puts a copy of the file into the work-tree, where you can see and work on it. These copies are not managed and maintained by Git, they just sit there in the work-tree after Git extracts them. Git cannot be held responsible for them, except during the extracting process when it creates them (or replaces their contents with whatever is extracted).
In between the current (frozen) HEAD
commit and the (totally liquid, not-managed-by-Git) work-tree, Git keeps a sort of "almost frozen" or "slushy" copy of each file as well. That copy initially matches the HEAD
commit, and it's always in the special Git-only format.
These copies of every file live in what Git calls, variously, the index, the staging area, or the cache, depending on who / what part of Git is doing the calling.
When you use git add
, you tell Git: take whatever's in the work-tree copy, and copy that back into the index, replacing the old index copy. This Git-ifies the contents, getting them all mostly-frozen, so that they're ready to go into the next commit. If the file you are git add
-ing was not in the index before, now it is in the index, in the special Git-only form, ready to be committed.
Hence, the best short description I know of for the index is that it is the commit you propose to make next. It has its own copy of every file, ready to be committed. This is what makes git commit
so fast, compared to most other commit-oriented version control systems: the others have to scan, often painfully slowly, through your entire work-tree, re-compressing every file to see if it's different or get it ready for the next commit. By saving the ones from the current commit into this index, Git has them all ready for the next commit.
So there are three copies of every file: the HEAD
copy, the index copy, and the work-tree copy. The HEAD
(current commit) copy is frozen and safe, like every committed copy. The index and work-tree copies can be changed. If you want to be sure not to lose a file, you copy it into the index and commit it. You can sometimes remove the commit later, using git rebase -i
to squash things down.
1More precisely, the copy of a file in a commit lasts as long as the commit itself lasts. Mostly, that's forever; but if you make some commits, never send / push them to anyone, and then use git rebase -i
to squish them together, you end up with one final commit, and the other intermediate ones eventually expire (and never get sent / pushed).
Upvotes: 2