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>
123 lines
4.5 KiB
JSON
123 lines
4.5 KiB
JSON
{
|
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
"title": "josh-sync configuration",
|
|
"description": "Configuration for bidirectional monorepo ↔ subrepo sync via josh-proxy",
|
|
"type": "object",
|
|
"required": ["schema_version", "josh", "targets", "bot"],
|
|
"properties": {
|
|
"schema_version": {
|
|
"type": "integer",
|
|
"const": 2,
|
|
"description": "Schema version. Required. Must be 2 for josh-sync v2.x configs."
|
|
},
|
|
"josh": {
|
|
"type": "object",
|
|
"required": ["proxy_url", "monorepo_path", "monorepo_url"],
|
|
"properties": {
|
|
"proxy_url": {
|
|
"type": "string",
|
|
"description": "Josh-proxy URL (no trailing slash)",
|
|
"pattern": "^https?://"
|
|
},
|
|
"monorepo_path": {
|
|
"type": "string",
|
|
"description": "Repo path as josh-proxy sees it (org/repo)"
|
|
},
|
|
"monorepo_url": {
|
|
"type": "string",
|
|
"description": "Monorepo git clone URL — git@host:org/repo.git (SSH) or https://host/org/repo.git. The gitea host is derived from this URL."
|
|
},
|
|
"monorepo_auth": {
|
|
"type": "string",
|
|
"enum": ["https", "ssh"],
|
|
"default": "https",
|
|
"description": "Auth method for cloning the monorepo. SSH uses the user's ssh-agent / ~/.ssh/config; HTTPS injects ${SYNC_BOT_USER}:${SYNC_BOT_TOKEN} into the URL."
|
|
}
|
|
}
|
|
},
|
|
"targets": {
|
|
"type": "array",
|
|
"minItems": 1,
|
|
"items": {
|
|
"type": "object",
|
|
"required": ["name", "subfolder", "subrepo_url", "branches"],
|
|
"properties": {
|
|
"name": {
|
|
"type": "string",
|
|
"description": "Unique target identifier"
|
|
},
|
|
"subfolder": {
|
|
"type": "string",
|
|
"description": "Monorepo subfolder path"
|
|
},
|
|
"josh_filter": {
|
|
"type": "string",
|
|
"description": "Josh filter expression (auto-derived from subfolder if omitted)",
|
|
"pattern": "^:"
|
|
},
|
|
"subrepo_url": {
|
|
"type": "string",
|
|
"description": "External subrepo git URL (HTTPS or SSH)"
|
|
},
|
|
"subrepo_auth": {
|
|
"type": "string",
|
|
"enum": ["https", "ssh"],
|
|
"default": "https",
|
|
"description": "Auth method for subrepo"
|
|
},
|
|
"subrepo_token_var": {
|
|
"type": "string",
|
|
"description": "Env var name for per-target HTTPS token (default: SUBREPO_TOKEN)"
|
|
},
|
|
"subrepo_ssh_key_var": {
|
|
"type": "string",
|
|
"description": "Env var name for per-target SSH key (default: SUBREPO_SSH_KEY)"
|
|
},
|
|
"branches": {
|
|
"type": "object",
|
|
"description": "Branch mapping: mono_branch → subrepo_branch",
|
|
"additionalProperties": {
|
|
"type": "string"
|
|
}
|
|
},
|
|
"forward_only": {
|
|
"type": "array",
|
|
"items": { "type": "string" },
|
|
"default": [],
|
|
"description": "Branches that only sync mono → subrepo (never reverse)"
|
|
},
|
|
"exclude": {
|
|
"type": "array",
|
|
"items": { "type": "string" },
|
|
"default": [],
|
|
"description": "File/directory patterns to exclude from sync via josh :exclude filter. Josh pattern syntax: 'dir/' for directories, '*.ext' for globs, '**/dir/' for nested matches. Patterns are embedded inline in the josh-proxy URL."
|
|
},
|
|
"history_lock": {
|
|
"type": "string",
|
|
"enum": ["rewrite", "preserve"],
|
|
"description": "Onboarding strategy override. 'preserve' selects the non-destructive adopt strategy (keeps subrepo history via an adoption merge); 'rewrite' selects the destructive reset strategy (force-pushes josh-filtered history). When omitted, `josh-sync onboard` auto-detects: subrepos with branches → adopt, empty subrepos → reset. The CLI flag `--mode={reset,adopt}` overrides this."
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"bot": {
|
|
"type": "object",
|
|
"required": ["name", "email", "trailer"],
|
|
"properties": {
|
|
"name": {
|
|
"type": "string",
|
|
"description": "Git commit author name for sync commits"
|
|
},
|
|
"email": {
|
|
"type": "string",
|
|
"description": "Git commit author email"
|
|
},
|
|
"trailer": {
|
|
"type": "string",
|
|
"description": "Git trailer key for loop prevention"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|