From 22252da6b73e2badfdffb3ec8631fd68133c59f4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Jul 2022 22:07:12 +0000 Subject: [PATCH 1/8] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v2.32.1 → v2.37.1](https://github.com/asottile/pyupgrade/compare/v2.32.1...v2.37.1) - [github.com/psf/black: 22.3.0 → 22.6.0](https://github.com/psf/black/compare/22.3.0...22.6.0) - [github.com/pre-commit/mirrors-prettier: v2.6.2 → v2.7.1](https://github.com/pre-commit/mirrors-prettier/compare/v2.6.2...v2.7.1) - [github.com/pre-commit/pre-commit-hooks: v4.2.0 → v4.3.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.2.0...v4.3.0) - [github.com/pre-commit/mirrors-mypy: v0.960 → v0.961](https://github.com/pre-commit/mirrors-mypy/compare/v0.960...v0.961) --- .pre-commit-config.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8b79dcb..81b26cb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,19 +4,19 @@ repos: - repo: https://github.com/asottile/pyupgrade - rev: v2.32.1 + rev: v2.37.1 hooks: - id: pyupgrade - repo: https://github.com/psf/black - rev: 22.3.0 + rev: 22.6.0 hooks: - id: black - repo: https://github.com/pre-commit/mirrors-prettier - rev: v2.6.2 + rev: v2.7.1 hooks: - id: prettier - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.2.0 + rev: v4.3.0 hooks: - id: check-added-large-files - id: check-ast @@ -44,7 +44,7 @@ repos: - id: isort args: [--profile, black] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.960 + rev: v0.961 hooks: - id: mypy args: [--show-error-codes, --ignore-missing-imports] From 79e2e99718e760d5fcb6d920c148b46ce9c5963e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikola=20Forr=C3=B3?= Date: Sun, 17 Jul 2022 09:51:26 +0200 Subject: [PATCH 2/8] Fix deduplication of tag names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nikola Forró --- specfile/sources.py | 26 ++++++++++++++++--------- tests/unit/test_sources.py | 40 ++++++++++++++++++++++++++++++++++---- 2 files changed, 53 insertions(+), 13 deletions(-) diff --git a/specfile/sources.py b/specfile/sources.py index 796153e..69febbf 100644 --- a/specfile/sources.py +++ b/specfile/sources.py @@ -410,17 +410,25 @@ def _get_initial_tag_setup(self, number: int = 0) -> Tuple[int, str, str]: suffix = f"{number:0{self._default_source_number_digits}}" return len(self._tags) if self._tags else 0, f"{prefix}{suffix}", ": " - def _deduplicate_tag_names(self) -> None: - """Eliminates duplicate numbers in source tag names.""" + def _deduplicate_tag_names(self, start: int = 0) -> None: + """ + Eliminates duplicate numbers in source tag names. + + Args: + start: Starting index, defaults to the first source tag. + """ tags = self._get_tags() if not tags: return - tag_sources = sorted(list(zip(*tags))[0], key=lambda ts: ts.number) + tag_sources = list(zip(*tags[start:]))[0] for ts0, ts1 in zip(tag_sources, tag_sources[1:]): - if ts1.number <= ts0.number: - ts1._tag.name, ts1._tag._separator = self._get_tag_format( - ts0, ts0.number + 1 - ) + if ts1.number == ts0.number: + if ts1._number is not None: + ts1._number = ts0.number + 1 + else: + ts1._tag.name, ts1._tag._separator = self._get_tag_format( + ts1, ts0.number + 1 + ) def insert(self, i: int, location: str) -> None: """ @@ -453,7 +461,7 @@ def insert(self, i: int, location: str) -> None: index, Tag(name, location, Macros.expand(location), separator, Comments()), ) - self._deduplicate_tag_names() + self._deduplicate_tag_names(i) else: container.insert( index, @@ -501,7 +509,7 @@ def insert_numbered(self, number: int, location: str) -> int: self._tags.insert( index, Tag(name, location, Macros.expand(location), separator, Comments()) ) - self._deduplicate_tag_names() + self._deduplicate_tag_names(i) return i def remove(self, location: str) -> None: diff --git a/tests/unit/test_sources.py b/tests/unit/test_sources.py index 923b9a6..cea111f 100644 --- a/tests/unit/test_sources.py +++ b/tests/unit/test_sources.py @@ -109,11 +109,43 @@ def test_sources_get_initial_tag_setup(tags, number, index): @pytest.mark.parametrize( "tags, deduplicated_tags", [ - (["Source", "Source0"], ["Source", "Source1"]), - (["Source0028", "Source28"], ["Source0028", "Source0029"]), + (["Source", "Source"], ["Source", "Source"]), + (["Source0", "Source"], ["Source0", "Source"]), ( - ["Source2", "Source2", "Source3", "Source3"], - ["Source2", "Source3", "Source4", "Source5"], + ["Source0", "Source0", "Source", "Source"], + ["Source0", "Source1", "Source", "Source"], + ), + ( + ["Source100", "Source100", "Source", "Source1028", "Source1011"], + ["Source100", "Source101", "Source", "Source1028", "Source1011"], + ), + ( + ["Source2", "Source2", "Source4", "Source", "Source999"], + ["Source2", "Source3", "Source4", "Source", "Source999"], + ), + ( + ["Source3", "Source4", "Source", "Source5"], + ["Source3", "Source4", "Source", "Source6"], + ), + ( + [ + "Source", + "Source", + "Source100", + "Source101", + "Source101", + "Source102", + "Source", + ], + [ + "Source", + "Source", + "Source100", + "Source101", + "Source102", + "Source103", + "Source", + ], ), ], ) From 73d3dc8cf4968a99fec9ed3b0bd5056615d20aca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikola=20Forr=C3=B3?= Date: Wed, 20 Jul 2022 14:21:57 +0200 Subject: [PATCH 3/8] Allow to remove a source by number MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nikola Forró --- specfile/sourcelist.py | 5 +++ specfile/sources.py | 15 +++++++++ tests/unit/test_sources.py | 69 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+) diff --git a/specfile/sourcelist.py b/specfile/sourcelist.py index f0f347d..d51a2ad 100644 --- a/specfile/sourcelist.py +++ b/specfile/sourcelist.py @@ -23,6 +23,11 @@ def __init__(self, location: str, comments: Comments) -> None: self.location = location self.comments = comments.copy() + def __eq__(self, other: object) -> bool: + if not isinstance(other, SourcelistEntry): + return NotImplemented + return self.location == other.location and self.comments == other.comments + def __repr__(self) -> str: comments = repr(self.comments) return f"SourcelistEntry('{self.location}', {comments})" diff --git a/specfile/sources.py b/specfile/sources.py index 69febbf..594972d 100644 --- a/specfile/sources.py +++ b/specfile/sources.py @@ -523,6 +523,21 @@ def remove(self, location: str) -> None: if source.location == location: del container[index] + def remove_numbered(self, number: int) -> None: + """ + Removes a source by number. + + Args: + number: Number of the source to be removed. + """ + items = self._get_items() + try: + container, index = next((c, i) for s, c, i in items if s.number == number) + except StopIteration: + pass + else: + del container[index] + def count(self, location: str) -> int: """ Counts sources by location. diff --git a/tests/unit/test_sources.py b/tests/unit/test_sources.py index cea111f..35ed61c 100644 --- a/tests/unit/test_sources.py +++ b/tests/unit/test_sources.py @@ -342,6 +342,75 @@ def test_sources_insert_numbered(tags, number, location, index): assert sources[index].location == location +@pytest.mark.parametrize( + "tags, sourcelists, number, new_tags, new_sourcelists", + [ + ( + [ + ("Name", "test"), + ("Version", "0.1"), + ("Source0", "source0"), + ("Source1", "source1"), + ], + [], + 1, + [ + ("Name", "test"), + ("Version", "0.1"), + ("Source0", "source0"), + ], + [], + ), + ( + [ + ("Name", "test"), + ("Version", "0.1"), + ("Source0", "source0"), + ("Source1", "source1"), + ], + [], + 2, + [ + ("Name", "test"), + ("Version", "0.1"), + ("Source0", "source0"), + ("Source1", "source1"), + ], + [], + ), + ( + [ + ("Name", "test"), + ("Version", "0.1"), + ("Source0", "source0"), + ("Source1", "source1"), + ], + [["source2", "source3"]], + 2, + [ + ("Name", "test"), + ("Version", "0.1"), + ("Source0", "source0"), + ("Source1", "source1"), + ], + [["source3"]], + ), + ], +) +def test_sources_remove_numbered(tags, sourcelists, number, new_tags, new_sourcelists): + tags = Tags([Tag(t, v, v, ": ", Comments()) for t, v in tags]) + sourcelists = [ + Sourcelist([SourcelistEntry(s, Comments()) for s in sl]) for sl in sourcelists + ] + sources = Sources(tags, sourcelists) + sources.remove_numbered(number) + assert tags == Tags([Tag(t, v, v, ": ", Comments()) for t, v in new_tags]) + assert sourcelists == [ + Sourcelist([SourcelistEntry(s, Comments()) for s in sl]) + for sl in new_sourcelists + ] + + @pytest.mark.parametrize( "ref_name, ref_separator, number, name, separator", [ From f040a3f5aaab772b36346858b889ae93330defcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikola=20Forr=C3=B3?= Date: Thu, 21 Jul 2022 16:28:10 +0200 Subject: [PATCH 4/8] Raise more specific exception when adding a duplicate source MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nikola Forró --- specfile/exceptions.py | 6 ++++-- specfile/sources.py | 14 +++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/specfile/exceptions.py b/specfile/exceptions.py index cf66771..70d531b 100644 --- a/specfile/exceptions.py +++ b/specfile/exceptions.py @@ -7,8 +7,6 @@ class SpecfileException(Exception): """Something went wrong during our execution.""" - pass - class RPMException(SpecfileException): """Exception related to RPM.""" @@ -31,3 +29,7 @@ class MacroRemovalException(SpecfileException): class MacroOptionsException(SpecfileException): """Exception related to processing macro options.""" + + +class DuplicateSourceException(SpecfileException): + """Exception related to adding a duplicate source.""" diff --git a/specfile/sources.py b/specfile/sources.py index 594972d..a94eb5d 100644 --- a/specfile/sources.py +++ b/specfile/sources.py @@ -8,7 +8,7 @@ from pathlib import Path from typing import Iterable, List, Optional, Tuple, Union, cast, overload -from specfile.exceptions import SpecfileException +from specfile.exceptions import DuplicateSourceException from specfile.rpm import Macros from specfile.sourcelist import Sourcelist, SourcelistEntry from specfile.tags import Comments, Tag, Tags @@ -439,11 +439,11 @@ def insert(self, i: int, location: str) -> None: location: Location of the new source. Raises: - SpecfileException if duplicates are disallowed and there - already is a source with the same location. + DuplicateSourceException if duplicates are disallowed and there + already is a source with the same location. """ if not self._allow_duplicates and location in self: - raise SpecfileException(f"Source '{location}' already exists") + raise DuplicateSourceException(f"Source '{location}' already exists") items = self._get_items() if i > len(items): i = len(items) @@ -488,11 +488,11 @@ def insert_numbered(self, number: int, location: str) -> int: Index of the newly inserted source. Raises: - SpecfileException if duplicates are disallowed and there - already is a source with the same location. + DuplicateSourceException if duplicates are disallowed and there + already is a source with the same location. """ if not self._allow_duplicates and location in self: - raise SpecfileException(f"Source '{location}' already exists") + raise DuplicateSourceException(f"Source '{location}' already exists") tags = self._get_tags() if tags: # find the nearest source tag From b4c4c451970907748cadc455cded0d5b1aa4cd99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikola=20Forr=C3=B3?= Date: Sat, 16 Jul 2022 10:31:53 +0200 Subject: [PATCH 5/8] Use packit's way of expressing number of digits in a source number MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nikola Forró --- specfile/sources.py | 35 +++++++++++++++++++++++++---------- tests/unit/test_sources.py | 6 ++++-- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/specfile/sources.py b/specfile/sources.py index a94eb5d..445cb03 100644 --- a/specfile/sources.py +++ b/specfile/sources.py @@ -100,14 +100,25 @@ def _extract_number(self) -> Optional[str]: @property def number(self) -> int: """Source number.""" - return self._number or int(self._extract_number() or 0) + if self._number is not None: + return self._number + return int(self._extract_number() or 0) @property def number_digits(self) -> int: - """Number of digits in the source number.""" - if self._number: + """ + Gets number of digits in the source number. + + Returns 0 if the source has no number, 1 if the source number + has no leading zeros and the actual number of digits if there are + any leading zeros. + """ + if self._number is not None: return 0 - return len(self._extract_number() or "") + number = self._extract_number() + if not number: + return 0 + return len(number) if number.startswith("0") else 1 @property def location(self) -> str: @@ -378,13 +389,14 @@ def _get_tag_format( Tuple in the form of (name, separator). """ prefix = self.PREFIX.capitalize() - if self._detect_implicit_numbering(): + if number_digits_override is not None: + number_digits = number_digits_override + else: + number_digits = reference.number_digits + if self._detect_implicit_numbering() or number_digits == 0: suffix = "" else: - if number_digits_override is not None: - suffix = f"{number:0{number_digits_override}}" - else: - suffix = f"{number:0{reference.number_digits}}" + suffix = f"{number:0{number_digits}}" name = f"{prefix}{suffix}" diff = len(reference._tag.name) - len(name) if diff >= 0: @@ -404,7 +416,10 @@ def _get_initial_tag_setup(self, number: int = 0) -> Tuple[int, str, str]: Tuple in the form of (index, name, separator). """ prefix = self.PREFIX.capitalize() - if self._default_to_implicit_numbering: + if ( + self._default_to_implicit_numbering + or self._default_source_number_digits == 0 + ): suffix = "" else: suffix = f"{number:0{self._default_source_number_digits}}" diff --git a/tests/unit/test_sources.py b/tests/unit/test_sources.py index 35ed61c..65f6740 100644 --- a/tests/unit/test_sources.py +++ b/tests/unit/test_sources.py @@ -82,7 +82,8 @@ def test_sources_detect_implicit_numbering(tags, default, result): @pytest.mark.parametrize( "ref_name, ref_separator, number, name, separator", [ - ("Source", ": ", 28, "Source28", ":"), + ("Source", ": ", 28, "Source", ": "), + ("Source0", ": ", 28, "Source28", ":"), ("Source0001", ": ", 2, "Source0002", ": "), ], ) @@ -415,7 +416,8 @@ def test_sources_remove_numbered(tags, sourcelists, number, new_tags, new_source "ref_name, ref_separator, number, name, separator", [ ("Patch99", ": ", 100, "Patch100", ": "), - ("Patch9999", ": ", 28, "Patch0028", ": "), + ("Patch9999", ": ", 28, "Patch28", ": "), + ("Patch0999", ": ", 28, "Patch0028", ": "), ("Source2", ": ", 0, "Patch0", ": "), ], ) From d0bc9d94f87a54f89d7fc07fefe2ca41b953de69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikola=20Forr=C3=B3?= Date: Sat, 16 Jul 2022 10:33:59 +0200 Subject: [PATCH 6/8] Compress empty lines when deleting tags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nikola Forró --- specfile/tags.py | 26 +++++++++--- tests/constants.py | 1 + .../data/spec_commented_patches/patch0.patch | 18 ++++++++ .../data/spec_commented_patches/patch1.patch | 19 +++++++++ .../data/spec_commented_patches/patch2.patch | 20 +++++++++ .../data/spec_commented_patches/patch3.patch | 20 +++++++++ .../data/spec_commented_patches/patch4.patch | 20 +++++++++ .../data/spec_commented_patches/patch5.patch | 20 +++++++++ .../data/spec_commented_patches/patch6.patch | 20 +++++++++ .../spec_commented_patches/test-0.1.tar.xz | Bin 0 -> 200 bytes tests/data/spec_commented_patches/test.spec | 39 ++++++++++++++++++ tests/integration/conftest.py | 8 ++++ tests/integration/test_specfile.py | 19 +++++++++ 13 files changed, 225 insertions(+), 5 deletions(-) create mode 100644 tests/data/spec_commented_patches/patch0.patch create mode 100644 tests/data/spec_commented_patches/patch1.patch create mode 100644 tests/data/spec_commented_patches/patch2.patch create mode 100644 tests/data/spec_commented_patches/patch3.patch create mode 100644 tests/data/spec_commented_patches/patch4.patch create mode 100644 tests/data/spec_commented_patches/patch5.patch create mode 100644 tests/data/spec_commented_patches/patch6.patch create mode 100644 tests/data/spec_commented_patches/test-0.1.tar.xz create mode 100644 tests/data/spec_commented_patches/test.spec diff --git a/specfile/tags.py b/specfile/tags.py index 4059605..a3939eb 100644 --- a/specfile/tags.py +++ b/specfile/tags.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: MIT import collections +import itertools import re from typing import Any, Iterable, List, Optional, Union, overload @@ -340,14 +341,29 @@ def __getitem__(self, i): def __delitem__(self, i: Union[SupportsIndex, slice]) -> None: def delete(index): - preceding_lines = self.data[index].comments._preceding_lines.copy() + preceding_lines = self.data[index].comments._preceding_lines[:] del self.data[index] + # preserve preceding lines of the deleted tag but compress empty lines if index < len(self.data): - self.data[index].comments._preceding_lines = ( - preceding_lines + self.data[index].comments._preceding_lines - ) + lines = self.data[index].comments._preceding_lines else: - self._remainder = preceding_lines + self._remainder + lines = self._remainder + delimiter = [] + if preceding_lines and not preceding_lines[-1] or lines and not lines[0]: + delimiter.append("") + lines[:] = ( + list( + reversed( + list( + itertools.dropwhile( + lambda l: not l, reversed(preceding_lines) + ) + ) + ) + ) + + delimiter + + list(itertools.dropwhile(lambda l: not l, lines)) + ) if isinstance(i, slice): for index in reversed(range(len(self.data))[i]): diff --git a/tests/constants.py b/tests/constants.py index e2629f5..a6896f4 100644 --- a/tests/constants.py +++ b/tests/constants.py @@ -12,5 +12,6 @@ SPEC_AUTOPATCH = DATA_DIR / "spec_autopatch" SPEC_PATCHLIST = DATA_DIR / "spec_patchlist" SPEC_MULTIPLE_SOURCES = DATA_DIR / "spec_multiple_sources" +SPEC_COMMENTED_PATCHES = DATA_DIR / "spec_commented_patches" SPECFILE = "test.spec" diff --git a/tests/data/spec_commented_patches/patch0.patch b/tests/data/spec_commented_patches/patch0.patch new file mode 100644 index 0000000..3a016f7 --- /dev/null +++ b/tests/data/spec_commented_patches/patch0.patch @@ -0,0 +1,18 @@ +From e5e63915ae9cfb5a40bccbd53514f211b5c8e63b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Nikola=20Forr=C3=B3?= +Date: Wed, 16 Mar 2022 10:29:59 +0100 +Subject: [PATCH 1/7] patch0 + +--- + test.txt | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/test.txt b/test.txt +index 9daeafb..dec2cbe 100644 +--- a/test.txt ++++ b/test.txt +@@ -1 +1,2 @@ + test ++test +-- +2.35.1 diff --git a/tests/data/spec_commented_patches/patch1.patch b/tests/data/spec_commented_patches/patch1.patch new file mode 100644 index 0000000..3eb1641 --- /dev/null +++ b/tests/data/spec_commented_patches/patch1.patch @@ -0,0 +1,19 @@ +From 10cd6fe9acec1233c3456871f26b122df901d160 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Nikola=20Forr=C3=B3?= +Date: Wed, 16 Mar 2022 10:30:15 +0100 +Subject: [PATCH 2/7] patch1 + +--- + test.txt | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/test.txt b/test.txt +index dec2cbe..0867e73 100644 +--- a/test.txt ++++ b/test.txt +@@ -1,2 +1,3 @@ + test + test ++test +-- +2.35.1 diff --git a/tests/data/spec_commented_patches/patch2.patch b/tests/data/spec_commented_patches/patch2.patch new file mode 100644 index 0000000..4406da1 --- /dev/null +++ b/tests/data/spec_commented_patches/patch2.patch @@ -0,0 +1,20 @@ +From 015100d135c721e4b1e34e6e71e218b43143a310 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Nikola=20Forr=C3=B3?= +Date: Wed, 16 Mar 2022 10:30:29 +0100 +Subject: [PATCH 3/7] patch2 + +--- + test.txt | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/test.txt b/test.txt +index 0867e73..d0c7fbe 100644 +--- a/test.txt ++++ b/test.txt +@@ -1,3 +1,4 @@ + test + test + test ++test +-- +2.35.1 diff --git a/tests/data/spec_commented_patches/patch3.patch b/tests/data/spec_commented_patches/patch3.patch new file mode 100644 index 0000000..4a3a999 --- /dev/null +++ b/tests/data/spec_commented_patches/patch3.patch @@ -0,0 +1,20 @@ +From fb8a4a90040105bfead30585a89cec0798390ca7 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Nikola=20Forr=C3=B3?= +Date: Mon, 25 Apr 2022 12:39:30 +0200 +Subject: [PATCH 4/7] patch3 + +--- + test.txt | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/test.txt b/test.txt +index d0c7fbe..f762684 100644 +--- a/test.txt ++++ b/test.txt +@@ -2,3 +2,4 @@ test + test + test + test ++test +-- +2.35.1 diff --git a/tests/data/spec_commented_patches/patch4.patch b/tests/data/spec_commented_patches/patch4.patch new file mode 100644 index 0000000..5e59b40 --- /dev/null +++ b/tests/data/spec_commented_patches/patch4.patch @@ -0,0 +1,20 @@ +From ce488bdb437dc7da535235db0e3b3bfd7b1a7da4 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Nikola=20Forr=C3=B3?= +Date: Mon, 25 Apr 2022 12:39:37 +0200 +Subject: [PATCH 5/7] patch4 + +--- + test.txt | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/test.txt b/test.txt +index f762684..339c7a8 100644 +--- a/test.txt ++++ b/test.txt +@@ -3,3 +3,4 @@ test + test + test + test ++test +-- +2.35.1 diff --git a/tests/data/spec_commented_patches/patch5.patch b/tests/data/spec_commented_patches/patch5.patch new file mode 100644 index 0000000..2fd64e7 --- /dev/null +++ b/tests/data/spec_commented_patches/patch5.patch @@ -0,0 +1,20 @@ +From 2b6f99fdaa15c486e09350916a486e92706c5ad2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Nikola=20Forr=C3=B3?= +Date: Mon, 25 Apr 2022 12:39:42 +0200 +Subject: [PATCH 6/7] patch5 + +--- + test.txt | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/test.txt b/test.txt +index 339c7a8..0883796 100644 +--- a/test.txt ++++ b/test.txt +@@ -4,3 +4,4 @@ test + test + test + test ++test +-- +2.35.1 diff --git a/tests/data/spec_commented_patches/patch6.patch b/tests/data/spec_commented_patches/patch6.patch new file mode 100644 index 0000000..3227eb3 --- /dev/null +++ b/tests/data/spec_commented_patches/patch6.patch @@ -0,0 +1,20 @@ +From 9469faa96d3734e9dae635196763c67aa3eb5cbf Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Nikola=20Forr=C3=B3?= +Date: Mon, 25 Apr 2022 12:39:47 +0200 +Subject: [PATCH 7/7] patch6 + +--- + test.txt | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/test.txt b/test.txt +index 0883796..d5b79ce 100644 +--- a/test.txt ++++ b/test.txt +@@ -5,3 +5,4 @@ test + test + test + test ++test +-- +2.35.1 diff --git a/tests/data/spec_commented_patches/test-0.1.tar.xz b/tests/data/spec_commented_patches/test-0.1.tar.xz new file mode 100644 index 0000000000000000000000000000000000000000..d6cd06f12418b341ffa4f8a8199e8d6c29d1e35c GIT binary patch literal 200 zcmV;(05|{rH+ooF000E$*0e?f03iVu0001VFXf})C;tG4T>v^6O3od8%~Fm&LU2N! zuy&4uL5UJz@Itd(Oxtq-K1lz=GdP6O(ULn@#lwG2mvIrDmlzBR?s4@24M`C>xSnC_ zH0vva z@jYfqH7tfQj(^f4^UJse00029k+3M>;}(Db0iywcPyhg?23X*+#Ao{g000001X)^` ChEm)B literal 0 HcmV?d00001 diff --git a/tests/data/spec_commented_patches/test.spec b/tests/data/spec_commented_patches/test.spec new file mode 100644 index 0000000..5cb7ed4 --- /dev/null +++ b/tests/data/spec_commented_patches/test.spec @@ -0,0 +1,39 @@ +Name: test +Version: 0.1 +Release: 1%{?dist} +Summary: Test package + +License: MIT + +Source: %{name}-%{version}.tar.xz + +# this is a downstream-only patch +Patch0: patch0.patch + +# this is patch1 +Patch1: patch1.patch +# this is patch2 +Patch2: patch2.patch + +# these two patches are related to each other +Patch3: patch3.patch +Patch4: patch4.patch + +# this is patch5 +# it's a temporary workaround for some issue +Patch5: patch5.patch +# this is patch6 +Patch6: patch6.patch + + +%description +Test package + + +%prep +%autosetup -p1 + + +%changelog +* Thu Jun 07 2018 Nikola Forró - 0.1-1 +- first version diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index e4a264e..8095c25 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -8,6 +8,7 @@ from tests.constants import ( SPEC_AUTOPATCH, SPEC_AUTOSETUP, + SPEC_COMMENTED_PATCHES, SPEC_MINIMAL, SPEC_MULTIPLE_SOURCES, SPEC_PATCHLIST, @@ -64,3 +65,10 @@ def spec_multiple_sources(tmp_path): destination = tmp_path / "spec_multiple_sources" shutil.copytree(SPEC_MULTIPLE_SOURCES, destination) return destination / SPECFILE + + +@pytest.fixture(scope="function") +def spec_commented_patches(tmp_path): + destination = tmp_path / "spec_commented_patches" + shutil.copytree(SPEC_COMMENTED_PATCHES, destination) + return destination / SPECFILE diff --git a/tests/integration/test_specfile.py b/tests/integration/test_specfile.py index 7e496fd..0d7b789 100644 --- a/tests/integration/test_specfile.py +++ b/tests/integration/test_specfile.py @@ -220,6 +220,25 @@ def test_set_version_and_release(spec_minimal, version, release): assert spec._spec.sourceHeader[rpm.RPMTAG_RELEASE] == spec.expanded_raw_release +def test_remove_patches(spec_commented_patches): + spec = Specfile(spec_commented_patches) + with spec.patches() as patches: + del patches[1:3] + patches.remove_numbered(5) + with spec.sections() as sections: + assert sections.package[-11:-2] == [ + "# this is a downstream-only patch", + "Patch0: patch0.patch", + "", + "# these two patches are related to each other", + "Patch3: patch3.patch", + "Patch4: patch4.patch", + "", + "# this is patch6", + "Patch6: patch6.patch", + ] + + @pytest.mark.skipif( rpm.__version__ < "4.16", reason="%autochangelog requires rpm 4.16 or higher" ) From 9d8ab58b73bedc2b2ed31076affdd3e0e3620680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikola=20Forr=C3=B3?= Date: Sat, 16 Jul 2022 11:05:03 +0200 Subject: [PATCH 7/8] Add convenience property for getting texts of tag comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nikola Forró --- specfile/tags.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/specfile/tags.py b/specfile/tags.py index a3939eb..d919253 100644 --- a/specfile/tags.py +++ b/specfile/tags.py @@ -125,6 +125,11 @@ def __contains__(self, item: object) -> bool: return item in [c.text for c in self.data] return item in self.data + @property + def raw(self) -> List[str]: + """List of comment texts""" + return [c.text for c in self.data] + @overload def __getitem__(self, i: SupportsIndex) -> Comment: pass From ae65ce3acd5acaf081a6aa0d49d5cd2dcd5bf10a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikola=20Forr=C3=B3?= Date: Wed, 20 Jul 2022 10:09:25 +0200 Subject: [PATCH 8/8] Add convenience method for adding a patch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nikola Forró --- specfile/exceptions.py | 4 ++++ specfile/specfile.py | 38 +++++++++++++++++++++++++++++- tests/integration/test_specfile.py | 27 +++++++++++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/specfile/exceptions.py b/specfile/exceptions.py index 70d531b..35e0a89 100644 --- a/specfile/exceptions.py +++ b/specfile/exceptions.py @@ -33,3 +33,7 @@ class MacroOptionsException(SpecfileException): class DuplicateSourceException(SpecfileException): """Exception related to adding a duplicate source.""" + + +class SourceNumberException(SpecfileException): + """Exception related to source numbers.""" diff --git a/specfile/specfile.py b/specfile/specfile.py index 589c19b..2b367d8 100644 --- a/specfile/specfile.py +++ b/specfile/specfile.py @@ -12,7 +12,7 @@ import arrow from specfile.changelog import Changelog, ChangelogEntry -from specfile.exceptions import SpecfileException +from specfile.exceptions import SourceNumberException, SpecfileException from specfile.prep import Prep from specfile.rpm import RPM, Macros from specfile.sections import Sections @@ -416,3 +416,39 @@ def set_version_and_release(self, version: str, release: str = "1") -> None: with self.tags() as tags: tags.version.value = version tags.release.value = self._get_updated_release(tags.release.value, release) + + def add_patch( + self, + location: str, + number: Optional[int] = None, + comment: Optional[str] = None, + initial_number: int = 0, + number_digits: int = 4, + ) -> None: + """ + Adds a patch. + + Args: + location: Patch location (filename or URL). + number: Patch number. It will be auto-assigned if not specified. + If specified, it must be higher than any existing patch number. + comment: Associated comment. + initial_number: Auto-assigned number to start with if there are no patches. + number_digits: Number of digits in the patch number. + + Raises: + SourceNumberException when the specified patch number is not higher + than any existing patch number. + """ + with self.patches(default_source_number_digits=number_digits) as patches: + highest_number = max((p.number for p in patches), default=-1) + if number is not None: + if number <= highest_number: + raise SourceNumberException( + "Patch number must be higher than any existing patch number" + ) + else: + number = max(highest_number + 1, initial_number) + index = patches.insert_numbered(number, location) + if comment: + patches[index].comments.extend(comment.splitlines()) diff --git a/tests/integration/test_specfile.py b/tests/integration/test_specfile.py index 0d7b789..0a290c0 100644 --- a/tests/integration/test_specfile.py +++ b/tests/integration/test_specfile.py @@ -220,6 +220,33 @@ def test_set_version_and_release(spec_minimal, version, release): assert spec._spec.sourceHeader[rpm.RPMTAG_RELEASE] == spec.expanded_raw_release +@pytest.mark.parametrize( + "location, number, comment", + [ + ("patchX.patch", None, None), + ("patchX.patch", 0, None), + ("patch2.patch", None, None), + ("patch3.patch", 3, "patch3"), + ], +) +def test_add_patch(spec_autosetup, location, number, comment): + spec = Specfile(spec_autosetup) + if number == 0 or location == "patch2.patch": + with pytest.raises(SpecfileException): + spec.add_patch(location, number, comment) + else: + spec.add_patch(location, number, comment) + with spec.patches() as patches: + assert patches[-1].location == location + if number is not None: + assert patches[-1].number == number + else: + assert patches[-1].number == 3 + with spec.sections() as sections: + if comment is not None: + assert sections.package[-4] == f"# {comment}" + + def test_remove_patches(spec_commented_patches): spec = Specfile(spec_commented_patches) with spec.patches() as patches: