repomatic.github.labels module¶
Repository label management.
The label domain in one place: matching an issue or pull request against the
[tool.repomatic.labels] rules to decide which labels it earns, and applying
label definitions to a repository through labelmaker. Backs the
apply-labels and sync-labels commands.
A rule is one label mapped to a list of patterns: regexes or keywords over the
thread’s text (DEFAULT_CONTENT_RULES), globs over a pull request’s
changed paths (DEFAULT_FILE_RULES). Any pattern matching applies the
label. A project entry for a label replaces the default entry wholesale, and
an empty list disables it; see resolve_content_rules().
Note
The schema says “label X when any changed file matches any of these globs” or
“when any of these words appears”, and nothing more. Per-matcher quantifiers,
branch regexes and any/all group nesting are left out on purpose: no rule
of a repository this toolkit manages needs them.
- repomatic.github.labels.DEFAULT_CONTENT_RULES: dict[str, tuple[str, ...]] = {'🆙 changelog': ('change-log', 'changelog'), '🐛 bug': ('bug', 'error', 'exception', 'fix', 'traceback'), '📚 documentation': ('docstring', 'license', 'mailmap', 'markdown', 'readme', 'sphinx', 'typo'), '🔗 dependencies': ('.lock', 'pyproject.toml'), '🤖 ci': ('actions', 'ci-cd', 'cicd', 'coverage', 'gitignore', 'workflow')}¶
Default content rules: keywords matched against a thread’s title and body.
Every entry is a plain keyword, compiled case-insensitively with word boundaries on its word-character edges (see
compile_content_pattern()), soBugmatches andprefixdoes not tripfix. The keys must name labels thatrepomatic/data/labels.tomldefines, or the labelling call fails on a label GitHub does not have;tests/test_labels.pyenforces that.Tune for precision, not recall: a missing label costs one manual click, a wrong one is noise on every issue that trips it. Never key a rule off a token the project prints in its own output, or a user pasting a trace sets every label at once.
Note
🤖 cideliberately does not match.github, which is the case the rule above names. Every issue formrepomaticbundles for an awesome list links its own.github/code-of-conduct.mdand.github/contributing.mdin the self-check boxes, and GitHub renders those option labels into the filed body verbatim, so the keyword pre-labelled every submission filed through one. A pull request that actually touches those paths is already covered by the.github/**/*glob inDEFAULT_FILE_RULES, and prose about CI still matchesworkflow,actionsorci-cd.Note
💖 sponsordeliberately has no rule here, nor inDEFAULT_FILE_RULES. It means “a sponsor is involved”, which is a fact about the author that only the GraphQL sponsorship query can establish, andsponsor-labelapplies it from exactly that. Matching the words “funding” or “sponsor” (or a pull request touching.github/funding.yml) labels the topic instead, so anyone opening “Add a funding.yml” read as a sponsor. Precision-first means no rule beats an ambiguous one when an authoritative source already exists.
- repomatic.github.labels.DEFAULT_FILE_RULES: dict[str, tuple[str, ...]] = {'🆙 changelog': ('.github/workflows/changelog.yaml', '.github/workflows/release.yaml', 'changelog.md'), '📚 documentation': ('.github/code-of-conduct.md', '.github/workflows/docs.yaml', '.mailmap', 'docs/**/*', 'license', 'readme.md'), '🔗 dependencies': ('*.lock', '**/pyproject.toml'), '🤖 ci': ('.github/**/*', '.gitignore', 'pyproject.toml')}¶
Default file rules: globs matched against the paths a pull request changes.
The dialect is
minimatch’s (seeGLOB_FLAGS):**crosses directories,{a,b}expands, a leading!subtracts from the label’s other globs, and a leading dot is matched like any other character. Keep the globs precise: one broad enough to catch unrelated changes mislabels every pull request touching them.
- repomatic.github.labels.CONTENT_PATTERN_RE = re.compile('^/(?P<body>.*)/(?P<flags>[a-z]*)$', re.DOTALL)¶
The
/body/flagsspelling a content pattern may take, mirroring JavaScript.Matching this shape is what makes a pattern a regex: the body is passed to
reas written, and only the flags named between the slashes apply, so a bare/foo/is case-sensitive. A pattern not in this shape is a literal keyword instead, escaped and word-anchored and always matched case-insensitively (seecompile_content_pattern()). So the slashed form is the one to reach for when a rule genuinely needs regex syntax, and the one that has to spelliout to get back the case-insensitivity the bare form gives for free.
- repomatic.github.labels.CONTENT_PATTERN_FLAGS: dict[str, int] = {'i': re.IGNORECASE, 'm': re.MULTILINE, 's': re.DOTALL}¶
JavaScript regex flags with a Python equivalent, and their translation.
The rest of JavaScript’s set is accepted and ignored rather than rejected:
gandygovern stateful iteration that a single membership test never reaches,uandvdescribe a Unicode mode Python’sreis always in, anddonly adds capture-group offsets nobody here reads. Refusing them would fail a rule over a flag that changes nothing about whether it matches.
- repomatic.github.labels.GLOB_FLAGS = 33608¶
wcmatchflags reproducing theminimatchdialectactions/labelerused.GLOBSTARgives**its cross-directory meaning,BRACEexpands{a,b}, andNEGATEhonours a leading!. The two worth spelling out:DOTGLOB, becauseactions/labelerpassed{dot: true}and half the globs a repository cares about start with a dot (.github/**/*). Without it a workflow change matches nothing.NEGATEALL, becauseminimatchreads a lone!**/*.mdas “everything that is not markdown”, whilewcmatchdefaults to matching nothing at all when no positive pattern accompanies the exclusion.
- repomatic.github.labels.INLINE_LABEL_FIELDS: tuple[str, ...] = ('name', 'color', 'description', 'create', 'update', 'enforce-case', 'rename-from', 'on-rename-clash')¶
Per-label fields of labelmaker’s specification, in its documented order.
serialize_inline_labelspasses them through verbatim (colors get their leading#stripped), so declarative renames and the other per-label knobs ride the regular sync.rename-fromis the one field with a constraint worth knowing before use: it is strictly one-to-one, renaming only when the target is absent and exactly one listed source exists. It therefore cannot merge several labels into one, and is useless once a sync has already created the target. See “Retiring a label is a migration, not a deletion” in thegithub-housekeepingskill.
- repomatic.github.labels.resolve_content_rules(config=None)[source]¶
The content rules in force: bundled defaults overlaid with the project’s.
- repomatic.github.labels.resolve_file_rules(config=None)[source]¶
The file rules in force: bundled defaults overlaid with the project’s.
- repomatic.github.labels.compile_content_pattern(pattern: str) Pattern[str] | None[source]¶
Compile one content pattern, as a keyword or a
/body/flagsregex.Memoized: the rule tables hand the same patterns to every matching call, so each spelling compiles once per process (and a malformed one is warned about once instead of on every thread it is matched against).
A bare pattern is a literal keyword: escaped, matched case-insensitively, and word-anchored on each edge that is itself a word character, so
fixdoes not fire insideprefixwhile.lockstill matches the tail ofuv.lock(a\bbefore the dot would demand a word character ahead of it). Case-insensitivity is the point of defaulting this way: users capitalize freely, and a convention every rule must remember to spell is a convention half of them forget.The
/body/flagsform passes the body through as a regex, in the JavaScript spelling existing rules are written in. No flags means case-sensitive.Returns
Noneon a body theremodule rejects, having logged it. A single malformed rule must not take the whole labelling run down with it: the job is a convenience that runs once per opened issue, and the other rules still have work to do.
- repomatic.github.labels.match_content_rules(rules, text)[source]¶
Return every label with a pattern matching text.
- repomatic.github.labels.match_file_rules(rules, files)[source]¶
Return every label whose globs match a changed file.
A label’s globs are evaluated as one set, so a
!-negated entry subtracts from its siblings (["docs/**", "!docs/generated/**"]reads the way a.gitignorewould) rather than standing alone. A pull request that changes no files matches nothing.
- repomatic.github.labels.labels_for_thread(config, number, *, kind, repository=None)[source]¶
The labels the project’s rules give one issue or pull request.
In the labeller job the event payload carries the thread. A thread the payload does not carry has its text read from the API instead, so the content rules see the real title and body rather than an empty string. File rules need a diff, which only a pull request has.
- Parameters:
config (
Config) – The resolved[tool.repomatic]configuration.number (
int) – The issue or pull request number.kind (
ThreadKind) – Whether number names an issue or a pull request.repository (
str|None) – Repository inowner/nameform, orNoneto letghresolve it.
- Return type:
- Returns:
The matched labels, sorted.
- repomatic.github.labels.serialize_inline_labels(entries)[source]¶
Serialize
[tool.repomatic.labels.extra]entries to a labelmaker TOML config.Each entry becomes a
[[profiles.default.labels]]block under thedefaultprofile, carrying every per-label field of labelmaker’s specification (INLINE_LABEL_FIELDS): arename-fromlist renames a label in place on GitHub, preserving its issue and PR associations, and thecreate,update,enforce-caseandon-rename-clashknobs pass through alike. Leading#on hex colors is stripped, on both single colors and multi-color lists, so the output matches labelmaker’s convention.Entries missing a
nameare skipped with a warning, and unknown fields are dropped with a warning: labelmaker rejects both and would abort the whole sync.Returns an empty string when there are no valid entries, so the caller can skip writing a temp file and invoking labelmaker entirely.
- Return type:
- repomatic.github.labels.EXTRA_LABELS_DIR = 'extra-labels'¶
Directory of a repository holding its hand-written label definitions.
- repomatic.github.labels.extra_label_filename(url)[source]¶
The filename a
labels.extra-filesdownload is saved under.Shared by the download of
repomatic init labelsand bylabel_sources(), which keys a fetched definition the same way, so it shadows a committedextra-labels/file of that name.
- class repomatic.github.labels.LabelSource(path, text, profiles=None)[source]¶
Bases:
objectOne labelmaker definition file, as
sync-labelsapplies it.
- repomatic.github.labels.label_sources(config, *, is_awesome, root=None)[source]¶
Every label definition the configuration applies to a repository.
In apply order: the bundled
labels.tomlunder thedefaultprofile (and theawesomeone for anawesome-*repository), the hand-written files ofextra-labels/and thelabels.extra-filesdownloads, then the inline[tool.repomatic.labels.extra]definitions. Bothapply_labels()anddeclared_label_names()read this one list, so a label the sync creates is never reported as undeclared.- Parameters:
- Yields:
Each definition file.
- Raises:
repomatic.http.FetchError – When a
labels.extra-filesURL cannot be fetched. The list would otherwise come back short, and every label only that file declares would be lost.
- exception repomatic.github.labels.LabelConfigError[source]¶
Bases:
ValueErrorA label definition file that
declared_label_names()cannot read.
- repomatic.github.labels.declared_label_names(config, *, is_awesome, labels_dir=None)[source]¶
Every label name the configured sources declare for a repository.
Reads the
label_sources()thatapply_labels()applies, which is what letscheck_undeclared_labels()call anything outside this set an orphan.- Parameters:
- Return type:
- Returns:
The declared label names.
- Raises:
repomatic.http.FetchError – When a
labels.extra-filesURL cannot be fetched.LabelConfigError – When an
extra-labels/orlabels.extra-filesdefinition cannot be read. The set would otherwise come back short.
- repomatic.github.labels.apply_labels(config, repository, *, is_awesome, root=None)[source]¶
Apply every configured label source to repository via
labelmaker.Stages each of the
label_sources()in a scratch directory, so a sync leaves the working tree untouched, then applies them in order.- Parameters:
- Raises:
RuntimeError – When a labelmaker invocation fails, or when a
labels.extra-filesURL cannot be fetched.- Return type: