Git Branches: Create, Switch, Stash and Delete
How Git branches work: create and switch with git switch, carry or stash uncommitted work, detached HEAD, list, rename and delete branches safely.
A branch lets you work on a change without touching the version everyone else uses. You start a branch, commit to it as often as you like, and merge it back when it is ready (Part 5), or delete it if the idea did not work out. Branches in Git are cheap and fast because of how they are stored, and once you have seen that, the rest of this part (switching, stashing, detached HEAD) follows from it.
Prerequisites
- The
status-pagerepository from Part 3, or any repository with a few commits.
A Branch Is a Pointer to a Commit
That is all a branch is: a file in .git/refs/heads/ that contains the hash of one commit. HEAD names the branch you are on. When you commit, Git writes the new commit with the current one as its parent, then updates the branch file to the new hash. HEAD follows because it points to the branch, not to a commit.
Creating a branch writes one more small file, whatever the size of the project. (Git also packs refs into .git/packed-refs from time to time, and Git 3.0's reftable format stores them differently; git rev-parse main works with every format, so use it in scripts instead of reading files.)
Create a Branch and Switch to It
The next change is a dark color scheme. Start a branch for it:
git switch -c <name> creates the branch at the current commit and switches to it. git branch <name> only creates it, and git switch <name> switches to a branch that exists. The older git checkout -b <name> does the same as git switch -c.
Add the dark scheme at the end of style.css and commit it:
@media (prefers-color-scheme: dark) {
body {
background: #111;
color: #eee;
}
a {
color: #8ab4f8;
}
}
feature/dark-mode moved to the new commit; main stayed where it was. Switch back to main and the change is gone from the working tree, because Git replaced the files with main's snapshot:
Uncommitted Changes and Switching
You are on the branch, halfway through the next change, a muted footer color as the first line of style.css:
footer {
color: #777;
}
Then someone reports a typo in the README on main. Try to switch:
style.css is different on main and on this branch, so switching would overwrite your edit. Git refuses rather than losing it. When the files you changed are the same on both branches, Git switches and brings the changes along (you see an M line); that is also how you move work you started on the wrong branch: git switch -c new-branch keeps your uncommitted changes (more on that).
Here you have three choices: commit the half-done work, throw it away with git restore, or put it aside with git stash.
Put work aside with git stash
git stash saves your uncommitted changes (staged and unstaged) on a stack and resets the working tree to the last commit:
git stash pop applies the newest stash and removes it from the stack. If applying it causes a conflict, Git keeps the stash so you do not lose it; resolve the conflict, then run git stash drop.
| Command | What it does |
|---|---|
git stash push -m "msg" |
Stash tracked changes with a description |
git stash push -u |
Also stash untracked files (-a adds ignored files too) |
git stash push -- <path> |
Stash only some files (stash a single file) |
git stash list, git stash show -p stash@{1} |
List stashes; show one as a diff |
git stash pop, git stash apply |
Apply the newest stash and drop it; apply but keep it |
git stash branch <name> |
Create a branch at the commit the stash was made on, and apply it there |
git stash drop stash@{1}, git stash clear |
Delete one stash; delete all of them |
Stashes are easy to forget. Prefer a commit on the branch (you can tidy it up later, Part 8) for anything you will not come back to within the hour. Git 2.51 added git stash export and git stash import, which turn stashes into a ref you can push and fetch, for moving them to another machine.
See the Branches as a Graph
Both branches now have commits the other does not have:
Read the graph from the top: each * is a commit, and the lines show parents. main has the README fix; feature/dark-mode has two commits on top of "Add the web-2 service", the commit where the branch started. Part 5 merges them. Many editors and tools draw this graph with colors; the Git Concepts simulator lets you build one step by step.
Detached HEAD: Looking at an Old Commit
You can switch to any commit or tag, not only to a branch. Git then puts you in detached HEAD state, which means HEAD points straight at a commit:
In detached HEAD you can look around, build and run old versions, and even commit. But new commits are on no branch: when you switch away, nothing points to them, and Git deletes them during a later cleanup (the reflog keeps them reachable for 30 days by default, Part 8). If you want to keep what you did, create a branch right there with git switch -c <name>. git switch - returns to the branch you were on before. (git checkout <commit> also detaches HEAD, without needing --detach, which is why people end up there by accident.)
List, Rename and Delete Branches
git branch -v adds each branch's last commit; -vv also shows the remote branch it tracks (Part 6). --merged and --no-merged answer "is this branch's work in main yet?", and --sort=-committerdate puts the most recently used branches first. Get the current branch name covers more ways to read it in scripts.
To try an idea quickly, start a branch, commit, and see how deleting works:
git branch -m old newrenames a branch (-m newrenames the current one). Renaming a branch that is already pushed needs one more step on the remote.git branch -ddeletes a branch only if its commits are merged: into its upstream if it has one (Part 6), otherwise into your current branch. That check has saved a lot of work.git branch -Ddeletes it anyway. The commits are not gone yet: Git printed the hash (was ...), and the reflog has it too, so you can recreate the branch withgit branch experiment/search <hash>(Part 8).
Deleting a branch on the remote is a separate command (Part 6; delete a branch locally and remotely).
Name Branches So Others Understand Them
A branch name is a path-like name: feature/dark-mode is stored as refs/heads/feature/dark-mode. Most teams use a prefix for the kind of work, and many add a ticket number:
| Prefix | For | Example |
|---|---|---|
feature/ |
New functionality | feature/dark-mode |
fix/ |
Bug fixes | fix/123-broken-footer-link |
chore/, docs/ |
Maintenance, documentation | docs/contributing-guide |
release/, hotfix/ |
Release management (Part 7) | release/1.4 |
Names cannot contain spaces, .., ~, ^, : or a few other characters, and a branch feature cannot exist next to feature/dark-mode (one would have to be a file and a directory at once). git check-ref-format tells you whether a name is valid:
Common Mistakes
- Working on
mainby accident. Checkgit status(it starts with "On branch ...") or show the branch in your shell prompt. If you have not committed yet,git switch -c new-branchmoves the work over; if you have, see move commits to a new branch. - Committing in detached HEAD and switching away. Create a branch before you leave, or recover the commit from the reflog.
- Long-lived branches. The longer a branch lives, the more
mainmoves away from it and the harder the merge. Merge small pieces often. - Using
-Dout of habit.-drefuses to delete unmerged work; read its message before you force it. - Forgotten stashes.
git stash listonce in a while; a stash is not a backup and is never pushed.
Summary
- A branch is a file with a commit hash;
HEADsays which branch you are on. git switch -ccreates and switches,git switchswitches; your files change to the branch's snapshot.- Git refuses to switch when it would overwrite uncommitted changes; commit, restore or
git stashthem first. - Detached HEAD means
HEADpoints at a commit; create a branch before committing there. git branch -v,--merged,-mand-dlist, check, rename and delete branches.
Next: Merging and Resolving Conflicts, where the dark mode branch goes into main.
Keep Learning
Tags
Found an issue?