Skip to content

Commit bb53b5b

Browse files
committed
implement per-module reviewer assignment for better coverage, test it
Signed-off-by: Venky Ganesh <[email protected]>
1 parent 8c6f689 commit bb53b5b

File tree

3 files changed

+413
-417
lines changed

3 files changed

+413
-417
lines changed

.github/scripts/assign_reviewers.py

Lines changed: 64 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -99,31 +99,61 @@ def map_modules(changed_files: list[str],
9999

100100

101101
def gather_reviewers(
102-
modules: set[str],
103-
module_owners: dict[str, list[str]],
104-
*,
105-
pr_author: str | None = None,
106-
existing_reviewers: set[str] | None = None
107-
) -> tuple[list[str], set[str]]:
108-
"""Gather reviewers and return both reviewers and modules without owners"""
109-
reviewers: set[str] = set()
102+
modules: set[str],
103+
module_owners: dict[str, list[str]],
104+
*,
105+
pr_author: str | None = None,
106+
existing_reviewers: set[str] | None = None,
107+
per_module_limit: int = 2
108+
) -> tuple[list[str], dict[str, list[str]], set[str]]:
109+
"""
110+
Gather reviewers ensuring each module gets representation.
111+
112+
Args:
113+
modules: Set of module names that were touched
114+
module_owners: Dict mapping module names to lists of owners
115+
pr_author: PR author to exclude from reviewers
116+
existing_reviewers: Set of already assigned reviewers to exclude
117+
per_module_limit: Maximum reviewers to assign per module
118+
119+
Returns:
120+
- List of all unique reviewers to assign
121+
- Dict mapping modules to their assigned reviewers
122+
- Set of modules without owners
123+
"""
124+
all_reviewers: set[str] = set()
125+
module_assignments: dict[str, list[str]] = {}
110126
modules_without_owners: set[str] = set()
111127

112-
for module in modules:
128+
for module in sorted(modules): # Sort for consistent ordering
113129
owners = module_owners.get(module, [])
114-
if owners:
115-
reviewers.update(owners)
116-
else:
130+
if not owners:
117131
modules_without_owners.add(module)
132+
module_assignments[module] = []
133+
continue
134+
135+
# Filter out PR author and existing reviewers
136+
eligible_owners = [
137+
o for o in owners if o != pr_author and (
138+
not existing_reviewers or o not in existing_reviewers)
139+
]
140+
141+
if not eligible_owners:
142+
# All owners are excluded
143+
print(
144+
f" ⚠️ Module '{module}': All owners excluded (PR author or already assigned)"
145+
)
146+
module_assignments[module] = []
147+
continue
118148

119-
if pr_author:
120-
reviewers.discard(pr_author)
149+
# Sample up to per_module_limit reviewers for this module
150+
num_to_select = min(len(eligible_owners), per_module_limit)
151+
selected = random.sample(eligible_owners, num_to_select)
121152

122-
# Remove existing reviewers to avoid duplicate assignments
123-
if existing_reviewers:
124-
reviewers -= existing_reviewers
153+
module_assignments[module] = selected
154+
all_reviewers.update(selected)
125155

126-
return sorted(reviewers), modules_without_owners
156+
return sorted(all_reviewers), module_assignments, modules_without_owners
127157

128158

129159
def main() -> None:
@@ -141,10 +171,11 @@ def main() -> None:
141171
args = parser.parse_args()
142172

143173
pr_number = os.environ["PR_NUMBER"]
144-
reviewer_limit = int(os.environ.get("REVIEWER_LIMIT", "0"))
174+
per_module_limit = int(os.environ.get("PER_MODULE_REVIEWER_LIMIT", "2"))
145175
pr_author = os.environ.get("PR_AUTHOR")
146176

147177
print(f"Testing PR #{pr_number} with author: {pr_author}")
178+
print(f"Per-module reviewer limit: {per_module_limit}")
148179

149180
# Check existing reviewers
150181
existing_user_reviewers, existing_team_reviewers = get_existing_reviewers(
@@ -170,19 +201,26 @@ def main() -> None:
170201
module_owners = load_json(Path(".github") / "module-owners.json")
171202

172203
modules, unmapped_files = map_modules(changed_files, module_paths)
173-
reviewers, modules_without_owners = gather_reviewers(
204+
reviewers, module_assignments, modules_without_owners = gather_reviewers(
174205
modules,
175206
module_owners,
176207
pr_author=pr_author,
177208
existing_reviewers=
178-
existing_user_reviewers # Avoid re-assigning existing users
179-
)
209+
existing_user_reviewers, # Avoid re-assigning existing users
210+
per_module_limit=per_module_limit)
180211

181-
if reviewer_limit and len(reviewers) > reviewer_limit:
182-
reviewers = random.sample(reviewers, reviewer_limit)
212+
print(f"\nChanged modules: {sorted(modules)}")
213+
214+
# Show module-specific assignments
215+
if module_assignments:
216+
print("\nModule assignments:")
217+
for module, assigned in sorted(module_assignments.items()):
218+
if assigned:
219+
print(f" {module}: {assigned}")
220+
else:
221+
print(f" {module}: No eligible reviewers")
183222

184-
print(f"Changed modules: {sorted(modules)}")
185-
print(f"Potential reviewers: {reviewers}")
223+
print(f"\nFinal reviewers to assign: {reviewers}")
186224

187225
# Provide detailed feedback about coverage gaps
188226
if unmapped_files:

0 commit comments

Comments
 (0)