#!/usr/bin/env bash set -euo pipefail # Get the directory where this script is located SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" MYSQL_CONTAINER="git-kc-mysql" MYSQL_USER="root" MYSQL_DB="giteadb" MYSQL_ROOT_PASS="K0DprNKJ^vAu3Mx32hMZ%LCzWKElFRfA" GIT_HOST="root@vps.git" # ───────────────────────────────────────────── # create # ───────────────────────────────────────────── do_create() { if [[ $# -lt 1 ]]; then echo "Usage: $0 create " exit 1 fi if [[ -z "${AGENT_WORKSPACE:-}" ]]; then echo "Error: script must be executed by pcexec" exit 1 fi REPO_NAME="$1" # Validate repo name if ! [[ "$REPO_NAME" =~ ^[a-zA-Z0-9_.-]+$ ]]; then echo "Error: Invalid repository name '$REPO_NAME'" echo "Only alphanumeric, hyphens, underscores, and dots are allowed." exit 1 fi REPO_DIR="${AGENT_WORKSPACE}/${REPO_NAME}" mkdir -p "${REPO_DIR}" cd "${REPO_DIR}" git init USERNAME="$(secret-mgr get-username --key git)" REMOTE_URL="https://git.hangman-lab.top/${USERNAME}/${REPO_NAME}.git" git remote add origin "${REMOTE_URL}" do_config --repo-path "${REPO_DIR}" echo "Done! Repository created at: ${REPO_DIR}" } # ───────────────────────────────────────────── # add-collaborators # ───────────────────────────────────────────── do_add_collaborators() { local user="" repo="" while [[ $# -gt 0 ]]; do case "$1" in --user) user="$2"; shift 2 ;; --repo) repo="$2"; shift 2 ;; *) echo "Unknown option: $1"; exit 1 ;; esac done if [[ -z "$user" || -z "$repo" ]]; then echo "Usage: $0 add-collaborators --user --repo " exit 1 fi if ! secret-mgr list | grep -q "git-access-token"; then echo "Error: git-access-token not found. Generate one first." exit 1 fi owner=$(secret-mgr get-username --key git) token=$(secret-mgr get-secret --key git-access-token) curl -s -X PUT \ -H "Authorization: token $token" \ -H "Content-Type: application/json" \ -d '{"permission":"write"}' \ "https://git.hangman-lab.top/api/v1/repos/$owner/$repo/collaborators/$user" } # ───────────────────────────────────────────── # list-all # ───────────────────────────────────────────── do_list_all() { USERNAME=$(ego-mgr get default-username) if [[ -z "$USERNAME" ]]; then echo "Error: cannot get username from ego-mgr" >&2 exit 1 fi QUERY=" SELECT r.name, u.name as owner, r.is_private, COALESCE((r.owner_id = (SELECT id FROM user WHERE lower_name = LOWER('$USERNAME')) COLLATE utf8mb4_unicode_ci OR a.user_id = (SELECT id FROM user WHERE lower_name = LOWER('$USERNAME')) COLLATE utf8mb4_unicode_ci OR EXISTS (SELECT 1 FROM team_user tu JOIN team t ON t.id = tu.team_id WHERE tu.uid = (SELECT id FROM user WHERE lower_name = LOWER('$USERNAME')) COLLATE utf8mb4_unicode_ci AND (t.includes_all_repositories = 1 OR EXISTS (SELECT 1 FROM team_repo tr WHERE tr.team_id = t.id AND tr.repo_id = r.id)))), 0) as can_write FROM repository r JOIN user u ON r.owner_id = u.id LEFT JOIN access a ON a.repo_id = r.id AND a.user_id = (SELECT id FROM user WHERE lower_name = LOWER('$USERNAME')) COLLATE utf8mb4_unicode_ci WHERE r.is_archived = 0 AND (r.owner_id = (SELECT id FROM user WHERE lower_name = LOWER('$USERNAME')) COLLATE utf8mb4_unicode_ci OR r.is_private = 0 OR a.user_id = (SELECT id FROM user WHERE lower_name = LOWER('$USERNAME')) COLLATE utf8mb4_unicode_ci OR EXISTS (SELECT 1 FROM team_user tu WHERE tu.uid = (SELECT id FROM user WHERE lower_name = LOWER('$USERNAME')) COLLATE utf8mb4_unicode_ci)) ORDER BY r.name " RESULT=$(ssh -o StrictHostKeyChecking=no "$GIT_HOST" \ "docker exec $MYSQL_CONTAINER mysql -u $MYSQL_USER -p'$MYSQL_ROOT_PASS' -N -e \"$QUERY\" $MYSQL_DB" 2>/dev/null) echo "| proj-name | owner | url | can-write |" echo "|------------|-------|-----|-----------|" [[ -z "$RESULT" ]] && exit 0 echo "$RESULT" | while IFS=$'\t' read -r name owner is_private can_write; do can_write_val=$([[ "$can_write" == "1" ]] && echo "yes" || echo "no") echo "| $name | $owner | https://git.hangman-lab.top/$owner/$name.git | $can_write_val |" done } # ───────────────────────────────────────────── # config # ───────────────────────────────────────────── do_config() { local REPO_PATH="" local RECURSIVE=false while [[ $# -gt 0 ]]; do case "$1" in --repo-path) REPO_PATH="${2:-}"; shift 2 ;; --recursive) RECURSIVE=true; shift ;; *) echo "Usage: $0 config --repo-path [--recursive]"; exit 1 ;; esac done if [[ -z "$REPO_PATH" ]]; then echo "Usage: $0 config --repo-path [--recursive]" exit 1 fi EMAIL=$(ego-mgr get email) if [[ -z "$EMAIL" ]]; then echo "Error: email not set in ego-mgr" exit 1 fi is_git_repo() { if [[ -d "$1/.git" ]]; then return 0; fi if [[ -f "$1/.git" ]]; then gitdir=$(grep -m1 "gitdir:" "$1/.git" | cut -d' ' -f2 | tr -d ' ') [[ -n "$gitdir" ]]; return 0 fi return 1 } if ! is_git_repo "$REPO_PATH"; then echo "Not a git repo: $REPO_PATH" exit 1 fi USER="$(secret-mgr get-username --key git)" PASS="$(secret-mgr get-secret --key git)" ENC_USER="$(U="$USER" python3 - <<'PY' import os, urllib.parse print(urllib.parse.quote(os.environ['U'], safe='')) PY )" configure_repo() { local repo="$1" relative="${2:-}" local name="${relative:-$repo}" echo "Configuring: $name" ( cd "$repo" && git config user.name "$USER" ) ( cd "$repo" && git config user.email "$EMAIL" ) local git_dir git_dir="$(cd "$repo" && git rev-parse --absolute-git-dir)" local cred_file="${git_dir}/credentials" ( cd "$repo" && git config credential.helper "store --file ${cred_file}" ) ( cd "$repo" && GIT_ASKPASS=true git credential-store --file "${cred_file}" store </dev/null | awk '{print $2}' || true) for sm in $submodules; do sm_path="$REPO_PATH/$sm" if is_git_repo "$sm_path"; then configure_repo "$sm_path" "$sm" fi done fi echo "OK" } # ───────────────────────────────────────────── # get-latest # ───────────────────────────────────────────── do_get_latest() { local REPO_NAME="" BRANCH="main" RECURSIVE=false FORCE=false while [[ $# -gt 0 ]]; do case "$1" in --recursive) RECURSIVE=true; shift ;; --force) FORCE=true; shift ;; -*) echo "Unknown option: $1" echo "Usage: $0 get-latest [branch] [--recursive] [--force]" exit 1 ;; *) [[ -z "$REPO_NAME" ]] && REPO_NAME="$1" && shift && continue [[ -z "$BRANCH" ]] && BRANCH="$1" && shift && continue echo "Unknown argument: $1" exit 1 ;; esac done if [[ -z "$REPO_NAME" ]]; then echo "Usage: $0 get-latest [branch] [--recursive] [--force]" exit 1 fi if [[ -z "${AGENT_WORKSPACE:-}" ]]; then echo "Error: script must be executed by pcexec" exit 1 fi local REPO_DIR="${AGENT_WORKSPACE}/${REPO_NAME}" if [[ -d "$REPO_DIR" ]]; then # Repo exists locally — update in place if ! git -C "$REPO_DIR" rev-parse --is-inside-work-tree 2>/dev/null; then echo "Error: $REPO_DIR is not a git repository" exit 1 fi local dirty dirty=$(git -C "$REPO_DIR" status --porcelain 2>/dev/null) if [[ -n "$dirty" ]] && [[ "$FORCE" != "true" ]]; then echo "Error: $REPO_DIR has uncommitted changes. Use --force to discard them." exit 1 fi if [[ -n "$dirty" ]]; then echo "Discarding uncommitted changes..." git -C "$REPO_DIR" checkout -- . 2>/dev/null || true fi echo "Fetching: $REPO_NAME ($BRANCH)" git -C "$REPO_DIR" fetch origin "$BRANCH" 2>/dev/null || true git -C "$REPO_DIR" checkout "$BRANCH" 2>/dev/null || git -C "$REPO_DIR" checkout -b "$BRANCH" "origin/$BRANCH" 2>/dev/null || true if [[ "$RECURSIVE" == "true" ]]; then echo "Updating submodules..." git -C "$REPO_DIR" submodule update --init --recursive 2>/dev/null || true fi echo "Done: $REPO_DIR updated." else # Repo does not exist locally — clone it echo "Repo not found locally. Looking up URL..." local url="" url=$(bash "$SCRIPT_DIR/repo" list-all 2>/dev/null | grep "| $REPO_NAME |" | awk -F'\\|' '{gsub(/^ +| +$/,"",$4); print $4}') if [[ -z "$url" ]]; then echo "Error: repository '$REPO_NAME' not found in list-all output" exit 1 fi echo "Cloning: $url" git clone "$url" "$REPO_DIR" 2>/dev/null || { echo "Error: git clone failed" exit 1 } do_config --repo-path "$REPO_DIR" if [[ "$RECURSIVE" == "true" ]]; then echo "Updating submodules..." git -C "$REPO_DIR" submodule update --init --recursive 2>/dev/null || true fi echo "Done: $REPO_NAME cloned to $REPO_DIR." fi } # ───────────────────────────────────────────── # search # ───────────────────────────────────────────── do_search() { if [[ $# -lt 1 ]]; then echo "Usage: $0 search " exit 1 fi REPO_NAME="$1" TOKEN=$(secret-mgr get-secret --key git-access-token 2>/dev/null || secret-mgr get-secret --key git) RESP=$(curl -s -H "Authorization: token $TOKEN" \ "https://git.hangman-lab.top/api/v1/repos/search?q=${REPO_NAME}") RESULT=$(echo "$RESP" | python3 -c " import sys, json data = json.load(sys.stdin) results = data.get('data', []) if isinstance(data, dict) else [] for r in results: if isinstance(r, dict) and r.get('name') == '${REPO_NAME}': owner = r.get('owner', {}) login = owner.get('login', '') if isinstance(owner, dict) else '' out = { 'name': r.get('name', ''), 'id': r.get('id'), 'owner': login, 'clone-url': r.get('clone_url', '') } print(json.dumps(out)) sys.exit(0) print('NOT_FOUND') " 2>/dev/null) if [[ "$RESULT" == "NOT_FOUND" ]]; then echo "Error: repository '$REPO_NAME' not found" >&2 exit 1 fi echo "$RESULT" } # ───────────────────────────────────────────── # Dispatch # ───────────────────────────────────────────── if [[ $# -lt 1 ]]; then echo "Usage: $0 [args...]" echo "" echo "Commands:" echo " create Create a new repository" echo " add-collaborators --user --repo Add collaborator" echo " list-all List all visible repositories" echo " config --repo-path [--recursive] Configure repo credentials" echo " get-latest [branch] [--recursive] [--force] Pull latest or clone if missing" echo " search Search for a repository by exact name" exit 1 fi subcommand="$1"; shift case "$subcommand" in create) do_create "$@" ;; add-collaborators) do_add_collaborators "$@" ;; list-all) do_list_all "$@" ;; config) do_config "$@" ;; get-latest) do_get_latest "$@" ;; search) do_search "$@" ;; *) echo "Unknown command: $subcommand"; exit 1 ;; esac