Путеводитель по Руководству Linux

  User  |  Syst  |  Libr  |  Device  |  Files  |  Other  |  Admin  |  Head  |



   gitcore-tutorial    ( 7 )

основное руководство Git для разработчиков (A Git core tutorial for developers)

HOW DOES THE MERGE WORK?

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).