2

I know there is a long way to checkout both modified files by following commands.

git add <filename>
git reset HEAD <filename>
git checkout <filename>

But is there one single command which can checkout all the "unmerged paths"?

Ankit Agarwal
  • 123
  • 1
  • 2
  • 8

1 Answers1

5

The sequence of commands you listed will not produce the --theirs version. Instead, it produces, in both the work-tree and the index, the --ours version. There are several ways to achieve this with fewer commands, and for multiple path names.

The second shorted method is perhaps the one you should use:

git checkout --ours path1 path2 ... pathN

or:

git checkout --theirs path1 path2 ... pathN

followed by git add of those same paths. Be very sure that it is OK to throw out their or your changes with respect to the merge base! Pay particular attention to which commit is the merge base, so that you know what you are keeping and what you are throwing out.

To make this one step shorter, you can use git checkout HEAD or git checkout MERGE_HEAD and skip the git add step; but see below.


The key thing to understand here is that whenever Git detects conflicts, Git stores all three versions of the file in the index. In this situation, you can access any or all of these three versions. This is, for instance, how git mergetool obtains the versions it calls BASE, LOCAL, and REMOTE, which Git more generally calls the merge base, the HEAD or --ours version, and the other or --theirs version.

Remember that the index is the place where Git keeps a copy of each file that will go into the next commit you make. (Git calls this the index, or the staging area, or sometimes the cache, depending on which part of Git documentation is doing the calling.) In normal operation, there is only one copy of any such file. That is, if you have a file named README.txt that was in the commit you checked out and is in the work-tree that you can work on, you also have a copy of this same README.txt in the index.

The copy in the work-tree is an ordinary file, in the same format as any normal file on your computer. The committed copy, stored in the HEAD commit, is in a special, Git-only format, where it is not only compressed but also entirely read-only: the committed copy can never be changed. You can, of course, make a new and different commit that has a different version of README.txt, but the way you do that is by first copying that new and different README.txt into the index.

Note that at any time, you can view the copy in the HEAD commit with git show HEAD:README.txt. Similarly, you can view the copy in the index with git show :README.txt, where the leading colon is special Git syntax for a file from the index.

Typically you copy a new version into the index via git add README.txt, which simply takes whatever is in the work-tree right now and copies it into the index / staging-area, overwriting the (single) previous copy. But when you hit a merge conflict during git merge or git cherry-pick or git revert or git stash apply or any other operation that performs the to merge action—what I like to call merge as a verb—the index takes on a new role.

While the index normally holds just one copy of README.txt, during a conflicted merge, the index holds (up to) three copies of README.txt. Remember that a merge works by comparing your version of the file—your README.txt—to some merge base version of that same file, and then also by comparing their version of README.txt to that same merge base version. By making these two separate comparisons, Git can figure out what you changed, and what they changed. Git then combines these two sets of changes, applying both sets to the merge base version of the file. See also VonC's answer here.

If Git is not able to combine both sets of changes on its own, what Git does at this point is to use what it calls stage slots in the index. Rather than just the one README.txt in the index, Git can store up to four versions. However, it uses at most three, and it numbers them:

  • Staging slot zero holds the unconflicted :README.txt.
  • Staging slot #1 holds the merge base 1:README.txt.
  • Staging slot #2 holds the ours version 2:README.txt.
  • Staging slot #3 holds the theirs version :3:README.txt.

When git status, with or without --short, shows you unmerged files, what it means is that there are entries in staging slots 1, 2, and 3. In this case slot zero is not used!

Git writes the conflicted copy of README.txt to the work-tree, so that you can look at the conflicts and resolve it yourself, but it also stores all three inputs in the index. You can check any one of them out, but slots 2 and 3 have a syntax to make it easy:

git checkout --ours README.txt

extracts from slot 2, while:

git checkout --theirs README.txt

extracts from slot 3. All three versions remain in the index, but now the work-tree holds either ours (the HEAD version) or theirs (the MERGE_HEAD version).

Running git add takes whatever is in the work-tree right now, even if it still has merge conflicts in it, and writes that to slot zero, wiping out slots 1 through 3. This causes Git to believe that the conflict is resolved.

You can of course check out, or add, more than one file name at a time, so you can use:

git status --porcelain | grep '^UU ' | cut -f2-

to generate the list of all unmerged files (their short status is UU), and use that to make a list of files to extract. This method is not very robust (fails in the presence of files with funny characters or spaces in their names) but suffices for simple cases:

files=$(git status --porcelain | grep '^UU ' | cut -f2-)
git checkout --theirs $files
git add $files

Removing one step

To shorten all of this one step, we can make use of a peculiar fact about git checkout. Using git checkout --ours path or git checkout --theirs path has Git extract a file from the index (in its Git-only format there) to the work-tree (in normal format), which does not change anything in the index itself. But using git checkout tree-ish path has Git extract a file from a commit, not from the index. When Git does this, it first copies that file into the index. Only after the file is in the index does git checkout copy the file to the work-tree.

Copying a file into the index has the side effect of deleting any stage 1, 2, and 3 slot copies of the file, writing the file to staging slot zero. So if the file was previously unmerged, it has suddenly become merged.

Note that if we are in a conflicted merge, the hash ID of the commit we are merging is stored in MERGE_HEAD. If we are in the middle of a cherry-pick, the hash ID of the commit we are picking is in CHERRY_PICK_HEAD. (See the other commands for where they store their hash IDs.)

Hence, even though the copy that is in staging slot 2 came from the HEAD commit, and the copy in staging slot 3 came from MERGE_HEAD, if we deliberately extract a file from HEAD or MERGE_HEAD into the index and then into the work-tree, this writes the file to staging slot zero. The effect is to resolve the merge while also choosing our (HEAD) or their (MERGE_HEAD) version of the file.

Thus, for files that do not have difficult names, the command:

git checkout HEAD $(git status --porcelain | grep '^UU ' | cut -f2-)

does the trick to choose --ours, while:

git checkout MERGE_HEAD $(git status --porcelain | grep '^UU ' | cut -f2-)

does the trick to choose --theirs for git merge (only!). Again, do not use this unless you know exactly what you are doing.

larsks
  • 277,717
  • 41
  • 399
  • 399
torek
  • 448,244
  • 59
  • 642
  • 775