Branching and Merging Basics
Branches as movable labels, switching without losing work, fast-forward versus three-way merges, resolving your first conflict, and cleaning up merged branches.
Branches as movable labels, switching without losing work, fast-forward versus three-way merges, resolving your first conflict, and cleaning up merged branches.
You have been committing in a straight line so far, which works right up until the day you start something risky and cannot finish it before lunch. Branches are how Git lets you keep two unfinished ideas apart without keeping two copies of your project.
By the end of this lesson you can open a branch for every piece
of work you start, merge it back into main, and walk calmly
through a conflict when two edits land on the same line.
A branch is a movable label that points at one commit. That is the whole definition. It is not a folder, not a copy of your files, and not a container that commits live inside.
Picture a row of numbered index cards laid out on a desk — your commits. A branch is a paper clip you attach to the last card. When you add a new card, the clip slides forward to it. Two clips can sit on the same card, or on cards in different parts of the row, and nothing about the cards changes.
There is one more label worth knowing: HEAD. HEAD points
at the branch you are currently on, which is how Git knows where
to attach your next commit. When you switch branches, HEAD
moves; when you commit, the branch under HEAD slides forward.
Here is a repository with three commits and one branch. The letters are commit IDs, and the label sits at the tip:
A---B---C <- main (HEAD is on main)git branch with no arguments lists your branches and marks the
current one with an asterisk. Give it a name and it creates that
branch — pointing at your current commit — without moving you
onto it.
git branch # list branches
git branch feature/greeting # create, but stay where you are
git switch feature/greeting # move onto itMost of the time you want both steps at once, which is what -c
("create") does:
git switch -c feature/greetingYou will also see git checkout -b feature/greeting everywhere —
in older tutorials, in your colleagues' terminal history, in
Stack Overflow answers. It does the same thing. git checkout
was overloaded to mean half a dozen unrelated operations, so Git
split it into git switch for branches and git restore for
files. Both spellings still work; the newer ones are clearer
about what they touch.
Now make two commits on the new branch:
echo "Hello, forager." > greeting.txt
git add greeting.txt
git commit -m "Add a greeting file"
# edit greeting.txt again
git commit -am "Warm up the greeting wording"The graph now forks. main has not moved, because nothing you
did touched it:
A---B---C <- main
\
D---E <- feature/greeting (HEAD)Read that as: commits D and E sit on top of C, and the
feature/greeting label is clipped to E. The main clip is
still on C, exactly where you left it.
When you run git switch main, Git rewrites the files in your
working directory to match commit C. greeting.txt disappears
from your folder — not because it was deleted, but because it
does not exist at that point in history. Switch back and it
returns. Your editor may need a moment to notice.
This only works cleanly when your changes are committed. If you have edits in progress that switching would destroy, Git refuses:
error: Your local changes to the following files would be
overwritten by checkout:
greeting.txt
Please commit your changes or stash them before you switch
branches.
Aborting.That message is Git protecting you, and it names both honest ways out. The first is to commit — even a scrappy "WIP" commit you amend later. The second is to stash, which lifts your uncommitted changes onto a shelf and gives you a clean tree:
git stash # shelve changes; git stash pop brings them backStash has real depth to it and belongs to the intermediate course. That one line is enough to unblock you today.
Prefix branches by intent and keep them short: feature/search,
fix/login-redirect, docs/readme-badges. The slash is only a
naming convention, but it groups related work in listings and
tells a reviewer what kind of change to expect. Avoid test2
and johns-branch — a name that means nothing in three weeks is
a name you will be afraid to delete.
Merging means bringing the commits from one branch into
another. You do it from the branch that should receive the work,
so switch to main first:
git switch main
git merge feature/greetingGit reports Fast-forward. Because main still points at C,
and C is already in feature/greeting's history, there is
nothing to combine — Git slides the main clip forward to E:
A---B---C---D---E <- main, feature/greeting (HEAD on main)No merge commit appears, because nothing needed reconciling. The history stays a straight line, and both labels now sit on the same commit.
Real projects rarely stay that tidy. Suppose you branch again,
make commits D and E, and meanwhile main gains a commit F
of its own. The two lines have diverged:
D---E <- feature/tagline
/
A---B---C---F <- main (HEAD)Now git merge feature/tagline has real work to do. Git finds
the last commit both branches share (C, the merge base),
compares each side against it, and combines the two sets of
changes. That is a three-way merge, and it produces a new
merge commit:
D---E
/ \
A---B---C---F---M <- main (HEAD)Commit M is unusual in one way: it has two parents, F and
E. That is how Git records that two lines of history joined
here, and it is why git log --graph --oneline can draw the
fork you see above.
Both of those were git merge. Which one you get is not a
choice you make — it depends entirely on whether the receiving
branch moved while you were away.
The label slides forward. No new commit.
Happens when main has not moved since you branched, so your
commits already sit on top of it.
History stays a straight line, and nothing records that these commits were one piece of work.
A merge commit with two parents.
Happens when both sides moved. Git finds the last commit they share, compares each side against it, and combines both sets of changes.
This is also the only case where a conflict can happen.
A conflict happens when both branches changed the same lines of the same file and Git cannot tell which version you meant. It is not an error and nothing is broken; Git has run out of authority to decide and is handing the choice to you. Let's cause one on purpose.
git switch -c fix/tagline
# change the tagline line in README.md
git commit -am "Sharpen the tagline"
git switch main
# change the same line, differently
git commit -am "Reword the tagline"
git merge fix/taglineAuto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.Open README.md and you find both versions written into the
file, fenced by markers:
<<<<<<< HEAD
Notes on mushroom foraging.
=======
Field notes on foraging, updated weekly.
>>>>>>> fix/taglineThree markers, three jobs. Everything between <<<<<<< HEAD and
======= is the version on the branch you are standing on — the
receiving side, here main. Everything between ======= and
>>>>>>> is the incoming version, and the name after >>>>>>>
tells you which branch it came from. The ======= line is only
a divider; it means nothing on its own.
Resolving one is four steps, and they are the same four steps every time.
Open the file and find the markers
git status lists every file still marked "both modified".
Work through them one at a time.
Edit it into the text you actually want
Keep one side, keep the other, or write a third version that borrows from both. Git does not care which.
Delete all three marker lines
Including the ======= divider. A marker left behind is a
syntax error committed into your project.
Stage the file, then finish the merge
git add README.md, and once every file is staged,
git merge --continue — or plain git commit, which does
the same thing here.
A merged branch has served its purpose, and leaving dozens
around makes git branch useless. Find the finished ones and
delete them:
git branch --merged # branches fully contained in this one
git branch -d fix/tagline # safe delete; refuses if unmergedThe lowercase -d is a safety catch: it deletes the label only
when every commit on that branch already exists somewhere else,
so nothing becomes unreachable.
Once this is habit, it stops feeling like ceremony. Branch off
main before you start anything. Work and commit as often as you
like, in small pieces. When the work is done, switch to main,
merge, and delete the branch. Then branch again for the next
thing. Five steps, and the cost of the first one is 40 bytes.
git branch # list branches; * marks current
git branch <name> # create a branch, stay put
git switch <name> # move onto an existing branch
git switch -c <name> # create and switch in one step
git checkout -b <name> # older spelling, same effect
git stash # shelve uncommitted changes
git stash pop # bring shelved changes back
git merge <branch> # merge <branch> into current one
git merge --no-ff <branch> # always make a merge commit
git merge --continue # finish after resolving conflicts
git merge --abort # cancel a merge, restore files
git status # see what a conflict still needs
git log --graph --oneline # draw the commit graph
git branch --merged # branches safe to delete
git branch -d <name> # delete; refuses if unmerged
git branch -D <name> # force delete; can lose commitsEverything so far lives on your machine. The next lesson, working with remotes and GitHub, takes these same branches online: pushing a branch so others can see it, pulling their work down, and opening a pull request so a merge can be reviewed before it lands.
Beyond that, the intermediate course covers rebase and
interactive rebase — replaying your commits onto a new base
to produce a straight, tidy history — along with deeper conflict
tooling like git mergetool and rerere. They are worth
learning, and they are much easier once merging feels routine.
For now, practice on something with no stakes. Make a small repo, branch, change the same line two different ways, and force a conflict on purpose. Resolving three conflicts you created yourself is what turns the real one into an ordinary Tuesday.