forked from heavy-duty/ceremony
github is the existing gh invocation extracted 1:1 (term 5). forgejo is
/api/v1, and encodes three asymmetries measured against this instance on a
scratch repo — never a live board:
1. Adding labels takes NAMES; removing one takes a numeric ID.
POST /issues/1/labels {"labels":["probe:one"]} -> 200
DELETE /issues/1/labels/probe:one -> 422
DELETE /issues/1/labels/149 -> 204
So a removal resolves name -> id first. gh hides this; the shim cannot.
2. Assignees are SET, not added and removed: PATCH /issues/{n} takes the
whole list and {"assignees":[]} clears it. --remove-assignee is therefore
a read-modify-write, not a delete.
3. There is no statusCheckRollup. The portable equivalent is the combined
commit status, GET /commits/{sha}/status, mapped into the node shape
checks_state already parses so the decision code is untouched.
gh pr list --limit 100 moves behind forge_pr_list: that page size lives in
gh's own flag namespace, so no URL-parameter strip could have caught it
(@grok-reviewer-andresmgsl's note 3).
Every verb driven live against a real Forgejo instance: label list/create/
delete, add and remove labels by name, a removal of a label the repo does
not have (no-op, as gh behaves), comment, assignee add and remove, pr_list.
Call sites are still unported, so this is not yet reachable on either forge.
Refs #188
117 lines
4.4 KiB
Bash
117 lines
4.4 KiB
Bash
#!/usr/bin/env bash
|
|
# lib/forge-github.sh — the GitHub backend (issue #188, term 1). Sourced by
|
|
# lib/forge.sh when forge_detect says github; never at the same time as the
|
|
# forgejo backend — they define the same verbs on purpose.
|
|
#
|
|
# This file is the CURRENT call set, extracted 1:1 and nothing more. Term 5
|
|
# of the frozen Spec is "GitHub consumers are unchanged", and the cheapest
|
|
# way to keep that true is for every verb here to be a thin pass-through to
|
|
# the `gh` invocation the call site used before the port. No behaviour is
|
|
# added, fixed or tidied on this path; anything that looks like an
|
|
# improvement here is a regression risk against a forge nobody is currently
|
|
# reporting bugs on.
|
|
|
|
# forge_api [--paginate] <endpoint> [--jq <expr>]
|
|
#
|
|
# The one deliberate difference from a pure pass-through: the caller no
|
|
# longer names a page size, because the page-size parameter is not portable
|
|
# and is therefore the backend's to own (#188).
|
|
#
|
|
# ?per_page=100 GitHub: 100 items Forgejo: 30 items (IGNORED)
|
|
# ?limit=100 GitHub: 30 items Forgejo: 50 items (capped)
|
|
#
|
|
# Both answer HTTP 200 either way, so a call site that names one is a silent
|
|
# truncation waiting for the other forge. per_page=100 is injected here —
|
|
# exactly what the call sites said before — so the GitHub path is unchanged
|
|
# in behaviour while the parameter stops being a call-site concern.
|
|
forge_api() {
|
|
local paginate=false endpoint="" jqexpr="" have_jq=false
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--paginate) paginate=true ;;
|
|
--jq) jqexpr="$2"; have_jq=true; shift ;;
|
|
-*) ;;
|
|
*) [ -n "$endpoint" ] || endpoint="$1" ;;
|
|
esac
|
|
shift
|
|
done
|
|
[ -n "$endpoint" ] || { echo "forge_api: endpoint required" >&2; return 1; }
|
|
|
|
if [ "$paginate" = true ]; then
|
|
endpoint="$(github_page_url "$endpoint")"
|
|
if [ "$have_jq" = true ]; then
|
|
gh api --paginate "$endpoint" --jq "$jqexpr"
|
|
else
|
|
gh api --paginate "$endpoint"
|
|
fi
|
|
else
|
|
if [ "$have_jq" = true ]; then
|
|
gh api "$endpoint" --jq "$jqexpr"
|
|
else
|
|
gh api "$endpoint"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# github_page_url <endpoint> — pure, so the page-size contract is testable
|
|
# without a network. Strips any page-size parameter a caller left behind in
|
|
# either dialect, then applies GitHub's own.
|
|
github_page_url() {
|
|
local endpoint="${1:?github_page_url: endpoint required}" clean
|
|
clean="$(printf '%s' "$endpoint" | sed -E 's/([?&])(per_page|limit|page)=[0-9]+/\1/g; s/[?&]+$//; s/([?&])&+/\1/g')"
|
|
case "$clean" in
|
|
*\?) printf '%sper_page=100\n' "$clean" ;;
|
|
*\?*) printf '%s&per_page=100\n' "$clean" ;;
|
|
*) printf '%s?per_page=100\n' "$clean" ;;
|
|
esac
|
|
}
|
|
|
|
# --- the verbs the reconcilers use, extracted 1:1 -------------------------
|
|
# Every one of these is the exact `gh` invocation the call site carried
|
|
# before the port. Term 5 is kept by making this file boring.
|
|
|
|
# forge_issue_edit <n> <gh-style flags…> — labels and assignees on an issue
|
|
# or a PR (gh treats them interchangeably, and so do the call sites).
|
|
forge_issue_edit() {
|
|
local n="${1:?forge_issue_edit: number required}"
|
|
shift
|
|
gh issue edit "$n" -R "$REPO" "$@"
|
|
}
|
|
|
|
# forge_issue_comment <n> <body>
|
|
forge_issue_comment() {
|
|
local n="${1:?forge_issue_comment: number required}" body="${2?forge_issue_comment: body required}"
|
|
gh issue comment "$n" -R "$REPO" --body "$body"
|
|
}
|
|
|
|
# forge_pr_list — open PR numbers, one per line. Note this used
|
|
# `gh pr list --limit 100`: a page size in gh's OWN flag namespace, which no
|
|
# URL-parameter strip could have caught, so it moves behind the shim with
|
|
# the rest (#188).
|
|
forge_pr_list() {
|
|
gh pr list -R "$REPO" --state open --limit 100 --json number --jq '.[].number'
|
|
}
|
|
|
|
# forge_pr_view <n> — {mergeable, statusCheckRollup} as JSON, or non-zero
|
|
# with the reason on stderr. `gh pr view` rather than the REST PR object:
|
|
# the API's `mergeable` is a tri-state boolean GitHub computes lazily, while
|
|
# this returns the MERGEABLE/CONFLICTING/UNKNOWN string the UI shows.
|
|
forge_pr_view() {
|
|
local n="${1:?forge_pr_view: number required}"
|
|
gh pr view "$n" -R "$REPO" --json mergeable,statusCheckRollup
|
|
}
|
|
|
|
# forge_label_list — every label name in the repo.
|
|
forge_label_list() {
|
|
gh label list -R "$REPO" --limit 200 --json name --jq '.[].name'
|
|
}
|
|
|
|
forge_label_create() {
|
|
local name="${1:?}" color="${2:?}" desc="${3:-}"
|
|
gh label create "$name" -R "$REPO" --color "$color" --description "$desc" --force
|
|
}
|
|
|
|
forge_label_delete() {
|
|
local name="${1:?}"
|
|
gh label delete "$name" -R "$REPO" --yes
|
|
}
|