Git Merging Strategies for GitOps-Ready Repositories
In a GitOps workflow, your git history is your audit trail. When a deployment fails, you need to trace it back through commits. When a rollback is needed, you need a clean target. The merge strategy you choose directly affects how usable that trail is.
The Three Strategies
%%{ init: { 'look': 'handDrawn' } }%%
graph TD
A[Feature branch] -->|Merge strategy| B[Option 1: Regular merge<br/>Preserves all commits]
A -->|Merge strategy| C[Option 2: Squash merge<br/>Single commit per feature]
A -->|Merge strategy| D[Option 3: Rebase + merge<br/>Clean linear history]
Strategy 1: Regular Merge
git checkout main
git merge feature/add-observability
All commits from the feature branch are preserved in the merge commit. This is the most honest history — you see exactly what happened, including intermediate work-in-progress commits.
Problem: In active branches, this creates a messy commit graph. git log --graph looks like a railroad switching yard.
When to use: Small teams, experimental branches, or when you need full traceability of every change.
Strategy 2: Squash Merge
git checkout main
git merge --squash feature/add-observability
git commit -m "feat: add observability to deployment pipeline"
All feature branch commits collapse into one commit on main. Clean history, easy rollback (one commit reverts the entire feature).
Problem: You lose the granularity of the feature branch’s development history. The squash commit is a black box.
When to use: GitOps pipelines where you want one logical change = one commit = easy rollback. Most CI/CD systems map commits to deployments, so a clean commit per feature makes rollback trivial.
%%{ init: { 'look': 'handDrawn' } }%%
graph LR
A[feature/add-logs] -->|5 commits| B[ squash merge]
B --> C[1 commit on main]
C --> D[CI detects commit<br/>triggers deployment]
D --> E[Rollback = git revert<br/>1 commit undoes all 5]
Strategy 3: Interactive Rebase + Merge
git rebase -i HEAD~5 # Rewrite commits interactively
git checkout main
git merge feature/add-observability # Fast-forward
Clean, linear history. You control exactly what each commit says before it lands on main.
Problem: Requires --force-with-lease push. If two people are working on the same branch, rebasing rewrites history and causes conflicts on the next pull.
When to use: Open-source projects, large teams with well-defined PR processes, or when your team values a clean git log above all else.
GitOps-Specific Considerations
%%{ init: { 'look': 'handDrawn' } }%%
graph LR
A[main branch] --> B[CI detects new commit]
B --> C[GitOps operator<br/>ArgoCD / Flux]
C --> D[Sync to cluster]
D --> E{Deployment succeed?}
E -->|Yes| F[Commit tagged as deployed]
E -->|No| G[Rollback = git revert]
G --> H[CI detects revert<br/>syncs previous commit]
For GitOps to work cleanly:
- Squash merges make rollback reliable — one commit = one deployment unit
- Commit messages become the changelog — write them like release notes, not work logs
- Atomic commits — don’t mix “fix typo” with “add feature” in the same squash commit
Which Strategy to Choose
| Scenario | Strategy |
|---|---|
| Active feature branch with WIP commits | Squash merge |
| Small, atomic PRs that are review-ready | Rebase + merge |
| Auditing requirement — full history | Regular merge |
| GitOps with ArgoCD/Flux | Squash merge |
| Monorepo with many contributors | Rebase + merge |
The goal isn’t purity — it’s a history you can actually use when something breaks at 2am.
The other half of that is release structure: which branches exist, and what a version number is allowed to mean. I covered that separately in GitFlow and semantic versioning.