I used my local branch feature
to create a PR for a github repo (I don't have write access to it). Later I decided I want to separate its last commit into a standalone PR, so I moved feature
one commit back:
git checkout feature
git branch feature2
git reset --hard @~
git push -f
The first PR is merged upstream, so now I want to create the second PR:
git checkout master
git pull upstream master
git push origin
git checkout feature2
git rebase master
Unfortunately, it turns out that git
lacks the information that feature
was merged into master
. Therfore, it doesn't realize that the nearest common base of feature2
and master
is very close: it's just feature
. Instead, rebase
goes back all the way to common base of feature
and master
as if they were never merged. As a result, git rebase master
becomes unnecessarily messy.
Why did Github
lose the information that feature
was merged into master
through an upstream PR? Is there any way to provide Github
with that information?
In the end, I had to resort to:
git checkout master
git checkout -b feature2_new
git cherry-pick feature2
Luckily I only needed to take care of a single commit. And even with a single commit, I think that a merge with the true base (if git knew about it) would be better than the cherry-pick
because git
would be able to use its knowledge of history to resolve more conflicts automatically.
Note that if I were to merge feature
into master
locally instead of doing a github PR, no information would have been lost. Of course, then my master
would not be in sync with the upstream repo, so it would be pointless.
Github now supports 3 techniques to merge pull requests:
Only the regular merge preserves the knowledge that my local commits were part of the PR merged into master. If it was used, I wouldn't have encountered the problem I described in the question.
The other two techniques lose that knowledge - and there's nothing I can do to create it retroactively (without modifying the upstream master). That's the price to pay for a simpler history.
Intuitively, in order for git
to know that an upstream master commit U
is related to my local commit L
, there needs to be an arrow pointing from U
to L
.
Conceptually, there are two ways to achieve this.
First, U
can have two parents: one connecting it to L
, the other connecting it to all the previous commits on the upstream master
. This is precisely what Github merge technique does.
Second, U
can have L
as its sole parent, if L
already points to all the previous commits on the upstream master
. Github could have supported this by allowing fast-forward with its merge technique, but it chose not to.
If a Github PR is merged with either squash and merge or rebase and merge, all commits created on the upstream master have only one parent; there are no arrows between them and my local commits.
Edit:
Also I now believe that the loss of history I was asking about was no big deal in the first place. IIUC, the conflicts I would encounter with git cherry-pick
are actually the same as the ones with git rebase
if master
was connected to feature2
through a regular merge commit. And if I had more than 1 commit split into a standalone PR, cherry-pick would handle that easily too.