Trunk-based Development: The Secret to Eliminating ‘Merge Hell’ and Accelerating CI/CD

Git tutorial - IT technology blog
Git tutorial - IT technology blog

The Nightmare of “Merge Hell” and the Limits of Git Flow

Imagine this: You spend a whole week coding away on a separate feature branch. Everything runs perfectly on your local machine. But when it’s time to merge into main for release… “boom”! Hundreds of bright red conflict lines appear. You spend the whole afternoon manually picking through code. Worse, after merging, the system crashes due to overlapping changes you didn’t anticipate.

The Git Flow model with its various feature, develop, and release branches used to be the gold standard. However, to achieve true CI/CD (Continuous Integration and Continuous Delivery), maintaining long-lived branches is the biggest hurdle. It unnecessarily drags down release speed. That’s when Trunk-based Development (TBD) comes to the rescue.

At my previous team, as our headcount grew to 10, Git Flow started to feel clunky. Every release was an ordeal because we had to wait for sub-teams to resolve overlapping merges. After switching to Trunk-based, conflict resolution time dropped from 4 hours per week to less than 15 minutes. The team could confidently deploy to production at any time.

What exactly is Trunk-based Development?

Instead of branching out in multiple directions, Trunk-based Development focuses all developers on working on a single branch (usually main or master). This branch is called the Trunk.

There are two common ways to implement it:

  • For small teams: Devs commit and push directly to the main branch.
  • For large teams: Devs create extremely short-lived feature branches. These branches only exist for a few hours to a maximum of one day, then merge immediately into main after passing Code Review.

The core philosophy is simple: Integrate early and often. Don’t keep code on your machine for too long. Push it to the Trunk as soon as you finish the smallest piece of functionality.

3 Pillars for Successful TBD Implementation

To adopt TBD without “breaking” the source code, you need a much stricter set of rules than traditional Git Flow.

1. Break Down Tasks (Atomic Commits)

Never push a 2,000-line PR (Pull Request) to the Trunk. Break features down into tiny tasks. Each commit should solve a single problem. If a feature takes 3 days to code, find a way to split it into 5-6 small merges.

# Instead of waiting for everything to finish, merge the skeleton first
git checkout -b feat/api-user-schema
# Code the schema definition...
git commit -m "feat: add user schema and validation rules"
git push origin feat/api-user-schema
# Create a PR and merge into main within 30 minutes

2. Feature Flags – The Universal “Switch”

How do you merge unfinished code into main without breaking the user experience? The answer is Feature Flags. You wrap new code in an if condition.

# Real-world example with payment logic
from feature_flags import is_enabled

def process_payment(user):
    if is_enabled("use_new_stripe_adapter", user):
        # New logic currently in testing
        return stripe_v2_adapter.process(user)
    else:
        # Old logic still running stably
        return paypal_legacy_adapter.process(user)

Thanks to Feature Flags, I can push code to production every day while keeping it hidden. Once QA finishes testing in the live environment, I just flip the switch on the dashboard. No code redeployment needed.

3. “Relentless” CI System

In TBD, the main branch must always be in a release-ready state. You must have a CI pipeline (GitHub Actions, GitLab CI…) that runs automated tests extremely fast.

My experience: Set up Branch Protection. No one, not even the Tech Lead, should be allowed to merge into main unless all tests pass 100%. If a build breaks, the team’s top priority is to fix the build and stop all new coding.

Why should your team switch to TBD now?

After many projects using TBD, these are the most noticeable changes I’ve seen:

  • Immediate bug detection: Code is continuously integrated. If there’s a logic conflict, CI will report it immediately. You don’t have to wait until the end of the sprint to find out you broke a colleague’s code.
  • Clear design thinking: To use Feature Flags, you are forced to write modular code and separate components. This inadvertently leads to a cleaner, more maintainable system architecture.
  • Extremely fast delivery: Eliminating cumbersome merge processes reduces lead time. Once the code is done, it can go to staging or production immediately.

Pitfalls to Avoid

TBD is powerful but can backfire if you fall into these 3 traps:

  1. Laziness in writing Unit Tests: Don’t play with TBD if the project doesn’t have good test coverage. Pushing code continuously to main without automated filters is suicide.
  2. “Marinating” feature branches for too long: If a branch exists for more than 2 days, it’s no longer Trunk-based. Find every way to break it down.
  3. Technical debt from Feature Flags: Don’t forget to remove if/else blocks once a feature is stable. My team usually spends one morning at the end of the month cleaning up old flags to avoid code clutter.

Conclusion: Should you switch?

Trunk-based Development is not just a Git technique; it’s a change in mindset. If you are at a startup that needs speed or a DevOps team looking to optimize your pipeline, try adopting TBD.

Initially, the team might find it annoying to break down tasks or write Feature Flags. But after just 2-3 sprints, when the fear of “Merge Hell” disappears, you’ll see productivity skyrocket. Release speed will no longer be a hurdle, but a competitive advantage for your team.

Share: