Skip to main content

What is a Branch?

A branch is a lightweight, movable pointer to a commit. From Git’s glossary:
A “branch” is a line of development. The most recent commit on a branch is referred to as the tip of that branch. The tip of the branch is referenced by a branch head, which moves forward as additional development is done on the branch.
Branches allow you to:
  • Develop features independently
  • Experiment without affecting stable code
  • Organize work by context (features, bugs, releases)
  • Collaborate with multiple streams of development
Git branches are incredibly lightweight - just 41 bytes (a reference to a commit SHA-1 and a newline). This makes branch operations nearly instantaneous.

Branch Fundamentals

Branch Storage

Branches are stored as references in .git/refs/heads/:
$ cat .git/refs/heads/main
750b4ead9c87ceb3ddb7a390e6c7074521797fb3
This file contains the SHA-1 of the commit the branch points to. When you make a new commit, Git simply updates this file with the new commit’s SHA-1.

The Current Branch (HEAD)

HEAD is a symbolic reference that points to your current branch:
$ cat .git/HEAD
ref: refs/heads/main
When you commit, Git:
  1. Creates the new commit object
  2. Updates the branch HEAD points to
  3. The branch now points to the new commit

Creating and Switching Branches

Creating Branches

From branch.h and git-branch.adoc:
1

Create a new branch

$ git branch feature-x
Creates a new branch pointing to the current commit.
2

Create and switch in one command

$ git checkout -b feature-x
# Or using the newer syntax:
$ git switch -c feature-x
3

Create from a specific commit

$ git branch feature-x abc123
Creates the branch pointing to commit abc123.

Switching Branches

Traditional: git checkout

$ git checkout main
Switched to branch 'main'

Modern: git switch

$ git switch main
Switched to branch 'main'
git switch was introduced in Git 2.23 to separate the branch-switching functionality from git checkout, which also handles file restoration.

Branch Operations

Listing Branches

# List local branches
$ git branch
  feature-x
* main
  bugfix-123

# List with commit info
$ git branch -v
  feature-x  750b4ea Add new feature
* main       4ccb6d7 Update docs
  bugfix-123 2a1b3c4 Fix critical bug

# List all branches (including remote)
$ git branch -a
  feature-x
* main
  remotes/origin/main
  remotes/origin/feature-x
The * indicates your current branch (where HEAD points).

Renaming Branches

From branch.h, Git provides safe branch renaming:
# Rename current branch
$ git branch -m new-name

# Rename any branch
$ git branch -m old-name new-name

# Force rename (overwrite existing)
$ git branch -M old-name new-name
Git updates:
  • The branch reference
  • The reflog
  • Upstream tracking configuration

Deleting Branches

1

Delete a merged branch

$ git branch -d feature-x
Deleted branch feature-x (was 750b4ea).
Safe deletion - only works if branch is fully merged.
2

Force delete an unmerged branch

$ git branch -D experimental
Deleted branch experimental (was abc123).
This discards unmerged work. Use with caution!
3

Delete remote-tracking branch

$ git branch -d -r origin/old-feature
Removes the local copy of a remote branch.

Branch Types

Local Branches

Branches in your repository stored in refs/heads/:
refs/heads/main
refs/heads/feature-x
refs/heads/bugfix-123
These are the branches you create and work on directly.

Remote-Tracking Branches

Local copies of branches from remote repositories, stored in refs/remotes/<remote>/:
refs/remotes/origin/main
refs/remotes/origin/feature-x
From Git’s data model documentation:
A remote-tracking branch refers to a commit ID. It’s how Git stores the last-known state of a branch in a remote repository.
These are updated by git fetch and git pull.

Upstream Branches

Local branches can track remote branches. From branch.h:
/**
 * Sets branch.<new_ref>.{remote,merge} config settings such that
 * new_ref tracks orig_ref according to the specified tracking mode.
 */
void dwim_and_setup_tracking(struct repository *r, const char *new_ref,
                             const char *orig_ref, enum branch_track track,
                             int quiet);
Set up tracking:
# When creating a branch
$ git checkout -b feature-x origin/feature-x

# For an existing branch
$ git branch --set-upstream-to=origin/feature-x
View tracking information:
$ git branch -vv
  feature-x 750b4ea [origin/feature-x: ahead 2] Add feature
* main      4ccb6d7 [origin/main] Update docs

Branching Strategies

Feature Branches

Create a branch for each feature or task:
$ git checkout -b feature/user-authentication
# Work on feature
$ git commit -m "Implement login"
$ git commit -m "Add password reset"
# Merge back to main
$ git checkout main
$ git merge feature/user-authentication
Use descriptive branch names like feature/, bugfix/, or hotfix/ prefixes to organize work.

Long-Running Branches

Maintain branches for different stability levels:
main       - Production-ready code
develop    - Integration branch
feature-*  - Feature branches

Topic Branches

Short-lived branches for specific topics:
$ git checkout -b bugfix/fix-login-error
# Fix the bug
$ git commit -m "Fix login validation"
# Merge and delete
$ git checkout main
$ git merge bugfix/fix-login-error
$ git branch -d bugfix/fix-login-error

Detached HEAD State

From glossary-content.adoc:
Normally HEAD stores the name of a branch. However, Git also allows you to check out an arbitrary commit that isn’t necessarily the tip of any particular branch. The HEAD in such a state is called “detached”.
Entering detached HEAD:
$ git checkout abc123
Note: switching to 'abc123'.

You are in 'detached HEAD' state...
In this state:
  • HEAD points directly to a commit, not a branch
  • New commits aren’t associated with any branch
  • Switching branches may make these commits unreachable
To save work from detached HEAD:
$ git checkout -b new-branch-name

Branch Implementation

From branch.c and branch.h, Git’s branch implementation includes:

Branch Creation

/*
 * Creates a new branch, where:
 *   - name is the new branch name
 *   - start_name is the name of the existing branch that 
 *     the new branch should start from
 *   - force enables overwriting an existing branch
 *   - track causes the new branch to be configured to merge 
 *     the remote branch that start_name tracks
 */
void create_branch(struct repository *r,
                   const char *name, const char *start_name,
                   int force, int clobber_head_ok,
                   int reflog, int quiet, 
                   enum branch_track track,
                   int dry_run);

Branch Validation

/*
 * Check if a branch 'name' can be created as a new branch
 */
int validate_new_branchname(const char *name, struct strbuf *ref, 
                            int force);

Merging Branches

Fast-Forward Merge

When the target branch hasn’t diverged:
    A---B---C  (main)
             \
              D---E  (feature)
$ git checkout main
$ git merge feature
Fast-forward
Result:
    A---B---C---D---E  (main, feature)
Git simply moves the branch pointer forward.

Three-Way Merge

When branches have diverged:
    A---B---C  (main)
         \
          D---E  (feature)
$ git checkout main
$ git merge feature
Merge made by the 'recursive' strategy.
Result:
    A---B---C---F  (main)
         \     /
          D---E    (feature)
Git creates a merge commit F with two parents.

Branch Best Practices

1

Keep branches focused

Each branch should have a single, clear purpose. This makes code review and integration easier.
2

Update regularly

Merge or rebase from main frequently to avoid large conflicts:
$ git checkout feature-x
$ git rebase main
3

Delete merged branches

Clean up after merging to keep your branch list manageable:
$ git branch -d feature-x
4

Use descriptive names

Good: feature/user-auth, bugfix/login-error Bad: temp, test, branch1
5

Don't commit directly to main

Use branches for all work, even small changes. This maintains a clean history and enables code review.

Advanced Branch Operations

Branch Comparison

# Commits in feature-x not in main
$ git log main..feature-x

# Files changed between branches
$ git diff main...feature-x

# Check if branch is merged
$ git branch --merged main

Orphan Branches

Create a branch with no history:
$ git checkout --orphan new-root
$ git rm -rf .
$ echo "New beginning" > README.md
$ git add README.md
$ git commit -m "Root commit"
Useful for:
  • GitHub Pages (gh-pages branch)
  • Separate documentation
  • Completely different project in same repository

Working with Worktrees

Git worktrees let you check out multiple branches simultaneously:
# Create worktree for feature branch
$ git worktree add ../feature-x-worktree feature-x

# Work in the new directory
$ cd ../feature-x-worktree
$ git log --oneline

# Remove when done
$ git worktree remove ../feature-x-worktree

Further Reading

  • git help branch - Complete branch command reference
  • git help checkout / git help switch - Switching branches
  • git help merge - Merging branches
  • git help rebase - Alternative to merging

Build docs developers (and LLMs) love