Skip to content

Commit e8d64e5

Browse files
joanagmaiaclaude
andcommitted
fix: address PR review on maintainer identity comparison (DE-980)
Four follow-ups from PR #4277 review, all aimed at preserving prior behaviour rather than changing the feature: - compare_and_update_maintainers: guard the end-date pass with a "still mentioned in source" check. The old github_username-keyed dict always included extracted maintainers regardless of identity-lookup outcome, so a transient resolution failure never wrongly end-dated a maintainer still present in the file. Restore that invariant under the new identity-based keying. - get_maintainers_for_repo: add mi."endDate" IS NULL. Reinstates the reactivation path the old platform='github' filter incidentally provided for email-linked maintainers (end-dated rows fell out of the comparison set, hit the "new maintainer" branch, and got reactivated via upsert's ON CONFLICT ... SET "endDate" = NULL). - insert_new_maintainers: restore concurrent upserts via asyncio.gather + Semaphore(MAX_CONCURRENT_CHUNKS). The earlier refactor accidentally serialised them, regressing first-run performance on large MAINTAINERS files. - _resolve_maintainers: use self.MAX_CONCURRENT_CHUNKS instead of the hardcoded literal so concurrency tuning stays in one place. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Joana Maia <jmaia@contractor.linuxfoundation.org>
1 parent 052a630 commit e8d64e5

2 files changed

Lines changed: 52 additions & 17 deletions

File tree

services/apps/git_integration/src/crowdgit/database/crud.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,11 +410,17 @@ async def get_maintainers_for_repo(repo_id: str):
410410
# actually persists. A stricter read filter hides rows from the incremental diff,
411411
# which leads to spurious re-inserts and missed end-dates for email-linked
412412
# maintainers (e.g. platform='git' rows on the linux kernel repo).
413+
#
414+
# endDate IS NULL keeps the comparison set limited to active maintainers, matching
415+
# the previous behaviour for email-linked maintainers (where the old platform filter
416+
# incidentally excluded them and let the "new maintainer" branch reactivate them
417+
# via the upsert's `endDate = NULL` on conflict). Without this, end-dated rows would
418+
# sit in the comparison set and block reactivation when the maintainer reappears.
413419
maintainers_sql_query = """
414420
SELECT mi.role, mi."originalRole", mi."repoUrl", mi."repoId", mi."identityId", mem.value as github_username
415421
FROM "maintainersInternal" mi
416422
JOIN "memberIdentities" mem ON mi."identityId" = mem.id
417-
WHERE mi."repoId" = $1 AND mem."deletedAt" is null
423+
WHERE mi."repoId" = $1 AND mi."endDate" IS NULL AND mem."deletedAt" is null
418424
"""
419425
return await query(
420426
maintainers_sql_query,

services/apps/git_integration/src/crowdgit/services/maintainer/maintainer_service.py

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,9 @@ async def _resolve_maintainers(
173173
) -> list[tuple[MaintainerInfoItem, str]]:
174174
# Centralised resolver used by both the first-run and incremental paths so
175175
# they share identical lookup + fallback semantics. The semaphore caps DB
176-
# concurrency at 3 (large MAINTAINERS files can carry thousands of entries).
177-
semaphore = asyncio.Semaphore(3)
176+
# concurrency at MAX_CONCURRENT_CHUNKS (large MAINTAINERS files can carry
177+
# thousands of entries).
178+
semaphore = asyncio.Semaphore(self.MAX_CONCURRENT_CHUNKS)
178179

179180
async def resolve(m: MaintainerInfoItem) -> tuple[MaintainerInfoItem, str | None]:
180181
async with semaphore:
@@ -195,14 +196,21 @@ async def insert_new_maintainers(
195196
self, repo_url: str, repo_id: str, maintainers: list[MaintainerInfoItem]
196197
):
197198
resolved = await self._resolve_maintainers(maintainers)
198-
for maintainer, identity_id in resolved:
199-
role = maintainer.normalized_title
200-
original_role = self.make_role(maintainer.title)
201-
await upsert_maintainer(repo_id, identity_id, repo_url, role, original_role)
202-
self.logger.info(
203-
f"Successfully upserted maintainer {maintainer.github_username} "
204-
f"with identity_id {identity_id}"
205-
)
199+
# Preserve the previous behaviour of running upserts concurrently so first-run
200+
# processing of large MAINTAINERS files (thousands of entries) doesn't serialise.
201+
semaphore = asyncio.Semaphore(self.MAX_CONCURRENT_CHUNKS)
202+
203+
async def upsert(maintainer: MaintainerInfoItem, identity_id: str) -> None:
204+
async with semaphore:
205+
role = maintainer.normalized_title
206+
original_role = self.make_role(maintainer.title)
207+
await upsert_maintainer(repo_id, identity_id, repo_url, role, original_role)
208+
self.logger.info(
209+
f"Successfully upserted maintainer {maintainer.github_username} "
210+
f"with identity_id {identity_id}"
211+
)
212+
213+
await asyncio.gather(*[upsert(m, identity_id) for m, identity_id in resolved])
206214

207215
async def compare_and_update_maintainers(
208216
self,
@@ -243,13 +251,34 @@ async def compare_and_update_maintainers(
243251
f"with identity_id {identity_id} role {role}"
244252
)
245253

246-
for identity_id, role in current_by_key:
247-
if (identity_id, role) not in new_by_key:
248-
self.logger.info(
249-
f"Maintainer with identity {identity_id} role {role} no longer exists, "
250-
f"updating its endDate..."
254+
# Defensive guard for the end-date pass: the previous implementation keyed
255+
# the "new" set by github_username, so any maintainer present in the source
256+
# was unconditionally in that set and never wrongly end-dated. The identity-
257+
# based key now only contains successfully resolved entries, so resolution
258+
# failures (transient DB issues, stale extraction values) could end-date a
259+
# maintainer who is still in the file. Skip end-dating when the current
260+
# row's identifying value still appears in the extracted source.
261+
mentioned_values = set()
262+
for m in maintainers:
263+
for v in (m.github_username, m.email):
264+
if v and v != "unknown":
265+
mentioned_values.add(v.lower())
266+
267+
for (identity_id, role), current in current_by_key.items():
268+
if (identity_id, role) in new_by_key:
269+
continue
270+
current_value = (current.get("github_username") or "").lower()
271+
if current_value and current_value in mentioned_values:
272+
self.logger.warning(
273+
f"Maintainer with identity {identity_id} role {role} could not be "
274+
f"re-resolved but is still mentioned in the source; skipping end-date"
251275
)
252-
await set_maintainer_end_date(repo_id, identity_id, role, change_date)
276+
continue
277+
self.logger.info(
278+
f"Maintainer with identity {identity_id} role {role} no longer exists, "
279+
f"updating its endDate..."
280+
)
281+
await set_maintainer_end_date(repo_id, identity_id, role, change_date)
253282

254283
async def save_maintainers(
255284
self,

0 commit comments

Comments
 (0)