By looking into my team's commit log, I found lines of code changes from same patch appear twice in different commits.
Further prove is like this: Master branch has commit A in its change list(as being seen in "git log"). If I check out commit A as HEAD, I can find commit B in its change list. However if I checkout master as HEAD, I can not find commit B in its change list.
I believe there was a non-fast-forward push happened at some point. But is there any easy way to identify such non-fast-forward push by using git command or other tools?
If you're using Github (and possibly others), a forced push on a pull request will be visible in the PR history, though I'm not sure how long they record it.
Using the Github v4 API forced pushes should be available on a PullRequest's timeline as a BaseRefForcePushedEvent and/or HeadRefForcePushedEvents.
You may be able to detect a forced push if you have access to the bare remote repository, but it's a complicated. I'll illustrate below.
Normally pushes are building on top of existing commits. If you had a local repo a few commits ahead of your remote like so...
And you
git pushD and E will be send up and the local and remote will have the same history formaster.But if
localdoes some rebasing, let's say they rewrite B, history has "diverged".Now if you try to push it will be rejected,
git pushwill not merge for you it will only fast-forward. This is typically solved with agit push --forcewhich simply replaces the existing history.Note that the old commits are still there, both in your local and origin. And they will remain there until garbage collection happens, normally for weeks.
On local you can see old commits with
git-reflog. The remote repository may have a reflog, and if it is you're in luck. But it may not. Yet the old commits are still there.You can see them in the
objectsdirectory. Commit 53715b45364b160ce6e36d934be903459aebe254 is in the fileobjects/53/715b45364b160ce6e36d934be903459aebe254... though it may be packed for efficiency.So the commit is likely still there, but how can you find evidence of rebasing? One trick is to run garbage collection and see what's left.
git gcwill only pack referenced commits, and it will save unreferenced commits younger than two weeks old (this can be configured with the various "prune" config options). Any commits left inobjectsare likely to be old commits.The old commits are
53715b45364b160ce6e36d934be903459aebe254,b4036885119a0c0a726d4a07c39880f5d4e5d688, and025f398bbc061f1fa0b61f8454f5d134f43a8a4c. After runninggit gc...The unreferenced commits were left unpacked.
I don't know how foolproof this technique is, but it makes it possible to find evidence of a recent force push.
The best option to solve this problem is to not have it in the first place.
If you disallow direct commits to
master, requiring all commits to be done via PR, this ensures you have a record of everything that goes intomaster, that all additions tomasterare done with clean merges, and that nobody (except admins) can change master's history. In concert with automated checks and tests this ensuresmasteris always in good shape and the team has a solid base to work from.In addition, forced push after rebase is normal, but a normal
git push --forceremains dangerous. Instead usegit push --force-with-leaseto push after rebase. This will check that only the commits which were changed by rebasing are force pushed.git push --force-with-leasemakes rebasing safer. I have it aliased togit repush.