Nebulaworks Insight Content Card Background - Tim arterbury canopy umbrella
Developing and releasing software in a team setting can be messy. With many developers working on the same code base, we need a workflow that allows a team to develop in parallel, and allows for new functionality to be safely integrated into our environments and applications. In this blog, we’ll be discussing a branching strategy that allows us to do just that. Introducing Trunk-Based Development for beginners.
Table of Contents
Before we get into branching strategies, let us do a quick refresher on Git commits, branches and tags. These are features provided by Git that aid us in our software development endeavors. Nearly all version Control Systems (VCS) have similar features, but for this blog, we’ll be discussing these features as they relate to Git.
Let’s first discuss Git commits. Commits are the building blocks of the Git VCS. At a high level, a commit is a “snapshot” of our repository. This “snapshot” includes the state of all tracked files (files that Git is aware of) in a repository. In addition, it provides us with information about any new changes that were made since the last commit. All changes introduced to our repository will be done by creating a commit and pushing it to a branch. As we continue to develop our code base we will continuously add commits. Commits are the building blocks of Git!
Git branches are central to the management and development of our codebase. They facilitate development by allowing engineers to iterate in parallel and they ensure the stability of deployed/compiled code by allowing us to reference specific commits. Branches are “pointers” to a git commit. You can think of them as different “versions” of our code. This commit pointing ability allows multiple developers to add their own unique commits, without affecting any commits prior to the one they branched from.
As we iterate on our codebase, we want to continuously leverage newly developed features. This can easily be done by leveraging code from a specific branch, but we do run the risk of having new features (commits) pushed to our branch. These new features can introduce bugs and unwanted behavior which is not desirable in live environments (environments/applications that users interact with). Our goal is to keep downtime to a minimum. We need something more stable, we need something immutable. This is where Git tags come into play. Like branches, tags are references to specific points in Git history. Unlike branches, git tags cannot be changed. After they are created, they cannot have any commits added to them. The immutable nature of tags makes them perfect for our production environments and applications.
Now that we’ve got Git commits, branches, and tags down, let’s get into the Trunk-Based Development (TBD) branching
model. TBD has developers iterating on a single branch that is the “trunk”. Here at Nebulaworks, our trunk is the
master branch (the name is arbitrary). TBD discourages long-lived feature branches and lends itself to quick iteration
due to its single branch nature. This is not to say that this branching model is any less secure when compared to other
branching strategies that have multiple branches. The same hard gates are present that ensures that code merged into
master works as expected. This master branch is always in a deployable state. This workflow makes heavy use of
git rebase as opposed to
git merge. TBD provides us with the following benefits:
- History only includes changes and merge PRs providing meaningful insight via git log
- Facilitates communication between team members via the codebase since changes are always being incorporated together in master
- Commits are meaningful and atomic
- Merges only come from Merge Requests (MR)
- Continuous rebasing against our branches helps us keep up with the remote
masterremote branch is always moving forward
If you would like to learn more about trunk-based development check out trunkbaseddevelopment.com.
The Trunk-Based Development Workflow
To better illustrate the TBD workflow, I will be walking you through the development of a very simple python application that lets us know just how great TBD is! Our example will be leveraging Git for version control and Github for our hosting service. We’ll be highlighting important TBD concepts along the way. Key concepts will be prefixed with [Key Concept]. We will be breaking up the workflow into a couple of sections so it is easier to follow:
- Creating our First Feature
- Creating our Second Feature
- Cutting a Release
Let’s get started
Creating Our First Feature
First, we need to clone down the repository.
git clone firstname.lastname@example.org:<repopath>.git
By default, we will find ourselves on the
masterbranch which currently has nothing. Let’s start adding some python code.
master. Make this branch’s name related to the work being done. In this case, the feature branch is tied to an issue that I have spun out in a ticketing platform (jira, gitlab board, etc).
git checkout -b mr/issue-1 # mr are my initials
[Key Concept] We are creating a
new branchto ensure that
masteris always in a deployable state. We do not want to introduce changes that could potentially break code in master. Whenever we want to add a new feature to our codebase a new branch will be created to develop and test said feature.
On our new branch let’s create a python script that lets us know why TBD is awesome! We will be adding and commiting the script to our repository.
cat << EOF > tbd-script.py print("Trunk-Based Development is awesome!") EOF git add tbd-script.py git commit -m "Added tbd python script"
Let’s push this new branch up to Github.
git push origin mr/issue-1
[Key Concept] Before we are able to merge our new feature into
masterwe will run tests to verify that our feature works. Running
python3 tbd-script.pywill output
Trunk-Based Development is awesome!to the terminal. For this simple feature, a test like this is satisfactory. In reality, your code should be subject to meaningful tests.
Now that we’ve verified that our feature works, let’s create a pull request for the new branch against master with the proposed changes to kick off a discussion.
Note: This can be done via the GUI or CLI of your VCS.
[Key Concept] A Pull Request (PR) should be opened for all new branches that you wish to merge into master. The sooner you open up a PR, the better, even if the PR isn’t ready yet (make sure to add
WIP)! The more time we have to discuss proposed changes and fixes, the higher our code quality will be.
After having your PR reviewed, if further changes are needed, repeat steps
If everything looks good a team member will merge your PR!
[Key Concept] Your PR MUST be
mergedby someone who did not contribute any commits to the new branch. The more eyes we have on our code, the better the quality.
Good job so far! We have successfully added a new feature into our master branch. Now everyone will know how great TBD is! Let’s start working on our second feature.
Creating Our Second Feature
Let’s pull and rebase remote
masteronto our local
git pull --rebase origin master
[Key Concept] Since our PR was approved and merged in Github, we need to make sure that our local
masterbranch is up to date with our remote
master. We are treating both masters as one and the same!
Note: We’re using the
--rebaseflag to make sure that our local master’s history aligns with the remote. It also prevents any ugly merge bubbles!
Now that our local master branch is up-to-date let’s get started on
issue-2. We’ll repeat step
Let’s add some new functionality!
cat << EOF > tbd-script.py print("Trunk-Based Development is awesome!") print("It allows for fast iteration!") # new line added EOF git add tbd-script.py git commit -m "Adding new print statement to tbd script"
Let’s push our code up to Github.
git push origin mr/issue-2
We should perform any tests that we need to validate the new functionality. Running
python3 tbd-script.pywill show us output that we can validate.
Now that we’ve verified our feature works as expected, let’s open up a PR for the
[Key Concept] Uh oh. It looks like our PR is showing merge conflicts with master. Another developer on our team created a branch for feature 3 called
al/issue-3. It looks like
al/issue-3was already merged into master. During development, it is common to have PRs being merged into master after our issue branch was originally branched from master. We need to get these new commits onto our
mr/issue-2branch. This process is known as
First, let’s update our local master branch
git checkout master git pull --rebase origin master
Let’s grab the latest commits from our local master branch, and get them into our branch.
git checkout mr/issue-2 git rebase master
[Key Concept] Frequent rebasing is encouraged in the TBD workflow. As all developers are iterating on master, it will be updated constantly.
git rebaseallows us to temporarily remove any commits made on our branch, apply the missing commits from master onto our branch and then reapply our commits on top of them. This ensures that we’re keeping master’s commit history consistent across all branches.
Note: During the rebase you might have to deal with conflicts, this is expected and unavoidable if there are changes
Now that our branch is up-to-date, we should re-test our branch, make any necessary changes and push to our remote.
git push origin mr/issue-2 -f
Since we have added new commits to our branch’s git history, we need to pass in the
-fflag. This will allow git to overwrite the history of the remote branch. If we don’t do this Git will error out when it sees that the local and remote
mr/issue-2branch’s history differ.
Note: Our existing PR will be updated with any changes made to our branch.
Cutting a Release
At this point in time, we are happy with our python app and we are ready to show it to the world. Since our application
will be servicing users, we need to make sure that it is up and running at all times. In order to ensure the stability
of our code we will be performing a
Create a branch off
master. Let’s call it
RC/0.1(RC = Release Candidate)
git checkout master # checking out to our master branch git pull --rebase origin master # ensure that our local master is up-to-date with the remote master git checkout -b RC/0.1 # create the RC branch git push origin RC/0.1 # push RC branch to remote
[Key Concept] RC branches are created off master periodically (usually at the end of a sprint) when we’re ready to release functionality developed in the previous sprint. These branches provide us with more stability than master, because we limit the amount of commits that we push to them. We limit pushed commits by requiring all new commits to be added via a
The Hot Fix Process
- A new branch is created to develop functionality that fixes the problem in our RC branch.
- The new branch is then merged into master.
- The commit/PR with the fix is cherry-picked onto our RC branch.
git checkout RC/0.1 # checkout to our RC branch git cherry-pick -m 1 -x <SHA> # cherry pick commit onto our RC branch git push origin RC/0.1 # update remote RC with new fix
Note: This assumes a cherry-pick of an entire PR which is most common. Additionally including the “-x” adds the original commit SHA to the cherry-pick commit message!
Hotfixing is the only way new commits should make their way onto an RC branch. This process minimizes the likelihood of bad code making its way into our RC branches!
RC branches are pretty stable because of our hotfix process. They are great for testing out our codebase in environments
like staging. We want to be able to vet out code that will be released to production. However, there still exists the
possibility of someone pushing commits to them. This makes them unfit for production. We will need to reference code
that is immutable. It is time to cut a
tag from our RC branch.
Cut a Release (Tag)
Cut a release by creating a tag on the release branch as follows:
git checkout RC/0.1 git tag -a -m "Releasing version 0.1.0" 0.1.0 git push origin --tags
[Key Concept] The
0.1.0tag we just cut will provide users with an environment/application that works and includes all the functionality that we’ve developed so far. Every sprint we will go through this same process of cutting releases. Adhere to your preferred software versioning convention (consistency is what is important). Congratulations, we’ve released our codebase to production!
Now that you’ve read Trunk-Based Development for Beginners, you can see the simplicity and transparency that Trunk-Based Development brings to the development process. I encourage you to give it a try and see if it is a good fit for your team.
Nebulaworks has helped some of the largest companies leverage Trunk-Based Development to develop and manage their infrastructure. If you’d like to learn more about our DevOps consulting services, feel free to reach out to us.