When josh-proxy rejects a reverse sync push due to unmappable merge commits, fall back to linearizing: cherry-pick regular commits individually, squash only the merge commits via cherry-pick -m 1. Also adds a recommended Git workflow section to the guide explaining where cross-branch merges should happen (monorepo) vs feature work (subrepo), and expands troubleshooting for the "josh rejected push" error with root cause analysis and prevention advice. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3.9 KiB
ADR-011: Linearize Fallback for Reverse Sync
Status: Accepted Date: 2026-03
Context
Josh-proxy rejects reverse sync pushes when the subrepo history contains merge commits whose parents it cannot map through the filter. This happens when:
- A long-lived branch (e.g.,
stage) is merged intomainvia a merge commit - That branch contains auto-sync merge commits or criss-cross merges with
main - Josh encounters the merge commit, tries to map both parents through the filter, and fails with a 500 error
This is a legitimate subrepo workflow — teams merge staging branches into main regularly. The sync tool should handle it without requiring teams to change their Git workflow.
Alternatives considered
-
Fail and log: The pre-v1.3 behavior. Leaves the sync stuck until someone manually intervenes. Bad for unattended cron-based sync.
-
Squash all commits into one: Create a single
commit-treewith the diff between josh-filtered base and subrepo HEAD. Simple but destroys all commit granularity — 10 unrelated commits appear as one blob on the monorepo PR. -
Rewrite subrepo history: Rebase or filter-branch the subrepo to remove problematic merges. Breaks the sync relationship with the monorepo (josh's SHA mapping becomes invalid) and forces all developers to re-clone.
-
Linearize: cherry-pick regular commits, squash only merges: Walk the human commits in order, cherry-pick non-merge commits as-is, and use
cherry-pick -m 1for merge commits. Preserves individual commit granularity for regular commits; only the problematic merge commits lose their multi-parent structure.
Decision
When the direct push through josh-proxy fails, fall back to option 4: linearize the history by cherry-picking onto the josh-filtered base.
How it works
- Direct
git pushthrough josh-proxy is attempted first (existing behavior) - If it fails, create a temporary branch from
mono-filtered/<branch> - Walk human commits (oldest-first, bot commits excluded) from the ancestry path:
- Regular commits (≤1 parent):
git cherry-pick <sha>— preserves author, date, and message - Merge commits (>1 parent):
git cherry-pick -m 1 <sha>— applies the merge's diff relative to its first parent as a single commit
- Regular commits (≤1 parent):
- If cherry-pick conflicts (rare — usually due to ordering issues), fall back to
git diff | git applywith the original author metadata - Push the linearized branch through josh-proxy
- PR body includes a note explaining that merge commits were squashed
What is preserved
- Individual non-merge commits (author, date, message, diff)
- The net effect of each merge commit (as a squashed single commit)
- The original commit list in the PR body for reference
What is lost
- The multi-parent structure of merge commits (they become single-parent)
- The distinction between "changes introduced by the merge" vs "changes from each parent" — the merge is represented as its diff from first parent
Consequences
Positive:
- Reverse sync no longer gets stuck on merge commits — handles the common case automatically
- Non-merge commits retain full granularity (no unnecessary squashing)
- Subrepo history is untouched — no rewriting, no broken sync relationship
- The PR body documents when linearization was used, so reviewers know
Negative:
- Merge commit semantics are lost on the monorepo side (they appear as regular commits)
- If a merge commit's changes conflict with a prior cherry-picked commit during linearization, the diff-apply fallback may produce a subtly different result than the original merge resolution
- Adds complexity to the reverse sync path (two code paths: direct push and linearize fallback)
Risk mitigation:
- The direct push is always tried first — linearization only activates when josh rejects the push
- The monorepo PR is still reviewed by humans before merging
- The original commit SHAs are listed in the PR body for cross-referencing