SBPro dc2cdea360 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
"#"
2026-02-12 09:20:55 +03:00
"#"
2026-02-12 09:20:55 +03:00
"#"
2026-05-26 10:38:20 +03:00

josh-sync

Bidirectional monorepo ↔ subrepo sync via josh-proxy. Supports multiple sync targets from a single config.

Quick Start

1. Add config

Create .josh-sync.yml in your monorepo root:

schema_version: 2

josh:
  proxy_url: "https://josh.example.com"
  monorepo_path: "org/monorepo"
  monorepo_url: "git@gitea.example.com:org/monorepo.git"
  monorepo_auth: "ssh"               # optional, default "https"

targets:
  - name: "billing"
    subfolder: "services/billing"
    subrepo_url: "git@gitea.example.com:ext/billing.git"
    subrepo_auth: "ssh"
    branches:
      main: main
    exclude:                          # files excluded from subrepo (optional)
      - ".monorepo/"

bot:
  name: "josh-sync-bot"
  email: "josh-sync-bot@example.com"
  trailer: "Josh-Sync-Origin"

2. Add CI workflows

Copy from examples/ and customize paths/branches:

# .gitea/workflows/josh-sync-forward.yml
- uses: https://your-gitea.example.com/org/josh-sync@v1
  with:
    direction: forward
  env:
    SYNC_BOT_USER: ${{ secrets.SYNC_BOT_USER }}
    SYNC_BOT_TOKEN: ${{ secrets.SYNC_BOT_TOKEN }}
    SUBREPO_SSH_KEY: ${{ secrets.SUBREPO_SSH_KEY }}

3. Local dev (Nix)

Add josh-sync as a flake input, then:

{ inputs, ... }: {
  imports = [ inputs.josh-sync.devenvModules.default ];
}

Run josh-sync preflight to validate your setup.

Documentation

Versioning

josh-sync follows semver:

Bump Meaning Action required
Patch (1.x.y) Bug fixes only Safe to upgrade
Minor (1.y.0) New optional config fields, new commands, new action inputs Safe to upgrade; new features are opt-in
Major (y.0.0) Removed or renamed config fields, CLI flags, action inputs, env vars, or state format Read the Breaking Changes section in the changelog before upgrading

CI workflows pin to a floating major tag (e.g. @v1). A breaking change bumps the major version and moves the tag — @v1 workflows keep working until you explicitly update to @v2.

CLI

josh-sync sync [--forward|--reverse] [--force] [--target NAME[,NAME]] [--branch BRANCH]
josh-sync preflight
josh-sync import <target>
josh-sync adopt <target> [--restart]
josh-sync reset <target>
josh-sync onboard <target> [--restart]
josh-sync migrate-pr <target> [PR#...] [--all]
josh-sync status
josh-sync state show <target> [branch]
josh-sync state reset <target> [branch]

How It Works

  • Forward sync (mono → subrepo): pushes directly if clean, creates conflict PR if not. Uses --force-with-lease for safety.
  • Reverse sync (subrepo → mono): creates a PR by default. Add --force to bypass the PR and push directly to the mono branch (destructive).
  • Adoption: existing active subrepos can be connected to josh-sync with a normal fast-forward merge commit, preserving old subrepo history and avoiding force-push.
  • File exclusion: exclude patterns are embedded inline in the josh-proxy URL. Excluded files exist only in the monorepo.
  • Filter reconciliation: Changing the exclude list auto-creates a merge commit that connects old and new histories — no force-push needed.
  • Loop prevention: Josh-Sync-Origin: git trailer filters out bot commits.
  • State tracking: orphan branch josh-sync-state stores JSON per target/branch.

Dependencies

bash >=4, git, curl, jq, yq (mikefarah/yq v4+), openssh, rsync

The Nix flake bundles all dependencies automatically.

License

MIT

Description
No description provided
Readme MIT 617 KiB
Languages
Shell 97.6%
Nix 1.5%
Makefile 0.9%