blob: b7bb8e77fe33a77eb0a7969025e8eaaf56105342 [file] [log] [blame]
# SPDX-FileCopyrightText: StorPool <support@storpool.com>
# SPDX-License-Identifier: BSD-2-Clause
"""Parse a quilt patches file, then parse the patches themselves."""
from __future__ import annotations
import dataclasses
import pathlib
import re
from . import defs
_REPO_PARTS = 2
_RE_CHANGE_ID = re.compile(r"^ \s* Change-Id \s* : \s* (?P<value> I [0-9a-f]+ ) \s* $", re.X)
_RE_DIFF_START = re.compile(r"^ --- [ ]", re.X)
class QuiltError(defs.GApplyError):
"""An error that occurred while parsing the quilt patch structure."""
@dataclasses.dataclass(frozen=True)
class Patch:
"""A single patch read from the quilt series file."""
change_id: str
filename: str
path: pathlib.Path
relpath: pathlib.Path
repo: defs.Repo
def _extract_change_id(patchfile: pathlib.Path) -> str:
"""Extract the value of the patch's Change-Id trailer."""
change_id = None
for line in patchfile.read_text(encoding="UTF-8").splitlines():
if _RE_DIFF_START.match(line):
if change_id is None:
raise defs.GApplyError(f"No Change-Id line found in {patchfile}")
return change_id
change = _RE_CHANGE_ID.match(line)
if change is not None:
change_id = change.group("value")
raise defs.GApplyError(f"No diff start line ('--- ...') found in {patchfile}")
def parse_series(cfg: defs.Config) -> tuple[list[Patch], list[defs.Repo]]:
"""Parse a series file, return a list of patches and a list of repository names."""
repos = set()
def parse_line(sline: str) -> Patch:
"""Parse a single relative patch filename read from the series file."""
fields = sline.split()
if len(fields) != 1:
raise NotImplementedError(f"quilt patch options not supported yet: {sline!r}")
filename = fields[0]
relpath = pathlib.Path(filename)
if (
relpath.is_absolute()
or len(relpath.parts) <= _REPO_PARTS
or any(part.startswith(".") for part in relpath.parts)
):
raise QuiltError(f"Invalid patch filename {filename!r} in {cfg.series}")
repo = defs.Repo(origin=relpath.parts[0], repo="/".join(relpath.parts[1:_REPO_PARTS]))
repos.add(repo)
patchfile = cfg.patches / relpath
if not patchfile.is_file():
raise QuiltError(f"Need a regular patch file at {patchfile}")
change_id = _extract_change_id(patchfile)
return Patch(
change_id=change_id,
filename=filename,
path=patchfile,
relpath=relpath,
repo=repo,
)
if not cfg.series.is_file():
raise QuiltError(f"Need a regular series file at {cfg.series}")
res = [parse_line(line) for line in cfg.series.read_text(encoding="UTF-8").splitlines()]
return res, sorted(repos)