Skip to content

SQUASH vs ITERATIVE

Two workflow modes that push directly to the destination, with different approaches to commit history.

Diagram Diagram
FeatureSQUASHITERATIVE
Commits created1 per sync1 per origin commit
History preservedNoYes
Handles mergesGracefullyMay fail
Bisect-friendlyNoYes
Initial sync speedFastSlower
ComplexityLowHigher

Recommended for most workflows. Use SQUASH when:

  • Destination history doesn’t need to match origin
  • You have complex merge histories
  • You want cleaner destination history
  • You’re syncing frequently (avoid commit spam)
  • You’re just mirroring without bidirectional sync
core.workflow(
name = "export",
mode = "SQUASH",
...
)

Copybara creates informative commit messages:

Project import generated by Copybara.
Changes:
- fix: correct validation logic
- feat: add user authentication
- docs: update README
GitOrigin-RevId: abc123def456

You can customize with metadata.squash_notes():

transformations = [
metadata.squash_notes(
prefix = "Sync from internal:\n\n",
show_author = True,
show_description = True,
oldest_first = True,
),
]

Use ITERATIVE when:

  • You need exact commit-to-commit mapping
  • Auditing requires preserved history
  • You need to git bisect on the destination
  • Each commit must be individually traceable
  • Linear history in origin (no merges)
core.workflow(
name = "export",
mode = "ITERATIVE",
...
)

Each destination commit includes:

Original commit message here
GitOrigin-RevId: abc123

Merge commits are handled transparently - all changes are combined:

Diagram

Merge commits can cause issues:

Diagram
Mode100 commits1000 commits10000 commits
SQUASHFastFastFast
ITERATIVEModerateSlowVery slow

SQUASH processes all changes at once; ITERATIVE must process each commit individually.

Both modes perform similarly for incremental syncs (only new commits are processed).

core.workflow(
name = "export-squash",
mode = "SQUASH",
transformations = [
metadata.squash_notes(
prefix = "Weekly sync from internal:\n\n",
show_author = True,
show_description = True,
),
metadata.add_header("Synced-From: internal"),
],
...
)
core.workflow(
name = "export-iterative",
mode = "ITERATIVE",
transformations = [
metadata.map_author({
"internal@corp.com": "external@example.com",
}),
metadata.expose_label("Reviewed-by"),
],
...
)
Diagram

You can switch modes between syncs, but be aware:

  • SQUASH → ITERATIVE: Works, but you’ll lose the “squash” point in history
  • ITERATIVE → SQUASH: Works, subsequent sync will squash all new changes

The state tracking (via GitOrigin-RevId) works across mode changes.