Compare commits
5 Commits
72430714af
...
v1.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 401d0e87a4 | |||
| fbacec7f6f | |||
| 553f006174 | |||
| cb14cf9bd4 | |||
| 0363b0ee77 |
2
Makefile
2
Makefile
@@ -23,7 +23,7 @@ dist/josh-sync: bin/josh-sync lib/*.sh VERSION
|
|||||||
@echo '# Generated by: make build' >> dist/josh-sync
|
@echo '# Generated by: make build' >> dist/josh-sync
|
||||||
@echo '' >> dist/josh-sync
|
@echo '' >> dist/josh-sync
|
||||||
@# Inline all library modules (strip shebangs and source directives)
|
@# Inline all library modules (strip shebangs and source directives)
|
||||||
@for f in lib/core.sh lib/config.sh lib/auth.sh lib/state.sh lib/sync.sh; do \
|
@for f in lib/core.sh lib/config.sh lib/auth.sh lib/state.sh lib/sync.sh lib/onboard.sh; do \
|
||||||
echo "# --- $$f ---" >> dist/josh-sync; \
|
echo "# --- $$f ---" >> dist/josh-sync; \
|
||||||
grep -v '^#!/' "$$f" | grep -v '^# shellcheck source=' >> dist/josh-sync; \
|
grep -v '^#!/' "$$f" | grep -v '^# shellcheck source=' >> dist/josh-sync; \
|
||||||
echo '' >> dist/josh-sync; \
|
echo '' >> dist/josh-sync; \
|
||||||
|
|||||||
@@ -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 ───────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -165,6 +165,34 @@ SUBREPO_SSH_KEY="-----BEGIN OPENSSH PRIVATE KEY-----
|
|||||||
# AUTH_REPO_TOKEN=<auth-specific-token>
|
# AUTH_REPO_TOKEN=<auth-specific-token>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Updating josh-sync in devenv
|
||||||
|
|
||||||
|
To update to the latest version:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
devenv update josh-sync
|
||||||
|
```
|
||||||
|
|
||||||
|
Or with plain Nix flakes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix flake lock --update-input josh-sync
|
||||||
|
```
|
||||||
|
|
||||||
|
To pin to a specific version, use a tag ref in `devenv.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
josh-sync:
|
||||||
|
url: git+https://your-gitea.example.com/org/josh-sync?ref=v1.1
|
||||||
|
flake: true
|
||||||
|
```
|
||||||
|
|
||||||
|
After updating, verify the version:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
josh-sync --version
|
||||||
|
```
|
||||||
|
|
||||||
### Option B: Manual installation
|
### Option B: Manual installation
|
||||||
|
|
||||||
Install the required tools, then either:
|
Install the required tools, then either:
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
|
|
||||||
installPhase = ''
|
installPhase = ''
|
||||||
mkdir -p $out/{bin,lib}
|
mkdir -p $out/{bin,lib}
|
||||||
|
cp VERSION $out/
|
||||||
cp lib/*.sh $out/lib/
|
cp lib/*.sh $out/lib/
|
||||||
cp bin/josh-sync $out/bin/
|
cp bin/josh-sync $out/bin/
|
||||||
chmod +x $out/bin/josh-sync
|
chmod +x $out/bin/josh-sync
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user