Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXII · Merge vs RebasePatterns

The hybrid workflow — feature rebase, fast-forward into main, and trunk-based development

Advanced⏱ ~20 mingit

What you'll learn

  • Describe the hybrid workflow: feature branches rebase locally, fast-forward into main
  • Apply the rebase-then-fast-forward pattern to produce a linear main history with rebased feature commits
  • Recognise how trunk-based development uses the hybrid workflow with very short-lived branches
  • Configure the tooling (`branch.autosetuprebase`, `merge.ff`) to enforce the hybrid at the configuration level
  • Identify when the hybrid breaks down (long-lived branches, signed tags) and the merge verb must take over

Prerequisites

Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x

Not yet marked complete on this device.

The hybrid workflow combines the two verbs in a disciplined sequence: rebase on the feature branch (to keep its history clean and linear), merge as a fast-forward into the trunk (to integrate the rebased commits without an extra merge commit). The feature branch’s commits sit on top of the trunk’s tip; the merge is a no-op pointer move; the resulting trunk history is a single straight line. The hybrid is the most common workflow in modern infrastructure repositories because it captures the benefit of rebase (clean linear history, easy to read, easy to revert) without the cost of rebase (rewriting shared OIDs) — provided the rebase happens at the moment of merge, not the day before. Trunk-based development is the operational discipline that enforces the hybrid at scale: branches live for hours, not days; the rebase window is always open; the trunk is always deployable.

The rebase-then-fast-forward pattern

The hybrid workflow has three steps: (1) develop on a feature branch, (2) rebase the feature branch onto the trunk tip at the moment of merge, (3) merge the feature branch into the trunk as a fast-forward.

# Step 1: develop on the feature branch
git checkout -b feature/iam-rotation main
# ... commits on feature/iam-rotation ...

# Step 2: rebase onto the trunk tip just before merge
git fetch origin
git rebase origin/main
# Successfully rebased and updated refs/heads/feature/iam-rotation.

# Step 3: merge into the trunk as a fast-forward
git checkout main
git merge --ff-only feature/iam-rotation
# Updating M2..R3
# Fast-forward
flowchart LR
    subgraph STEP1["step 1 develop on feature branch"]
        C["main tip at branch creation"] --> F1["F1 (feature)"]
        F1 --> F2["F2"]
        F2 --> F3["F3 (feature tip)"]
    end
    subgraph STEP2["step 2 rebase onto trunk tip"]
        M2["new main tip (M2)"] --> R1["R1 (was F1)"]
        R1 --> R2["R2 (was F2)"]
        R2 --> R3["R3 (was F3)"]
    end
    subgraph STEP3["step 3 fast-forward merge"]
        M2b["main tip (M2)"] --> R1b["R1"]
        R1b --> R2b["R2"]
        R2b --> R3b["R3 (also new main tip)"]
    end

The --ff-only flag in step 3 is the discipline: it tells git merge to refuse anything but a fast-forward. If the rebase in step 2 was incomplete (the feature branch still has commits that are not descendants of main’s tip), the merge refuses with a clear error rather than silently producing a merge commit. The error message tells the engineer to re-run the rebase.

Trunk-based development as the discipline

Trunk-based development is the operational discipline that makes the hybrid workflow safe at scale. The discipline has three rules:

  1. Branches live for hours, not days. A feature branch is created from main, worked on for a few hours, rebased onto main at the moment of merge, and deleted. The branch never accumulates enough divergence to make the rebase painful.
  2. The trunk is always deployable. Every commit on main is a candidate for production deployment. The trunk is protected by CI: every push triggers a build, a test suite, and a deploy-to-staging. A broken commit is caught within minutes, not days.
  3. The rebase window is always open. Because branches live for hours and no one holds the OIDs for long, the rebase is always safe. The only consumer of the OIDs is the contributor’s local clone.
flowchart LR
    subgraph TBD["trunk-based development"]
        T1["main commit A"] --> T2["main commit B"]
        T2 --> T3["main commit C"]
        T3 --> T4["main commit D"]
        T1 -.feature branch.-> T5["feature commit"]
        T5 -.rebase + ff.-> T3
        T2 -.feature branch.-> T6["feature commit"]
        T6 -.rebase + ff.-> T4
    end

The diagram shows the trunk-based pattern: the trunk (main) progresses through commits A, B, C, D; each commit was originally on a short-lived feature branch that was rebased and fast-forwarded into the trunk. The trunk history is a single straight line; the branch events are gone from the graph; the audit trail is in the PR system and the CI logs.

The discipline’s cost is operational: every push to main triggers a full CI run, which means CI must be fast (minutes, not hours). The discipline’s benefit is structural: the trunk is always deployable, the rebase window is always open, and the merge-rebase question never arises because no branch lives long enough to be shared.

When the hybrid breaks down

The hybrid workflow is not universal. It breaks down in three situations:

  1. Long-lived feature branches. A branch that lives for days or weeks accumulates divergence from main. The rebase at the moment of merge produces many conflicts; the fast-forward merge may fail; the engineer falls back to a merge commit. The hybrid is not viable for long-lived branches.
  2. Signed tags and releases. A release branch that will be tagged cannot be rebased: the tag would point at an unreachable OID. The release branch uses merge (--no-ff) into main, breaking the linear history at the release boundary.
  3. Compliance-driven branch events. Some regulated environments require that every integration into main be recorded as a merge commit with a specific message format and PR reference. The hybrid’s silent fast-forward does not produce the required merge commit; the merge verb is forced.
# The hybrid fails: rebase produces conflicts that the engineer cannot resolve quickly
git rebase origin/main
# CONFLICT (content): Merge conflict in terraform/main.tf
# error: could not apply R2... Update trust policy

# The fallback: merge --no-ff to preserve the branch event
git checkout main
git merge --no-ff feature/iam-rotation
# Merge made by the 'recursive' strategy.

When the hybrid fails, the merge verb is the fallback. The graph gains a merge bubble at the point of failure; the trunk history is no longer perfectly linear. The cost is a single non-linear segment in an otherwise linear history; the benefit is that the integration proceeded without forcing the engineer to spend hours resolving rebase conflicts.

Configuring the hybrid at the tooling level

The hybrid workflow is enforced by four configuration keys:

# 1. Feature branches rebase on pull
git config branch.autosetuprebase local

# 2. The trunk does not rebase on pull (preserves OIDs)
git config branch.main.rebase false

# 3. Merges into the trunk must be fast-forward
git config merge.ff only

# 4. Merges into release branches are merge commits (--no-ff)
git config branch.release/2026-q3.mergeoptions "--no-ff"

The merge.ff = only setting on main is the most consequential: it makes every merge into main refuse anything but a fast-forward. An engineer who tries to merge a feature branch that has not been rebased gets a clear error and is forced to rebase before merging. The branch.<name>.mergeoptions setting on release branches overrides the global setting for those specific branches: every merge into a release branch produces a merge commit, recording the integration event in the graph.

Production discipline

  1. The rebase happens at the moment of merge, not the day before. The rebase window closes as soon as the branch is shared. The hybrid workflow’s safety depends on the rebase being immediate.
  2. Configure merge.ff = only on the trunk. The setting enforces the fast-forward requirement at the command level. An engineer who tries to merge a non-rebased feature branch is forced to rebase.
  3. Use --ff-only on every merge into the trunk. The flag is the explicit form of the merge.ff = only policy; it makes the intent visible in the command and in the shell history.
  4. Invest in fast CI. Trunk-based development requires CI runs to complete in minutes. A slow CI breaks the discipline and pushes engineers toward longer-lived branches.
  5. Document the hybrid in CONTRIBUTING. The hybrid is not the default Git behaviour; engineers must be told that feature branches are rebased and merges are fast-forwarded, and why.

Cross-course references

  • CI/CD Pipeline Patterns - Part V (MergeQueues) is the automation of the hybrid: each PR is rebased onto the trunk tip in the merge queue’s temporary branch, and the trunk receives the result as a fast-forward. The contributor’s branch is never rebased; the merge queue’s temporary branch is.
  • GitOps with Argo CD - Part VI (MergeStrategies) maps the hybrid onto GitOps: the GitOps controller reads the trunk’s linear history and applies each commit as it appears; the absence of merge commits means the controller has a single linear progression of desired states to reconcile against.
  • Terraform for Production Sysadmins - Part XI (PRWorkflows) recommends the hybrid for Terraform main: feature plan branches are rebased at the moment of merge, the trunk receives a fast-forward, and the resulting history is a single linear sequence of applied plans.

Quiz

Knowledge check · 4 questions

  1. Q1. An engineer is about to merge a feature branch into `main` using the hybrid workflow. The rebase has not been run, and `main` has advanced by three commits since the feature branch was created. What is the correct sequence of commands?

  2. Q2. Trunk-based development requires fast CI because every push to the trunk triggers a full build and test run; slow CI breaks the discipline by encouraging longer-lived branches.

  3. Q3. Name the four configuration keys that enforce the hybrid workflow at the tooling level and explain what each one does.

  4. Q4. Diagnose a hybrid workflow failure and recommend the recovery procedure when the fast-forward merge is refused.

    An engineer is merging a feature branch into `main` using the hybrid workflow. The engineer rebased the feature branch onto `main` two hours ago, then went to a meeting. While the engineer was in the meeting, a teammate pushed two commits to `main` (a hotfix and a CI config update). The engineer returns and runs `git checkout main && git merge --ff-only feature/iam-rotation`. The merge refuses with 'Not possible to fast-forward, aborting'.

Passing score: 75%. Answers are checked in this browser.