Branches scare people for no good reason. Maybe it’s the name, which makes you picture a whole second copy of your project. There’s no copy. A Git branch is a tiny file with a commit hash in it, and once you’ve seen that for yourself, most of the commands stop feeling risky.
So let’s open one up, then work through creating, switching, listing, renaming and deleting, on your machine and on a remote. I ran all of it on Ubuntu 26.04.1 (Git 2.53.0) and RHEL 10.2 (Git 2.52.0). Output matched on both, hashes aside. Git wasn’t on my RHEL box out of the box, so if git --version fails, run sudo dnf install git there or sudo apt install git on Ubuntu. Installing and using Git on Linux covers the rest of the setup.
Want to follow along without touching a real project? This gives you a repo with two commits and a local stand-in for GitHub:
mkdir ~/gitdemo && cd ~/gitdemo
git init --bare -b main remote.git
git init -b main app && cd app
git config user.name "Your Name"
git config user.email you@example.com
echo "# Demo app" > README.md && git add . && git commit -m "Initial commit"
echo "port=8080" > app.conf && git add . && git commit -m "Add app config"
git remote add origin ~/gitdemo/remote.git
git push -u origin main
What is a Git branch?
A branch is a movable name that points at one commit. That’s the whole definition. You commit on it, the name moves forward to the new commit, and every other branch stays where it was. People use them to keep unfinished work away from main until it’s ready.
How Git branches work internally
Have a look inside .git. Two files tell the story:
cat .git/HEAD
ref: refs/heads/main
cat .git/refs/heads/main
003f35fe8008b4f865da27ce0fc4697b70082a2f
HEAD says which branch you’re on, and the branch file holds the commit it points to. Make a new branch and see what Git actually does:
git branch feature/login
cat .git/refs/heads/feature/login
003f35fe8008b4f865da27ce0fc4697b70082a2f
wc -c .git/refs/heads/feature/login
41 .git/refs/heads/feature/login
git log --oneline --all --decorate
003f35f (HEAD -> main, origin/main, feature/login) Add app config
b658c8c Initial commit
41 bytes. A 40-character hash and a newline. No files got copied, which is why creating a branch is instant even in a huge repo. The slash in feature/login just became a folder.
Don’t script against those files, though. After I ran git pack-refs --all, .git/refs/heads was empty and the hashes had moved into .git/packed-refs. git rev-parse main gave the right answer either way, so use that.
Creating a branch with git branch
git branch name creates a branch at your current commit and leaves you where you are. People miss that second part all the time. The star shows you’re still on main:
git branch
feature/login
* main
Add a starting point to branch from somewhere else, then -v shows where each one landed:
git branch hotfix HEAD~1
git branch -v
feature/login 003f35f Add app config
hotfix b658c8c Initial commit
* main 003f35f Add app config
Names can’t contain spaces. Git answers fatal: 'bad name' is not a valid branch name. Slashes are fine, and prefixes like feature/ or fix/ keep long lists readable.
Switching branches with git switch
git switch feature/login
Switched to branch 'feature/login'
cat .git/HEAD
ref: refs/heads/feature/login
Only HEAD changed, plus the files in your folder. Commit something here and look at the history:
git log --oneline --all --decorate
89dd701 (HEAD -> feature/login) Add login setting
b658c8c (hotfix) Initial commit
003f35f (origin/main, main) Add app config
feature/login moved. main didn’t. Switch back and app.conf no longer has the new line, because that version of the file only belongs to the other branch. git switch - jumps to wherever you just were, like cd -.
You will run into this one eventually:
git switch feature/login
error: Your local changes to the following files would be overwritten by checkout:
app.conf
Please commit your changes or stash them before you switch branches.
Aborting
Git is protecting your edit, and nothing was lost. Commit it or stash it. Changes that don’t collide are different. An untracked notes.txt simply came with me when I switched, which surprises people the first time.
Creating and switching branches in one command
Most days you want both steps at once, so -c does both. Give it a second argument to start from a specific branch, whatever you’ve got checked out at the time:
git switch -c feature/search
Switched to a new branch 'feature/search'
git switch -c fix/typo main
Switched to a new branch 'fix/typo'
git branch --show-current
fix/typo
Legacy branch switching with git checkout
You’ll see git checkout in older docs and scripts, and it still works fine for branches. git checkout hotfix switches, and git checkout -b name creates and switches. So why did Git add switch in version 2.23? checkout does too many jobs, and some of them throw away work. Same typo-sized mistake, two commands, with an uncommitted edit in app.conf:
git switch app.conf
fatal: invalid reference: app.conf
git status --short
M app.conf
git checkout app.conf
Updated 1 path from the index
git status --short
My edit is gone. No prompt, and no undo since it was never committed. switch only knows about branches, so it just refused. They differ on old commits too. git switch 22cf923 stops with a branch is expected, got commit and wants --detach, while git checkout 22cf923 drops you straight into detached HEAD. I’d use switch for branches and git restore when you really do want to throw away file changes.
Listing local and remote branches
Plain git branch is local only. -r shows remote-tracking branches and -a shows both:
git branch -a
feature/login
feature/search
fix/typo
hotfix
legacy/demo
* main
remotes/origin/feature/login
remotes/origin/fix/typo
remotes/origin/main
Three more worth knowing:
git branch -vv
feature/login 89dd701 [origin/feature/login] Add login setting
feature/search 003f35f Add app config
fix/typo 1f0cfdb [origin/fix/typo] Fix typo
hotfix b658c8c Initial commit
legacy/demo 003f35f Add app config
* main d8f7c53 [origin/main: ahead 2] Remove docs file
git branch --no-merged
feature/login
fix/typo
git branch --list 'feature/*'
feature/login
feature/search
-vv adds the upstream and how far ahead you are, and --no-merged tells you what would be lost if you got careless with cleanup.
Fresh clones confuse people here. A clone shows only main under git branch, though the rest are sitting in git branch -a. You don’t need to create them by hand. git switch fix/typo built the local branch and connected it to origin/fix/typo for me.
And -r is only as fresh as your last fetch. Someone had deleted a branch on the server here, and my clone still listed it. git ls-remote asks the server directly:
git branch -r
origin/HEAD -> origin/main
origin/feature/sign-in
origin/main
git ls-remote --heads origin
51c074efcddbd1b5d8e9fb2c6df9cef597e119ca refs/heads/main
Renaming a branch
Local is one command. -m renames the current branch, or give it the old and new names to rename from anywhere:
git branch -m feature/sign-in
git branch -m feature/search feature/find
There’s no rename on a remote, so it’s a push and a delete. And check this after the rename, because it’s easy to miss:
git branch -vv
* feature/sign-in 3c4595c [origin/feature/login] Add login
The new name still tracks the old remote branch. Push with -u to fix that, then remove the old one:
git push origin -u feature/sign-in
To /home/asif/gitdemo/remote.git
* [new branch] feature/sign-in -> feature/sign-in
branch 'feature/sign-in' set up to track 'origin/feature/sign-in'.
git push origin --delete feature/login
To /home/asif/gitdemo/remote.git
- [deleted] feature/login
Teammates with the old branch checked out still need to update on their side, so give them a heads-up.
Deleting local branches
-d is the careful one, and it refuses to drop work that isn’t merged:
git branch -d feature/report
error: the branch 'feature/report' is not fully merged
hint: If you are sure you want to delete it, run 'git branch -D feature/report'
-D deletes it anyway. You also can’t delete the branch you’re standing on, so switch away first. Here’s one that caught me out in testing: if the branch has been pushed, -d counts its upstream as merged. It deleted an unmerged branch of mine with just a warning, merged to 'refs/remotes/origin/feature/sign-in', but not yet merged to HEAD. The work was safe on the remote, but don’t treat -d as proof the code reached main.
Deleted the wrong one with -D? The commits hang around for a while, and the reflog can find them:
git branch -D feature/report
Deleted branch feature/report (was 51c074e).
git reflog -n 3
a0e4423 HEAD@{0}: checkout: moving from feature/report to main
51c074e HEAD@{1}: commit: Add report
a0e4423 HEAD@{2}: checkout: moving from main to feature/report
git branch feature/report 51c074e
git log --oneline -n 1 feature/report
51c074e Add report
Git even prints the hash when it deletes. Handy, if you act before it scrolls away.
Deleting remote branches
git push origin --delete feature/sign-in
To /home/asif/gitdemo/remote.git
- [deleted] feature/sign-in
Other clones find out only when they fetch, and even then a plain git fetch keeps the stale name. Add --prune:
git fetch --prune
From /home/asif/gitdemo/remote
- [deleted] (none) -> origin/feature/sign-in
git branch -vv
feature/sign-in 3c4595c [origin/feature/sign-in: gone] Add login
* main 51c074e [origin/main] Add report
That gone marks a local branch whose remote has disappeared. It stays until you remove it yourself, and -d refused here since it hadn’t been merged, so you’ll need -D once you’re sure the work isn’t needed.
Two more things. git branch -d -r origin/fix/typo only removes your local tracking copy, and the next fetch brought it right back. Nothing changes on the server. And the server’s default branch is protected: trying to delete main on my test remote got deletion of the current branch prohibited.
Where to go next
Branching gets more interesting once you start merging. From init to push with branch management carries on from here, and if your remote is GitHub, set up passwordless SSH so you can stop typing credentials on every push.
Frequently asked questions
What is the difference between git switch and git checkout?
git switch only changes branches. git checkout also restores files and checks out old commits, so a small mistake can discard uncommitted work. Both are still supported.
How do I create a new branch and switch to it?
git switch -c branch-name. On older Git, before 2.23, use git checkout -b branch-name.
Does creating a branch copy my files?
No. It writes a small reference to an existing commit. Mine was 41 bytes.
How do I see all branches, including remote ones?
git branch -a. Run git fetch --prune first so the list is current, or use git ls-remote --heads origin to ask the server directly.
How do I rename a branch locally and on the remote?
Run git branch -m new-name, then git push origin -u new-name, then git push origin --delete old-name.
What is the difference between git branch -d and -D?
-d stops if the branch isn’t merged into its upstream, or into HEAD when it has no upstream. -D deletes it regardless.
Can I recover a deleted Git branch?
Usually, if you’re quick. Find the commit in git reflog, then run git branch name hash. Reflog entries and unreachable commits do get cleaned up eventually.
Why does a deleted remote branch still show up in my list?
Your clone keeps a local copy of what it last saw on the server. git fetch --prune removes the stale ones.