What a branch is
So far, every commit has gone onto one line of history. Real work rarely stays that tidy. You may want to try a new recipe without disturbing the ones that work, or fix a typo while a bigger change is half done. Branches let you keep separate lines of work side by side, and combine them when they are ready.
In Git, a branch is simply a movable label that points at a commit. Creating one copies nothing: Git writes down the new name and the commit it points at, and that is all. This is why branches are cheap and fast, and why it is normal to create one for every idea, however small, and delete it afterwards.
main is an ordinary branch. It has no special powers; it is just the branch a repository starts with, and the one most projects treat as the main line.
The label is what makes a branch move. When you commit, Git makes the new commit and moves the label of the branch you are on forward to it. The other branches stay where they were. So each branch grows its own line of commits, and they all share the history from before they split.
Branches are like bookmarks in a book rather than photocopies of it. A bookmark costs nothing, you can have several, and you can move each one on as you read. The pages themselves exist only once.
Most work with branches follows the same short cycle, which this module walks through:
- Create a branch for one piece of work, and switch to it.
- Commit on it as often as you like.
mainis not affected. - When the work is ready, switch back to
mainand merge the branch into it. - Delete the branch, which has done its job.
Creating and switching branches
Two commands do most of the work:
git branchlists your branches, with an asterisk (*) next to the one you are on.git branch <name>creates a new branch at the commit you are on, but does not switch to it.git switch <name>moves you onto a branch. It updates the files in your working tree to match the branch’s newest commit.
The sessions in this module start from a small recipes repository with two commits, a README and a pancake recipe. Your own repository has more commits and different hashes, which changes nothing about the commands. In this session, you create a branch for desserts and add a cake recipe on it.
git branch
* main
git branch desserts
git branch
desserts
* main
* 288d56b (HEAD -> main, desserts) Add a pancake recipe * 02804ba Add a README
git switch desserts
Switched to branch 'desserts'
cake.md so that it contains:# Lemon cake
- 200 g flour
- 2 lemons
git add cake.md
git commit -m "Add a lemon cake recipe"
[desserts ccf2a69] Add a lemon cake recipe
1 file changed, 4 insertions(+)
create mode 100644 cake.md
* ccf2a69 (HEAD -> desserts) Add a lemon cake recipe * 288d56b (main) Add a pancake recipe * 02804ba Add a README
git switch main
Switched to branch 'main'
ls
README.md pancakes.md
git switch desserts
Switched to branch 'desserts'
ls
README.md cake.md pancakes.md
Right after git branch desserts, the list shows two branches, and the asterisk shows that you are still on main. The first graph shows why creating the branch was instant: desserts and main are two labels on the same commit, 288d56b.
The graphs on these pages show commits as circles, newest at the top, with a line from each commit down to its parent. Labels name the branches that point at each commit, and HEAD → main marks the branch you are on. In a terminal, git log --oneline --graph --all draws the same picture in text.
After git switch desserts and a commit, the second graph shows that only the desserts label moved, to the new commit ccf2a69. main stays on 288d56b.
Now look at the two ls commands. On main, the folder holds only README.md and pancakes.md; on desserts, cake.md is back. Switching branches changes the files in your folder to match the branch you switch to. The cake recipe was never lost: it lives in the desserts commit, and Git puts it back whenever you switch to that branch.
HEAD
You have seen HEAD in Module 3 and in the graphs above. HEAD is Git’s name for “where you are now”. Normally it points at a branch, and the branch points at a commit: in the graphs, HEAD → desserts means that you are on desserts, and that desserts points at that commit.
This two-step arrangement explains the two kinds of movement you have seen:
- When you commit, Git moves the branch that HEAD points at to the new commit. HEAD goes along, because it points at the branch.
- When you switch, Git moves HEAD itself to another branch, and changes your files to match. No branch label moves.
git status reports where HEAD is in its first line: On branch main means that HEAD points at main.
The detached HEAD from Module 3 fits the same picture. After git switch --detach, HEAD points straight at a commit instead of at a branch, so a new commit there moves no branch label. That is why such commits are easy to lose, and why you create a branch before you switch away from them.
Fast-forward merges
Sooner or later you want the work from one branch in another. git merge <branch> brings the named branch’s work into the branch you are on. So first switch to the branch that should receive the work, usually main, and then merge.
How Git merges depends on how the two branches relate. The simplest case is when the other branch contains everything on your branch, plus some newer commits. Then there is nothing to combine: Git simply moves your branch’s label forward to the other branch’s newest commit. This is called a fast-forward, and it makes no new commit.
In the session, you are on main, and desserts is one commit ahead with a cake recipe, as the first graph shows. To follow along, run git switch main first.
* 4d632df (desserts) Add a lemon cake recipe * 288d56b (HEAD -> main) Add a pancake recipe * 02804ba Add a README
git merge desserts
Updating 288d56b..4d632df
Fast-forward
cake.md | 1 +
1 file changed, 1 insertion(+)
create mode 100644 cake.md
* 4d632df (HEAD -> main, desserts) Add a lemon cake recipe * 288d56b Add a pancake recipe * 02804ba Add a README
git branch -d desserts
Deleted branch desserts (was 4d632df).
git branch
* main
Git reports Updating 288d56b..4d632df and Fast-forward: it moved main from 288d56b to 4d632df, desserts’ newest commit. The summary below lists what the merge brought in, the new file cake.md. Compare the graphs before and after. The commits did not change; only the main label jumped forward, so that main and desserts now point at the same commit.
The name fast-forward describes what happened: main was behind, and Git wound it forward along commits that already existed, the way you would fast-forward a recording.
With its work merged, the desserts branch has done its job. git branch -d desserts deletes it, and Git reports which commit the label was on (was 4d632df). Only the label goes. The commits stay, because main contains them, and git branch now lists only main.
Git refuses -d for a branch whose commits are not contained in the branch you are on, to protect work that would otherwise be on no branch at all. Exercise 5 shows what to do then.
Three-way merges
Often both branches move on after they split. You add a soup recipe on a soups branch while someone adds salt to the pancakes on main. Now neither branch contains the other, and Git cannot just move a label. Instead it makes a merge commit: a new commit that combines the work of both branches. Unlike every commit so far, it has two parents, one on each branch.
The session starts from the first graph: soups has a tomato soup commit, and main has a salt commit that soups lacks. To follow along, set that up yourself: create soups, commit a soup recipe on it, switch back to main, and commit a change to pancakes.md. Then merge.
A merge commit needs a message. On your computer, Git opens your editor with a ready-made one, Merge branch 'soups'. Save and close the editor to accept it; the session keeps it as it is.
* 5049848 (HEAD -> main) Add salt to the pancakes | * 689ed1b (soups) Add a tomato soup recipe |/ * 288d56b Add a pancake recipe * 02804ba Add a README
git merge soups
Merge made by the 'ort' strategy.
soup.md | 1 +
1 file changed, 1 insertion(+)
create mode 100644 soup.md
* e59bfd4 (HEAD -> main) Merge branch 'soups' |\ | * 689ed1b (soups) Add a tomato soup recipe * | 5049848 Add salt to the pancakes |/ * 288d56b Add a pancake recipe * 02804ba Add a README
git log --oneline --graph
* e59bfd4 Merge branch 'soups'
|\
| * 689ed1b Add a tomato soup recipe
* | 5049848 Add salt to the pancakes
|/
* 288d56b Add a pancake recipe
* 02804ba Add a README
Compare the two graphs. Before the merge, the branches have split after 288d56b: main went one way, soups the other. After it, the merge commit e59bfd4 joins them, with lines down to both of its parents, 5049848 on main and 689ed1b on soups. main moved to the merge commit; soups stayed where it was, and could now be deleted with git branch -d soups.
Merge made by the 'ort' strategy. names the method Git used to combine the changes. You do not need to know more about it. Versions of Git older than 2.34 used a method called recursive by default, and print that name instead.
git log --oneline --graph draws the same shape in text. The merge commit is at the top, and the |\ and |/ lines show the history splitting into two lines and joining again, with each branch’s commit on its own line.
This is called a three-way merge, because Git compares three versions of the project: the newest commit on each branch, and the commit where the branches split. Whatever changed on only one side since the split is taken from that side. When both sides changed the same lines, Git cannot decide alone. That is a conflict, the subject of Module 5.
A merge never changes existing commits. If your branch has nothing the other lacks, Git fast-forwards it: the label moves and no commit is made. If both branches have new commits, Git adds a merge commit with two parents. Either way, every commit from both branches ends up in your branch’s history.
Tidying up branches
A few more commands make branches quicker to use:
git switch -c <name>creates a branch and switches to it in one step. It is the usual way to start new work.git branch -vlists each branch with its newest commit.git switch -goes back to the branch you were on before.git branch -m <old> <new>renames a branch.
In the session, you start a breakfast branch, then decide it should be called brunch.
git switch -c breakfast
Switched to a new branch 'breakfast'
porridge.md so that it contains:# Porridge
git add porridge.md
git commit -m "Add a porridge recipe"
[breakfast 5ad8c30] Add a porridge recipe
1 file changed, 1 insertion(+)
create mode 100644 porridge.md
git branch -v
* breakfast 5ad8c30 Add a porridge recipe
main 288d56b Add a pancake recipe
git switch -
Switched to branch 'main'
git branch -m breakfast brunch
git branch -v
brunch 5ad8c30 Add a porridge recipe
* main 288d56b Add a pancake recipe
git branch -v shows the porridge commit 5ad8c30 on the new branch, and main still on 288d56b. After git switch - takes you back to main, the rename changes only the name: brunch points at the same commit as breakfast did.
Older guides use git checkout -b <name> to create a branch and switch to it, and git checkout <name> to switch. They still work; git switch does the same jobs and is harder to misuse.
Name branches after what they are for: short, lower case, with hyphens between words, such as fix-oven-temperature. Slashes are allowed, and teams often use them to group branches, as in feature/login.
Delete branches once they are merged. A short git branch list shows at a glance which work is still in progress.
Exercises
True or false?
- Creating a branch copies all your files.
- A branch is a label that points at a commit.
- When you commit, the label of the branch you are on moves to the new commit.
- Deleting a merged branch deletes its commits.
Show solution
- False. Creating a branch creates a label; no files are copied.
- True.
- True.
- False. Only the label is deleted; the commits stay, because the branch they were merged into still contains them.
Fast-forward or merge commit? Say which kind of merge each situation produces.
- You create
fix-typofrommain, commit twice, and nobody changesmain. Then you mergefix-typointomain. - You create
soupsfrommainand commit; meanwhile someone commits tomain. Then you mergesoupsintomain. - You are on your branch, which has no new commits, and you merge
main, which has two new ones.
Show solution
- Fast-forward.
mainhas not moved, so its label can jump tofix-typo’s newest commit. - Merge commit. Both branches have new commits, so Git creates a commit with two parents.
- Fast-forward of your branch: it contains nothing that
mainlacks, so its label simply moves tomain’s newest commit.
A feature branch. Create a branch called drinks, make two commits on it, merge it into main and delete it.
Show solution
git switch -c drinks
Switched to a new branch 'drinks'
lemonade.md so that it contains:# Lemonade
git add lemonade.md
git commit -m "Add a lemonade recipe"
[drinks faff603] Add a lemonade recipe
1 file changed, 1 insertion(+)
create mode 100644 lemonade.md
tea.md so that it contains:# Iced tea
git add tea.md
git commit -m "Add an iced tea recipe"
[drinks d2aea9e] Add an iced tea recipe
1 file changed, 1 insertion(+)
create mode 100644 tea.md
git switch main
Switched to branch 'main'
git merge drinks
Updating 288d56b..d2aea9e
Fast-forward
lemonade.md | 1 +
tea.md | 1 +
2 files changed, 2 insertions(+)
create mode 100644 lemonade.md
create mode 100644 tea.md
git branch -d drinks
Deleted branch drinks (was d2aea9e).
git log --oneline
d2aea9e Add an iced tea recipe
faff603 Add a lemonade recipe
288d56b Add a pancake recipe
02804ba Add a README
Because main did not move while you worked on drinks, the merge is a fast-forward, and git log --oneline shows a straight line.
Uncommitted changes and switching. You edited pancakes.md without committing and run git switch desserts. What can happen?
Show solution
If pancakes.md is the same on both branches, Git switches and carries your uncommitted edit along. If switching would overwrite your edit, because the file differs on desserts, Git refuses with “Your local changes to the following files would be overwritten by checkout” and leaves everything as it was. Commit your change first, or put it aside with git stash (Module 8).
Delete a branch that was never merged. A branch called experiment holds a recipe that did not work. Delete the branch. To try it, first create experiment, commit something on it, and switch back to main.
Show solution
git branch -d refuses, because the branch has commits that no other branch contains; -D deletes it anyway:
git branch -d experiment
error: the branch 'experiment' is not fully merged
hint: If you are sure you want to delete it, run 'git branch -D experiment'
hint: Disable this message with "git config set advice.forceDeleteBranch false"
git branch -D experiment
Deleted branch experiment (was 4e55929).
git branch
* main
Use -D with care: the commit is no longer on any branch. For a while it can still be found through the reflog (Module 8).
Read the graph. git log --oneline --graph --all prints:
* 5049848 (HEAD -> main) Add salt to the pancakes
| * 689ed1b (soups) Add a tomato soup recipe
|/
* 288d56b Add a pancake recipe
* 02804ba Add a README
Which branch are you on? Which commits are on both branches? What would git merge soups do?
Show solution
You are on main. Commits 288d56b and 02804ba are on both branches. Both branches have a commit the other lacks, so git merge soups would create a merge commit whose parents are 5049848 and 689ed1b, exactly as in Section 5.
Self-check quiz
-c creates the branch; git branch <name> creates it without switching.main and run git merge desserts. desserts contains main’s newest commit plus two more. What happens?main is missing from desserts, so no merge commit is needed.HEAD → desserts in a graph mean?git branch -d experiment reports that the branch is not fully merged. Why?-d protects work that would no longer be on any branch; -D deletes anyway.git switch -c fix replace?git checkout -b; git switch -c does the same job.Further reading
- Scott Chacon and Ben Straub, Pro Git, sections 3.1: Branches in a Nutshell and 3.2: Basic Branching and Merging.
- The reference pages for git branch, git switch and git merge.
- Learn Git Branching, for extra practice with branches and merges in your browser.