Skip to content

Instantly share code, notes, and snippets.

@durden
Last active January 31, 2025 21:02
Show Gist options
  • Save durden/9cd0aebc995aabf78d4b06c2d74c6ab7 to your computer and use it in GitHub Desktop.
Save durden/9cd0aebc995aabf78d4b06c2d74c6ab7 to your computer and use it in GitHub Desktop.
Faster way to check CODEOWNERS on specific file/folders
"""
To use this you'll need the following installed into your env
- https://pypi.org/project/codeowners/
- https://pypi.org/project/tabulate/
Another approach to installing everything would be to use uv's scripts-dependency functionality:
- https://docs.astral.sh/uv/guides/scripts/#declaring-script-dependencies
Then using `uvx url-to-gist` with https://docs.astral.sh/uv/guides/tools/#requesting-different-sources
Usage: parse_codeowners.py <git root> <space separated list of paths to check codeowners on>
The main benefit this over running just `codeowners <folder>` is that this script will ignore files
not tracked by git, making it much faster since for python projects it will ignore __pycache__
folders, etc.
"""
import collections
import subprocess
import sys
from tabulate import tabulate
import codeowners
class GitRepo:
def __init__(self, root_dir: str | None = None) -> None:
if root_dir: # pragma: no cover
self.root_dir = root_dir
def root_dir(self) -> str: # pragma: no cover
return subprocess.check_output(
("git", "rev-parse", "--show-toplevel"),
text=True,
).strip()
def load_codeowners_file(self, ref: str) -> str:
return subprocess.check_output(
("git", "cat-file", "blob", f"{ref}:.github/CODEOWNERS"),
cwd=self.root_dir,
text=True,
)
def ls_files(self, paths):
if not paths:
return frozenset()
return frozenset(
subprocess.check_output(
("git", "ls-files", "--", *paths),
cwd=self.root_dir,
text=True,
)
.strip()
.splitlines(),
)
def main(repo_dir, paths_to_check):
repo = GitRepo(repo_dir)
codeowners_file = f"{repo_dir}/.github/CODEOWNERS"
with open(codeowners_file, "r") as file:
text = file.read()
owners = codeowners.CodeOwners(text)
owners_by_path = collections.defaultdict(set)
for path in repo.ls_files(paths_to_check):
for owner in owners.of(path):
if owner[0] == "TEAM":
owners_by_path[path].add(owner[1])
else:
owners_by_path[path].add("unowned")
table = []
for path, owners in owners_by_path.items():
if len(owners) > 1:
try:
owners.remove("unowned")
except KeyError:
pass
table.append((path, ",".join(owners)))
print(tabulate(sorted(table, key=lambda tup: tup[0])))
if __name__ == "__main__":
main(sys.argv[1], sys.argv[2:])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment