Did I just lose all my work? How to restore my local data? (Git)

I’m very new to git. I’m an idiot and I was using it without really knowing what I was doing. I accidentally overwrote everything in my local with the master. 36e6aed is a commit I performed on the master 9 days ago. Can I restore my ‘major updates’ commit?

Note: before this I did git add . and then I type in something wrong after that, got confused, exited the window, and did what is printed below.

location/of/local/branch (master|MERGING)
$ git stash
Saved working directory and index state WIP on master: 36e6aed Restructure

location/of/local/branch (master)
$ git commit -m 'major updates'
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean

location/of/local/branch (master)
$ git status
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean

location/of/local/branch (master)
$ git reflog
36e6aed (HEAD -> master, origin/master) HEAD@{0}: reset: moving to HEAD
36e6aed (HEAD -> master, origin/master) HEAD@{1}: commit: Restructure
b1ad2dc HEAD@{2}: pull origin master: Merge made by the 'ort' strategy.
5173228 HEAD@{3}: commit: tortoise mods
3df3009 HEAD@{4}: commit: Restructure
76a5c29 HEAD@{5}: pull origin master: Fast-forward
da38dd1 HEAD@{6}: pull: Fast-forward
122b86d HEAD@{7}: pull: Merge made by the 'ort' strategy.
bdc9928 HEAD@{8}: commit: In & Out folders
f339839 HEAD@{9}: commit (initial): Initial Commit

>Solution :

You did not make a major updates commit. Fortunately, you did make a commit; we’ll get it back in a moment. Here’s what Git said when you tried to make that one:

$ git commit -m 'major updates'
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean

This (not very helpful) Git message is saying "I didn’t do anything because there was nothing to do."

The trick here is realizing why there was nothing to do, and that’s from right before the attempt to make a commit:

location/of/local/branch (master|MERGING)
$ git stash
Saved working directory and index state WIP on master: 36e6aed Restructure

The git stash command means, roughly: Commit what I have now, but on no branch at all, then remove all the work now that it is safely saved in a commit. So you did make a commit, but it’s not on any branch. To get it back, simply run git stash apply, inspect the result carefully to make sure you like it, then—if and only if you like it—run git stash drop.

(You can run git stash pop. This means apply and immediately drop. I recommend separating the steps so that you can inspect first. Usually, the apply is fine and dropping is correct, but its a bit like closing your eyes and leaping off the bridge hoping you land in the lake and not on the rock next to the lake.)

There’s a lot more to it than this, but in your case you just want to get back the stashed stuff so that you can commit it normally.

More

Your shell prompt, before you ran git stash, showed:

location/of/local/branch (master|MERGING)

This implies you’re using one of the Git-aware shell prompt packages. That particular package noticed that you were in the middle of a merge, probably a merge that paused due to conflicts. When you invoke git merge—and git pull will invoke git merge for you, which counts as you invoking git merge—Git tries to combine work. Git is not always able to do this on its own.

When Git’s attempts to combine your work (your commits on your branch) with someone else’s work (their commits on their branch1), this may go smoothly, or it might not. If it does go smoothly, Git will often2 make a new commit. If not, however, the merge stops in the middle, and those prompt-setting shell add-ons insert the |MERGING notation.

When the merge does stop in the middle like this, Git leaves you with a mess in your working tree.3 Your job is to resolve the mess and run git add. Running git add on a conflicted file tells Git that you have correctly resolved the conflict. Git assumes that you know what you’re doing here, and takes whatever is in your working tree copy at this time as the correct resolution.

When you run git add . you are telling Git to scan the entire current directory and sub-directories—probably your entire working tree—and git add each file. So this tells Git that you resolved all the conflicts. Until you do that, you can’t make a new commit, not even the ones that git stash makes. So your git stash worked because you ran git add ..

If you really were ready to go (had resolved everything), the git stash pop or git stash apply you’ll do to get things back will get what you wanted. If not, you still need the git stash apply or git stash pop step, but after that, you need to resume resolving the conflicts: the apply step will have brought any unresolved conflict marker sections back. Git can’t help you here because you already told Git that you resolved everything, so Git has forgotten what the conflicts were, and will think that a working tree file with conflict markers in it is the correct resolution (though it almost certainly isn’t). You’ll just have to carefully review what you plan to commit (use git diff and/or git diff --staged to find out).


1There’s a big problem with the word branch in Git: it means at least two or three different things, with the meaning switching around every time someone says or reads the word. See also What exactly do we mean by "branch"? In this case, "your branch" and "their branch" may have the same name—e.g., both might be called master—but they’re in two different repositories and are in fact different branches, in at least one of the many senses of the word "branch". It’s all very confusing and unfortunate.

2The git merge command sometimes doesn’t have to do any actual merging, and then doesn’t make a new commit unless you force it to do so. Git calls this not-actually-merging operation a fast forward merge, even though there’s no actual merging. This, too, s confusing and unfortunate.

3Your working tree or work-tree is where you do your work. Git copies files out of some commit, so that they become ordinary files; but these files are not actually in Git. You then work on / with these copies. When you’re ready to use them for a new commit, you run git add to get Git to scan the updated files and prepare them, then you run git commit to commit the scanned results, which are in Git’s internal form and aren’t the files in your working tree.


One more thing

Though you’re probably not ready for this yet (and can ignore it while getting your work done), you should know that git stash actually makes at least two commits. One of the two commits that git stash makes saves Git’s index (also known as the staging area). The other commit that git stash makes saves your working tree.

Then, having made these two commits, git stash runs git reset --hard. That’s what removed all your work: the git reset --hard step of the git stash save or git stash push.4 Normally git reset --hard is pretty destructive, but the fact that git stash first made some commits makes this okay (or okay-ish).


4The difference between the save and push verbs here is historical: they both do the same thing for the normal case of stashing. However, in Git 2.9 or so, git stash learned to make "partial stashes". To do that, the Git folks had to create a new verb, push, as the original verb save had been poorly planned. The default git stash action is the save-or-push operation and when you don’t use pathspecs with git stash push, it does a full save and hence a full reset. When you do use pathspecs, this changes the final clean-out from a git reset --hard to something much more selective.

There were numerous bugs in this git stash push partial-save code early on, so I advise not using it until about Git 2.17. Well, I generally advise avoiding git stash entirely, except in some very limited cases, but I mean that if you are going to use git stash push and use the partial push feature, be extra careful with it in these intermediate versions of Git.

Leave a Reply