--- description: Push local recipe commits to git.autonomic.zone and open a PR against an upstream-synced main branch argument-hint: allowed-tools: [Bash, Read] --- # Recipe Create PR Take the local commits made to a recipe (e.g. during `/recipe-upgrade-apply`) and open a pull request on the Gitea instance at `git.autonomic.zone`. The Gitea repo's `main` branch is force-synced from the recipe's upstream `main` so the PR diff shows only the local changes. The recipe name is: $ARGUMENTS ## Prerequisites Credentials are read from `test-ssh/.testenv`. The following variables must be set: - `GITEA_USERNAME` — bot account on the Gitea instance - `GITEA_PASSWORD` — bot password - `GITEA_URL` — Gitea host (e.g. `git.autonomic.zone`) Optional: - `GITEA_NAMESPACE` — owner under which to create/find repos. Defaults to `recipe-maintainers` (the org whose `recipe-maintainers` team auto-grants access to all repos within it). The recipe must be checked out at `~/.abra/recipes/$ARGUMENTS` with at least one commit beyond `origin/main`. ## Steps Run the following script with the recipe name substituted in: ```bash #!/usr/bin/env bash set -euo pipefail RECIPE="$ARGUMENTS" WORKSPACE="/workspace" RECIPE_DIR="${HOME}/.abra/recipes/${RECIPE}" TESTENV="${WORKSPACE}/test-ssh/.testenv" # --- Load credentials --- [ -f "${TESTENV}" ] || { echo "ERROR: ${TESTENV} not found"; exit 1; } set -a; . "${TESTENV}"; set +a : "${GITEA_USERNAME:?missing in .testenv}" : "${GITEA_PASSWORD:?missing in .testenv}" : "${GITEA_URL:?missing in .testenv}" NAMESPACE="${GITEA_NAMESPACE:-recipe-maintainers}" PASS_ENC=$(python3 -c "import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1],safe=''))" "${GITEA_PASSWORD}") API="https://${GITEA_URL}/api/v1" AUTH=(-u "${GITEA_USERNAME}:${GITEA_PASSWORD}") # --- Validate recipe checkout --- [ -d "${RECIPE_DIR}/.git" ] || { echo "ERROR: ${RECIPE_DIR} is not a git repo. Run 'abra recipe fetch ${RECIPE}' first."; exit 1; } cd "${RECIPE_DIR}" # --- Fetch upstream main --- echo "→ Fetching upstream main from origin..." git fetch origin main # --- Find diverged commits --- DIVERGED=$(git log --oneline origin/main..HEAD 2>/dev/null || true) if [ -z "${DIVERGED}" ]; then echo "ERROR: HEAD has no commits beyond origin/main. Nothing to PR." exit 1 fi echo "→ Local commits to PR:" echo "${DIVERGED}" | sed 's/^/ /' # --- Determine PR branch name from the most recent commit --- LATEST_MSG=$(git log -1 --pretty=%s HEAD) if echo "${LATEST_MSG}" | grep -qiE "upgrade to [0-9]"; then VERSION=$(echo "${LATEST_MSG}" | grep -oiE "upgrade to [0-9][^[:space:]]+" | awk '{print $NF}') BRANCH="upgrade-${VERSION}" else BRANCH="pr-$(date -u +%Y%m%d-%H%M%S)" fi echo "→ PR branch: ${BRANCH}" # --- Check / create Gitea repo --- REPO_URL_API="${API}/repos/${NAMESPACE}/${RECIPE}" STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${AUTH[@]}" "${REPO_URL_API}") if [ "${STATUS}" = "404" ]; then echo "→ Repo ${NAMESPACE}/${RECIPE} does not exist; creating..." CREATE_BODY=$(python3 -c "import json;print(json.dumps({'name':'${RECIPE}','private':True,'default_branch':'main','auto_init':False}))") # Try org namespace first CREATE_OUT=$(mktemp) CREATE_STATUS=$(curl -s -o "${CREATE_OUT}" -w "%{http_code}" "${AUTH[@]}" \ -H "Content-Type: application/json" \ -X POST "${API}/orgs/${NAMESPACE}/repos" \ -d "${CREATE_BODY}") if [ "${CREATE_STATUS}" != "201" ]; then echo " ! create under org ${NAMESPACE} returned HTTP ${CREATE_STATUS} — falling back to user namespace ${GITEA_USERNAME}" cat "${CREATE_OUT}" >&2 echo "" >&2 CREATE_STATUS=$(curl -s -o "${CREATE_OUT}" -w "%{http_code}" "${AUTH[@]}" \ -H "Content-Type: application/json" \ -X POST "${API}/user/repos" \ -d "${CREATE_BODY}") if [ "${CREATE_STATUS}" != "201" ]; then echo "ERROR: failed to create repo (HTTP ${CREATE_STATUS}):" cat "${CREATE_OUT}" rm -f "${CREATE_OUT}" exit 1 fi NAMESPACE="${GITEA_USERNAME}" fi rm -f "${CREATE_OUT}" echo " ✓ created ${NAMESPACE}/${RECIPE}" elif [ "${STATUS}" = "200" ]; then echo "→ Repo ${NAMESPACE}/${RECIPE} already exists" else echo "ERROR: unexpected HTTP ${STATUS} when checking ${REPO_URL_API}" exit 1 fi # --- Set up the gitea remote with credentials embedded --- REMOTE_URL="https://${GITEA_USERNAME}:${PASS_ENC}@${GITEA_URL}/${NAMESPACE}/${RECIPE}.git" if git remote | grep -qx gitea; then git remote set-url gitea "${REMOTE_URL}" else git remote add gitea "${REMOTE_URL}" fi # --- Force-sync Gitea main with origin/main so the PR diff is clean --- echo "→ Force-syncing gitea/main from origin/main..." git push --force gitea "refs/remotes/origin/main:refs/heads/main" # --- Push local commits as the PR branch --- echo "→ Pushing local commits as branch '${BRANCH}'..." git push --force gitea "HEAD:refs/heads/${BRANCH}" # --- Create the PR --- # NOTE: for an upgrade PR the body (passed in via RECIPE_PR_BODY) MUST link the upstream release notes # — one explicit line per upgraded image/service, e.g. # **Upstream release notes:** →: # pulling each URL from recipe-info//upstream.md (between the current → new version). These links # belong in the PR body itself, NOT only in a side report, so the reviewer sees what changed upstream. PR_TITLE="${LATEST_MSG}" if [ -n "${RECIPE_PR_BODY:-}" ]; then PR_BODY="${RECIPE_PR_BODY}" else PR_BODY=$(printf "Local commits on top of upstream main:\n\n%s\n" "$(git log origin/main..HEAD --pretty='- %h %s')") fi PR_BODY="${PR_BODY} cc @trav @notplants" PR_PAYLOAD=$(python3 -c " import json, sys print(json.dumps({ 'title': sys.argv[1], 'body': sys.argv[2], 'head': sys.argv[3], 'base': 'main', 'reviewers': ['trav', 'notplants'], }))" "${PR_TITLE}" "${PR_BODY}" "${BRANCH}") PR_RESPONSE=$(mktemp) PR_STATUS=$(curl -s -o "${PR_RESPONSE}" -w "%{http_code}" "${AUTH[@]}" \ -H "Content-Type: application/json" \ -X POST "${API}/repos/${NAMESPACE}/${RECIPE}/pulls" \ -d "${PR_PAYLOAD}") if [ "${PR_STATUS}" = "201" ]; then PR_URL=$(python3 -c "import json;print(json.load(open('${PR_RESPONSE}'))['html_url'])") echo "" echo "✓ PR created: ${PR_URL}" elif [ "${PR_STATUS}" = "409" ] || grep -q "pull request already exists" "${PR_RESPONSE}" 2>/dev/null; then echo "" echo "ℹ A PR for branch '${BRANCH}' already exists. See:" echo " https://${GITEA_URL}/${NAMESPACE}/${RECIPE}/pulls" else echo "ERROR: PR creation failed (HTTP ${PR_STATUS}):" cat "${PR_RESPONSE}" rm -f "${PR_RESPONSE}" exit 1 fi rm -f "${PR_RESPONSE}" ``` After the script runs, report back the PR URL (or the existing-PR list URL if Gitea returned 409). ## Notes - The `gitea` remote is created/updated in `~/.abra/recipes//.git/config` with the password embedded — this is acceptable here since `.testenv` already stores the password in plaintext, but be aware the remote URL is now stored in that local file. - Re-running the skill is safe: the script force-pushes both the synced `main` and the PR branch, and reports gracefully if a PR for that branch already exists. - The PR branch name is derived from the most recent commit message — if it matches `upgrade to `, the branch becomes `upgrade-`; otherwise a timestamped name is used. - **For upgrade PRs, `RECIPE_PR_BODY` must carry the upstream release-notes links** (one line per upgraded service: `**Upstream release notes:** →: `, sourced from `recipe-info//upstream.md`). `/recipe-upgrade-apply` composes the body with these links already; if you invoke this command directly for an upgrade, include them in `RECIPE_PR_BODY` yourself so the reviewer sees what changed upstream.