48

Here is what happened:

I have two remote git branches: master and feature1. For some reason I have to use git push --force for the feature1 branch, but I didn't know when I use git push --force it will also push the master branch. Then, a disaster happened, as I pushed my local master branch to the remote repository.

Luckily, my local branch is not too far away from the remote. Basically, my remote master has two pull requests merged ahead of my local master.

So my problem is: can I reopen the pull request and remerge? I noticed that there is commit version for merge request, so I am worried if I simply make new pull request it will mess up something? Ideally I just want to redo the merging of the two requests.

Is there some other way to recover from this disaster? I learned the --force is a really, really bad choice. :(

Update, example of what happened:

I have following branches:

master
feature1
origin/master
origin/feature1

I integrate two pull requests by using the GitHub's Auto merge pull requests. Then, I didn't fetch the master branch on my local machine. Thus, I think my origin/master is two versions behind the remote master.

Then I accidentally used git -f push, which overwrote the remote branch and now I lost the commits from the pull requests on remote repository.

How can I recover from it without messing up other contributors' history?

Jawa
  • 2,336
  • 6
  • 34
  • 39
Brian
  • 735
  • 1
  • 8
  • 14
  • possible duplicate of [How can I recover from an erronous git push -f origin master?](http://stackoverflow.com/questions/3973994/how-can-i-recover-from-an-erronous-git-push-f-origin-master) – CharlesB Sep 24 '12 at 19:58
  • You will soon (git1.8.5, Q4 2013) be able to [do a `git push -force` more carefully](http://stackoverflow.com/a/18505634/6309). – VonC Sep 10 '13 at 08:45

2 Answers2

99

When working with github, refer to GHugo's answer which gives a foolproof procedure with github. If you're on an in-house (or other non-github) installation, read on.

You can always restore the previously observed state of master, by resetting to the old commit and issuing another push -f. The steps involved typically look like this:

# work on local master
git checkout master

# reset to the previous state of origin/master, as recorded by reflog
git reset --hard origin/master@{1}

# at this point verify that this is indeed the desired commit.
# (if necessary, use git reflog to find the right one, and
# git reset --hard to that one)

# finally, push the master branch (and only the master branch) to the server
git push -f origin master

Note, however, that this restores remote master to the state most recently retrieved by git fetch or equivalent. Any commits pushed by others after the last time you fetched will be lost. However, those commits will still be available in their reflogs, so they can restore them using steps like the above.

user4815162342
  • 141,790
  • 18
  • 296
  • 355
  • 1
    the problem is that i did this git push -f, so I overwrite the origin's master branch. And I did this before I fetch my origin/master, so if I do this reset, won't it just reset it to my local origin/master? – Brian Sep 24 '12 at 17:19
  • basically i did this git push -f before I use git fetch origin/master – Brian Sep 24 '12 at 17:20
  • 1
    The command will restore origin/master to its state before the push. – user4815162342 Sep 24 '12 at 17:30
  • After running the commands above, you will be at the state you were at before merging the push-requests. Redo the merge at github, and you should be set. – user4815162342 Sep 24 '12 at 17:54
  • yep, so how can i redo the merge? – Brian Sep 24 '12 at 17:57
  • If github's UI doesn't allow you to redo the merge, you can always do it yourself - find the SHA1 of the commit, issue `git merge COMMIT`, and push the merge. After all, one of the chief advantages of DVCS like git is that one doesn't depend on a centralized service like github. – user4815162342 Sep 24 '12 at 18:26
  • `git reset --hard origin/master@{1}` is not working for me: `fatal: Log for 'origin/master' only has 1 entries.` – Emil Laine Dec 14 '15 at 13:59
  • This is good but I do not understand how it solves OP’s problem in particular. “retrieved by `git fetch` or equivalent”—but OP won’t have the remote `master` that they are looking for since they didn’t fetch before force-pushing. – Guildenstern Oct 08 '21 at 18:11
  • @Guildenstern The truth is that we don't know how often the OP fetched or when the last `git push` to master occurred by someone else. This answer provides the best possible recovery given pure `git`, and documents the limitations. In case of github the answer by GHubo is the best approach. – user4815162342 Oct 14 '21 at 12:17
  • We don’t know what other people did, true, but OP did not fetch after integrating the two PRs: “Then, I didn't fetch the `master` branch on my local machine.”. – Guildenstern Oct 14 '21 at 14:41
  • @Guildenstern I interpreted that as the OP failing to incorporate the newer versions of remote `master` into their `master`, not that they never ran `git fetch` to refresh the local view of `origin/master`. The OP (likely) did "observe" the `origin/master` that they force-pushed over, which is why the recipe from the answer helped. – user4815162342 Oct 14 '21 at 14:55
54

Note that, with Github, you can use the API to recover a forced push even if you do not have the repository cloned locally (i.e., when you do not have a reflog), or the commit sha.

First, you must get the previous commit sha, the one before the forced push:

curl -u <username> https://api.github.com/repos/:owner/:repo/events

Then you can create a branch from this sha:

curl -u <github-username> -X POST -d '{"ref":"refs/heads/<new-branch-name>", "sha":"<sha-from-step-1>"}' https://api.github.com/repos/:owner/:repo/git/refs

Finally, you can clone the repository locally, and force push again to master:

git clone repo@github
git checkout master
git reset --hard origin/<new-branch-name>
git push -f origin master

Note that with two-factor authentication, you need to provide a token (see here for more information).

Credit: Sankara Rameswaran

GHugo
  • 2,584
  • 13
  • 14
  • 1
    You have saved me. I might have to use regex to get the text out of this json file i have now, unless you have another idea but i got my data back even if it's not in a usable format yet – rjm27trekkie Apr 15 '17 at 01:34
  • 2
    You deserve a lot more than 1 upvote!!! What a hero, 1.5 days of work not lost forever! – RichardAE Feb 13 '18 at 16:53
  • 1
    Thank you so much. You are a saviour. Just to add one more thing: it seems like the command didn't work for me at first. I had to create a github rest api personal access token to use in the command (curl -u name:token). Other than that, all is well – Thomas Bui Jan 09 '21 at 19:54
  • 2
    One thing to add to this otherwise lifesaving answer, github now requires a token when accessing the api using `curl -u :`. See https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token – Rey Abolofia Feb 03 '21 at 22:00
  • This was a huge help when a less experienced colleague force pushed and erased 2 years of git history. Now I'm learning about how to protect branches... – brendan Jul 18 '21 at 17:26
  • Saved me!!! I have had a long list when requesting `https://api.github.com/repos/:owner/:repo/events` so I used https://docs.github.com/en/rest/activity/events#list-events-for-the-authenticated-user endpoint to find the SHA. – Ali Niaki Jul 12 '22 at 15:49