Introduction
This post covers git branches. The main references for this post are the Atlassian Bitbucket pages on git branches and the Youtube Introduction to Git - Branching and Merging by David Mahler.
Prerequisites
This tutorial is a continuation of my posts on Local git control and Shared master local git control.
Create new repo
The post on Local git control introduced git repositories (repos) as directories with added functions. Open a Terminal window and navigate to the parent folder where you want to create your repository for this hands-on tutorial, then execute the following commands:
$ mkdir git-test-repo
$ cd git-test-repo
$ git init
$ pico README.md
and add a message to the README.md file:
Test repository for branching
Hit [ctrl]+[X] to exit pico and save the edits by pressing Y when asked.
Add another text file, for example Chapters.md ($ pico Chapters.md), add some lines of text and save and exit. In my example I added the folowing text:
## Ch1 The Sun
## Ch2 Milakovitch cycles
## Ch3 Physical and biogeochimcal climate forcing
Also add a .gitignore file:
$ pico .gitignore
.DS_Store
*.log
log/
logs/
and then save and exit.
If you list all (ls -a) the content of your working directory (the file system in your repo):
$ ls -a
you should see four entries
.git .gitignore Chapters.md README.md
Both .git and .gitignore are hidden (each start with a dot “.”).
git status
Run a git status command:
On branch master
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
.gitignore
Chapters.md
README.md
nothing added to commit but untracked files present (use "git add" to track)
You have “No commits yet”, and all of the added content is in red (not shown above though) - they have not been staged either.
git stage
stage (add to the tracking system) all of the content in your repo, followed by a status check:
$ git add .
$ git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: .gitignore
new file: Chapters.md
new file: README.md
The color of the file names is now green (not shown above), git is telling us that files are ready to be commited, or how to go about to unstage.
git commit
When you commit something in git, you lock in the changes, like taking a snapshot. The status of your entire (staged) project will be stored, and you can always go back to this exact content as long as your repo exists. To commit all files and folders that have, at any tine, been staged:
$ git commit -am "initial commit"
[master (root-commit) 51a7ae7] initial commit
3 files changed, 10 insertions(+)
create mode 100644 .gitignore
create mode 100644 Chapters.md
create mode 100644 README.md
The hexadecimal code in the return message (51a7ae7) is the identifier from the commit (or snapshot) history tree. If you want to return to the versions of the included (staged) files in this snapshot you refer to this code. The details for how to do that is covered in the parallel post on Local git control.
git branch
When you created the git repo (with the command git init) the default branch master was created. And all the operations so far has been reated to the master branch. git keeps track of which branch is active at any particular time through a pointer called HEAD in the history tree. HEAD follows the active branch (not the commit) and is called a symbolic pointer. Each branch have its own pointer, that points at the most recent commit for that branch. This means that HEAD indirectly, via the branch pointer, also points at the overall latest commit.
All changes that you do in the working directory tree, like ordinary editing, writing, copying, deleting etc, have no effect on any branch. Neither does staging effect branches. It is only when you commit changes that you attach the staged changes to the active branch.
To list all the branches in your repo:
$ git branch
* master
The branch your are working with is indicate by a *, in our case there is only one branch master.
To create a new branch:
$ git branch b001
If you then again use the basic branch command:
$ git branch
You should now have a second branch.
* master
new-branch
git log
The git log command return details on your commits and branches:
$ git log
commit 51a7ae7af28bb6264df12fbb7e1df9dc5673326f (HEAD -> master, b001)
Author: Karttur <thomas.gumbricht@karttur.com>
Date: Thu Feb 20 13:52:25 2020 +0100
initial commit
The first returned line tells us the full hexadecimal of the latest commit and towards where the HEAD pointer is looking (HEAD -> master, b001). As HEAD points both towards master and b001 we are at the bifurcation point of the branching, but nothing is commited after the branching. You also get information on who did this and when, and the message that went with the commit. The new branch that we added have not yet had any commits and contains nothing.
A more condensed alternative is:
$ git reflog
51a7ae7 (HEAD -> master, b001) HEAD@{0}: commit (initial): initial commit
And then you can add different parameters to the git log command, for instance:
$ git log --all --decorate --oneline --graph
For now this command generates a condensed summary:
* 51a7ae7 (HEAD -> master, b001) initial commit
But it will give a more illustrative picture of the bracnhes once we start working with them.
git checkout and switch branch
In git jargon, a “checkout” switches between different versions of either files, commits or branches - it was used in the post on Local git control to restore deleted files. It can also be used for “checking out” from one branch and “checking in” on another. The Atlassian Bitbucket page on git checkout explains in detail. To switch from the current branch, master in our case, to the b001 branch we just created:
$ git checkout b001
Switched to branch 'b001'
$ git reflog
51a7ae7 (HEAD -> b001, master) HEAD@{0}: checkout: moving from master to b001
51a7ae7 (HEAD -> b001, master) HEAD@{1}: commit (initial): initial commit
The only thing that has actually happened in the repo is that HEAD now points towards the branch b001.
git branch and checkout combined
You can use the checkout command to create a new branch on the fly:
$ git checkout -b b002
Switched to a new branch 'b002'
This will create a third branch, b002, with the same bifucation point from master as b001.
Create new document
While in the branch b002 create a new markdown document:
$ pico ch1-The-Sun.md
and add some notes in it. Save the edits, exit and return to the command line. Check the status:
$ git status
On branch b002
Untracked files:
(use "git add <file>..." to include in what will be committed)
ch1-The-Sun.md
nothing added to commit but untracked files present (use "git add" to track)
Temporarily change back to your master branch and try the status command:
$ git checkout master
$ git status
And the message will be the same! It is only when commited that any changes get attached to the active branch.
Return to the branch you were in when editing the file “ch1-The-Sun.md”.
$ git checkout b002
stage and commit to branch
With HEAD pointing towards branch b002, stage and commit the changes:
$ git add .
git commit -m "created ch1-The-Sun.md"
[b002 d6e4774] created ch1-The-Sun.md
1 file changed, 14 insertions(+)
create mode 100644 ch1-The-Sun.md
Edit existing document
checkout to change branch to b001:
$ git checkout b001
Switched to branch 'b001'
Open the document Chapters.md and add or edit the text:
$ pico Chapters.md
...
## Add another chapter
Save and exit, and then commit (the document is already staged just add the parameter -a to commit):
$ git commit -am "added chapter in Chapters.md"
[b001 878d2a9] added chapter in Chapters.md
1 file changed, 2 insertions(+)
Make sure you have a clean working trees and nothing to commit by:
$ git status
On branch b001
nothing to commit, working tree clean
git log
First try:
$ git reflog
which returns an extended summary of the recent events:
878d2a9 (HEAD -> b001) HEAD@{0}: commit: added chapter in Chapters.md
51a7ae7 (master) HEAD@{1}: checkout: moving from b002 to b001
d6e4774 (b002) HEAD@{2}: commit: created ch1-The-Sun.md
51a7ae7 (master) HEAD@{3}: checkout: moving from b001 to b002
51a7ae7 (master) HEAD@{4}: checkout: moving from master to b001
51a7ae7 (master) HEAD@{5}: commit (initial): initial commit
Then try the condensed, graphical alternative introduced above:
$ git log --all --decorate --oneline --graph
* 878d2a9 (HEAD -> b001) added chapter in Chapters.md
| * d6e4774 (b002) created ch1-The-Sun.md
|/
* 51a7ae7 (master) initial commit
In the response to the last command, you can see that we have three different branches with unique content (from bottom to top):
- commit-id:51a7ae7; branch: master; message: “initial commit”
- commit-id:d6e4774; branch: b002; message: “created ch1-The-Sun.md”
- commit-id:878d2a9; branch: b001; message: “added chapter in Chapters.md”
The HEAD pointer shows that b001 is the singularly active branch.
With b001 as the active branch type:
$ cat Chapters.md
## Ch1 The Sun
## Ch2 Milakovitch cycles
## Ch3 Physical and biogeochimcal climate forcing
## Add another chapter
You can see that the line added (“## Add another chapter”) in the commit related to b001 (878d2a9) is included. If you check out to b002 and run the same command:
$ git checkout b002
$ cat Chapters.md
## Ch1 The Sun
## Ch2 Milakovitch cycles
## Ch3 Physical and biogeochimcal climate forcing
The added line is not there.
git merge
Merging is the git process of joining a bifurcation back into the mainstream. In our example we have three unique branches, a master and the two branch-offs b001 and b002 that bifurcated at the same commit but contain different edits. Both b001 and b002 are “ahead of” master. We need to merge both side branches back into master. The merge command is at the core of git, but also requires some more careful considerations, the Atlassian Bitbucket reference of git merge will probably come in handy.
Fast-Forward merge
A Fast-Forward merge happens when the master pointer still points at the commit that was the bifurcation point of the branch to merge with master. All that is actually needed in such a case is to “fast forward” the master pointer to the pointer of the branch to merge. If you look at the log list above, the HEAD pointer is to b001 and the b001 pointer is towards the commit 878d2a9. Thus if you execute the command $ git merge on these two branches (with master the receiving branch) all that is required is for master to catch up with b001.
Execute git status to ensure that HEAD is pointing to the correct merge-receiving branch (master in our case). If needed, execute git checkout <receiving> command to switch to the receiving branch.
To explore what the differences are between master and b001 and use git diff:
$ git checkout master
$ git diff master..b001
diff --git a/Chapters.md b/Chapters.md
index a80f1ec..01338d4 100644
--- a/Chapters.md
+++ b/Chapters.md
@@ -3,3 +3,5 @@
## Ch2 Milakovitch cycles
## Ch3 Physical and biogeochimcal climate forcing
+
+## Add another chapter
The response tells us that the line “## Add another chapter” will be added to the file Chapters.md. Execute the merge:
$ git merge b001 -m "merging b0001 to master">
Updating 51a7ae7..878d2a9
Fast-forward (no commit created; -m option ignored)
Chapters.md | 2 ++
1 file changed, 2 insertions(+)
The response reports “Fast-forward” and that as a consequence “no commit created; -m option ignored”. Confirm the content of Chapters.md:
$ cat Chapters.md
It should now contain the added line (“## Add another chapter”). Also check the difference betwen master and b001:
$ git diff master..b001
git branch --merged
Before deleting the branch that is now merged with master you can test the command git branch --merged that reports branches that are integrated into master:
$ git branch --merged
b001
* master
You can now safely delete branch b001:
git branch -d b001
Deleted branch b001 (was 878d2a9).
If you look above (or execute log command), you will see that the commit 878d2a9 is where master is now pointing.
3-way merge
Check the branching of your repo by executing the command:
$ git log --all --decorate --oneline --graph
* 878d2a9 (HEAD -> master) added chapter in Chapters.md
| * d6e4774 (b002) created ch1-The-Sun.md
|/
* 51a7ae7 initial commit
The result is very similar to before the merging of b001 into master. The only difference is that HEAD now points at master. And that is exactly the result of the Fast Forward merge we just did. But as both remaining branches (master and b002) contain edits compared to the bifurcation point, we can not merge these two using Fast Forward. Instead you have to perform a 3-way merge.
Make sure you are in the master branch:
$ git status
On branch master
nothing to commit, working tree clean
Execute the merge:
$ git merge b002
As you did not give a message (-m) git suggests a default message, either edit or accept it. The merge will complete with a new message:
Merge made by the 'recursive' strategy.
ch1-The-Sun.md | 14 ++++++++++++++
1 file changed, 14 insertions(+)
create mode 100644 ch1-The-Sun.md
The reported result tell us that “Merge made by the ‘recursive’ strategy.” Check out the graphical representation of what happened:
$ git log --all --decorate --oneline --graph
* 5a76be7 (HEAD -> master) Merge branch 'b002'
|\
| * d6e4774 (b002) created ch1-The-Sun.md
* | 878d2a9 added chapter in Chapters.md
|/
* 51a7ae7 initial commit
Check the merge status:
$ git branch --merged
b001
* master
It is safe to delete branch b002:
git branch -d b001
Merge conflicts
Merge conflicts appear when the same text (or line) in the file has been edited in multiple copies of the same file. In this section you will create a new branch (b003) and make (conflicting) edits to Chapters.md in both master and b003:
$ git checkout -b b003
$ pico Chapters.md
Edit the last chapter title:
## Add another chapter -> ## Ch4 Clouds and water vapor
commit the changes to b003:
$ git commit -am "b003 edits to Chapters.md"
checkout to master and make a different edit to Chapters.md:
$ git checkout master
$ pico Chapters.md
## Add another chapter -> ## Ch4 Sea ice and ocean color
commit the changes to master:
$ git commit -am "master edits to Chapters.md"
Check out the flow of commits for our branches:
$ git log --all --decorate --oneline --graph
* ba31829 (HEAD -> master) master edits to Chapters.md
| * 79d1120 (b003) b003 edits to Chapters.md
|/
* 5a76be7 Merge branch 'b002'
To merge b003 into master requires a 3-way merge. Make sure you are in the master branch and then start the merge:
$ git merge b003
Auto-merging Chapters.md
CONFLICT (content): Merge conflict in Chapters.md
Automatic merge failed; fix conflicts and then commit the result.
This merge could not be automatically solved, and a conflict is reported. First execture a git status:
$ git status
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: Chapters.md
no changes added to commit (use "git add" and/or "git commit -a")
git status gives us an escape route, to abort the merge with the command git merge --abort. But git has also, behind the scenes, prepared the working directory version of Chapters.md to highlight the conflicts for you. Thus open the file and inspect the conflicts:
$ pico Chapters.md
## Ch1 The Sun
## Ch2 Milakovitch cycles
## Ch3 Physical and biogeochimcal climate forcing
<<<<<<< HEAD
## Ch4 Sea ice and ocean color
=======
## Ch4 Clouds and water vapor
>>>>>>> b003
The lines above and below the equal signs (“=======”) are where you find conflicts. Above are the edits from the HEAD pointer (pointing to the receiving branch) and below are the edits from the branch to merge. You can edit this file, while also removing the git markers, for example like this:
## Ch1 The Sun
## Ch2 Milakovitch cycles
## Ch3 Physical and biogeochimcal climate forcing
## Ch4 Clouds and water vapor
When you are satisfied, stage the edited file:
$ git add Chapters.md
commit the changes, if you do not give a message (-m) git suggests a default, or you can enter a message interactively:
$ git commit
$ git log --all --decorate --oneline --graph
* f3a0b75 (HEAD -> master) Merge branch 'b003'
|\
| * 79d1120 (b003) b003 edits to Chapters.md
* | ba31829 master edits to Chapters.md
|/
* 5a76be7 Merge branch 'b002'
You can safely delete b003:
git branch -d b003
Resources
Youtube tutorial Introduction to Git - Branching and Merging by David Mahler (20170918)