Well, first, branches don't really get deleted. Only branch names get deleted. But this gets us into a thorny question: What exactly do we mean by "branch"?
Let's take a fast look at the process of merging. We start with a series of commits in the commit graph (or "DAG"; see the linked question) that look like this:
...--o--*-----o <-- master
\
o--o--o--o--o <-- feature
We then run:
git checkout master
git merge feature
which somehow figures out what we've changed in both master
and feature
since the last time we merged them (which was actually never, but they were together at one time, at the point marked *
here). Git then makes a new merge commit that points back to both of these branch tip commits:
...--o--*-----o---------o <-- master
\ /
o--o--o--o--o <-- feature
and we have "a merge": a commit, of type merge commit.
We can now erase the word feature
and the arrow, i.e., remove the name. The graph remains intact, retained by the name master
:
...--o--*-----o---------M <-- master
\ /
o--o--o--o--F
Should we wish to see what went into master
via feature
, all we have to do is find the commit I have labeled F
(for Feature) here. Note that I have also labeled the merge commit M
(for merge).
The way to find it is to start from master
and work backwards until we find M
. (It's right there at the tip of master
right now, although later, it will be some number of steps back from the tip.) Then, we simply look at the second parent commit of M
.
To find the second parent of a commit whose hash ID we know, we just tell Git: tell me the hash ID of the second parent of this other hash ID. The easy way to do this is with git rev-parse
. Let's say the hash ID of M
is badf00d
:
git rev-parse badf00d^2
Git spits out the full hash ID of F
. The hat-two suffix means "second parent" (hat-one, or just hat by itself, means "first parent").
Now we may also want to find commit *
. That's the merge base of the commit that is the first parent of M
, and this particular commit F
that we just found. To find the merge base of two commits, we ask Git:
git merge-base badf00d^1 badf00d^2
We can then look at every commit in the range starting just after the merge base *
and going up through and including commit F
, using git log
or git format-patch
or whatever.
We can do this with the raw hashes, or we can point names (temporary or permanent, they will live exactly as long as you like) to commits M
, F
, and/or *
, using git branch
or git tag
. Each name remembers the hash ID for you. The chief difference between a tag name and a branch name is that if you git checkout
a tag name, you get a "detached HEAD" and are not on a branch, but if you git checkout
a branch name, you get on that branch, and if you make new commits, they will cause that branch to advance:
$ git branch newname <hash-ID-of-commit-F>
...--o--*-----o---------M--o--o--o <-- master
\ /
o--o--o--o--F <-- newname
$ git checkout newname
... hack away ...
$ git commit ...
...--o--*-----o---------M--o--o--o <-- master
\ /
o--o--o--o--F--o <-- newname (HEAD)
This is all that branches are, in Git: the names just point to commits, while the branch structure, the history or DAGlet or whatever name you want, is formed by the permanent parts of the commit DAG. Branch names have the special feature that you can git checkout
them and make them advance by running git commit
.