1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# Label sync: `.github/labels.yml` is the definition, the repository is the
# copy.
#
# Labels are normally edited in the web UI, which means the set drifts, nobody
# remembers why a colour is what it is, and a label someone deletes by
# accident is gone with no record. Putting them in a file under review makes a
# label change a pull request like any other.
#
# ----------------------------------------------------------------------------
# This is a SYNC, not a merge. `skip-delete` is left at its default of false,
# so a label present on the repository but absent from `.github/labels.yml` is
# DELETED, and deleting a label strips it from every issue and pull request
# carrying it. That is the correct behaviour for a file that claims to define
# the label set -- but it means `.github/labels.yml` has to list every label
# the repository keeps, including ones no human applies:
#
# * `dependencies` -- Dependabot applies it to its own pull requests and
# recreates it if missing, so leaving it out of the file would produce a
# delete-recreate cycle on every sync. It is in `.github/labels.yml`
# today; do not tidy it away on the grounds that no human uses it.
#
# The `dry-run` job below exists so this is discovered in review rather than
# after a merge.
# ----------------------------------------------------------------------------
name: Labels
on:
push:
branches:
paths:
- ".github/labels.yml"
# A change to the sync itself has to be able to run the sync.
- ".github/workflows/labels.yml"
pull_request:
paths:
- ".github/labels.yml"
- ".github/workflows/labels.yml"
# For re-running after a label was changed by hand in the web UI, which is
# the one case with no commit to trigger on.
workflow_dispatch:
permissions:
contents: read
concurrency:
group: labels-${{ github.ref }}
# The sync is idempotent and converges on the file, so a superseded run has
# nothing to contribute. Two runs racing on the labels API would only
# produce confusing partial states.
cancel-in-progress: true
jobs:
# Split in two purely for the permissions. A dry run has no business holding
# a token that can delete labels, and a single job cannot vary its
# permissions by event.
dry-run:
name: Dry run
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: read
# Reading the current labels to diff against the file. Labels live under
# the issues API, hence the scope name. Read, not write -- the point of
# this job is that it cannot change anything. (Pull requests from forks
# get a read-only token regardless; this makes the intent explicit for
# branches in this repository, where they would not.)
issues: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: crazy-max/ghaction-github-labeler@548a7c3603594ec17c819e1239f281a3b801ab4d # v6.0.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
yaml-file: .github/labels.yml
# Prints the create/update/delete plan and exits. A malformed file,
# a duplicate name or an unintended deletion shows up in the check
# output while the pull request is still open.
dry-run: true
sync:
name: Sync
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: read
# Creating, recolouring, renaming and deleting labels. This is the only
# write in any of the five security workflows that touches repository
# content rather than the security tab, and it is the narrowest scope
# that can do it.
issues: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: crazy-max/ghaction-github-labeler@548a7c3603594ec17c819e1239f281a3b801ab4d # v6.0.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
yaml-file: .github/labels.yml
skip-delete: false