1

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.

Community
  • 1
  • 1
mike
  • 1,670
  • 11
  • 21
  • I don't think a revert commit in this scenario even *makes sense*, since you've officially overwritten (and lost) a commit from the perspective of C. – Makoto Aug 18 '16 at 20:26
  • You don't want to do `git pull` here as it would merge with your local changes. As you only removed the last commit on top and locally you still have it, it's just one more up. So `git pull` is the reason you don't "see" that change. A `git fetch` and the `git reset --hard` better reflects that. Take care and backups as this is version control and not backup. – hakre Aug 18 '16 at 20:38
  • @Makoto by using git-revert I meant to completely replace `git reset HEAD^ --hard; git push -f` with it. – mike Aug 19 '16 at 06:50
  • @hakre I wanted to have a kind of universal developer-branch where developer could do all the modifications to commits like adding/removing/changing order/joining/... but also he could "backup" them to server an maybe share with other developers. Now I understand this is not the way to go. – mike Aug 19 '16 at 06:54
  • In case someone is interested the way to have developer-branches under gerrit is to use namespaces, see here: http://stackoverflow.com/questions/39120868/how-to-create-custom-namespace-under-gerrit – mike Aug 25 '16 at 07:28

1 Answers1

3

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.

axiac
  • 68,258
  • 9
  • 99
  • 134