DEV Community

Shrijith Venkatramana
Shrijith Venkatramana

Posted on

Unpacking Git's Branching: A Look at the Internals

Hi, I'm Shrijith Venkatramana. Right now, I'm building on an aggregation of 50,000+ resources on my site Free DevTools. This site hosts many free developer tools, manuals, cheatsheets and icon sets - which you can download or use without any login. Do give it a try here

Git branching lets developers work on features or fixes without disrupting the main codebase. At its core, branches are lightweight pointers to commits, making them efficient for managing parallel development. This article breaks down how branching operates inside Git, with examples to show the mechanics in action.

Branches as Simple Pointers

In Git, a branch isn't a full copy of the code—it's a reference to a specific commit. Stored in the .git/refs/heads/ directory, each branch file contains the SHA-1 hash of the commit it points to. This design keeps branching fast and cheap.

Key point: Branches move forward with new commits, updating their reference automatically.

For example, start with a fresh repository:

# Initialize a new Git repository
git init my-repo
cd my-repo

# Create and commit a file
echo "Initial content" > file.txt
git add file.txt
git commit -m "Initial commit"
# Output: [master (root-commit) abc123] Initial commit

# Check the branch reference
cat .git/refs/heads/master
# Output: abc1234567890abcdef1234567890abcdef1234 (example SHA)
Enter fullscreen mode Exit fullscreen mode

Here, master points to the commit hash. Adding another commit shifts this pointer.

Git documentation on branches: git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell

HEAD: Git's Current Position Marker

HEAD is a special reference that tracks where you are in the repository. It usually points to a branch, like refs/heads/main, but can detach to point directly at a commit. This controls what git status and commits affect.

Key point: When HEAD points to a branch, commits update that branch's pointer.

View HEAD in action:

# After initial setup from previous example
git checkout -b feature
# Output: Switched to a new branch 'feature'

# Inspect HEAD
cat .git/HEAD
# Output: ref: refs/heads/feature

# Commit something new
echo "Feature addition" >> file.txt
git add file.txt
git commit -m "Add feature"
# Output: [feature def456] Add feature
Enter fullscreen mode Exit fullscreen mode

HEAD now references the feature branch, which points to the new commit.

Creating Branches: Instant and Low-Cost

Git creates a branch by writing a new file in .git/refs/heads/ with the current commit's hash. No files are copied—it's just a 41-byte file (40 for the hash plus a newline).

Key point: Branches start from the current HEAD, inheriting the commit history.

Try creating one:

# Continuing from previous
git branch bugfix
# No output, but creates the branch

# List branches
git branch
# Output:
#   bugfix
# * feature
#   master

# Check the new branch reference
cat .git/refs/heads/bugfix
# Output: def4567890abcdef1234567890abcdef1234567 (matches feature's current commit)
Enter fullscreen mode Exit fullscreen mode

This shows bugfix points to the same commit as feature at creation time.

Switching Branches with Checkout

Checkout updates the working directory to match the target branch's commit. Git swaps files in the index and worktree, using the tree-ish (commit) the branch references.

Key point: If conflicts arise, Git aborts; otherwise, HEAD updates to the new branch.

Example of switching:

# From feature branch
echo "Bug fix content" > bug.txt
git add bug.txt
git commit -m "Fix bug on bugfix"  # But wait, we're on feature—switch first

git checkout bugfix
# Output: Switched to branch 'bugfix'

# Now commit on bugfix
echo "Bug fix content" > bug.txt
git add bug.txt
git commit -m "Fix bug"
# Output: [bugfix ghi789] Fix bug

# Switch back
git checkout feature
# Output: Switched to branch 'feature'
# Note: bug.txt is gone from worktree
Enter fullscreen mode Exit fullscreen mode

The worktree reflects the branch's state after checkout.

Merging: Combining Branch Histories

Merging integrates changes from one branch into another. Git finds the common ancestor and applies differences. Fast-forward merges just move the pointer if no divergence; otherwise, it creates a merge commit.

Merge Type Description When It Happens
Fast-Forward Moves target branch to source commit No new commits on target
Three-Way Merge Creates new commit with two parents Divergent histories

Key point: Use --no-ff to force a merge commit even for fast-forwards.

Merge example:

# From previous setup, on master (assuming initial commit)
git checkout master
# Output: Switched to branch 'master'

git merge feature
# Output: Fast-forward (if no divergence)
# Or: Merge made by the 'recursive' strategy. (if three-way)

# For three-way, assume divergence
# First, commit on master
echo "Master update" >> file.txt
git add file.txt
git commit -m "Update on master"
# Output: [master jkl012] Update on master

# Now merge
git merge bugfix
# Output: Auto-merging file.txt (if conflicts, resolve manually)
# Merge commit created if successful
Enter fullscreen mode Exit fullscreen mode

After merge, the target branch includes the source's changes.

Git merge internals: git-scm.com/docs/git-merge

Rebasing: Linearizing Branch Commits

Rebasing replays commits from one branch onto another, rewriting history for a cleaner timeline. It moves the branch base, potentially causing conflicts per commit.

Key point: Avoid rebasing shared branches to prevent history mismatches for collaborators.

Rebase demo:

# Setup: Assume feature diverged from master
git checkout feature
# Output: Switched to branch 'feature'

git rebase master
# Output: Successfully rebased and updated refs/heads/feature.
# Or: Conflicts if changes overlap—resolve and git rebase --continue

# Before rebase, log might show:
git log --oneline --graph
# Output:
# * def456 (feature) Add feature
# * abc123 (master) Initial commit

# After, feature commits sit atop master's latest
Enter fullscreen mode Exit fullscreen mode

This keeps history linear but changes commit hashes.

Detached HEAD: Working Without a Branch

In detached HEAD, HEAD points directly to a commit, not a branch. Commits here aren't referenced by any branch unless you create one.

Key point: Use this for temporary experiments; lost commits can be recovered via reflog if needed.

Example:

# Detach to a commit
git checkout abc123  # Initial commit hash
# Output: Note: switching to 'abc123'.
# You are in 'detached HEAD' state.

# Commit in detached state
echo "Experimental change" >> file.txt
git add file.txt
git commit -m "Experiment"
# Output: [detached HEAD mno345] Experiment

# To save, create branch
git checkout -b experiment
# Output: Switched to a new branch 'experiment'

# Check log
git log --oneline
# Output:
# mno345 (HEAD -> experiment) Experiment
# abc123 Initial commit
Enter fullscreen mode Exit fullscreen mode

Without branching, the commit risks garbage collection.

Remote Branches: Tracking Upstream Changes

Remote branches are read-only references to branches in remote repositories, prefixed like origin/main. They update on fetch; local branches track them for push/pull.

Key point: Set upstream with git branch --set-upstream-to for easier operations.

Remote example:

# Add a remote (assume a cloned repo for simplicity)
# In a new repo:
git remote add origin https://github.com/user/repo.git

# Fetch remotes
git fetch origin
# Output: Fetching updates remote refs

# List remote branches
git branch -r
# Output:
#   origin/master
#   origin/feature

# Track a remote branch locally
git checkout -b local-feature origin/feature
# Output: Branch 'local-feature' set up to track remote branch 'feature' from 'origin'.
# Switched to a new branch 'local-feature'
Enter fullscreen mode Exit fullscreen mode

This syncs local work with remote states.

Understanding these internals helps debug issues like merge conflicts or lost commits. Experiment in a test repo to see how pointers shift—tools like git log --graph visualize the structure. For deeper dives, explore Git's object database where commits and trees store the actual data.

Top comments (0)