Mad Physicist's answer is correct (and upvoted), but a diagram might help.
The key to understanding this is that for most purposes in Git, branch names are almost entirely irrelevant. What matters is the commit graph (and the snapshots attached to each participating commit in that graph).
Let's say you have the following graph:
...--A--B--C <-- mainline
\
D--E--F <-- feature
and you are proposing to git merge
the feature branch into the main-line branch. Normally you would do this with:
git checkout mainline && git merge feature
which would direct Git to perform the merge ("merge" as a verb) using commits B
and C
as "what we did", and commits B
and F
as "what they did". If all goes well Git automatically makes a new commit, which adjusts the label mainline
to point to the new commit. If things do not go well, Git forces us to fix up the mess (edit things in the work-tree, and git add
the result) and make the new commit ourselves. This also makes a new commit, which also adjusts the label mainline
to point to the new commit. In any case the new commit has two parents: commits C
and F
(in that order). In other words, the new commit is a merge ("merge" as a noun). So let's draw this:
...--A--B--C------G <-- mainline
\ /
D--E--F <-- feature
Now, note that this graph is identical to this graph:
C
/ \
...--A--B \-----G <-- mainline
\ /
D--E--F <-- feature
But imagine for a moment that we can make Git not move the label mainline
after we make the commit, so that we get this graph instead:
C <-- mainline
/ \
...--A--B \-----G <-- ??? (HEAD)
\ /
D--E--F <-- feature
Except for leaving the branch name mainline
alone, the effect is exactly the same: we do the same merge-as-a-verb work and make the same merge-as-a-noun commit object G
.
This is what happens if you make a new branch name (which fills in the ???
part). The "before the new merge" picture is:
C <-- mainline, test-branch (HEAD)
/
...--A--B
\
D--E--F <-- feature
and the after-merge picture is the one with merge commit G
added.
Git knows which branch name to update based on HEAD
.
Note that you can even make the merge using Git's "detached HEAD" mode. In this mode, the name HEAD
points directly to a commit ID, instead of having the HEAD
file store the branch name. The rest of Git works as usual, but when making a new commit, instead of updating the branch-name stored in HEAD
—there isn't one—Git simply updates HEAD
itself.
Once you decide that the merge is good, you can then ask Git to "slide the name mainline
forward" in a fast-forward operation:
C <-- (old mainline)
/ \
...--A--B \-----G <-- HEAD, (new mainline if slide-forward works)
\ /
D--E--F <-- feature
This "slide forward" operation fails, on purpose, if it's not possible to just slide forward. For instance, suppose that our work to do the merge, resolving conflicts or whatever it is we had to do, and then running our tests, took a relatively long time ... and during that time, someone else snuck in and updated mainline
, adding some new commit of their own:
C---------H <-- mainline
/ \
...--A--B \-----G <-- HEAD
\ /
D--E--F <-- feature
It's no longer possible to "slide forward" to G
: the name mainline
would first have to "slide back" to C
, losing H
. The fast-forward operation will fail, letting us know that our temporary merge G
cannot become a permanent member of branch mainline
after all.
(I've drawn the new commit H
as an ordinary commit, but it doesn't matter whether it's a simple commit, or a merge commit: the point is that it comes after C
, but not after G
. These fast-forward operations must move only "forward"—rightward, in these graph drawings.)