"#"
This commit is contained in:
672
bin/josh-sync
Executable file
672
bin/josh-sync
Executable file
@@ -0,0 +1,672 @@
|
||||
#!/usr/bin/env bash
|
||||
# bin/josh-sync — CLI entrypoint for josh-sync
|
||||
#
|
||||
# Usage: josh-sync <command> [flags]
|
||||
#
|
||||
# Commands:
|
||||
# sync Run forward and/or reverse sync
|
||||
# preflight Validate config, connectivity, auth
|
||||
# import <target> Initial import: pull subrepo into monorepo
|
||||
# reset <target> Reset subrepo to josh-filtered view
|
||||
# status Show target config and sync state
|
||||
# state show|reset Manage sync state directly
|
||||
#
|
||||
# Global flags:
|
||||
# --config FILE Config path (default: .josh-sync.yml)
|
||||
# --debug Verbose logging
|
||||
# --version Show version
|
||||
# --help Show usage
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ─── Resolve library root ──────────────────────────────────────────
|
||||
|
||||
if [ -n "${JOSH_SYNC_ROOT:-}" ]; then
|
||||
JOSH_LIB_DIR="${JOSH_SYNC_ROOT}/lib"
|
||||
else
|
||||
JOSH_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../lib" && pwd)"
|
||||
fi
|
||||
|
||||
# Source library modules
|
||||
# shellcheck source=../lib/core.sh
|
||||
source "${JOSH_LIB_DIR}/core.sh"
|
||||
# shellcheck source=../lib/config.sh
|
||||
source "${JOSH_LIB_DIR}/config.sh"
|
||||
# shellcheck source=../lib/auth.sh
|
||||
source "${JOSH_LIB_DIR}/auth.sh"
|
||||
# shellcheck source=../lib/state.sh
|
||||
source "${JOSH_LIB_DIR}/state.sh"
|
||||
# shellcheck source=../lib/sync.sh
|
||||
source "${JOSH_LIB_DIR}/sync.sh"
|
||||
|
||||
# ─── Version ────────────────────────────────────────────────────────
|
||||
|
||||
josh_sync_version() {
|
||||
local version_file
|
||||
if [ -n "${JOSH_SYNC_ROOT:-}" ]; then
|
||||
version_file="${JOSH_SYNC_ROOT}/VERSION"
|
||||
else
|
||||
version_file="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/VERSION"
|
||||
fi
|
||||
if [ -f "$version_file" ]; then
|
||||
cat "$version_file"
|
||||
else
|
||||
echo "dev"
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── Usage ──────────────────────────────────────────────────────────
|
||||
|
||||
usage() {
|
||||
cat >&2 <<EOF
|
||||
josh-sync $(josh_sync_version) — bidirectional monorepo ↔ subrepo sync via josh-proxy
|
||||
|
||||
Usage: josh-sync <command> [flags]
|
||||
|
||||
Commands:
|
||||
sync Run forward and/or reverse sync
|
||||
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)
|
||||
status Show target config and sync state
|
||||
state show <target> [branch] Show sync state JSON
|
||||
state reset <target> [branch] Reset sync state to {}
|
||||
|
||||
Global flags:
|
||||
--config FILE Config path (default: .josh-sync.yml)
|
||||
--debug Verbose logging
|
||||
--version Show version and exit
|
||||
--help Show this help
|
||||
|
||||
Sync flags:
|
||||
--forward Forward only (mono → subrepo)
|
||||
--reverse Reverse only (subrepo → mono)
|
||||
--target NAME Filter to one target (env: JOSH_SYNC_TARGET)
|
||||
--branch BRANCH Filter to one branch
|
||||
|
||||
Environment:
|
||||
JOSH_SYNC_TARGET Restrict to a single target name
|
||||
JOSH_SYNC_STATE_BRANCH State branch name (default: josh-sync-state)
|
||||
SYNC_BOT_USER Git username for auth
|
||||
SYNC_BOT_TOKEN Token with repo scope
|
||||
SUBREPO_TOKEN Subrepo-specific token (optional)
|
||||
SUBREPO_SSH_KEY SSH key for SSH targets (optional)
|
||||
EOF
|
||||
}
|
||||
|
||||
# ─── Sync Command ───────────────────────────────────────────────────
|
||||
|
||||
cmd_sync() {
|
||||
local direction="both"
|
||||
local filter_target="${JOSH_SYNC_TARGET:-}"
|
||||
local filter_branch=""
|
||||
local config_file=".josh-sync.yml"
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--forward) direction="forward"; shift ;;
|
||||
--reverse) direction="reverse"; shift ;;
|
||||
--target) filter_target="$2"; shift 2 ;;
|
||||
--branch) filter_branch="$2"; shift 2 ;;
|
||||
--config) config_file="$2"; shift 2 ;;
|
||||
--debug) export JOSH_SYNC_DEBUG=1; shift ;;
|
||||
*) die "Unknown flag: $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
parse_config "$config_file"
|
||||
|
||||
# Forward sync
|
||||
if [ "$direction" = "forward" ] || [ "$direction" = "both" ]; then
|
||||
_sync_direction "forward" "$filter_target" "$filter_branch"
|
||||
fi
|
||||
|
||||
# Reverse sync
|
||||
if [ "$direction" = "reverse" ] || [ "$direction" = "both" ]; then
|
||||
_sync_direction "reverse" "$filter_target" "$filter_branch"
|
||||
fi
|
||||
}
|
||||
|
||||
_sync_direction() {
|
||||
local direction="$1"
|
||||
local filter_target="$2"
|
||||
local filter_branch="$3"
|
||||
|
||||
while read -r TARGET_JSON; do
|
||||
local target_name
|
||||
target_name=$(echo "$TARGET_JSON" | jq -r '.name')
|
||||
|
||||
# Filter to specific target if requested
|
||||
if [ -n "$filter_target" ] && [ "$filter_target" != "$target_name" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
log "INFO" "══════ Target: ${target_name} (${direction}) ══════"
|
||||
load_target "$TARGET_JSON"
|
||||
|
||||
# Resolve branches
|
||||
local branches
|
||||
if [ -n "$filter_branch" ]; then
|
||||
branches="$filter_branch"
|
||||
elif [ "$direction" = "reverse" ]; then
|
||||
# Exclude forward_only branches for reverse sync
|
||||
branches=$(echo "$TARGET_JSON" | jq -r '
|
||||
(.forward_only // []) as $fwd |
|
||||
.branches | keys[] | select(. as $b | $fwd | index($b) | not)
|
||||
')
|
||||
else
|
||||
branches=$(echo "$TARGET_JSON" | jq -r '.branches | keys[]')
|
||||
fi
|
||||
|
||||
# Sync each branch
|
||||
for branch in $branches; do
|
||||
echo "" >&2
|
||||
log "INFO" "━━━ Processing branch: ${branch} (${direction}) ━━━"
|
||||
|
||||
# Resolve branch mapping
|
||||
local mapped
|
||||
mapped=$(echo "$TARGET_JSON" | jq -r --arg b "$branch" '.branches[$b] // empty')
|
||||
if [ -z "$mapped" ]; then
|
||||
log "WARN" "No mapping for branch ${branch} — skipping"
|
||||
continue
|
||||
fi
|
||||
|
||||
export SYNC_BRANCH_MONO="$branch"
|
||||
export SYNC_BRANCH_SUBREPO="$mapped"
|
||||
|
||||
# Check state BEFORE cloning (skip if unchanged)
|
||||
local state prev_sha current_sha
|
||||
state=$(read_state "$branch")
|
||||
|
||||
if [ "$direction" = "forward" ]; then
|
||||
prev_sha=$(echo "$state" | jq -r '.last_forward.mono_sha // empty')
|
||||
current_sha=$(git rev-parse "origin/${branch}" 2>/dev/null || echo "unknown")
|
||||
|
||||
if [ "$prev_sha" = "$current_sha" ] && [ -n "$prev_sha" ]; then
|
||||
log "INFO" "Branch ${branch} unchanged since last sync — skipping"
|
||||
continue
|
||||
fi
|
||||
else
|
||||
prev_sha=$(echo "$state" | jq -r '.last_reverse.subrepo_sha // empty')
|
||||
current_sha=$(subrepo_ls_remote "${mapped}")
|
||||
|
||||
if [ -z "$current_sha" ]; then
|
||||
log "WARN" "Subrepo branch ${mapped} doesn't exist — skipping"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ "$prev_sha" = "$current_sha" ] && [ -n "$prev_sha" ]; then
|
||||
log "INFO" "Subrepo branch ${mapped} unchanged — skipping"
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
# Run sync
|
||||
local result
|
||||
if [ "$direction" = "forward" ]; then
|
||||
result=$(forward_sync)
|
||||
else
|
||||
result=$(reverse_sync)
|
||||
fi
|
||||
log "INFO" "Result: ${result}"
|
||||
|
||||
# Handle warnings
|
||||
if [ "$result" = "lease-rejected" ]; then
|
||||
echo "::warning::Target ${target_name}, branch ${branch}: subrepo changed during sync — will retry"
|
||||
continue
|
||||
fi
|
||||
if [ "$result" = "conflict" ]; then
|
||||
echo "::warning::Target ${target_name}, branch ${branch}: merge conflict — PR created on subrepo"
|
||||
fi
|
||||
if [ "$result" = "josh-rejected" ]; then
|
||||
echo "::error::Target ${target_name}, branch ${branch}: josh rejected push — check proxy logs"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Update state (only on success)
|
||||
local new_state
|
||||
if [ "$direction" = "forward" ]; then
|
||||
local subrepo_sha_now
|
||||
subrepo_sha_now=$(subrepo_ls_remote "${mapped}")
|
||||
new_state=$(jq -n \
|
||||
--arg m_sha "$current_sha" \
|
||||
--arg s_sha "${subrepo_sha_now:-}" \
|
||||
--arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
||||
--arg status "$result" \
|
||||
--argjson prev "$state" \
|
||||
'$prev + {last_forward: {mono_sha:$m_sha, subrepo_sha:$s_sha, timestamp:$ts, status:$status}}')
|
||||
else
|
||||
local mono_sha_now
|
||||
mono_sha_now=$(git rev-parse "origin/${branch}" 2>/dev/null || echo "")
|
||||
new_state=$(jq -n \
|
||||
--arg s_sha "$current_sha" \
|
||||
--arg m_sha "${mono_sha_now:-}" \
|
||||
--arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
||||
--arg status "$result" \
|
||||
--argjson prev "$state" \
|
||||
'$prev + {last_reverse: {subrepo_sha:$s_sha, mono_sha:$m_sha, timestamp:$ts, status:$status}}')
|
||||
fi
|
||||
|
||||
write_state "$branch" "$new_state"
|
||||
done
|
||||
done < <(echo "$JOSH_SYNC_TARGETS" | jq -c '.[]')
|
||||
}
|
||||
|
||||
# ─── Preflight Command ─────────────────────────────────────────────
|
||||
|
||||
cmd_preflight() {
|
||||
local config_file=".josh-sync.yml"
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--config) config_file="$2"; shift 2 ;;
|
||||
--debug) export JOSH_SYNC_DEBUG=1; shift ;;
|
||||
*) die "Unknown flag: $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
local RED='\033[0;31m'
|
||||
local GREEN='\033[0;32m'
|
||||
local YELLOW='\033[1;33m'
|
||||
local NC='\033[0m'
|
||||
|
||||
pass() { echo -e " ${GREEN}✓${NC} $1"; }
|
||||
fail() { echo -e " ${RED}✗${NC} $1"; ERRORS=$((ERRORS + 1)); }
|
||||
warn() { echo -e " ${YELLOW}!${NC} $1"; }
|
||||
|
||||
local ERRORS=0
|
||||
|
||||
echo "Josh Sync — Pre-flight Check"
|
||||
echo "============================="
|
||||
echo ""
|
||||
|
||||
# 1. Config
|
||||
echo "1. Configuration"
|
||||
|
||||
if [ ! -f "$config_file" ]; then
|
||||
fail "${config_file} not found (run from monorepo root)"
|
||||
exit 1
|
||||
fi
|
||||
pass "${config_file} exists"
|
||||
|
||||
parse_config "$config_file"
|
||||
|
||||
[ -n "$JOSH_PROXY_URL" ] && pass "proxy_url: ${JOSH_PROXY_URL}" || fail "proxy_url missing"
|
||||
[ -n "$MONOREPO_PATH" ] && pass "monorepo_path: ${MONOREPO_PATH}" || fail "monorepo_path missing"
|
||||
[ -n "$BOT_TRAILER" ] && pass "trailer: ${BOT_TRAILER}" || fail "trailer missing"
|
||||
[ -n "${GITEA_TOKEN:-}" ] && pass "SYNC_BOT_TOKEN is set" || warn "SYNC_BOT_TOKEN missing in .env"
|
||||
[ -n "${BOT_USER:-}" ] && pass "BOT_USER: ${BOT_USER}" || warn "BOT_USER missing in .env"
|
||||
|
||||
local target_count
|
||||
target_count=$(echo "$JOSH_SYNC_TARGETS" | jq 'length')
|
||||
pass "Targets configured: ${target_count}"
|
||||
|
||||
# 2. Per-target checks
|
||||
echo ""
|
||||
echo "2. Targets"
|
||||
|
||||
while read -r TARGET_JSON; do
|
||||
local target_name subfolder auth
|
||||
target_name=$(echo "$TARGET_JSON" | jq -r '.name')
|
||||
subfolder=$(echo "$TARGET_JSON" | jq -r '.subfolder')
|
||||
auth=$(echo "$TARGET_JSON" | jq -r '.subrepo_auth // "https"')
|
||||
|
||||
echo ""
|
||||
echo " ── Target: ${target_name} ──"
|
||||
|
||||
load_target "$TARGET_JSON"
|
||||
|
||||
[ -n "$JOSH_FILTER" ] && pass "josh_filter: ${JOSH_FILTER}" || fail "josh_filter missing"
|
||||
[ -n "$SUBREPO_URL" ] && pass "subrepo_url: ${SUBREPO_URL}" || fail "subrepo_url missing"
|
||||
|
||||
# Subfolder exists
|
||||
if [ -d "$subfolder" ]; then
|
||||
local file_count
|
||||
file_count=$(find "$subfolder" -type f | wc -l)
|
||||
pass "Subfolder ${subfolder}/ exists (${file_count} files)"
|
||||
else
|
||||
fail "Subfolder ${subfolder}/ not found"
|
||||
if [ -z "${CI:-}" ]; then
|
||||
echo " If the subrepo already has content, run: josh-sync import ${target_name}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# SSH key check
|
||||
if [ "$auth" = "ssh" ]; then
|
||||
local local_ssh_key_var
|
||||
local_ssh_key_var=$(echo "$TARGET_JSON" | jq -r '.subrepo_ssh_key_var // "SUBREPO_SSH_KEY"')
|
||||
if [ -n "${!local_ssh_key_var:-}" ]; then
|
||||
pass "SSH key set (${local_ssh_key_var})"
|
||||
else
|
||||
warn "SSH key missing (${local_ssh_key_var})"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Josh proxy connectivity
|
||||
if timeout 60 git ls-remote "$(josh_auth_url)" HEAD >/dev/null 2>&1; then
|
||||
pass "Josh filter works for ${target_name}"
|
||||
else
|
||||
fail "Cannot ls-remote through josh for ${target_name}"
|
||||
echo " Try: git ls-remote ${JOSH_PROXY_URL}/${MONOREPO_PATH}.git${JOSH_FILTER}.git"
|
||||
fi
|
||||
|
||||
# Subrepo connectivity
|
||||
if [ "$auth" = "ssh" ]; then
|
||||
local local_ssh_key_var2
|
||||
local_ssh_key_var2=$(echo "$TARGET_JSON" | jq -r '.subrepo_ssh_key_var // "SUBREPO_SSH_KEY"')
|
||||
if [ -n "${!local_ssh_key_var2:-}" ]; then
|
||||
if timeout 10 git ls-remote "$(subrepo_auth_url)" HEAD >/dev/null 2>&1; then
|
||||
pass "Subrepo reachable via SSH"
|
||||
else
|
||||
fail "Cannot ls-remote subrepo via SSH"
|
||||
fi
|
||||
else
|
||||
warn "Skipping subrepo check — no SSH key"
|
||||
fi
|
||||
else
|
||||
if timeout 10 git ls-remote "$(subrepo_auth_url)" HEAD >/dev/null 2>&1; then
|
||||
pass "Subrepo reachable via HTTPS"
|
||||
else
|
||||
fail "Cannot ls-remote subrepo via HTTPS"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Branch mapping
|
||||
echo "$TARGET_JSON" | jq -r '
|
||||
(.forward_only // []) as $fwd |
|
||||
.branches | to_entries[] |
|
||||
.key as $k | .value as $v |
|
||||
" mono/\($k) \(if ($fwd | index($k)) then "→ only" else "↔" end) subrepo/\($v)"
|
||||
'
|
||||
done < <(echo "$JOSH_SYNC_TARGETS" | jq -c '.[]')
|
||||
|
||||
# 3. Workflow path coverage
|
||||
echo ""
|
||||
echo "3. Workflow path coverage"
|
||||
|
||||
local forward_workflow=".gitea/workflows/josh-sync-forward.yml"
|
||||
if [ -f "$forward_workflow" ]; then
|
||||
pass "${forward_workflow} exists"
|
||||
|
||||
local workflow_paths
|
||||
workflow_paths=$(yq -r '.on.push.paths[]?' "$forward_workflow" 2>/dev/null \
|
||||
| sed 's|/\*\*$||; s|/\*$||' || echo "")
|
||||
|
||||
while read -r subfolder; do
|
||||
if echo "$workflow_paths" | grep -q "^${subfolder}$"; then
|
||||
pass "Workflow path covers ${subfolder}"
|
||||
else
|
||||
warn "Workflow paths missing ${subfolder}/** — add it to ${forward_workflow}"
|
||||
fi
|
||||
done < <(echo "$JOSH_SYNC_TARGETS" | jq -r '.[].subfolder')
|
||||
else
|
||||
warn "${forward_workflow} not found (optional for local-only use)"
|
||||
fi
|
||||
|
||||
if [ -f ".gitea/workflows/josh-sync-reverse.yml" ]; then
|
||||
pass ".gitea/workflows/josh-sync-reverse.yml exists"
|
||||
else
|
||||
warn ".gitea/workflows/josh-sync-reverse.yml not found (optional)"
|
||||
fi
|
||||
|
||||
# Summary
|
||||
echo ""
|
||||
echo "============================="
|
||||
if [ "$ERRORS" -eq 0 ]; then
|
||||
echo -e "${GREEN}All checks passed.${NC} Ready to deploy."
|
||||
else
|
||||
echo -e "${RED}${ERRORS} check(s) failed.${NC} Fix issues above before deploying."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── Import Command ────────────────────────────────────────────────
|
||||
|
||||
cmd_import() {
|
||||
local config_file=".josh-sync.yml"
|
||||
local target_name=""
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--config) config_file="$2"; shift 2 ;;
|
||||
--debug) export JOSH_SYNC_DEBUG=1; shift ;;
|
||||
-*) die "Unknown flag: $1" ;;
|
||||
*) target_name="$1"; shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$target_name" ]; then
|
||||
echo "Usage: josh-sync import <target>" >&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" "══════ Import target: ${target_name} ══════"
|
||||
load_target "$target_json"
|
||||
|
||||
local branches
|
||||
branches=$(echo "$target_json" | jq -r '.branches | keys[]')
|
||||
|
||||
for branch in $branches; do
|
||||
local mapped
|
||||
mapped=$(echo "$target_json" | jq -r --arg b "$branch" '.branches[$b] // empty')
|
||||
[ -z "$mapped" ] && continue
|
||||
|
||||
export SYNC_BRANCH_MONO="$branch"
|
||||
export SYNC_BRANCH_SUBREPO="$mapped"
|
||||
|
||||
local result
|
||||
result=$(initial_import)
|
||||
log "INFO" "Result: ${result}"
|
||||
done
|
||||
}
|
||||
|
||||
# ─── Reset Command ─────────────────────────────────────────────────
|
||||
|
||||
cmd_reset() {
|
||||
local config_file=".josh-sync.yml"
|
||||
local target_name=""
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--config) config_file="$2"; shift 2 ;;
|
||||
--debug) export JOSH_SYNC_DEBUG=1; shift ;;
|
||||
-*) die "Unknown flag: $1" ;;
|
||||
*) target_name="$1"; shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$target_name" ]; then
|
||||
echo "Usage: josh-sync reset <target>" >&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" "══════ Reset target: ${target_name} ══════"
|
||||
load_target "$target_json"
|
||||
|
||||
local branches
|
||||
branches=$(echo "$target_json" | jq -r '.branches | keys[]')
|
||||
|
||||
for branch in $branches; do
|
||||
local mapped
|
||||
mapped=$(echo "$target_json" | jq -r --arg b "$branch" '.branches[$b] // empty')
|
||||
[ -z "$mapped" ] && continue
|
||||
|
||||
export SYNC_BRANCH_MONO="$branch"
|
||||
export SYNC_BRANCH_SUBREPO="$mapped"
|
||||
|
||||
local result
|
||||
result=$(subrepo_reset)
|
||||
log "INFO" "Result: ${result}"
|
||||
done
|
||||
}
|
||||
|
||||
# ─── Status Command ────────────────────────────────────────────────
|
||||
|
||||
cmd_status() {
|
||||
local config_file=".josh-sync.yml"
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--config) config_file="$2"; shift 2 ;;
|
||||
--debug) export JOSH_SYNC_DEBUG=1; shift ;;
|
||||
*) die "Unknown flag: $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
parse_config "$config_file"
|
||||
|
||||
echo "Josh Sync Status"
|
||||
echo "================"
|
||||
echo ""
|
||||
echo "Josh proxy: ${JOSH_PROXY_URL}"
|
||||
echo "Monorepo: ${MONOREPO_PATH}"
|
||||
echo "Bot: ${BOT_NAME} <${BOT_EMAIL}>"
|
||||
echo ""
|
||||
|
||||
while read -r TARGET_JSON; do
|
||||
local target_name subfolder auth
|
||||
target_name=$(echo "$TARGET_JSON" | jq -r '.name')
|
||||
subfolder=$(echo "$TARGET_JSON" | jq -r '.subfolder')
|
||||
auth=$(echo "$TARGET_JSON" | jq -r '.subrepo_auth // "https"')
|
||||
|
||||
echo "Target: ${target_name}"
|
||||
echo " Subfolder: ${subfolder}"
|
||||
echo " Subrepo: $(echo "$TARGET_JSON" | jq -r '.subrepo_url')"
|
||||
echo " Auth: ${auth}"
|
||||
echo " Filter: $(echo "$TARGET_JSON" | jq -r '.josh_filter')"
|
||||
|
||||
load_target "$TARGET_JSON"
|
||||
|
||||
echo " Branches:"
|
||||
echo "$TARGET_JSON" | jq -r '.branches | to_entries[] | " \(.key) → \(.value)"'
|
||||
|
||||
local fwd_only
|
||||
fwd_only=$(echo "$TARGET_JSON" | jq -r '(.forward_only // []) | join(", ")')
|
||||
[ -n "$fwd_only" ] && echo " Forward only: ${fwd_only}"
|
||||
|
||||
# Show state for each branch
|
||||
echo " State:"
|
||||
for branch in $(echo "$TARGET_JSON" | jq -r '.branches | keys[]'); do
|
||||
local state
|
||||
state=$(read_state "$branch")
|
||||
if [ "$state" = "{}" ]; then
|
||||
echo " ${branch}: (no state)"
|
||||
else
|
||||
local fwd_status fwd_ts rev_status rev_ts
|
||||
fwd_status=$(echo "$state" | jq -r '.last_forward.status // "-"')
|
||||
fwd_ts=$(echo "$state" | jq -r '.last_forward.timestamp // "-"')
|
||||
rev_status=$(echo "$state" | jq -r '.last_reverse.status // "-"')
|
||||
rev_ts=$(echo "$state" | jq -r '.last_reverse.timestamp // "-"')
|
||||
echo " ${branch}: fwd=${fwd_status} (${fwd_ts}), rev=${rev_status} (${rev_ts})"
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
done < <(echo "$JOSH_SYNC_TARGETS" | jq -c '.[]')
|
||||
}
|
||||
|
||||
# ─── State Command ─────────────────────────────────────────────────
|
||||
|
||||
cmd_state() {
|
||||
local subcmd="${1:-}"
|
||||
shift || true
|
||||
|
||||
local config_file=".josh-sync.yml"
|
||||
local target_name=""
|
||||
local branch="main"
|
||||
|
||||
# Parse remaining args
|
||||
local args=()
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--config) config_file="$2"; shift 2 ;;
|
||||
-*) die "Unknown flag: $1" ;;
|
||||
*) args+=("$1"); shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ "${#args[@]}" -ge 1 ] && target_name="${args[0]}"
|
||||
[ "${#args[@]}" -ge 2 ] && branch="${args[1]}"
|
||||
|
||||
case "$subcmd" in
|
||||
show)
|
||||
[ -n "$target_name" ] || { echo "Usage: josh-sync state show <target> [branch]" >&2; exit 1; }
|
||||
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"
|
||||
|
||||
load_target "$target_json"
|
||||
read_state "$branch" | jq '.'
|
||||
;;
|
||||
reset)
|
||||
[ -n "$target_name" ] || { echo "Usage: josh-sync state reset <target> [branch]" >&2; exit 1; }
|
||||
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"
|
||||
|
||||
load_target "$target_json"
|
||||
write_state "$branch" "{}"
|
||||
log "INFO" "State reset for target '${target_name}', branch '${branch}'"
|
||||
;;
|
||||
*)
|
||||
echo "Usage: josh-sync state <show|reset> <target> [branch]" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ─── Main ───────────────────────────────────────────────────────────
|
||||
|
||||
main() {
|
||||
local command="${1:-}"
|
||||
shift || true
|
||||
|
||||
# Handle global flags that can appear before command
|
||||
case "$command" in
|
||||
--version|-v)
|
||||
echo "josh-sync $(josh_sync_version)"
|
||||
exit 0
|
||||
;;
|
||||
--help|-h|"")
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$command" in
|
||||
sync) cmd_sync "$@" ;;
|
||||
preflight) cmd_preflight "$@" ;;
|
||||
import) cmd_import "$@" ;;
|
||||
reset) cmd_reset "$@" ;;
|
||||
status) cmd_status "$@" ;;
|
||||
state) cmd_state "$@" ;;
|
||||
*)
|
||||
echo "Unknown command: ${command}" >&2
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user