Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 479b592076 | |||
| 65cc8eaf8a | |||
| 4861179c6f | |||
| c9b37f2477 | |||
| d3799187a1 | |||
| b4eaa47ef6 | |||
| d6f334b861 |
29
CHANGELOG.md
29
CHANGELOG.md
@@ -1,7 +1,32 @@
|
||||
# Changelog
|
||||
|
||||
## 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.
|
||||
@@ -21,6 +46,10 @@
|
||||
|
||||
## 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.
|
||||
|
||||
12
README.md
12
README.md
@@ -63,6 +63,18 @@ Run `josh-sync preflight` to validate your setup.
|
||||
- **[Architecture Decision Records](docs/adr/)** — Design rationale and trade-offs
|
||||
- **[Changelog](CHANGELOG.md)** — Version history
|
||||
|
||||
## Versioning
|
||||
|
||||
josh-sync follows [semver](https://semver.org/):
|
||||
|
||||
| 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
|
||||
|
||||
```
|
||||
|
||||
@@ -259,6 +259,10 @@ _sync_direction() {
|
||||
echo "::error::Target ${target_name}, branch ${branch}: josh rejected push — check proxy logs"
|
||||
continue
|
||||
fi
|
||||
if [ "$result" = "skip-dirty" ]; then
|
||||
log "WARN" "Trees differ but no human commits — skipping state update (will retry)"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Update state (only on success)
|
||||
local new_state
|
||||
|
||||
58
docs/adr/011-linearize-fallback-reverse.md
Normal file
58
docs/adr/011-linearize-fallback-reverse.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# ADR-011: Linearize Fallback for Reverse Sync
|
||||
|
||||
**Status:** Accepted
|
||||
**Date:** 2026-03
|
||||
|
||||
## Context
|
||||
|
||||
Josh-proxy rejects reverse sync pushes when the subrepo history contains merge commits whose parents it cannot map through the filter. This happens when:
|
||||
|
||||
1. A long-lived branch (e.g., `stage`) is merged into `main` via a merge commit
|
||||
2. That branch contains auto-sync merge commits or criss-cross merges with `main`
|
||||
3. Josh encounters the merge commit, tries to map both parents through the filter, and fails with a 500 error
|
||||
|
||||
This is a legitimate subrepo workflow — teams merge staging branches into main regularly. The sync tool should handle it without requiring teams to change their Git workflow.
|
||||
|
||||
### Alternatives considered
|
||||
|
||||
1. **Squash all commits into one**: Simple but destroys all commit granularity.
|
||||
2. **Rewrite subrepo history**: Breaks josh's SHA mapping, forces all developers to re-clone.
|
||||
3. **Linearize: cherry-pick regular commits, squash only merges**: Preserves individual commit granularity; only merge commits lose their multi-parent structure.
|
||||
|
||||
## Decision
|
||||
|
||||
When the direct push through josh-proxy fails, fall back to option 4: linearize the history by cherry-picking onto the josh-filtered base.
|
||||
|
||||
### How it works
|
||||
|
||||
1. Direct `git push` through josh-proxy is attempted first (existing behavior)
|
||||
2. If it fails, create a temporary branch from `mono-filtered/<branch>`
|
||||
3. Walk human commits (oldest-first, bot commits excluded) from the ancestry path:
|
||||
- **Regular commits** (≤1 parent): `git cherry-pick <sha>` — preserves author, date, and message
|
||||
- **Merge commits** (>1 parent): `git cherry-pick -m 1 <sha>` — applies the merge's diff relative to its first parent as a single commit
|
||||
4. If cherry-pick conflicts (rare — usually due to ordering issues), fall back to `git diff | git apply` with the original author metadata
|
||||
5. Push the linearized branch through josh-proxy
|
||||
6. PR body includes a note explaining that merge commits were squashed
|
||||
|
||||
### What is preserved
|
||||
|
||||
- Individual non-merge commits (author, date, message, diff)
|
||||
- The net effect of each merge commit (as a squashed single commit)
|
||||
- The original commit list in the PR body for reference
|
||||
|
||||
### What is lost
|
||||
|
||||
- The multi-parent structure of merge commits (they become single-parent)
|
||||
- The distinction between "changes introduced by the merge" vs "changes from each parent" — the merge is represented as its diff from first parent
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
- Reverse sync no longer gets stuck on merge commits — handles the common case automatically
|
||||
- Non-merge commits retain full granularity (no unnecessary squashing)
|
||||
- Subrepo history is untouched — no rewriting, no broken sync relationship
|
||||
- The PR body documents when linearization was used, so reviewers know
|
||||
|
||||
**Negative:**
|
||||
- Merge commit semantics are lost on the monorepo side (they appear as regular commits)
|
||||
- Adds complexity to the reverse sync path (two code paths: direct push and linearize fallback)
|
||||
@@ -16,3 +16,4 @@ This directory contains Architecture Decision Records (ADRs) for josh-sync. Each
|
||||
| [008](008-first-parent-ordering.md) | First-parent ordering in reconciliation merges | Accepted |
|
||||
| [009](009-tree-comparison-guard.md) | Tree comparison as sync skip guard | Accepted |
|
||||
| [010](010-onboard-checkpoint-resume.md) | Onboard workflow with checkpoint/resume | Accepted |
|
||||
| [011](011-linearize-fallback-reverse.md) | Linearize fallback for reverse sync | Accepted |
|
||||
|
||||
@@ -518,6 +518,38 @@ Bot commits include a git trailer like `Josh-Sync-Origin: forward/main/2024-02-1
|
||||
|
||||
Sync state is stored as JSON files on an orphan branch (`josh-sync-state`), one file per target/branch. This tracks the last-synced commit SHAs and timestamps to avoid re-syncing the same changes.
|
||||
|
||||
## Recommended Git Workflow
|
||||
|
||||
Josh-proxy maps commits through a filter per-branch. It handles linear history and simple merges (short-lived feature branches) without issues. However, it **cannot map merge commits whose parents were created on the subrepo side** — because those commits were never pushed through josh and have no monorepo-side mapping.
|
||||
|
||||
This means cross-branch merges (e.g., `stage` → `main`) must happen on the monorepo side, where josh can filter the result cleanly.
|
||||
|
||||
### The rule
|
||||
|
||||
**The monorepo owns branch topology. The subrepo owns feature development.**
|
||||
|
||||
### What to do where
|
||||
|
||||
| Action | Where to do it | Why |
|
||||
|--------|---------------|-----|
|
||||
| Feature branch → `main` | **Subrepo** (PR, any merge strategy) | Short-lived branch with clean lineage — josh handles it |
|
||||
| `stage` → `main` (promotion) | **Monorepo** | Cross-branch merge — forward sync propagates the result to both subrepo branches |
|
||||
| `main` → `stage` (catch-up) | **Monorepo** | Same reason — avoids criss-cross merge history on subrepo |
|
||||
| Hotfix to `main` | **Either side** | Single commit or small PR — works everywhere |
|
||||
| Config/CI changes (monorepo-only) | **Monorepo** | Not synced to subrepo (use `exclude` for monorepo-only files) |
|
||||
|
||||
### What to avoid
|
||||
|
||||
- **Don't merge `stage` into `main` on the subrepo with a merge commit.** The merge parents include commits created on the subrepo side (forward sync merges, criss-cross merges) that josh has no mapping for. Josh rejects the push with a 500 error.
|
||||
- **Don't merge `main` into `stage` on the subrepo.** Creates criss-cross merge history that causes the same josh mapping failure when `stage` is later merged back.
|
||||
- **Don't rebase synced branches on the subrepo.** This rewrites commit SHAs that josh has already mapped, breaking the sync relationship.
|
||||
|
||||
### If you must merge cross-branch on the subrepo
|
||||
|
||||
Use a **squash merge**. A squash merge produces a single commit with one parent — josh can always map it. You lose the individual commit history on the target branch, but the sync goes through cleanly.
|
||||
|
||||
As a safety net, josh-sync automatically falls back to linearizing the history when josh rejects a push — cherry-picking regular commits individually and squashing only the problematic merge commits. See the [troubleshooting section](#josh-rejected-push-reverse-sync) and [ADR-011](adr/011-linearize-fallback-reverse.md).
|
||||
|
||||
## Excluding Files from Sync
|
||||
|
||||
Some files in the monorepo subfolder may not belong in the subrepo (e.g., monorepo-specific CI configs, internal tooling). The `exclude` config field removes these at the josh-proxy layer — excluded files never appear in the subrepo.
|
||||
@@ -616,7 +648,35 @@ Normal: the subrepo changed while sync was running. The next sync run will pick
|
||||
|
||||
### "Josh rejected push" (reverse sync)
|
||||
|
||||
Josh-proxy couldn't map the push back to the monorepo. Check josh-proxy logs, verify the josh filter is correct. May indicate a history divergence — consider running `josh-sync reset <target>`.
|
||||
Josh-proxy couldn't map the push back to the monorepo. This has two common causes:
|
||||
|
||||
#### Merge commits with unmappable parents
|
||||
|
||||
**Symptom:** Josh returns `500 Internal Server Error` with a message like:
|
||||
|
||||
```
|
||||
rejecting merge with 2 parents:
|
||||
"Merge pull request 'stage' (#30) from stage into main" (c4fa3c9...)
|
||||
1) "Merge branch 'auto-sync/import-...'" (4bf8704...)
|
||||
2) "Merge branch 'main' into stage" (d021654...)
|
||||
```
|
||||
|
||||
**Cause:** The subrepo has a merge commit whose parents josh-proxy cannot trace through its filter. This typically happens when:
|
||||
- A long-lived branch (e.g., `stage`) is merged into `main` via a merge commit (not squash)
|
||||
- That branch contains auto-sync merge commits or other history that doesn't exist in josh's filtered view
|
||||
- Someone merges `main` into a feature/staging branch and then merges it back — the criss-cross parents confuse josh's mapping
|
||||
|
||||
**Automatic handling:** josh-sync automatically falls back to linearizing the history when the direct push fails. Regular commits are cherry-picked individually (preserving authorship and messages), while merge commits are squashed into single commits via `cherry-pick -m 1`. The PR notes when this fallback was used. See [ADR-011](adr/011-linearize-fallback-reverse.md).
|
||||
|
||||
**Prevention:** In josh-synced subrepos, prefer **squash merges** when merging long-lived branches (stage, develop) into the synced branch. Squash merges produce a single commit with no merge parents, which josh can always map. Regular feature branch merges (short-lived, no auto-sync history) are usually fine.
|
||||
|
||||
**Manual resolution (if automatic fallback also fails):** This indicates a more fundamental history issue. Options:
|
||||
1. Cherry-pick the desired changes manually onto a clean branch and push through josh
|
||||
2. Run `josh-sync reset <target>` to re-establish history (destructive — all subrepo clones must re-fetch)
|
||||
|
||||
#### Filter or path mismatch
|
||||
|
||||
Josh-proxy couldn't map the push due to an incorrect filter. Check josh-proxy logs, verify the `josh_filter` or `subfolder` in `.josh-sync.yml` is correct, and ensure the subfolder exists in the monorepo.
|
||||
|
||||
### Import PR shows "No changes"
|
||||
|
||||
|
||||
@@ -16,6 +16,11 @@ parse_config() {
|
||||
local config_json
|
||||
config_json=$(yq -o json "$config_file") || die "Failed to parse ${config_file}"
|
||||
|
||||
# Schema version check (optional field — absent means v1)
|
||||
local schema_version
|
||||
schema_version=$(echo "$config_json" | jq -r '.schema_version // 1')
|
||||
[ "$schema_version" = "1" ] || die "Unsupported schema_version ${schema_version} in ${config_file} (this version of josh-sync supports schema_version 1 — see CHANGELOG.md for migration instructions)"
|
||||
|
||||
# Export global values
|
||||
export JOSH_PROXY_URL
|
||||
JOSH_PROXY_URL=$(echo "$config_json" | jq -r '.josh.proxy_url')
|
||||
|
||||
163
lib/sync.sh
163
lib/sync.sh
@@ -226,7 +226,9 @@ ${BOT_TRAILER}: filter-change/${mono_branch}/$(date -u +%Y-%m-%dT%H:%M:%SZ)")
|
||||
# ─── Reverse Sync: Subrepo → Monorepo ──────────────────────────────
|
||||
#
|
||||
# Always creates a PR on the monorepo — never pushes directly.
|
||||
# Returns: skip | pr-created | josh-rejected
|
||||
# Returns: skip | skip-dirty | pr-created | josh-rejected
|
||||
# skip — trees identical, nothing to do
|
||||
# skip-dirty — trees differ but no human commits found (don't update state)
|
||||
|
||||
reverse_sync() {
|
||||
local mono_branch="$SYNC_BRANCH_MONO"
|
||||
@@ -265,15 +267,27 @@ reverse_sync() {
|
||||
fi
|
||||
|
||||
# 4. Find new human commits (excludes bot commits from forward sync)
|
||||
# Uses --ancestry-path to restrict to the direct lineage and avoid
|
||||
# leaking old history through reconciliation merge parents.
|
||||
local human_commits
|
||||
human_commits=$(git log --ancestry-path "mono-filtered/${mono_branch}..HEAD" \
|
||||
--oneline --invert-grep --grep="^${BOT_TRAILER}:" 2>/dev/null || echo "")
|
||||
# Uses merge-base + --ancestry-path to restrict to the direct lineage.
|
||||
# merge-base is needed because mono-filtered may have advanced past the
|
||||
# last forward sync (any monorepo commit, even outside the subfolder,
|
||||
# causes josh to create a new filtered commit). Using mono-filtered
|
||||
# directly with --ancestry-path returns empty when it's not an ancestor
|
||||
# of HEAD. merge-base always finds the last connected point.
|
||||
local base human_commits
|
||||
base=$(git merge-base "mono-filtered/${mono_branch}" HEAD 2>/dev/null || echo "")
|
||||
|
||||
if [ -n "$base" ]; then
|
||||
human_commits=$(git log --ancestry-path "${base}..HEAD" \
|
||||
--oneline --invert-grep --grep="^${BOT_TRAILER}:" 2>/dev/null || echo "")
|
||||
else
|
||||
# No common ancestor (first sync or fully diverged) — skip ancestry-path
|
||||
human_commits=$(git log "mono-filtered/${mono_branch}..HEAD" \
|
||||
--oneline --invert-grep --grep="^${BOT_TRAILER}:" 2>/dev/null || echo "")
|
||||
fi
|
||||
|
||||
if [ -z "$human_commits" ]; then
|
||||
log "INFO" "No new human commits in subrepo — nothing to sync"
|
||||
echo "skip"
|
||||
echo "skip-dirty"
|
||||
return
|
||||
fi
|
||||
|
||||
@@ -284,37 +298,138 @@ reverse_sync() {
|
||||
local ts
|
||||
ts=$(date +%Y%m%d-%H%M%S)
|
||||
local staging_branch="auto-sync/subrepo-${subrepo_branch}-${ts}"
|
||||
local push_ref="HEAD"
|
||||
local linearized=false
|
||||
|
||||
if git push -o "base=${mono_branch}" "$(josh_auth_url)" "HEAD:refs/heads/${staging_branch}"; then
|
||||
log "INFO" "Pushed to staging branch via josh: ${staging_branch}"
|
||||
if ! git push -o "base=${mono_branch}" "$(josh_auth_url)" "${push_ref}:refs/heads/${staging_branch}" 2>/dev/null; then
|
||||
# Josh rejects pushes when the history contains merge commits whose
|
||||
# parents it cannot map (e.g., merges of staging/feature branches that
|
||||
# include auto-sync history). Fall back to linearizing: cherry-pick
|
||||
# regular commits as-is, squash only the problematic merge commits.
|
||||
# See ADR-011.
|
||||
log "WARN" "Direct push failed — linearizing history (cherry-pick + squash merges)"
|
||||
log "INFO" "─── linearize start ───"
|
||||
|
||||
# 6. Create PR on monorepo (NEVER direct push)
|
||||
local pr_body
|
||||
pr_body="## Subrepo changes
|
||||
local original_head
|
||||
original_head=$(git rev-parse HEAD)
|
||||
|
||||
git checkout -b "tmp-linearize-$$" "mono-filtered/${mono_branch}" >/dev/null 2>&1 \
|
||||
|| die "Failed to create linearize branch"
|
||||
|
||||
# Reuse merge-base from step 4 (same reasoning: mono-filtered may have
|
||||
# advanced past the last forward sync, breaking --ancestry-path).
|
||||
local commit_shas
|
||||
if [ -n "$base" ]; then
|
||||
commit_shas=$(git log --ancestry-path "${base}..${original_head}" \
|
||||
--reverse --format="%H" --invert-grep --grep="^${BOT_TRAILER}:" 2>/dev/null || echo "")
|
||||
else
|
||||
commit_shas=$(git log "mono-filtered/${mono_branch}..${original_head}" \
|
||||
--reverse --format="%H" --invert-grep --grep="^${BOT_TRAILER}:" 2>/dev/null || echo "")
|
||||
fi
|
||||
|
||||
local sha
|
||||
for sha in $commit_shas; do
|
||||
local parent_count
|
||||
parent_count=$(git cat-file -p "$sha" | grep -c "^parent ")
|
||||
local short_msg
|
||||
short_msg=$(git log -1 --format="%s" "$sha")
|
||||
|
||||
if [ "$parent_count" -le 1 ]; then
|
||||
# Regular commit — cherry-pick to preserve message and authorship
|
||||
if git cherry-pick "$sha" >/dev/null 2>&1; then
|
||||
log "INFO" " cherry-pick ${sha:0:7}: ${short_msg}"
|
||||
else
|
||||
log "WARN" " conflict ${sha:0:7} — applying patch from parent"
|
||||
git cherry-pick --abort 2>/dev/null
|
||||
local msg author_name author_email author_date
|
||||
msg=$(git log -1 --format="%B" "$sha")
|
||||
author_name=$(git log -1 --format="%an" "$sha")
|
||||
author_email=$(git log -1 --format="%ae" "$sha")
|
||||
author_date=$(git log -1 --format="%aI" "$sha")
|
||||
# Diff from the commit's own parent — NOT from HEAD (which is
|
||||
# the linearize branch and may have a different base).
|
||||
git diff "${sha}^" "$sha" | git apply --index >/dev/null 2>&1 \
|
||||
|| die "Failed to apply diff for ${sha:0:7}"
|
||||
GIT_AUTHOR_NAME="$author_name" GIT_AUTHOR_EMAIL="$author_email" \
|
||||
GIT_AUTHOR_DATE="$author_date" \
|
||||
git commit -m "$msg" >/dev/null 2>&1 || die "Failed to commit ${sha:0:7}"
|
||||
log "INFO" " applied ${sha:0:7}: ${short_msg}"
|
||||
fi
|
||||
else
|
||||
# Merge commit — cherry-pick relative to first parent (squashes the merge)
|
||||
local msg author_name author_email author_date
|
||||
msg=$(git log -1 --format="%B" "$sha")
|
||||
author_name=$(git log -1 --format="%an" "$sha")
|
||||
author_email=$(git log -1 --format="%ae" "$sha")
|
||||
author_date=$(git log -1 --format="%aI" "$sha")
|
||||
|
||||
if git cherry-pick -m 1 "$sha" >/dev/null 2>&1; then
|
||||
log "INFO" " squash ${sha:0:7}: ${short_msg}"
|
||||
else
|
||||
# Cherry-pick failed — distinguish empty result (changes already
|
||||
# applied by a prior cherry-pick) from a real conflict.
|
||||
if git diff --cached --quiet 2>/dev/null && git diff --quiet 2>/dev/null; then
|
||||
# Working tree clean — the merge changes are already present
|
||||
git cherry-pick --abort 2>/dev/null || git reset --hard HEAD >/dev/null 2>&1
|
||||
log "INFO" " skip ${sha:0:7}: ${short_msg} (already applied)"
|
||||
else
|
||||
# Real conflict — apply diff from first parent (what the merge
|
||||
# actually introduced, NOT from HEAD which would capture all
|
||||
# tree differences).
|
||||
git cherry-pick --abort 2>/dev/null
|
||||
git diff "${sha}^1" "$sha" | git apply --index >/dev/null 2>&1 \
|
||||
|| die "Failed to apply merge diff for ${sha:0:7}"
|
||||
GIT_AUTHOR_NAME="$author_name" GIT_AUTHOR_EMAIL="$author_email" \
|
||||
GIT_AUTHOR_DATE="$author_date" \
|
||||
git commit -m "$msg" >/dev/null 2>&1 || die "Failed to commit merge ${sha:0:7}"
|
||||
log "INFO" " squash ${sha:0:7}: ${short_msg}"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
log "INFO" "─── linearize done ────"
|
||||
|
||||
push_ref="HEAD"
|
||||
linearized=true
|
||||
|
||||
git push -o "base=${mono_branch}" "$(josh_auth_url)" \
|
||||
"${push_ref}:refs/heads/${staging_branch}" \
|
||||
|| { log "ERROR" "Josh rejected linearized push — check josh-proxy logs"; echo "josh-rejected"; return; }
|
||||
fi
|
||||
|
||||
log "INFO" "Pushed to staging branch via josh: ${staging_branch}${linearized:+ (linearized)}"
|
||||
|
||||
# 6. Create PR on monorepo (NEVER direct push)
|
||||
local pr_body
|
||||
local linearize_note=""
|
||||
if [ "$linearized" = true ]; then
|
||||
linearize_note="
|
||||
> **Note:** Merge commits were squashed during sync because the subrepo
|
||||
> history contained merges that josh-proxy cannot map (see ADR-011).
|
||||
> Regular commits are preserved individually."
|
||||
fi
|
||||
pr_body="## Subrepo changes
|
||||
|
||||
New commits from subrepo \`${subrepo_branch}\`:
|
||||
|
||||
\`\`\`
|
||||
${human_commits}
|
||||
\`\`\`
|
||||
|
||||
${linearize_note}
|
||||
**Review checklist:**
|
||||
- [ ] Changes scoped to synced subfolder
|
||||
- [ ] No leaked credentials or environment-specific config
|
||||
- [ ] CI passes"
|
||||
|
||||
create_pr "${MONOREPO_API}" "${GITEA_TOKEN}" \
|
||||
"$mono_branch" "$staging_branch" \
|
||||
"[Subrepo Sync] ${subrepo_branch} → ${mono_branch}" \
|
||||
"$pr_body" \
|
||||
|| die "Failed to create PR on monorepo (check GITEA_TOKEN)"
|
||||
create_pr "${MONOREPO_API}" "${GITEA_TOKEN}" \
|
||||
"$mono_branch" "$staging_branch" \
|
||||
"[Subrepo Sync] ${subrepo_branch} → ${mono_branch}" \
|
||||
"$pr_body" \
|
||||
|| die "Failed to create PR on monorepo (check GITEA_TOKEN)"
|
||||
|
||||
log "INFO" "Reverse sync PR created on monorepo"
|
||||
echo "pr-created"
|
||||
else
|
||||
log "ERROR" "Josh rejected push — check josh-proxy logs"
|
||||
echo "josh-rejected"
|
||||
fi
|
||||
log "INFO" "Reverse sync PR created on monorepo"
|
||||
echo "pr-created"
|
||||
}
|
||||
|
||||
# ─── Initial Import: Subrepo → Monorepo (first time) ───────────────
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
"type": "object",
|
||||
"required": ["josh", "targets", "bot"],
|
||||
"properties": {
|
||||
"schema_version": {
|
||||
"type": "integer",
|
||||
"const": 1,
|
||||
"description": "Schema version. Bump when config fields are removed or renamed (matches josh-sync major version)."
|
||||
},
|
||||
"josh": {
|
||||
"type": "object",
|
||||
"required": ["proxy_url", "monorepo_path"],
|
||||
|
||||
Reference in New Issue
Block a user