Git basics · Lesson 4 of 6 · about 25 minutes

Branches

Work on something new without touching what already works, switch between versions of your project in a second, and merge your work back in when it's ready.

By the end you'll know

  • What a branch is, and why it's just a label on a commit
  • How to create branches, switch between them, and see which one you're on
  • How merging works, and the two kinds of merge Git makes
  • How to delete branches, and why Git sometimes refuses
  • What a merge conflict looks like, and how to fix a simple one

Why branches exist

So far every commit went onto one line of history, main. That's fine for small changes. But say you want to try something bigger: rewrite a page, add a feature, experiment. If you do it directly on main, your half-finished work is mixed in with the version that works, and if the idea doesn't pan out, you have to untangle it.

A branch is a separate line of work. You make one, commit to it as much as you like, and main doesn't change at all. When the work is ready, you merge it back into main. If it isn't worth keeping, you delete the branch, and it's as if it never happened.

Think of drafting in a copy of a document. Before rewriting a section of an important report, you make a copy to try your ideas in. The original stays clean and usable the whole time. If the draft works, you fold the changes back into the original. If it doesn't, you throw the copy away. A branch is that copy, except Git makes it instantly and only stores what you change.

What a branch really is

A branch sounds heavy, like a whole copy of your project. It isn't. A branch is just a label pointing at a commit. When you commit on a branch, the label moves forward to the new commit. That's all.

And HEAD, from Lesson 3, normally points at a branch: it's how Git knows which label to move when you commit. In your git-practice folder:

BASH
git branch
TEXT
* main

One branch, main, and the * marks the one you're on.

Try it: make a branch and work on it

git switch -c creates a branch and switches to it in one step (-c for "create"):

BASH
git switch -c add-ideas
TEXT
Switched to a new branch 'add-ideas'

Now make a commit on it:

BASH
echo "# Ideas" > ideas.txt
echo "- learn branches" >> ideas.txt
git add ideas.txt
git commit -m "Start a list of ideas"
git log --oneline -3
TEXT
0f62b4d (HEAD -> add-ideas) Start a list of ideas
2d59507 (main) Revert "Add a line to the README"
db4b1f8 Add a line to the README

(Your IDs will differ.) Read that carefully: HEAD -> add-ideas is one commit ahead, and main is still where it was. Your new commit exists only on add-ideas.

Switch between branches

Go back to main, and look at your files:

BASH
git switch main
ls

ideas.txt is gone. Not deleted: it simply doesn't exist on main, because the commit that created it is on the other branch. Switch back:

BASH
git switch add-ideas
ls

And it's back. Switching branches changes the files in your folder to match that branch, in a fraction of a second. That's what makes branches so cheap to use: you can keep several lines of work going and move between them freely.

Merge it back

The idea is done, so bring it into main. Merging always works the same way: switch to the branch you want to update, then merge the other branch into it:

BASH
git switch main
git merge add-ideas
TEXT
Updating 2d59507..0f62b4d
Fast-forward
 ideas.txt | 2 ++
 1 file changed, 2 insertions(+)

Fast-forward means main hadn't changed since you made the branch, so Git didn't need to combine anything: it just slid the main label forward to your new commit. Now ideas.txt is on main too.

The branch has done its job, so delete it. That only removes the label; the commit is safely part of main now:

BASH
git branch -d add-ideas
TEXT
Deleted branch add-ideas (was 0f62b4d).

When both branches moved: a merge commit

Fast-forward only works when main stood still. Usually it doesn't: someone else, or you, commits to main while the branch is in progress. Try it:

BASH
git switch -c more-ideas
echo "- learn merging" >> ideas.txt
git commit -am "Add merging to the ideas"
git switch main
echo "- learn git switch" >> todo.txt
git commit -am "Add git switch to the to-do list"

Now each branch has a commit the other doesn't. Merge:

BASH
git merge --no-edit more-ideas
TEXT
Merge made by the 'ort' strategy.

(--no-edit accepts Git's suggested message, as in Lesson 3; ort is just the name of Git's merging method.) Git combined the two lines of work into a new merge commit that has two parents. See the shape of your history:

BASH
git log --oneline --graph -5
TEXT
*   82c819e Merge branch 'more-ideas'
|\
| * 444385e Add merging to the ideas
* | b979160 Add git switch to the to-do list
|/
* 0f62b4d Start a list of ideas

--graph draws the branches: two lines splitting and joining again.

Fast-forward compared with a merge commitOn the left, a fast-forward: main had no new commits, so Git simply moves the main label forward to the branch's latest commit, and the history stays one straight line. On the right, a merge commit: main and the branch both have new commits, so Git creates a new commit that joins the two lines, and main points to it.Fast-forward: main stood stillABCbranchmainmain's label slides from B to CMerge commit: both movedABCMergetwo parentsbranchmainmain now points hereA branch is a label on a commit. Merging either moves the label, or joins two lines with a new commit.
If main didn't move, merging just slides its label forward. If both lines moved, Git makes a merge commit that joins them.

Delete the merged branch:

BASH
git branch -d more-ideas

Deleting branches: -d and -D

Make a branch with an idea you decide against, and try to delete it without merging:

BASH
git switch -c experiment
echo "an idea I'm not sure about" >> ideas.txt
git commit -am "Try an idea"
git switch main
git branch -d experiment
TEXT
error: the branch 'experiment' is not fully merged.
If you are sure you want to delete it, run 'git branch -D experiment'

That's Git protecting you. Lowercase -d only deletes a branch whose work is already in the branch you're on, so you can't lose commits by accident. If you really don't want the work, capital -D forces it:

BASH
git branch -D experiment

(If you change your mind later, the reflog from Lesson 3 can still find that commit for a while.)

In my real setup

Every lesson on this site started life as a branch: docker-lesson-7, git-lesson-2, and so on. Apart from the site's first few commits, and one empty commit I pushed to kick off a stuck deploy, nothing goes straight onto main. Each change goes on its own branch, gets checked, and is merged in through a pull request (Lesson 5).

When I looked at my branches while writing this lesson, I found five old ones from the site's first week, and Git said four of them were "not fully merged". They were merged, through pull requests that squash a branch's commits into one brand-new commit on main. Because that new commit isn't one of the branch's own commits, -d can't tell the work is already there, so it refuses. Once I'd checked that their changes really were on main, -D was the right tool. "Not fully merged" means "I can't prove it's merged", which isn't always the same as "it isn't".

Break it on purpose

Switch with changes in the way. Make a branch that changes the to-do list, then go back to main and change the same file without committing:

BASH
git switch -c conflict-demo
echo "- learn merge conflicts" >> todo.txt
git commit -am "Add merge conflicts to the to-do list"
git switch main
echo "- learn git branch" >> todo.txt
git switch conflict-demo
TEXT
error: Your local changes to the following files would be overwritten by checkout:
	todo.txt
Please commit your changes or stash them before you switch branches.
Aborting

Switching would replace todo.txt with the branch's version, and your uncommitted edit would be lost, so Git refuses and changes nothing. It offers two ways out: commit your change first, or stash it, which sets it aside temporarily (Lesson 6). Here, commit it:

BASH
git commit -am "Add git branch to the to-do list"

Merge two changes to the same line. Both branches have now added a different line at the same spot in todo.txt. Merge:

BASH
git merge conflict-demo
TEXT
Auto-merging todo.txt
CONFLICT (content): Merge conflict in todo.txt
Automatic merge failed; fix conflicts and then commit the result.

A merge conflict: Git can combine changes to different parts of a file by itself, but when two branches change the same spot, it can't know which you want, so it asks you. Look at the file. cat prints a file's contents to the screen:

BASH
cat todo.txt
TEXT
- learn git switch
<<<<<<< HEAD
- learn git branch
=======
- learn merge conflicts
>>>>>>> conflict-demo

Git marked the clash. Between <<<<<<< HEAD and ======= is your current branch's version; between ======= and >>>>>>> conflict-demo is the other branch's. To resolve it, open todo.txt in your editor, decide what the file should say, and delete the three marker lines. Here you want both lines, so the end of the file becomes:

TEXT
- learn git switch
- learn git branch
- learn merge conflicts

Save it, then tell Git it's resolved by staging it, and finish the merge:

BASH
git add todo.txt
git commit --no-edit
git branch -d conflict-demo

That's the whole process: Git stops, you choose, you stage, you commit. Lesson 6 covers bigger conflicts and how to back out of a merge.

Clean up

Run git branch: only main should be left. Keep git-practice for the rest of the track.

Check yourself

  1. What is a Git branch, really?

  2. You make ideas.txt on a branch, commit it, then switch to main. Where's ideas.txt?

  3. You want the work from add-ideas in main. What's the right sequence?

  4. Git says Fast-forward after a merge. What does that mean?

  5. git branch -d says a branch is not fully merged. What's going on?

  6. A merge stops with CONFLICT in todo.txt. What do you do?

Key terms

  • Branch: a separate line of work. Really, a label pointing at a commit.
  • git branch: lists branches; * marks the one you're on.
  • git switch / git switch -c: move to a branch / create one and move to it.
  • git merge: brings another branch's work into the branch you're on.
  • Fast-forward: a merge where Git just moves the label, because only one line had new commits.
  • Merge commit: a commit with two parents, joining two lines of work.
  • git branch -d / -D: delete a merged branch / force-delete any branch.
  • Merge conflict: two branches changed the same spot; you choose the result, stage it and commit.

Go deeper