Peter Pentchev | b91fc93 | 2023-01-19 15:27:13 +0200 | [diff] [blame^] | 1 | # SPDX-FileCopyrightText: StorPool <support@storpool.com> |
| 2 | # SPDX-License-Identifier: BSD-2-Clause |
| 3 | """Parse a quilt patches file, then parse the patches themselves.""" |
| 4 | |
| 5 | from __future__ import annotations |
| 6 | |
| 7 | import dataclasses |
| 8 | import pathlib |
| 9 | import re |
| 10 | |
| 11 | from . import defs |
| 12 | |
| 13 | |
| 14 | _REPO_PARTS = 2 |
| 15 | |
| 16 | _RE_CHANGE_ID = re.compile(r"^ \s* Change-Id \s* : \s* (?P<value> I [0-9a-f]+ ) \s* $", re.X) |
| 17 | |
| 18 | _RE_DIFF_START = re.compile(r"^ --- [ ]", re.X) |
| 19 | |
| 20 | |
| 21 | class QuiltError(defs.GApplyError): |
| 22 | """An error that occurred while parsing the quilt patch structure.""" |
| 23 | |
| 24 | |
| 25 | @dataclasses.dataclass(frozen=True) |
| 26 | class Patch: |
| 27 | """A single patch read from the quilt series file.""" |
| 28 | |
| 29 | change_id: str |
| 30 | filename: str |
| 31 | path: pathlib.Path |
| 32 | relpath: pathlib.Path |
| 33 | repo: defs.Repo |
| 34 | |
| 35 | |
| 36 | def _extract_change_id(patchfile: pathlib.Path) -> str: |
| 37 | """Extract the value of the patch's Change-Id trailer.""" |
| 38 | change_id = None |
| 39 | for line in patchfile.read_text(encoding="UTF-8").splitlines(): |
| 40 | if _RE_DIFF_START.match(line): |
| 41 | if change_id is None: |
| 42 | raise defs.GApplyError(f"No Change-Id line found in {patchfile}") |
| 43 | |
| 44 | return change_id |
| 45 | |
| 46 | change = _RE_CHANGE_ID.match(line) |
| 47 | if change is not None: |
| 48 | change_id = change.group("value") |
| 49 | |
| 50 | raise defs.GApplyError(f"No diff start line ('--- ...') found in {patchfile}") |
| 51 | |
| 52 | |
| 53 | def parse_series(cfg: defs.Config) -> tuple[list[Patch], list[defs.Repo]]: |
| 54 | """Parse a series file, return a list of patches and a list of repository names.""" |
| 55 | repos = set() |
| 56 | |
| 57 | def parse_line(sline: str) -> Patch: |
| 58 | """Parse a single relative patch filename read from the series file.""" |
| 59 | fields = sline.split() |
| 60 | if len(fields) != 1: |
| 61 | raise NotImplementedError(f"quilt patch options not supported yet: {sline!r}") |
| 62 | filename = fields[0] |
| 63 | |
| 64 | relpath = pathlib.Path(filename) |
| 65 | if ( |
| 66 | relpath.is_absolute() |
| 67 | or len(relpath.parts) <= _REPO_PARTS |
| 68 | or any(part.startswith(".") for part in relpath.parts) |
| 69 | ): |
| 70 | raise QuiltError(f"Invalid patch filename {filename!r} in {cfg.series}") |
| 71 | |
| 72 | repo = defs.Repo(origin=relpath.parts[0], repo="/".join(relpath.parts[1:_REPO_PARTS])) |
| 73 | repos.add(repo) |
| 74 | |
| 75 | patchfile = cfg.patches / relpath |
| 76 | if not patchfile.is_file(): |
| 77 | raise QuiltError(f"Need a regular patch file at {patchfile}") |
| 78 | |
| 79 | change_id = _extract_change_id(patchfile) |
| 80 | return Patch( |
| 81 | change_id=change_id, filename=filename, path=patchfile, relpath=relpath, repo=repo |
| 82 | ) |
| 83 | |
| 84 | if not cfg.series.is_file(): |
| 85 | raise QuiltError(f"Need a regular series file at {cfg.series}") |
| 86 | |
| 87 | res = [parse_line(line) for line in cfg.series.read_text(encoding="UTF-8").splitlines()] |
| 88 | return res, sorted(repos) |