Branching — Create, Switch & Merge
Why Branches Matter
If there is one feature that makes Git genuinely transformative for software development, it is branching. But to understand why, you need to understand what branching looked like before Git.
In Subversion (SVN), creating a branch meant copying the entire directory tree on the server. This was slow (tens of seconds to minutes) and expensive in disk space. As a result, SVN teams created as few branches as possible — usually just trunk and the occasional release branch. Everyone committed directly to trunk. The result was constant integration pain, a constantly partially-broken codebase, and a culture of big-bang merges at the end of a feature.
In Git, creating a branch takes a fraction of a millisecond. It costs 41 bytes of disk space (the size of a SHA-1 hash stored as a file). As a result, Git teams branch constantly — for every feature, every bugfix, every experiment. This is not a best practice bolted on top of Git. It is the way Git was designed to be used.
Understanding branches deeply unlocks almost everything else in Git.
What a Branch Actually Is
A Git branch is nothing more than a lightweight movable pointer to a commit.
Think back to the object model from lesson 1. Every commit has a SHA-1 hash. A branch is a file in .git/refs/heads/ that contains one SHA-1 hash — the hash of the commit that the branch currently points to.
That is literally all a branch is: a 40-character hex string in a file.
When you make a new commit on a branch, Git:
- Creates the new commit object, pointing to the previous commit as its parent
- Updates the branch file to contain the new commit's hash
The branch "moves forward" automatically as you commit.
This is why branching in Git is essentially free — there is no directory copying, no server round-trip, no expensive operation of any kind. It is just updating a file.
HEAD — The Special Pointer
HEAD is a special pointer that tells Git which commit is currently checked out — which snapshot your working directory reflects.
Most of the time, HEAD points to a branch, not directly to a commit. When HEAD points to a branch, committing causes the branch to advance to the new commit, and HEAD advances with it.
When HEAD points directly to a commit (not a branch), you are in "detached HEAD" state. This happens when you check out a specific commit by its hash. You can look at the code and even make commits, but those commits are not reachable from any branch and will eventually be garbage-collected unless you create a branch to point to them.
You can see where HEAD is pointing at any time with:
Creating Branches
git branch — List and Create Branches
With no branches except main in our new repo:
The * indicates the currently checked-out branch.
Create a new branch with:
This creates a new branch that points to the same commit as main right now. It does not switch to that branch — you are still on main.
You can create a branch pointing to any commit, not just the current HEAD:
Switching Between Branches
git switch (modern, recommended)
git switch was introduced in Git 2.23 (2019) as a cleaner alternative to git checkout for switching branches:
git checkout (traditional)
The older, still-widely-used command:
Both work identically for branch switching. git switch is preferred for new workflows because it has a clearer purpose — git checkout does many things (switching branches, checking out files, checking out commits) which can be confusing. New learners should use git switch; you will encounter git checkout extensively in existing documentation and teams.
Working with Branches in Practice
Let's build on the taskr project with a realistic branching workflow.
Now make changes on this branch:
Make another commit on this branch:
Now switch back to main and make a different change there:
Now visualize the diverged history:
This graph shows two branches diverging from commit 9a1b2c3. main has one commit ahead, feature/add-priority has two commits ahead.
Merging: Bringing Branches Together
When a feature branch is complete, you merge it back into main (or whatever base branch your team uses).
Git performs two fundamentally different types of merges:
Fast-Forward Merge
A fast-forward merge happens when the branch being merged in is a direct linear descendant of the current branch — there is no divergence.
In this case, main just needs to move its pointer forward to point to D. No new merge commit is needed — Git just "fast-forwards" the pointer.
Output:
The word "Fast-forward" confirms this was a fast-forward merge. main now points to the same commit as feature/add-help.
Three-Way Merge
When both branches have diverged (both have commits since their common ancestor), Git performs a three-way merge. It uses three snapshots: the common ancestor, the tip of the current branch, and the tip of the branch being merged in.
Git combines the changes from both branches and creates a new merge commit — a commit with two parents.
If there are no conflicts, Git creates the merge commit automatically:
Now the log looks like:
The merge commit d4e5f6a has two parents, shown by the branching lines in the graph.
Forcing a Merge Commit (No Fast-Forward)
Sometimes you want a merge commit even when a fast-forward is possible, to preserve the evidence that a feature branch existed:
This is common in teams that want to see feature branch history clearly in the log.
Deleting Branches
After merging a branch, you typically delete it:
The -d flag is safe: Git will refuse to delete a branch that has not been merged into HEAD, protecting you from accidentally losing work. The -D flag bypasses this check.
Deleted branches can be recovered as long as you know their commit hash (or run git reflog — covered in lesson 4).
Branch Naming Conventions
Good branch names make it clear what work is happening on a branch. Common conventions:
Type Prefixes
Full Examples
Practical Rules
- Use lowercase with hyphens, not underscores or spaces
- Include a ticket number if your team uses an issue tracker:
feature/JIRA-123-user-auth - Be descriptive but concise — 3-5 words after the prefix is usually enough
- Avoid:
my-branch,test,temp,new,fix(too vague) - Avoid:
feature/adding-the-new-user-authentication-system-for-the-login-page(too verbose)
Visualizing Branch Graphs
Understanding branch graphs is a skill you will use constantly when working with teams.
Learn to read the symbols:
*— a commit on a branch|— a vertical line connecting commits on the same branch/— a line showing a branch diverging\— a line showing branches converging into a merge commit|/— where a branch splits off from another|\— where branches merge back together
For more elaborate visualization, there are GUI tools:
- gitk — built into Git, launches a GUI graph viewer
- GitHub/GitLab web UI — shows branch graphs in the repository view
- VS Code Source Control — the GitLens extension provides excellent graph visualization
- GitKraken, Sourcetree, Fork — dedicated Git GUI applications
Common Branching Mistakes and How to Avoid Them
Mistake 1: Working Directly on main
Always do feature work on a branch. Even if you are working alone. The habit pays dividends immediately when:
- You need to pause the feature and do a quick bug fix
- You want to share the code for review before it lands in main
- The feature turns out to be a dead end and needs to be abandoned
Mistake 2: Long-Lived Branches
The longer a branch lives without being merged, the harder the merge becomes. Keep branches short-lived: days, not weeks. If a feature is large, break it into smaller slices that can be merged individually.
Mistake 3: Not Updating Your Branch
If main has received changes while you were working on your feature branch, merge or rebase main into your branch before merging your branch into main. This resolves conflicts on your branch (where only you are affected) rather than on main (where the whole team is affected).
Mistake 4: Confusing Local and Remote Branches
A local branch and its remote counterpart (e.g., origin/main) are separate things. We cover this in lesson 5, but be aware: git branch shows only local branches by default. git branch -a shows all, including remote-tracking branches.
Practical Exercises
Exercise 1: Create and Merge Branches
Exercise 2: Practice Fast-Forward vs Three-Way Merges
Exercise 3: Branch Naming Practice
Look at these vague branch names and rename them according to good conventions:
test→ ?my-stuff→ ?fix2→ ?new-feature→ ?johns-work→ ?
Write what you would rename each to, given the context that: you are fixing a bug where tasks with apostrophes in the title crash the app.
Challenge: Simulate a Team Workflow
Create three branches:
feature/search-tasks— add a search command to taskrfix/empty-file-crash— fix a crash when the tasks file is emptydocs/improve-readme— update README with all commands
Make commits on each. Then merge them into main in order: fix first, docs second, feature third. Observe how the graph evolves with each merge.
Summary
- A Git branch is simply a pointer to a commit stored as a 41-byte file in
.git/refs/heads/. Creating a branch is instantaneous and costs essentially nothing. HEADpoints to the currently checked-out branch (or commit in detached HEAD state). When you commit, HEAD's branch advances to the new commit.git branch <name>creates a branch.git switch <name>(orgit checkout <name>) switches to it.git switch -c <name>creates and switches in one step.- A fast-forward merge simply moves the branch pointer forward when there is no divergence. A three-way merge creates a new merge commit when both branches have diverged.
git branch -dsafely deletes a merged branch.git branch -Dforce-deletes regardless of merge status.- Use type prefixes (
feature/,fix/,hotfix/) in branch names, keep names descriptive, and keep branches short-lived. - Visualize your branch history with
git log --oneline --graph --all.
The next lesson covers one of the most important skills in Git: undoing changes. You will learn how to recover from mistakes at every level — unstaged changes, staged changes, and committed changes.