difference between git merge ours theirs commands

2019-06-11 08:22发布

问题:

I am practising GIT. Whats the difference between below commands?

git merge -s ours branch

git merge -s theirs branch

git merge -X ours branch

git merge -X theirs branch

回答1:

Before I start this answer, let me emphasize that there are two different words that are both spelled "merge". There is a verb form, to merge, which means "to combine changes". There is a noun or adjective form, a merge or a merge commit, which refers to a specific kind of commit.

The git merge command usually does both: it merges (verb) some changes, and then makes a merge (noun) or a merge commit (adjective). Git has many ways to do the verb form of merge, though. It also allows the git merge command to omit the final noun form merge (but we'll ignore this case entirely here).

Strategy or extended option—the short version

The short version is that you almost never want the strategy option -s ours.

Using the -X (eXtended) options, you can tell Git to favor the "ours" or "theirs" side of a merge in the case of a conflict. That is, suppose the merge base version of file wallaby.txt says, in part:

The Bennett's wallaby is
a variety of the red-necked wallaby.

One of the two branch tip versions—let's call this the left side or local or --ours version—says:

The Bennett's wallaby is
a smaller variety of the red-necked wallaby.

The other branch tip version, i.e., the right side or remote or other or --theirs version—says:

The Bennett's wallaby,
which is found on Tasmania,
is a variety of the red-necked wallaby.

When we compare the base version to the two branch tips, the comparisons make similar, but not exactly the same, changes to the same range of lines:

$ git diff $base $left
...
 The Bennett's wallaby is
-a variety of the red-necked wallaby.
+a smaller variety of the red-necked wallaby.
...

But:

$ git diff $base $right
...
-The Bennett's wallaby is
-a variety of the red-necked wallaby.
+The Bennett's wallaby,
+which is found on Tasmania,
+is a variety of the red-necked wallaby.

These two changes clash, and you will get a merge conflict.

The eXtended -X ours option means "prefer our change": where the two changes clash, ignore their change entirely, and take ours instead. The eXtended -X theirs option means "prefer their change", i.e., ignore ours.

Note that whichever change you prefer, you will lose something here. One change mentions that the Bennett's wallaby is a bit smaller, and other other mentions that it's the Tasmanian form. If you pick just one, you lose the other. You probably should combine this change manually, rather than letting Git choose one side.

When and why you might use -s ours

If this is the only change to the file, using -s ours instead of -X ours would not make a difference to this particular file-level merge: either way we would take "our" (left-side) change, and ignore theirs. But suppose further that only their version fixes a typo elsewhere in the file:

 $ git diff $base $right
 ...
 -walaby
 +wallaby
 ...

with nothing like that in the $left (local or --ours) output. If we use -X ours instead of -s ours, we will at least pick up this fix. Of course, the right side might have a change that adds typos instead of removing them. Git won't know or care: its job is to combine the changes, not to decide whether they make any sense, whether individually or combined!

Moreover, as Tim Beigeleisen mentioned in a comment above, the -s ours option does not even look at the right-hand side diff. In fact, it does not run any diffs at all! It tells Git to make the merge commit without using the other side in any way. In other words, it merely records that there was a merge, without actually doing any merging.

The only reason to do this is to make a commit-graph history record of the merge. It is this history, as recorded in the commit graph itself, that determines how you and Git will "see" what happened, when you—or anyone else—come back later to look. The presence of a merge commit means that you and Git will see the merge as having already taken all the good parts of whatever got merged—even if the method of "taking" was ignore everything. Having already taken all those parts, you will never take them again: so -s ours allows you to "kill off" a branch, not by merely ignoring it, but by recording for all history that you deliberately killed it.

Git records only the result, not the method

Git does not record the merge strategy, nor any strategy options. So the only way you can tell whether someone used such options is to guess. If you come across some repository with some merge commits inside it, you can repeat the merges yourself, and see if you get the same result. If you do get the same result, perhaps the person who did the merge used the exact same command you just used. If you get a different result, perhaps the person who did the merge used a different command, or a different set of arguments. Or maybe they resolved any conflicts manually!

More background

For more background, let me quote from my perennially-not-making-much-progress (because I have a job :-) ) book.


A high level view of merging

The goal of a merge is easy to understand. Several people or groups, or even just one person with two or more tasks, started from a common code base, and made a series of changes. For instance, in the Marsupial Maker, Alice may be working on wombats while Bob works on kangaroos. Each person or group (or even just one person taking on multiple roles) works in her or his private repository and/or private branch. These two lines of development—i.e., branches in the philosophical sense we noted in Chapter 1—are related by this common starting point. We won’t worry yet how they manage to share their commits, but at some point, someone—perhaps Alice or Bob, or perhaps a third person—will combine the changes. The combination should take all the good parts of both changes. The simplest method of combining is to perform the three-way merge from the same chapter. Now that we understand commit graphs, and have a general idea about comparing newer commits against older ones, it is time to take a brief high level look at how both Git and Mercurial perform merges.

Two commits and a merge base

In Chapter 2, we noted briefly (see page 38 and page 45) that the LCA of any two commits is their merge base. In some cases, there can be more than one merge base, but this is rare and we won’t address it yet. Instead, let’s just note that the—presumably single—merge base is, by definition, not just a common ancestor of two other commits. It is, in fact, the correct common starting point: it is the first commit that is reachable from both of the two heads (Mercurial) or branch tips (Git) that we are merging. The VCS needs to find the merge base to find out both what we did and what they did.

In both Git and Mercurial, we choose one of our two commits by the normal checkout process. Whatever commit we have checked out now—our current commit—participates in the merge. We choose a second commit using some appropriate commit-identifier, typically a branch name but occasionally a hash ID, or in Mercurial, a simple revision number (or sometimes even nothing at all, and the VCS figures it out for us). That will be the “other” or “theirs” commit.18 We may then simply run git merge otherbranch or hg merge otherbranch.


18I call the three commits base, current, and other here. Git has no single, consistent name for the current and other commits. Mercurial consistently calls them the local and other commits. I also refer to the two non-base commits as the sides of the merge. In several places, Git does call the current commit ours and the other commit theirs. There is, however, a problem with the ours/theirs nomenclature that we will see later, when we cover cherry-picking and rebasing.