Git branches - Is your mental model wrong?Tweet this post
This post assumes you know…
- what a version control system is (and that git is a version control system)
- roughly what a git commit is
This post covers…
- commit history navigation (aka checking out commits)
- how branches relate to tags and commits
- what it means to be on a branch
- branch deletion
- and, most importantly, what the heck a branch is anyway
Why is this stuff important to know?
Understanding branches in git is central to understanding many of the most important git commands, and understanding the basics of branches is essential to understanding remote branches, which you need to know about if you want to work on projects with other people.
What is the problem?
We often relate the thing we are trying to understand to something that we already understand, often something in the physical world.
When we start out as developers, using git for the first time, we are shown diagrams of git commits forming chains that “branch” outwards (see Scott Chacon’s excellent Pro Git Book: http://git-scm.com/doc). When we look into the underlying implementation of git we find data structures known as “trees”. Then we hear that there is such a thing as a git branch, and suddenly we instinctively feel we know what that is: it must surely bear a similarity to the branch of a tree, else why would they have named it a “branch”.
And that must mean…
- Branches are made up of a load of commits joined together, right?
- I can move up and down a branch, right?
- Two people can be at different points on the same branch, right?
- I can delete a branch by deleting the commits that make up the branch, right?
- I guess I can move a branch if I break the links between commits or something, right?
- When two branches merge they become one single branch, right?
No! No! No! No! No! No! 1.
1. Oh, mama mia, mama mia. Mama mia, let me go…
Git branches are nothing, and I mean nothing, like tree branches. So what are they?
What you need to know
Checking out commits
When you commit your work in Git, a new commit is created in the project’s Git repository. Each commit is linked to the previous commit, and the full chain of all commits is called “the commit history”.
You can go up and down the commit history by “checking out” git commits. You do this on the command line by entering
git checkout followed by the commit hash (or key). The commit hash is a string of 40 characters that uniquely identifies the commit.
However, using commit hashes to navigate around the commit history is a pain in the backside.
Tags to the rescue
Commit hashes don’t exactly roll off the tongue, so the makers of Git provide us with the ability to create tags to highlight and label particular commits. A git tag is just a pointer with a human readable name of our own choosing. When we are at a commit that we think we might want to return to, we simply add a tag with the command
git tag [name of tag] and that tag obediently points to the currently checked out commit; for example, we might create a git tag called “release_version_1” which might point to the currently checked out commit with hash 734713bc047d87bf7eac9674765ae793478c50d3.
Then, later, we can easily navigate back to that commit with the command
git checkout release_version_1 instead of having to remember that god-awful looking commit hash and having to enter
git checkout 734713bc047d87bf7eac9674765ae793478c50d3 to return to that commit.
Branches: auto-moving tags
By far the most common commit we need to navigate back to is our latest commit: where our latest work is. Of course our latest commit changes every time we add a new commit, and the problem with using tags to label our latest commit is that tags don’t move. A tag always points to the commit it was set up to point to, so each time we add a new commit our existing tags stubbornly point to the same commits they always pointed to, forcing us to keep adding new tags. For example, if we used tags to label our latest commit we might end up with something like the following.
current_work - current_work_2 - current_work_3 - current_work_this_is_getting_ridiculous - etc…
Wouldn’t it be great if we had a type of tag that, whenever we made a new commit, moved and pointed to the new commit?
Well the good people behind git have given us just that. And what are these auto-moving tags called? Err… “branches” obviously.
Branches can be created as easily as tags, with the command
git branch [name of branch]; for example,
git branch my_branch creates a new branch called my_branch pointing to the currently checked out commit. Then you just checkout the branch (just like you check out a commit) with the command
git checkout [name of branch]. Then whenever you make a new commit the branch moves so that it points to new commit: super convenient, huh?.
On and off the branch
Although the whole point of branches is to move when you make a new commit, there might be occasions when you want to make a commit but don’t want the branch to move and point to the new commit. For this, the git boffins introduced the idea of being on-the-branch and being not on-the-branch. If you check out the branch itself then you are considered to be on the branch and any commits you make will move the branch forward so that it points to the new commit, but if you check out the commit itself (using the commit hash), then you are not on-the-branch, and any commits you make will not move the branch forward.
Detached head state
Some of you may have encountered the cryptic git message “you are now in detached head state…“. This slightly painful sounding warning is just reminding you that you aren’t currently on a branch, which may be just what you intended and therefore nothing to worry about.
Creating and checking out a branch with a single command
By default, when a branch is created with the command
git branch [name of branch] you are not on-the-branch so after you first create a branch you will almost always want to check it out with the command
git checkout [name of branch]. Alternatively, you can use the shortcut ‘git checkout -b [name of new branch]’ to both create and check out a new branch in one single command.
Trying out ideas (topic/feature branches)
You aren’t restricted to having just one branch: You can have as many as you like. This means you can try out an idea on one branch, then try out a different idea on another branch without messing up your other work on the first branch, and then you can switch back and forth between the two branches as you wish.
git branch -d [name of branch] to delete any branches that you’ve finished with.
Alternatively, leave your branches there for posterity: it’s entirely up to you.
But, surely there’s more to branches than that? Nope 2.. That’s all there is to branches.
2. And don’t call me Shirley
So now you can see why it doesn’t make sense to think of moving up and down a branch. A branch is a single point on the commit history, just like a tag. And deleting and creating branches (just like deleting and creating tags) doesn’t affect the commits themselves in any way.
While branches themselves are very simple, there’s some powerful things you can do with them.
Undoing commits by moving (i.e. resetting) the branch
Since the branch you are on essentially marks which commit is your current/latest work, you can effectively undo unwanted commits simply by changing what the branch points to and making new commits from there. This is known as resetting the branch, and, unsurprisingly, you do this with the command
git reset [commit hash]3.
3. Note, that git reset has another very important dimension (–hard, –soft etc..) that I won’t go into here.
Other branch related topics not covered in this post
- branch merging
- resetting: hard and soft
- remote branches and branch tracking