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
CodePudding user response:
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.