Add onboard and migrate-pr commands (v1.1.0)

New commands for safely onboarding existing subrepos into the monorepo
without losing open PRs:

- josh-sync onboard <target>: interactive, resumable 5-step flow
  (import → wait for merge → reset to new repo)
- josh-sync migrate-pr <target> [PR#...] [--all]: migrate PRs from
  archived repo to new repo via patch application

Also refactors create_pr() to wrap create_pr_number(), eliminating
duplicated curl/jq logic.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-13 12:41:44 +03:00
parent 405e5f4535
commit 105216a27e
4 changed files with 619 additions and 16 deletions

View File

@@ -9,6 +9,8 @@
# preflight Validate config, connectivity, auth
# import <target> Initial import: pull subrepo into monorepo
# reset <target> Reset subrepo to josh-filtered view
# onboard <target> Import existing subrepo into monorepo (interactive)
# migrate-pr <target> [PR#...] [--all] Move PRs from archived to new subrepo
# status Show target config and sync state
# state show|reset Manage sync state directly
#
@@ -39,6 +41,8 @@ source "${JOSH_LIB_DIR}/auth.sh"
source "${JOSH_LIB_DIR}/state.sh"
# shellcheck source=../lib/sync.sh
source "${JOSH_LIB_DIR}/sync.sh"
# shellcheck source=../lib/onboard.sh
source "${JOSH_LIB_DIR}/onboard.sh"
# ─── Version ────────────────────────────────────────────────────────
@@ -69,6 +73,8 @@ Commands:
preflight Validate config, connectivity, auth, workflow coverage
import <target> Initial import: pull existing subrepo into monorepo (creates PR)
reset <target> Reset subrepo to josh-filtered view (after merging import PR)
onboard <target> Import existing subrepo into monorepo (interactive, resumable)
migrate-pr <target> [PR#...] [--all] Move PRs from archived to new subrepo
status Show target config and sync state
state show <target> [branch] Show sync state JSON
state reset <target> [branch] Reset sync state to {}
@@ -643,6 +649,146 @@ cmd_state() {
esac
}
# ─── Onboard Command ──────────────────────────────────────────────
cmd_onboard() {
local config_file=".josh-sync.yml"
local target_name=""
local restart=false
while [ $# -gt 0 ]; do
case "$1" in
--config) config_file="$2"; shift 2 ;;
--debug) export JOSH_SYNC_DEBUG=1; shift ;;
--restart) restart=true; shift ;;
-*) die "Unknown flag: $1" ;;
*) target_name="$1"; shift ;;
esac
done
if [ -z "$target_name" ]; then
echo "Usage: josh-sync onboard <target> [--restart]" >&2
parse_config "$config_file"
echo "Available targets:" >&2
echo "$JOSH_SYNC_TARGETS" | jq -r '.[].name' | sed 's/^/ /' >&2
exit 1
fi
parse_config "$config_file"
local target_json
target_json=$(echo "$JOSH_SYNC_TARGETS" | jq -c --arg n "$target_name" '.[] | select(.name == $n)')
[ -n "$target_json" ] || die "Target '${target_name}' not found in config"
log "INFO" "══════ Onboard target: ${target_name} ══════"
load_target "$target_json"
onboard_flow "$target_json" "$restart"
}
# ─── Migrate PR Command ──────────────────────────────────────────
cmd_migrate_pr() {
local config_file=".josh-sync.yml"
local target_name=""
local all=false
local pr_numbers=()
while [ $# -gt 0 ]; do
case "$1" in
--config) config_file="$2"; shift 2 ;;
--debug) export JOSH_SYNC_DEBUG=1; shift ;;
--all) all=true; shift ;;
-*) die "Unknown flag: $1" ;;
*)
if [ -z "$target_name" ]; then
target_name="$1"
else
pr_numbers+=("$1")
fi
shift ;;
esac
done
if [ -z "$target_name" ]; then
echo "Usage: josh-sync migrate-pr <target> [PR#...] [--all]" >&2
parse_config "$config_file"
echo "Available targets:" >&2
echo "$JOSH_SYNC_TARGETS" | jq -r '.[].name' | sed 's/^/ /' >&2
exit 1
fi
parse_config "$config_file"
local target_json
target_json=$(echo "$JOSH_SYNC_TARGETS" | jq -c --arg n "$target_name" '.[] | select(.name == $n)')
[ -n "$target_json" ] || die "Target '${target_name}' not found in config"
load_target "$target_json"
# Load archived repo info from onboard state
local onboard_state archived_api
onboard_state=$(read_onboard_state "$target_name")
archived_api=$(echo "$onboard_state" | jq -r '.archived_api')
if [ -z "$archived_api" ] || [ "$archived_api" = "null" ]; then
die "No archived repo info found. Run 'josh-sync onboard ${target_name}' first."
fi
log "INFO" "Archived repo: ${archived_api}"
if [ "$all" = true ]; then
# Migrate all open PRs from archived repo
local prs
prs=$(list_open_prs "$archived_api" "$SUBREPO_TOKEN") \
|| die "Failed to list PRs on archived repo"
local count
count=$(echo "$prs" | jq 'length')
log "INFO" "Found ${count} open PR(s) on archived repo"
echo "$prs" | jq -r '.[] | .number' | while read -r num; do
migrate_one_pr "$num" || true
done
elif [ ${#pr_numbers[@]} -gt 0 ]; then
# Migrate specific PR numbers
for num in "${pr_numbers[@]}"; do
migrate_one_pr "$num" || true
done
else
# Interactive: list open PRs, let user pick
local prs
prs=$(list_open_prs "$archived_api" "$SUBREPO_TOKEN") \
|| die "Failed to list PRs on archived repo"
local count
count=$(echo "$prs" | jq 'length')
if [ "$count" -eq 0 ]; then
log "INFO" "No open PRs on archived repo"
return
fi
echo "" >&2
echo "Open PRs on archived repo:" >&2
echo "$prs" | jq -r '.[] | " #\(.number): \(.title) (\(.base.ref) <- \(.head.ref))"' >&2
echo "" >&2
echo "Enter PR numbers to migrate (space-separated), or 'all':" >&2
local selection
read -r selection
if [ "$selection" = "all" ]; then
echo "$prs" | jq -r '.[] | .number' | while read -r num; do
migrate_one_pr "$num" || true
done
else
for num in $selection; do
migrate_one_pr "$num" || true
done
fi
fi
log "INFO" "PR migration complete"
}
# ─── Main ───────────────────────────────────────────────────────────
main() {
@@ -662,12 +808,14 @@ main() {
esac
case "$command" in
sync) cmd_sync "$@" ;;
preflight) cmd_preflight "$@" ;;
import) cmd_import "$@" ;;
reset) cmd_reset "$@" ;;
status) cmd_status "$@" ;;
state) cmd_state "$@" ;;
sync) cmd_sync "$@" ;;
preflight) cmd_preflight "$@" ;;
import) cmd_import "$@" ;;
reset) cmd_reset "$@" ;;
onboard) cmd_onboard "$@" ;;
migrate-pr) cmd_migrate_pr "$@" ;;
status) cmd_status "$@" ;;
state) cmd_state "$@" ;;
*)
echo "Unknown command: ${command}" >&2
usage