summaryrefslogtreecommitdiff
path: root/tools/patman/patchwork.py
diff options
context:
space:
mode:
authorTom Rini <[email protected]>2026-07-06 08:12:19 -0600
committerTom Rini <[email protected]>2026-07-06 08:28:51 -0600
commit18761722e67d6e3770ab296a04fe3d40022e76d2 (patch)
treee544dae54d83d5d3524e527c435be0395efe3a17 /tools/patman/patchwork.py
parenta25c9bd19215ddd95d7494ebf8d842c46fdf3ba8 (diff)
parent91b94aff149d2c6efe3afa81adaf5edcc6119221 (diff)
Merge patch series "Remove patman from the U-Boot tree"
Simon Glass <[email protected]> says: patman is now maintained as a standalone 'patch-manager' package, so remove it from the tree. The command becomes a stub that tells people to run 'pip install patch-manager'. buildman still imports the shared modules commit and patchstream (along with their dependencies), so this series leaves those in place. It drops the tool's code, tests, CI hooks and packaging, and removes the in-tree documentation, moving the b4 contributor guide alongside the patman note in the patch-sending docs. It also adds a .patman-defaults file so the external tool is set up for U-Boot, next to the existing .b4-config. Where the CI jobs relied on patman's requirements for the setuptools that pylibfdt needs, they now install scripts/dtc/pylibfdt/requirements.txt instead. More could be done here: commit and patchstream (and their dependencies series, get_maintainer and settings) only remain because buildman still imports them. A follow-up could move those into u_boot_pylib (or buildman itself) and drop the rest, leaving tools/patman as just the stub. Link: https://lore.kernel.org/r/[email protected]
Diffstat (limited to 'tools/patman/patchwork.py')
-rw-r--r--tools/patman/patchwork.py852
1 files changed, 0 insertions, 852 deletions
diff --git a/tools/patman/patchwork.py b/tools/patman/patchwork.py
deleted file mode 100644
index d485648e467..00000000000
--- a/tools/patman/patchwork.py
+++ /dev/null
@@ -1,852 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0+
-#
-# Copyright 2025 Simon Glass <[email protected]>
-#
-"""Provides a basic API for the patchwork server
-"""
-
-import asyncio
-import re
-
-import aiohttp
-from collections import namedtuple
-
-from u_boot_pylib import terminal
-
-# Information passed to series_get_states()
-# link (str): Patchwork link for series
-# series_id (int): Series ID in database
-# series_name (str): Series name
-# version (int): Version number of series
-# show_comments (bool): True to show comments
-# show_cover_comments (bool): True to show cover-letter comments
-STATE_REQ = namedtuple(
- 'state_req',
- 'link,series_id,series_name,version,show_comments,show_cover_comments')
-
-# Responses from series_get_states()
-# int: ser_ver ID number
-# COVER: Cover-letter info
-# list of Patch: Information on each patch in the series
-# list of dict: patches, see get_series()['patches']
-STATE_RESP = namedtuple('state_resp', 'svid,cover,patches,patch_list')
-
-# Information about a cover-letter on patchwork
-# id (int): Patchwork ID of cover letter
-# state (str): Current state, e.g. 'accepted'
-# num_comments (int): Number of comments
-# name (str): Series name
-# comments (list of dict): Comments
-COVER = namedtuple('cover', 'id,num_comments,name,comments')
-
-# Number of retries
-RETRIES = 3
-
-# Max concurrent request
-MAX_CONCURRENT = 50
-
-# Patches which are part of a multi-patch series are shown with a prefix like
-# [prefix, version, sequence], for example '[RFC, v2, 3/5]'. All but the last
-# part is optional. This decodes the string into groups. For single patches
-# the [] part is not present:
-# Groups: (ignore, ignore, ignore, prefix, version, sequence, subject)
-RE_PATCH = re.compile(r'(\[(((.*),)?(.*),)?(.*)\]\s)?(.*)$')
-
-# This decodes the sequence string into a patch number and patch count
-RE_SEQ = re.compile(r'(\d+)/(\d+)')
-
-
-class Patch(dict):
- """Models a patch in patchwork
-
- This class records information obtained from patchwork
-
- Some of this information comes from the 'Patch' column:
-
- [RFC,v2,1/3] dm: Driver and uclass changes for tiny-dm
-
- This shows the prefix, version, seq, count and subject.
-
- The other properties come from other columns in the display.
-
- Properties:
- pid (str): ID of the patch (typically an integer)
- seq (int): Sequence number within series (1=first) parsed from sequence
- string
- count (int): Number of patches in series, parsed from sequence string
- raw_subject (str): Entire subject line, e.g.
- "[1/2,v2] efi_loader: Sort header file ordering"
- prefix (str): Prefix string or None (e.g. 'RFC')
- version (str): Version string or None (e.g. 'v2')
- raw_subject (str): Raw patch subject
- subject (str): Patch subject with [..] part removed (same as commit
- subject)
- data (dict or None): Patch data:
- """
- def __init__(self, pid, state=None, data=None, comments=None,
- series_data=None):
- super().__init__()
- self.id = pid # Use 'id' to match what the Rest API provides
- self.seq = None
- self.count = None
- self.prefix = None
- self.version = None
- self.raw_subject = None
- self.subject = None
- self.state = state
- self.data = data
- self.comments = comments
- self.series_data = series_data
- self.name = None
-
- # These make us more like a dictionary
- def __setattr__(self, name, value):
- self[name] = value
-
- def __getattr__(self, name):
- return self[name]
-
- def __hash__(self):
- return hash(frozenset(self.items()))
-
- def __str__(self):
- return self.raw_subject
-
- def parse_subject(self, raw_subject):
- """Parse the subject of a patch into its component parts
-
- See RE_PATCH for details. The parsed info is placed into seq, count,
- prefix, version, subject
-
- Args:
- raw_subject (str): Subject string to parse
-
- Raises:
- ValueError: the subject cannot be parsed
- """
- self.raw_subject = raw_subject.strip()
- mat = RE_PATCH.search(raw_subject.strip())
- if not mat:
- raise ValueError(f"Cannot parse subject '{raw_subject}'")
- self.prefix, self.version, seq_info, self.subject = mat.groups()[3:]
- mat_seq = RE_SEQ.match(seq_info) if seq_info else False
- if mat_seq is None:
- self.version = seq_info
- seq_info = None
- if self.version and not self.version.startswith('v'):
- self.prefix = self.version
- self.version = None
- if seq_info:
- if mat_seq:
- self.seq = int(mat_seq.group(1))
- self.count = int(mat_seq.group(2))
- else:
- self.seq = 1
- self.count = 1
-
-
-class Review:
- """Represents a single review email collected in Patchwork
-
- Patches can attract multiple reviews. Each consists of an author/date and
- a variable number of 'snippets', which are groups of quoted and unquoted
- text.
- """
- def __init__(self, meta, snippets):
- """Create new Review object
-
- Args:
- meta (str): Text containing review author and date
- snippets (list): List of snippets in th review, each a list of text
- lines
- """
- self.meta = ' : '.join([line for line in meta.splitlines() if line])
- self.snippets = snippets
-
-
-class Patchwork:
- """Class to handle communication with patchwork
- """
- def __init__(self, url, show_progress=True, single_thread=False):
- """Set up a new patchwork handler
-
- Args:
- url (str): URL of patchwork server, e.g.
- 'https://patchwork.ozlabs.org'
- """
- self.url = url
- self.fake_request = None
- self.proj_id = None
- self.link_name = None
- self._show_progress = show_progress
- self.semaphore = asyncio.Semaphore(
- 1 if single_thread else MAX_CONCURRENT)
- self.request_count = 0
-
- async def _request(self, client, subpath):
- """Call the patchwork API and return the result as JSON
-
- Args:
- client (aiohttp.ClientSession): Session to use
- subpath (str): URL subpath to use
-
- Returns:
- dict: Json result
-
- Raises:
- ValueError: the URL could not be read
- """
- # print('subpath', subpath)
- self.request_count += 1
- if self.fake_request:
- return self.fake_request(subpath)
-
- full_url = f'{self.url}/api/1.2/{subpath}'
- async with self.semaphore:
- # print('full_url', full_url)
- for i in range(RETRIES + 1):
- try:
- async with client.get(full_url) as response:
- if response.status != 200:
- raise ValueError(
- f"Could not read URL '{full_url}'")
- result = await response.json()
- # print('- done', full_url)
- return result
- break
- except aiohttp.client_exceptions.ServerDisconnectedError:
- if i == RETRIES:
- raise
-
- @staticmethod
- def for_testing(func):
- """Get an instance to use for testing
-
- Args:
- func (function): Function to call to handle requests. The function
- is passed a URL and is expected to return a dict with the
- resulting data
-
- Returns:
- Patchwork: testing instance
- """
- pwork = Patchwork(None, show_progress=False)
- pwork.fake_request = func
- return pwork
-
- class _Stats:
- def __init__(self, parent):
- self.parent = parent
- self.request_count = 0
-
- def __enter__(self):
- return self
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- self.request_count = self.parent.request_count
-
- def collect_stats(self):
- """Context manager to count requests across a range of patchwork calls
-
- Usage:
- pwork = Patchwork(...)
- with pwork.count_requests() as counter:
- pwork.something()
- print(f'{counter.count} requests')
- """
- self.request_count = 0
- return self._Stats(self)
-
- async def get_projects(self):
- """Get a list of projects on the server
-
- Returns:
- list of dict, one for each project
- 'name' (str): Project name, e.g. 'U-Boot'
- 'id' (int): Project ID, e.g. 9
- 'link_name' (str): Project's link-name, e.g. 'uboot'
- """
- async with aiohttp.ClientSession() as client:
- return await self._request(client, 'projects/')
-
- async def _query_series(self, client, desc):
- """Query series by name
-
- Args:
- client (aiohttp.ClientSession): Session to use
- desc: String to search for
-
- Return:
- list of series matches, each a dict, see get_series()
- """
- query = desc.replace(' ', '+')
- return await self._request(
- client, f'series/?project={self.proj_id}&q={query}')
-
- async def _find_series(self, client, svid, ser_id, version, ser):
- """Find a series on the server
-
- Args:
- client (aiohttp.ClientSession): Session to use
- svid (int): ser_ver ID
- ser_id (int): series ID
- version (int): Version number to search for
- ser (Series): Contains description (cover-letter title)
-
- Returns:
- tuple:
- int: ser_ver ID (as passed in)
- int: series ID (as passed in)
- str: Series link, or None if not found
- list of dict, or None if found
- each dict is the server result from a possible series
- """
- desc = ser.desc
- name_found = []
-
- # Do a series query on the description
- res = await self._query_series(client, desc)
- for pws in res:
- if pws['name'] == desc:
- if int(pws['version']) == version:
- return svid, ser_id, pws['id'], None
- name_found.append(pws)
-
- # When there is no cover letter, patchwork uses the first patch as the
- # series name
- cmt = ser.commits[0]
-
- res = await self._query_series(client, cmt.subject)
- for pws in res:
- patch = Patch(0)
- patch.parse_subject(pws['name'])
- if patch.subject == cmt.subject:
- if int(pws['version']) == version:
- return svid, ser_id, pws['id'], None
- name_found.append(pws)
-
- return svid, ser_id, None, name_found or res
-
- async def find_series(self, ser, version):
- """Find a series based on its description and version
-
- Args:
- ser (Series): Contains description (cover-letter title)
- version (int): Version number
-
- Return: tuple:
- tuple:
- str: Series ID, or None if not found
- list of dict, or None if found
- each dict is the server result from a possible series
- int: number of server requests done
- """
- async with aiohttp.ClientSession() as client:
- # We don't know the svid and it isn't needed, so use -1
- _, _, link, options = await self._find_series(client, -1, -1,
- version, ser)
- return link, options
-
- async def find_series_list(self, to_find):
- """Find the link for each series in a list
-
- Args:
- to_find (dict of svids to sync):
- key (int): ser_ver ID
- value (tuple):
- int: Series ID
- int: Series version
- str: Series link
- str: Series description
-
- Return: tuple:
- list of tuple, one for each item in to_find:
- int: ser_ver_ID
- int: series ID
- int: Series version
- str: Series link, or None if not found
- list of dict, or None if found
- each dict is the server result from a possible series
- int: number of server requests done
- """
- self.request_count = 0
- async with aiohttp.ClientSession() as client:
- tasks = [asyncio.create_task(
- self._find_series(client, svid, ser_id, version, desc))
- for svid, (ser_id, version, link, desc) in to_find.items()]
- results = await asyncio.gather(*tasks)
-
- return results, self.request_count
-
- def project_set(self, project_id, link_name):
- """Set the project ID
-
- The patchwork server has multiple projects. This allows the ID and
- link_name of the relevant project to be selected
-
- This function is used for testing
-
- Args:
- project_id (int): Project ID to use, e.g. 6
- link_name (str): Name to use for project URL links, e.g. 'uboot'
- """
- self.proj_id = project_id
- self.link_name = link_name
-
- async def get_series(self, client, link):
- """Read information about a series
-
- Args:
- client (aiohttp.ClientSession): Session to use
- link (str): Patchwork series ID
-
- Returns: dict containing patchwork's series information
- id (int): series ID unique across patchwork instance, e.g. 3
- url (str): Full URL, e.g.
- 'https://patchwork.ozlabs.org/api/1.2/series/3/'
- web_url (str): Full URL, e.g.
- 'https://patchwork.ozlabs.org/project/uboot/list/?series=3
- project (dict): project information (id, url, name, link_name,
- list_id, list_email, etc.
- name (str): Series name, e.g. '[U-Boot] moveconfig: fix error'
- date (str): Date, e.g. '2017-08-27T08:00:51'
- submitter (dict): id, url, name, email, e.g.:
- "id": 6125,
- "url": "https://patchwork.ozlabs.org/api/1.2/people/6125/",
- "name": "Chris Packham",
- "email": "[email protected]"
- version (int): Version number
- total (int): Total number of patches based on subject
- received_total (int): Total patches received by patchwork
- received_all (bool): True if all patches were received
- mbox (str): URL of mailbox, e.g.
- 'https://patchwork.ozlabs.org/series/3/mbox/'
- cover_letter (dict) or None, e.g.:
- "id": 806215,
- "url": "https://patchwork.ozlabs.org/api/1.2/covers/806215/",
- "web_url": "https://patchwork.ozlabs.org/project/uboot/cover/
- "msgid": "<[email protected]>",
- "list_archive_url": null,
- "date": "2017-08-27T09:44:07",
- "name": "[U-Boot,v2,0/4] usb: net: Migrate USB Ethernet",
- "mbox": "https://patchwork.ozlabs.org/project/uboot/cover/
- patches (list of dict), each e.g.:
- "id": 806202,
- "url": "https://patchwork.ozlabs.org/api/1.2/patches/806202/",
- "web_url": "https://patchwork.ozlabs.org/project/uboot/patch/
- "msgid": "<[email protected]>",
- "list_archive_url": null,
- "date": "2017-08-27T08:00:51",
- "name": "[U-Boot] moveconfig: fix error message do_autoconf()",
- "mbox": "https://patchwork.ozlabs.org/project/uboot/patch/
- """
- return await self._request(client, f'series/{link}/')
-
- async def get_patch(self, client, patch_id):
- """Read information about a patch
-
- Args:
- client (aiohttp.ClientSession): Session to use
- patch_id (str): Patchwork patch ID
-
- Returns: dict containing patchwork's patch information
- "id": 185,
- "url": "https://patchwork.ozlabs.org/api/1.2/patches/185/",
- "web_url": "https://patchwork.ozlabs.org/project/cbe-oss-dev/patch/
- project (dict): project information (id, url, name, link_name,
- list_id, list_email, etc.
- "msgid": "<[email protected]>",
- "list_archive_url": null,
- "date": "2008-09-05T07:16:27",
- "name": "powerpc/spufs: Fix possible scheduling of a context",
- "commit_ref": "b2e601d14deb2083e2a537b47869ab3895d23a28",
- "pull_url": null,
- "state": "accepted",
- "archived": false,
- "hash": "bc1c0b80d7cff66c0d1e5f3f8f4d10eb36176f0d",
- "submitter": {
- "id": 93,
- "url": "https://patchwork.ozlabs.org/api/1.2/people/93/",
- "name": "Andre Detsch",
- "email": "[email protected]"
- },
- "delegate": {
- "id": 1,
- "url": "https://patchwork.ozlabs.org/api/1.2/users/1/",
- "username": "jk",
- "first_name": "Jeremy",
- "last_name": "Kerr",
- "email": "[email protected]"
- },
- "mbox": "https://patchwork.ozlabs.org/project/cbe-oss-dev/patch/
- "series": [],
- "comments": "https://patchwork.ozlabs.org/api/patches/185/
- comments/",
- "check": "pending",
- "checks": "https://patchwork.ozlabs.org/api/patches/185/checks/",
- "tags": {},
- "related": [],
- "headers": {...}
- "content": "We currently have a race when scheduling a context
- after we have found a runnable context in spusched_tick, the
- context may have been scheduled by spu_activate().
-
- This may result in a panic if we try to unschedule a context
- been freed in the meantime.
-
- This change exits spu_schedule() if the context has already
- scheduled, so we don't end up scheduling it twice.
-
- Signed-off-by: Andre Detsch <[email protected]>",
- "diff": '''Index: spufs/arch/powerpc/platforms/cell/spufs/sched.c
- =======================================================
- --- spufs.orig/arch/powerpc/platforms/cell/spufs/sched.c
- +++ spufs/arch/powerpc/platforms/cell/spufs/sched.c
- @@ -727,7 +727,8 @@ static void spu_schedule(struct spu *spu
- \t/* not a candidate for interruptible because it's called
- \t from the scheduler thread or from spu_deactivate */
- \tmutex_lock(&ctx->state_mutex);
- -\t__spu_schedule(spu, ctx);
- +\tif (ctx->state == SPU_STATE_SAVED)
- +\t\t__spu_schedule(spu, ctx);
- \tspu_release(ctx);
- }
- '''
- "prefixes": ["3/3", ...]
- """
- return await self._request(client, f'patches/{patch_id}/')
-
- async def _get_patch_comments(self, client, patch_id):
- """Read comments about a patch
-
- Args:
- client (aiohttp.ClientSession): Session to use
- patch_id (str): Patchwork patch ID
-
- Returns: list of dict: list of comments:
- id (int): series ID unique across patchwork instance, e.g. 3331924
- web_url (str): Full URL, e.g.
- 'https://patchwork.ozlabs.org/comment/3331924/'
- msgid (str): Message ID, e.g.
- list_archive_url: (unknown?)
- date (str): Date, e.g. '2024-06-20T13:38:03'
- subject (str): email subject, e.g. 'Re: [PATCH 3/5] buildman:
- Support building within a Python venv'
- date (str): Date, e.g. '2017-08-27T08:00:51'
- submitter (dict): id, url, name, email, e.g.:
- "id": 61270,
- "url": "https://patchwork.ozlabs.org/api/people/61270/",
- "name": "Heinrich Schuchardt",
- "email": "[email protected]"
- content (str): Content of email, e.g. 'On 20.06.24 15:19,
- Simon Glass wrote:
- >...'
- headers: dict: email headers, see get_cover() for an example
- """
- return await self._request(client, f'patches/{patch_id}/comments/')
-
- async def get_cover(self, client, cover_id):
- """Read information about a cover letter
-
- Args:
- client (aiohttp.ClientSession): Session to use
- cover_id (int): Patchwork cover-letter ID
-
- Returns: dict containing patchwork's cover-letter information:
- id (int): series ID unique across patchwork instance, e.g. 3
- url (str): Full URL, e.g. https://patchwork.ozlabs.org/project/uboot/list/?series=3
- project (dict): project information (id, url, name, link_name,
- list_id, list_email, etc.
- url (str): Full URL, e.g. 'https://patchwork.ozlabs.org/api/1.2/covers/2054866/'
- web_url (str): Full URL, e.g. 'https://patchwork.ozlabs.org/project/uboot/cover/[email protected]/'
- project (dict): project information (id, url, name, link_name,
- list_id, list_email, etc.
- msgid (str): Message ID, e.g. '[email protected]>'
- list_archive_url (?)
- date (str): Date, e.g. '2017-08-27T08:00:51'
- name (str): Series name, e.g. '[U-Boot] moveconfig: fix error'
- submitter (dict): id, url, name, email, e.g.:
- "id": 6170,
- "url": "https://patchwork.ozlabs.org/api/1.2/people/6170/",
- "name": "Simon Glass",
- "email": "[email protected]"
- mbox (str): URL to mailbox, e.g. 'https://patchwork.ozlabs.org/project/uboot/cover/[email protected]/mbox/'
- series (list of dict) each e.g.:
- "id": 446956,
- "url": "https://patchwork.ozlabs.org/api/1.2/series/446956/",
- "web_url": "https://patchwork.ozlabs.org/project/uboot/list/?series=446956",
- "date": "2025-03-04T13:09:37",
- "name": "binman: Check code-coverage requirements",
- "version": 1,
- "mbox": "https://patchwork.ozlabs.org/series/446956/mbox/"
- comments: Web URL to comments: 'https://patchwork.ozlabs.org/api/covers/2054866/comments/'
- headers: dict: e.g.:
- "Return-Path": "<[email protected]>",
- "X-Original-To": "[email protected]",
- "Delivered-To": "[email protected]",
- "Authentication-Results": [
- "legolas.ozlabs.org;
-\tdkim=pass (1024-bit key;
- unprotected) header.d=chromium.org [email protected] header.a=rsa-sha256
- header.s=google header.b=dG8yqtoK;
-\tdkim-atps=neutral",
- "legolas.ozlabs.org;
- spf=pass (sender SPF authorized) smtp.mailfrom=lists.denx.de
- (client-ip=85.214.62.61; helo=phobos.denx.de;
- [email protected]; receiver=patchwork.ozlabs.org)",
- "phobos.denx.de;
- dmarc=pass (p=none dis=none) header.from=chromium.org",
- "phobos.denx.de;
- spf=pass [email protected]",
- "phobos.denx.de;
-\tdkim=pass (1024-bit key;
- unprotected) header.d=chromium.org [email protected]
- header.b=\"dG8yqtoK\";
-\tdkim-atps=neutral",
- "phobos.denx.de;
- dmarc=pass (p=none dis=none) header.from=chromium.org",
- "phobos.denx.de;
- spf=pass [email protected]"
- ],
- "Received": [
- "from phobos.denx.de (phobos.denx.de [85.214.62.61])
-\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
-\t key-exchange X25519 server-signature ECDSA (secp384r1))
-\t(No client certificate requested)
-\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4Z6bd50jLhz1yD0
-\tfor <[email protected]>; Wed, 5 Mar 2025 00:10:00 +1100 (AEDT)",
- "from h2850616.stratoserver.net (localhost [IPv6:::1])
-\tby phobos.denx.de (Postfix) with ESMTP id 434E88144A;
-\tTue, 4 Mar 2025 14:09:58 +0100 (CET)",
- "by phobos.denx.de (Postfix, from userid 109)
- id 8CBF98144A; Tue, 4 Mar 2025 14:09:57 +0100 (CET)",
- "from mail-io1-xd2e.google.com (mail-io1-xd2e.google.com
- [IPv6:2607:f8b0:4864:20::d2e])
- (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits))
- (No client certificate requested)
- by phobos.denx.de (Postfix) with ESMTPS id 48AE281426
- for <[email protected]>; Tue, 4 Mar 2025 14:09:55 +0100 (CET)",
- "by mail-io1-xd2e.google.com with SMTP id
- ca18e2360f4ac-85ae33109f6so128326139f.2
- for <[email protected]>; Tue, 04 Mar 2025 05:09:55 -0800 (PST)",
- "from chromium.org (c-73-203-119-151.hsd1.co.comcast.net.
- [73.203.119.151]) by smtp.gmail.com with ESMTPSA id
- ca18e2360f4ac-858753cd304sm287383839f.33.2025.03.04.05.09.49
- (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
- Tue, 04 Mar 2025 05:09:50 -0800 (PST)"
- ],
- "X-Spam-Checker-Version": "SpamAssassin 3.4.2 (2018-09-13) on phobos.denx.de",
- "X-Spam-Level": "",
- "X-Spam-Status": "No, score=-2.1 required=5.0 tests=BAYES_00,DKIMWL_WL_HIGH,
- DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,
- RCVD_IN_DNSWL_BLOCKED,SPF_HELO_NONE,SPF_PASS autolearn=ham
- autolearn_force=no version=3.4.2",
- "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;
- d=chromium.org; s=google; t=1741093792; x=1741698592; darn=lists.denx.de;
- h=content-transfer-encoding:mime-version:message-id:date:subject:cc
- :to:from:from:to:cc:subject:date:message-id:reply-to;
- bh=B2zsLws430/BEZfatNjeaNnrcxmYUstVjp1pSXgNQjc=;
- b=dG8yqtoKpSy15RHagnPcppzR8KbFCRXa2OBwXfwGoyN6M15tOJsUu2tpCdBFYiL5Mk
- hQz5iDLV8p0Bs+fP4XtNEx7KeYfTZhiqcRFvdCLwYtGray/IHtOZaNoHLajrstic/OgE
- 01ymu6gOEboU32eQ8uC8pdCYQ4UCkfKJwmiiU=",
- "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;
- d=1e100.net; s=20230601; t=1741093792; x=1741698592;
- h=content-transfer-encoding:mime-version:message-id:date:subject:cc
- :to:from:x-gm-message-state:from:to:cc:subject:date:message-id
- :reply-to;
- bh=B2zsLws430/BEZfatNjeaNnrcxmYUstVjp1pSXgNQjc=;
- b=eihzJf4i9gin9usvz4hnAvvbLV9/yB7hGPpwwW/amgnPUyWCeQstgvGL7WDLYYnukH
- 161p4mt7+cCj7Hao/jSPvVZeuKiBNPkS4YCuP3QjXfdk2ziQ9IjloVmGarWZUOlYJ5iQ
- dZnxypUkuFfLcEDSwUmRO1dvLi3nH8PDlae3yT2H87LeHaxhXWdzHxQdPc86rkYyCqCr
- qBC2CTS31jqSuiaI+7qB3glvbJbSEXkunz0iDewTJDvZfmuloxTipWUjRJ1mg9UJcZt5
- 9xIuTq1n9aYf1RcQlrEOQhdBAQ0/IJgvmZtzPZi9L+ppBva1ER/xm06nMA7GEUtyGwun
- c6pA==",
- "X-Gm-Message-State": "AOJu0Yybx3b1+yClf/IfIbQd9u8sxzK9ixPP2HimXF/dGZfSiS7Cb+O5
- WrAkvtp7m3KPM/Mpv0sSZ5qrfTnKnb3WZyv6Oe5Q1iUjAftGNwbSxob5eJ/0y3cgrTdzE4sIWPE
- =",
- "X-Gm-Gg": "ASbGncu5gtgpXEPGrpbTRJulqFrFj1YPAAmKk4MiXA8/3J1A+25F0Uug2KeFUrZEjkG
- KMdPg/C7e2emIvfM+Jl+mKv0ITBvhbyNCyY1q2U1s1cayZF05coZ9ewzGxXJGiEqLMG69uBmmIi
- rBEvCnkXS+HVZobDQMtOsezpc+Ju8JRA7+y1R0WIlutl1mQARct6p0zTkuZp75QyB6dm/d0KYgd
- iux/t/f0HC2CxstQlTlJYzKL6UJgkB5/UorY1lW/0NDRS6P1iemPQ7I3EPLJO8tM5ZrpJE7qgNP
- xy0jXbUv44c48qJ1VszfY5USB8fRG7nwUYxNu6N1PXv9xWbl+z2xL68qNYUrFlHsB8ILTXAyzyr
- Cdj+Sxg==",
- "X-Google-Smtp-Source": "
- AGHT+IFeVk5D4YEfJgPxOfg3ikO6Q7IhaDzABGkAPI6HA0ubK85OPhUHK08gV7enBQ8OdoE/ttqEjw==",
- "X-Received": "by 2002:a05:6602:640f:b0:855:63c8:abb5 with SMTP id
- ca18e2360f4ac-85881fdba3amr1839428939f.13.1741093792636;
- Tue, 04 Mar 2025 05:09:52 -0800 (PST)",
- "From": "Simon Glass <[email protected]>",
- "To": "U-Boot Mailing List <[email protected]>",
- "Cc": "Simon Glass <[email protected]>, Alexander Kochetkov <[email protected]>,
- Alper Nebi Yasak <[email protected]>,
- Brandon Maier <[email protected]>,
- Jerome Forissier <[email protected]>,
- Jiaxun Yang <[email protected]>,
- Neha Malcom Francis <[email protected]>,
- Patrick Rudolph <[email protected]>,
- Paul HENRYS <[email protected]>, Peng Fan <[email protected]>,
- Philippe Reynes <[email protected]>,
- Stefan Herbrechtsmeier <[email protected]>,
- Tom Rini <[email protected]>",
- "Subject": "[PATCH 0/7] binman: Check code-coverage requirements",
- "Date": "Tue, 4 Mar 2025 06:09:37 -0700",
- "Message-ID": "<[email protected]>",
- "X-Mailer": "git-send-email 2.43.0",
- "MIME-Version": "1.0",
- "Content-Transfer-Encoding": "8bit",
- "X-BeenThere": "[email protected]",
- "X-Mailman-Version": "2.1.39",
- "Precedence": "list",
- "List-Id": "U-Boot discussion <u-boot.lists.denx.de>",
- "List-Unsubscribe": "<https://lists.denx.de/options/u-boot>,
- <mailto:[email protected]?subject=unsubscribe>",
- "List-Archive": "<https://lists.denx.de/pipermail/u-boot/>",
- "List-Post": "<mailto:[email protected]>",
- "List-Help": "<mailto:[email protected]?subject=help>",
- "List-Subscribe": "<https://lists.denx.de/listinfo/u-boot>,
- <mailto:[email protected]?subject=subscribe>",
- "Errors-To": "[email protected]",
- "Sender": "\"U-Boot\" <[email protected]>",
- "X-Virus-Scanned": "clamav-milter 0.103.8 at phobos.denx.de",
- "X-Virus-Status": "Clean"
- content (str): Email content, e.g. 'This series adds a cover-coverage check to CI for Binman. The iMX8 tests
-are still not completed,...'
- """
- async with aiohttp.ClientSession() as client:
- return await self._request(client, f'covers/{cover_id}/')
-
- async def get_cover_comments(self, client, cover_id):
- """Read comments about a cover letter
-
- Args:
- client (aiohttp.ClientSession): Session to use
- cover_id (str): Patchwork cover-letter ID
-
- Returns: list of dict: list of comments, each:
- id (int): series ID unique across patchwork instance, e.g. 3472068
- web_url (str): Full URL, e.g. 'https://patchwork.ozlabs.org/comment/3472068/'
- list_archive_url: (unknown?)
-
- project (dict): project information (id, url, name, link_name,
- list_id, list_email, etc.
- url (str): Full URL, e.g. 'https://patchwork.ozlabs.org/api/1.2/covers/2054866/'
- web_url (str): Full URL, e.g. 'https://patchwork.ozlabs.org/project/uboot/cover/[email protected]/'
- project (dict): project information (id, url, name, link_name,
- list_id, list_email, etc.
- date (str): Date, e.g. '2025-03-04T13:16:15'
- subject (str): 'Re: [PATCH 0/7] binman: Check code-coverage requirements'
- submitter (dict): id, url, name, email, e.g.:
- "id": 6170,
- "url": "https://patchwork.ozlabs.org/api/people/6170/",
- "name": "Simon Glass",
- "email": "[email protected]"
- content (str): Email content, e.g. 'Hi,
-
-On Tue, 4 Mar 2025 at 06:09, Simon Glass <[email protected]> wrote:
->
-> This '...
- headers: dict: email headers, see get_cover() for an example
- """
- return await self._request(client, f'covers/{cover_id}/comments/')
-
- async def get_series_url(self, link):
- """Get the URL for a series
-
- Args:
- link (str): Patchwork series ID
-
- Returns:
- str: URL for the series page
- """
- return f'{self.url}/project/{self.link_name}/list/?series={link}&state=*&archive=both'
-
- async def _get_patch_status(self, client, patch_id):
- """Get the patch status
-
- Args:
- client (aiohttp.ClientSession): Session to use
- patch_id (int): Patch ID to look up in patchwork
-
- Return:
- PATCH: Patch information
-
- Requests:
- 1 for patch, 1 for patch comments
- """
- data = await self.get_patch(client, patch_id)
- state = data['state']
- comment_data = await self._get_patch_comments(client, patch_id)
-
- return Patch(patch_id, state, data, comment_data)
-
- async def get_series_cover(self, client, data):
- """Get the cover information (including comments)
-
- Args:
- client (aiohttp.ClientSession): Session to use
- data (dict): Return value from self.get_series()
-
- Returns:
- COVER object, or None if no cover letter
- """
- # Patchwork should always provide this, but use get() so that we don't
- # have to provide it in our fake patchwork _fake_patchwork_cser()
- cover = data.get('cover_letter')
- cover_id = None
- if cover:
- cover_id = cover['id']
- info = await self.get_cover_comments(client, cover_id)
- cover = COVER(cover_id, len(info), cover['name'], info)
- return cover
-
- async def series_get_state(self, client, link, read_comments,
- read_cover_comments):
- """Sync the series information against patchwork, to find patch status
-
- Args:
- client (aiohttp.ClientSession): Session to use
- link (str): Patchwork series ID
- read_comments (bool): True to read the comments on the patches
- read_cover_comments (bool): True to read the comments on the cover
- letter
-
- Return: tuple:
- COVER object, or None if none or not read_cover_comments
- list of PATCH objects
- """
- data = await self.get_series(client, link)
- patch_list = list(data['patches'])
-
- count = len(patch_list)
- patches = []
- if read_comments:
- # Returns a list of Patch objects
- tasks = [self._get_patch_status(client, patch_list[i]['id'])
- for i in range(count)]
-
- patch_status = await asyncio.gather(*tasks)
- for patch_data, status in zip(patch_list, patch_status):
- status.series_data = patch_data
- patches.append(status)
- else:
- for i in range(count):
- info = patch_list[i]
- pat = Patch(info['id'], series_data=info)
- pat.raw_subject = info['name']
- patches.append(pat)
- if self._show_progress:
- terminal.print_clear()
-
- if read_cover_comments:
- cover = await self.get_series_cover(client, data)
- else:
- cover = None
-
- return cover, patches