Documentation
¶
Overview ¶
Package pattern implements GitHub CODEOWNERS pattern matching.
CODEOWNERS patterns follow "most of the same rules used in gitignore files" (GitHub docs), with three documented exceptions: no `!` negation, no `[ ]` character ranges, and no `\#` escaping of a leading hash. Square brackets are literal characters. Matching is case-sensitive (S-6).
The matching logic is ported from hmarr/codeowners (match.go), MIT License, Copyright (c) 2020 Harry Marr — the actively maintained reference implementation whose semantics are differentially tested against GitHub's observed behavior. The vendored corpus in testdata/patterns.json comes from the same project. Local divergences from the port, all deliberate:
- Compile rejects patterns starting with `!` (GitHub: negation "doesn't work"; a mutation tool must never accept or emit one).
- Compile rejects patterns starting with `\#` (S-2/S-6: GitHub honors no `\#` escape — a line starting with `#` is always a comment, so the rule would be dead there; same standard as `!`). Only the LEADING position is special-cased: a mid-pattern `#` needs no escape at all, so `\#` after the first character keeps the oracle's literal-`#` meaning.
- Compile rejects empty patterns.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Contains ¶ added in v0.0.4
Contains reports whether every path matching `inner` also matches `outer` — containment of the pattern LANGUAGES, not of their match sets over some snapshot of a file tree. Callers use it to decide whether editing a rule in place can affect anything outside an operation's scope, and a CODEOWNERS rule governs files that do not exist yet, so a tree-based answer is an accident of what happens to be committed today.
Contains is SOUND but deliberately incomplete: it never reports true for a pair where containment fails, and answers false whenever it cannot prove the containment. Callers must treat false as "unknown", not as "disjoint".
Paths are repo-relative FILE paths (see Match), so a pattern ending in `/**` is taken to require at least one further segment.
func NeverMatches ¶ added in v0.0.27
NeverMatches reports whether a pattern's language is EMPTY: no repo-relative path, in any repository, present or future, can match it. It is a statement about the pattern alone, which is what puts a refusal built on it at exit 3 (the policy is broken everywhere) rather than exit 2 (this repo needs a human).
It is deliberately NOT part of Compile. GitHub loads such a line and skips no line for it — `**/ @org/x` is a well-formed rule that simply owns nothing — so calling it a syntax error would misreport an existing file: audit would report it as a line GitHub skips (S-3/A-1) instead of as a dead pattern (A-4). Reading is unaffected; only WRITING one is refused, by ops.checkScope, on the same standard as `!` and `\#` — "a mutation tool must never emit a rule that is dead by construction" (R-5).
Two families reach it. Both are already documented in this package as dead and neither was refused anywhere:
- `/`, which buildPatternRegex compiles to `\A\z`;
- a LEADING `**` immediately followed by another `**` — `**/`, `**/**`, `**/**/x`. buildPatternRegex tracks whether the separator was already consumed (needSlash), and the `**` arms do not honour it: the leading `**` emits `(?:.+/)?` and clears the flag, then the next `**` emits its own separator regardless, so every match would need a leading `/` or a `//`. See contains.go, which refuses to model these for the same reason.
- an empty segment (`a//b`, `//x`), which forces the same `//`.
Non-leading adjacency is NOT dead and must not be refused: `x/**/**` compiles to `\Ax(?:/.+)?/.*\z` and matches `x/a`, and `foo/**/` normalizes to the same shape. The analysis below decides each pattern rather than pattern-matching on spellings, so those stay alive.
Method: the regex buildPatternRegex emits is a concatenation of pieces, each of which either consumes a mandatory separator, contributes segment text, or contributes an optional run. Track, as a SET, what a valid path prefix can look like at each boundary — nothing yet, ending in `/`, or ending inside a segment — and drop any transition that would require a leading `/` or a `//`, neither of which git produces. The pattern is dead exactly when no state survives, or when no surviving state can end a path (a path never ends in `/`). Since every piece is modeled from the arm that emits it, the answer is exact for this compiler, and TestNeverMatchesAgreesWithTheMatcher checks that against the matcher over a generated corpus.
Types ¶
type Pattern ¶
type Pattern struct {
// contains filtered or unexported fields
}
Pattern is a compiled CODEOWNERS pattern.