I accidentally made a pull from a different branch, which shifted the HEAD saying:
HEAD is now at 7c0208906 Merge remote-tracking branch 'refs/remotes/origin/lorem-ipsum'
Now, it keeps pulling from the origin/lorem-ipsum instead of origin/master
The files which got pulled also came with conflicts. Now its been a few days, new changes have been made on the master branch by others in the parent repository and i'm behind.
How can I revert the state of my repository back to how it was prior to my wrong pull and how can I simply shift the HEAD to previous state?
Please help, I'm stuck!
The
git pullcommand just runsgit fetch(which is always1 safe to do at any time) followed bygit merge2 (which is less so). What you therefore need to recover from is yourgit merge. But this:... suggests that you told Git to remember that the "upstream" for
masterisorigin/lorem-ipsum, notorigin/master. If that's true—you don't show the actualgit pullyou ran, nor the output ofgit statusorgit branch -vv, all of which would be useful clues here—then you also need to fix the upstream setting for yourmaster.Before we dive into the next step, there's one more thing to note:
It's important to remember that Git doesn't pull files. Git obtains, and then merges, commits. Commits have files, and merging some commits can result in conflicts in files, so this might seem like a minor quibble, but it affects how you recover these things.
When you do get a merge conflict, you must resolve the conflicted files yourself,
git addthe results, and do a finalgit committo make the merge commit. (When you don't get a merge conflict, Git adds and commits the result for you.) Thegit statuscommand is extremely useful as it will tell you if you are in the middle of a conflicted merge, plus a lot of other good information. Use it often.Getting rid of a bad merge
If you're in the middle of a bad merge and you want the whole thing to go away, that's easy: just run
git merge --abort. That stops the merge process and puts everything back the way it was before you started.If you've finished the merge, but it was bad and you want to get rid of it, that's harder, because you, or Git, finish a merge by making a new commit, and the whole point of commits is that they are permanent and unchanging. Commits stick around "forever",3 and new commits build upon previous commits, so it's really hard to get rid of a "middle" one. It's much easier to get rid of a few "end" ones.
Let's draw some commits, including a merge, to see what we mean here. Note that each commit has some incomprehensible hash ID (
1fc39a7ordeadc0dor some such). To keep things clearer, we'll use single uppercase letters for each commit. Note also that each commit has a parent commit, except for merges which have two parent commits. We say that each commit "points back to" its parent (or, for a merge, parents plural):Here
Kis our merge commit, pointing back to bothGandJ.Gis the commit that was the last commit on yourmaster. The namemasternow means ("points to") this commitK. The nameorigin/lorem-ipsumpoints to commitJ. We also markmasterwithHEAD, to note that this is the branch we have checked-out now.To remove
Kentirely, we can usegit reset. Thegit resetcommand can be quite destructive, so before you use it, rungit statusto make sure there's nothing you care about that you will lose. (This is a recurring theme: rungit status.) Having made sure you are onmaster, that there's nothing else you can lose right now, and thatmasterpoints to this merge commitKthat you do want to lose on purpose:The
~1suffix tells Git to find the commit to whichmasterpoints, and then move back one step. That is, follow the arrow coming out ofK. If there are two arrows—there are, becauseKis a merge—Git should follow the first one, which is always the one pointing to the commit that was onmasterjust before we did the merge. So Git follows the arrow to commitG.(Another way to do this is to use
git logto find the actual hash ID for commitG, and rungit reset --hard <hash-id>. That's harder to type in, although cut-and-paste should work well. But this "step back 1" with~1is easier, I think.)What
git reset --harddoes is three things:Change the branch to point to the selected commit. That means
masternow names commitG, notK:(I left out the internal arrows this time, as they're a pain to draw and don't give us much. What happened to
K? It's still in there, in your repository, but it's in the recycling bin now, where it is hard to find and Git normally won't show it any more.)The
reset --hardstep also resets Git's index. Git's index is best described, I think, as "where you build up your next commit". In normal cases, you want your index to match your current commit until you're starting to edit andgit addchanges to make a new commit. So this is what you want: for your index to match commitG.The
reset --hardstep also resets your work-tree, i.e., the place where you have all your files visible in the normal way so that you can work with or on them. This throws away the merged versions fromKand replaces them with the versions fromG, which is also what you want.Now it's as though the merge never happened.
Your upstream setting is still probably
origin/lorem-ipsum, though.Fixing or changing your upstream
To change the upstream setting of your current branch (still
master), simply rungit branch --set-upstream-to=new-upstream. In this case, you want to set it toorigin/masteragain, so:Now your current branch's (
master's) upstream isorigin/master, so thatgit pullmeans "fetch, then merge withorigin/master"—presumably what you want.I recommend avoiding
git pullThe
git pullcommand is meant to be convenient. And it is ... but this convenience is a sort of a trap. If you knew you were doinggit fetchfollowed bygit merge, you would have known to look at ways to undo a merge (and there are lots of existing SO answers for this).Besides this, it's often better to use
git rebaseanyway. So then you should rungit fetchfollowed bygit rebase. You can getgit pullto do this for you ... but sometimes rebase isn't the way to go; sometimes merge is better. As you learn more Git, you will find that the merge-vs-rebase decision sometimes depends on what you get when you fetch. In which case, how can you decide in advance whether to rebase or merge, before you see what gets fetched?It's better to learn the separate steps first, I think. Then, once you know them well, you can decide whether typing in just the one command (
git pull) is worth the convenience vs the occasional headache when it goes wrong. You will also know by then whether you want to merge or rebase by default, and you can set upgit pullto do that.1There are ways you can run
git fetchmanually that are not entirely safe, but they are very difficult to do. You won't get this accidentally.2You can direct
git pullto usegit rebaseas its second step, instead of runninggit mergeas its second step. I assume, and what you show suggests, you did not do this.3Well, that is, commits are permanent until you deliberately throw them out. Then they are eventually tossed out for real with the rest of the rubbish, by
git gc, the Garbage Collector, after a suitable waiting period during which you can recover them from the trash.