gha-utils 4.18.0__tar.gz → 4.18.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of gha-utils might be problematic. Click here for more details.
- {gha_utils-4.18.0 → gha_utils-4.18.1}/PKG-INFO +1 -1
- {gha_utils-4.18.0 → gha_utils-4.18.1}/gha_utils/__init__.py +1 -1
- {gha_utils-4.18.0 → gha_utils-4.18.1}/gha_utils/cli.py +1 -1
- {gha_utils-4.18.0 → gha_utils-4.18.1}/gha_utils/matrix.py +3 -3
- {gha_utils-4.18.0 → gha_utils-4.18.1}/gha_utils/metadata.py +67 -10
- {gha_utils-4.18.0 → gha_utils-4.18.1}/gha_utils.egg-info/PKG-INFO +1 -1
- {gha_utils-4.18.0 → gha_utils-4.18.1}/pyproject.toml +3 -3
- {gha_utils-4.18.0 → gha_utils-4.18.1}/tests/test_metadata.py +12 -8
- {gha_utils-4.18.0 → gha_utils-4.18.1}/gha_utils/__main__.py +0 -0
- {gha_utils-4.18.0 → gha_utils-4.18.1}/gha_utils/changelog.py +0 -0
- {gha_utils-4.18.0 → gha_utils-4.18.1}/gha_utils/mailmap.py +0 -0
- {gha_utils-4.18.0 → gha_utils-4.18.1}/gha_utils/py.typed +0 -0
- {gha_utils-4.18.0 → gha_utils-4.18.1}/gha_utils/test_plan.py +0 -0
- {gha_utils-4.18.0 → gha_utils-4.18.1}/gha_utils.egg-info/SOURCES.txt +0 -0
- {gha_utils-4.18.0 → gha_utils-4.18.1}/gha_utils.egg-info/dependency_links.txt +0 -0
- {gha_utils-4.18.0 → gha_utils-4.18.1}/gha_utils.egg-info/entry_points.txt +0 -0
- {gha_utils-4.18.0 → gha_utils-4.18.1}/gha_utils.egg-info/requires.txt +0 -0
- {gha_utils-4.18.0 → gha_utils-4.18.1}/gha_utils.egg-info/top_level.txt +0 -0
- {gha_utils-4.18.0 → gha_utils-4.18.1}/readme.md +0 -0
- {gha_utils-4.18.0 → gha_utils-4.18.1}/setup.cfg +0 -0
- {gha_utils-4.18.0 → gha_utils-4.18.1}/tests/test_changelog.py +0 -0
- {gha_utils-4.18.0 → gha_utils-4.18.1}/tests/test_mailmap.py +0 -0
- {gha_utils-4.18.0 → gha_utils-4.18.1}/tests/test_matrix.py +0 -0
|
@@ -116,7 +116,7 @@ def gha_utils():
|
|
|
116
116
|
)
|
|
117
117
|
@option(
|
|
118
118
|
"--format",
|
|
119
|
-
type=Choice(Dialects, case_sensitive=False),
|
|
119
|
+
type=Choice(Dialects, case_sensitive=False), # type: ignore[arg-type]
|
|
120
120
|
default=Dialects.github,
|
|
121
121
|
help="Rendering format of the metadata.",
|
|
122
122
|
)
|
|
@@ -48,7 +48,7 @@ class Matrix:
|
|
|
48
48
|
"""
|
|
49
49
|
|
|
50
50
|
def __init__(self, *args, **kwargs):
|
|
51
|
-
self.variations: dict = {}
|
|
51
|
+
self.variations: dict[str, tuple[str, ...]] = {}
|
|
52
52
|
|
|
53
53
|
# Tuples are used to keep track of the insertion order and force immutability.
|
|
54
54
|
self.include: tuple[dict[str, str], ...] = tuple()
|
|
@@ -66,9 +66,9 @@ class Matrix:
|
|
|
66
66
|
"""
|
|
67
67
|
dict_copy = self.variations.copy()
|
|
68
68
|
if not ignore_includes and self.include:
|
|
69
|
-
dict_copy["include"] = self.include
|
|
69
|
+
dict_copy["include"] = self.include # type: ignore[assignment]
|
|
70
70
|
if not ignore_excludes and self.exclude:
|
|
71
|
-
dict_copy["exclude"] = self.exclude
|
|
71
|
+
dict_copy["exclude"] = self.exclude # type: ignore[assignment]
|
|
72
72
|
return FrozenDict(dict_copy)
|
|
73
73
|
|
|
74
74
|
def __repr__(self) -> str:
|
|
@@ -156,6 +156,7 @@ import json
|
|
|
156
156
|
import logging
|
|
157
157
|
import os
|
|
158
158
|
import re
|
|
159
|
+
import sys
|
|
159
160
|
import tomllib
|
|
160
161
|
from collections.abc import Iterable
|
|
161
162
|
from enum import StrEnum
|
|
@@ -166,11 +167,11 @@ from random import randint
|
|
|
166
167
|
from re import escape
|
|
167
168
|
from typing import Any, Final, cast
|
|
168
169
|
|
|
170
|
+
import gitignore_parser
|
|
169
171
|
from bumpversion.config import get_configuration # type: ignore[import-untyped]
|
|
170
172
|
from bumpversion.config.files import find_config_file # type: ignore[import-untyped]
|
|
171
173
|
from bumpversion.show import resolve_name # type: ignore[import-untyped]
|
|
172
174
|
from extra_platforms import is_github_ci
|
|
173
|
-
from gitignore_parser import parse_gitignore
|
|
174
175
|
from packaging.specifiers import SpecifierSet
|
|
175
176
|
from packaging.version import Version
|
|
176
177
|
from pydriller import Commit, Git, Repository # type: ignore[import-untyped]
|
|
@@ -349,10 +350,66 @@ MYPY_VERSION_MIN: Final = (3, 8)
|
|
|
349
350
|
"""
|
|
350
351
|
|
|
351
352
|
|
|
353
|
+
# XXX Patch gitignore-parser to support Windows paths. Refs:
|
|
354
|
+
# https://github.com/mherrmann/gitignore_parser/issues/60
|
|
355
|
+
# https://github.com/mherrmann/gitignore_parser/pull/61
|
|
356
|
+
# XXX In the future, replace this with wcmatch once it supports gitignore files:
|
|
357
|
+
# https://github.com/facelessuser/wcmatch/issues/226
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
_OriginalIgnoreRule = gitignore_parser.IgnoreRule
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
class PatchedIgnoreRule(_OriginalIgnoreRule): # type: ignore[misc,valid-type]
|
|
364
|
+
"""Patch version of ``IgnoreRule`` to support Windows paths.
|
|
365
|
+
|
|
366
|
+
Taken from: https://github.com/mherrmann/gitignore_parser/pull/61/files
|
|
367
|
+
"""
|
|
368
|
+
|
|
369
|
+
@staticmethod
|
|
370
|
+
def _count_trailing_symbol(symbol: str, text: str) -> int:
|
|
371
|
+
"""Count the number of trailing characters in a string."""
|
|
372
|
+
count = 0
|
|
373
|
+
for char in reversed(str(text)):
|
|
374
|
+
if char == symbol:
|
|
375
|
+
count += 1
|
|
376
|
+
else:
|
|
377
|
+
break
|
|
378
|
+
return count
|
|
379
|
+
|
|
380
|
+
def match(self, abs_path: str | Path) -> bool:
|
|
381
|
+
matched = False
|
|
382
|
+
if self.base_path:
|
|
383
|
+
rel_path = (
|
|
384
|
+
gitignore_parser._normalize_path(abs_path)
|
|
385
|
+
.relative_to(self.base_path)
|
|
386
|
+
.as_posix()
|
|
387
|
+
)
|
|
388
|
+
else:
|
|
389
|
+
rel_path = gitignore_parser._normalize_path(abs_path).as_posix()
|
|
390
|
+
# Path() strips the trailing following symbols on windows, so we need to
|
|
391
|
+
# preserve it: ' ', '.'
|
|
392
|
+
if sys.platform.startswith("win"):
|
|
393
|
+
rel_path += " " * self._count_trailing_symbol(" ", abs_path)
|
|
394
|
+
rel_path += "." * self._count_trailing_symbol(".", abs_path)
|
|
395
|
+
# Path() strips the trailing slash, so we need to preserve it
|
|
396
|
+
# in case of directory-only negation
|
|
397
|
+
if self.negation and type(abs_path) is str and abs_path[-1] == "/":
|
|
398
|
+
rel_path += "/"
|
|
399
|
+
if rel_path.startswith("./"):
|
|
400
|
+
rel_path = rel_path[2:]
|
|
401
|
+
if re.search(self.regex, rel_path):
|
|
402
|
+
matched = True
|
|
403
|
+
return matched
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
gitignore_parser.IgnoreRule = PatchedIgnoreRule
|
|
407
|
+
|
|
408
|
+
|
|
352
409
|
class JSONMetadata(json.JSONEncoder):
|
|
353
410
|
"""Custom JSON encoder for metadata serialization."""
|
|
354
411
|
|
|
355
|
-
def default(self, o: Any) ->
|
|
412
|
+
def default(self, o: Any) -> Any:
|
|
356
413
|
if isinstance(o, Matrix):
|
|
357
414
|
return o.matrix()
|
|
358
415
|
|
|
@@ -592,11 +649,15 @@ class Metadata:
|
|
|
592
649
|
events.
|
|
593
650
|
|
|
594
651
|
.. seealso::
|
|
595
|
-
|
|
596
652
|
- https://stackoverflow.com/a/67204539
|
|
597
653
|
- https://stackoverflow.com/a/62953566
|
|
598
654
|
- https://stackoverflow.com/a/61861763
|
|
599
655
|
|
|
656
|
+
.. seealso::
|
|
657
|
+
Pull request events on GitHub are a bit complex, see: `The Many SHAs of a
|
|
658
|
+
GitHub Pull Request
|
|
659
|
+
<https://www.kenmuse.com/blog/the-many-shas-of-a-github-pull-request/>`_.
|
|
660
|
+
|
|
600
661
|
.. todo::
|
|
601
662
|
Refactor so we can get rid of ``self.github_context``. Maybe there's enough
|
|
602
663
|
metadata lying around in the environment variables that we can inspect the
|
|
@@ -657,11 +718,7 @@ class Metadata:
|
|
|
657
718
|
@cached_property
|
|
658
719
|
def new_commits_hash(self) -> tuple[str, ...] | None:
|
|
659
720
|
"""List all hashes of new commits."""
|
|
660
|
-
return
|
|
661
|
-
cast(tuple[str, ...], self.new_commits_matrix["commit"])
|
|
662
|
-
if self.new_commits_matrix
|
|
663
|
-
else None
|
|
664
|
-
)
|
|
721
|
+
return self.new_commits_matrix["commit"] if self.new_commits_matrix else None
|
|
665
722
|
|
|
666
723
|
@cached_property
|
|
667
724
|
def release_commits(self) -> tuple[Commit, ...] | None:
|
|
@@ -695,7 +752,7 @@ class Metadata:
|
|
|
695
752
|
def release_commits_hash(self) -> tuple[str, ...] | None:
|
|
696
753
|
"""List all hashes of release commits."""
|
|
697
754
|
return (
|
|
698
|
-
|
|
755
|
+
self.release_commits_matrix["commit"]
|
|
699
756
|
if self.release_commits_matrix
|
|
700
757
|
else None
|
|
701
758
|
)
|
|
@@ -737,7 +794,7 @@ class Metadata:
|
|
|
737
794
|
gitignore = None
|
|
738
795
|
if self.gitignore_exists:
|
|
739
796
|
logging.debug(f"Load {GITIGNORE_PATH} to filter out ignored files.")
|
|
740
|
-
gitignore = parse_gitignore(GITIGNORE_PATH)
|
|
797
|
+
gitignore = gitignore_parser.parse_gitignore(GITIGNORE_PATH)
|
|
741
798
|
|
|
742
799
|
for file_path in iglob(
|
|
743
800
|
patterns,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
# Docs: https://packaging.python.org/en/latest/guides/writing-pyproject-toml/
|
|
3
3
|
name = "gha-utils"
|
|
4
|
-
version = "4.18.
|
|
4
|
+
version = "4.18.1"
|
|
5
5
|
# Python versions and their status: https://devguide.python.org/versions/
|
|
6
6
|
requires-python = ">= 3.11"
|
|
7
7
|
description = "⚙️ CLI helpers for GitHub Actions + reuseable workflows"
|
|
@@ -120,7 +120,7 @@ pretty = true
|
|
|
120
120
|
|
|
121
121
|
[[tool.mypy.overrides]]
|
|
122
122
|
ignore_missing_imports = true
|
|
123
|
-
module = ["boltons.*"]
|
|
123
|
+
module = ["boltons.*", "gitignore_parser.*"]
|
|
124
124
|
|
|
125
125
|
[tool.pytest.ini_options]
|
|
126
126
|
# https://docs.pytest.org/en/latest/customize.html#pyproject-toml
|
|
@@ -138,7 +138,7 @@ addopts = [
|
|
|
138
138
|
xfail_strict = true
|
|
139
139
|
|
|
140
140
|
[tool.bumpversion]
|
|
141
|
-
current_version = "4.18.
|
|
141
|
+
current_version = "4.18.1"
|
|
142
142
|
allow_dirty = true
|
|
143
143
|
ignore_missing_files = true
|
|
144
144
|
|
|
@@ -58,27 +58,31 @@ def regex(pattern: str) -> re.Pattern:
|
|
|
58
58
|
return re.compile(pattern, re.DOTALL)
|
|
59
59
|
|
|
60
60
|
|
|
61
|
-
def iter_checks(metadata: Any, expected: Any) -> None:
|
|
61
|
+
def iter_checks(metadata: Any, expected: Any, context: Any) -> None:
|
|
62
62
|
"""Recursively iterate over expected content and check it matches in metadata."""
|
|
63
63
|
|
|
64
64
|
if isinstance(expected, re.Pattern):
|
|
65
65
|
assert isinstance(metadata, str)
|
|
66
|
-
assert re.fullmatch(expected, metadata) is not None
|
|
66
|
+
assert re.fullmatch(expected, metadata) is not None, (
|
|
67
|
+
f"{metadata!r} does not match {expected.pattern!r} in {context!r}"
|
|
68
|
+
)
|
|
67
69
|
|
|
68
70
|
elif isinstance(expected, dict):
|
|
69
71
|
assert isinstance(metadata, dict)
|
|
70
72
|
assert set(metadata) == set(expected)
|
|
71
73
|
for key, value in expected.items():
|
|
72
|
-
iter_checks(metadata[key], value)
|
|
74
|
+
iter_checks(metadata[key], value, metadata)
|
|
73
75
|
|
|
74
76
|
elif isinstance(expected, list):
|
|
75
77
|
assert isinstance(metadata, list)
|
|
76
78
|
assert len(metadata) == len(expected)
|
|
77
79
|
for item in expected:
|
|
78
|
-
iter_checks(metadata[expected.index(item)], item)
|
|
80
|
+
iter_checks(metadata[expected.index(item)], item, metadata)
|
|
79
81
|
|
|
80
82
|
else:
|
|
81
|
-
assert metadata == expected
|
|
83
|
+
assert metadata == expected, (
|
|
84
|
+
f"{metadata!r} does not match {expected!r} in {context!r}"
|
|
85
|
+
)
|
|
82
86
|
assert type(metadata) is type(expected)
|
|
83
87
|
|
|
84
88
|
|
|
@@ -203,7 +207,7 @@ expected = {
|
|
|
203
207
|
"cli_id": "gha-utils",
|
|
204
208
|
"module_id": "gha_utils.__main__",
|
|
205
209
|
"callable_id": "main",
|
|
206
|
-
"module_path": regex(r"gha_utils(
|
|
210
|
+
"module_path": regex(r"gha_utils(/|\\)__main__\.py"),
|
|
207
211
|
},
|
|
208
212
|
{
|
|
209
213
|
"commit": regex(r"[a-z0-9]+"),
|
|
@@ -256,7 +260,7 @@ def test_metadata_json_format():
|
|
|
256
260
|
metadata = Metadata().dump(Dialects.json)
|
|
257
261
|
assert isinstance(metadata, str)
|
|
258
262
|
|
|
259
|
-
iter_checks(json.loads(metadata), expected)
|
|
263
|
+
iter_checks(json.loads(metadata), expected, metadata)
|
|
260
264
|
|
|
261
265
|
|
|
262
266
|
def test_metadata_github_format():
|
|
@@ -326,4 +330,4 @@ def test_metadata_github_format():
|
|
|
326
330
|
new_value = " ".join(f'"{i}"' for i in value)
|
|
327
331
|
github_format_expected[key] = new_value
|
|
328
332
|
|
|
329
|
-
iter_checks(metadata, github_format_expected)
|
|
333
|
+
iter_checks(metadata, github_format_expected, raw_metadata)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|