Tutorial Git Branch
Shows the git branch and the git merge commands in detail.
The example is a written example from Version Control with Git [1].
Introduction
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 more branches at the same time.
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, then 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 from those in your working directory or index in one pass.
Setup UC-1
<syntaxhighlight lang="bash"> $ mkdir /Test/gitbranch $ cd /Test/gitbranch $ git init Initialized empty Git repository in /Test/gitbranch/.git/ $ git config user.email "hafr@example.nl" $ git config user.name "Harm Frielink"
$ cat > file Line 1 stuff Line 2 stuff Line 3 stuff ^D
- Adding and committing
$ git add file $ git commit -m "Initial 3 line file" [master (root-commit) 4530cfd] Initial 3 lines
1 file changed, 3 insertions(+) create mode 100644 file
- Let's add another file
$ cat > other_file Here is stuff on another file! ^D
- Adding and committing
$ git add other_file $ git commit -m "Another file" [master b11d58a] Another file
1 file changed, 1 insertion(+) create mode 100644 other_file
</syntaxhighlight>
Creating the branches
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.
<syntaxhighlight lang="bash">
- Create branch 'alternate' with starting point 'master previous, one commit behind the current head'.
$ git checkout -b alternate master^ Switched to a new branch 'alternate'
$ git show-branch
- [alternate] Initial 3 lines
! [master] Another file
--
+ [master] Another file
- + [alternate] Initial 3 lines
</syntaxhighlight>
The git show-branch output is broken down into two sections separated by a line of dashes (--).
The section above the separator lists the names of branches enclosed in square brackets, one per line.
Each branch name is associated with a single column of output, identified by either an exclamation mark (!) or— if it is also the current branch — an asterisk (*).
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.
<syntaxhighlight lang="bash">
$ cat >> file
Line 4 alternate stuff
^D
$ git commit -a -m "Add alternate's line 4"
[alternate 2a7eab5] Add alternate's line 4
1 file changed, 1 insertion(+)
</syntaxhighlight>
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 alternate the branch.
Because the two changes do not affect the same parts of a common file, a merge should proceed smoothly and without incident.
Merging without Conflicts
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:
<syntaxhighlight lang="bash">
$ git checkout master
Switched to branch 'master'
- Show the status of the working directory
$ git status On branch master nothing to commit, working directory clean
- Shows the branches
$ git show-branch ! [alternate] Add alternate's line 4
* [master] Another file
-- + [alternate] Add alternate's line 4
* [master] Another file
+* [alternate^] Initial 3 lines
- Yep, ready for a merge!
$ git merge alternate Merge made by the 'recursive' strategy.
file | 1 + 1 file changed, 1 insertion(+)
</syntaxhighlight>
You can use another commit graph viewing tool, a part of git log, to see what what’s been done: <syntaxhighlight lang="bash"> $ git log --graph --pretty=oneline --abbrev-commit
- 68778fa Merge branch 'alternate' into 'master'
|\ | * 2a7eab5 Add alternate's line 4
- | b11d58a Another file
|/
- 4530cfd Initial 3 lines
</syntaxhighlight>
Merging with Conflicts
Begin with the results of the merge from the previous section and introduce independent and conflicting changes on the master and alternate branches.
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 few additional lines in it and then commit the changes:
<syntaxhighlight lang="bash">
$ git checkout master
$ cat >> file Line 5 stuff Line 5 stuff ^D
$ git commit -a -m "Add line 5 and 6" [master 419394e] Add line 5 and 6
1 file changed, 2 insertions(+)
</syntaxhighlight>
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.
<syntaxhighlight lang="bash">
$ 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 5929d11..c9e7e6f 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" [alternate 53e319c] Add alternate line 5 and 6
1 file changed, 2 insertions(+)
</syntaxhighlight>
Review the scenario. The current branch history looks like this: <syntaxhighlight lang="bash"> $ 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
</syntaxhighlight>
To continue, check out the master branch and try to perform the merge: <syntaxhighlight lang="bash"> $ git checkout master
$ git merge alternate Auto-merging file CONFLICT (content): Merge conflict in file Automatic merge failed; fix conflicts and then commit the result. </syntaxhighlight>
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:
<syntaxhighlight lang="bash">
$ git diff
diff --cc file
index 2a44cd1,c9e7e6f..0000000
--- a/file
+++ b/file
@@@ -2,5 -2,5 +2,10 @@@ Line 1 stuf
Line 2 stuff Line 3 stuff Line 4 Alternate stuff
++<<<<<<< HEAD
+Line 5 stuff +Line 6 stuff
++======= + Line 5 alternate stuff + Line 6 alternate stuff ++>>>>>>> alternate </syntaxhighlight>
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.
When resolving a merge conflict, you are free to choose any resolution you would like for the file.
That includes picking lines from only one side or the other, or a mix from both sides, or even making up something completely new and different.
Although that last option might be confusing, it is a valid choice.
In this case, a line from each branch as the makeup of my resolved version has been chosen.
The edited file now has this content:
<syntaxhighlight lang="bash">
$ nano file
....
$ cat file
Line 1 stuff
Line 2 stuff
Line 3 stuff
Line 4 Alternate stuff
Line 5 stuff
Line 6 alternate stuff
</syntaxhighlight>
If you are happy with the conflict resolution, you should git add the file to the index and stage it for the merge commit: <syntaxhighlight lang="bash"> $ git add file $ git commit -m "Merging conflicts resolved" [master 513bb4c] Merging conflicts resolved
$ git show-branch ! [alternate] Add alternate line 5 and 6
* [master] Merging conflicts resolved
--
- [master] Merging conflicts resolved
+* [alternate] Add alternate line 5 and 6 </syntaxhighlight>
Setup UC-2
An example of a merge-conflict that can not be solved automatically. <syntaxhighlight lang="bash"> $ mkdir /Test/gitmergewc $ cd /Test/gitmergewc $ git init Initialized empty Git repository in /Test/gitmergewc/.git/ $ echo hello > hello $ git add hello $ git commit -m "Initial hello file" [master (root-commit) 4151062] Initial hello file
1 file changed, 1 insertion(+) create mode 100644 hello
$ git checkout -b alt Switched to a new branch 'alt' $ echo world >> hello $ echo 'Yay!' >> hello $ git commit -a -m "One world" [alt 7770968] One world
1 file changed, 2 insertions(+)
$ git checkout master Switched to branch 'master' $ echo worlds >> hello $ echo 'Yay!' >> hello $ git commit -a -m "All worlds" [master 66d8520] All worlds
1 file changed, 2 insertions(+)
</syntaxhighlight>
One branch says world, whereas the other says worlds — a deliberate difference.
Merging
As in the earlier example, if you check out master and try to merge the alt branch into it, a conflict arises. <syntaxhighlight lang="bash"> $ git merge alt Auto-merging hello CONFLICT (content): Merge conflict in hello Automatic merge failed; fix conflicts and then commit the result. </syntaxhighlight>
As expected, Git warns you about the conflict found in the hello file.
Locating Conflicted Files
You can also use either the git status command or the git ls-files -u command to show the set of files that remain unmerged in your working tree. <syntaxhighlight lang="bash"> $ git status On branch master You have unmerged paths.
(fix conflicts and run "git commit")
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: hello
no changes added to commit (use "git add" and/or "git commit -a")
$ git ls-files -u 100644 ce013625030ba8dba906f756967f9e9ca394464a 1 hello 100644 e63164d9518b1e6caf28f455ac86c8246f78ab70 2 hello 100644 562080a4c6518e1bf67a9f58a32a67bff72d4f00 3 hello </syntaxhighlight>
Inspecting Conflicts
When a conflict appears, the working directory copy of each conflicted file is enhanced with three-way diff or merge markers.
Continuing from where the example left off, the resulting conflicted file now looks like this:
<syntaxhighlight lang="bash">
$ cat hello
hello
<<<<<<< HEAD
worlds
=
world >>>>>>> alt Yay! </syntaxhighlight>
Git diff with conflicts
<syntaxhighlight lang="bash"> $ git diff diff --cc hello index e63164d,562080a..0000000 --- a/hello +++ b/hello @@@ -1,3 -1,3 +1,7 @@@
hello
++<<<<<<< HEAD
+worlds
++======= + world ++>>>>>>> alt
Yay!
</syntaxhighlight>
What does it all mean?
It’s the simple combination of two diffs: one versus the first parent, called HEAD, and one against the second parent, or alt. (Don’t be surprised if the second parent is an absolute SHA1 name representing some unnamed commit from some other repository!)
To make things easier, Git also gives the second parent the special name MERGE_HEAD.
You can compare both the HEAD and MERGE_HEAD versions against the working directory (“merged”) version: <syntaxhighlight lang="bash"> $ git diff HEAD diff --git a/hello b/hello index e63164d..1f2f61c 100644 --- a/hello +++ b/hello @@ -1,3 +1,7 @@
hello
+<<<<<<< HEAD
worlds
+======= +world +>>>>>>> alt
Yay!
</syntaxhighlight>
And then this: <syntaxhighlight lang="bash"> $ git diff MERGE_HEAD diff --git a/hello b/hello index 562080a..1f2f61c 100644 --- a/hello +++ b/hello @@ -1,3 +1,7 @@
hello
+<<<<<<< HEAD +worlds +=======
world
+>>>>>>> alt
Yay!
</syntaxhighlight>
<syntaxhighlight lang="text">
See page 132 and further to continue...
</syntaxhighlight>
See also
- Git, General on Git
- GitHub, General on GitHub
- Tutorial Git Add, Tutorial on 'git add'.
- Tutorial Git Commit, Tutorial on git commit.
- Tutorial Git Diff, Tutorial on 'git diff'
Reference
- ↑ Version Control with Git, Powerful Tools and Techniques for Collaborative Software Development, Jon Loeliger & Matthew McCullough, O'Reilly, 2012 Second Edition, isbn=9781449316389