Files
josh-sync/docs/adr/013-non-destructive-adoption.md

57 lines
2.8 KiB
Markdown
Raw Permalink Normal View History

2026-05-26 10:38:20 +03:00
# ADR-013: Non-destructive adoption merge for existing subrepos
**Status:** Accepted
**Date:** 2026-04
Release 2.2.0 — unified onboard strategy + safety hardening Folds `josh-sync adopt` into `josh-sync onboard <target>` as the adopt strategy alongside the original reset flow. Strategy resolves by precedence: --mode flag > targets[].history_lock config (preserve|rewrite) > auto-detect via `git ls-remote --heads`. `josh-sync adopt` is kept as a thin alias. Adds the new `targets[].history_lock` config field (validated at parse time) and folds `<target>/adopt.json` state into `<target>/onboard.json` with a `.strategy` field; legacy state files are read with a backward-compat fallback. Safety hardening (from a multi-angle review of the unification): - auto-detect distinguishes auth/network failure from empty repo - resume validates --mode against the strategy saved in state - `josh-sync adopt` rejects a conflicting --mode in the forwarded args - missing import-PR lookup dies in both strategies (was WARN+continue for reset, which could create duplicate import PRs on resume) - --restart durably removes the legacy adopt.json from the state branch - adopt_branch is now a subshell function (EXIT trap can't clobber callers) - strategy value validated after resolve/load (reset|adopt) - --mode with missing/empty value dies with a usage hint - migrate-pr against an adopt-strategy target dies with a specific hint - reset importing asserts archived_url is present (no "null" → git clone) End-to-end + CLI bats coverage added (tests/unit/adopt_e2e.bats, tests/unit/cli.bats). 72 tests, shellcheck clean. Makefile dist bundle header now correctly interpolates VERSION and line count. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 03:32:04 +01:00
**Update (2026-05):** The standalone `josh-sync adopt <target>` command described below has been folded into `josh-sync onboard <target>` as the `adopt` strategy (selectable via `--mode=adopt`, `targets[].history_lock: preserve`, or auto-detection when the subrepo has any branches). `josh-sync adopt` is retained as a thin CLI alias. The state file moved from `<target>/adopt.json` to `<target>/onboard.json` with a `.strategy` field; the legacy file is still read for backward compatibility. See the guide's Onboarding section.
2026-05-26 10:38:20 +03:00
## Context
Existing subrepos often already have real history, open PRs, and developer clones.
The original `import` then `reset` onboarding path establishes Josh-compatible
history by force-pushing the Josh-filtered monorepo history onto the subrepo.
That is correct for empty replacement repos, but it rewrites the active subrepo
branch and invalidates local clones.
We need a path for active subrepos where history must remain available on the
same branch and where open PR branches should stay based on the existing history.
## Decision
Add `josh-sync adopt <target>` as a separate workflow from `onboard` and `reset`.
Adoption imports the subrepo content into the monorepo, waits for the import PR
to merge, then creates one merge commit on each configured subrepo branch:
1. First parent: Josh-filtered monorepo HEAD
2. Second parent: existing subrepo HEAD
3. Tree: Josh-filtered monorepo tree
Josh follows first-parent history back to the monorepo, while Git still keeps
the old subrepo history reachable through parent 2. The push is a normal
fast-forward push from the existing subrepo HEAD to the adoption merge commit.
No force-push is used.
Before creating the adoption merge, josh-sync requires the existing subrepo tree
to match the Josh-filtered monorepo tree. If the trees differ, adoption aborts
and the user must merge the import PR or reconcile the branch contents first.
Adoption state is stored separately at `<target>/adopt.json` on the
`josh-sync-state` branch.
## Consequences
**Positive:**
- Existing subrepo history remains on the active branch.
- Existing clones can fast-forward instead of hard-resetting or re-cloning.
- Open PR branches remain based on reachable history.
- Josh-compatible ancestry is established without a destructive rewrite.
**Negative:**
- Adoption adds one synthetic merge commit to each adopted subrepo branch.
- Strict "no subrepo commit at all" adoption is impossible if the existing
subrepo branch must become connected to the Josh-filtered history without a
rewrite. A commit is needed to join the two histories.
- Tree equality is strict. If the monorepo import differs from the subrepo
content, adoption stops until the user resolves the mismatch.