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
(ormaster
) 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 newergit switch <branch-name>
. - Create and switch to a new branch in a single step.
- Delete local branches using
git branch -d <branch-name>
andgit 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:
- 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.
- 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.
- 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.
- 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
). - 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 thanmy-branch
orissue123
. - Use hyphens or underscores to separate words:
feature/new-login-flow
orbugfix/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
orbugfix/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):
# 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.
git branch
Expected Output:
* main
Explanation of Output:
- It lists all local branches.
- The asterisk
*
next tomain
indicates that it’s the currently checked-out branch (i.e.,HEAD
points tomain
).
You can also use -v
or -vv
for more verbose output, showing the last commit on each branch:
git branch -v
Expected Output:
* 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
.
git branch feature/add-recipe-storage
(No output is shown if successful)
Now, list the branches again:
git branch
Expected Output:
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 branchfeature/add-recipe-storage
currently points to the same commit asmain
(C2).
You can visualize this with git log
:
git log --oneline --graph --decorate --all
Expected Output:
* <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)
git checkout feature/add-recipe-storage
Expected Output:
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:
git switch feature/add-recipe-storage
Expected Output:
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:
git branch
Expected Output:
* feature/add-recipe-storage
main
```bash
git log --oneline --graph --decorate --all
Expected Output:
* <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.
- Create a new file
recipes.md
:echo "# Recipes" > recipes.md echo "- Chocolate Cake" >> recipes.md
- Stage and commit it:
git add recipes.md git commit -m "C3: Add initial recipes.md for feature branch"
Expected Output (for commit):
[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:
git log --oneline --graph --decorate --all
Expected Output:
* <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 (andHEAD
) 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 seerecipes.md
.
Now, switch back to the main
branch:
git switch main
Expected Output:
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
:
git log --oneline --graph --decorate --all
Expected Output:
* <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.
git checkout -b bugfix/readme-typo
Expected Output:
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:
# git switch main # (if not already there)
# git switch -c bugfix/readme-typo-alternative
Let’s stick with bugfix/readme-typo for now.
Verify:
git branch
Expected Output:
* 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:
# 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:
git log --oneline --graph --decorate --all
Expected Output:
* <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).
git switch main
git branch -d feature/add-recipe-storage
Expected Output (because C3 is not merged into main):
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:
git branch -D feature/add-recipe-storage
Expected Output:
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).
git branch
Expected Output:
bugfix/readme-typo
* main
git log --oneline --graph --decorate --all
Expected Output:
* <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:
|
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.
- Feature Development Workflow:
- From the
main
branch, create and switch to a new branch namedfeature/user-login
. - On this branch, create a file
login.html
with some basic HTML content. - Make a commit for adding
login.html
. - Add another line to
login.html
(e.g., “Password field”). - Make a second commit on
feature/user-login
. - View the log graph showing all branches. How does
feature/user-login
relate tomain
?
- From the
- Hotfix Scenario:
- Switch back to the
main
branch. - Imagine an urgent bug needs fixing. Create and switch to a new branch named
hotfix/critical-bug-001
frommain
. - Modify
README.md
on thishotfix
branch (e.g., add “HOTFIX APPLIED” at the end). - Commit this change on the
hotfix
branch. - View the log graph again. You should now see
main
,feature/user-login
, andhotfix/critical-bug-001
.
- Switch back to the
- Branch Management:
- List all your local branches.
- Switch to the
main
branch. - Attempt to delete the
feature/user-login
branch using the “safe” delete command. What happens and why? - Now, delete the
feature/user-login
branch using the “force” delete command. - 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
(ormaster
) 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>
(orgit checkout <branch-name>
): SwitchesHEAD
and your working directory to the specified branch.git switch -c <branch-name>
(orgit 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
- Pro Git Book:
- Chapter 3.1 Git Branching – Branches in a Nutshell: https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell
- Chapter 3.2 Git Branching – Basic Branching and Merging (covers branching part): https://git-scm.com/book/en/v2/Git-Branching-Basic-Branching-and-Merging
- Chapter 3.4 Git Branching – Branch Management: https://git-scm.com/book/en/v2/Git-Branching-Branch-Management
- Official Git Documentation:
git branch
: https://git-scm.com/docs/git-branchgit switch
: https://git-scm.com/docs/git-switchgit checkout
(note its varied uses): https://git-scm.com/docs/git-checkout
- Learn Git Branching (Interactive Tutorial): https://learngitbranching.js.org/
