Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5e9b134168 | |||
| 1cb23a4411 |
33
CHANGELOG.md
33
CHANGELOG.md
@@ -1,5 +1,38 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 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
|
## 1.3.0
|
||||||
|
|
||||||
### Breaking Changes
|
### Breaking Changes
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ CI workflows pin to a floating major tag (e.g. `@v1`). A breaking change bumps t
|
|||||||
## CLI
|
## CLI
|
||||||
|
|
||||||
```
|
```
|
||||||
josh-sync sync [--forward|--reverse] [--target NAME[,NAME]] [--branch BRANCH]
|
josh-sync sync [--forward|--reverse] [--force] [--target NAME[,NAME]] [--branch BRANCH]
|
||||||
josh-sync preflight
|
josh-sync preflight
|
||||||
josh-sync import <target>
|
josh-sync import <target>
|
||||||
josh-sync reset <target>
|
josh-sync reset <target>
|
||||||
@@ -92,7 +92,7 @@ josh-sync state reset <target> [branch]
|
|||||||
## How It Works
|
## How It Works
|
||||||
|
|
||||||
- **Forward sync** (mono → subrepo): pushes directly if clean, creates conflict PR if not. Uses `--force-with-lease` for safety.
|
- **Forward sync** (mono → subrepo): pushes directly if clean, creates conflict PR if not. Uses `--force-with-lease` for safety.
|
||||||
- **Reverse sync** (subrepo → mono): always creates a PR, never pushes directly.
|
- **Reverse sync** (subrepo → mono): creates a PR by default. Add `--force` to bypass the PR and push directly to the mono branch (destructive).
|
||||||
- **File exclusion**: `exclude` patterns are embedded inline in the josh-proxy URL. Excluded files exist only in the monorepo.
|
- **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.
|
- **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.
|
- **Loop prevention**: `Josh-Sync-Origin:` git trailer filters out bot commits.
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ Global flags:
|
|||||||
Sync flags:
|
Sync flags:
|
||||||
--forward Forward only (mono → subrepo)
|
--forward Forward only (mono → subrepo)
|
||||||
--reverse Reverse only (subrepo → mono)
|
--reverse Reverse only (subrepo → mono)
|
||||||
|
--force Force-push subrepo state to mono branch (--reverse only, skips PR)
|
||||||
--target NAME Filter to target(s) — comma-separated for multiple (env: JOSH_SYNC_TARGET)
|
--target NAME Filter to target(s) — comma-separated for multiple (env: JOSH_SYNC_TARGET)
|
||||||
--branch BRANCH Filter to one branch
|
--branch BRANCH Filter to one branch
|
||||||
|
|
||||||
@@ -108,6 +109,7 @@ cmd_sync() {
|
|||||||
local filter_target="${JOSH_SYNC_TARGET:-}"
|
local filter_target="${JOSH_SYNC_TARGET:-}"
|
||||||
local filter_branch=""
|
local filter_branch=""
|
||||||
local config_file=".josh-sync.yml"
|
local config_file=".josh-sync.yml"
|
||||||
|
local force_flag=false
|
||||||
|
|
||||||
while [ $# -gt 0 ]; do
|
while [ $# -gt 0 ]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
@@ -117,10 +119,18 @@ cmd_sync() {
|
|||||||
--branch) filter_branch="$2"; shift 2 ;;
|
--branch) filter_branch="$2"; shift 2 ;;
|
||||||
--config) config_file="$2"; shift 2 ;;
|
--config) config_file="$2"; shift 2 ;;
|
||||||
--debug) export JOSH_SYNC_DEBUG=1; shift ;;
|
--debug) export JOSH_SYNC_DEBUG=1; shift ;;
|
||||||
|
--force) force_flag=true; shift ;;
|
||||||
*) die "Unknown flag: $1" ;;
|
*) die "Unknown flag: $1" ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
if [ "$force_flag" = true ] && [ "$direction" != "reverse" ]; then
|
||||||
|
die "--force requires --reverse"
|
||||||
|
fi
|
||||||
|
if [ "$force_flag" = true ]; then
|
||||||
|
export SYNC_REVERSE_FORCE=1
|
||||||
|
fi
|
||||||
|
|
||||||
parse_config "$config_file"
|
parse_config "$config_file"
|
||||||
|
|
||||||
# Forward sync
|
# Forward sync
|
||||||
@@ -263,6 +273,9 @@ _sync_direction() {
|
|||||||
log "WARN" "Trees differ but no human commits — skipping state update (will retry)"
|
log "WARN" "Trees differ but no human commits — skipping state update (will retry)"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
if [ "$result" = "forced" ]; then
|
||||||
|
log "WARN" "Target ${target_name}, branch ${branch}: force-pushed subrepo directly to mono — DESTRUCTIVE"
|
||||||
|
fi
|
||||||
|
|
||||||
# Update state (only on success)
|
# Update state (only on success)
|
||||||
local new_state
|
local new_state
|
||||||
|
|||||||
10
lib/auth.sh
10
lib/auth.sh
@@ -28,6 +28,16 @@ subrepo_auth_url() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ─── Monorepo Auth URL ─────────────────────────────────────────────
|
||||||
|
# Derives a push-capable HTTPS URL from MONOREPO_API.
|
||||||
|
# MONOREPO_API shape: https://<host>/api/v1/repos/<org/repo>
|
||||||
|
|
||||||
|
mono_auth_url() {
|
||||||
|
local api_host_path
|
||||||
|
api_host_path=$(echo "$MONOREPO_API" | sed 's|https://||; s|/api/v1/repos/|/|')
|
||||||
|
echo "https://${BOT_USER}:${GITEA_TOKEN}@${api_host_path}.git"
|
||||||
|
}
|
||||||
|
|
||||||
# ─── Remote Queries ─────────────────────────────────────────────────
|
# ─── Remote Queries ─────────────────────────────────────────────────
|
||||||
|
|
||||||
subrepo_ls_remote() {
|
subrepo_ls_remote() {
|
||||||
|
|||||||
53
lib/sync.sh
53
lib/sync.sh
@@ -240,6 +240,8 @@ reverse_sync() {
|
|||||||
|
|
||||||
log "INFO" "=== Reverse sync: subrepo/${subrepo_branch} → mono/${mono_branch} ==="
|
log "INFO" "=== Reverse sync: subrepo/${subrepo_branch} → mono/${mono_branch} ==="
|
||||||
|
|
||||||
|
local force_mode="${SYNC_REVERSE_FORCE:-0}"
|
||||||
|
|
||||||
# 1. Clone subrepo
|
# 1. Clone subrepo
|
||||||
git clone "$(subrepo_auth_url)" \
|
git clone "$(subrepo_auth_url)" \
|
||||||
--branch "$subrepo_branch" --single-branch \
|
--branch "$subrepo_branch" --single-branch \
|
||||||
@@ -286,9 +288,13 @@ reverse_sync() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$human_commits" ]; then
|
if [ -z "$human_commits" ]; then
|
||||||
log "INFO" "No new human commits in subrepo — nothing to sync"
|
if [ "$force_mode" = "1" ]; then
|
||||||
echo "skip-dirty"
|
log "WARN" "No human commits found — force mode: continuing anyway"
|
||||||
return
|
else
|
||||||
|
log "INFO" "No new human commits in subrepo — nothing to sync"
|
||||||
|
echo "skip-dirty"
|
||||||
|
return
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "INFO" "New human commits to sync:"
|
log "INFO" "New human commits to sync:"
|
||||||
@@ -400,16 +406,28 @@ reverse_sync() {
|
|||||||
|
|
||||||
log "INFO" "Pushed to staging branch via josh: ${staging_branch}${linearized:+ (linearized)}"
|
log "INFO" "Pushed to staging branch via josh: ${staging_branch}${linearized:+ (linearized)}"
|
||||||
|
|
||||||
# 6. Create PR on monorepo (NEVER direct push)
|
# 6. Publish: force-push directly (--force) or create PR (default)
|
||||||
local pr_body
|
if [ "$force_mode" = "1" ]; then
|
||||||
local linearize_note=""
|
log "WARN" "Force mode: pushing ${staging_branch} directly to mono/${mono_branch} — bypassing PR"
|
||||||
if [ "$linearized" = true ]; then
|
local mono_dir="${work_dir}/monorepo"
|
||||||
linearize_note="
|
git clone "$(mono_auth_url)" \
|
||||||
|
--branch "$staging_branch" --single-branch \
|
||||||
|
"$mono_dir" || die "Failed to clone monorepo staging branch for force-push"
|
||||||
|
git -C "$mono_dir" push --force "$(mono_auth_url)" \
|
||||||
|
"HEAD:refs/heads/${mono_branch}" \
|
||||||
|
|| die "Failed to force-push to mono/${mono_branch}"
|
||||||
|
log "INFO" "Force-pushed mono/${staging_branch} → mono/${mono_branch}"
|
||||||
|
echo "forced"
|
||||||
|
else
|
||||||
|
local pr_body
|
||||||
|
local linearize_note=""
|
||||||
|
if [ "$linearized" = true ]; then
|
||||||
|
linearize_note="
|
||||||
> **Note:** Merge commits were squashed during sync because the subrepo
|
> **Note:** Merge commits were squashed during sync because the subrepo
|
||||||
> history contained merges that josh-proxy cannot map (see ADR-011).
|
> history contained merges that josh-proxy cannot map (see ADR-011).
|
||||||
> Regular commits are preserved individually."
|
> Regular commits are preserved individually."
|
||||||
fi
|
fi
|
||||||
pr_body="## Subrepo changes
|
pr_body="## Subrepo changes
|
||||||
|
|
||||||
New commits from subrepo \`${subrepo_branch}\`:
|
New commits from subrepo \`${subrepo_branch}\`:
|
||||||
|
|
||||||
@@ -422,14 +440,15 @@ ${linearize_note}
|
|||||||
- [ ] No leaked credentials or environment-specific config
|
- [ ] No leaked credentials or environment-specific config
|
||||||
- [ ] CI passes"
|
- [ ] CI passes"
|
||||||
|
|
||||||
create_pr "${MONOREPO_API}" "${GITEA_TOKEN}" \
|
create_pr "${MONOREPO_API}" "${GITEA_TOKEN}" \
|
||||||
"$mono_branch" "$staging_branch" \
|
"$mono_branch" "$staging_branch" \
|
||||||
"[Subrepo Sync] ${subrepo_branch} → ${mono_branch}" \
|
"[Subrepo Sync] ${subrepo_branch} → ${mono_branch}" \
|
||||||
"$pr_body" \
|
"$pr_body" \
|
||||||
|| die "Failed to create PR on monorepo (check GITEA_TOKEN)"
|
|| die "Failed to create PR on monorepo (check GITEA_TOKEN)"
|
||||||
|
|
||||||
log "INFO" "Reverse sync PR created on monorepo"
|
log "INFO" "Reverse sync PR created on monorepo"
|
||||||
echo "pr-created"
|
echo "pr-created"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# ─── Initial Import: Subrepo → Monorepo (first time) ───────────────
|
# ─── Initial Import: Subrepo → Monorepo (first time) ───────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user