| # 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) |