diff --git a/codeowners_diff.py b/codeowners_diff.py index 0da5312..3078e69 100644 --- a/codeowners_diff.py +++ b/codeowners_diff.py @@ -4,7 +4,6 @@ import difflib import functools import glob -import operator import os.path import re import subprocess @@ -69,9 +68,12 @@ def of(self, filepath: str) -> tuple[str, ...]: class MarkdownPrinter: def __init__( - self, changed_owners: Mapping[str, Mapping[str, tuple[str, ...]]], + self, + changed_owners: Mapping[str, Mapping[str, tuple[str, ...]]], + max_files_to_print: int | None, ) -> None: self.changed_owners = changed_owners + self.max_files_to_print = max_files_to_print def render_lines(self) -> Iterator[str]: if not self.changed_owners: @@ -80,24 +82,38 @@ def render_lines(self) -> Iterator[str]: yield f'{len(self.changed_owners)} files have changed ownership:' yield '' - yield from tabulate( + + files_to_print = list( sorted( - ( - { - 'file': f'`{file}`', - **{ - self._column_title(ref): ', '.join(owners_) - for ref, owners_ in owners.items() - }, - } - for file, owners in self.changed_owners.items() - ), - key=operator.itemgetter('file'), # type: ignore[arg-type] + self.changed_owners.items(), + ), + )[:self.max_files_to_print] + + yield from tabulate( + ( + { + 'file': f'`{file}`', + **{ + self._column_title(ref): ', '.join(owners_) + for ref, owners_ in owners.items() + }, + } + for file, owners in files_to_print ), headers='keys', tablefmt='pipe', ).splitlines() + if ( + self.max_files_to_print is not None and + (len(self.changed_owners) > self.max_files_to_print) + ): + yield '' + yield ( + 'Note that the above table was truncated to ' + f'{self.max_files_to_print} items.' + ) + def _column_title(self, ref: str) -> str: if ref: return f'`{ref}`' @@ -151,6 +167,11 @@ def main(argv: Sequence[str] | None = None) -> int: dest='repo_root', default=None, help='git repository to run the tool in (default: current directory)', ) + parser.add_argument( + '--limit', + default=None, + help='maximum files to show in diff message (default: no limit)', + ) args = parser.parse_args(argv) git_repo = GitRepo(args.repo_root) @@ -180,7 +201,7 @@ def main(argv: Sequence[str] | None = None) -> int: if base_file_owners[file] != head_file_owners[file] } - printer = MarkdownPrinter(changed_file_owners) + printer = MarkdownPrinter(changed_file_owners, max_files_to_print=args.limit) print(*printer.render_lines(), sep='\n') return 0 diff --git a/tests/codeowners_diff_test.py b/tests/codeowners_diff_test.py index 8074f52..afa78f4 100644 --- a/tests/codeowners_diff_test.py +++ b/tests/codeowners_diff_test.py @@ -109,16 +109,18 @@ def test_handle_missing_codeowners_file( class TestMarkdownPrinter: def test_render_lines(self) -> None: - printer = codeowners_diff.MarkdownPrinter({ - 'foo/bar/baz.py': { - 'base': ('@some-owner', '@some/team'), - 'HEAD': ('@another/team',), - }, - 'foo/bar/bang.py': { - 'base': ('@some/team',), - 'HEAD': ('@another/team', '@some-user'), - }, - }) + printer = codeowners_diff.MarkdownPrinter( + { + 'foo/bar/baz.py': { + 'base': ('@some-owner', '@some/team'), + 'HEAD': ('@another/team',), + }, + 'foo/bar/bang.py': { + 'base': ('@some/team',), + 'HEAD': ('@another/team', '@some-user'), + }, + }, None, + ) lines = printer.render_lines() assert '\n'.join(lines) == """\ @@ -130,7 +132,36 @@ def test_render_lines(self) -> None: | `foo/bar/baz.py` | @some-owner, @some/team | @another/team |""" def test_no_changes(self) -> None: - printer = codeowners_diff.MarkdownPrinter({}) + printer = codeowners_diff.MarkdownPrinter({}, None) lines = printer.render_lines() assert '\n'.join(lines) == 'No files have changed ownership.' + + def test_max_files_to_print(self) -> None: + printer = codeowners_diff.MarkdownPrinter( + { + 'foo/bar/baz.py': { + 'base': ('@some-owner', '@some/team'), + 'HEAD': ('@another/team',), + }, + 'foo/bar/bash.py': { + 'base': ('@some-owner', '@some/team'), + 'HEAD': ('@another/team',), + }, + 'foo/bar/bang.py': { + 'base': ('@some/team',), + 'HEAD': ('@another/team', '@some-user'), + }, + }, None, + ) + + lines = printer.render_lines() + assert '\n'.join(lines) == """\ +3 files have changed ownership: + +| file | `base` | `HEAD` | +|:------------------|:------------------------|:--------------------------| +| `foo/bar/bang.py` | @some/team | @another/team, @some-user | +| `foo/bar/baz.py` | @some-owner, @some/team | @another/team | + +Note that the above table was truncated to 2 items.""" diff --git a/tests/end_to_end_test.py b/tests/end_to_end_test.py index b5c047f..61cf6e8 100644 --- a/tests/end_to_end_test.py +++ b/tests/end_to_end_test.py @@ -80,6 +80,29 @@ def test_compare_working_tree_with_HEAD( assert captured.err == '' +def test_limit_is_respected( + tmp_path: Path, capsys: pytest.CaptureFixture[str], +) -> None: + _create_git_repo(tmp_path) + _create_branch(tmp_path, 'branch-a', '@some-user') + _create_branch(tmp_path, 'branch-b', '@another/team') + + codeowners_diff.main( + ('branch-a', 'branch-b', '-C', str(tmp_path), '--limit', '0'), + ) + + captured = capsys.readouterr() + assert captured.out == """\ +1 files have changed ownership: + +| file | `branch-a` | `branch-b` | +|:-----------------|:-------------|:--------------| + +Note that the above table was truncated to 0 items. +""" + assert captured.err == '' + + def _create_git_repo(root_dir: Path) -> None: subprocess.run(('git', 'init'), cwd=root_dir)