Branches
branch, switch, checkout. The feature-branch workflow, short and visual.
Why
You're fixing a bug while a coworker builds a new feature in the same repository. If you both
commit to main, the half-finished feature ships to production along with your fix. Branches
solve this: every task gets its own line of commits, and it only reaches main after review.
What a branch is
A branch is a named pointer to a commit. Inside the repository it's a record shaped like
"branch name → commit hash". In the traditional storage format that's a file,
.git/refs/heads/<name>, holding one 40-character hash:
cat .git/refs/heads/main
2acb8ed9d5faa57a03f063d84ebbd0f2efd00e68
The file might not exist. Git sometimes packs refs into a single file, .git/packed-refs (for
example after git gc), and a repository using the newer reftable format stores them in a
.git/reftable/ folder. The meaning doesn't change: a branch is still a "name → commit" record.
When you commit, the current branch's pointer moves to the new commit automatically. When you
switch to another branch, HEAD starts pointing at it, and the files in your working folder
update to match its latest commit.
This gives branches their main property: they're cheap. Creating one just means writing a single hash; no copy of the project gets made. That's why you open a branch per task and delete it after merging.
Commands
git branch # list local branches, * marks the current one
git branch feature/login # create a branch at the current commit (HEAD), stay where you are
git switch feature/login # switch to the branch
git switch -c feature/login # create and switch to it right away
git branch -d feature/login # delete, if its commits are already merged
git branch -D feature/login # delete without checking
git branch -d refuses to delete a branch whose commits aren't merged anywhere yet, and
suggests -D:
error: the branch 'feature/login' is not fully merged
hint: If you are sure you want to delete it, run 'git branch -D feature/login'
This protects you from losing work. -D deletes the branch silently, so check first that you
don't need its commits.
Before Git 2.23, you switched with git checkout. The command still works, but 2.23 split its
responsibilities: git switch switches branches, git restore restores files. You'll see
checkout constantly in other people's tutorials. Write switch yourself: it's less likely to
accidentally overwrite files.
To create a branch from a specific commit instead of the current one:
git switch -c hotfix abc1234
In place of abc1234 you put a hash (the first 7 characters are enough) or a tag name.
HEAD: where you are
HEAD is a pointer to where you currently are. Usually it points at a branch, and through it at
that branch's latest commit. In git log output this looks like HEAD -> main.
Sometimes you get detached HEAD: HEAD points straight at a commit instead of a branch. This
happens when you want to look at an old version or a tag. git switch makes you say so
explicitly:
git switch --detach v1.0
git checkout v1.0 detaches HEAD without asking.
You can commit in detached HEAD, but the new commit doesn't belong to any branch. Switch back,
and it's gone from git log --all. Git warns you about this when you switch and shows the hash
of the abandoned commit:
Warning: you are leaving 1 commit behind, not connected to
any of your branches:
1a75b2a detached-work
If you've done work in detached HEAD and haven't left yet, create a branch right there:
git switch -c rescue. The commits will stay on it.
If you've already left, the commit isn't gone yet. Git keeps a log of HEAD movements, the
reflog. Every line in it is a commit HEAD has visited:
git reflog
2acb8ed HEAD@{0}: checkout: moving from 1a75b2a... to main
1a75b2a HEAD@{1}: commit: detached-work
2acb8ed HEAD@{2}: checkout: moving from main to HEAD
Once you've found the hash you need, point a branch at it: git branch <name> <hash>. The
reflog only lives in your local repository. Entries for commits no branch can reach are kept for
30 days by default (gc.reflogExpireUnreachable), the rest for 90 (gc.reflogExpire). Once an
entry is gone, git gc can erase the commit itself too.
Feature-branch workflow
This is how almost any team works:
- From a fresh
main, create a branch for the task:git switch -c feature/payments-retry. - Work on it. Keep commits small, each about one change.
- Push the branch to the remote repository:
git push -u origin feature/payments-retry. - Open a Merge Request in GitLab or a Pull Request in GitHub. This is a request to merge the
branch into
main: your teammates review the changes, CI runs checks, you make fixes. - Once it's approved, the branch gets merged into
mainand deleted.
The -u flag (long form --set-upstream) on the first push records the link between the local
branch and the branch on the remote. Git responds like this:
branch 'feature/payments-retry' set up to track 'origin/feature/payments-retry'.
After that, plain git push and git pull are enough, no arguments needed. The link shows up
in git branch -vv, in square brackets:
* feature/payments-retry 2acb8ed [origin/feature/payments-retry] one
main 2acb8ed one
Why main gets protected
Usually main is the source of truth for deployment: CI/CD runs on push to main. That's why
team repositories follow rules like these:
- direct push to
mainis forbidden; changes land only through an MR/PR; - an MR doesn't get merged until CI is green and a reviewer has approved;
- force-push to
mainis banned entirely.
In GitLab this is set up under Settings → Repository → Branch rules (older versions called this section Protected branches). GitHub has branch protection rules for this, plus the newer rulesets: the Rulesets section in repository settings.
Deleting a branch on the remote
git push origin --delete feature/payments-retry
More often the branch gets deleted through the UI after merging. GitLab has a "Delete source branch" checkbox in the MR; GitHub has a "Delete branch" button after the merge.
Common mistakes
- You committed to
maininstead of a branch. Before you start working, checkgit branch: the asterisk should be on your branch. - You created a branch with
git branch nameand assumed you were already on it. That command doesn't switch. You needgit switch name, orgit switch -c nameto do both at once. - First push without
-u. The branch appears on the remote, but a plaingit pushnext time won't know where to send it. Fix it by pushing again withgit push -u origin <branch>. - You left detached HEAD with commits on it. They aren't deleted; you can find them in
git refloguntil the entry expires. - Using
git branch -Djust in case. If the branch's commits aren't merged, after-Dthe only way to find them again is through the reflog.
What you'll need for the tasks
git branch,git branch -vv,git branch <name> <commit>git switch <branch>,git switch -c <branch>,git switch --detach <commit>git add,git commit -m(from the previous lesson)git push -u origin <branch>git log --oneline --all --graph,git reflog- flag
-C <folder>: run a Git command against a repository from a different folder
In the next lesson we'll cover how to merge a branch back: merge or rebase.
Interview-style questions
Interview questions on this topic: 4
Included with practice access, $49 one-time. Get access
Answer out loud or in writing, then reveal the answer and honestly mark whether you knew it.
Done reading?
Mark the lesson as read to move your track progress forward.
Lesson marked as done.
Go through it with an AI agent
The agent explains anything unclear in plain words, gives you 2 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.
Tell the agent: use MCP izi, give me lesson git-basics-03-branches
Progress saved in this browser
Clear your history or open this on your phone, and the marks will be gone. Leave your email and they'll move to your account. No password needed.
Done. moved to your account. You can see your progress on any device. My progress