blob: bca4e9a222d62e918f7b86f4230edaf949ad83de [file]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""lists.sr.ht patchsets integration and mapping."""
from __future__ import annotations
import contextlib
from typing import Any
from magpie_sourcehut.client import query_graphql
def _normalize_owner(owner: str) -> str:
"""Ensure the owner username starts with '~'."""
if not owner.startswith("~"):
return f"~{owner}"
return owner
def get_patchset(owner: str, list_name: str, patchset_id: int) -> dict[str, Any]:
"""Retrieve patchset details from lists.sr.ht."""
owner = _normalize_owner(owner)
q = """
query GetPatchset($owner: String!, $name: String!, $id: Int!) {
list(owner: $owner, name: $name) {
id
name
patchset(id: $id) {
id
subject
version
status
patches {
id
subject
diff
}
thread {
id
emails {
edges {
node {
id
subject
body
sender {
canonicalName
}
date
}
}
}
}
}
}
}
"""
res = query_graphql("lists", q, {"owner": owner, "name": list_name, "id": patchset_id})
mlist = res.get("list") or {}
return mlist.get("patchset") or {}
def list_patchsets(owner: str, list_name: str) -> list[dict[str, Any]]:
"""List patchsets on a specific mailing list on lists.sr.ht."""
owner = _normalize_owner(owner)
q = """
query ListPatchsets($owner: String!, $name: String!) {
list(owner: $owner, name: $name) {
id
name
patches {
edges {
node {
id
subject
version
status
}
}
}
}
}
"""
res = query_graphql("lists", q, {"owner": owner, "name": list_name})
mlist = res.get("list") or {}
patches_conn = mlist.get("patches") or {}
edges = patches_conn.get("edges") or []
return [edge.get("node") for edge in edges if edge and edge.get("node")]
def map_patchset_to_pr(patchset: dict[str, Any]) -> dict[str, Any]:
"""Map a lists.sr.ht patchset to a uniform PR/MR review abstraction structure.
Args:
patchset: A dictionary representing the patchset retrieved via GraphQL.
Returns:
A dictionary containing the uniform PR/MR fields.
"""
if not patchset:
return {}
# Extract thread emails for description & comments
thread = patchset.get("thread") or {}
emails_conn = thread.get("emails") or {}
edges = emails_conn.get("edges") or []
emails = [edge.get("node") for edge in edges if edge and edge.get("node")]
# Sort emails by date if possible
with contextlib.suppress(Exception):
emails.sort(key=lambda x: x.get("date", ""))
# The cover letter / description is the first email (or patchset subject)
description = ""
author = "Unknown"
if emails:
first_email = emails[0]
description = first_email.get("body", "")
sender = first_email.get("sender") or {}
author = sender.get("canonicalName", "Unknown")
# Map patchset status to a standardized PR state (OPEN, MERGED, CLOSED)
status = patchset.get("status", "PROPOSED")
state = "OPEN"
if status == "ACCEPTED":
state = "MERGED"
elif status in ("REJECTED", "SUPERSEDED"):
state = "CLOSED"
# Map patches inside the patchset to commits
commits = []
for patch in patchset.get("patches") or []:
if patch:
commits.append(
{
"id": str(patch.get("id")),
"subject": patch.get("subject", ""),
"diff": patch.get("diff", ""),
}
)
# Map replies (all emails except the first/cover letter) to review comments
comments = []
for email in emails[1:]:
sender = email.get("sender") or {}
comments.append(
{
"id": str(email.get("id")),
"author": sender.get("canonicalName", "Unknown"),
"body": email.get("body", ""),
"date": email.get("date", ""),
}
)
return {
"id": str(patchset.get("id")),
"title": patchset.get("subject", ""),
"description": description,
"author": author,
"state": state,
"commits": commits,
"comments": comments,
"raw_status": status,
"version": patchset.get("version"),
}