14

I have a pull request that Github says it can't automatically merge. This change is behind master a few commits, but without conflicts.

Manually merging I don't get any conflicts, and I get this output:

(on master)
$git merge otherbranch
[vim pops up for commit message, :wq]
Auto-merging <file>
Merge made by the 'recursive' strategy.
<file> | 1 +
1 file changed, 1 insertion(+)

Is that why Github can't merge automatically? It merged automatically from the command line anyway. Is this not automatic enough for Github?

jpimentel
  • 694
  • 1
  • 7
  • 23

3 Answers3

13

The reason is that git merge uses a strategy called "recursive" merging by default. It is able to perform 3-way merging: taking the patch from one side, and applying that patch to the other side, to produce a new file. It also does this recursively, in more complicated situations where a lot of branching and merging has been happening and there are multiple merge bases for the two commits being merged.

I recently encountered this same situation:

$ git merge-base --all 90d64557 05b3dd8f
711d1ad9772e42d64e5ecd592bee95fd63590b86
f303c59666877696feef62844cfbb7960d464fc1
$

With 2 merge bases, a 3-way merge is not possible. The 'recursive' strategy therefore resolved this by first recursing into a merge of those two commits:

$ git merge-base 711d1ad9 f303c596
3f5db59435ffa4a453e5e82348f478547440e7eb
$

OK, only one merge base, so the 3-way merging can begin. what's the changes on either side?

$ git diff --stat 3f5db594 711d1ad9
 normalize/coll.py      | 116 ++++++++++++++++++++++++++++++++++++++-----------
 normalize/visitor.py   |  49 ++++++++++-----------
 tests/test_property.py |  10 +++--
 3 files changed, 120 insertions(+), 55 deletions(-)
$ git diff --stat 3f5db594 f303c596
 normalize/identity.py  | 38 +++++++++++++++++++++++++++++++-------
 tests/test_property.py |  2 ++
 2 files changed, 33 insertions(+), 7 deletions(-)
$ 

These two diffs both made changes to the same file, so they can't be resolved using a simple strategy of just taking the newer version of each file from each side (an index merge). Instead, git takes the new file from one side, and tries to apply the patch from the other side to it. The result is a combined commit, which can be simulated like this:

$ git checkout -b tmp
Switched to a new branch 'tmp'
$ git reset --hard f303c59666877696feef62844cfbb7960d464fc1
HEAD is now at f303c59 record_id: throw a more helpful error if record identity is not hashable
$ git merge 711d1ad9772e42d64e5ecd592bee95fd63590b86
Auto-merging tests/test_property.py
Merge made by the 'recursive' strategy.
 normalize/coll.py      | 116 ++++++++++++++++++++++++++++++++++++++-----------
 normalize/visitor.py   |  49 ++++++++++-----------
 tests/test_property.py |  10 +++--
 3 files changed, 120 insertions(+), 55 deletions(-)
$ git diff --stat 3f5db594 HEAD tests/test_property.py
 tests/test_property.py | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)
$

It then returns to the original 3-way merge, using this merge result as its starting point; and this also involves changes to the same file:

$ git diff --stat HEAD 90d64557| grep selector
 normalize/selector.py          |  17 +--
 tests/test_selector.py         |  19 ++--
$ git diff --stat HEAD 05b3dd8f| grep selector
 normalize/selector.py  | 29 +++++++++++++++++------------
 tests/test_selector.py |  9 +++++++++
$

However again the changes are to different sections of the file, and so taking the diff and applying it to the other side is successful.

So, C git was able to resolve this merge by first doing a 3-way merge on the two merge bases of the two starting points, and then doing another 3-way merge on the two original commits being merged and the intermediate result of the first merge.

Github's automatic resolution doesn't do this. It's not necessarily incapable, and I'm not sure how much of the recursive strategy it does implement, but it is erring on the side of caution, which is exactly what you expect a big green button like that to do :-).

samv
  • 146
  • 1
  • 4
  • Nice addition on the recursive merge strategy. +1 Good complement to my own answer. – VonC Apr 01 '14 at 20:54
5

No, it isn't about merging. It is about rebasing.

You should try and rebase, in your local repo (cloned of your fork), otherbranch on top of master.

First, make sure master is the most recent one from the original upstream repo:

fork

cd /your/local/repo
git remote add upstream /url/original/repo
git fetch upstream

# Make sure you don't have any local commmit on your master
git branch -f master upstream/master # reset your master branch 
                                     # to the one from upstream repo
git checkout otherbranch
git rebase master

That rebase will generate conflicts, which you should resolve, git add, and then git rebase --continue.

Finally, simply push --force your branch to your fork: that will update your pull request automatically (nothing else to do).

git push -u -f otherbranch origin

(if it was already pushed once, a git push alone should be enough)

See more tips about pull-requests here.

Community
  • 1
  • 1
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • 1
    I'm not sure if rebase is relevant. Author tries to just merge. Smart local git can do it automatically but GitHub seems to limit its git to some primitive merge strategies and thus is not able to resolve conflicts automatically. – Andrey Semakin Nov 14 '19 at 09:37
  • @AndreySemakin As far as I know, there is no "limit" on merge done on GitHub side, when it tries to merge a PR branch to the original master branch. And a rebase (on top of upstream/master) here ensure that, once any possible conflicts is resolve, the next merge done by GitHub will be a trivial one. Therefore, I prefer this approach (I still do, 6 years later) – VonC Nov 14 '19 at 10:14
0

By default Git doesn't know if it can auto merge your branch or not before confirming the commit message for the merge commit.

If you know you won't have any conflicts, you can automate this process of recursive auto-merging by changing GIT_EDITOR to non-interactive tool, for example by adding cat or true:

GIT_EDITOR=true git merge otherbranch

The same for pull. You can also specify merging strategy like -X theirs or -X ours.

or another method is to rebase (adding -r to your pull command).

kenorb
  • 155,785
  • 88
  • 678
  • 743