I need to confirm/correct my assumptions when creating branches. If I'm in master branch, after doing:
git checkout -b some_branch
it means I have started a new branch from master.
On the other hand, if I checkout another branch, and create a branch there:
git checkout some_branch git checkout -b other_branch
This means I've created other_branch using all the current code committed from some_branch, right?
And, regardless of the current branch, if this is done:
git branch branch_2 branch_1
Then branch_2 will be created using branch_1 as the base. Are these assumptions correct?
There is no such thing in Git as a base branch of a branch. Instead, there is only a current commit, which Git calls the tip of the branch.
To understand this visually, as it were, you should start by drawing (at least part of) Git's commit graph. Here's an example of a tiny repository with just three commits in it:
A <-B <-C <--master
The "true name" of any given commit is one of those big ugly hash IDs, c0ffeeface1deadbead...
and so on. This hash ID uniquely identifies the specific commit, and in fact is made by hashing (hence the name "hash ID") the contents of that commit. They look random and are impossible to remember, so here I just use single uppercase letters.
The way Git "sees" the graph is that it starts by reading a branch name, such as master
. This branch name contains the hash ID of a commit like C
. We say that master
points to commit C
.
Meanwhile, commit C
itself contains the hash ID of its previous (or parent) commit B
. So we say that C
points to B
, the same way master
points to C
.
Likewise, commit B
points back to A
. A
is the very first commit ever, so there's nowhere for it to point back to ... so it just doesn't. We call A
a root commit, and it lets us (and Git) stop working backwards.
These internal arrows are kind of annoying to draw, and note that the hash ID of B
is actually part of C
itself, so it can never change (if we try to change this part of C
, we get a new, different commit). So we can stop bothering drawing them, and write instead:
A--B--C <-- master
the arrow coming out of a branch name, though, is not constant, and that's where this whole idea of the tip commit comes from.
Suppose we want to add a new commit to master
. We do all the usual setup that Git requires (add or modify some files and use git add
) and then run git commit
. Git:
Writes out a new commit D
(which gets a new, unique hash ID). This new commit points back to C
:
A--B--C <-- master \ D
Then, changes master
(or more precisely, its stored hash ID) so that it points to the new commit we just made:
A--B--C \ D <-- master
and of course there's no reason to keep this kink in the drawing anymore:
A--B--C--D <-- master
So this is how branches grow, in Git.
To make a new branch, Git simply creates the branch name pointing to some existing commit:
A \ B \ C \ D <-- master
We can pick any one of these commits and make a new branch name point there. Let's pick B
and make newbr
point there:
A \ B <-- newbr \ C \ D <-- master
We would do this with git branch newbr <thing-that-finds-B>
.
How do we find B
? Well, one way is to run git log
and cut-and-paste the hash ID. But another way is to use a branch name. The name newbr
now points to B
. If we want to make another branch point to commit B
too:
git branch thirdbr newbr
This makes Git look up newbr
, which points to B
, and create the new name thirdbr
, which ... also points to B
:
A--B <-- newbr, thirdbr \ C--D <-- master
This is why creating a branch in Git is so bleeping fast: it does almost nothing at all! It just makes a label that points to some existing commit.
The commit to which some branch-name points is called the tip commit of that branch. Note that one commit can be the tip of several branches at the same time. This is part of a larger thing about Git: some commits are on many branches, all at the same time. For instance, commit A
—the root commit—is on every branch. (It's possible to have more than one root commit in a repository, although it's a bit tricky, and we don't need to worry about that now. You can't do it with the commands shown this far.)
The special property of branch labels, though, is that they move. Not only do they move, they move automatically.
We already saw this when we made new commit D
. Git wrote the new commit's ID into master
. But: How did Git know to use master
? For that matter, how did Git know to use C
as the parent commit when Git made commit D
?
Well, of course we only had one branch at the time, but let's make a new commit now, now that we have three labels master
, newbr
, and thirdbr
. First, though, let's do git checkout thirdbr
, and draw the result:
A--B <-- newbr, thirdbr (HEAD) \ C--D <-- master
Nothing really changed in the drawing,1 except that I added the word HEAD
here. HEAD is how Git knows which branch-and-commit are the current branch and commit.
So now we do our usual modify-some-files, git add
, and git commit
. Git writes out the new commit, with its parent set to commit B
. Git sees that the current branch is thirdbr
and thirdbr
points to B
, so the current commit is B
. Let's draw in the new commit E
:
E / A--B <-- newbr \ C--D <-- master
The only thing left is to move the current branch name thirdbr
so that it points to new commit E
:
E <-- thirdbr (HEAD) / A--B <-- newbr \ C--D <-- master
and we're all done: we've added a new commit to branch thirdbr
(which is still HEAD, and hence still the current branch, but now E
is the current commit).
When you add a commit to the current branch, it's HEAD that tells what the current commit was and where the new commit goes. HEAD normally contains the name of a branch, and that's what git checkout
does: it checks out a specific commit—usually, the tip commit of an existing branch—and then sets up the file HEAD
to remember the name of the branch. It's the branch name itself that remembers the tip commit.
Using git checkout -b newname commit
means: "check out the specified commit, then make a new branch name newname that points to that commit, and then set HEAD to that new name." If you leave out the commit part, the default is to use HEAD. Since HEAD is (always) the current commit, Git gets to skip the "check out" part and just create the new branch name and store it into the HEAD
file.
1While nothing changed in the graph, Git did have to update our work-tree, and the index, so that we could have the files as of the way they were at commit B
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With