@@ -153,43 +153,51 @@ def make_role(self, title: str):
153153 )
154154 return slugify (title )
155155
156+ async def _resolve_identity (
157+ self , github_username : str | None , email : str | None
158+ ) -> str | None :
159+ # Fall back from github username to email so a wrong/missing username
160+ # does not prevent an email match.
161+ if github_username and github_username != "unknown" :
162+ identity_id = await find_github_identity (github_username )
163+ if identity_id :
164+ return identity_id
165+ if email and email != "unknown" :
166+ return await find_maintainer_identity_by_email (email )
167+ return None
168+
169+ async def _resolve_maintainers (
170+ self , maintainers : list [MaintainerInfoItem ]
171+ ) -> list [tuple [MaintainerInfoItem , str ]]:
172+ semaphore = asyncio .Semaphore (3 )
173+
174+ async def resolve (m : MaintainerInfoItem ) -> tuple [MaintainerInfoItem , str | None ]:
175+ async with semaphore :
176+ identity_id = await self ._resolve_identity (m .github_username , m .email )
177+ return m , identity_id
178+
179+ results = await asyncio .gather (* [resolve (m ) for m in maintainers ])
180+
181+ resolved : list [tuple [MaintainerInfoItem , str ]] = []
182+ for m , identity_id in results :
183+ if identity_id is None :
184+ self .logger .warning (f"Identity not found for maintainer: { m } " )
185+ continue
186+ resolved .append ((m , identity_id ))
187+ return resolved
188+
156189 async def insert_new_maintainers (
157190 self , repo_url : str , repo_id : str , maintainers : list [MaintainerInfoItem ]
158191 ):
159- async def process_maintainer ( maintainer : MaintainerInfoItem ):
160- self . logger . info ( f"Processing maintainer: { maintainer . github_username } " )
192+ resolved = await self . _resolve_maintainers ( maintainers )
193+ for maintainer , identity_id in resolved :
161194 role = maintainer .normalized_title
162195 original_role = self .make_role (maintainer .title )
163- # Find the identity in the database
164- github_username = maintainer .github_username
165- email = maintainer .email
166-
167- if github_username == "unknown" and email == "unknown" :
168- self .logger .warning ("username & email with value 'unknown' aborting" )
169- return
170- identity_id = (
171- await find_github_identity (github_username )
172- if github_username != "unknown"
173- else await find_maintainer_identity_by_email (email )
174- )
175- self .logger .debug (
176- f"Found identity_id for { github_username } : { identity_id } (type: { type (identity_id )} )"
196+ await upsert_maintainer (repo_id , identity_id , repo_url , role , original_role )
197+ self .logger .info (
198+ f"Successfully upserted maintainer { maintainer .github_username } "
199+ f"with identity_id { identity_id } "
177200 )
178- if identity_id :
179- await upsert_maintainer (repo_id , identity_id , repo_url , role , original_role )
180- self .logger .info (
181- f"Successfully upserted maintainer { github_username } with identity_id { identity_id } "
182- )
183- else :
184- self .logger .warning (f"Identity not found for GitHub user: { maintainer } " )
185-
186- semaphore = asyncio .Semaphore (3 )
187-
188- async def process_with_semaphore (maintainer : MaintainerInfoItem ):
189- async with semaphore :
190- await process_maintainer (maintainer )
191-
192- await asyncio .gather (* [process_with_semaphore (maintainer ) for maintainer in maintainers ])
193201
194202 async def compare_and_update_maintainers (
195203 self ,
@@ -200,63 +208,38 @@ async def compare_and_update_maintainers(
200208 ):
201209 self .logger .info (f"Comparing and updating maintainers for repo: { repo_id } " )
202210 current_maintainers = await get_maintainers_for_repo (repo_id )
203- current_maintainers_dict = {m ["github_username" ]: m for m in current_maintainers }
204- new_maintainers_dict = {m .github_username : m for m in maintainers }
205211
206- for github_username , maintainer in new_maintainers_dict .items ():
207- role = maintainer .normalized_title
208- original_role = self .make_role (maintainer .title )
209- if github_username == "unknown" and maintainer .email in ("unknown" , None ):
210- self .logger .warning (
211- f"Skipping unknown github_username & email with title { maintainer .title } "
212- )
212+ # Key by (identityId, role) — the natural unique tuple for a linked maintainer.
213+ # Keying by github_username collapses every "unknown" extraction into one slot,
214+ # which silently drops most email-only maintainers (e.g. linux MAINTAINERS).
215+ current_by_key : dict [tuple [str , str ], dict ] = {
216+ (m ["identityId" ], m ["role" ]): m for m in current_maintainers
217+ }
218+
219+ resolved = await self ._resolve_maintainers (maintainers )
220+ new_by_key : dict [tuple [str , str ], MaintainerInfoItem ] = {
221+ (identity_id , m .normalized_title ): m for m , identity_id in resolved
222+ }
223+
224+ for (identity_id , role ), maintainer in new_by_key .items ():
225+ if (identity_id , role ) in current_by_key :
213226 continue
214- elif github_username not in current_maintainers_dict :
215- # New maintainer
216- identity_id = (
217- await find_github_identity (github_username )
218- if github_username != "unknown"
219- else await find_maintainer_identity_by_email (maintainer .email )
220- )
221- self .logger .info (f"Found new maintainer { github_username } to be inserted" )
222- if identity_id :
223- await upsert_maintainer (
224- repo_id , identity_id , repo_url , role , original_role , start_date = change_date
225- )
226- self .logger .info (
227- f"Successfully inserted new maintainer { github_username } with identity_id { identity_id } "
228- )
229- else :
230- # will happen for new users if their identity isn't created yet but should be fixed on the next iteration
231- self .logger .warning (f"Identity not found for username: { github_username } " )
232- else :
233- # Existing maintainer
234- current_maintainer = current_maintainers_dict [github_username ]
235- if current_maintainer ["role" ] != role :
236- # Role has changed: we update maintainer
237- self .logger .info (
238- f"Role changed from { current_maintainer ['role' ]} to { role } for maintainer { current_maintainer ['identityId' ]} "
239- )
240- await upsert_maintainer (
241- repo_id ,
242- current_maintainer ["identityId" ],
243- repo_url ,
244- role ,
245- original_role ,
246- change_date ,
247- )
227+ original_role = self .make_role (maintainer .title )
228+ await upsert_maintainer (
229+ repo_id , identity_id , repo_url , role , original_role , start_date = change_date
230+ )
231+ self .logger .info (
232+ f"Inserted new maintainer { maintainer .github_username } "
233+ f"with identity_id { identity_id } role { role } "
234+ )
248235
249- for github_username , current_maintainer in current_maintainers_dict . items () :
250- if github_username not in new_maintainers_dict :
236+ for identity_id , role in current_by_key :
237+ if ( identity_id , role ) not in new_by_key :
251238 self .logger .info (
252- f"Maintainer { github_username } with identity { current_maintainer ['identityId' ]} no longer exists, updating its endDate..."
253- )
254- await set_maintainer_end_date (
255- repo_id ,
256- current_maintainer ["identityId" ],
257- current_maintainer ["role" ],
258- change_date ,
239+ f"Maintainer with identity { identity_id } role { role } no longer exists, "
240+ f"updating its endDate..."
259241 )
242+ await set_maintainer_end_date (repo_id , identity_id , role , change_date )
260243
261244 async def save_maintainers (
262245 self ,
0 commit comments