Skip to content

Commit 8e3a77b

Browse files
committed
improve CODEOWNER add/update behavior
1 parent f8f289e commit 8e3a77b

File tree

1 file changed

+43
-64
lines changed

1 file changed

+43
-64
lines changed

ccos/teams/set_codeowners.py

+43-64
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@
2323
GIT_USER_NAME = "CC creativecommons.github.io Bot"
2424
GIT_USER_EMAIL = "cc-creativecommons-github-io-bot@creativecommons.org"
2525
SYNC_BRANCH = "ct_codeowners"
26+
CODEOWNERS_TEMPLATE = """\
27+
# https://help.github.com/en/articles/about-code-owners
28+
# If you want to match two or more code owners with the same pattern, all the
29+
# code owners must be on the same line. If the code owners are not on the same
30+
# line, the pattern matches only the last mentioned code owner.
31+
* @creativecommons/technology
32+
"""
2633

2734
log_name = os.path.basename(os.path.splitext(inspect.stack()[-1].filename)[0])
2835
LOG = logging.getLogger(log_name)
@@ -35,7 +42,7 @@ def create_codeowners_for_data(args, databag):
3542
organization = get_cc_organization(github_client)
3643

3744
LOG.info("Identifying and fixing CODEOWNER issues...")
38-
projects = databag["projects"]
45+
projects = sorted(databag["projects"], key=lambda d: d["name"])
3946
for project in projects:
4047
project_name = project["name"]
4148
LOG.info(
@@ -51,7 +58,7 @@ def create_codeowners_for_data(args, databag):
5158
)
5259

5360
LOG.info("Checking all projects...")
54-
repos = project["repos"]
61+
repos = sorted(project["repos"])
5562
with TemporaryDirectory() as temp_dir:
5663
for repo_name in repos:
5764
check_and_fix_repo(
@@ -113,18 +120,16 @@ def check_and_fix_repo(args, organization, repo_name, teams, temp_dir):
113120
codeowners_path = Path(os.path.join(repo_dir, ".github", "CODEOWNERS"))
114121
fix_required = False
115122

116-
if not codeowners_path.exists() and codeowners_path.is_file():
123+
if not codeowners_path.exists():
117124
fix_required = True
118125
LOG.info("CODEOWNERS does not exist, creating...")
119126
os.makedirs(codeowners_path.parent, exist_ok=True)
120-
open(codeowners_path, "a").close()
127+
with open(codeowners_path, "w") as codeowners_file:
128+
codeowners_file.write(CODEOWNERS_TEMPLATE)
121129
LOG.log(ccos.log.SUCCESS, "Done.")
122130

123131
teams = filter_valid_teams(gh_repo, teams)
124-
team_mention_map = get_team_mention_map(codeowners_path, teams)
125-
if not all(team_mention_map.values()):
126-
fix_required = True
127-
add_missing_teams(codeowners_path, team_mention_map)
132+
fix_required = add_missing_teams(codeowners_path, teams)
128133

129134
if fix_required:
130135
branch_name = create_branch(local_repo)
@@ -134,8 +139,6 @@ def check_and_fix_repo(args, organization, repo_name, teams, temp_dir):
134139

135140
LOG.log(ccos.log.SUCCESS, "Done.")
136141

137-
LOG.log(ccos.log.SUCCESS, "All is well.")
138-
139142

140143
def filter_valid_teams(gh_repo, teams):
141144
"""
@@ -167,10 +170,10 @@ def create_branch(local_repo):
167170

168171

169172
def commit_or_display_changes(args, local_repo, codeowners_path):
173+
local_repo.index.add(items=codeowners_path)
170174
if args.debug:
171-
LOG.debug(local_repo.git.diff())
175+
LOG.debug(local_repo.git.diff(staged=True))
172176
else:
173-
local_repo.index.add(items=codeowners_path)
174177
local_repo.index.commit(message="Sync Community Team(s) to CODEOWNERS")
175178

176179

@@ -223,50 +226,40 @@ def set_up_repo(clone_url, repo_dir):
223226
return local_repo
224227

225228

226-
def get_team_mention_map(codeowners_path, teams):
227-
"""
228-
Map the team slugs to whether they have been mentioned in the CODEOWNERS
229-
file in any capacity.
230-
231-
@param codeowners_path: the path of the CODEOWNERS file
232-
@param teams: all the GitHub teams for all Community Teams of a project
233-
@return: a dictionary of team slugs and their mentions
234-
"""
235-
with open(codeowners_path) as codeowners_file:
236-
contents = codeowners_file.read()
237-
return {team.slug: mentionified(team.slug) in contents for team in teams}
238-
239-
240-
def add_missing_teams(codeowners_path, team_mention_map):
229+
def add_missing_teams(codeowners_path, teams):
241230
"""
242231
Add the mention forms for all missing teams in a new line.
243232
244233
@param codeowners_path: the path of the CODEOWNERS file
245234
@param team_mention_map: the dictionary of team slugs and their mentions
246235
"""
247-
LOG.info("CODEOWNERS is incomplete, populating...")
248-
missing_team_slugs = [
249-
team_slug
250-
for team_slug in team_mention_map.keys()
251-
if not team_mention_map[team_slug]
252-
]
253-
with open(codeowners_path, "a") as codeowners_file:
254-
addendum = generate_ideal_codeowners_rule(missing_team_slugs)
255-
codeowners_file.write(addendum)
256-
codeowners_file.write("\n")
257-
LOG.log(ccos.log.SUCCESS, "Done.")
258-
259-
260-
def generate_ideal_codeowners_rule(team_slugs):
261-
"""
262-
Generate an ideal CODEOWNERS rule for the given set of roles. Assigns all
263-
files using the wildcard expression '*' to the given roles.
264-
265-
@param team_slugs: the set of team slugs to be added to the CODEOWNERS file
266-
@return: the line that should be added to the CODEOWNERS file
267-
"""
268-
combined_team_slugs = " ".join(map(mentionified, team_slugs))
269-
return f"* {combined_team_slugs}"
236+
fix_required = False
237+
community_teams = []
238+
for team in teams:
239+
community_teams.append(f"@{GITHUB_ORGANIZATION}/{team.slug}")
240+
community_teams.sort()
241+
new_codeowners = []
242+
with open(codeowners_path, "r") as codeowners_file:
243+
new_codeowners = codeowners_file.readlines()
244+
for index, line in enumerate(new_codeowners):
245+
if not line.startswith("* "):
246+
continue
247+
staff_teams = []
248+
teams = line.split()[1:]
249+
for team in teams:
250+
if not team.startswith("@creativecommons/ct-"):
251+
staff_teams.append(team)
252+
staff_teams.sort()
253+
new_line = f"* {' '.join(staff_teams)} {' '.join(community_teams)}\n"
254+
if line.strip() != new_line.strip():
255+
new_codeowners[index] = new_line
256+
fix_required = True
257+
if fix_required:
258+
LOG.info("CODEOWNERS is incomplete, populating...")
259+
with open(codeowners_path, "w") as codeowners_file:
260+
codeowners_file.writelines(new_codeowners)
261+
LOG.log(ccos.log.SUCCESS, "Done.")
262+
return fix_required
270263

271264

272265
def get_github_repo_url_with_credentials(repo_name):
@@ -282,17 +275,3 @@ def get_github_repo_url_with_credentials(repo_name):
282275
f"https://{github_username}:{github_token}"
283276
f"@github.com/{GITHUB_ORGANIZATION}/{repo_name}.git"
284277
)
285-
286-
287-
def mentionified(team_slug):
288-
"""
289-
Get the mention form of the given team. Mention forms are generated by
290-
prefixing the organization to the team slug.
291-
292-
mention form schema
293-
@<organization>/<team slug>
294-
295-
@param team_slug: the slug of the team to mention
296-
@return: the mentionable form of the given team slug
297-
"""
298-
return f"@{GITHUB_ORGANIZATION}/{team_slug}"

0 commit comments

Comments
 (0)