Files
josh-sync/CHANGELOG.md

12 KiB

Changelog

2.2.2

Fixes

  • Forward sync no longer skips the entire action when the checked-out HEAD commit has the configured sync trailer. This allows valid multi-hop setups where one repo receives sync commits from an upstream repository and forwards its filtered subtree to another repository. Reverse sync still filters bot commits by trailer for loop prevention.

2.2.1

Fixes

  • Initial import now force-adds copied subrepo files so tracked files that match the copied .gitignore are included in the import PR.

2.2.0

Changes

  • Unified onboarding via josh-sync onboard <target>. The previously separate adopt workflow is now an onboarding strategy alongside the original reset flow. Strategy is resolved by precedence: --mode={reset,adopt} flag → targets[].history_lock config (preserve → adopt, rewrite → reset) → auto-detect via git ls-remote --heads on the subrepo (heads → adopt, empty → reset). The chosen strategy is logged at preflight (and re-announced on every resume).
  • josh-sync adopt kept as a back-compat CLI alias for josh-sync onboard --mode=adopt. Existing in-flight adoptions (state at <target>/adopt.json on the josh-sync-state branch) continue to resume — read_onboard_state falls back to that legacy file with an implicit strategy: adopt and strips the legacy .mode field. Pre-unification onboard.json files (no .strategy) are read as strategy: reset.
  • New targets[].history_lock config field (preserve | rewrite) — pins the onboarding strategy independently of the subrepo's current emptiness. Validated at config parse time. See Config Reference.
  • End-to-end bats coverage — tree-mismatch surfacing, idempotency, merge shape (ADR-005 trailer, ADR-008 first-parent ordering), fast-forward push semantics, reverse-sync loop guard, linearize-fallback selection, resume from every checkpoint, strategy resolution precedence, legacy-state migration, and the CLI surface (flag parsing, alias forwarding, error messages).

Safety

  • Auto-detect now distinguishes auth/network failures from genuinely empty repos. A failing git ls-remote previously fell through to reset (destructive force-push); it now dies with a clear "pass --mode explicitly" message.
  • Resume validates --mode against the strategy already saved in state. Conflicting flags die with a --restart hint instead of being silently ignored.
  • josh-sync adopt rejects a conflicting --mode. Previously the user-supplied flag silently won.
  • Missing import-PR lookup now dies in both strategies. The reset path used to WARN and continue without recording the PR number, leading to duplicate import PRs on resume.
  • --restart durably removes the legacy <target>/adopt.json from the state branch so it can't silently resurrect via the read fallback.
  • adopt_branch is now a subshell function — its EXIT trap can no longer clobber callers' traps (test reporting, etc.).
  • Strategy value is validated after resolve/load (reset|adopt); unknown/corrupt values die with a --restart hint.
  • --mode with a missing/empty value dies with a usage hint instead of crashing under set -u.
  • migrate-pr against an adopt-strategy target dies with a specific message rather than the misleading "Run onboard first" loop.
  • Reset import step asserts archived_url is present instead of silently flowing the literal string "null" into git clone.

ADR-013 updated with a status note pointing at the unified command.

Fixes

  • Makefile dist/josh-sync bundle header now correctly interpolates VERSION and line count (was emitting empty values due to make's $(...) swallowing the shell command substitution).

2.1.0

Breaking Changes

None.

Features

  • adopt command for non-destructive existing subrepos: josh-sync adopt <target> imports existing subrepo content into the monorepo, waits for the import PR to merge, then creates an adoption merge commit on the subrepo. The adoption merge uses the Josh-filtered monorepo commit as first parent and the existing subrepo commit as second parent, preserving old subrepo history while establishing Josh-compatible ancestry.
  • Resumable adoption state: adoption progress is stored on josh-sync-state at <target>/adopt.json, separate from sync and onboard state.

Safety

  • Adoption requires the subrepo tree to match the Josh-filtered monorepo tree before creating the merge commit.
  • Adoption pushes with a normal fast-forward push only. It never force-pushes.

2.0.0

Breaking Changes

  • schema_version: 2 is now required. v1 configs are rejected with a migration pointer. Add schema_version: 2 at the top of .josh-sync.yml.
  • josh.monorepo_url is now required. Set it to your monorepo's git clone URL — git@host:org/repo.git (SSH), ssh://..., or https://host/org/repo.git. Removes the silent inference of the gitea host from the first target's subrepo_url, which silently misbehaved when the monorepo and a target lived on different gitea instances. See ADR-012.
  • MONOREPO_API derivation changed. The default is now https://{monorepo_url's host}/api/v1/repos/{monorepo_path}. The env-var override is preserved as an escape hatch for custom API endpoints.

Migration

# Before (v1.x)
josh:
  proxy_url: "https://josh.example.com"
  monorepo_path: "org/monorepo"

# After (v2.0.0)
schema_version: 2
josh:
  proxy_url: "https://josh.example.com"
  monorepo_path: "org/monorepo"
  monorepo_url: "git@gitea.example.com:org/monorepo.git"   # required
  monorepo_auth: "ssh"                                       # optional, default "https"

If you previously set the MONOREPO_API env var to work around the first-target-host bug, you can remove it — the new derivation gets it right from monorepo_url.

Features

  • Multi-host configs supported. The monorepo and any subrepo may live on different gitea instances. Target ordering no longer affects how the monorepo's host is resolved.
  • SSH clone of the monorepo via monorepo_auth: ssh. Uses the user's ssh-agent / ~/.ssh/config; no token required for the clone itself. PR creation still uses GITEA_TOKEN.
  • Symmetric config shape. Monorepo and subrepos now both declare a URL plus an auth method.

Fixes

  • First-target ordering no longer load-bearing on MONOREPO_API derivation (was gitea_host=.[0].gitea_host in v1.x).

1.5.0

Breaking Changes

None.

Features

  • --force flag for reverse sync: josh-sync sync --reverse --force bypasses the PR step and force-pushes the subrepo's state directly onto the monorepo branch. Also skips the skip-dirty guard. Only valid with --reverse. Returns status forced.

1.4.0

Breaking Changes

None.

Features

  • schema_version config field: New optional integer field in .josh-sync.yml. Defaults to 1 when absent. josh-sync fails fast with a clear error if an unsupported version is present — safe path for future config migrations without silent misinterpretation.

Fixes

  • Linearize fallback applying wrong diff: Fallback for failed cherry-pick was diffing from HEAD to the commit's full tree instead of from the commit's own parent, causing unrelated file deletions. Now diffs from the commit's own parent (${sha}^ / ${sha}^1).
  • Linearize fallback for already-applied merge commits: When cherry-pick -m 1 produces an empty result because a merge's changes were already applied by a prior cherry-pick, the commit is now skipped instead of attempting a failing git apply.
  • Linearize fallback output: Suppressed git noise from cherry-pick, checkout, and commit during linearization; added delimiters and consistent indented log lines.

Docs

  • Workflow guide: removed redundant step-by-step subsections (table already covers them); tightened ADR-011 alternatives; removed stale version pins.
  • README: added Versioning section with semver policy table and floating tag explanation.

1.3.0

Breaking Changes

None.

Features

  • Linearize fallback for reverse sync: When josh-proxy rejects a push due to unmappable merge commits, reverse sync automatically falls back to linearizing the history — cherry-picking regular commits individually and squashing only the problematic merge commits via cherry-pick -m 1. Preserves individual commit granularity while handling complex merge topologies that josh cannot map. PR body notes when linearization was used.

Fixes

  • Reverse sync human commit detection: Fixed --ancestry-path returning empty when mono-filtered has advanced past the last forward sync. Any monorepo commit (even outside the subfolder) causes josh to create a new filtered commit, breaking the ancestry path to subrepo HEAD. Now uses git merge-base to find the last connected point.
  • State not updated on false skip: When trees differ but no human commits are found, reverse sync now returns skip-dirty instead of skip. State is not updated, so the system retries on the next run instead of silently dropping commits.

Docs

  • Expanded troubleshooting guide for "Josh rejected push" with root cause analysis and prevention advice.
  • Added ADR-011: Linearize fallback for reverse sync.
  • Added recommended Git workflow section to the guide.

1.2.0

Breaking Changes

None.

Features

  • File exclusion: exclude config field removes files/directories from the subrepo at the josh-proxy transport layer. Patterns are embedded inline in the josh-proxy URL using :exclude[::pattern,...] syntax — no extra files to generate or commit.
  • Filter change reconciliation: When the josh filter changes (e.g., adding/removing exclude patterns), josh-sync automatically creates a reconciliation merge commit that connects old and new histories. No manual reset or force-push required.
  • Tree comparison guard: Reverse sync now compares subrepo tree to josh-filtered tree before checking commit log. Skips immediately when trees are identical, avoiding false positives from reconciliation merge history.
  • Unrelated histories detection: Forward sync detects when histories are unrelated (no common ancestor) and falls back to reconciliation instead of creating a useless conflict PR.

Fixes

  • Pre-v1.2 state compatibility: When upgrading from v1.0/v1.1 (no josh_filter stored in state), the old filter is derived from subfolder so reconciliation triggers correctly.
  • Reconciliation merge parent order: Josh-filtered history is always first parent so josh-proxy can follow first-parent traversal back to the monorepo.
  • Reverse sync --ancestry-path flag prevents old subrepo history from leaking through reconciliation merge parents.
  • PR body \n now renders as actual newlines instead of literal text.
  • Conflict result no longer updates sync state (added continue to skip state write).
  • action.yml now copies VERSION file for correct --version output in CI.
  • .gitignore now includes dist/ and .env.

1.1.0

Breaking Changes

None.

Features

  • onboard command: Interactive, resumable workflow for importing existing subrepos into the monorepo. Walks through: prerequisites check, import (creates PRs), wait for merge, reset (pushes josh-filtered history). Checkpoint/resume at every step.
  • migrate-pr command: Migrates open PRs from an archived subrepo to the new one. Supports interactive selection, --all flag, and specific PR numbers. Uses git apply --3way for resilient patch application.
  • Onboard state tracking: Stored on the josh-sync-state branch at <target>/onboard.json. Tracks step progress, import PR numbers, reset branches, and migrated PRs.

1.0.0

Initial release. Extracted from private-monorepo-example into a standalone reusable library.

Features

  • Bidirectional sync: forward (mono → subrepo) and reverse (subrepo → mono)
  • Multi-target support via .josh-sync.yml config
  • Per-target credential overrides (SSH keys, HTTPS tokens)
  • Force-with-lease safety for forward sync
  • Loop prevention via git trailers
  • State tracking on orphan branch (josh-sync-state)
  • Initial import and subrepo reset commands
  • Composite action for Gitea/GitHub CI
  • Nix flake with devenv module
  • Preflight validation checks
  • Config schema (JSON Schema)

Breaking Changes (vs. inline scripts)

  • Python + pyyaml replaced by yq-go (single static binary)
  • 7 separate scripts replaced by single josh-sync CLI
  • Consumer workflows use composite action (uses: https://your-gitea/org/josh-sync@v1)