Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/sentry/issues/ownership/grammar.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class OwnershipRule(TypedDict):
PATH = "path"
MODULE = "module"
CODEOWNERS = "codeowners"
EMPTY_CODEOWNERS_MATCHER = re.compile(rf"^\s*{CODEOWNERS}:\s*(?:#.*)?$")

# Grammar is defined in EBNF syntax.
ownership_grammar = Grammar(
Expand Down Expand Up @@ -327,6 +328,14 @@ def parse_rules(data: str) -> Any:
return OwnershipVisitor().visit(tree)


def remove_empty_codeowners_matchers(data: str) -> str:
return "".join(
line
for line in data.splitlines(keepends=True)
if not EMPTY_CODEOWNERS_MATCHER.match(line)
)


def dump_schema(rules: Sequence[Rule]) -> OwnershipSchema:
"""Convert a Rule tree into a JSON schema"""
return {"$version": VERSION, "rules": [r.dump() for r in rules]}
Expand Down Expand Up @@ -461,6 +470,9 @@ def convert_codeowners_syntax(
else:
formatted_path = path

if not formatted_path:
formatted_path = "/"

if not code_owners:
# Exclusion rule: path with no owners means "no ownership" for this path
result += f"codeowners:{formatted_path}\n"
Expand Down Expand Up @@ -620,6 +632,8 @@ def create_schema_from_issue_owners(
if issue_owners is None:
return None

issue_owners = remove_empty_codeowners_matchers(issue_owners)

try:
rules = parse_rules(issue_owners)
except ParseError as e:
Expand Down
38 changes: 38 additions & 0 deletions tests/sentry/issues/ownership/test_grammar.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
Rule,
convert_codeowners_syntax,
convert_schema_to_rules_text,
create_schema_from_issue_owners,
dump_schema,
get_invalid_owner_details,
load_schema,
Expand Down Expand Up @@ -1422,6 +1423,43 @@ def test_convert_codeowners_syntax_exclusion_with_stack_root() -> None:
assert "codeowners:webpack://static/apps/github\n" in result


def test_convert_codeowners_syntax_preserves_mapped_root_path() -> None:
code_mapping = type("", (), {})()
code_mapping.stack_root = ""
code_mapping.source_root = "/apps"

result = convert_codeowners_syntax(
"/apps @octocat\n",
{"@octocat": "octocat@sentry.io"},
code_mapping,
)
assert result == "codeowners:/ octocat@sentry.io\n"


def test_convert_codeowners_syntax_preserves_mapped_root_exclusion() -> None:
code_mapping = type("", (), {})()
code_mapping.stack_root = ""
code_mapping.source_root = "/apps"

result = convert_codeowners_syntax(
"/apps\n",
{},
code_mapping,
)
assert result == "codeowners:/\n"


def test_create_schema_from_issue_owners_skips_empty_codeowners_matcher() -> None:
assert (
create_schema_from_issue_owners(
project_id=1,
issue_owners="codeowners: #discover\n",
remove_deleted_owners=True,
)
== {"$version": 1, "rules": []}
)


def test_parse_code_owners_exclusion_rule() -> None:
codeowners = "/apps/ @getsentry/frontend\n/apps/github\n"
teams, usernames, emails = parse_code_owners(codeowners)
Expand Down
26 changes: 26 additions & 0 deletions tests/sentry/tasks/test_code_owners.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,32 @@ def test_simple(self) -> None:
assert code_owners.schema == {"$version": 1, "rules": []}
assert code_owners.date_synced is None

def test_update_schema_with_codeowners_root_mapping(self) -> None:
self.code_mapping.source_root = "/docs"
self.code_mapping.stack_root = ""
self.code_mapping.save()
self.code_owners.raw = "/docs @getsentry/ecosystem\n"
self.code_owners.save()

with self.tasks() and self.feature({"organizations:integrations-codeowners": True}):
self.create_external_team(integration=self.integration)
update_code_owners_schema(
organization=self.organization.id, integration=self.integration.id
)

code_owners = ProjectCodeOwners.objects.get(id=self.code_owners.id)
assert code_owners.schema == {
"$version": 1,
"rules": [
{
"matcher": {"type": "codeowners", "pattern": "/"},
"owners": [
{"type": "team", "identifier": "tiger-team", "id": self.team.id},
],
}
],
}

@freeze_time("2023-01-01 00:00:00")
@patch(
"sentry.integrations.github.integration.GitHubIntegration.get_codeowner_file",
Expand Down
Loading