Is it possible to tell Github that my branch was merged into upstream master?
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:
- Keeping track of To-Do and issues in Git
- How do I fork an existing repo _of mine_?
- Cloning specific branch
- How to make every user delete a branch locally
- Can't get new files from git branch - 'Already up-to-date'
- Git won't add any files - just an empty directory
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
master is very close: it’s just
rebase goes back all the way to common base of
master as if they were never merged. As a result,
git rebase master becomes unnecessarily messy.
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
git would be able to use its knowledge of history to resolve more conflicts automatically.
Note that if I were to merge
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.
4 Solutions collect form web for “Is it possible to tell Github that my branch was merged into upstream master?”
To answer the questions as asked,
Is it possible to tell Github that my branch was merged into upstream master? […] Why did Github lose the information that feature was merged into master through an upstream PR?
Yes, it’s certainly possible to record the merge. That’s usual.
Someone chose to tell git (and github) not to record this one, so the effects appeared on the upstream master branch without a trace of where they came from.
What you’re looking at is the results of someone choosing to divorce the mainline history from the history you offered. Why they chose to do that, you’ll have to find out from them. It’s a common and widely-used option, for reasons any number of people will be very happy to opine about. Linearizing history looks nice but involves tradeoffs, there’s downsides either way and different people and different situations will tilt the balance in their own ways.
Regardless, after fetching the result you’ve now got an unrecorded merge, which is fine, clean and dandy so long as you never try to merge subsequent work still based on the unrecorded parent.
What to do about it?
The option you chose, which could also and more flexibly (it handles multiple-commit feature1..feature2 histories) be full-spelled as
git rebase --onto master feature1 feature2
is generally cleanest: upstream abandoned your feature1 history, so you abandon it, rebasing your feature2 on the content they have now.
If for some reason you really don’t want to abandon the feature1 history in your own repository — maybe you’ve got more than just feature2 based off the old feature1 tip and the rebasing would start to get tedious — you can also add a local record of the merge.
echo $(git rev-parse origin/master origin/master~ feature1) >>.git/info/grafts
This tells the local git that the current origin/master commit has as parents both its recorded first-parent commit and also the local feature1 commit. Since upstream has abandoned the feature1 commit and you haven’t, all of git’s machinery now works properly both here and upstream.
Different projects have different rules for what pull-request histories should be based on, some want everything based on some latest tip, others want everything based off a maintenance-base tag, others want bugfixes based on the commit that introduced the bug (I think this should be far more common). Some don’t care much because everybody’s so conversant with the code base that rebase-as-desired is still simplest.
But the important part here is that rebase-before-pushing is your last opportunity to be sure that what you’re pushing is exactly right. It’s an excellent habit to get into, and grafts work beautifully in that workflow.
Github now supports 3 techniques to merge pull requests:
- Merge: creates a merge commit (no fast forward) + fetches all the original commits from the PR branch
- Squash and merge: creates a single commit
- Rebase and merge: creates as many commits as the PR branch, but they are rebased onto master
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
Conceptually, there are two ways to achieve this.
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.
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.
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.
The underlying root cause of your woes is that when the pull request for
feature (the feature branch with one commit rolled back) completes, it results with a merge commit going into
master. Here is a diagram showing what
feature2 look like after the
feature pull request into
master has completed:
master: ... A -- B -- C -- M \ feature: D \ feature2: E
Here, we can see that
feature branched off from
master at commit
feature2 is simply a continuation of
feature with one extra commit. Merge commit
M sits on the top of
master and it represents all the extra work done in
feature. Note that this is a merge commit, and hence has nothing to do with the history of
Next, you ran the following rebase of
git checkout feature2 git rebase master
After this rebase,
feature2 will look like this:
feature2: ... A -- B -- C -- M -- D' -- E'
Note carefully that the merge commit remains a part of the history. Even though functionally speaking it might seem unnecessary because commit
D contains everything needed to make that merge commit, this commit still appears.
If you are wondering what you can do to avoid this, one option would be to have kept the history of
master linear. The flaw was the pull request which ended with the merge commit. If, instead you had played the commits from
feature directly on top of
master then you would not have had this problem. Consider the following commands:
git checkout feature git rebase master
Then, do a fast forward merge of
git checkout master git merge feature
This would have left
feature2 looking like this:
master: ... A -- B -- C -- D feature2: ... A -- B -- C -- D -- E
Now, if you were to merge
master, Git would simply play the
E commit, rather than going back to the original point whence
Yes normally. But git answers this question by looking at the history, not the changes. If you use Github’s squash (i.e. nuke the history), then you forfeit the ability to leverage this history; namely git’s ability to detect whether part of that history already exists in the upstream.
Building on the diagram created by Tim Biegeleisen:
master: ... A -- B -- C -- M \ / feature: D \ feature2: E
When you go to rebase feature2 onto master you should actually see this history:
master: ... A -- B -- C -- M \ / \ feature: D \ \ feature2: E'
Because rebase will never recreate a commit that already exists in the destination. It will know that D is already in master’s history.
When you rebased feature2 on master, you saw commits that logically were already present in master. This can only happen in the following scenario.
Prior to the merge, you rewrite some of the commits in feature:
D' feature / ... A -- B -- C \ D \ E feature2
perform the merge:
D' feature / \ ... A -- B -- C --- M master \ D \ E feature2
then tried rebasing feature2 on top of master:
D' feature / \ ... A -- B -- C --- M master \ D'' \ E' feature2