Merge Strategies in GitOps Repos - Squash, Rebase, and Main-Branch Discipline
Status: Active
Last Updated: 2026-08-26
Category: GitOps - Workflow
Prerequisites: git-fundamentals, gitops
Time: 2 hours
Tags: git, merge, squash, rebase, gitops, release, atomic-commits
Summary
How to choose between merge, squash, and rebase commits in a GitOps repository, why every manifest change must be a single atomic commit, and how to tag releases so reconcilers and rollback procedures always have an unambiguous target.
๐ฏ What You'll Learn
By the end of this article, you'll be able to:
- โ Pick the right merge strategy per branch type (feature vs hotfix vs dependency bump)
- โ Keep main deployable at every commit
- โ Write atomic commits where one logical change = one revertable unit
- โ Tag releases and point reconcilers at fixed revisions
- โ Recover cleanly when a bad merge reaches production
Table of Contents
- Context / Why This Matters
- Merge vs Squash vs Rebase
- Main-Branch Discipline
- Atomic Manifest Commits
- Release Tagging
Context / Why This Matters
In a GitOps repo the commit history is the deployment history. A normal application repo's messy history is cosmetic; a manifests repo's messy history makes git revert dangerous, rollbacks ambiguous, and audits incomplete. gitops ties every change to a ticket, a branch, and a Forgejo Action โ merge strategy is what keeps that chain clean end-to-end.
This article applies general Git practice from git-fundamentals to the specific constraints of declarative infrastructure, complementing cicd-concepts.
Merge vs Squash vs Rebase
| Strategy | History contains | Best for | Avoid for |
|---|---|---|---|
Merge commit (--no-ff) |
All WIP commits + merge node | Long-lived integration branches, hotfixes needing full forensic trail | Manifest repos (pollutes deployment history) |
Squash (--squash) |
Exactly one commit per MR | Feature work, docs changes โ default choice | Multi-part changes that should revert independently |
Rebase (rebase + ff) |
Linear history, each logical step preserved | Carefully crafted PRs with meaningful steps | Shared/public branches (rewrites others' history) |
Recommendation for fogserv.cloud
Manifests / config repos : SQUASH. One MR = one commit = one deployment event.
App source repos : developer's choice; squash preferred.
Hotfixes : SQUASH with ticket ID in the subject line.
Dependency bumps : SQUASH, batched weekly into one MR per ecosystem.
Rationale: Flux/Argo sync on commit SHA. With squash, "which commit changed this resource?" has exactly one answer, and reverting a deployment is one git revert <sha> instead of untangling interleaved WIP commits.
Enforcing it in Forgejo
Set protected-branch settings on main:
- Require signed linear history or squash-merge only
- Require passing Actions checks (cicd-concepts)
- Require at least one approval for paths under
clusters/prod/**
Main-Branch Discipline
main in a GitOps repo is not "work in progress" โ it is what production will look like after the next reconcile. Rules:
- Every commit on main must be applyable. CI renders manifests (
kustomize build,helm template) and fails the check if rendering breaks. Never merge "to test". - No direct pushes. Everything arrives via MR, even one-line fixes โ that is what creates the audit trail gitops requires.
- Staging before prod. Same commit promoted by directory overlay (
clusters/stagingโclusters/prod), never divergent edits. - Revert forward-looking: to undo a bad change, revert the commit; do not push a "counter-change" that adds confusion.
- Short-lived branches. Merge within days; stale branches drift against a moving main and produce surprise conflicts exactly when you can least afford them.
Branch naming tied to tickets
git checkout -b ticket-142/api-replica-autoscaling # Forgejo Issue #142
The ticket number in the branch name (and squashed commit subject) links deployment telemetry back to the issue automatically, as required by the gitops workflow.
Atomic Manifest Commits
One logical change = one MR = one squashed commit. An atomic commit:
- Changes only resources related to its purpose (image bump + its HPA tweak: fine; image bump + unrelated ingress rewrite: no).
- Leaves rendered output valid at that SHA โ CI verifies.
- Can be reverted alone without breaking neighbors.
Example: good vs bad diff scope
# GOOD - atomic: one purpose, self-consistent
clusters/prod/apps/api/deployment.yaml
-image: harbor.fogserv.cloud/prod/api:2.3.1
+image: harbor.fogserv.cloud/prod/api:2.3.2
clusters/prod/apps/api/kustomization.yaml
-images:
- - newTag: 2.3.1
+images:
+ - newTag: 2.3.2
# BAD - two unrelated concerns entangled
clusters/prod/apps/api/deployment.yaml (image bump)
clusters/prod/apps/web/ingress.yaml (unrelated TLS change)
If both were merged atomically-bad and the TLS change broke traffic, reverting would also roll back the API image โ or vice versa. Split them.
Commit message convention
<type>(<scope>): <summary> (#ticket)
api(deploy): bump api to 2.3.2 for CVE-2026-1234 (#142)
Rendered with kustomize 5.4.0; staging soak passed 24h.
Types: deploy, infra, policy, docs, chore. The KB update demanded by documentation-as-code rides along in the same MR whenever behavior changes.
Release Tagging
Tags give reconcilers and humans a stable name for "the state we certified".
# After staging soak passes
git tag -a prod-v2.3.2 -m "Prod promotion of api 2.3.2, ticket #142"
git push origin prod-v2.3.2
Usage patterns:
- Pin environments via Kustomize overlays referencing tags, or record the deployed SHA in the Forgejo Issue at promotion time.
- Rollback becomes trivially precise:
git revertrange bounded byprod-v2.3.2..prod-v2.3.3, then retag. - Never move or delete tags on released environments โ treat them like immutable infrastructure (immutable-infrastructure).
Flux note: pin the source ref.name to a branch but record the resolved SHA per environment; Argo CD users can pin targetRevision to a tag directly per app.
Practical Examples
Example 1: Clean hotfix lifecycle end-to-end
# 1. Branch from main
git checkout -b hotfix/ticket-201-ingress-tls main
# 2. Minimal fix, single concern
$EDITOR clusters/prod/ingress/web.yaml
git add clusters/prod/ingress/web.yaml
git commit -m "ingress(web): restore TLS secret name after rotation (#201)"
# 3. Push, MR, squash-merge once checks pass
git push origin hotfix/ticket-201-ingress-tls
# 4. If it was wrong: surgical undo
git revert <squash-sha>
git push # MR auto-created; reconciler converges after merge
Example 2: Batched dependency bumps
One weekly MR titled chore(deps): weekly bumps 2026-W35, containing all Renovate-style updates across overlays. Squashed to one commit; if any bump misbehaves, bisect within the MR branch before merge, not after โ post-merge, revert the whole batch and re-split rather than debugging in prod.
Troubleshooting & Common Pitfalls
| Problem | Cause | Fix |
|---|---|---|
| Revert of a merge commit does nothing | Default revert assumes non-merge parent | Use git revert -m 1 <sha>, or prefer squash merges so this never arises |
| Reconciler stuck on old revision | Force-push rewrote main history | Never force-push main; recover by pushing a corrective commit |
| Two MRs conflict over generated lockfile | Parallel automation writing same file | Serialize automation jobs or use a dedicated bot branch with rebase |
| "It worked in staging" but prod differs | Divergent hand-edits instead of overlay promotion | Diff overlays: diff -r clusters/staging clusters/prod; fix by refactoring to parameterized base |
| Deployment history untraceable | Merge commits with WIP noise | Switch repo policy to squash-only on protected main |
Next Steps / Ops Actions
- Configure Forgejo protected-branch rules for
clusters/prod/**. - Add render-check (
kustomize build/helm template) to the pipeline per cicd-concepts. - Guard what those commits may contain with admission policies: policy-as-code.
- When a bad merge ships anyway, follow the recovery path in drift-detection-runbook and rollback-strategies.
Sources & Related
External references consulted:
- https://git-scm.com/book/en/v2/Distributed-Git-Maintaining-a-Project
- https://www.atlassian.com/git/tutorials/merging-vs-rebasing
- https://opengitops.dev/
Related knowledge-base articles:
- git-fundamentals โ underlying Git mechanics
- gitops โ governing workflow these conventions serve
- cicd-concepts โ pipeline gates behind merges
- immutable-infrastructure โ why tags are immutable too
Change Log
2026-08-26
- Initial creation: merge strategy matrix, main-branch rules, atomic-commit guidance, release tagging, troubleshooting table.