- 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.
This commit is contained in:
Executable
+117
@@ -0,0 +1,117 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user