Compare commits

..

3 Commits

Author SHA1 Message Date
401d0e87a4 Show [migrated] marker and summary in migrate-pr
Interactive picker now marks already-migrated PRs. All modes (--all,
explicit numbers, interactive) track and display success/fail/skip
counts at the end.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 21:08:23 +03:00
fbacec7f6f Improve PR migration: fetch branches locally + 3-way merge
Instead of fetching the API diff (which has context-sensitive patches
that break after josh-filtered reset), fetch the archived repo's
branches directly as a second remote and compute the diff locally.
Apply with git apply --3way for resilience against context mismatches.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 20:51:22 +03:00
553f006174 Fix onboard import cloning from empty new repo instead of archived repo
initial_import() now accepts an optional clone URL override parameter.
onboard_flow() passes the archived repo URL so content is cloned from
the right source.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 19:48:46 +03:00
3 changed files with 84 additions and 27 deletions

View File

@@ -735,6 +735,26 @@ cmd_migrate_pr() {
log "INFO" "Archived repo: ${archived_api}" log "INFO" "Archived repo: ${archived_api}"
# Load already-migrated PR numbers for skip detection and display
local migrated_numbers
migrated_numbers=$(echo "$onboard_state" | jq -r '[.migrated_prs // [] | .[].old_number] | map(tostring) | .[]')
# Counters for summary
local migrated=0 failed=0 skipped=0
# Helper: attempt migration of one PR with counting
_try_migrate() {
local num="$1"
if echo "$migrated_numbers" | grep -qx "$num"; then
log "INFO" "PR #${num} already migrated — skipping"
skipped=$((skipped + 1))
elif migrate_one_pr "$num"; then
migrated=$((migrated + 1))
else
failed=$((failed + 1))
fi
}
if [ "$all" = true ]; then if [ "$all" = true ]; then
# Migrate all open PRs from archived repo # Migrate all open PRs from archived repo
local prs local prs
@@ -744,14 +764,14 @@ cmd_migrate_pr() {
count=$(echo "$prs" | jq 'length') count=$(echo "$prs" | jq 'length')
log "INFO" "Found ${count} open PR(s) on archived repo" log "INFO" "Found ${count} open PR(s) on archived repo"
echo "$prs" | jq -r '.[] | .number' | while read -r num; do while read -r num; do
migrate_one_pr "$num" || true _try_migrate "$num"
done done < <(echo "$prs" | jq -r '.[] | .number')
elif [ ${#pr_numbers[@]} -gt 0 ]; then elif [ ${#pr_numbers[@]} -gt 0 ]; then
# Migrate specific PR numbers # Migrate specific PR numbers
for num in "${pr_numbers[@]}"; do for num in "${pr_numbers[@]}"; do
migrate_one_pr "$num" || true _try_migrate "$num"
done done
else else
@@ -767,26 +787,33 @@ cmd_migrate_pr() {
return return
fi fi
# Display PRs with [migrated] marker for already-processed ones
echo "" >&2 echo "" >&2
echo "Open PRs on archived repo:" >&2 echo "Open PRs on archived repo:" >&2
echo "$prs" | jq -r '.[] | " #\(.number): \(.title) (\(.base.ref) <- \(.head.ref))"' >&2 while IFS=$'\t' read -r num title base_ref head_ref; do
if echo "$migrated_numbers" | grep -qx "$num"; then
echo " #${num}: ${title} (${base_ref} <- ${head_ref}) [migrated]" >&2
else
echo " #${num}: ${title} (${base_ref} <- ${head_ref})" >&2
fi
done < <(echo "$prs" | jq -r '.[] | "\(.number)\t\(.title)\t\(.base.ref)\t\(.head.ref)"')
echo "" >&2 echo "" >&2
echo "Enter PR numbers to migrate (space-separated), or 'all':" >&2 echo "Enter PR numbers to migrate (space-separated), or 'all':" >&2
local selection local selection
read -r selection read -r selection
if [ "$selection" = "all" ]; then if [ "$selection" = "all" ]; then
echo "$prs" | jq -r '.[] | .number' | while read -r num; do while read -r num; do
migrate_one_pr "$num" || true _try_migrate "$num"
done done < <(echo "$prs" | jq -r '.[] | .number')
else else
for num in $selection; do for num in $selection; do
migrate_one_pr "$num" || true _try_migrate "$num"
done done
fi fi
fi fi
log "INFO" "PR migration complete" log "INFO" "Migration complete: ${migrated} migrated, ${failed} failed, ${skipped} skipped"
} }
# ─── Main ─────────────────────────────────────────────────────────── # ─── Main ───────────────────────────────────────────────────────────

View File

@@ -169,6 +169,17 @@ onboard_flow() {
local import_prs local import_prs
import_prs=$(echo "$onboard_state" | jq -r '.import_prs // {}') import_prs=$(echo "$onboard_state" | jq -r '.import_prs // {}')
# Build the archived repo clone URL for initial_import().
# The content lives in the archived repo — the new repo at SUBREPO_URL is empty.
local archived_url archived_clone_url
archived_url=$(echo "$onboard_state" | jq -r '.archived_url')
if [ "${SUBREPO_AUTH:-https}" = "ssh" ]; then
archived_clone_url="$archived_url"
else
# shellcheck disable=SC2001
archived_clone_url=$(echo "$archived_url" | sed "s|https://|https://${BOT_USER}:${SUBREPO_TOKEN}@|")
fi
for branch in $branches; do for branch in $branches; do
local mapped local mapped
mapped=$(echo "$target_json" | jq -r --arg b "$branch" '.branches[$b] // empty') mapped=$(echo "$target_json" | jq -r --arg b "$branch" '.branches[$b] // empty')
@@ -185,7 +196,7 @@ onboard_flow() {
log "INFO" "Importing branch: ${branch} (subrepo: ${mapped})" log "INFO" "Importing branch: ${branch} (subrepo: ${mapped})"
local result local result
result=$(initial_import) result=$(initial_import "$archived_clone_url")
log "INFO" "Import result for ${branch}: ${result}" log "INFO" "Import result for ${branch}: ${result}"
if [ "$result" = "pr-created" ]; then if [ "$result" = "pr-created" ]; then
@@ -325,7 +336,8 @@ onboard_flow() {
} }
# ─── Migrate One PR ────────────────────────────────────────────── # ─── Migrate One PR ──────────────────────────────────────────────
# Applies a PR's diff from the archived repo to the new subrepo. # Fetches the PR's branch from the archived repo, computes a local diff,
# and applies it to the new subrepo with --3way for resilience.
# Usage: migrate_one_pr <pr_number> # Usage: migrate_one_pr <pr_number>
# #
# Expects: JOSH_SYNC_TARGET_NAME, SUBREPO_API, SUBREPO_TOKEN, BOT_NAME, BOT_EMAIL loaded # Expects: JOSH_SYNC_TARGET_NAME, SUBREPO_API, SUBREPO_TOKEN, BOT_NAME, BOT_EMAIL loaded
@@ -365,15 +377,7 @@ migrate_one_pr() {
log "INFO" "Migrating PR #${pr_number}: \"${title}\" (${base} <- ${head})" log "INFO" "Migrating PR #${pr_number}: \"${title}\" (${base} <- ${head})"
# 2. Get diff from archived repo # 2. Clone new subrepo, add archived repo as second remote
local diff
diff=$(get_pr_diff "$archived_api" "$archived_token" "$pr_number")
if [ -z "$diff" ]; then
log "WARN" "Empty diff for PR #${pr_number} — skipping"
return 1
fi
# 3. Clone new subrepo, apply patch
# Save cwd so we can restore it (function runs in caller's shell, not subshell) # Save cwd so we can restore it (function runs in caller's shell, not subshell)
local original_dir local original_dir
original_dir=$(pwd) original_dir=$(pwd)
@@ -390,10 +394,32 @@ migrate_one_pr() {
git config user.name "$BOT_NAME" git config user.name "$BOT_NAME"
git config user.email "$BOT_EMAIL" git config user.email "$BOT_EMAIL"
# Build authenticated URL for the archived repo
local archived_url archived_clone_url
archived_url=$(echo "$onboard_state" | jq -r '.archived_url')
if [ "${SUBREPO_AUTH:-https}" = "ssh" ]; then
archived_clone_url="$archived_url"
else
# shellcheck disable=SC2001
archived_clone_url=$(echo "$archived_url" | sed "s|https://|https://${BOT_USER}:${SUBREPO_TOKEN}@|")
fi
# Fetch the PR's head and base branches from the archived repo
git remote add archived "$archived_clone_url"
git fetch archived "$head" "$base" 2>&1 \
|| die "Failed to fetch branches from archived repo"
# 3. Compute diff locally and apply with --3way
git checkout -B "$head" >&2 git checkout -B "$head" >&2
if echo "$diff" | git apply --check 2>/dev/null; then local diff
echo "$diff" | git apply diff=$(git diff "archived/${base}..archived/${head}")
if [ -z "$diff" ]; then
log "WARN" "Empty diff for PR #${pr_number} — skipping"
return 1
fi
if echo "$diff" | git apply --3way 2>&1; then
git add -A git add -A
git commit -m "${title} git commit -m "${title}
@@ -418,8 +444,8 @@ Migrated from archived repo PR #${pr_number}" >&2
'.migrated_prs += [{"old_number":$old, "new_number":$new_num, "title":$title}]') '.migrated_prs += [{"old_number":$old, "new_number":$new_num, "title":$title}]')
write_onboard_state "$target_name" "$onboard_state" write_onboard_state "$target_name" "$onboard_state"
else else
log "ERROR" "Patch doesn't apply cleanly for PR #${pr_number} — skipping" log "ERROR" "Could not apply changes for PR #${pr_number} even with 3-way merge"
log "ERROR" "Manual migration needed: get diff from archived repo and resolve conflicts" log "ERROR" "Manual migration needed: branch '${head}' from archived repo"
return 1 return 1
fi fi
} }

View File

@@ -200,9 +200,13 @@ reverse_sync() {
# #
# Used when a subrepo already has content and you're adding it to the # Used when a subrepo already has content and you're adding it to the
# monorepo for the first time. Creates a PR. # monorepo for the first time. Creates a PR.
# Usage: initial_import [clone_url_override]
# clone_url_override — if set, clone from this URL instead of subrepo_auth_url()
# (used by onboard to clone from the archived repo)
# Returns: skip | pr-created # Returns: skip | pr-created
initial_import() { initial_import() {
local clone_url="${1:-$(subrepo_auth_url)}"
local mono_branch="$SYNC_BRANCH_MONO" local mono_branch="$SYNC_BRANCH_MONO"
local subrepo_branch="$SYNC_BRANCH_SUBREPO" local subrepo_branch="$SYNC_BRANCH_SUBREPO"
local subfolder local subfolder
@@ -225,8 +229,8 @@ initial_import() {
--branch "$mono_branch" --single-branch \ --branch "$mono_branch" --single-branch \
"${work_dir}/monorepo" || die "Failed to clone monorepo" "${work_dir}/monorepo" || die "Failed to clone monorepo"
# 2. Clone subrepo # 2. Clone subrepo (or archived repo when clone_url is overridden)
git clone "$(subrepo_auth_url)" \ git clone "$clone_url" \
--branch "$subrepo_branch" --single-branch \ --branch "$subrepo_branch" --single-branch \
"${work_dir}/subrepo" || die "Failed to clone subrepo" "${work_dir}/subrepo" || die "Failed to clone subrepo"