Nebulaworks Insight Content Card Background - Tim arterbury canopy umbrella

Nebulaworks Insight Content Card Background - Tim arterbury canopy umbrella

Trunk-Based Development for Beginners

February 22, 2020 Matthew Ramirez

An overview of trunk-based development: Git, branching strategies, tagging, rebasing, releases, and hot-fixes

Recent Updates

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

Git Features

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.

Commits

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!

Branches

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.

Tags

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.

Trunk-Based Development

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:

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:

Let’s get started

Creating Our First Feature

  1. First, we need to clone down the repository.

    git clone git@github.com:<repopath>.git
    

    By default, we will find ourselves on the master branch which currently has nothing. Let’s start adding some python code.

  2. Create a new branch off 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 branch to ensure that master is 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.

  3. 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"
    
  4. 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.py will 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.

  5. 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.

  6. After having your PR reviewed, if further changes are needed, repeat steps 2 and 3.

  7. If everything looks good a team member will merge your PR!

    [Key Concept] Your PR MUST be approved and merged by 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

  1. Let’s pull and rebase remote master onto our local master branch.

    git pull --rebase origin master
    

    [Key Concept] Since our PR was approved and merged in Github, we need to make sure that our local master branch is up to date with our remote master. We are treating both masters as one and the same!

    Note: We’re using the --rebase flag to make sure that our local master’s history aligns with the remote. It also prevents any ugly merge bubbles!

  2. Now that our local master branch is up-to-date let’s get started on issue-2. We’ll repeat step 1.

  3. 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"
    
  4. Let’s push our code up to Github.

    git push origin mr/issue-2
    
  5. We should perform any tests that we need to validate the new functionality. Running python3 tbd-script.py will show us output that we can validate.

Rebasing

  1. Now that we’ve verified our feature works as expected, let’s open up a PR for the mr/issue-2 branch.

    [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-3 was 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-2 branch. This process is known as rebasing.

  2. First, let’s update our local master branch

    git checkout master
    
    git pull --rebase origin master
    
  3. 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 rebase allows 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

  4. 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 -f flag. 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-2 branch’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 release.

  1. 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 hotfix.

The Hot Fix Process

  1. A new branch is created to develop functionality that fixes the problem in our RC branch.
  2. The new branch is then merged into master.
  3. 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)

  1. 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.0 tag 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!

Conclusion

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.

Insight Authors

Matthew Ramirez, Senior Software Engineer Matthew Ramirez Senior Software Engineer
Nebulaworks - Wide/concrete light half gray

Looking for a partner with engineering prowess? We got you.

Learn how we've helped companies like yours.