The letter A styled as Alchemists logo. lchemists
Published July 1, 2024 Updated October 17, 2025
Cover
Git Deployments

Git deployments are the result of a green Continuous Integration (CI) process which pushes changes from a Git repository to a remote server. There are many workflows for deploying changes from a Git repository and this article will highlight a few of them while specifically focusing the Rebased Branch Workflow.

I’m not sure if the Rebased Branch Workflow workflow goes by a different name but I’ve definitely not seen this workflow talked about as much as it should be even though I’ve alluded to aspects of this workflow in previous articles:

If you’re new to this site and haven’t read any of the above articles, you might want to spend some time getting up to speed. There is a lot of great content in those articles to level you up and make you a better engineer. If not, I’ll be referencing them again later in this article.

I’ve been using the Rebased Branch Workflow for years (actually, over a decade now) and find it superior to all other workflows for deploying changes. In order to compare/contrast this workflow with existing workflows, let’s start by looking at the most common workflows.

Overview

As mentioned earlier, there are multiple workflows for deploying changes from a Git repository. Below are a few of the most common workflows I’ve seen in the wild that you might be familiar with or using now.

Trunk (Centralized)

  • You work directly on the main branch, feature branches are not used.

  • Feature flags are used to hide incomplete work.

  • Emphasizes small, frequent commits.

  • CI runs tests on every commit to main.

This only works in small teams and doesn’t scale to larger teams due to becoming complete chaos while losing the ability to craft good commits. In truth, this doesn’t work for projects with only a single engineer either because you need to have a feature branch running against CI to confirm your changes are stable before rebasing onto the main branch. Otherwise, your Git history becomes murky and hard to read as you constantly make more commits to fix mistakes.

Feature Branch

  • You create a new feature branch off of the main branch for a specific chunk of work. All work is done in the feature branch until complete.

  • Code reviews are opened with a request to merge your feature branch onto the main branch.

  • Features are merged — or worse: squash merged — onto the main branch upon code review approval.

  • The feature branch is either manually or automatically deleted once merged. In truth, many of these feature branches are never deleted.

  • CI runs tests on the feature branch before merging.

  • CI runs whenever there is a change to the main branch.

The above is akin to how Git was designed where feature branches are meant to be short lived, constantly deleted, and main is always production-ready. The major downside is, by default, this workflow tends to be merge/squash merge heavy which leads to a non-linear and hard to read/maintain Git history.

Gitflow

  • Uses two primary branches: main and develop.

  • All feature branches are created off the develop branch.

  • Code reviews are opened against the develop branch.

  • A release branch is created off of the develop branch when prepping for deploy and then merged back into the main and develop branches when done.

  • In the worst of cases, additional branches are created — and maintained — for each environment used. Example: stage, qa, production.

  • Release branches (i.e. release-1.2) are rarely deleted after use. They seldom adhere to Strict Semantic Versioning either.

  • Hotfix branches (i.e. hotfix-1) are created off of the main branch for urgent production issues. They seldom adhere to Strict Semantic Versioning either.

  • The main branch is generally, but not always, tagged after deployment to production. Sometimes the tag uses dates or inconsistent and non-Strict Semantic Versioning which complicates matters further.

  • CI runs tests on all branches.

You should avoid using this workflow due to the following reasons:

  • Is overly complicated for a process that should be much simpler.

  • This workflow is indicative of poor team process and organizational structure due to lack of rigor.

  • Uses long lived feature branches that have to be constantly maintained and kept up-to-date instead of deleting and keeping feature branches short lived.

  • Someone — or a group of individuals — has to constantly manage multiple branches and deal with conflict resolution which increases the likelihood of production issues. Remember those release masters from the CVS/SVN days? Oof. Why, people, why!?!

  • Requires increased communication amongst all teams and individuals which increases process and slows down productivity.

Forked

  • You fork the main repository and work off either a feature branch or main branch of your fork.

  • All work is done within the fork.

  • Changes are submitted via code review.

  • CI runs tests on the fork before merging.

This workflow is most common in Open Source where you don’t have access to the main repository so must work off the fork. In general, this workflow is fairly clean because, once your work is complete, you can delete the fork to reduce your maintenance burden. Sadly, I see way too many engineers with forked repositories that haven’t been touched in years that should have been deleted long ago.

Ultimately, if actively working on a forked repository, you eventually get elevated to committer status in the main repository which means you can abandon this workflow in favor of whatever workflow the main repository is using.

Summary

The problem with most of the above workflows is they:

  • Generally merge or, worse, squash merge changes once reviewed.

  • Use too many, long lived, feature branches.

  • Incur a high cost of team communication to coordinate deploys which reduces productivity.

  • Don’t delete branches which leads to increased repository size when cloning.

  • Suffer from a Git history that’s not readable, doesn’t provide a rich history long tail thinking, or provides valuable learning resource for newcomers to level up on.

Can we do better? Yes we can! Let’s talk about how we can approach this problem using the Rebased Branch Workflow with a strong focus on capturing the what, why, how, and who of our work.

Rebased Branch Workflow

This workflow is rooted in a Git Rebase workflow with a strong focus on quality, rigor, and a linear Git history while also capturing the following information:

  • What: Explains what change we are making.

  • Why: Explains why the change is necessary.

  • How: Details the atomic change which, in most cases, is your implementation and corresponding specification/test.

  • Who: Details who the primary author of the change is — along with any supporting collaborators — so they can be properly attributed, honored, and highlighted in the corresponding release notes as produced when deploying a new milestone (version).

💡 See Git Commit Anatomy for well crafted commits and Milestoner for version automation.

Features

At a high level, here are the primary benefits of leveraging this workflow:

  • Enforces a Git Rebase workflow by default.

  • Enforces a linear commit history (no merges or squash merges).

  • Enforces short lived feature branches which are automatically deleted once rebased onto main.

  • Uses main as the primary — production ready — branch which must remain green. If for some reason the main branch becomes red, drop everything and fix the main branch immediately.

  • CI automatically runs on main and and all feature branch changes.

  • Automatically deploys changes once the main branch is green in CI.

  • Due to having a linear history, feature branch work is easier to keep up-to-date since merge conflicts are few and far between.

  • With a linear Git history, it is much easier to grok the long tail of thought and discussion made by past and current engineers. This is especially important for new — and old — engineers wanting learn how and why the architecture evolved.

Steps

A typical workflow consists of the following steps:

  1. Rebase (i.e. git pull --rebase) your main branch to ensure you are up-to-date with latest team activity.

  2. Create a feature branch — off of main — to start work on a fix or new feature.

  3. Each commit would include a Milestone: (major|minor|patch) trailer — even multiple Git Trailers — for automatic version bumping, release note generation, and deployment purposes.

  4. As you work within your feature branch, you would continuously rebase local changes and/or changes from main (i.e. git pull --rebase origin main) to ensure any local/upstream merge conflicts are resolved early and often.

  5. Once work on your feature branch is complete, you can open up your feature branch for Code Reviews. As your team provides feedback, you can quickly fold in all changes by constantly rebasing them into your feature branch using the following commands in any order (see my Git Rebase article for further details):

    • git commit --amend

    • git commit --amend --no-edit

    • git commit --amend --all --no-edit

    • git commit --fixup <sha>

    • git commit --fixup=amend:<sha>

    • git commit --fixup=reword:<sha>

    • git commit --squash <sha>

  6. As you apply feedback from code review — using the above commands — rebase all changes so your amend!, fixup!, and/or squash! special Git directives are properly folded into your original commits as you never want these directives to show up on your main branch. Adding Git Lint to your CI process ensures this never happens.

  7. Once the team is done with your code review, you can rebase your feature branch onto the main branch so CI can build main and ensure everything is green. 💡 If using GitHub, see my Code Reviews article on how to ensure only the Rebase button shows up.

  8. Depending on team culture/process, automatic or manual deployment to either a Stage or Production environment should occur. See the Environments section below for details.

Environments

With this workflow, you can still have multiple environments. At minimum — and a given — you’ll need a Production environment. Typically, you’ll have both a Stage and Production environment. Optionally, you’ll want a Review environment which is automatically spun for each code review and automatically torn down after the code review.

In either case, the workflow might look like this:

  • Production Only: Changes rebased onto main → CI (green) → Automatic deploy to Production based on commit SHA.

  • Stage and Production: Changes rebased onto main → CI (green) → Automatic deploy to Stage → Manual (or automatic) deploy to Production based on commit SHA.

  • Review: Code review is opened → CI is run → Environment is spun up → Once the code review is approved or closed, the environment is spun down and destroyed accordingly.

Having too many environments is like having too many branches which increases cost, adds additional maintenance, and slows team productivity. Keep your environments to a minimum and use only what you need.

Tags

Git Tags (a.k.a. milestones) are lightweight aliases to a Git commit SHA. Even better, they can have a subject and body, much like a commit message. Most importantly, tags allow us to differentiate between various milestones and their associated deployments.

While you can create an arbitrary label for a tag, please don’t. Every tag should use Strict Semantic Versioning. Briefly, this means your tag must consist of the following:

  • Major (X.y.z): Incremented for any backwards incompatible public API changes.

  • Minor (x.Y.z): Incremented for new, backwards compatible, public API enhancements/fixes.

  • Patch (x.y.Z): Incremented for small, backwards compatible, bug fixes.

Here are a few examples, with comments, to detail what each version is:

1.0.0 # Major
0.1.0 # Minor
0.0.1 # Patch

A tag needs to be created with every deploy. Even better, this can be automated with Milestoner using Git Trailers. All you need to do is one of the following trailers when creating commit messages:

Milestone: patch
Milestone: minor
Milestone: major

You can use Git Lint to enforce consistency and ensure all trailers are properly formatted. Then, when it’s time to deploy, Milestoner will automatically calculate the correct version, since last tag, based on your commit trailers.

Cherry Picks

To err is human; to forgive, divine.

 — Alexander Pope

Try as we might, mistakes will happen. Using this workflow vastly reduces them. That said, in situations in which a production issue crops up, we can quickly cherry-pick a patch onto main for deployment.

In general, the cherry picked SHA(s) would be atomic and sourced from a feature branch with strict adherence on the SHA(s) being code reviewed and green in CI before being deployed. Example:

# One at a time.
git cherry-pick <sha>

# A range of commits.
git cherry-pick abc..ghi

Ideally, you wouldn’t cherry pick at all but simply create a feature branch off of your currently deployed tag. For example, consider the following (assuming you’ve already deployed 0.5.0 and need to patch it):

git checkout 0.5.0
git switch --create 0.5.1
git commit
git push

# Assuming code review is approved and CI is green.
git switch main
git merge --ff-only 0.5.1
git branch --delete --force 0.5.1 && git push --delete origin 0.5.1

# Assuming CI is green.
git push

# Automatically tags, builds release notes, and deploys (similar to `git tag 0.5.1 && git push --tags` but very bare bones).
milestoner --publish

💡 The use of git merge --ff-only 0.5.1 is, essentially, the same manual step as used within the GitHub UI when pressing the Rebase button once your code review is approved. The --ff-only option ensures no merge bubble is created. Highly recommend setting this as your default in your global Git Configuration.

Feature Flags

You’ll recall, when discussing common deployment workflows earlier, that some workflows relied on feature flags you could toggle behavior on/off without deploying new changes. There is nothing stopping you from using feature flags in this workflow either.

Keep in mind that feature flags incur a maintenance cost. This means you must remain diligent in deleting them as soon as you are done using them or you’ll end up maintaining code no longer used.

Tools

In addition to the above — and hinted at throughout this article — the following Ruby gems are great for augmenting this workflow within your own team(s):

Conclusion

Congratulations, you’ve now gained a deeper understanding of the various workflows used when deploying from a Git repository to a remote server. You’ve also learned about the power of the Rebased Branch Workflow through the use of Git Rebase which allows you to capture the long tail of knowledge while also gaining an automation advantaging when building versions and associated release notes.