简体   繁体   中英

Github - how to revert a pull from wrong branch

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 pull command just runs git fetch (which is always 1 safe to do at any time) followed by git merge 2 (which is less so). What you therefore need to recover from is your git merge . But this:

Now, it keeps pulling from the origin/lorem-ipsum instead of origin/master

... suggests that you told Git to remember that the "upstream" for master is origin/lorem-ipsum , not origin/master . If that's true—you don't show the actual git pull you ran, nor the output of git status or git branch -vv , all of which would be useful clues here—then you also need to fix the upstream setting for your master .

Before we dive into the next step, there's one more thing to note:

The files which got pulled also came with conflicts.

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 add the results, and do a final git commit to make the merge commit. (When you don't get a merge conflict, Git adds and commits the result for you.) The git status command 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 ( 1fc39a7 or deadc0d or 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):

... <-E <-F <-G <-K   <-- master (HEAD)
       \         /
        H <-I <-J   <-- origin/lorem-ipsum

Here K is our merge commit, pointing back to both G and J . G is the commit that was the last commit on your master . The name master now means ("points to") this commit K . The name origin/lorem-ipsum points to commit J . We also mark master with HEAD , to note that this is the branch we have checked-out now.

To remove K entirely, we can use git reset . The git reset command can be quite destructive, so before you use it, run git status to make sure there's nothing you care about that you will lose. (This is a recurring theme: run git status . ) Having made sure you are on master , that there's nothing else you can lose right now, and that master points to this merge commit K that you do want to lose on purpose:

git reset --hard master~1

The ~1 suffix tells Git to find the commit to which master points, and then move back one step . That is, follow the arrow coming out of K . If there are two arrows—there are, because K is a merge—Git should follow the first one, which is always the one pointing to the commit that was on master just before we did the merge. So Git follows the arrow to commit G .

(Another way to do this is to use git log to find the actual hash ID for commit G , and run git reset --hard <hash-id> . That's harder to type in, although cut-and-paste should work well. But this "step back 1" with ~1 is easier, I think.)

What git reset --hard does is three things:

  1. Change the branch to point to the selected commit. That means master now names commit G , not K :

     ...--E--F--G <-- master (HEAD) \\ H--I--J <-- origin/lorem-ipsum 

    (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.)

  2. The reset --hard step 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 and git add changes to make a new commit. So this is what you want: for your index to match commit G .

  3. The reset --hard step also resets your work-tree , ie, 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 from K and replaces them with the versions from G , 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 run git branch --set-upstream-to= new-upstream . In this case, you want to set it to origin/master again, so:

git branch --set-upstream-to=origin/master

Now your current branch's ( master 's) upstream is origin/master , so that git pull means "fetch, then merge with origin/master "—presumably what you want.

I recommend avoiding git pull

The git pull command is meant to be convenient. And it is ... but this convenience is a sort of a trap. If you knew you were doing git fetch followed by git 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 rebase anyway. So then you should run git fetch followed by git rebase . You can get git pull to 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 up git pull to do that.


1 There are ways you can run git fetch manually that are not entirely safe, but they are very difficult to do. You won't get this accidentally.

2 You can direct git pull to use git rebase as its second step, instead of running git merge as its second step. I assume, and what you show suggests, you did not do this.

3 Well, 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM