Lesson 6 of 15

Branching

A Branch Is Just a Label

Branching sounds like a big operation — surely Git has to copy the whole project? It does not. A branch in Git is a file containing one commit hash, roughly forty bytes on disk. That is the entire implementation. When you commit while on a branch, Git writes the new commit and moves that label forward to point at it.

This is why branching in Git is instant even on projects with a hundred thousand files, and it is why the advice "make a branch for anything you are not sure about" is realistic rather than annoying. Older version control systems made branching expensive, so teams avoided it; Git made it nearly free, and the whole culture of feature branches and pull requests grew out of that.

HEAD is a second label that points at whichever branch you currently have checked out. Switching branches means moving HEAD and then rewriting the files in your working directory to match that branch's commit. Your folder physically changes: files that exist only on the other branch appear, files added on this branch disappear. That can be startling the first time, and it is exactly what should happen.

The practical framing for a college project: main holds the version that works and that you would be willing to demonstrate. Everything experimental happens on a branch. If the experiment fails, you delete the branch and main is untouched — no backup folders, no undoing by hand.

Example
# List local branches; * marks the current one
git branch
#   feature/cart
# * main

# Which branch am I on, as plain text?
git branch --show-current

# Create a branch (does NOT switch to it)
git branch feature/login

# Switch to it
git switch feature/login

# Create and switch in one step — what you will use most
git switch -c feature/login

# The older spelling, which you will see everywhere and which still works
git checkout feature/login
git checkout -b feature/login
Notes
  • git switch and git restore were added to split up the jobs that git checkout used to do alone. checkout still works and is not going away, so tutorials using it are not wrong. Prefer switch for branches and restore for files: one checkout command that can either move you to a branch or silently delete your uncommitted edits is a genuinely bad interface to learn on.

A Real Branch Workflow

Here is the cycle you will repeat for the rest of your career, using a group web project as the example. Three of you are building a college fest website. You have taken the navbar.

Start from an up-to-date main: switch to it and pull, so that your branch begins from the same place as everyone else's work. Then create your branch and do the work there, committing as you go — several small commits, not one giant one at the end. When it is finished and tested, switch back to main, pull again in case teammates merged things while you were working, and merge your branch in. Finally delete the branch, because it has served its purpose and a list of forty stale branches helps nobody.

The one step people skip is pulling before they branch. Branch from a stale main and you are building on a version of the project that no longer exists, which guarantees a bigger, messier conflict when you merge. Ten seconds of git pull at the start prevents it.

Note also that switching branches with uncommitted changes may fail. Git refuses when the switch would overwrite an edit you have not saved anywhere — it is protecting you. The answer is to commit the work, or stash it if it is not commit-worthy yet. If your changes touch files that are identical on both branches, Git carries them across with you without complaint, which is convenient but occasionally confusing.

Example
# 1. Start from an up-to-date main
git switch main
git pull

# 2. Branch for your task
git switch -c feature/navbar

# 3. Work, committing in small pieces
git add index.html css/nav.css
git commit -m "Add navbar markup and base styles"
git add css/nav.css
git commit -m "Make navbar collapse below 768px"

# 4. Bring it back into main
git switch main
git pull
git merge feature/navbar

# 5. Clean up
git branch -d feature/navbar

# Switching with uncommitted work that would be overwritten:
# error: Your local changes to the following files would be overwritten
#        by checkout: css/style.css
# Fix it by committing, or:
git stash
git switch main
git stash pop
Notes
  • git switch - jumps back to the branch you were on previously, the same way cd - works in a terminal. When you are bouncing between a feature branch and main, it saves a lot of typing.

Detached HEAD: What It Means and How to Get Out

Sooner or later you will run something like git checkout a81c9f2 to look at an old version, and Git will print a long paragraph containing the words "You are in 'detached HEAD' state". It reads like an error. It is not, and understanding it takes about a minute.

Normally HEAD points at a branch, and the branch points at a commit. Detached HEAD means HEAD is pointing straight at a commit with no branch in between. You are standing on a specific snapshot rather than on a branch. Looking around is perfectly safe — the files are all there, you can run the project, read the code, run git log.

The danger appears only if you commit while detached. Those commits are real, but no branch label points at them. The moment you switch back to main, nothing references them any more, and they become unreachable — Git will eventually garbage-collect them. This is how people genuinely lose an afternoon's work: they detach, forget, work, switch away, and the commits appear to vanish.

Getting out is easy, and there are only two cases. If you made no commits, just git switch main (or git switch -) and you are back to normal. If you did commit and want to keep the work, run git switch -c some-branch-name before leaving — this creates a branch pointing at where you are, and your commits are now safely attached. If you have already switched away and lost the hash, do not panic: git reflog lists where HEAD has been, and the Stash & Reset lesson shows how to use it to recover exactly this situation.

Example
# How you get there
git checkout a81c9f2          # or: git switch --detach a81c9f2
# Note: switching to 'a81c9f2'.
# You are in 'detached HEAD' state. You can look around, make experimental
# changes and commit them...

git branch --show-current     # prints nothing — you are on no branch
git status
# HEAD detached at a81c9f2

# Way out A: you changed nothing
git switch main

# Way out B: you committed and want to keep it — do this BEFORE leaving
git switch -c experiment/old-layout
# your commits now live on a real branch

# Way out C: you already left and think the work is gone
git reflog                    # find the hash in the list
git switch -c rescued a81c9f2
Notes
  • You will also land in detached HEAD during an interactive rebase or a git bisect run. In those cases it is completely expected — the tool is stepping you through individual commits — and finishing the operation puts HEAD back on a branch by itself.

Deleting and Renaming Branches

git branch -d feature/navbar deletes a branch, and the lowercase -d is the safe form: Git refuses if the branch contains commits that have not been merged anywhere, telling you the branch is not fully merged. That refusal is a feature. It is Git noticing that you are about to throw away work and asking you to confirm you meant it.

-D (capital) forces the deletion regardless. Use it when you genuinely want to abandon an experiment. Even then the commits are not immediately destroyed — they linger unreferenced for some weeks and git reflog can still find them — but you should treat -D as meaning "I am done with this".

Deleting a local branch does nothing to the copy on GitHub. If you pushed the branch, it is still on the server until you delete it there too, either with git push origin --delete or by clicking the button GitHub offers after merging a pull request. Similarly, when a teammate deletes a branch on GitHub, your local list still shows a stale origin/ entry until you run git fetch --prune.

Renaming uses -m. Renaming the branch you are currently on needs only the new name. This is also how an older repository moves from master to main, though on a shared project that also requires pushing the new name, updating the default branch in the GitHub settings, and telling your teammates — so it is not a change to make casually mid-project.

Example
# Safe delete — refuses if the branch has unmerged commits
git branch -d feature/navbar
# error: The branch 'feature/navbar' is not fully merged.

# Force delete — abandon the work deliberately
git branch -D feature/navbar

# Delete the copy on GitHub as well
git push origin --delete feature/navbar

# Clear out stale origin/* entries for branches deleted by others
git fetch --prune

# Rename
git branch -m old-name new-name
git branch -m new-name            # renames the branch you are on

# See every branch, including remote ones, with tracking info
git branch -a
git branch -vv
Notes
  • git branch -a is the command to run when a teammate says "it's pushed" and you cannot find it. Branches on the server appear as remotes/origin/whatever, and running git switch whatever creates a matching local branch that follows it.

Naming Branches So Your Team Can Read Them

Branch names are free-form, so a project with no convention ends up with test, test2, ananya, newbranch and final, and nobody can tell what any of them contain. A shared convention costs nothing and makes git branch -a readable at a glance.

The common pattern is a category, a slash, and a short hyphenated description of the work: feature/user-login, bugfix/cart-total-wrong. If your team tracks issues, putting the number in helps a great deal — feature/42-user-login lets anyone jump from the branch to the discussion behind it.

Keep names lowercase and use hyphens rather than spaces or underscores. Spaces in a branch name technically require quoting everywhere and will make life miserable. Also avoid naming a branch the same as a file in the project, since some commands then need -- to disambiguate.

  • feature/add-login — new functionality
  • bugfix/cart-total-wrong — fixing something broken
  • hotfix/payment-timeout — an urgent fix going straight to production
  • chore/update-dependencies — maintenance with no user-visible change
  • docs/setup-instructions — documentation only
  • experiment/dark-mode — something you may well throw away
Notes
  • Keep branches short-lived. A branch that lives for three weeks drifts far from main, and the merge at the end becomes a long afternoon of conflicts. Merging every day or two keeps each merge trivial, and is the single most effective thing a student team can do to avoid conflict pain.
Ask AI