There are a few ways to find out the original HEAD before the push:
Terminal scrollback
If you're lucky enough to have the terminal open still, there will be some output when the push was made that looks like this:
...
To user@host:repo.git
+ abcdef0...1234567 HEAD -> branchname (forced update)
Here, abcdef0
was the previous HEAD (your A
) and 1234567
was what you forced it to be instead.
git reflog
The output of git reflog
tells you the chronological history of what you did. You basically want to go back to the line in question (where you had checked out your branch before the changes) and grab the commit ID from the first column.
The most helpful command here is git reflog show remotes/origin/branchname
. This should show you your forced update (1234567
) and the previous commit ID (abcdef0
) as the top two lines.
Previous reference
A couple of commit references might be useful here. These are basically just references to different points on the reflog:
@{1}
(or branchname@{1}
, if you are not on that branch) is the prior value of that reference. Only works if you haven't done any other commits to your local branch. (But @{2}
, @{3}
etc will allow you to go further back.)
- Similarly,
remotes/origin/branchname@{1}
will be the prior value of the ref on the remote. Only works if someone else hasn't pushed to the remote. (Same point about @{n}
above.)
Checking you've got the correct ID
If you want to confirm that the ID you've grabbed from one of the above methods is correct, simply check it out:
git checkout abcdef0
and have a look around. If the git log
looks familiar (I also recommend tig for browsing your repository, and you can even run tig abcdef0
to look at the log from a given commit, which won't affect your reflog), then you can be confident that you're resetting to the right place.
Resetting to the previous state
Once you have the previous commit ID, you can reset to that and force push again:
git checkout branchname # if you're not on it already
git reset --hard abcdef0
git push -f
or just:
git push -f origin abcdef0:branchname
This will restore the state of the branch to the state before the forced push. (Quick note: that first snippet will update your local branch as well as the remote; the second one only updates the remote.)
What’s the impact?
Whenever you force push, it can cause an issue for other users who have branches checked out.
If you force push by accident and then force push back to what it was before, and nobody's had a chance to fetch or pull, you might be OK.
If others have pulled the branch since you force pushed, and you force push again to a previous commit, then they will run into problems when subsequently updating (they probably already ran into problems the first time, and this will make things worse).
If they've not made any commits to their local branch, they can either just delete and re-checkout (after a git fetch
to make sure they have up-to-date references), or do the following:
git fetch
git checkout branchname # if you're not on it already
git reset origin/branchname
If they have made local commits, then they will need to rebase those changes onto the correct history (and possibly resolve any conflicts):
git fetch
git checkout branchname # if you're not on it already
git rebase --onto origin/branchname 1234567
The above means "replay all commits after 1234567
(the incorrect head) on top of origin/branchname
(the correct head)."
Concept of divergence
To answer how git decides whether a remote and local branch have diverged, consider these two graphs of commits:
B
o---o---o
/
o---o---o---o
A
D
o---o---o
/
o---o---o---o---o---o E
C
In the top diagram, intuitively B
is ahead of A
, or more precisely B
contains A
.
In the bottom diagram, neither D
nor E
contains the other; they have both diverged, in this case from a common ancestor C
.
If you want to merge B
into A
, then a fast-forward merge will work, which pretty simply updates the ref of A
to that of B
:
B
o---o---o
/ A
o---o---o---o
If you want to merge D
into E
(or vice-versa) then a fast-forward merge is not allowed: you must create a merge commit:
D
o---o---o
/ \
o---o---o---o---o---o---o E
C
Alternatively, you could rebase your changes, which takes all the commits between C
and E
and replays them on D
:
D
o---o---o---o---o E'
/
o---o---o---o---o---o
C E
I've shown the original E
and the rebased E'
here to demonstrate that rebasing usually results in a divergence from the original state.
Notice how I've been talking generally about branches here, rather than specifically about remote/local versions of a given branch. The concepts are the same however; the only difference being that a push (which is a merge of a local to a remote) must be a fast-forward merge.
If the local and remote have diverged, you must first pull, either rebasing the local changes on top of the new remote, or by creating a merge commit. In both cases, the local is now ahead of the remote, which means a push becomes possible again.
Best Answer
You need to make sure that no other users of this repository are fetching the incorrect changes or trying to build on top of the commits that you want removed because you are about to rewind history.
Then you need to 'force' push the old reference.
or in your case
You may have
receive.denyNonFastForwards
set on the remote repository. If this is the case, then you will get an error which includes the phrase[remote rejected]
.In this scenario, you will have to delete and recreate the branch.
If this doesn't work - perhaps because you have
receive.denyDeletes
set, then you have to have direct access to the repository. In the remote repository, you then have to do something like the following plumbing command.