Branches: work on many things without breaking main
What a branch really is (a movable pointer to a commit), how to create, rename, switch and delete branches, carry uncommitted work safely, and merge a finished branch back into main.
By the end of this lesson you'll be able to
0 of 6 completeA branch is a pointer
Most people imagine a branch as a copy of the code. It isn't. A branch is a tiny file holding one commit ID. Commits point back to their parents, and a branch just marks the newest one in a line of work:
$ cat .git/HEAD # where you are: a branch ref: refs/heads/main $ cat .git/refs/heads/main # what main is: one commit ID 9c53c908c97e192de201aed90f9063ca0ba0ade9
cat .git/HEADcatprint a file
That's the whole trick. Creating a branch writes 41 bytes, so it's instant even in a huge repository. Make them freely: one per feature, fix or experiment.
Create, list and rename
$ git switch -c feature/coupons # create a branch here, and switch to it Switched to a new branch 'feature/coupons' $ git branch # * marks the current branch * feature/coupons main $ git branch -m feature/coupon-codes # rename the current branch $ git branch -v # with each branch's last commit * feature/coupon-codes 0438c28 Add an empty coupon table main dad9c23 Add the app entry point $ git branch hotfix main # create from another starting point, without switching
git switch -c feature/couponsgitversion control
Naming branches
Use a short type, a slash and a description in lowercase with hyphens. Teams and CI tools rely on
these prefixes (a pipeline may deploy every release/* branch, for example):
| Prefix | For | Example |
|---|---|---|
feature/ | new functionality | feature/coupon-codes |
fix/ | bug fixes | fix/empty-cart-crash |
chore/ | tooling, dependencies | chore/upgrade-node-22 |
docs/ | documentation | docs/setup-guide |
release/, hotfix/ | release processes | hotfix/1.4.1 |
Add the issue number when you have one: fix/142-empty-cart.
Switching branches
$ git switch main # go to an existing branch $ git switch - # back to the previous branch $ git checkout main # the older command; does the same here
git switch maingitversion control
git switch --the previous directory
Switching changes the files in your working folder to match that branch. coupons.py exists on
feature/coupon-codes and disappears when you switch to main; it comes back when you switch
again. Nothing is lost: it's in the commit.
Uncommitted changes travel with you when they don't conflict with the other branch. If they would be overwritten, Git refuses to switch:
error: Your local changes to the following files would be overwritten by checkout: app.py Please commit your changes or stash them before you switch branches.
Commit them, or put them aside with git stash (the Undoing changes lesson). git switch is the
modern command for branches only. git checkout also restores files, which is why newer Git split
it into switch and restore.
Merging a finished branch
When work on a branch is done, you merge it into main. There are two outcomes:
$ git switch main $ git merge feature/coupon-codes Updating dad9c23..0438c28 Fast-forward coupons.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 coupons.py
git switch maingitversion control
Fast-forward: main had no new commits since the branch started, so Git just moved main forward
to the branch's commit. No new commit is needed.
When both branches have new commits, Git combines them in a merge commit, which has two parents:
$ git merge fix/readme Merge made by the 'ort' strategy. README.md | 1 + 1 file changed, 1 insertion(+) $ git log --oneline --graph * 9c53c90 Merge branch 'fix/readme' |\ | * 26df073 Document how to run the app * | 032e8d2 Add VERSION file |/ * 0438c28 Add an empty coupon table * dad9c23 Add the app entry point
git merge fix/readmegitversion control
git log --oneline --graphloghistory
If both branches changed the same lines, Git stops and asks you to resolve a conflict. The Merge strategies lesson covers conflicts, rebasing and squashing in detail.
Deleting branches
A merged branch has done its job. Delete it so git branch stays readable:
$ git branch -d feature/coupon-codes # safe: only if merged Deleted branch feature/coupon-codes (was 0438c28). $ git branch -d spike error: the branch 'spike' is not fully merged $ git branch -D spike # force: you're sure the work isn't needed Deleted branch spike (was 1bae2b4). $ git branch --merged main # branches already merged into main: safe to delete
git branch -d feature/coupon-codesgitversion control
Deleting a branch deletes the pointer, not the commits. For a while afterwards the commits can still
be recovered with git reflog, using the ID Git printed ("was 1bae2b4").
Choose a branching strategy deliberately
Trunk-based development keeps branches short and integrates frequently, which lowers merge drift and works well with continuous delivery. A release branch isolates stabilisation when several versions must be supported, but it adds cherry-picks and coordination. GitFlow is heavier still; choose it only when the release process genuinely needs long-lived integration and release branches.
$ git log --oneline --graph --decorate --all $ git branch --merged main $ git switch -c feature/short-lived-change
git log --oneline --graph --decorate --allgitversion controlloghistory
Ship a feature and a fix in parallel
While building a coupon feature on its own branch, a README fix is needed urgently. Do the fix on a second branch, merge it first, then finish and merge the feature. End with a clean main, both branches deleted, and a history graph that shows what happened.
main with both changes; git log --graph showing the mergeReveal one safe solution
$ git switch -c feature/coupons $ echo 'COUPONS = {"WELCOME10": 10}' > coupons.py $ git add coupons.py && git commit -m "Add the coupon table" $ git switch main && git switch -c fix/readme # urgent fix, from main $ echo "Run: python app.py" >> README.md $ git commit -am "Document how to run the app" $ git switch main && git merge fix/readme # fast-forward $ git switch feature/coupons $ echo 'def discount(code): return COUPONS.get(code, 0)' >> coupons.py $ git commit -am "Look up coupon discounts" $ git switch main && git merge --no-edit feature/coupons # merge commit $ git branch -d fix/readme feature/coupons $ git log --oneline --graph * 70b46cf Merge branch 'feature/coupons' |\ | * ebe3728 Look up coupon discounts | * 63892d4 Add the coupon table * | f0952f8 Document how to run the app |/ * dad9c23 Add the app entry point
git switch -c feature/couponsgitversion control
echo 'COUPONS = {"WELCOME10": 10}' > coupons.pyechoprint text>write output to a file, replacing it
git add coupons.py && git commit -m "Add the coupon table"addstage changes&&run the next command only if this one workedcommitsave staged changes
echo "Run: python app.py" >> README.md>>append output to a file
git log --oneline --graphloghistory
git commit -am stages every tracked file that changed and commits it; it never adds new files. The
fix merged as a fast-forward because main hadn't moved; the feature needed a merge commit because
main had the fix by then.
Try it yourself
0 of 6 steps doneRun each task in your own account or terminal, then tick it off.
Quick check
What is a Git branch, technically?
Official reading for this lesson
Or get every quiz answer right and it completes itself.