Files
josh-sync/CHANGELOG.md

208 lines
12 KiB
Markdown
Raw Normal View History

# 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](docs/config-reference.md#targets-section).
- **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 `die`s 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](docs/adr/012-explicit-monorepo-url.md).
- **`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
```yaml
# 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](https://code.itkan.io/pe/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`)