Is there a "theirs" version of "git merge -s ours"?

Viewed 612081

When merging topic branch "B" into "A" using git merge, I get some conflicts. I know all the conflicts can be solved using the version in "B".

I am aware of git merge -s ours. But what I want is something like git merge -s theirs.

Why doesn't it exist? How can I achieve the same result after the conflicting merge with existing git commands? (git checkout every unmerged file from B)

The "solution" of just discarding anything from branch A (the merge commit point to B version of the tree) is not what I am looking for.

18 Answers

A possible and tested solution for merging branchB into our checked-out branchA:

# in case branchA is not our current branch
git checkout branchA

# make merge commit but without conflicts!!
# the contents of 'ours' will be discarded later
git merge -s ours branchB    

# make temporary branch to merged commit
git branch branchTEMP         

# get contents of working tree and index to the one of branchB
git reset --hard branchB

# reset to our merged commit but 
# keep contents of working tree and index
git reset --soft branchTEMP

# change the contents of the merged commit
# with the contents of branchB
git commit --amend

# get rid off our temporary branch
git branch -D branchTEMP

# verify that the merge commit contains only contents of branchB
git diff HEAD branchB

To automate it you can wrap it into a script using branchA and branchB as arguments.

This solution preserves the first and second parent of the merge commit, just as you would expect of git merge -s theirs branchB.

Older versions of git allowed you to use the "theirs" merge strategy:

git pull --strategy=theirs remote_branch

But this has since been removed, as explained in this message by Junio Hamano (the Git maintainer). As noted in the link, instead you would do this:

git fetch origin
git reset --hard origin

Beware, though, that this is different than an actual merge. Your solution is probably the option you're really looking for.

I solved my problem using

git checkout -m old
git checkout -b new B
git merge -s ours old

If you are on branch A do:

git merge -s recursive -X theirs B

Tested on git version 1.7.8

Why doesn't it exist?

While I mention in "git command for making one branch like another" how to simulate git merge -s theirs, note that Git 2.15 (Q4 2017) is now clearer:

The documentation for '-X<option>' for merges was misleadingly written to suggest that "-s theirs" exists, which is not the case.

See commit c25d98b (25 Sep 2017) by Junio C Hamano (gitster).
(Merged by Junio C Hamano -- gitster -- in commit 4da3e23, 28 Sep 2017)

merge-strategies: avoid implying that "-s theirs" exists

The description of -Xours merge option has a parenthetical note that tells the readers that it is very different from -s ours, which is correct, but the description of -Xtheirs that follows it carelessly says "this is the opposite of ours", giving a false impression that the readers also need to be warned that it is very different from -s theirs, which in reality does not even exist.

-Xtheirs is a strategy option applied to recursive strategy. This means that recursive strategy will still merge anything it can, and will only fall back to "theirs" logic in case of conflicts.

That debate for the pertinence or not of a theirs merge strategy was brought back recently in this Sept. 2017 thread.
It acknowledges older (2008) threads

In short, the previous discussion can be summarized to "we don't want '-s theirs' as it encourages the wrong workflow".

It mentions the alias:

mtheirs = !sh -c 'git merge -s ours --no-commit $1 && git read-tree -m -u $1' -

Yaroslav Halchenko tries to advocate once more for that strategy, but Junio C. Hamano adds:

The reason why ours and theirs are not symmetric is because you are you and not them---the control and ownership of our history and their history is not symmetric.

Once you decide that their history is the mainline, you'd rather want to treat your line of development as a side branch and make a merge in that direction, i.e. the first parent of the resulting merge is a commit on their history and the second parent is the last bad one of your history. So you would end up using "checkout their-history && merge -s ours your-history" to keep the first-parenthood sensible.

And at that point, use of "-s ours" is no longer a workaround for lack of "-s theirs".
It is a proper part of the desired semantics, i.e. from the point of view of the surviving canonical history line, you want to preserve what it did, nullifying what the other line of history did.

Junio adds, as commented by Mike Beaton:

git merge -s ours <their-ref> effectively says 'mark commits made up to <their-ref> on their branch as commits to be permanently ignored';
and this matters because, if you subsequently merge from later states of their branch, their later changes will be brought in without the ignored changes ever being brought in.

The equivalent(which keep parent order) to 'git merge -s theirs branchB'

Before merge:enter image description here

!!! Make sure you are in clean state !!!

Do the merge:

git commit-tree -m "take theirs" -p HEAD -p branchB 'branchB^{tree}'
git reset --hard 36daf519952 # is the output of the prev command

What we did ? We created a new commit which two parents ours and theirs and the contnet of the commit is branchB - theirs

After merge:enter image description here

More precisely:

git commit-tree -m "take theirs" -p HEAD -p 'SOURCE^{commit}' 'SOURCE^{tree}'

This answer was given by Paul Pladijs. I just took his commands and made a git alias for convenience.

Edit your .gitconfig and add the following:

[alias]
    mergetheirs = "!git merge -s ours \"$1\" && git branch temp_THEIRS && git reset --hard \"$1\" && git reset --soft temp_THEIRS && git commit --amend && git branch -D temp_THEIRS"

Then you can "git merge -s theirs A" by running:

git checkout B (optional, just making sure we're on branch B)
git mergetheirs A

A simple and intuitive (in my opinion) two-step way of doing it is

git checkout branchB .
git commit -m "Picked up the content from branchB"

followed by

git merge -s ours branchB

(which marks the two branches as merged)

The only disadvantage is that it does not remove files which have been deleted in branchB from your current branch. A simple diff between the two branches afterwards will show if there are any such files.

This approach also makes it clear from the revision log afterwards what was done - and what was intended.

Related