diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3c98fcfc..0d52f1e5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,7 @@ Release 0.12.0 (unreleased) * Show line number when manifest validation fails (#36) * Add Fuzzing (#819) * Don't allow NULL or control characters in manifest (#114) +* Allow multiple patches in manifest (#897) Release 0.11.0 (released 2026-01-03) ==================================== diff --git a/dfetch.yaml b/dfetch.yaml index a6a0605a..3bd895bf 100644 --- a/dfetch.yaml +++ b/dfetch.yaml @@ -20,4 +20,6 @@ manifest: repo-path: divi255/sphinxcontrib.asciinema.git dst: doc/_ext/sphinxcontrib_asciinema src: sphinxcontrib/asciinema - patch: doc/_ext/sphinxcontrib_asciinema.patch + patch: + - doc/_ext/patches/001-autoformat-sphinxcontrib.asciinema.patch + - doc/_ext/patches/002-fix-options-sphinxcontrib.asciinema.patch diff --git a/dfetch/__main__.py b/dfetch/__main__.py index 824cf355..0cc82c8b 100644 --- a/dfetch/__main__.py +++ b/dfetch/__main__.py @@ -65,7 +65,7 @@ def run(argv: Sequence[str]) -> None: try: args.func(args) - except RuntimeError as exc: + except (RuntimeError, TypeError) as exc: for msg in exc.args: logger.error(msg, stack_info=False) raise DfetchFatalException from exc diff --git a/dfetch/manifest/manifest.py b/dfetch/manifest/manifest.py index 089adb92..d80541c5 100644 --- a/dfetch/manifest/manifest.py +++ b/dfetch/manifest/manifest.py @@ -97,7 +97,9 @@ class ManifestDict(TypedDict, total=True): # pylint: disable=too-many-ancestors version: Union[int, str] remotes: NotRequired[Sequence[Union[RemoteDict, Remote]]] - projects: Sequence[Union[ProjectEntryDict, ProjectEntry, dict[str, str]]] + projects: Sequence[ + Union[ProjectEntryDict, ProjectEntry, dict[str, Union[str, list[str]]]] + ] class Manifest: @@ -138,12 +140,17 @@ def __init__( self._projects = self._init_projects(manifest["projects"]) def _init_projects( - self, projects: Sequence[Union[ProjectEntryDict, ProjectEntry, dict[str, str]]] + self, + projects: Sequence[ + Union[ProjectEntryDict, ProjectEntry, dict[str, Union[str, list[str]]]] + ], ) -> dict[str, ProjectEntry]: """Iterate over projects from manifest and initialize ProjectEntries from it. Args: - projects (Sequence[Union[ProjectEntryDict, ProjectEntry, Dict[str, str]]]): Iterable with projects + projects (Sequence[ + Union[ProjectEntryDict, ProjectEntry, Dict[str, Union[str, list[str]]]] + ]): Iterable with projects Raises: RuntimeError: Project unknown @@ -157,6 +164,10 @@ def _init_projects( if isinstance(project, dict): if "name" not in project: raise KeyError("Missing name!") + if not isinstance(project["name"], str): + raise TypeError( + f"Project name must be a string, got {type(project['name']).__name__}" + ) last_project = _projects[project["name"]] = ProjectEntry.from_yaml( project, self._default_remote_name ) @@ -295,9 +306,9 @@ def _as_dict(self) -> dict[str, ManifestDict]: if len(remotes) == 1: remotes[0].pop("default", None) - projects: list[dict[str, str]] = [] + projects: list[dict[str, Union[str, list[str]]]] = [] for project in self.projects: - project_yaml: dict[str, str] = project.as_yaml() + project_yaml: dict[str, Union[str, list[str]]] = project.as_yaml() if len(remotes) == 1: project_yaml.pop("remote", None) projects.append(project_yaml) diff --git a/dfetch/manifest/project.py b/dfetch/manifest/project.py index a5eea093..80ad1b92 100644 --- a/dfetch/manifest/project.py +++ b/dfetch/manifest/project.py @@ -278,6 +278,7 @@ from dfetch.manifest.remote import Remote from dfetch.manifest.version import Version +from dfetch.util.util import always_str_list, str_if_possible ProjectEntryDict = TypedDict( "ProjectEntryDict", @@ -288,7 +289,7 @@ "src": str, "dst": str, "url": str, - "patch": str, + "patch": Union[str, list[str]], "repo": str, "branch": str, "tag": str, @@ -316,7 +317,7 @@ def __init__(self, kwargs: ProjectEntryDict) -> None: self._src: str = kwargs.get("src", "") # noqa self._dst: str = kwargs.get("dst", self._name) self._url: str = kwargs.get("url", "") - self._patch: str = kwargs.get("patch", "") # noqa + self._patch: list[str] = always_str_list(kwargs.get("patch", [])) self._repo_path: str = kwargs.get("repo-path", "") self._branch: str = kwargs.get("branch", "") self._tag: str = kwargs.get("tag", "") @@ -329,7 +330,7 @@ def __init__(self, kwargs: ProjectEntryDict) -> None: @classmethod def from_yaml( cls, - yamldata: Union[dict[str, str], ProjectEntryDict], + yamldata: Union[dict[str, Union[str, list[str]]], ProjectEntryDict], default_remote: str = "", ) -> "ProjectEntry": """Create a Project Entry from yaml data. @@ -409,8 +410,8 @@ def destination(self) -> str: return self._dst @property - def patch(self) -> str: - """Get the patch that should be applied.""" + def patch(self) -> list[str]: + """Get the patches that should be applied.""" return self._patch @property @@ -451,14 +452,14 @@ def as_recommendation(self) -> "ProjectEntry": """Get a copy that can be used as recommendation.""" recommendation = self.copy(self) recommendation._dst = "" # pylint: disable=protected-access - recommendation._patch = "" # pylint: disable=protected-access + recommendation._patch = [] # pylint: disable=protected-access recommendation._url = self.remote_url # pylint: disable=protected-access recommendation._remote = "" # pylint: disable=protected-access recommendation._remote_obj = None # pylint: disable=protected-access recommendation._repo_path = "" # pylint: disable=protected-access return recommendation - def as_yaml(self) -> dict[str, str]: + def as_yaml(self) -> dict[str, Union[str, list[str]]]: """Get this project as yaml dictionary.""" yamldata = { "name": self._name, @@ -467,7 +468,7 @@ def as_yaml(self) -> dict[str, str]: "src": self._src, "dst": self._dst if self._dst != self._name else None, "url": self._url, - "patch": self._patch, + "patch": str_if_possible(self._patch), "branch": self._branch, "tag": self._tag, "repo-path": self._repo_path, diff --git a/dfetch/manifest/schema.py b/dfetch/manifest/schema.py index cb1040d7..823b63ce 100644 --- a/dfetch/manifest/schema.py +++ b/dfetch/manifest/schema.py @@ -25,7 +25,7 @@ Optional("url"): SAFE_STR, Optional("repo-path"): SAFE_STR, Optional("remote"): SAFE_STR, - Optional("patch"): SAFE_STR, + Optional("patch"): SAFE_STR | Seq(SAFE_STR), Optional("vcs"): Enum(["git", "svn"]), Optional("src"): SAFE_STR, Optional("ignore"): Seq(SAFE_STR), diff --git a/dfetch/project/metadata.py b/dfetch/project/metadata.py index b9b363cf..6c595021 100644 --- a/dfetch/project/metadata.py +++ b/dfetch/project/metadata.py @@ -2,12 +2,14 @@ import datetime import os +from typing import Optional, Union import yaml from typing_extensions import TypedDict from dfetch.manifest.project import ProjectEntry from dfetch.manifest.version import Version +from dfetch.util.util import always_str_list, str_if_possible DONT_EDIT_WARNING = """\ # This is a generated file by dfetch. Don't edit this, but edit the manifest. @@ -25,7 +27,7 @@ class Options(TypedDict): # pylint: disable=too-many-ancestors remote_url: str destination: str hash: str - patch: str + patch: Union[str, list[str]] class Metadata: @@ -49,7 +51,9 @@ def __init__(self, kwargs: Options) -> None: self._remote_url: str = str(kwargs.get("remote_url", "")) self._destination: str = str(kwargs.get("destination", "")) self._hash: str = str(kwargs.get("hash", "")) - self._patch: str = str(kwargs.get("patch", "")) + + # Historically only a single patch was allowed + self._patch: list[str] = always_str_list(kwargs.get("patch", [])) @classmethod def from_project_entry(cls, project: ProjectEntry) -> "Metadata": @@ -73,12 +77,14 @@ def from_file(cls, path: str) -> "Metadata": data: Options = yaml.safe_load(metadata_file)["dfetch"] return cls(data) - def fetched(self, version: Version, hash_: str = "", patch_: str = "") -> None: + def fetched( + self, version: Version, hash_: str = "", patch_: Optional[list[str]] = None + ) -> None: """Update metadata.""" self._last_fetch = datetime.datetime.now() self._version = version self._hash = hash_ - self._patch = patch_ + self._patch = patch_ or [] @property def version(self) -> Version: @@ -120,7 +126,7 @@ def hash(self) -> str: return self._hash @property - def patch(self) -> str: + def patch(self) -> list[str]: """The applied patch as stored in the metadata.""" return self._patch @@ -160,7 +166,7 @@ def dump(self) -> None: "last_fetch": self.last_fetch_string(), "tag": self._version.tag, "hash": self.hash, - "patch": self.patch, + "patch": str_if_possible(self.patch), } } diff --git a/dfetch/project/subproject.py b/dfetch/project/subproject.py index 9d7d7558..8a5e2869 100644 --- a/dfetch/project/subproject.py +++ b/dfetch/project/subproject.py @@ -128,33 +128,33 @@ def update( actually_fetched = self._fetch_impl(to_fetch) self._log_project(f"Fetched {actually_fetched}") - applied_patch = "" - if self.__project.patch: - if os.path.exists(self.__project.patch): - self.apply_patch() - applied_patch = self.__project.patch + applied_patches = [] + for patch in self.__project.patch: + if os.path.exists(patch): + self.apply_patch(patch) + applied_patches.append(patch) else: - logger.warning(f"Skipping non-existent patch {self.__project.patch}") + logger.warning(f"Skipping non-existent patch {patch}") self.__metadata.fetched( actually_fetched, hash_=hash_directory(self.local_path, skiplist=[self.__metadata.FILENAME]), - patch_=applied_patch, + patch_=applied_patches, ) logger.debug(f"Writing repo metadata to: {self.__metadata.path}") self.__metadata.dump() - def apply_patch(self) -> None: + def apply_patch(self, patch: str) -> None: """Apply the specified patch to the destination.""" - patch_set = fromfile(self.__project.patch) + patch_set = fromfile(patch) if not patch_set: - raise RuntimeError(f'Invalid patch file: "{self.__project.patch}"') + raise RuntimeError(f'Invalid patch file: "{patch}"') if patch_set.apply(0, root=self.local_path, fuzz=True): - self._log_project(f'Applied patch "{self.__project.patch}"') + self._log_project(f'Applied patch "{patch}"') else: - raise RuntimeError(f'Applying patch "{self.__project.patch}" failed') + raise RuntimeError(f'Applying patch "{patch}" failed') def check_for_update( self, reporters: Sequence[AbstractCheckReporter], files_to_ignore: Sequence[str] diff --git a/dfetch/reporting/stdout_reporter.py b/dfetch/reporting/stdout_reporter.py index e6a90df1..4982088c 100644 --- a/dfetch/reporting/stdout_reporter.py +++ b/dfetch/reporting/stdout_reporter.py @@ -35,7 +35,7 @@ def add_project( logger.print_info_field(" tag", metadata.tag) logger.print_info_field(" last fetch", str(metadata.last_fetch)) logger.print_info_field(" revision", metadata.revision) - logger.print_info_field(" patch", metadata.patch) + logger.print_info_field(" patch", ", ".join(metadata.patch)) logger.print_info_field( " licenses", ",".join(license.name for license in licenses) ) diff --git a/dfetch/util/util.py b/dfetch/util/util.py index 494a2717..91194571 100644 --- a/dfetch/util/util.py +++ b/dfetch/util/util.py @@ -134,3 +134,28 @@ def hash_file(file_path: str, digest: HASH) -> HASH: buf = f_obj.read(1024 * 1024) return digest + + +def always_str_list(data: Union[str, list[str]]) -> list[str]: + """Convert a string or list of strings into a list of strings. + + Args: + data: A string or list of strings. + + Returns: + A list of strings. Empty strings are converted to empty lists. + """ + return data if not isinstance(data, str) else [data] if data else [] + + +def str_if_possible(data: list[str]) -> Union[str, list[str]]: + """Convert a single-element list to a string, otherwise keep as list. + + Args: + data: A list of strings. + + Returns: + A single string if the list has exactly one element, an empty string + if the list is empty, otherwise the original list. + """ + return "" if not data else data[0] if len(data) == 1 else data diff --git a/doc/_ext/sphinxcontrib_asciinema.patch b/doc/_ext/patches/001-autoformat-sphinxcontrib.asciinema.patch similarity index 66% rename from doc/_ext/sphinxcontrib_asciinema.patch rename to doc/_ext/patches/001-autoformat-sphinxcontrib.asciinema.patch index f3c2fbf2..f738195a 100644 --- a/doc/_ext/sphinxcontrib_asciinema.patch +++ b/doc/_ext/patches/001-autoformat-sphinxcontrib.asciinema.patch @@ -1,5 +1,5 @@ diff --git a/asciinema.py b/asciinema.py -index 8644220..44dad26 100644 +index 8644220..0b73d80 100644 --- a/asciinema.py +++ b/asciinema.py @@ -2,20 +2,20 @@ import os @@ -29,39 +29,17 @@ index 8644220..44dad26 100644 class Asciinema(nodes.General, nodes.Element): -@@ -24,22 +24,37 @@ class Asciinema(nodes.General, nodes.Element): - - def visit_html(self, node): - rst_to_js_option_names: dict[str, str] = { -+ "autoplay": "autoPlay", -+ "idle-time-limit": "idleTimeLimit", - "terminalfontsize": "terminalFontSize", -- "terminallineheigth": "terminalLineHeigth", -+ "terminallineheight": "terminalLineHeight", - "terminalfontfamily": "terminalFontFamily", +@@ -30,16 +30,17 @@ def visit_html(self, node): "audiourl": "audioUrl", } - options_raw = ['markers'] -- ++ options_raw = ["markers"] + - gen = ((rst_option_name, js_option_name) - for (rst_option_name, - js_option_name) in rst_to_js_option_names.items() - if rst_option_name in node["options"]) -+ options_raw = [ -+ "markers", -+ "loop", -+ "autoPlay", -+ "preload", -+ "pauseOnMarkers", -+ "cols", -+ "rows", -+ "speed", -+ ] -+ -+ for option, value in node["options"].items(): -+ node["options"][option] = ASCIINemaDirective.option_spec[option](value) -+ + gen = ( + (rst_option_name, js_option_name) + for (rst_option_name, js_option_name) in rst_to_js_option_names.items() @@ -75,7 +53,7 @@ index 8644220..44dad26 100644 template = """
""" option_template = '{}: "{}", ' @@ -84,7 +62,7 @@ index 8644220..44dad26 100644 else: template = """""" -@@ -58,14 +73,17 @@ def visit_html(self, node): +@@ -58,14 +59,17 @@ def visit_html(self, node): option_template_raw = 'data-{}="{}" ' options = "" for n, v in node["options"].items(): @@ -105,55 +83,7 @@ index 8644220..44dad26 100644 raise nodes.SkipNode -@@ -73,15 +91,35 @@ def depart(self, node): - pass - - -+def bool_parse(argument): -+ """Parse the option as boolean.""" -+ if argument is None: -+ raise ValueError("Boolean option must have a value") -+ -+ val = str(argument).strip().lower() -+ -+ if val in ("true", "false"): -+ return val -+ raise ValueError("Must be boolean; True or False") -+ -+ -+def bool_or_positive_int(argument): -+ """Parse the option as boolean or positive integer.""" -+ try: -+ return bool_parse(argument) -+ except ValueError: -+ return directives.positive_int(argument) -+ -+ - class ASCIINemaDirective(SphinxDirective): - has_content = True - final_argument_whitespace = False - option_spec = { - "cols": directives.positive_int, - "rows": directives.positive_int, -- "autoplay": directives.unchanged, -- "preload": directives.unchanged, -- "loop": directives.unchanged, -+ "autoplay": bool_parse, -+ "preload": bool_parse, -+ "loop": bool_or_positive_int, - "start-at": directives.unchanged, - "speed": directives.unchanged, - "idle-time-limit": directives.unchanged, -@@ -90,7 +128,7 @@ class ASCIINemaDirective(SphinxDirective): - "fit": directives.unchanged, - "controls": directives.unchanged, - "markers": directives.unchanged, -- "pauseOnMarkers": directives.unchanged, -+ "pauseOnMarkers": bool_parse, - "terminalfontsize": directives.unchanged, - "terminalfontfamily": directives.unchanged, - "terminallineheight": directives.unchanged, -@@ -102,25 +140,25 @@ class ASCIINemaDirective(SphinxDirective): +@@ -102,25 +106,25 @@ class ASCIINemaDirective(SphinxDirective): def run(self): arg = self.arguments[0] @@ -195,7 +125,7 @@ index 8644220..44dad26 100644 return [Asciinema(**kw)] def is_file(self, rel_file): -@@ -129,17 +167,18 @@ class ASCIINemaDirective(SphinxDirective): +@@ -129,17 +133,18 @@ class ASCIINemaDirective(SphinxDirective): def to_b64(self, filename): import base64 diff --git a/doc/_ext/patches/002-fix-options-sphinxcontrib.asciinema.patch b/doc/_ext/patches/002-fix-options-sphinxcontrib.asciinema.patch new file mode 100644 index 00000000..f7ddcb51 --- /dev/null +++ b/doc/_ext/patches/002-fix-options-sphinxcontrib.asciinema.patch @@ -0,0 +1,82 @@ +diff --git a/asciinema.py b/asciinema.py +index 0b73d80..44dad26 100644 +--- a/asciinema.py ++++ b/asciinema.py +@@ -24,13 +24,27 @@ class Asciinema(nodes.General, nodes.Element): + + def visit_html(self, node): + rst_to_js_option_names: dict[str, str] = { ++ "autoplay": "autoPlay", ++ "idle-time-limit": "idleTimeLimit", + "terminalfontsize": "terminalFontSize", +- "terminallineheigth": "terminalLineHeigth", ++ "terminallineheight": "terminalLineHeight", + "terminalfontfamily": "terminalFontFamily", + "audiourl": "audioUrl", + } + +- options_raw = ["markers"] ++ options_raw = [ ++ "markers", ++ "loop", ++ "autoPlay", ++ "preload", ++ "pauseOnMarkers", ++ "cols", ++ "rows", ++ "speed", ++ ] ++ ++ for option, value in node["options"].items(): ++ node["options"][option] = ASCIINemaDirective.option_spec[option](value) + + gen = ( + (rst_option_name, js_option_name) +@@ -77,15 +91,35 @@ def depart(self, node): + pass + + ++def bool_parse(argument): ++ """Parse the option as boolean.""" ++ if argument is None: ++ raise ValueError("Boolean option must have a value") ++ ++ val = str(argument).strip().lower() ++ ++ if val in ("true", "false"): ++ return val ++ raise ValueError("Must be boolean; True or False") ++ ++ ++def bool_or_positive_int(argument): ++ """Parse the option as boolean or positive integer.""" ++ try: ++ return bool_parse(argument) ++ except ValueError: ++ return directives.positive_int(argument) ++ ++ + class ASCIINemaDirective(SphinxDirective): + has_content = True + final_argument_whitespace = False + option_spec = { + "cols": directives.positive_int, + "rows": directives.positive_int, +- "autoplay": directives.unchanged, +- "preload": directives.unchanged, +- "loop": directives.unchanged, ++ "autoplay": bool_parse, ++ "preload": bool_parse, ++ "loop": bool_or_positive_int, + "start-at": directives.unchanged, + "speed": directives.unchanged, + "idle-time-limit": directives.unchanged, +@@ -94,7 +128,7 @@ class ASCIINemaDirective(SphinxDirective): + "fit": directives.unchanged, + "controls": directives.unchanged, + "markers": directives.unchanged, +- "pauseOnMarkers": directives.unchanged, ++ "pauseOnMarkers": bool_parse, + "terminalfontsize": directives.unchanged, + "terminalfontfamily": directives.unchanged, + "terminallineheight": directives.unchanged, diff --git a/doc/_ext/sphinxcontrib_asciinema/.dfetch_data.yaml b/doc/_ext/sphinxcontrib_asciinema/.dfetch_data.yaml index eaab1db8..5c941f3c 100644 --- a/doc/_ext/sphinxcontrib_asciinema/.dfetch_data.yaml +++ b/doc/_ext/sphinxcontrib_asciinema/.dfetch_data.yaml @@ -2,9 +2,11 @@ # For more info see https://dfetch.rtfd.io/en/latest/getting_started.html dfetch: branch: master - hash: c26afa0279fa96392fd7f46d516060a4 - last_fetch: 21/12/2025, 13:39:42 - patch: doc/_ext/sphinxcontrib_asciinema.patch + hash: dcd1473e1a3ca613b804e3e51e7ee342 + last_fetch: 07/01/2026, 21:38:48 + patch: + - doc/_ext/patches/001-autoformat-sphinxcontrib.asciinema.patch + - doc/_ext/patches/002-fix-options-sphinxcontrib.asciinema.patch remote_url: https://github.com/divi255/sphinxcontrib.asciinema.git revision: 5ee0c5be62236a5dee0032e1d8dd59957a0c1d4c tag: '' diff --git a/features/list-projects.feature b/features/list-projects.feature index bc616a82..4cea3401 100644 --- a/features/list-projects.feature +++ b/features/list-projects.feature @@ -79,8 +79,8 @@ Feature: List dependencies licenses : """ - Scenario: Git repo with applied patch - Given MyProject with applied patch 'diff.patch' + Scenario: Git repo with applied patches + Given MyProject with applied patches "001-diff.patch, 002-diff.patch" When I run "dfetch report" Then the output shows """ @@ -92,6 +92,6 @@ Feature: List dependencies tag : v2.0 last fetch : 02/07/2021, 20:25:56 revision : - patch : diff.patch + patch : 001-diff.patch, 002-diff.patch licenses : MIT License """ diff --git a/features/patch-after-fetch-git.feature b/features/patch-after-fetch-git.feature index 4a0dae2c..61ab463a 100644 --- a/features/patch-after-fetch-git.feature +++ b/features/patch-after-fetch-git.feature @@ -74,3 +74,50 @@ Feature: Patch after fetching from git repo +++ b'README1.md' Applying patch "diff.patch" failed """ + + Scenario: Multiple patch files are applied after fetching + Given the manifest 'dfetch.yaml' + """ + manifest: + version: '0.0' + + remotes: + - name: github-com-dfetch-org + url-base: https://github.com/dfetch-org/test-repo + + projects: + - name: ext/test-repo-tag + tag: v2.0 + dst: ext/test-repo-tag + patch: + - 001-diff.patch + - 002-diff.patch + """ + And the patch file '001-diff.patch' + """ + diff --git a/README.md b/README.md + index 32d9fad..62248b7 100644 + --- a/README.md + +++ b/README.md + @@ -1,2 +1,2 @@ + # Test-repo + -A test repo for testing dfetch. + +A test repo for testing patch. + """ + And the patch file '002-diff.patch' + """ + diff --git a/README.md b/README.md + index 62248b7..32d9fad 100644 + --- a/README.md + +++ b/README.md + @@ -1,2 +1,2 @@ + # Test-repo + -A test repo for testing patch. + +A test repo for testing dfetch. + """ + When I run "dfetch update" + Then the patched 'ext/test-repo-tag/README.md' is + """ + # Test-repo + A test repo for testing dfetch. + """ diff --git a/features/steps/git_steps.py b/features/steps/git_steps.py index 71acdc73..4282a3d6 100644 --- a/features/steps/git_steps.py +++ b/features/steps/git_steps.py @@ -147,7 +147,7 @@ def step_impl(context, directory, path): commit_all("A change") -@given("MyProject with applied patch 'diff.patch'") +@given('MyProject with applied patches "001-diff.patch, 002-diff.patch"') def step_impl(context): manifest = """ manifest: @@ -161,7 +161,9 @@ def step_impl(context): - name: ext/test-repo-tag tag: v2.0 dst: ext/test-repo-tag - patch: diff.patch + patch: + - 001-diff.patch + - 002-diff.patch """ generate_manifest( @@ -169,7 +171,7 @@ def step_impl(context): "dfetch.yaml", contents=manifest, ) - patch_file = """ + patch_file1 = """ diff --git a/README.md b/README.md index 32d9fad..62248b7 100644 --- a/README.md @@ -179,5 +181,19 @@ def step_impl(context): -A test repo for testing dfetch. +A test repo for testing patch. """ - generate_file(os.path.join(os.getcwd(), "diff.patch"), patch_file) + + patch_file2 = """ +diff --git a/README.md b/README.md +index 62248b7..32d9fad 100644 +--- a/README.md ++++ b/README.md +@@ -1,2 +1,2 @@ + # Test-repo +-A test repo for testing patch. ++A test repo for testing dfetch. +""" + + generate_file(os.path.join(os.getcwd(), "001-diff.patch"), patch_file1) + generate_file(os.path.join(os.getcwd(), "002-diff.patch"), patch_file2) + call_command(context, ["update"]) diff --git a/tests/test_fuzzing.py b/tests/test_fuzzing.py index 1e34ad19..ce3a1e9c 100644 --- a/tests/test_fuzzing.py +++ b/tests/test_fuzzing.py @@ -107,7 +107,7 @@ def opt_str(): url=opt_str(), repo_path=opt_str(), remote=opt_str(), - patch=opt_str(), + patch=opt_str() | st.lists(SAFE_TEXT, min_size=1, max_size=5), vcs=st.none() | vcs_enum, src=opt_str(), ignore=ignore_list, @@ -190,7 +190,7 @@ def test_update(data): parsed = load(as_document(example, schema).as_yaml(), schema) print("\nRound-trip parsed .data:\n", parsed.data) - test_data_conforms_to_schema() - test_manifest_can_be_created() - test_check() - test_update() + test_data_conforms_to_schema() # pylint: disable=no-value-for-parameter + test_manifest_can_be_created() # pylint: disable=no-value-for-parameter + test_check() # pylint: disable=no-value-for-parameter + test_update() # pylint: disable=no-value-for-parameter diff --git a/tests/test_project_entry.py b/tests/test_project_entry.py index 50d22055..0b560691 100644 --- a/tests/test_project_entry.py +++ b/tests/test_project_entry.py @@ -32,10 +32,12 @@ def test_projectentry_vcs(): def test_projectentry_patch(): - assert ( - ProjectEntry({"name": "SomeProject", "patch": "diff.patch"}).patch - == "diff.patch" - ) + assert ProjectEntry({"name": "SomeProject", "patch": "diff.patch"}).patch == [ + "diff.patch" + ] + assert ProjectEntry({"name": "SomeProject", "patch": ["diff.patch"]}).patch == [ + "diff.patch" + ] def test_projectentry_as_yaml():