5 changed files with 331 additions and 0 deletions

No files matched your search

+48
View File
@@ -0,0 +1,48 @@
# 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.
#
# The check script comes from the pull request, like the workflow file itself:
# `pull_request` runs both from the head, so taking the script from the base
# branch would not gain anything and would leave this workflow unable to run on
# the pull request that adds it.
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:
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
+32
View File
@@ -48,6 +48,38 @@ 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, on a line of its own, and add the `depends-on-upstream`
label:
```
Depends on: gitea#35851
```
The label is what blocks the merge: the `depends-upstream` check fails while it
is set, unless every dependency is verified as merged, and `main` requires the
`checks` contexts. The check reads the `Depends on:` lines (`gitea#<id>` or the
full pull request url, several lines are allowed) to find out what to wait for,
so the label needs at least one of them. A `gitea#<id>` mentioned anywhere else
in the description is ignored.
Without the label, a dependency that the check finds unmerged still fails it, so
forgetting the label does not let an unreleased feature through.
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.
The check asks the github api whether a pull request is merged, and the runners
share the 60 requests per hour it allows without authentication. The optional
`GITHUB_COM_TOKEN` secret (a token without scopes is enough) raises that limit;
without it an unverifiable dependency only fails the check when the label is
set.
## Cutting a version ## Cutting a version
```shell ```shell
+142
View File
@@ -0,0 +1,142 @@
#!/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 on a line of
# its own in the description:
#
# Depends on: gitea#35851
# Depends on: https://github.com/go-gitea/gitea/pull/35851
#
# and may be declared several times. Only such lines count, so a pull request
# that merely mentions gitea#35851 in prose is not held back.
#
# The `depends-on-upstream` label is what blocks the merge: it fails the check
# unless every declared dependency is verified as merged. Without the label, a
# dependency that is known to be unmerged still fails, so a forgotten label does
# not let an unreleased feature through.
#
# Exit codes: 0 nothing blocks, 1 blocked, 2 usage.
#
# Environment:
# GITHUB_COM_TOKEN optional, lifts the rate limit of the github api
# UPSTREAM_LABEL label to look for, `depends-on-upstream` by default
# REQUIRE_VERIFIED set to 1 to also fail when the state cannot be determined
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")"
# `label` is a keyword in jq 1.6, which is what the runner image has, so the
# argument cannot be named after it; `(.labels // [])` keeps it parsable there
labelled="$(jq -r --arg want "$LABEL" '[(.labels // [])[].name] | index($want) != null' "$PULL_JSON")"
require_verified="${REQUIRE_VERIFIED:-0}"
if [ "$labelled" = 'true' ]; then
# the label says the pull request is not ready, so an unverifiable dependency
# keeps it blocked instead of letting it through
require_verified=1
fi
# the ids of the `Depends on:` lines, deduplicated
refs="$(printf '%s\n' "$body" \
| grep -iE '^[[:space:]]*depends[ _-]?on[[:space:]]*:' \
| 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
}
unmerged=0
unknown=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'))"
unmerged=1
fi
;;
404)
echo "gitea#$ref is not a pull request of go-gitea/gitea, reference the pull request that adds the feature"
unmerged=1
;;
403 | 429)
echo "gitea#$ref: the github api rejected the request (rate limit), cannot tell whether it is merged"
unknown=1
;;
*)
echo "gitea#$ref: unexpected response $code from the github api, cannot tell whether it is merged"
unknown=1
;;
esac
done
if [ "$unknown" -eq 1 ] && [ -z "${GITHUB_COM_TOKEN:-}" ]; then
echo
echo "The github api allows 60 unauthenticated requests per hour and address,"
echo "which the runners share. Set the GITHUB_COM_TOKEN secret to a token with"
echo "no scopes to raise that limit."
fi
if [ "$unmerged" -eq 1 ] || { [ "$unknown" -eq 1 ] && [ "$require_verified" = '1' ]; }; then
echo
if [ "$labelled" = 'true' ]; then
echo "This pull request is labelled \"$LABEL\", so it stays blocked until every"
echo "dependency above is merged into gitea main. The scheduled job removes the"
echo "label and reruns this check as soon as that is the case."
else
echo "The documentation of an unreleased feature can only be merged once the"
echo "feature itself is in gitea main. Add the \"$LABEL\" label so the pull"
echo "request is picked up by the job that watches for it."
fi
exit 1
fi
if [ "$unknown" -eq 1 ]; then
echo
echo "Could not verify every dependency, letting the check pass: the pull request"
echo "does not carry the \"$LABEL\" label."
exit 0
fi
if [ "$labelled" = 'true' ]; then
echo
echo "every declared dependency is merged, the \"$LABEL\" label can be removed"
fi
+80
View File
@@ -0,0 +1,80 @@
#!/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"
}
# see the note in check-upstream-deps.sh about the argument name
label_id="$(gitea_api GET "$API/labels?limit=100" | jq -r --arg want "$LABEL" \
'.[] | select(.name == $want) | .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 want "$LABEL" \
'.[] | select([(.labels // [])[].name] | index($want) != 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"
# REQUIRE_VERIFIED keeps a dependency whose state could not be determined from
# counting as merged, which would drop the label too early
if ! output="$(REQUIRE_VERIFIED=1 "$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