Skip to main content
RunBook Academy

Git, CI/CD & GitOpsIV · Commit Graph and HistoryCommit Graph and History

The octopus and its cost — when multi-parent merges help and when they hurt

Intermediate⏱ ~20 mingit

What you'll learn

  • Identify when an octopus merge is the right tool: many clean topic branches, no conflicts
  • Recognise the costs of an octopus merge: parent-count growth, ambiguous log output, and harder first-parent walks
  • Recognise the cost of deep merge chains: merge-base computation cost, ambiguous ancestry, and harder `git blame`
  • Choose between octopus and a sequence of two-parent merges based on the team's merge frequency and conflict profile
  • Use `git log --merges --first-parent` to enumerate the merge events on the trunk without descending into merged branches
  • Identify when a chain of merges has produced a graph that should be flattened by a squash or a rebase

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.

An octopus merge is a multi-parent merge commit - four, eight, or sometimes dozens of parents in one go. It is the right tool for a specific situation (many clean topic branches merging into a long- running trunk on a fixed cadence) and the wrong tool for almost everything else. The cost of an octopus is not the merge itself; it is the cost the merge imposes on every downstream operation that walks the resulting DAG. This lesson collects the cases where octopus helps, the cases where it hurts, and the broader cost of deep merge chains.

When octopus is the right tool

Octopus is for clean integration of many branches at once. The canonical use case is a long-running trunk (e.g. main) that periodically absorbs several topic branches whose work has been kept green, with no overlap between them.

# Periodic integration: main absorbs three clean topic branches
git checkout main
git merge feature-a feature-b feature-c
# Merge made by the 'octopus' strategy.
# 4d2c8e0 (main) Merge branches 'feature-a', 'feature-b', and 'feature-c'
flowchart LR
    M["octopus M\nparents: A, B, C, D"] --> A["A (main)"]
    M --> B["B (feature-a)"]
    M --> C["C (feature-b)"]
    M --> D["D (feature-c)"]

The benefits:

  • One merge commit absorbs N branches. Without octopus, three topic branches would require three merge commits on the trunk. With octopus, one commit does the work, and the trunk history stays tidy.
  • No merge commit pollution. Each topic branch’s individual commits are reachable via the corresponding parent edge but are not enumerated by git log --first-parent main. The trunk history shows one event per integration cycle.
  • Atomic integration. All three branches either merge together or none of them do. There is no partial state where the trunk has feature-a but not feature-b.

The preconditions:

  • No conflicts. Octopus refuses on any conflict; this is a hard constraint, not a preference. The branches must have been kept disjoint in their changes.
  • Clean tests. Each tip should be green, because the merged result is tested as a unit, not piecewise.

When octopus is the wrong tool

Octopus is the wrong tool when:

  • Any branch conflicts. The merge aborts and the user must fall back to sequential recursive merges anyway.
  • The branches are short-lived. A feature branch that lives for two days should be merged with a regular two-parent merge, not an octopus. Octopus is for branches whose lifetime justifies a multi-parent integration event.
  • The audit trail needs per-branch attribution. An octopus merge’s message lists every branch that was merged, but the merged tree is a single tree. Auditors who need to ask “what changed in feature-b?” must walk the second parent to find out, which is harder than walking a dedicated merge commit per branch.

The cost of deep merge chains

An octopus merge has more than two parents. A merge commit in a chain of merges - backports of backports, recursive integrations - has a long first-parent chain to walk and a wide non-first-parent closure to skip. Every additional merge step adds:

  • One commit object per merge. The DAG grows by one commit per merge event.
  • One parent edge to traverse on the trunk. The first-parent chain grows by one edge, which is what git log --first-parent walks.
  • One additional non-first-parent closure. Each merged-in branch’s commits are reachable via a non-first-parent edge, and any operation that does not use --first-parent will descend into them.
# Trunk walk on a deep merge chain (cost grows linearly with chain depth)
git log --oneline --first-parent main

# Full walk: descends into every merged-in branch (cost grows much faster)
git log --oneline main

For a chain of N merges each absorbing M feature commits, the full walk enumerates O(N * M) commits; the first-parent walk enumerates O(N). The cost difference is exactly why production scripts should use --first-parent and why production displays should use --graph --oneline --decorate --all rather than git log without flags.

flowchart LR
    M1["merge M1\n2 parents"] --> M2["merge M2\n2 parents"]
    M2 --> M3["merge M3\n2 parents"]
    M3 --> M4["merge M4\n2 parents"]
    M1 --> F1["feature 1 (3 commits)"]
    M2 --> F2["feature 2 (5 commits)"]
    M3 --> F3["feature 3 (4 commits)"]
    M4 --> F4["feature 4 (2 commits)"]

In the diagram, the first-parent walk visits four commits (M1 through M4); the full walk visits those four plus every feature commit (14 in this example). For a deep chain with many features, the difference is two orders of magnitude.

When to flatten with a squash or rebase

A long-lived branch that has accumulated dozens of merge commits and feature commits can be flattened into a single clean branch tip by:

  • Squash merge (git merge --squash <branch>): produces a single commit on the trunk with the merged-in branch’s tree. The branch’s individual commits are not in the trunk’s DAG; only the resulting tree is.
  • Rebase (git rebase main on the feature branch): rewrites the feature branch’s commits with new parents anchored at main’s tip. The new commits have new OIDs; the original commits are orphaned unless a ref points at them.

Both operations simplify the DAG. Both produce new OIDs and break the chain of identity that any external system (signatures, attestations, deployed artifacts) may have relied on. They are correct tools when the history is being archived or when the audit requirements permit the rewrite; they are wrong tools when the chain of identity must be preserved.

# Squash-merge: one commit, one tree, no merge commit
git merge --squash feature/iam-rotation
git commit -m "Squashed feature/iam-rotation"

# Rebase: every feature commit rewritten with new parents
git checkout feature/iam-rotation
git rebase main
# Each rebased commit has a new OID; the originals are orphaned

Enumerating merge events on the trunk

git log --merges --first-parent main enumerates every merge commit on the trunk without descending into the merged-in branches. This is the canonical “what were the integration events on this branch?” view.

git log --merges --first-parent --oneline main
# 4d2c8e0 Merge branch 'feature/iam-rotation' into main
# 9f3c1d7 Merge branch 'feature/bump-terraform' into main
# 6f4e5a6 Merge branch 'feature/initial-setup' into main
git rev-list --merges --first-parent --count main
# 3

The number returned is the count of merge events on the trunk - a useful operational metric for “how much integration has this trunk absorbed?”.

Production discipline

  1. Use octopus for periodic clean integration. Weekly trunk pulls from clean topic branches are the canonical example. Do not use octopus for active development or any time a conflict is plausible.
  2. Set a parent-count budget. A merge commit with more than eight parents is unusual and may indicate a workflow that should be reorganised. Inspect it before approving.
  3. Use --first-parent for trunk views. Every long-running trunk benefits from first-parent walks in log, blame, and release-notes scripts. Without --first-parent, the cost grows linearly with merged-in branch complexity.
  4. Flatten when the audit trail permits. A repository with dozens of criss-cross merges on a long-lived branch is hard to reason about. A squash or rebase flattens the DAG at the cost of rewriting identity. The decision is an audit trade-off.

Cross-course references

  • GitOps with Argo CD - Part VIII (SyncPhasing) discusses the GitOps analogue of octopus: a single sync event that applies manifests from multiple branches at once. The GitOps controller either succeeds atomically or fails atomically, mirroring the octopus semantics.
  • Terraform for Production Sysadmins - Part XI (PRWorkflows) warns against octopus-style merges of multiple Terraform modules in one PR: conflicts across modules are common, and the octopus refusal forces a fallback to sequential merges anyway.
  • Linux for Production Sysadmins - Part XXXIV (ConfigMgmt) describes the kernel’s octopus merge strategy in merge strategies as a model for Git’s: a tidy tool for clean integration, a refusal-prone tool for active conflict resolution.

Quiz

Knowledge check · 4 questions

  1. Q1. Which scenario is the right use case for an octopus merge?

  2. Q2. An octopus merge will refuse to complete if any of the merged-in branches conflicts with the checked-out branch.

  3. Q3. Which `git log` invocation enumerates every merge commit on a branch's first-parent chain without descending into the merged-in branches, and what does the count represent?

  4. Q4. Decide whether to use an octopus merge or a sequence of two-parent merges for a release-prep integration, and justify the choice based on the conflict profile and audit requirements.

    A team is preparing a release and needs to integrate four long-running topic branches (`feature-a`, `feature-b`, `feature-c`, `feature-d`) into the release branch. The four branches have been kept green and their changes are largely disjoint, but `feature-b` and `feature-d` both modify a shared Terraform module's variables file. The team policy requires every merge into the release branch to be a single atomic event with one merge commit.

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