Chapter 9. Merges

Git is a distributed version control system (DVCS). It allows a developer in Japan, say, and another in New Jersey to make and record changes independently, and it permits the two developers to combine their changes at any time—all without a central repository. In this chapter, we’ll learn how to combine two or more different lines of development.

A merge unifies two or more commit history branches. Most often, a merge unites just two branches, although Git supports a merge of three, four, or many branches at the same time.

In Git, a merge must occur within a single repository—that is, all the branches to be merged must be present in the same repository. How the branches come to be in the repository is not important. (As you will see in Chapter 11, Git provides mechanisms for referring to other repositories and for bringing remote branches into your current working repository.)

When modifications in one branch do not conflict with modifications found in another branch, Git computes a merge result and creates a new commit that represents the new, unified state. But when branches conflict, which occurs whenever changes compete to alter the same line of the same file, Git does not resolve the dispute. Instead, Git marks such contentious changes as unmerged in the index and leaves reconciliation to you, the developer. When Git cannot merge automatically, it’s also up to you to make the final commit once all conflicts are resolved.

Merge Examples

To merge other_branch into branch, you should check out the target branch and merge the other branches into it, like this:

$ git checkout branch
$ git merge other_branch

Let’s work through a pair of example merges, one without conflicts and one with substantial overlaps. To simplify the examples in this chapter, let’s use multiple branches per the techniques presented in Chapter 7.

Preparing for a Merge

Before you begin a merge, it’s best to tidy up your working directory. During a normal merge, Git creates new versions of files and places them in your working directory when it is finished. Furthermore, Git also uses the index to store temporary and intermediate versions of files during the operation.

If you have modified files in your working directory or if you’ve modified the index via git add or git rm, your repository has a dirty working directory or index. If you start a merge in a dirty state, Git may be unable to combine the changes from all the branches and those in your working directory or index in one pass.

Tip

You don’t have to start with a clean directory. Git performs the merge, for example, if the files affected by the merge operation and the dirty files in your working directory are disjoint. However, as a general rule, your Git life will be much easier if you start each merge with a clean working directory and index.

Merging Two Branches

For the simplest scenario, let’s set up a repository with a single file, create two branches, and then merge the pair of branches again:

$ git init
Initialized empty Git repository in /tmp/conflict/.git/
$ git config user.email "[email protected]"
$ git config user.name "Jon Loeliger"

$ cat > file
Line 1 stuff
Line 2 stuff
Line 3 stuff
^D
$ git add file
$ git commit -m"Initial 3 line file"
Created initial commit 8f4d2d5: Initial 3 line file
1 files changed, 3 insertions(+), 0 deletions(-)
create mode 100644 file

Let’s create another commit on the master branch:

$ cat > other_file
Here is stuff on another file!
^D
$ git add other_file
$ git commit -m"Another file"
Created commit 761d917: Another file
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 other_file

So far, the repository has one branch with two commits, where each commit introduced a new file. Next, let’s change to a different branch and modify the first file:

$ git checkout -b alternate master^
Switched to a new branch "alternate"

$ git show-branch
* [alternate] Initial 3 line file
 ! [master] Another file
--
 + [master] Another file
*+ [alternate] Initial 3 line file

Here, the alternate branch is initially forked from the master^ commit, one commit behind the current head.

Make a trivial change to the file so you have something to merge and then commit it. Remember, it’s best to commit outstanding changes and start a merge with a clean working directory:

$ cat >> file
Line 4 alternate stuff
^D
$ git commit -a -m"Add alternate's line 4"
Created commit b384721: Add alternate's line 4
 1 files changed, 1 insertions(+), 0 deletions(-)

Now there are two branches and each has different development work. A second file has been added to the master branch, and a modification has been made to the alternate branch. Because the two changes do not affect the same parts of a common file, a merge should proceed smoothly and without incident.

The git merge operation is context-sensitive. Your current branch is always the target branch, and the other branch or branches are merged into the current branch. In this case, the alternate branch should be merged into the master branch, so the latter must be checked out before you continue:

$ git checkout master
Switched to branch "master"

$ git status
# On branch master
nothing to commit (working directory clean)

# Yep, ready for a merge!

$ git merge alternate
Merge made by recursive.
 file |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

You can use another commit graph viewing tool, a part of git log, to see what’s been done:

$ git log --graph --pretty=oneline --abbrev-commit

*   1d51b93... Merge branch 'alternate'
|
| * b384721... Add alternate's line 4
* | 761d917... Another file
|/
* 8f4d2d5... Initial 3 line file

That is exactly the same commit graph shown in Commit Graphs, except this graph is turned sideways, with the most recent commits at the top rather than the right. The two branches have split at the initial commit, 8f4d2d5; each branch shows one commit each (761d917 and b384721); and the two branches merge again at commit 1d51b93.

Tip

Using git log --graph is an excellent alternative to graphical tools such as gitk. The visualization provided by git log --graph is well suited to dumb terminals.

Technically, Git performs each merge symmetrically to produce one identical, combined commit that is added to your current branch. The other branch is not affected by the merge. Because the merge commit is added only to your current branch, you can say, I merged some other branch into this one.

A Merge with a Conflict

The merge operation is inherently problematic because it necessarily brings together potentially varying and conflicting changes from different lines of development. The changes on one branch may be similar to or radically different from the changes on a different branch. Modifications may alter the same files or a disjoint set of files. Git can handle all these varied possibilities, but often it requires guidance from you to resolve conflicts.

Let’s work through a scenario in which a merge leads to a conflict. We begin with the results of the merge from the previous section and introduce independent and conflicting changes on the master and alternate branches. We then merge the alternate branch into the master branch, face the conflict, resolve it, and commit the final result.

On the master branch, create a new version of file with a couple additional lines in it and then commit it:

$ git checkout master

$ cat >> file
Line 5 stuff
Line 6 stuff
^D

$ git commit -a -m"Add line 5 and 6"
Created commit 4d8b599: Add line 5 and 6
 1 files changed, 2 insertions(+), 0 deletions(-)

Now, on the alternate branch, modify the same file differently. Whereas you made new commits to the master branch, the alternate branch has not progressed yet:

$ git checkout alternate
Switched branch "alternate"

$ git show-branch
* [alternate] Add alternate's line 4
 ! [master] Add line 5 and 6
--
 + [master] Add line 5 and 6
*+ [alternate] Add alternate's line 4

# In this branch, "file" left off with "Line 4 alternate stuff"

$ cat >> file
Line 5 alternate stuff
Line 6 alternate stuff
^D

$ cat file
Line 1 stuff
Line 2 stuff
Line 3 stuff
Line 4 alternate stuff
Line 5 alternate stuff
Line 6 alternate stuff

$ git diff
diff --git a/file b/file
index a29c52b..802acf8 100644
--- a/file
+++ b/file
@@ -2,3 +2,5 @@ Line 1 stuff
 Line 2 stuff
 Line 3 stuff
 Line 4 alternate stuff
+Line 5 alternate stuff
+Line 6 alternate stuff

$ git commit -a -m"Add alternate line 5 and 6"
Created commit e306e1d: Add alternate line 5 and 6
 1 files changed, 2 insertions(+), 0 deletions(-)

Let’s review the scenario. The current branch history looks like this:

$ git show-branch
* [alternate] Add alternate line 5 and 6
 ! [master] Add line 5 and 6
--
*  [alternate] Add alternate line 5 and 6
 + [master] Add line 5 and 6
*+ [alternate^] Add alternate's line 4

To continue, check out the master branch and try to perform the merge:

$ git checkout master
Switched to branch "master"

$ git merge alternate
Auto-merged file
CONFLICT (content): Merge conflict in file
Automatic merge failed; fix conflicts and then commit the result.

When a merge conflict like this occurs, you should almost invariably investigate the extent of the conflict using the git diff command. Here, the single file named file has a conflict in its content:

$ git diff
diff --cc file
index 4d77dd1,802acf8..0000000
--- a/file
+++ b/file
@@@ -2,5 -2,5 +2,10 @@@ Line 1 stuff
  Line 2 stuff
  Line 3 stuff
  Line 4 alternate stuff
++<<<<<<< HEAD:file
 +Line 5 stuff
 +Line 6 stuff
++=======
+ Line 5 alternate stuff
+ Line 6 alternate stuff
++>>>>>>> alternate:file

The git diff command shows the differences between the file in your working directory and the index. In the traditional diff command output style, the changed content is presented between <<<<<<< and =======, with an alternate between ======= and >>>>>>>. However, additional plus and minus signs are used in the combined diff format to indicate changes from multiple sources relative to the final resulting version.

The previous output shows that the conflict covers lines 5 and 6, where deliberately different changes were made in the two branches. It’s then up to you to resolve the conflict. For now, simply edit the file to mirror this content:

$ cat file
Line 1 stuff
Line 2 stuff
Line 3 stuff
Line 4 alternate stuff
Line 5 stuff
Line 6 alternate stuff

If you are happy with the conflict resolution, you should git add the file to the index and stage it for the merge commit:

$ git add file

After you have resolved conflicts and staged final versions of each file in the index using git add, it is finally time to commit the merge using git commit. Git places you in your favorite editor with a template message that looks like this:

Merge branch 'alternate'

Conflicts:
        file
#
# It looks like you may be committing a MERGE.
# If this is not correct, please remove the file
#       .git/MERGE_HEAD
# and try again.
#

# Please enter the commit message for your changes.
# (Comment lines starting with '#' will not be included)
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   file
#

As usual, the lines beginning with the octothorp (#) are comments and meant solely for your information while you write a message. All comment lines are ultimately elided from the final commit log message. Feel free to alter or augment the commit message as you see fit, perhaps adding a note about how the conflict was resolved.

When you exit the editor, Git should indicate the successful creation of a new merge commit:

$ git commit

# Edit merge commit message

Created commit 7015896: Merge branch 'alternate'

$ git show-branch
! [alternate] Add alternate line 5 and 6
 * [master] Merge branch 'alternate'
--
 - [master] Merge branch 'alternate'
+* [alternate] Add alternate line 5 and 6

You can see the resulting merge commit using:

$ git log
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.137.108