There's a section in the git-rebase
manpage called "RECOVERING FROM UPSTREAM REBASE" that describes this quite well.
Fetching
For your specific case, let's walk through what happens. If you simply fetch, all that's doing is updating your remote tracking branch. That still works fine - it will give you a warning, though:
From <url>:
+ abc123...def456 master -> origin/master (forced update)
Pulling
A pull, whether normal (merging) or rebasing, calls fetch internally. So if you haven't already fetched, you will see the "forced update" warning. If you have fetched already, the forced update warning was given before, and there's no forced update to warn about now. You could also miss the forced update warning if there's a large diff report as part of the merge after it. After fetching, git-pull then calls merge or rebase as requested. This is where it gets really scary. As far as Git is concerned now, commits C
and D
are just local commits now. It doesn't matter that at one point they were published. So Git will merge/rebase just like it normally would.
The merge:
- A - B - L - M - N - O (origin/master)
\ \
C - D - X - Y - Z - Q (master)
The rebase:
- A - B - L - M - N - O (origin/master) - C' - D' - X' - Y' - Z' (master)
Now, either one could very well run into conflicts, if the removal of commits C and D makes the later commits different. Merge conflicts are the only other errors you'd notice. But if they were isolated changes, then everything goes "fine". If the user doesn't notice, then the might subsequently push this history, and thus reintroduce the removed commits! Sometimes this could be relatively benign. Maybe L is actually C with a typo fixed in the commit message, and MNO are identical to DEF, just with different parents. So reintroducing C and D would do nothing more than clutter the history. But maybe it's horribly dangerous: maybe removing C fixed some terrible bug, and it's just been reintroduced.
Avoiding the problem
And this is why it's a really bad idea to rewrite published history. The best case is ending up with some duplicated commits, and the worst case is a user unknowingly breaking everything. So if it does happen, the very first thing you have to do is tell everyone and direct them how to recover. The next thing is to vigilantly monitor everything subsequently published to make sure it doesn't reintroduce the old commits. If you're on the other side of this, fetching from a public repo, hopefully you trust the maintainers enough to never rebase the stable branch, or to tell you if they do. If you don't trust them, then it's good practice to fetch then merge/rebase, rather than pulling, so that you can notice the "forced update" message and proceed very carefully.
Recovering locally
The details of how to recover vary; I recommend the aforementioned manpage. It's hard to tell exactly what you need to do - it depends on whether C was rewritten into L (and maybe D into M) or they're entirely new commits, and on whether you want to rebase or merge. If you just want to rebase, rebasing XYZ onto O would be sufficient. If you want to merge, you'd rebase XYZ onto either B or M, then merge O.