Skip to content

Branches and merging ​

A branch lets you work on something without disturbing the main line of the project, then bring the work back when it is ready. By the end of this lesson you can create branches, merge them both ways Git can, and read the graph that results.

A branch is a label ​

In lesson 1 you saw that commits form a chain, each pointing at its parent. A branch is nothing more than a name that points at one commit. Git stores it as a tiny file holding that commit's hash.

text
c4906d4 ◄── 11d88db ◄── f11adbc ◄── 719f474 ◄── dce44e0
                                                   ▲
                                                  main

When you commit on a branch, Git creates the new commit and moves the label forward to it. The label always points at the newest commit on that branch, called its tip.

Because a branch is only a label, creating one is instant and costs nothing, however big the project. Use them freely: one branch per task is normal.

HEAD: where you are ​

You can have many branches, but you are on one at a time. HEAD is Git's name for "the branch you are on now". Git stores it as a pointer to a branch:

bash
cat .git/HEAD
text
ref: refs/heads/main

So HEAD points at main, and main points at a commit. When you commit, the branch HEAD points at is the one that moves.

Detached HEAD

If you check out a commit directly (for example git switch --detach 11d88db) instead of a branch, HEAD points at the commit itself. Git calls this detached HEAD. It is fine for looking around. Commits you make there belong to no branch, so create one (git switch -c new-name) before you leave, or they become hard to find.

Create and switch branches ​

bash
git branch
text
* main

git branch lists your branches; the * marks the one you are on. Create a branch and switch to it in one go:

bash
git switch -c day-plan
text
Switched to a new branch 'day-plan'

Now commit something on it:

bash
printf '# Day plan\n\nDay 1: Arrive, walk the old town\n' > days.md
git add days.md
git commit -m "Add day plan"
text
[day-plan ec2d506] Add day plan
 1 file changed, 3 insertions(+)
 create mode 100644 days.md
text
                                       main
                                        ▼
… ◄── 719f474 ◄── dce44e0 ◄── ec2d506
                                  ▲
                          day-plan, HEAD

Switch back to main and look at the folder:

bash
git switch main
ls
text
README.md
packing.txt

days.md is gone from the folder, because it does not exist on main yet. It is not lost: it is safe in the commit on day-plan. Switching branches changes the files on disk to match the branch.

CommandWhat it does
git branchList local branches
git switch nameSwitch to an existing branch
git switch -c nameCreate a branch at the current commit and switch to it
git switch -Switch back to the previous branch
git branch -d nameDelete a branch that has been merged

Older guides use git checkout name and git checkout -b name. They still work; git switch was added in Git 2.23 to do this one job with fewer surprises.

When Git will not switch ​

If you have uncommitted edits that do not clash with the other branch, Git carries them over to it. If switching would overwrite them, Git refuses:

text
error: Your local changes to the following files would be overwritten by checkout:
	packing.txt
Please commit your changes or stash them before you switch branches.
Aborting

Commit the edits, or put them aside with git stash (lesson 6), then switch.

Merge: bring work back ​

Merging combines another branch's work into the branch you are on. You always merge into the current branch, so first switch to the branch that should receive the work.

Fast-forward ​

main has no new commits since day-plan started. Git does not need to combine anything: it moves the main label forward to the tip of day-plan.

bash
git switch main
git merge day-plan
text
Updating dce44e0..ec2d506
Fast-forward
 days.md | 3 +++
 1 file changed, 3 insertions(+)
 create mode 100644 days.md
text
Before:                                    After:
          main                                            main, day-plan
           ▼                                                    ▼
… ◄── dce44e0 ◄── ec2d506         … ◄── dce44e0 ◄── ec2d506
                     ▲
                  day-plan

No new commit was created. The history stays a straight line.

Merge commit ​

Now both branches move on. You start a budget branch and commit on it, then go back to main and commit there too. The two lines of work have split, or diverged:

bash
git log --oneline --graph --all
text
* 86d5275 Add budget
| * 63e3bcf Plan day 2
|/
* ec2d506 Add day plan
* dce44e0 Ignore local settings, logs and build output
…

A fast-forward is impossible: main cannot jump to 86d5275 without losing 63e3bcf. So Git makes a merge commit, a commit with two parents that joins the two lines:

bash
git merge budget
text
Merge made by the 'ort' strategy.
 budget.txt | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 budget.txt

Git opens your editor for the merge message, with Merge branch 'budget' filled in; save and close to accept it. (ort is the name of Git's default merge method; you can ignore it.)

bash
git log --oneline --graph --all
text
*   7aa942b Merge branch 'budget'
|\
| * 86d5275 Add budget
* | 63e3bcf Plan day 2
|/
* ec2d506 Add day plan
* dce44e0 Ignore local settings, logs and build output
…
Fast-forwardMerge commit
WhenThe current branch has no commits of its own since the splitBoth branches have new commits
New commit?No, the label movesYes, with two parents
History shapeA straight lineTwo lines that join

If both branches changed the same lines of the same file, Git cannot decide which version wins and stops with a conflict. That is lesson 5.

Read a commit graph ​

git log --oneline --graph --all draws the history as text. --all includes every branch, not only the current one. Add --decorate if your terminal does not show branch names:

text
*   7aa942b (HEAD -> main) Merge branch 'budget'
|\
| * 86d5275 (budget) Add budget
* | 63e3bcf Plan day 2
|/
* ec2d506 (day-plan) Add day plan
  • Each * is a commit. Newest is on top.
  • |, / and \ are the lines between parents and children. |/ is where a branch split off; |\ is where two lines joined in a merge commit.
  • Names in brackets are labels: HEAD -> main means you are on main, and it points here. budget and day-plan show where those branches point.

BranchPilot's commit graph shows the same thing with colours: each dot is a commit, a hollow ring is a merge commit, each branch has its own lane, and labels mark branches, tags and HEAD. Right-click a branch and choose Merge into current… to merge; double-click a branch to switch to it.

Tidy up merged branches ​

Once a branch is merged, its label is no longer needed. The commits stay in the history either way.

bash
git branch -d budget day-plan
text
Deleted branch budget (was 86d5275).
Deleted branch day-plan (was ec2d506).

-d refuses to delete a branch with commits that are not merged anywhere:

text
error: the branch 'other' is not fully merged
hint: If you are sure you want to delete it, run 'git branch -D other'

-D deletes it anyway. Do that only if you are sure you no longer need those commits. BranchPilot asks the same question with Branch not fully merged and a Force delete button.

Good branch habits ​

  • Keep main working. Do each piece of work on its own branch and merge it when it is done.
  • Use short, descriptive names in lower case with hyphens: fix-login-timeout, add-day-plan. Many teams add a prefix such as feature/ or fix/.
  • Merge or delete branches when they are finished, so the list stays readable.

Check yourself ​

QuizBranches and merging

Question 1 of 6

What is a branch in Git?

Score: 0

What you learned ​

  • A branch is a label that points at a commit and moves forward when you commit.
  • HEAD says which branch you are on; switching branches changes the files on disk.
  • git switch -c creates and switches; git merge brings another branch into the current one.
  • A fast-forward moves the label; a merge commit has two parents and joins two lines of work.
  • git log --oneline --graph --all and BranchPilot's graph show how branches split and join.

Next steps ​