Git is a software development tool used to manage and track projects. It allows developers to store their code in a central location, and then pull changes from other developers to the codebase. Git branches allow for different parts of the codebase to be worked on separately, and then merged into the main branch when it’s ready.
Branches are a core feature of Git’s version tracking and are used constantly by teams working on the same software codebase. We’ll dive into how they work under the hood, and how you can use them to improve your Git workflow.
What Are Branches, Really?
Branches are used to split off Git history. You can think of Git commits like a line of changes going back in time. You can “checkout” any of these commits and move your local directory back in time to the state it was at when that commit was made.
Branches are commonly used for working on experimental features, or changes that take a while, or anything else that might otherwise break the repository. For example, you may be working on refactoring a big component of your codebase, and until it’s done, you want master branch to be stable.
Once the new feature branch is stable, it can be merged back into master, often through a pull request, which is a process that allows for code review and testing to be done before changes are made.
However, under the hood, branches work a bit differently than you might first expect. In Git, branches are just labels, or pointers, to a specific commit. That’s it, the master branch simply points to the latest commit made on master; when you make a new commit, the label is updated to point to the new commit.
While it’s useful to think of commits as moving forward in time; in reality, Git commits point backwards to each other. Each commit has a reference to the last commit, and this chain is used to construct the state of the repository.
If you make a new branch though, things work a little differently. Whichever branch you have checked out (with git checkout
To make the branch in this example, you must first make sure your repository HEAD is set to the master branch. This is because you can actually make branches starting from anywhere—including past commits or commits on other branches.
Then make a new branch, and swap to it:
At this point, nothing in your repository has changed. Both the feature and master branch labels point to the same commit.
However, anything you commit from this point on will be added to the feature branch. More specifically, a new commit will be created, set to point back towards the current commit, and the “feature” label will be updated to point to this new commit.
You could even checkout master and make more commits on the main branch. It won’t affect the feature branch, because all the label knows is that it points to that specific commit. It won’t be updated with the master label.
Merging and Rebasing
Of course, branches wouldn’t be too useful if they were stuck there forever, so Git provides tools to merge them back into the master branch. Technically you can merge sub-branches into any other branch, as long as the histories are compatible.
The simplest case is where you have a simple branch that just needs to be merged back. You can checkout the master branch, then run git merge feature to “replay” all the commits made on the feature branch onto master.
This incorporates them into the main timeline and creates a new “merge commit” with the changes.
It’s not always that simple though, and in many cases, you will have merge conflicts that need to be resolved. This can include branches modifying the same lines in files, file moves or deletion, or other kinds of bugs that come up when the software is changed since the feature branch was created.
If you have a long-running feature branch, one way to minimize this problem is to perform frequent merges, in reverse this time—from master into feature. This keeps feature up to date, and while it doesn’t really reduce the workload required, it prevents it from piling up into a huge mess.
This strategy is common for long-lived branches and is generally considered best practice for Git.
Another tool that is also used in this scenario is rebasing. Basically, rebasing is like picking up the entire branch and moving it to start from a new location, often the latest commit in the repository. This leads to a cleaner Git history in some cases and is the preferred solution for some complicated situations.
However, Git history is “immutable,” and because of this rebasing copies commits instead of actually moving them. This can lead to lots of problems on shared branches, if not coordinated properly with your team—if you rebase, and your coworker makes a new commit on the “old,” now deleted feature branch, it will be left stranding. They will have to stash the commit and pop it on the new branch to reconcile the changes.
How Do You Use Branches?
To start making a new branch, you’ll want to put your repository in the proper state so that the new branch label starts where you want it to. If you’re branching off of master, just checkout the entire branch to start at the latest commit. Otherwise, you can put your repo in a detached HEAD state by checking out an individual commit.
Then, you can make the new branch, and switch to it with checkout:
This can be done in one command, with the -b flag to checkout:
At this point, any commits made in your repo will be made to the new branch.
If you need to swap branches again, just run git checkout master to be set back to normal.
If you have local changes that you need to move, you can put them in git stash. The changes will be stored and can be reapplied after swapping branches.