Skip to content

Commit

Permalink
Merge pull request #88 from sirosen/alphabetize-codeowners-gitlab-syntax
Browse files Browse the repository at this point in the history
Support a gitlab dialect for codeowners
  • Loading branch information
sirosen authored Aug 15, 2024
2 parents 678e11c + 92cb872 commit d582034
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 13 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ following sample config:
### Unreleased

<!-- bumpversion-changelog -->
- Support GitLab section headers in alphabetize-codeowners when
`--dialect=gitlab` is passed.
- Casefold codeowner names for better unicode sorting. Thanks @adam-moss for
the PR!

Expand Down
64 changes: 59 additions & 5 deletions src/texthooks/alphabetize_codeowners.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,41 @@
Alphabetize the list of owners for each path in .github/CODEOWNERS
Ignores empty lines and comments, but normalizes whitespace on semantically significant
lines
lines.
Use '--dialect=gitlab' in order to support GitLab's extended CODEOWNERS syntax.
"""

import re
import sys
import typing as t

from ._common import parse_cli_args
from ._recorders import DiffRecorder

GITLAB_SECTION_TITLE_PATTERN = re.compile(r"\^?\[[^\]]+\]")
GITLAB_SECTION_N_APPROVALS_PATTERN = re.compile(r"\[\d+\]")


def main(*, argv=None) -> int:
args = parse_cli_args(
__doc__,
fixer=True,
argv=argv,
disable_args=["files"],
modify_parser=_add_files_arg,
modify_parser=_add_args,
)
filenames = args.files
if not filenames:
filenames = [".github/CODEOWNERS"]

line_fixer = make_line_fixer(args.dialect)

recorder = DiffRecorder(args.verbosity)
missing_file = False
for fn in filenames:
try:
recorder.run_line_fixer(sort_line, fn)
recorder.run_line_fixer(line_fixer, fn)
except FileNotFoundError:
missing_file = True
if recorder or missing_file:
Expand All @@ -38,11 +47,32 @@ def main(*, argv=None) -> int:
return 0


def _add_files_arg(parser):
def _add_args(parser):
parser.add_argument("files", nargs="*", help="default: .github/CODEOWNERS")
parser.add_argument(
"--dialect",
default="standard",
choices=(
"standard",
"gitlab",
),
help=(
"A dialect of codeowners parsing to use. "
"Defaults to the common syntax ('standard')."
),
)


def make_line_fixer(dialect: str) -> t.Callable[[str], str]:
if dialect == "standard":
return sort_line_standard
elif dialect == "gitlab":
return sort_line_gitlab
else:
raise NotImplementedError(f"Unrecognized dialect: {dialect}")


def sort_line(line: str) -> str:
def sort_line_standard(line: str) -> str:
if line.strip() == "" or line.strip().startswith("#"):
return line
# also normalizes whitespace
Expand All @@ -52,5 +82,29 @@ def sort_line(line: str) -> str:
return " ".join([path] + sorted(owners, key=str.casefold))


def sort_line_gitlab(line: str) -> str:
if line.strip() == "" or line.strip().startswith("#"):
return line

title_match = GITLAB_SECTION_TITLE_PATTERN.match(line)
if title_match:
after_section = line[title_match.end() :]
n_approvals_match = GITLAB_SECTION_N_APPROVALS_PATTERN.match(after_section)
if n_approvals_match:
cut_point = title_match.end() + n_approvals_match.end()
else:
cut_point = title_match.end()
section = line[:cut_point]
default_owners = line[cut_point:].split()
if not default_owners:
return line
return " ".join([section] + sorted(default_owners, key=str.casefold))
else:
path, *owners = line.split()
if not owners:
return line
return " ".join([path] + sorted(owners, key=str.casefold))


if __name__ == "__main__":
sys.exit(main())
83 changes: 75 additions & 8 deletions tests/acceptance/test_alphabetize_codeowners.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import pytest

from texthooks.alphabetize_codeowners import main as alphabetize_codeowners_main


def test_alphabetize_codeowners_no_changes(runner):
result = runner(alphabetize_codeowners_main, "foo")
@pytest.mark.parametrize("dialect", ("standard", "gitlab"))
def test_alphabetize_codeowners_no_changes(runner, dialect):
result = runner(alphabetize_codeowners_main, "foo", add_args=["--dialect", dialect])
assert result.exit_code == 0
assert result.file_data == "foo"

Expand All @@ -11,22 +14,34 @@ def test_alphabetize_codeowners_no_changes(runner):
assert result.file_data == "/foo/bar.txt @alice @bob"


def test_alphabetize_codeowners_normalizes_spaces(runner):
result = runner(alphabetize_codeowners_main, " /foo/bar.txt @alice\t@bob ")
@pytest.mark.parametrize("dialect", ("standard", "gitlab"))
def test_alphabetize_codeowners_normalizes_spaces(runner, dialect):
result = runner(
alphabetize_codeowners_main,
" /foo/bar.txt @alice\t@bob ",
add_args=["--dialect", dialect],
)
assert result.exit_code == 1
assert result.file_data == "/foo/bar.txt @alice @bob"


def test_alphabetize_codeowners_sorts(runner):
result = runner(alphabetize_codeowners_main, "/foo/bar.txt @Bob @alice @charlie")
@pytest.mark.parametrize("dialect", ("standard", "gitlab"))
def test_alphabetize_codeowners_sorts(runner, dialect):
result = runner(
alphabetize_codeowners_main,
"/foo/bar.txt @Bob @alice @charlie",
add_args=["--dialect", dialect],
)
assert result.exit_code == 1
assert result.file_data == "/foo/bar.txt @alice @Bob @charlie"


def test_alphabetize_codeowners_sorts_other(runner):
@pytest.mark.parametrize("dialect", ("standard", "gitlab"))
def test_alphabetize_codeowners_sorts_other(runner, dialect):
result = runner(
alphabetize_codeowners_main,
"/foo/bar.txt @Andy @adam @Bob @alice @charlie @groß @grost @grose",
add_args=["--dialect", dialect],
)
assert result.exit_code == 1
assert (
Expand All @@ -35,7 +50,8 @@ def test_alphabetize_codeowners_sorts_other(runner):
)


def test_alphabetize_codeowners_ignores_non_semantic_lines(runner):
@pytest.mark.parametrize("dialect", ("standard", "gitlab"))
def test_alphabetize_codeowners_ignores_non_semantic_lines(runner, dialect):
result = runner(
alphabetize_codeowners_main,
"""
Expand All @@ -44,5 +60,56 @@ def test_alphabetize_codeowners_ignores_non_semantic_lines(runner):
# comment 2: some non-alphabetized strings
# d c b a
/foo/bar.txt @alice @charlie""",
add_args=["--dialect", dialect],
)
assert result.exit_code == 0


def test_gitlab_alphabetize_codeowners_alphabetizes_default_owners(runner):
result = runner(
alphabetize_codeowners_main,
"""\
# section
[D A C B]
# optional section
^[D A C B E]
# section with owners
[D A C B] @mallory @alice
/foo/bar.txt
/foo/baz.txt""",
add_args=["--dialect", "gitlab"],
)
assert result.exit_code == 1
assert (
result.file_data
== """\
# section
[D A C B]
# optional section
^[D A C B E]
# section with owners
[D A C B] @alice @mallory
/foo/bar.txt
/foo/baz.txt"""
)


def test_gitlab_alphabetize_codeowners_alphabetizes_default_owners_with_min_reviewers(
runner,
):
result = runner(
alphabetize_codeowners_main,
"""\
[D A C B][2] @bob @mallory @alice
/foo/bar.txt
/foo/baz.txt""",
add_args=["--dialect", "gitlab"],
)
assert result.exit_code == 1
assert (
result.file_data
== """\
[D A C B][2] @alice @bob @mallory
/foo/bar.txt
/foo/baz.txt"""
)

0 comments on commit d582034

Please sign in to comment.