Reputation: 1720
I have a bare git repository B, from which I have cloned two repositories C and D. Then I have added some changed to C which were later pushed to B, and finally pulled by D. So everything is synchronized.
Now I want to remove last pushed commit from C so I do following:
$ git reset HEAD^ --hard
$ git push -f
(from http://christoph.ruegg.name/blog/git-howto-revert-a-commit-already-pushed-to-a-remote-reposit.html)
and on D I do:
$ git pull
and I get output as follows:
[mkm@horklum git1]$ git pull
From /home/mkm/projects/git_tests/git1
+ a5d681f...c481973 master -> origin/master (forced update)
Already up-to-date.
and git log
gives me the exact same output as before. I would like history on D to be the same as on C. I know from What and where does one potentially lose stuff when git says "forced update"? that last git pull
merged my history with the changed one on the server, but is it really what should happen? I would like history to be still synchronized everywhere.
I know I can do git revert commit and this is the recomended aproach in such scenerio, but I would like to understand why in case of forced push, history will not remain synchronized everywhere.
Upvotes: 1
Views: 421
Reputation: 72386
Let's try to draw the history of your repository (higher is older):
commit 1 --> o <-- the initial commit
|
...
|
commit N-1 --> o <-- B and C are here now
|
commit N --> o <-- D is here
You created some commits (up to commit N
) on C
, pushed to B
, pulled from D
. All three repos were in sync with their master
branches pointing to commit N
. (If your branch is not named master
then just put its name instead and read on).
Then you forced the master
branch of C
to go back to commit N-1
and also checked it out (this is what git reset --hard
does).
Also, you forced the master
branch of B
to go back to commit N-1
. This is what git push -f
does in this context.
Now, the commit N
doesn't exist any more in the B
and C
repositories.1 It is like you created the commits 1
..N-1
on C
, pushed them to B
, pulled them from D
(having B
, C
and D
in sync) and then you created commit N
on D
. It looks like commit N
was never created on B
and C
.
Then you run git pull
on D
and nothing changes. This is because in the background git pull
runs git fetch
followed by git merge
.
git fetch
retrieves from the remote repository (B
) all the reacheable commits that are not already present in the local repo. There are none in this situation; B
has a subset of the commits present on D
. It also learns about the current position of the branches on B
.
git merge
finds out that the local repo (D
) is one commit ahead of the remote repo (B
) and it has nothing to do.
In order to make D
look like B
you can run git fetch
then git reset --hard B/master
on D
. It will forcibly move the master
branch of D
where the master
branch of B
is then will check it out. It basically does what git reset --hard HEAD~1
did on B
.
1 This is not entirely true. The commit still exists but it is not accessible using branches. This makes it an orphan commit that will be removed on the next garbage collection. While it still exists in the repository it can be accessed using its hash and it can be recovered by creating a branch that points to it.
Upvotes: 3