Getting Started with Git Branching

Getting Started with Git Branching

Here in this article we will try to understand about one of the Core Concept of Git Distributed Version Control system which is Branching. Branching provides users with an sandbox environment to work on their changes completely isolated from the main or master branch. We will try to create different branches and make changes in them and see how we can merge these changes with our main or master branch code. We will also look at the Merge conflicts that we might encounter while merging changes from different branches.

Test Environment

Fedora 37 workstation
Git v2.40.0

What is Git Branching

Git Branching is a concept in which we take the current snapshot of the project and make a new area completely isolated from the mainline to work on new features and enhancements. It works on the concept of pointers to objects also called references, which makes them lightweight means occupying less space, easy to branch and fast for switching between branches which are nothing from pointer references which are updated as per our active branch.

Before getting into the details of branching, let us try to understand how Git stores this version controlled data. Everything in Git is stored in the form of objects with an Object ID. An Object ID is nothing but a SHA1 Hash consisting of 40 characters which is used to identify or reference our Object in git repository.

What are Git Objects

In Git there are only these types of Objects that get stored. These are called Blobs, Trees and Commits. Blobs are objects which hold the file content. Trees are a directory listing of blobs and sub-trees. Finally commits consist of references to these trees and blobs which when dereferenced will make out our snapshot that is stored in Git repository.

Example Git Objects Internals

Let us know try to understand about these two concepts through a pratical exercise.

Procedure

Step1: Identify Active branch and last commit

As a first step let us try to get our active branch and check the last commit id that was done on this branch. As you can see our active branch is master and the last commit on this branch is with commit id – f9c7919

[admin@fedser learngit]$ git branch -a
* master

[admin@fedser learngit]$ git log --oneline
f9c7919 (HEAD -> master) moviing license file to markdown file type
c423e39 removing dummpy file
acf06e8 dummy file to be removed
fa38ac8 updated contributing file
f0a11ab update .gitignore file to ignore log and bin files
2f9fb33 adding contributing and updating readme
5d430bb update files commit 2
e2d9213 Initial Commit

As i mentioned earlier Git works on the concept of pointer references to objects. Here let us try to understand about two pointers which are branch pointer and HEAD pointer. A branch always points the commit id and HEAD always points to the active branch. Let us see it through .git internal repository.

[admin@fedser learngit]$ cat .git/refs/heads/master 
f9c7919761b8a7be7e4a634d127b1b9369bba898

[admin@fedser learngit]$ cat .git/HEAD 
ref: refs/heads/master

Step2: Analyze Git Commit Object

As we looked in our first step the commit id that the master branch is pointing to is “f9c7919761b8a7be7e4a634d127b1b9369bba898”. Let us try to see the type of this object id using a Git tool “git cat-file”.

Here we are passing the “-t” option to know the type of the object

[admin@fedser learngit]$ git cat-file -t f9c7919761b8a7be7e4a634d127b1b9369bba898
commit

Here we are passing the “-p” option to know the content of the object

[admin@fedser learngit]$ git cat-file -p f9c7919761b8a7be7e4a634d127b1b9369bba898
tree 4003215edce68b1e61e278e236c3733be540f50f
parent c423e392dcf2193f9a2b6ced1af51e6275c52cca
author novicejava1 <sudhirbhoga@gmail.com> 1683019743 +0530
committer novicejava1 <sudhirbhoga@gmail.com> 1683019743 +0530

As you can see from the output this commit id consist of a tree object and one parent object which was an earlier commit id. Now let us try to dig further into the commit id by getting the tree details as shown below.

[admin@fedser learngit]$ git cat-file -t 4003215edce68b1e61e278e236c3733be540f50f
tree
[admin@fedser learngit]$ git cat-file -p 4003215edce68b1e61e278e236c3733be540f50f
100644 blob 5fd5ccf5c2626c69430711a58e66e8e15138f046	.gitignore
040000 tree 1df51376ac0218f228279cfa6ec3957114a49682	chapter1
040000 tree 9e468ded5903756bf33649a0deefe750760a3684	chapter2
040000 tree 7ed6e77f34efc3e66a12f98d77a99fe3f98df386	chapter3
100644 blob a60cda6f9fab1916c44d9e53f8cc9c406afaee70	contributing.md
100644 blob 8da84891bfc9327c9d079dc09cd5f84be307d8f3	license.md
100644 blob f51169dfb2b1845a4b27a759718b8a8120baad27	readme.md

Now you can see a tree consist of sub trees and blob. This way we can get into the details the objects that are stored in Git repository. All these Objects are stored at the following location in the .git directory. The 40 character object id is prepared usng the first two characters of the folder and filename within that folder and newline being the 40th character.

[admin@fedser learngit]$ tree .git/objects/
.git/objects/
├── 01
│   └── a359c4906e73f10628bd4cdcba47d004d178a1
├── 02
│   └── 81bee6092d9c98f56c1b8d33cbc7b2884d5492
├── 03
│   └── 5892383dc600e7721da6a82b9236c627fa83b4
├── 18
│   ├── 8bbe02293c63ade5149dba1267a994669b3667
│   └── dfcda50fb4ad71d1c4aadfe730cdb4042a8fd6
├── 1a
│   └── 28d6e89f5669c7e7cd9f1c2075d0b4e7377acd
├── 1d
│   └── f51376ac0218f228279cfa6ec3957114a49682
├── 2f
│   ├── 87dff1cb2ffbe3e91db33056886c5cfd794159
│   └── 9fb33c4123b0a8a3e7ffc1a209293f4dd39f79
├── 40
│   └── 03215edce68b1e61e278e236c3733be540f50f
├── 47
│   └── cf05dda6f67fe1fe879ed3764b75e43a532ced
├── 56
│   └── cf8160d2411ca9f3cc70ad741379bce8d7bbbc
├── 58
│   └── 1a0ae2feba36d6cf6ed50630952b6e31c9f998
├── 5d
│   └── 430bbfa870532547da51fdf8e3cf6370a05067
├── 5e
│   └── 854af325e97602e2f5f9105b8af4c2739fbcf9
├── 5f
│   └── d5ccf5c2626c69430711a58e66e8e15138f046
├── 69
│   └── b6a88814c72b8b8a53f9463ff53d70517e5f1f
├── 6f
│   └── 06612ef56412cffab9d263176e0809cf45d57c
├── 70
│   └── 2801dc1c77a78a51efea26f4dc5cd77b1d8cb4
├── 7c
│   └── 947ed7629d6b83394d08937a9196ef4d8aaff7
├── 7e
│   └── d6e77f34efc3e66a12f98d77a99fe3f98df386
├── 84
│   └── 9b349c4ade377fd8000bd096c7556d9cc695e2
├── 8a
│   └── 8b48cfc4e75ef7a3fd7f75405f5caa9bb4220d
├── 8d
│   └── a84891bfc9327c9d079dc09cd5f84be307d8f3
├── 94
│   └── 1c877024db6c25a4cc04acb0dfe3b500cd25cb
├── 9b
│   └── dbfc6655b0c35e684612262627e578aeef6eb5
├── 9e
│   └── 468ded5903756bf33649a0deefe750760a3684
├── a4
│   └── b2e0a9b6b6c829b3932a84bfa75b040fd9e6b2
├── a6
│   └── 0cda6f9fab1916c44d9e53f8cc9c406afaee70
├── ac
│   └── f06e8d0b8ba90c0d61a4f76942e6928f3334e9
├── b3
│   └── 387ffe1372c8b723e46f88c99e598e2bea9045
├── bb
│   └── 7a660089bee6a1573c8b60085453f85edc64af
├── c4
│   └── 23e392dcf2193f9a2b6ced1af51e6275c52cca
├── cd
│   └── 12fb84915787a52a1d3adef9a506b356e87ea2
├── e2
│   └── d92139ab6eb967ef9c35c9d83385ffa2dfc43a
├── e6
│   └── 9de29bb2d1d6434b8b29ae775ad8c2e48c5391
├── f0
│   └── a11ab36efaf93a0d45ec78fca7032fc0bda585
├── f5
│   └── 1169dfb2b1845a4b27a759718b8a8120baad27
├── f9
│   └── c7919761b8a7be7e4a634d127b1b9369bba898
├── fa
│   └── 38ac8a8f6631d65c6455ea82accff617292cd0
├── fc
│   └── dc952da7050e01ea3f407f28727cb4f8f36c13
├── info
└── pack

Step3: Create a branch

Let us now try to create a new branch and switch into it by checking out the last commit into our working directory.

[admin@fedser learngit]$ git switch -c "edition1"
Switched to a new branch 'edition1'

[admin@fedser learngit]$ git branch -a
* edition1
  master

[admin@fedser learngit]$ git status
On branch edition1
nothing to commit, working tree clean

Step4: Commit changes in new branch

Let us create some content in this branch and try to commit it as shown below.

[admin@fedser learngit]$ mkdir chapter4
[admin@fedser learngit]$ echo "GitInternals" > chapter4/GitInternals.txt
[admin@fedser learngit]$ git add .

[admin@fedser learngit]$ git commit -m "created chapter4"
[edition1 5623dae] created chapter4
 1 file changed, 1 insertion(+)
 create mode 100644 GitInternals.txt

[admin@fedser learngit]$ git status
On branch edition1
nothing to commit, working tree clean

Step5: Merge branch

In order to merge the changes from the edition1 branch to master branch we need to first switch to master branch and then carry out the merge.

[admin@fedser learngit]$ git switch master
Switched to branch 'master'
[admin@fedser learngit]$ git branch -a
  edition1
* master
[admin@fedser learngit]$ git merge edition1
Updating f9c7919..caa75f0
Fast-forward
 chapter4/GitInternals.txt | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 chapter4/GitInternals.txt

Verify the commit history as shown below.

[admin@fedser learngit]$ git log --oneline --graph
* caa75f0 (HEAD -> master, edition1) created chapter4
* 23bb8bc created chapter4
* 5623dae created chapter4
* f9c7919 moviing license file to markdown file type
* c423e39 removing dummpy file
* acf06e8 dummy file to be removed
* fa38ac8 updated contributing file
* f0a11ab update .gitignore file to ignore log and bin files
* 2f9fb33 adding contributing and updating readme
* 5d430bb update files commit 2
* e2d9213 Initial Commit

Step6: Update Same file on both branches

Ensure that we are the master branch and create some content and commit the changes into the master branch. Here i have create the following “chapter4/GitInternals.txt” content and committed the changes into master branch.

[admin@fedser learngit]$ git branch -a
  edition1
* master
[admin@fedser learngit]$ cat chapter4/GitInternals.txt 
GitInternals
This is an update to same file on master branch
[admin@fedser learngit]$ git add chapter4/GitInternals.txt 
[admin@fedser learngit]$ git commit -m "This is an update to same file on master branch"
[master 6a3ea27] This is an update to same file on master branch
 1 file changed, 1 insertion(+)

Now let us switch to “edition1” branch and create similar content structure and commit the changes as shown below. If you see carefully i have committed “GitInternals.txt” file in both the branches but with a different content.

[admin@fedser learngit]$ git switch edition1
Switched to branch 'edition1'
[admin@fedser learngit]$ cat chapter4/GitInternals.txt 
GitInternals
This is an update to same file on edition branch
[admin@fedser learngit]$ git add chapter4/GitInternals.txt 
[admin@fedser learngit]$ git commit -m "This is an update to same file on edition branch"
[edition1 1704b5c] This is an update to same file on edition branch
 1 file changed, 1 insertion(+)

Step7: Re-merge

Now let us try to re-merge the content from edition1 branch into the master branch and see if it gets merged.

First let us switch to the master branch and then try to do “git merge” the edition1 branch as shown below.

[admin@fedser learngit]$ git switch master
Switched to branch 'master'

[admin@fedser learngit]$ git merge edition1
Auto-merging chapter4/GitInternals.txt
CONFLICT (content): Merge conflict in chapter4/GitInternals.txt
Automatic merge failed; fix conflicts and then commit the result.

As you can see from the above error, merge activity has failed as Git is unable to decided on which content to be stored in the “GitInternals.txt” file. Whether it should store the content from master branch or from the edition1 branch. This is where the user need to intervene and resolve the conflict.

The conflict are identified in the “GitInternals.txt” file as shown below. The content between “<<<<<<<” and “=======” is from the HEAD which points to the current active branch which is master in this case. The content between “=======” and “>>>>>>>” is from the edition branch as present in the file content.

[admin@fedser learngit]$ cat chapter4/GitInternals.txt 
GitInternals
<<<<<<< HEAD
This is an update to same file on master branch
=======
This is an update to same file on edition branch
>>>>>>> edition1

Step8: Resolve the Conflict

Update our “GitInternals.txt” file as shown below to resolve the conflict and commit the changes now.

[admin@fedser learngit]$ cat chapter4/GitInternals.txt 
GitInternals
This is an update to same file with conflict resolved

Now let us check the “git status” and commit the changes as shown below.

[admin@fedser learngit]$ 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:   chapter4/GitInternals.txt
[admin@fedser learngit]$ git add chapter4/GitInternals.txt 
[admin@fedser learngit]$ git status
On branch master
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:
	modified:   chapter4/GitInternals.txt

[admin@fedser learngit]$ git commit -m "This is an update to same file with conflict resolved"
[master 762f6a0] This is an update to same file with conflict resolved

Step9: Validate the Commit History

Now we can check the log history to see our merge commit from two different commit id on two different branches. Every * in the log history is a commit mentioned with the commit id and commit message. As you can see from the output both the commit id “6a3ea27” on master branch and “1704b5c” on edition1 branch are merged with a new commit id “762f6a0”.

[admin@fedser learngit]$ git log --oneline --graph
*   762f6a0 (HEAD -> master) This is an update to same file with conflict resolved
|\  
| * 1704b5c (edition1) This is an update to same file on edition branch
* | 6a3ea27 This is an update to same file on master branch
|/  
* caa75f0 created chapter4
* 23bb8bc created chapter4
* 5623dae created chapter4
* f9c7919 moviing license file to markdown file type
* c423e39 removing dummpy file
* acf06e8 dummy file to be removed
* fa38ac8 updated contributing file
* f0a11ab update .gitignore file to ignore log and bin files
* 2f9fb33 adding contributing and updating readme
* 5d430bb update files commit 2
* e2d9213 Initial Commit

For details view remove the “–oneline” option as shown below.

[admin@fedser learngit]$ git log --graph
*   commit 762f6a031fe6c19e3ead49d7693f9951288c4451 (HEAD -> master)
|\  Merge: 6a3ea27 1704b5c
| | Author: novicejava1 <sudhirbhoga@gmail.com>
| | Date:   Tue May 2 18:15:11 2023 +0530
| | 
| |     This is an update to same file with conflict resolved
| | 
| * commit 1704b5c0ece8b6ba48106ec66cbcb5c20d6b462a (edition1)
| | Author: novicejava1 <sudhirbhoga@gmail.com>
| | Date:   Tue May 2 18:03:05 2023 +0530
| | 
| |     This is an update to same file on edition branch
| | 
* | commit 6a3ea2727dd91db77d37ff3bbdf1af5b2def8651
|/  Author: novicejava1 <sudhirbhoga@gmail.com>
|   Date:   Tue May 2 17:59:47 2023 +0530
|   
|       This is an update to same file on master branch
| 
...

Step10: Delete the branch

Once we are satisfied with our “edition1” branch changes which are merged with the master we can delete this branch.

Ensure that you are on the master branch.

[admin@fedser learngit]$ git branch -a
  edition1
* master

Again as i mentioned a branch point to the last commit that happened on that branch.

[admin@fedser learngit]$ cat .git/refs/heads/edition1 
1704b5c0ece8b6ba48106ec66cbcb5c20d6b462a

Now let us delete this branch

[admin@fedser learngit]$ git branch -d edition1
Deleted branch edition1 (was 1704b5c).

And if we now try to look for that branch reference in the internal .git repository it won’t be present.

[admin@fedser learngit]$ cat .git/refs/heads/edition1 
cat: .git/refs/heads/edition1: No such file or directory

Hope you enjoyed reading this article. Thank you..