We said this tutorial shows what plumbing does to help you cope
with the porcelain that isn't flushing, but we so far did not
talk about how the merge really works. If you are following this
tutorial the first time, I'd suggest to skip to "Publishing your
work" section and come back here later.
OK, still with me? To give us an example to look at, let's go
back to the earlier repository with "hello" and "example" file,
and bring ourselves back to the pre-merge state:
$ git show-branch --more=2 master mybranch
! [master] Merge work in mybranch
* [mybranch] Merge work in mybranch
--
-- [master] Merge work in mybranch
+* [master^2] Some work.
+* [master^] Some fun.
Remember, before running git merge, our master
head was at "Some
fun." commit, while our mybranch
head was at "Some work." commit.
$ git switch -C mybranch master^2
$ git switch master
$ git reset --hard master^
After rewinding, the commit structure should look like this:
$ git show-branch
* [master] Some fun.
! [mybranch] Some work.
--
* [master] Some fun.
+ [mybranch] Some work.
*+ [master^] Initial commit
Now we are ready to experiment with the merge by hand.
git merge
command, when merging two branches, uses 3-way merge
algorithm. First, it finds the common ancestor between them. The
command it uses is git merge-base:
$ mb=$(git merge-base HEAD mybranch)
The command writes the commit object name of the common ancestor
to the standard output, so we captured its output to a variable,
because we will be using it in the next step. By the way, the
common ancestor commit is the "Initial commit" commit in this
case. You can tell it by:
$ git name-rev --name-only --tags $mb
my-first-tag
After finding out a common ancestor commit, the second step is
this:
$ git read-tree -m -u $mb HEAD mybranch
This is the same git read-tree command we have already seen, but
it takes three trees, unlike previous examples. This reads the
contents of each tree into different stage in the index file (the
first tree goes to stage 1, the second to stage 2, etc.). After
reading three trees into three stages, the paths that are the
same in all three stages are collapsed into stage 0. Also paths
that are the same in two of three stages are collapsed into stage
0, taking the SHA-1 from either stage 2 or stage 3, whichever is
different from stage 1 (i.e. only one side changed from the
common ancestor).
After collapsing operation, paths that are different in three
trees are left in non-zero stages. At this point, you can inspect
the index file with this command:
$ git ls-files --stage
100644 7f8b141b65fdcee47321e399a2598a235a032422 0 example
100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1 hello
100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2 hello
100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello
In our example of only two files, we did not have unchanged files
so only example resulted in collapsing. But in real-life large
projects, when only a small number of files change in one commit,
this collapsing tends to trivially merge most of the paths fairly
quickly, leaving only a handful of real changes in non-zero
stages.
To look at only non-zero stages, use --unmerged
flag:
$ git ls-files --unmerged
100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1 hello
100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2 hello
100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello
The next step of merging is to merge these three versions of the
file, using 3-way merge. This is done by giving git
merge-one-file command as one of the arguments to git merge-index
command:
$ git merge-index git-merge-one-file hello
Auto-merging hello
ERROR: Merge conflict in hello
fatal: merge program failed
git merge-one-file script is called with parameters to describe
those three versions, and is responsible to leave the merge
results in the working tree. It is a fairly straightforward shell
script, and eventually calls merge program from RCS suite to
perform a file-level 3-way merge. In this case, merge detects
conflicts, and the merge result with conflict marks is left in
the working tree.. This can be seen if you run ls-files --stage
again at this point:
$ git ls-files --stage
100644 7f8b141b65fdcee47321e399a2598a235a032422 0 example
100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1 hello
100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2 hello
100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello
This is the state of the index file and the working file after
git merge returns control back to you, leaving the conflicting
merge for you to resolve. Notice that the path hello
is still
unmerged, and what you see with git diff at this point is
differences since stage 2 (i.e. your version).