ci: fail a pull request that waits for an unmerged gitea change

This commit is contained in:
Lunny Xiao
2026-09-08 11:50:21 -07:00
parent 24a7eb79e5
commit 7d4c23de0b
5 changed files with 274 additions and 0 deletions
+46
View File
@@ -0,0 +1,46 @@
# The status check context of this job is "checks / depends-upstream
# (pull_request)", which the required status check `checks / *` of the protected
# main branch matches, so a pull request waiting for gitea cannot be merged.
# It is a workflow of its own so that a label change does not cancel the build
# and the preview deployment of the `checks` workflow in test.yaml.
name: checks
on:
pull_request:
types:
- opened
- reopened
- synchronize
- edited
- labeled
- unlabeled
concurrency:
group: depends-upstream-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
depends-upstream:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
# the check script is taken from the base branch: a pull request must
# not be able to weaken its own gate
ref: ${{ github.event.pull_request.base.ref }}
persist-credentials: false
- name: check the upstream dependencies
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
# optional, only lifts the rate limit of the unauthenticated github api
GITHUB_COM_TOKEN: ${{ secrets.GITHUB_COM_TOKEN }}
run: |
set -euo pipefail
# read the description and the labels from the api instead of the
# event context, so nothing from a pull request body is expanded into
# this script
api="${GITHUB_API_URL:-$GITHUB_SERVER_URL/api/v1}/repos/$GITHUB_REPOSITORY"
curl --silent --show-error --fail --output pull.json "$api/pulls/$PR_NUMBER"
./scripts/check-upstream-deps.sh pull.json
@@ -0,0 +1,29 @@
name: unlabel merged upstream dependencies
on:
schedule:
- cron: '15 */6 * * *' # every 6 hours, quarter past
workflow_dispatch:
jobs:
unlabel:
if: github.repository == 'gitea/docs'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- name: drop the label from the pull requests upstream caught up with
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
GITHUB_COM_TOKEN: ${{ secrets.GITHUB_COM_TOKEN }}
run: |
set -euo pipefail
if [ -z "${DEPLOY_TOKEN:-}" ]; then
echo "secrets.DEPLOY_TOKEN is missing, cannot remove the label"
exit 1
fi
export GITEA_API="${GITHUB_API_URL:-$GITHUB_SERVER_URL/api/v1}/repos/$GITHUB_REPOSITORY"
./scripts/unlabel-upstream-deps.sh
+21
View File
@@ -48,6 +48,27 @@ The order of the top level sidebar groups comes from `sidebars.js` (and
`versioned_sidebars/` for released versions), the label and order of every other `versioned_sidebars/` for released versions), the label and order of every other
group from the `_category_.json` of its directory. group from the `_category_.json` of its directory.
### Documenting an unreleased feature
A page that describes a gitea change which is not merged yet must wait for it,
otherwise the site documents something no release has. Say so in the description
of the pull request:
```
Depends on: gitea#35851
```
The `depends-upstream` check reads those references (`gitea#<id>` or the full
pull request url, several are allowed) and fails while any of them is unmerged,
which blocks the merge since `main` requires the `checks` contexts. Add the
`depends-on-upstream` label as well, so the pull request is easy to find; the
label alone fails the check, it has to be accompanied by a reference.
The `unlabel merged upstream dependencies` job runs every 6 hours, removes the
label once every referenced pull request is merged and comments on the pull
request, which reruns the check. `scripts/check-upstream-deps.sh` is the check
itself and can be run locally against the json of a pull request.
## Cutting a version ## Cutting a version
```shell ```shell
+101
View File
@@ -0,0 +1,101 @@
#!/bin/bash
#
# Fails when a pull request depends on a gitea change that is not merged yet.
#
# Usage: ./scripts/check-upstream-deps.sh <pull request json>
#
# The json is what the gitea api returns for a pull request; the script reads
# the description and the labels from it. A dependency is declared in the
# description as
#
# Depends on: gitea#35851
# Depends on: https://github.com/go-gitea/gitea/pull/35851
#
# and may appear several times. `GITHUB_COM_TOKEN` is optional and only lifts
# the rate limit of the unauthenticated github api, `UPSTREAM_LABEL` overrides
# the label the check looks for.
set -euo pipefail
LABEL="${UPSTREAM_LABEL:-depends-on-upstream}"
PULL_JSON="${1:-}"
if [ -z "$PULL_JSON" ] || [ ! -f "$PULL_JSON" ]; then
echo "usage: $0 <pull request json>" >&2
exit 2
fi
body="$(jq -r '.body // ""' "$PULL_JSON")"
labelled="$(jq -r --arg label "$LABEL" '[.labels[]?.name] | index($label) != null' "$PULL_JSON")"
# every gitea#<id> and every pull request url, deduplicated
refs="$(printf '%s\n' "$body" \
| grep -oiE 'gitea#[0-9]+|github\.com/go-gitea/gitea/pull/[0-9]+' \
| grep -oE '[0-9]+' \
| sort -un || true)"
if [ -z "$refs" ]; then
if [ "$labelled" = 'true' ]; then
echo "This pull request carries the \"$LABEL\" label but does not say what it"
echo "depends on. Add a line to the description, for example:"
echo
echo " Depends on: gitea#35851"
exit 1
fi
echo "no upstream dependency declared"
exit 0
fi
github_api() {
local url="$1"
if [ -n "${GITHUB_COM_TOKEN:-}" ]; then
curl --silent --show-error --write-out '\n%{http_code}' \
-H "Authorization: Bearer $GITHUB_COM_TOKEN" "$url"
else
curl --silent --show-error --write-out '\n%{http_code}' "$url"
fi
}
status=0
for ref in $refs; do
response="$(github_api "https://api.github.com/repos/go-gitea/gitea/pulls/$ref")"
code="$(printf '%s' "$response" | tail -n 1)"
payload="$(printf '%s' "$response" | sed '$d')"
case "$code" in
200)
if [ "$(printf '%s' "$payload" | jq -r '.merged')" = 'true' ]; then
echo "gitea#$ref is merged"
else
echo "gitea#$ref is not merged yet ($(printf '%s' "$payload" | jq -r '.state'))"
status=1
fi
;;
404)
echo "gitea#$ref is not a pull request of go-gitea/gitea, reference the pull request that adds the feature"
status=1
;;
403 | 429)
echo "the github api refused the request for gitea#$ref (rate limit?), cannot tell whether it is merged"
status=1
;;
*)
echo "unexpected response $code from the github api for gitea#$ref"
status=1
;;
esac
done
if [ "$status" -ne 0 ]; then
echo
echo "The documentation of an unreleased feature can only be merged once the"
echo "feature itself is in gitea main. Keep the \"$LABEL\" label on this pull"
echo "request; the scheduled job removes it and reruns this check as soon as"
echo "every referenced pull request is merged."
exit 1
fi
if [ "$labelled" = 'true' ]; then
echo
echo "every referenced pull request is merged, the \"$LABEL\" label can be removed"
fi
+77
View File
@@ -0,0 +1,77 @@
#!/bin/bash
#
# Removes the `depends-on-upstream` label from every open pull request whose
# referenced gitea pull requests are all merged, and says so in a comment.
# Removing the label reruns the `depends-upstream` check of that pull request.
#
# Usage: ./scripts/unlabel-upstream-deps.sh
#
# Environment:
# GITEA_API api of the repository, e.g. https://gitea.com/api/v1/repos/gitea/docs
# DEPLOY_TOKEN token used to remove the label and to comment
# GITHUB_COM_TOKEN optional, lifts the rate limit of the github api
# UPSTREAM_LABEL label to look for, `depends-on-upstream` by default
# DRY_RUN set to 1 to only report what would happen
set -euo pipefail
LABEL="${UPSTREAM_LABEL:-depends-on-upstream}"
API="${GITEA_API:?GITEA_API is required}"
DRY_RUN="${DRY_RUN:-0}"
gitea_api() {
local method="$1" url="$2" data="${3:-}"
local args=(--silent --show-error --fail -X "$method")
if [ -n "${DEPLOY_TOKEN:-}" ]; then args+=(-H "Authorization: token $DEPLOY_TOKEN"); fi
if [ -n "$data" ]; then args+=(-H 'Content-Type: application/json' --data "$data"); fi
curl "${args[@]}" "$url"
}
label_id="$(gitea_api GET "$API/labels?limit=100" | jq -r --arg label "$LABEL" \
'.[] | select(.name == $label) | .id')"
if [ -z "$label_id" ]; then
echo "the repository has no \"$LABEL\" label, nothing to do"
exit 0
fi
pulls="$(gitea_api GET "$API/pulls?state=open&limit=50")"
indexes="$(printf '%s' "$pulls" | jq -r --arg label "$LABEL" \
'.[] | select([.labels[]?.name] | index($label) != null) | .number')"
if [ -z "$indexes" ]; then
echo "no open pull request carries the \"$LABEL\" label"
exit 0
fi
here="$(cd "$(dirname "$0")" && pwd)"
work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT
for index in $indexes; do
gitea_api GET "$API/pulls/$index" > "$work/pull.json"
# the check script exits 0 when every reference is merged; it needs the label
# to be absent from its input to stay quiet about it, so drop it here
jq 'del(.labels)' "$work/pull.json" > "$work/pull-unlabelled.json"
if ! output="$("$here/check-upstream-deps.sh" "$work/pull-unlabelled.json" 2>&1)"; then
echo "#$index still waits for upstream:"
printf '%s\n' "$output" | sed 's/^/ /'
continue
fi
if printf '%s' "$output" | grep -q 'no upstream dependency declared'; then
echo "#$index carries the label but declares no dependency, leaving it alone"
continue
fi
if [ "$DRY_RUN" = '1' ]; then
echo "#$index would lose the label:"
printf '%s\n' "$output" | sed 's/^/ /'
continue
fi
gitea_api DELETE "$API/issues/$index/labels/$label_id" > /dev/null
comment="$(printf 'Every referenced gitea pull request is merged, so the `%s` label was removed and the check was rerun.\n\n%s' "$LABEL" "$output")"
gitea_api POST "$API/issues/$index/comments" "$(jq -n --arg body "$comment" '{body: $body}')" > /dev/null
echo "#$index unlabelled"
done