Labels don't travel with a mirror push -- mirroring copies files, not repository settings -- but they matter on the GitHub side anyway, because GitHub reads the same .github/ISSUE_TEMPLATE/ files and silently drops a labels: entry naming a label it doesn't have. Until now the only thing keeping the two sets aligned was remembering to do it by hand, which is exactly the kind of thing that gets forgotten and then fails invisibly. Add scripts/sync-labels.py and a workflow that runs it daily, on any change to the script itself, and on manual dispatch. Gitea stays the source of truth: labels are managed there and GitHub is made to match. - **Creates and updates** anything missing or drifted. Colors and descriptions are normalized before comparison -- Gitea returns colors bare, GitHub sometimes with a leading '#', and a description may be null on one side and "" on the other -- so a steady state is a true no-op rather than a rewrite of all 33 labels every run. - **Deletes only unused extras.** An extra label on the mirror is removed only when no issue or PR there carries it; one in use is reported with its count and left alone. An unattended scheduled job must not be able to strip a label off somebody's issue. - **Refuses to run on an empty source**, since treating that as truth would propose deleting every label on the mirror. - **--dry-run** prints the plan and changes nothing; **--self-test** checks the diff logic offline against fixtures, and gates the sync step in CI so a broken diff can't mutate anything. Stdlib only, so the CI step installs nothing beyond python3. The job is gated with the same github.server_url check ci.yml uses -- without it the mirror would queue this forever against a runner that only exists on Gitea. Labels are matched by name, so a rename reads as delete-plus-create; the new name is created and the old is pruned only if unused. The two forges share no stable label ID, so a rename can't be tracked across them.
72 lines
2.7 KiB
YAML
72 lines
2.7 KiB
YAML
name: Sync labels to mirror
|
|
|
|
# Labels do not travel with a mirror push -- mirroring copies files, not
|
|
# repository settings -- but they matter on the GitHub side anyway, because
|
|
# GitHub reads the same .github/ISSUE_TEMPLATE/ files and silently drops a
|
|
# labels: entry naming a label it does not have. Gitea is the source of
|
|
# truth; this makes the mirror match.
|
|
|
|
on:
|
|
schedule:
|
|
# 06:00 UTC daily. Label churn is rare, so a slower cadence than this
|
|
# would leave the mirror wrong for most of a working day after an edit.
|
|
- cron: "0 6 * * *"
|
|
push:
|
|
branches:
|
|
- main
|
|
paths:
|
|
# Exercise the sync as soon as its own logic changes, rather than
|
|
# waiting for the next scheduled run to find out it is broken.
|
|
- "scripts/sync-labels.py"
|
|
- ".github/workflows/sync-labels.yml"
|
|
workflow_dispatch:
|
|
inputs:
|
|
dry_run:
|
|
description: "Report the plan without changing anything"
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
|
|
jobs:
|
|
sync-labels:
|
|
# This file is mirrored to GitHub as-is. The runner label below only
|
|
# exists on the Gitea instance, so on GitHub the job would sit queued
|
|
# forever against a runner that will never pick it up -- the same
|
|
# problem the github-mirror stand-in in ci.yml exists to solve. A
|
|
# skipped job costs nothing and produces no stuck status.
|
|
if: github.server_url != 'https://github.com'
|
|
runs-on: racknerd-mini
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
token: ${{ secrets.GITEA_TOKEN }}
|
|
|
|
- name: Install Python
|
|
run: |
|
|
sudo apt-get -o Acquire::Retries=3 update -qq
|
|
sudo DEBIAN_FRONTEND=noninteractive apt-get install \
|
|
--no-install-recommends -y python3
|
|
|
|
# Cheap, offline, and no token needed. Catches a broken diff before
|
|
# anything is allowed to mutate labels on the mirror.
|
|
- name: Check the diff logic
|
|
run: python3 scripts/sync-labels.py --self-test
|
|
|
|
- name: Sync labels
|
|
env:
|
|
GH_MIRROR_TOKEN: ${{ secrets.GH_MIRROR_TOKEN }}
|
|
run: |
|
|
if [ -z "$GH_MIRROR_TOKEN" ]; then
|
|
echo "::error::GH_MIRROR_TOKEN is not set in this repository's Actions secrets."
|
|
echo "Create a fine-grained GitHub token scoped to rootiest/fish-config with"
|
|
echo "Issues: read and write, plus Pull requests: read, and add it as"
|
|
echo "GH_MIRROR_TOKEN under Settings -> Actions -> Secrets."
|
|
exit 1
|
|
fi
|
|
if [ "${{ inputs.dry_run }}" = "true" ]; then
|
|
python3 scripts/sync-labels.py --dry-run
|
|
else
|
|
python3 scripts/sync-labels.py
|
|
fi
|