pattern

package
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 3, 2026 License: MIT Imports: 3 Imported by: 0

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

func Contains(outer, inner string) bool

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

func NeverMatches(patternStr string) bool

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.

func Compile

func Compile(patternStr string) (*Pattern, error)

Compile parses a CODEOWNERS pattern.

func (*Pattern) Match

func (p *Pattern) Match(testPath string) bool

Match reports whether the repo-relative path (forward slashes, no leading slash) matches the pattern. Paths name tracked FILES; a pattern matching a directory matches the files beneath it per the rules encoded here.

func (*Pattern) String

func (p *Pattern) String() string

String returns the original pattern text.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL