1

I pushed something to master branch and then run a git pull --rebase, I got a forced update, and then I found my last commit is missing, that's my command line history:

➜  ljmall git:(master) git commmit -m "refactor: enable self checkout on every environments"
[master 694c4c0f9] refactor: enable self checkout on every environments
 3 files changed, 2 insertions(+), 17 deletions(-)

➜  ljmall git:(master) gp ----> (git push)
Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (9/9), done.
Writing objects: 100% (9/9), 1.36 KiB | 1.36 MiB/s, done.
Total 9 (delta 8), reused 0 (delta 0)
To git.dmright.com:/opt/git/ljmall.git
   fd72e86d7..694c4c0f9  master -> master

➜  ljmall git:(master) gst ----> (git status)
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean


➜  ljmall git:(master) veil pull ----> (git pull --rebase)
From git.dmright.com:/opt/git/ljmall
 + 694c4c0f9...fd72e86d7 master     -> origin/master  (forced update)
First, rewinding head to replay your work on top of it...

I have no idea why the git server accepted my commit but after git pull --rebase, it is missing.

I'm not sure I can reproduce this issue, because this is my first time, before that I committed 100+ times without any issue.

my Git version is 2.16.2, Git server version is 2.14.2, I built the git server: git clone --mirror git@github.com:/xxx/xxx.git

Also I use PyCharm IDE, I don't know it could be related with this issue.

Fetch URL is same as push URL.

I have tried revert my missing files modification via a PyCharm feature, so I commit and push again, without any issue, that's my command line history:

➜  ljmall git:(master) ✗ git add .
➜  ljmall git:(master) ✗ git commit -nm "refactor: enable self checkout on every environments"
[master bc0dbb78e] refactor: enable self checkout on every environments
 3 files changed, 2 insertions(+), 17 deletions(-)
➜  ljmall git:(master) gp
Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (9/9), done.
Writing objects: 100% (9/9), 1.36 KiB | 1.36 MiB/s, done.
Total 9 (delta 8), reused 0 (delta 0)
To git.dmright.com:/opt/git/ljmall.git
   fd72e86d7..bc0dbb78e  master -> master
➜  ljmall git:(master) git pull --rebase
Already up to date.
Current branch master is up to date.
dawncold
  • 183
  • 1
  • 1
  • 12

2 Answers2

1

That should happen only if someone else has forced pushed (git push --force) to git.dmright.com:/opt/git/ljmall.git between:

  • your first push
  • your first pull --rebase

You can read about that scenario in "git pull --rebase lost commits after coworker's git push --force"

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • I hope there was someone run `git push --force`, if so, just tell he/she you should not do that. Only I commit to git server since yesterday afternoon, almost only my commits in last 24hrs, and the time gap between my missing commit and the commit before it is very brief, I can't imagine someone could do this: 1. stole my SSH private or modify server auth log, 2. pull my commit. 3. force push to server. – dawncold Mar 08 '18 at 07:35
  • @dawncold do you have two repo on your remote server (one in .git, one without .git) as suggested by torek? – VonC Mar 08 '18 at 07:36
  • There is only one repo on git server, but I found something odd: we have a github repo and create another git server via: git clone --mirror GITHUB_REPO_URL, so we push to our git server and there is a periodical job (every 5 minutes) run `git push --mirror` to github. I found there is a cron job executed (before or at same time) I push my missing commit to git server. It could result a rollback? So I pull a rollbacked repo and then forced update? I think that's a little reasonable. – dawncold Mar 08 '18 at 07:51
  • @dawncold As long as the push --mirror (https://git-scm.com/docs/git-push#git-push---mirror) is done from your remote to GitHub, that should not matter: no "rollback" would be pushed. Is there also a cron with a pull? – VonC Mar 08 '18 at 07:55
  • I enable a post-update hook with only `exec git update-server-info`, no another git pull cron job on git server. I have collected some git push --mirror output before, there is some something wrong like: [remote rejected] env-ljmall-public--4 -> env-ljmall-public--4 (cannot lock ref 'refs/heads/env-ljmall-public--4': is at c8160801358f1d9019d5f5bf1c737228fba86c43 but expected 1f00bef7a80c3bf939f06d1b602a57ccc8d58d6d) I think that's related with this issue. – dawncold Mar 08 '18 at 08:11
  • @dawncold I would recommend avoiding any cron job. Your post-update hook could push to GitHub if you want that GitHub repo to reflect what you are doing. – VonC Mar 08 '18 at 08:13
  • Thanks your suggestion, we push to github synchronously before, but it's a little slow sometimes, so we changed `git push --mirror` to a asynchronous by adding a `&` to command, but we found sometimes we would get a rollbacked repo, so I change to cron job, but it is not a final solution. Do you have any ideas about why cron job will result a rollback repo? – dawncold Mar 08 '18 at 08:21
  • @dawncold it should not. Is there concurrent pushes directly to GitHub? – VonC Mar 08 '18 at 08:22
  • No concurrent push directly to GitHub, we only push to our git server, because our team is small so we could control this. – dawncold Mar 08 '18 at 08:34
  • @dawncold then, except for a push --force, I don't see where a rollback could come from (unless there was a pull from GitHub done directly on the server) – VonC Mar 08 '18 at 08:40
1

TL;DR

It's probably a case of someone else (e.g., a co-worker) misusing git push --force, as VonC suggests. This combines with the way git pull interacts (badly) with the --fork-point code is an attempt to recover from an upstream rewind or history rewrite. Using Git's reflogs, you could have recovered your lost commits even without the help of a PyCharm IDE feature, but if you avoid git pull you can avoid having to do even that (if you're particularly eagle-eyed and/or careful, at least).

Long

I have no idea why the git server accepted my commit but after git pull --rebase, it is missing.

That is rather curious. I will note the following in your pasted output though:

➜  ljmall git:(master) gp ----> (git push)
[snip]
To git.dmright.com:/opt/git/ljmall.git
   fd72e86d7..694c4c0f9  master -> master

This tells us that your Git called up git.dmright.com and had that server look and work in /opt/git/ljmall.git. Note that this name ends with .git. The final line tells us that their Git—the one on git.dmright.com—ended by agreeing to your Git's request to set their master to 694c4c0f9; it had been fd72e86d7 earlier, and moving from fd72e86d7 to 694c4c0f9 was a fast-forward operation.

Shortly afterward, however:

➜  ljmall git:(master) veil pull ----> (git pull --rebase)
[snip]
From git.dmright.com:/opt/git/ljmall
 + 694c4c0f9...fd72e86d7 master     -> origin/master  (forced update)

Your Git is once again speaking with whatever machine answers the Internet-phone-number git.dmright.com. That machine is looking in /opt/git/ljmall. Note that this name does not end with .git. That may be entirely normal and harmless, but it might not be. We'll come back to this in a moment.

Meanwhile, the final line—the:

+ 694c4c0f9...fd72e86d7 master -> origin/master (forced update)

part—tells us that your Git believed their master, which your Git remembers as origin/master, was pointing to 694c4c0f9 at this point. That makes sense, because your system believed their system when they told you they were setting their master to 694c4c0f9, as the last step of your git push. But they told you: No, my master points to fd72e86d7. In other words, their master appears to have thrown away the commit(s) you sent them.

This is also what the "forced update" message is about. Moving your own origin/master—your memory of their master—from 694c4c0f9 to fd72e86d7 requires that you, too, throw away the commit (from your origin/master only—you keep it in your own master!—your own branches are safe from this, at least at the git fetch level). Doing this "discard from origin/master" requires using the force option; but the force option is implied when updating your remote-tracking names, because your Git intends to remember what their Git has, even if that requires throwing away commits.

There is, however, an immediate subsequent problem, because git pull --rebase runs git rebase with the fork-point code enabled. This can have the side effect of throwing your own commit off your own branch! (See my answer to the StackOverflow question to which VonC's answer links.) Had you avoided git pull, you might have noticed this forced update. You could then be careful not to run git rebase right away: you could look around to see what happened, and whether you should take extra steps (such as not using git rebase yet) to recover.

This is kind of a small thing, but git pull tends to be full of these small sharp edges. It's much better today than it was back in Git 1.5 and 1.6, but I still recommend avoiding git pull, at least until you're very familiar with Git.

The oddity of .git suffixes

Git servers, when asked to look for (e.g.) /opt/git/ljmall, will first check to see if that exists. If not, they will go on to add the .git suffix themselves, looking for /opt/git/ljmall.git. If the second path exists, they will use that, even though you told them to use the name without the .git suffix.

But: what happens if both /opt/git/ljmall and /opt/git/ljmall.git exists? If you sometimes tell them: use the .git one, and sometimes tell them: use the unsuffixed one, you will wind up using the two independent repositories with the unfortunately-similar names. This will lead to nothing but confusion. It's wise to make sure that the server does not have both, but it's also wise, as the client of the server, to avoid using both suffixed and unsuffixed names. Make sure your fetch and push URLs match—or if they differ on purpose, make sure they're much more different than a mere .git suffix.

torek
  • 448,244
  • 59
  • 642
  • 775
  • There is something wrong with it, but I don't have enough information to explain why it is. Someone run `git push --force` might be a reason, if there was a co-worker do that I'm glad to hear that. Only a few people could push to git server and all of them push commits via SSH with RSA, so I could check my server auth log, but there is only my SSH key founded in log file, actually there is no people commit since yesterday afternoon. – dawncold Mar 08 '18 at 07:27