Chapter 5: Branching Fundamentals of Git

Chapter Objectives

By the end of this chapter, you will be able to:

  • Define what a Git branch is and understand its utility in a development workflow.
  • Explain the role and significance of the default main (or master) branch.
  • Create new branches using git branch <branch-name>.
  • List local and remote-tracking branches.
  • Switch between branches using git checkout <branch-name> and the newer git switch <branch-name>.
  • Create and switch to a new branch in a single step.
  • Delete local branches using git branch -d <branch-name> and git branch -D <branch-name>.
  • Understand how the HEAD pointer relates to branches and the current working directory.
  • Follow common branch naming conventions.

Introduction

In the previous chapters, you’ve learned how to initialize repositories, make commits, and navigate your project’s history. So far, this history has been mostly linear. However, real-world projects often require working on multiple things simultaneously: developing new features, fixing bugs, and experimenting with ideas, all without destabilizing the main, working version of the project. This is where Git’s branching capabilities shine.

Branching is arguably one of Git’s most powerful and distinguishing features. It allows you to diverge from the main line of development and continue to do work without messing with that main line. This chapter introduces the fundamental concepts of branching in Git. You’ll learn what branches are, why they are incredibly useful, and how to create, list, switch between, and delete them. Understanding branching is key to unlocking much of Git’s flexibility and power, especially in collaborative environments.

Theory

What is a Branch?

In Git, a branch is simply a lightweight, movable pointer to a specific commit. Think of it as a label or a bookmark for a commit. When you start making commits on a new branch, this pointer moves forward automatically with each new commit made on that branch.

Unlike many older Version Control Systems where branching can be an expensive operation (often involving copying the entire codebase), Git branches are incredibly cheap and fast. Creating a new branch in Git is as simple as writing a 41-byte file (the SHA-1 hash of the commit it points to) in the .git/refs/heads directory. This efficiency encourages developers to use branches frequently for tasks of any size.

Why Use Branches?

Branches are a cornerstone of the Git workflow for several reasons:

  1. Isolation: You can work on a new feature, a bug fix, or an experiment in an isolated environment (the branch) without affecting the main codebase. If your work isn’t ready or turns out to be a dead end, you can easily discard the branch without impacting other work.
  2. Parallel Development: Multiple team members can work on different features or tasks concurrently on separate branches. These independent lines of development can then be merged back together when ready.
  3. Experimentation: Want to try out a risky new idea or refactor a large piece of code? Create a branch! If it works out, you can merge it. If not, you can delete the branch, and your main project remains untouched.
  4. Workflow Organization: Branches help organize the development process. For example, you might have a main branch for stable releases, develop branch for ongoing development, and various feature branches for new functionalities (e.g., feature/user-authentication).
  5. Context Switching: If you’re working on a feature and an urgent bug fix request comes in, you can commit your current work on the feature branch, switch to a bugfix branch (or create one from main), fix the bug, merge it, and then switch back to your feature branch to continue where you left off.

The main (or master) Branch

When you initialize a new Git repository, Git creates a default branch for you. Historically, this branch was named master. In recent years, the community and Git itself have moved towards main as the default name for inclusivity reasons. Throughout this book, we’ll primarily use main, but be aware that you might encounter master in older repositories or projects.

The main branch is not special in its technical capabilities compared to other branches. However, by convention, it’s typically considered the primary branch of your project. It often represents the official, stable, or production-ready version of your codebase. Developers create other branches from main, do their work, and then merge their changes back into main once they are complete and tested.

How Branches Work: Pointers and Commits

When you create a branch, Git creates a new pointer that points to the same commit you are currently on. As you make new commits while on that new branch, the new branch’s pointer moves forward to the new commit, while the old branch’s pointer remains where it was (unless you switch back to it and make commits there).

%%{
  init: {
    "theme": "base",
    "themeVariables": {
      "primaryColor": "#EDE9FE",      /* Default commit color - Light Purple */
      "primaryTextColor": "#5B21B6",  /* Text on commits */
      "primaryBorderColor": "#5B21B6",
      "lineColor": "#5B21B6",         /* Branch lines */
      "textColor": "#1F2937",         /* General text */
      "fontSize": "14px",
      "fontFamily": "Open Sans",
      "commitLabelColor": "#FFFFFF",
      "commitLabelBackground": "#5B21B6",
      "branchLabelColor": "#1F2937"
    },
    "gitGraph": {
      "showBranches": true,
      "showCommitLabel": true,
      "mainBranchName": "main",
    }
  }
}%%
gitGraph TB:
    commit id: "C1" tag: "Initial commit"
    commit id: "C2" tag: "Add version to README"
    
    branch feature/user-login
    checkout feature/user-login
    
    commit id: "C3" tag: "Add login.html"
    
    checkout main 
    commit id: "C4" tag: "Update documentation"
    
    checkout feature/user-login
    commit id: "C5" tag: "Fix login form validation"
    
    checkout main
    merge feature/user-login tag: "Merge feature/user-login"
    commit id: "C7" tag: "Prepare for release"

The HEAD Pointer Revisited

We introduced HEAD in Chapter 4 as a special pointer to the current commit. More accurately, HEAD usually points to the current local branch. When you are on the main branch, HEAD points to main. If you switch to a branch named feature-x, HEAD then points to feature-x.

When you run git commit, Git creates a new commit object whose parent is the commit that HEAD (and thus the current branch) is currently pointing to. Then, Git moves the pointer for the current branch (and HEAD along with it) forward to this new commit.

It’s possible for HEAD to point directly to a commit hash instead of a branch name. This is called a “detached HEAD” state, which we’ll explore in a later chapter. For now, assume HEAD points to your current active branch.

Branch Naming Conventions

While Git allows a lot of flexibility in branch names, following conventions makes your repository easier to understand and manage, especially in a team:

Convention / Guideline Description Examples
Be Descriptive Choose names that clearly indicate the purpose or content of the branch. user-profile-page
fix-login-validation
my-branch (Too vague)
test1 (Not descriptive)
Use Separators Use hyphens () or underscores (_) to separate words for readability. Avoid spaces. feature/new-login-flow
bugfix_incorrect_calculation
Categorize with Prefixes (Slashes) Group branches by type using forward slashes. This is a very common and helpful practice. feature/shopping-cart
bugfix/ticket-4321
hotfix/critical-db-issue
release/v1.2.0
docs/update-readme
Keep it Concise Aim for names that are informative but not overly long. refactor/payment-module
this-is-a-very-long-branch-name-for-a-small-feature (Too long)
Use Alphanumeric Characters Stick to letters, numbers, hyphens, underscores, and forward slashes (for prefixes). Most other special characters should be avoided.
Avoid Git Internal Names Do not use names that could conflict with Git’s internal references or commands. HEAD
FETCH_HEAD
master (if `main` is your default and you want to avoid confusion, unless intentional)
  • Use descriptive names: user-profile-page is better than my-branch or issue123.
  • Use hyphens or underscores to separate words: feature/new-login-flow or bugfix/incorrect-calculation. Avoid spaces.
  • Categorize with prefixes (slashes): This is a very common and useful convention.
    • feature/<description> (e.g., feature/shopping-cart)
    • bugfix/<description-or-id> (e.g., bugfix/login-error-on-ie11 or bugfix/ticket-4321)
    • hotfix/<description> (for urgent production fixes)
    • release/<version-number> (e.g., release/v1.2.0)
  • Keep them relatively short but informative.
  • Avoid using characters that might be problematic for shells or file systems, although Git is quite permissive. Stick to alphanumeric characters, hyphens, underscores, and forward slashes for prefixes.

Warning: Avoid using names that could conflict with Git internal references or commands (e.g., HEAD, FETCH_HEAD).

Practical Examples

Let’s get hands-on with branching. We’ll use the my-git-cookbook-ch4 repository from the previous chapter, or you can quickly set up a similar one.

Command Description Example / Notes
git branch Lists all local branches. The current branch is marked with an asterisk (*). git branch
git branch -v (shows last commit)
git branch -vv (shows tracking info too)
git branch <branch-name> Creates a new branch pointing to the current commit (HEAD). Does not switch to the new branch. git branch feature/new-idea
git switch <branch-name> Switches the working directory and HEAD to the specified existing branch. (Newer command) git switch main
git switch feature/new-idea
git checkout <branch-name> Switches the working directory and HEAD to the specified existing branch. (Traditional command) git checkout main
git switch -c <new-branch-name> Creates a new branch and immediately switches to it. (Newer command) git switch -c bugfix/urgent-fix
git checkout -b <new-branch-name> Creates a new branch and immediately switches to it. (Traditional command) git checkout -b feature/another-one
git branch -d <branch-name> Deletes a local branch. This is a “safe” delete; it prevents deletion if the branch has unmerged changes. git branch -d old-feature (Fails if unmerged)
git branch -D <branch-name> Force-deletes a local branch, even if it has unmerged changes. Use with caution. git branch -D experimental-stuff
git log –all –decorate –graph –oneline A useful combination to visualize the commit history of all branches, including branch pointers and relationships. Helps understand the current branching structure.

Setup (if continuing from Chapter 4, or create anew):

Bash
# If you don't have my-git-cookbook-ch4, or want a fresh start:
# rm -rf my-git-cookbook-ch5 # Optional: remove old one if exists
mkdir my-git-cookbook-ch5
cd my-git-cookbook-ch5
git init

# Commit 1 on main
echo "# My Git Cookbook" > README.md
git add README.md
git commit -m "C1: Initial commit with README"

# Commit 2 on main
echo "Version 1.0" >> README.md
git add README.md
git commit -m "C2: Add version to README"

Ensure your user.name and user.email are configured.

1. Listing Branches: git branch

The git branch command, with no arguments, lists your local branches.

Bash
git branch

Expected Output:

Bash
* main

Explanation of Output:

  • It lists all local branches.
  • The asterisk * next to main indicates that it’s the currently checked-out branch (i.e., HEAD points to main).

You can also use -v or -vv for more verbose output, showing the last commit on each branch:

Bash
git branch -v

Expected Output:

Bash
* main <short_hash_C2> C2: Add version to README

2. Creating a New Branch: git branch <branch-name>

Let’s create a new branch called feature/add-recipe-storage.

Bash
git branch feature/add-recipe-storage

(No output is shown if successful)

Now, list the branches again:

Bash
git branch

Expected Output:

Bash
  feature/add-recipe-storage
* main

Explanation of Output:

  • The new branch feature/add-recipe-storage is listed.
  • Notice that main is still the active branch (marked with *). Creating a branch does not automatically switch you to it. The new branch feature/add-recipe-storage currently points to the same commit as main (C2).

You can visualize this with git log:

Bash
git log --oneline --graph --decorate --all

Expected Output:

Bash
* <short_hash_C2> (HEAD -> main, feature/add-recipe-storage) C2: Add version to README
* <short_hash_C1> C1: Initial commit with README

This shows both main and feature/add-recipe-storage pointing to commit C2. HEAD is still on main.

3. Switching Branches: git checkout and git switch

To start working on the new feature, we need to switch to the feature/add-recipe-storage branch.

a. Using git checkout (Traditional command)

Bash
git checkout feature/add-recipe-storage

Expected Output:

Bash
Switched to branch 'feature/add-recipe-storage'

b. Using git switch (Newer, recommended command)

Git introduced git switch (and git restore) in version 2.23 to separate responsibilities from the overloaded git checkout command. git switch is specifically for branch operations. If you had switched back to main (git switch main), you could use:

Bash
git switch feature/add-recipe-storage

Expected Output:

Bash
Switched to branch 'feature/add-recipe-storage'

We will use git switch for switching branches going forward, but git checkout <branchname> still works and is widely used.

Now, check the branch list and log:

Bash
git branch

Expected Output:

Bash
* feature/add-recipe-storage
  main
```bash
git log --oneline --graph --decorate --all

Expected Output:

Bash
* <short_hash_C2> (HEAD -> feature/add-recipe-storage, main) C2: Add version to README
* <short_hash_C1> C1: Initial commit with README

Now HEAD points to feature/add-recipe-storage. Your working directory files still reflect commit C2.

4. Making Commits on the New Branch

Let’s add a new file and commit it on the feature/add-recipe-storage branch.

  1. Create a new file recipes.md:echo "# Recipes" > recipes.md echo "- Chocolate Cake" >> recipes.md
  2. Stage and commit it:git add recipes.md git commit -m "C3: Add initial recipes.md for feature branch"

Expected Output (for commit):

Bash
[feature/add-recipe-storage <short_hash_C3>] C3: Add initial recipes.md for feature branch
 1 file changed, 2 insertions(+)
 create mode 100644 recipes.md

Now, let’s look at the history:

Bash
git log --oneline --graph --decorate --all

Expected Output:

Bash
* <short_hash_C3> (HEAD -> feature/add-recipe-storage) C3: Add initial recipes.md for feature branch
* <short_hash_C2> (main) C2: Add version to README
* <short_hash_C1> C1: Initial commit with README

Explanation of Output:

  • The feature/add-recipe-storage branch (and HEAD) has moved forward to C3.
  • The main branch still points to C2. The history has diverged!
  • If you were to ls or check your files, you’d see recipes.md.

Now, switch back to the main branch:

Bash
git switch main

Expected Output:

Bash
Switched to branch 'main'
Your branch is up to date with 'origin/main'. # This message might appear if you have a remote, otherwise just "Switched..."

Check your files. recipes.md will be gone from your working directory because it only exists on the feature/add-recipe-storage branch (specifically, in commit C3 which main doesn’t include yet).

This demonstrates the isolation provided by branches – changes on one branch don’t affect others until you explicitly merge them.

Check the log again from main:

Bash
git log --oneline --graph --decorate --all

Expected Output:

Bash
* <short_hash_C3> (feature/add-recipe-storage) C3: Add initial recipes.md for feature branch
* <short_hash_C2> (HEAD -> main) C2: Add version to README
* <short_hash_C1> C1: Initial commit with README

HEAD now points to main at C2.

5. Combined Creation and Switching

It’s very common to want to create a new branch and switch to it immediately.

a. Using git checkout -b <new-branch-name> (Traditional)

Let’s create a bugfix/readme-typo branch from main and switch to it.

First, ensure you are on main: git switch main.

Bash
git checkout -b bugfix/readme-typo

Expected Output:

Bash
Switched to a new branch 'bugfix/readme-typo'

b. Using git switch -c <new-branch-name> (Newer)

This does the same thing: creates a new branch and switches to it.

If you were on main, you could run:

Bash
# git switch main # (if not already there)
# git switch -c bugfix/readme-typo-alternative

Let’s stick with bugfix/readme-typo for now.

Verify:

Bash
git branch

Expected Output:

Bash
* bugfix/readme-typo
  feature/add-recipe-storage
  main

HEAD is now on bugfix/readme-typo. This new branch was created pointing to where main was (C2).

Let’s make a quick fix on this branch:

Bash
# Imagine there was a typo in README.md, let's simulate a fix.
echo "Version 1.0.1 (typo fixed)" > README.md # Overwrite for simplicity
git add README.md
git commit -m "C4: Fix typo in README version"

View the history:

Bash
git log --oneline --graph --decorate --all

Expected Output:

Bash
* <short_hash_C4> (HEAD -> bugfix/readme-typo) C4: Fix typo in README version
| * <short_hash_C3> (feature/add-recipe-storage) C3: Add initial recipes.md for feature branch
|/
* <short_hash_C2> (main) C2: Add version to README
* <short_hash_C1> C1: Initial commit with README

Now we have two separate lines of development (feature/add-recipe-storage and bugfix/readme-typo) that diverged from main at C2.

6. Deleting a Branch: git branch -d and git branch -D

Once work on a branch is completed and typically merged into another branch (like main), you might want to delete it to keep your repository clean.

a. Safe Delete: git branch -d <branch-name>

This command will only delete a branch if its changes have been fully merged into the branch you’re currently on, or into another branch that is an ancestor of your current branch. It’s a safety measure to prevent accidental loss of unmerged work.

Let’s try to delete feature/add-recipe-storage. First, switch to main (you can’t delete the branch you’re currently on).

Bash
git switch main
git branch -d feature/add-recipe-storage

Expected Output (because C3 is not merged into main):

Bash
error: The branch 'feature/add-recipe-storage' is not fully merged.
If you are sure you want to delete it, run 'git branch -D feature/add-recipe-storage'.

Git prevents the deletion because feature/add-recipe-storage contains commit C3, which is not yet part of main‘s history.

b. Force Delete: git branch -D <branch-name>

If you are sure you want to delete a branch, even if it has unmerged changes (perhaps it was an experiment you want to discard), you can use -D.

Warning: Use git branch -D with caution, as it can lead to losing commit history if those commits don’t exist on any other branch and haven’t been merged.

Let’s assume, for this example, we want to discard the feature/add-recipe-storage branch:

Bash
git branch -D feature/add-recipe-storage

Expected Output:

Bash
Deleted branch feature/add-recipe-storage (was <short_hash_C3>).

Now, if you list branches or view the log, feature/add-recipe-storage and its unique commit C3 will be gone from the easy-to-reach refs (though the commit object C3 might still exist in Git’s database for a while until garbage collection, but it’s no longer pointed to by a branch).

Bash
git branch

Expected Output:

Bash
  bugfix/readme-typo
* main
Bash
git log --oneline --graph --decorate --all

Expected Output:

Bash
* <short_hash_C4> (bugfix/readme-typo) C4: Fix typo in README version
|/
* <short_hash_C2> (HEAD -> main) C2: Add version to README
* <short_hash_C1> C1: Initial commit with README

OS-Specific Notes

The Git branching commands (git branch, git checkout, git switch) and the concept of HEAD behave identically across Windows, macOS, and Linux. The underlying mechanisms are part of Git’s core and are platform-independent. Any differences you might encounter would typically be related to terminal behavior or shell-specific features, not the Git commands themselves for branching.

Common Mistakes & Troubleshooting Tips

Git Issue / Error Symptom(s) Troubleshooting / Solution
Committing on wrong branch Work intended for a feature branch ends up on main or another incorrect branch. Solution:
  • Check git status or prompt before commit.
  • If caught early: git reset HEAD~1 –soft, git switch -c correct-branch, then git commit.
  • Or: git branch correct-branch (from current state), then git switch main, git reset –hard <commit_before_mistake>. (Use –hard with extreme caution).
Deleting current branch Error: Cannot delete branch ‘…’ checked out at ‘…’. Solution: Switch to a different branch first (git switch main), then run git branch -d branch-to-delete.
git branch -d fails Error: The branch ‘…’ is not fully merged. Solution: The branch has unmerged commits. If sure, use git branch -D branch-name to force delete. Otherwise, merge the branch first.
Branch name typo Error: pathspec ‘…’ did not match any file(s) known to git or invalid reference. Solution: Use git branch to list correct names. Use tab completion in shell to avoid typos.
Losing work with -D Important unmerged commits disappear after git branch -D. Solution: Be very cautious with -D. Ensure work is merged or backed up. Recovery might be possible via git reflog (advanced) if done recently.
Working directory changes when switching branches Files appear/disappear or change content unexpectedly after git switch. Explanation: This is expected behavior. Git updates your working directory to match the snapshot of the commit the new branch points to. Ensure uncommitted changes are dealt with (commit, stash) before switching if they are not meant for the target branch.

Exercises

Use the my-git-cookbook-ch5 repository. If you deleted branches during the examples, you might want to recreate a similar state or start fresh with the setup. Assume main has C1 and C2.

  1. Feature Development Workflow:
    1. From the main branch, create and switch to a new branch named feature/user-login.
    2. On this branch, create a file login.html with some basic HTML content.
    3. Make a commit for adding login.html.
    4. Add another line to login.html (e.g., “Password field”).
    5. Make a second commit on feature/user-login.
    6. View the log graph showing all branches. How does feature/user-login relate to main?
  2. Hotfix Scenario:
    1. Switch back to the main branch.
    2. Imagine an urgent bug needs fixing. Create and switch to a new branch named hotfix/critical-bug-001 from main.
    3. Modify README.md on this hotfix branch (e.g., add “HOTFIX APPLIED” at the end).
    4. Commit this change on the hotfix branch.
    5. View the log graph again. You should now see main, feature/user-login, and hotfix/critical-bug-001.
  3. Branch Management:
    1. List all your local branches.
    2. Switch to the main branch.
    3. Attempt to delete the feature/user-login branch using the “safe” delete command. What happens and why?
    4. Now, delete the feature/user-login branch using the “force” delete command.
    5. Verify that the branch has been deleted.

Summary

Branching is a fundamental concept in Git that enables parallel development, isolation, and organized workflows:

  • Branches are lightweight pointers to commits.
  • The main (or master) branch is typically the primary line of development.
  • git branch: Lists local branches. The * indicates the current branch (HEAD).
  • git branch <branch-name>: Creates a new branch pointing to the current commit.
  • git switch <branch-name> (or git checkout <branch-name>): Switches HEAD and your working directory to the specified branch.
  • git switch -c <branch-name> (or git checkout -b <branch-name>): Creates a new branch and immediately switches to it.
  • git branch -d <branch-name>: Safely deletes a branch (only if merged).
  • git branch -D <branch-name>: Force-deletes a branch (use with caution).
  • HEAD is a special pointer, usually indicating the current local branch.
  • Adopting clear branch naming conventions improves repository clarity.

Mastering branching is essential for leveraging Git’s full potential, especially when working in teams or on complex projects. The next chapter will cover how to integrate the work done on different branches using merging.

Further Reading

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top