blob: 3de9049be6861002ad92f29852a856364ebacc43 [file] [log] [blame]
Peter Pentchevb91fc932023-01-19 15:27:13 +02001# 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
5from __future__ import annotations
6
7import dataclasses
8import pathlib
9import re
10
11from . 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
21class QuiltError(defs.GApplyError):
22 """An error that occurred while parsing the quilt patch structure."""
23
24
25@dataclasses.dataclass(frozen=True)
26class 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
36def _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
53def 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)