b7e195e426
- New: mcp_server.py — FastMCP stdio server exposing 7 tools: gitea_create_issue, gitea_create_pr, gitea_close_issue, gitea_list_issues, gitea_view_issue, gitea_mark_issue, gitea_mirror_refs - New: auth.py — shared authentication and API helpers (get_credentials, get_auth_header, api_request, repo_api_url) - Refactored: create_pr.py, create_issue.py, manage_labels.py to use shared auth module (eliminates credential duplication) - New: tests/test_mcp_server.py — 17 tests for all MCP tools - Updated: tests/test_credentials.py — now tests auth.py directly - Updated: tests/test_create_issue.py — adapted for refactored imports - New: requirements.txt — frozen venv deps (mcp[cli], pytest) - Updated: README.md — MCP server as primary interface - Config: added gitea-tools to mcp_config.json Closes #1. Resolves #2, #5. Relates to #7.
118 lines
5.3 KiB
Bash
Executable File
118 lines
5.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# sync_repos.sh — mirror branches + tags between the two Gitea instances that
|
|
# host the Timesheet repo, in BOTH directions.
|
|
#
|
|
# dadeschools : gitea.dadeschools.net / Contractor/Timesheet (HTTPS — SSH:2222 is flaky)
|
|
# prgs : gitea-ssh.prgs.cc:2222 / Scaled-Tech-Consulting/Timesheet (SSH — HTTPS host 404s)
|
|
#
|
|
# Safety model:
|
|
# * ADDITIVE by default. A branch on only one side is pushed to the other.
|
|
# * A shared branch where one side is strictly ahead is fast-forwarded.
|
|
# * A shared branch that has DIVERGED is skipped with a loud warning
|
|
# (never auto-overwritten). Resolve those by hand.
|
|
# * Dry-run by default; pass --apply to actually push. --force lets the
|
|
# fast-forward pushes use --force (still skips diverged branches).
|
|
#
|
|
# Auth is automatic via the macOS keychain (`git credential fill`), same as the
|
|
# other Gitea-Tools scripts. Run it from inside any clone of the repo, or set
|
|
# REPO=/path/to/clone.
|
|
#
|
|
# Usage:
|
|
# ./sync_repos.sh # dry run — show what WOULD sync
|
|
# ./sync_repos.sh --apply # perform the sync
|
|
# ./sync_repos.sh --apply --force
|
|
set -euo pipefail
|
|
|
|
# --- config ------------------------------------------------------------------
|
|
DADE_URL="https://gitea.dadeschools.net/Contractor/Timesheet.git"
|
|
PRGS_URL="ssh://git@gitea-ssh.prgs.cc:2222/Scaled-Tech-Consulting/Timesheet.git"
|
|
REPO="${REPO:-$(pwd)}"
|
|
|
|
APPLY=0
|
|
FORCE=0
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--apply) APPLY=1 ;;
|
|
--force) FORCE=1 ;;
|
|
-h|--help) sed -n '2,30p' "$0"; exit 0 ;;
|
|
*) echo "Unknown flag: $arg" >&2; exit 2 ;;
|
|
esac
|
|
done
|
|
|
|
cd "$REPO"
|
|
git rev-parse --git-dir >/dev/null 2>&1 || { echo "Not a git repo: $REPO" >&2; exit 1; }
|
|
|
|
note() { printf '%s\n' "$*"; }
|
|
action() { if [ "$APPLY" -eq 1 ]; then printf ' ✓ %s\n' "$*"; else printf ' [dry] %s\n' "$*"; fi; }
|
|
|
|
# --- fetch both sides into private namespaces --------------------------------
|
|
note "==> Fetching both remotes..."
|
|
git fetch --prune "$DADE_URL" '+refs/heads/*:refs/sync/dade/*' 'refs/tags/*:refs/tags/*' >/dev/null 2>&1 \
|
|
|| { echo "ERROR: cannot fetch dadeschools (check VPN/keychain)" >&2; exit 1; }
|
|
git fetch --prune "$PRGS_URL" '+refs/heads/*:refs/sync/prgs/*' >/dev/null 2>&1 \
|
|
|| { echo "ERROR: cannot fetch prgs (check SSH access)" >&2; exit 1; }
|
|
|
|
# --- reconcile branches ------------------------------------------------------
|
|
# push <dest_url> <sha> <branch> <label>
|
|
push_branch() {
|
|
local url="$1" sha="$2" b="$3" label="$4" ff_flag=""
|
|
[ "$FORCE" -eq 1 ] && ff_flag="--force"
|
|
action "push '${b}' -> ${label}"
|
|
if [ "$APPLY" -eq 1 ]; then
|
|
git push $ff_flag "$url" "${sha}:refs/heads/${b}" >/dev/null 2>&1 \
|
|
&& note " done." \
|
|
|| note " FAILED (see: git push $ff_flag $url ${sha}:refs/heads/${b})"
|
|
fi
|
|
}
|
|
|
|
branches=$(git for-each-ref --format='%(refname:lstrip=3)' refs/sync/dade refs/sync/prgs | sort -u)
|
|
|
|
note "==> Reconciling branches..."
|
|
synced=0; pushed=0; diverged=0
|
|
for b in $branches; do
|
|
d=$(git rev-parse -q --verify "refs/sync/dade/${b}" || true)
|
|
p=$(git rev-parse -q --verify "refs/sync/prgs/${b}" || true)
|
|
|
|
if [ -n "$d" ] && [ -z "$p" ]; then
|
|
note "branch '${b}': only on dadeschools"; push_branch "$PRGS_URL" "$d" "$b" "prgs"; pushed=$((pushed+1)); continue
|
|
fi
|
|
if [ -z "$d" ] && [ -n "$p" ]; then
|
|
note "branch '${b}': only on prgs"; push_branch "$DADE_URL" "$p" "$b" "dadeschools"; pushed=$((pushed+1)); continue
|
|
fi
|
|
# on both
|
|
if [ "$d" = "$p" ]; then synced=$((synced+1)); continue; fi
|
|
if git merge-base --is-ancestor "$p" "$d"; then
|
|
note "branch '${b}': dadeschools is ahead (fast-forward)"; push_branch "$PRGS_URL" "$d" "$b" "prgs"; pushed=$((pushed+1))
|
|
elif git merge-base --is-ancestor "$d" "$p"; then
|
|
note "branch '${b}': prgs is ahead (fast-forward)"; push_branch "$DADE_URL" "$p" "$b" "dadeschools"; pushed=$((pushed+1))
|
|
else
|
|
note "branch '${b}': ⚠ DIVERGED — skipped (resolve manually; --force will not auto-pick a winner)"; diverged=$((diverged+1))
|
|
fi
|
|
done
|
|
|
|
# --- tags (additive both ways) ----------------------------------------------
|
|
note "==> Syncing tags..."
|
|
sync_tags() {
|
|
local url="$1" label="$2"
|
|
local remote_tags local_tags missing
|
|
remote_tags=$(git ls-remote --tags "$url" 2>/dev/null | awk '{print $2}' | grep -v '\^{}' | sed 's#refs/tags/##' | sort || true)
|
|
local_tags=$(git tag -l | sort)
|
|
missing=$(comm -23 <(printf '%s\n' "$local_tags") <(printf '%s\n' "$remote_tags"))
|
|
if [ -z "$missing" ]; then note " ${label}: tags up to date"; return; fi
|
|
for t in $missing; do
|
|
action "push tag '${t}' -> ${label}"
|
|
[ "$APPLY" -eq 1 ] && git push "$url" "refs/tags/${t}" >/dev/null 2>&1 && note " done."
|
|
done
|
|
}
|
|
sync_tags "$DADE_URL" "dadeschools"
|
|
sync_tags "$PRGS_URL" "prgs"
|
|
|
|
# --- cleanup private namespace ----------------------------------------------
|
|
git for-each-ref --format='%(refname)' refs/sync | while read -r r; do git update-ref -d "$r"; done
|
|
|
|
note ""
|
|
note "==> Summary: ${synced} already in sync, ${pushed} branch push(es), ${diverged} diverged/skipped."
|
|
[ "$APPLY" -eq 0 ] && note " (dry run — re-run with --apply to perform the sync)"
|
|
[ "$diverged" -gt 0 ] && note " ⚠ ${diverged} diverged branch(es) need manual reconciliation."
|
|
exit 0
|