gha-utils 4.19.1__py3-none-any.whl → 4.21.0__py3-none-any.whl

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/__init__.py CHANGED
@@ -17,4 +17,4 @@
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- __version__ = "4.19.1"
20
+ __version__ = "4.21.0"
gha_utils/cli.py CHANGED
@@ -239,9 +239,9 @@ def changelog(ctx, source, changelog_path):
239
239
  "--create-if-missing/--skip-if-missing",
240
240
  is_flag=True,
241
241
  default=True,
242
- help="Create the destination mailmap file if it is not found. Or skip the update "
243
- "process entirely if if does not already exists in the first place. This option "
244
- f"is ignore if the destination is to print the result to {sys.stdout.name}.",
242
+ help="If not found, either create the missing destination mailmap file, or skip "
243
+ "the update process entirely. This option is ignored if the destination is to print "
244
+ f"the result to {sys.stdout.name}.",
245
245
  )
246
246
  @argument(
247
247
  "destination_mailmap",
gha_utils/mailmap.py CHANGED
@@ -128,7 +128,7 @@ class Mailmap:
128
128
 
129
129
  @cached_property
130
130
  def git_contributors(self) -> set[str]:
131
- """Returns the set of all constributors found in the Git commit history.
131
+ """Returns the set of all contributors found in the Git commit history.
132
132
 
133
133
  No normalization happens: all variations of authors and committers strings
134
134
  attached to all commits are considered.
gha_utils/metadata.py CHANGED
@@ -20,11 +20,18 @@ The following variables are `printed to the environment file
20
20
  <https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#environment-files>`_:
21
21
 
22
22
  ```text
23
+ is_bot=false
23
24
  new_commits=346ce664f055fbd042a25ee0b7e96702e95 6f27db47612aaee06fdf08744b09a9f5f6c2
24
25
  release_commits=6f27db47612aaee06fdf08744b09a9f5f6c2
26
+ mailmap_exists=true
25
27
  gitignore_exists=true
26
28
  python_files=".github/update_mailmap.py" ".github/metadata.py" "setup.py"
29
+ json_files=
30
+ yaml_files="config.yaml" ".github/workflows/lint.yaml" ".github/workflows/test.yaml"
31
+ workflow_files=".github/workflows/lint.yaml" ".github/workflows/test.yaml"
27
32
  doc_files="changelog.md" "readme.md" "docs/license.md"
33
+ markdown_files="changelog.md" "readme.md" "docs/license.md"
34
+ zsh_files=
28
35
  is_python_project=true
29
36
  package_name=click-extra
30
37
  blacken_docs_params=--target-version py37 --target-version py38
@@ -34,119 +41,233 @@ released_version=2.0.0
34
41
  is_sphinx=true
35
42
  active_autodoc=true
36
43
  release_notes=[🐍 Available on PyPi](https://pypi.org/project/click-extra/2.21.3).
37
- new_commits_matrix={'commit': ['346ce664f055fbd042a25ee0b7e96702e95',
38
- '6f27db47612aaee06fdf08744b09a9f5f6c2'],
39
- 'include': [{'commit': '346ce664f055fbd042a25ee0b7e96702e95',
40
- 'short_sha': '346ce66',
41
- 'current_version': '2.0.1'},
42
- {'commit': '6f27db47612aaee06fdf08744b09a9f5f6c2',
43
- 'short_sha': '6f27db4',
44
- 'current_version': '2.0.0'}]}
45
- release_commits_matrix={'commit': ['6f27db47612aaee06fdf08744b09a9f5f6c2'],
46
- 'include': [{'commit': '6f27db47612aaee06fdf08744b09a9f5f6c2',
47
- 'short_sha': '6f27db4',
48
- 'current_version': '2.0.0'}]}
49
- nuitka_matrix={'os': ['ubuntu-24.04-arm', 'ubuntu-24.04', 'macos-15', 'macos-13', 'windows-11-arm', 'windows-2025'],
50
- 'entry_point': ['mpm'],
51
- 'commit': ['346ce664f055fbd042a25ee0b7e96702e95',
52
- '6f27db47612aaee06fdf08744b09a9f5f6c2'],
53
- 'include': [{'target': 'linux-arm64',
54
- 'os': 'ubuntu-24.04-arm',
55
- 'platform_id': 'linux',
56
- 'arch': 'arm64',
57
- 'extension': 'bin'},
58
- {'target': 'linux-x64',
59
- 'os': 'ubuntu-24.04',
60
- 'platform_id': 'linux',
61
- 'arch': 'x64',
62
- 'extension': 'bin'},
63
- {'target': 'macos-arm64',
64
- 'os': 'macos-15',
65
- 'platform_id': 'macos',
66
- 'arch': 'arm64',
67
- 'extension': 'bin'},
68
- {'target': 'macos-x64',
69
- 'os': 'macos-13',
70
- 'platform_id': 'macos',
71
- 'arch': 'x64',
72
- 'extension': 'bin'},
73
- {'target': 'windows-arm64',
74
- 'os': 'windows-11-arm',
75
- 'platform_id": 'windows',
76
- 'arch": 'arm64',
77
- 'extension": 'exe'},
78
- {'target': 'windows-x64',
79
- 'os': 'windows-2025',
80
- 'platform_id': 'windows',
81
- 'arch': 'x64',
82
- 'extension': 'exe'},
83
- {'entry_point': 'mpm',
84
- 'cli_id': 'mpm',
85
- 'module_id': 'meta_package_manager.__main__',
86
- 'callable_id': 'main',
87
- 'module_path': 'meta_package_manager/__main__.py'},
88
- {'commit': '346ce664f055fbd042a25ee0b7e96702e95',
89
- 'short_sha': '346ce66',
90
- 'current_version': '2.0.0'},
91
- {'commit': '6f27db47612aaee06fdf08744b09a9f5f6c2',
92
- 'short_sha': '6f27db4',
93
- 'current_version': '1.9.1'},
94
- {'os': 'ubuntu-24.04-arm',
95
- 'entry_point': 'mpm',
96
- 'commit': '346ce664f055fbd042a25ee0b7e96702e95',
97
- 'bin_name': 'mpm-linux-arm64-346ce66.bin'},
98
- {'os': 'ubuntu-24.04-arm',
99
- 'entry_point': 'mpm',
100
- 'commit': '6f27db47612aaee06fdf08744b09a9f5f6c2',
101
- 'bin_name': 'mpm-linux-arm64-6f27db4.bin'},
102
- {'os': 'ubuntu-24.04',
103
- 'entry_point': 'mpm',
104
- 'commit': '346ce664f055fbd042a25ee0b7e96702e95',
105
- 'bin_name': 'mpm-linux-x64-346ce66.bin'},
106
- {'os': 'ubuntu-24.04',
107
- 'entry_point': 'mpm',
108
- 'commit': '6f27db47612aaee06fdf08744b09a9f5f6c2',
109
- 'bin_name': 'mpm-linux-x64-6f27db4.bin'},
110
- {'os': 'macos-15',
111
- 'entry_point': 'mpm',
112
- 'commit': '346ce664f055fbd042a25ee0b7e96702e95',
113
- 'bin_name': 'mpm-macos-arm64-346ce66.bin'},
114
- {'os': 'macos-15',
115
- 'entry_point': 'mpm',
116
- 'commit': '6f27db47612aaee06fdf08744b09a9f5f6c2',
117
- 'bin_name': 'mpm-macos-arm64-6f27db4.bin'},
118
- {'os': 'macos-13',
119
- 'entry_point': 'mpm',
120
- 'commit': '346ce664f055fbd042a25ee0b7e96702e95',
121
- 'bin_name': 'mpm-macos-x64-346ce66.bin'},
122
- {'os': 'macos-13',
123
- 'entry_point': 'mpm',
124
- 'commit': '6f27db47612aaee06fdf08744b09a9f5f6c2',
125
- 'bin_name': 'mpm-macos-x64-6f27db4.bin'},
126
- {'os': 'windows-11-arm',
127
- 'entry_point': 'mpm',
128
- 'commit': '346ce664f055fbd042a25ee0b7e96702e95',
129
- 'bin_name': 'mpm-windows-arm64-346ce66.bin'},
130
- {'os': 'windows-11-arm',
131
- 'entry_point': 'mpm',
132
- 'commit': '6f27db47612aaee06fdf08744b09a9f5f6c2',
133
- 'bin_name': 'mpm-windows-arm64-6f27db4.bin'},
134
- {'os': 'windows-2025',
135
- 'entry_point': 'mpm',
136
- 'commit': '346ce664f055fbd042a25ee0b7e96702e95',
137
- 'bin_name': 'mpm-windows-x64-346ce66.exe'},
138
- {'os': 'windows-2025',
139
- 'entry_point': 'mpm',
140
- 'commit': '6f27db47612aaee06fdf08744b09a9f5f6c2',
141
- 'bin_name': 'mpm-windows-x64-6f27db4.exe'},
142
- {'state': 'stable'}]}
44
+ new_commits_matrix={
45
+ "commit": [
46
+ "346ce664f055fbd042a25ee0b7e96702e95",
47
+ "6f27db47612aaee06fdf08744b09a9f5f6c2"
48
+ ],
49
+ "include": [
50
+ {
51
+ "commit": "346ce664f055fbd042a25ee0b7e96702e95",
52
+ "short_sha": "346ce66",
53
+ "current_version": "2.0.1"
54
+ },
55
+ {
56
+ "commit": "6f27db47612aaee06fdf08744b09a9f5f6c2",
57
+ "short_sha": "6f27db4",
58
+ "current_version": "2.0.0"
59
+ }
60
+ ]
61
+ }
62
+ release_commits_matrix={
63
+ "commit": ["6f27db47612aaee06fdf08744b09a9f5f6c2"],
64
+ "include": [
65
+ {
66
+ "commit": "6f27db47612aaee06fdf08744b09a9f5f6c2",
67
+ "short_sha": "6f27db4",
68
+ "current_version": "2.0.0"
69
+ }
70
+ ]
71
+ }
72
+ build_targets=[
73
+ {
74
+ "target": "linux-arm64",
75
+ "os": "ubuntu-24.04-arm",
76
+ "platform_id": "linux",
77
+ "arch": "arm64",
78
+ "extension": "bin"
79
+ },
80
+ {
81
+ "target": "linux-x64",
82
+ "os": "ubuntu-24.04",
83
+ "platform_id": "linux",
84
+ "arch": "x64",
85
+ "extension": "bin"
86
+ },
87
+ {
88
+ "target": "macos-arm64",
89
+ "os": "macos-26",
90
+ "platform_id": "macos",
91
+ "arch": "arm64",
92
+ "extension": "bin"
93
+ },
94
+ {
95
+ "target": "macos-x64",
96
+ "os": "macos-15-intel",
97
+ "platform_id": "macos",
98
+ "arch": "x64",
99
+ "extension": "bin"
100
+ },
101
+ {
102
+ "target": "windows-arm64",
103
+ "os": "windows-11-arm",
104
+ "platform_id": "windows",
105
+ "arch": "arm64",
106
+ "extension": "exe"
107
+ },
108
+ {
109
+ "target": "windows-x64",
110
+ "os": "windows-2025",
111
+ "platform_id": "windows",
112
+ "arch": "x64",
113
+ "extension": "exe"
114
+ }
115
+ ]
116
+ nuitka_matrix={
117
+ "os": [
118
+ "ubuntu-24.04-arm",
119
+ "ubuntu-24.04",
120
+ "macos-26",
121
+ "macos-15-intel",
122
+ "windows-11-arm",
123
+ "windows-2025"
124
+ ],
125
+ "entry_point": ["mpm"],
126
+ "commit": [
127
+ "346ce664f055fbd042a25ee0b7e96702e95",
128
+ "6f27db47612aaee06fdf08744b09a9f5f6c2"
129
+ ],
130
+ "include": [
131
+ {
132
+ "target": "linux-arm64",
133
+ "os": "ubuntu-24.04-arm",
134
+ "platform_id": "linux",
135
+ "arch": "arm64",
136
+ "extension": "bin"
137
+ },
138
+ {
139
+ "target": "linux-x64",
140
+ "os": "ubuntu-24.04",
141
+ "platform_id": "linux",
142
+ "arch": "x64",
143
+ "extension": "bin"
144
+ },
145
+ {
146
+ "target": "macos-arm64",
147
+ "os": "macos-26",
148
+ "platform_id": "macos",
149
+ "arch": "arm64",
150
+ "extension": "bin"
151
+ },
152
+ {
153
+ "target": "macos-x64",
154
+ "os": "macos-15-intel",
155
+ "platform_id": "macos",
156
+ "arch": "x64",
157
+ "extension": "bin"
158
+ },
159
+ {
160
+ "target": "windows-arm64",
161
+ "os": "windows-11-arm",
162
+ "platform_id": "windows",
163
+ "arch": "arm64",
164
+ "extension": "exe"
165
+ },
166
+ {
167
+ "target": "windows-x64",
168
+ "os": "windows-2025",
169
+ "platform_id": "windows",
170
+ "arch": "x64",
171
+ "extension": "exe"
172
+ },
173
+ {
174
+ "entry_point": "mpm",
175
+ "cli_id": "mpm",
176
+ "module_id": "meta_package_manager.__main__",
177
+ "callable_id": "main",
178
+ "module_path": "meta_package_manager/__main__.py"
179
+ },
180
+ {
181
+ "commit": "346ce664f055fbd042a25ee0b7e96702e95",
182
+ "short_sha": "346ce66",
183
+ "current_version": "2.0.0"
184
+ },
185
+ {
186
+ "commit": "6f27db47612aaee06fdf08744b09a9f5f6c2",
187
+ "short_sha": "6f27db4",
188
+ "current_version": "1.9.1"
189
+ },
190
+ {
191
+ "os": "ubuntu-24.04-arm",
192
+ "entry_point": "mpm",
193
+ "commit": "346ce664f055fbd042a25ee0b7e96702e95",
194
+ "bin_name": "mpm-linux-arm64-346ce66.bin"
195
+ },
196
+ {
197
+ "os": "ubuntu-24.04-arm",
198
+ "entry_point": "mpm",
199
+ "commit": "6f27db47612aaee06fdf08744b09a9f5f6c2",
200
+ "bin_name": "mpm-linux-arm64-6f27db4.bin"
201
+ },
202
+ {
203
+ "os": "ubuntu-24.04",
204
+ "entry_point": "mpm",
205
+ "commit": "346ce664f055fbd042a25ee0b7e96702e95",
206
+ "bin_name": "mpm-linux-x64-346ce66.bin"
207
+ },
208
+ {
209
+ "os": "ubuntu-24.04",
210
+ "entry_point": "mpm",
211
+ "commit": "6f27db47612aaee06fdf08744b09a9f5f6c2",
212
+ "bin_name": "mpm-linux-x64-6f27db4.bin"
213
+ },
214
+ {
215
+ "os": "macos-26",
216
+ "entry_point": "mpm",
217
+ "commit": "346ce664f055fbd042a25ee0b7e96702e95",
218
+ "bin_name": "mpm-macos-arm64-346ce66.bin"
219
+ },
220
+ {
221
+ "os": "macos-26",
222
+ "entry_point": "mpm",
223
+ "commit": "6f27db47612aaee06fdf08744b09a9f5f6c2",
224
+ "bin_name": "mpm-macos-arm64-6f27db4.bin"
225
+ },
226
+ {
227
+ "os": "macos-15-intel",
228
+ "entry_point": "mpm",
229
+ "commit": "346ce664f055fbd042a25ee0b7e96702e95",
230
+ "bin_name": "mpm-macos-x64-346ce66.bin"
231
+ },
232
+ {
233
+ "os": "macos-15-intel",
234
+ "entry_point": "mpm",
235
+ "commit": "6f27db47612aaee06fdf08744b09a9f5f6c2",
236
+ "bin_name": "mpm-macos-x64-6f27db4.bin"
237
+ },
238
+ {
239
+ "os": "windows-11-arm",
240
+ "entry_point": "mpm",
241
+ "commit": "346ce664f055fbd042a25ee0b7e96702e95",
242
+ "bin_name": "mpm-windows-arm64-346ce66.bin"
243
+ },
244
+ {
245
+ "os": "windows-11-arm",
246
+ "entry_point": "mpm",
247
+ "commit": "6f27db47612aaee06fdf08744b09a9f5f6c2",
248
+ "bin_name": "mpm-windows-arm64-6f27db4.bin"
249
+ },
250
+ {
251
+ "os": "windows-2025",
252
+ "entry_point": "mpm",
253
+ "commit": "346ce664f055fbd042a25ee0b7e96702e95",
254
+ "bin_name": "mpm-windows-x64-346ce66.exe"
255
+ },
256
+ {
257
+ "os": "windows-2025",
258
+ "entry_point": "mpm",
259
+ "commit": "6f27db47612aaee06fdf08744b09a9f5f6c2",
260
+ "bin_name": "mpm-windows-x64-6f27db4.exe"
261
+ },
262
+ {"state": "stable"}
263
+ ]
264
+ }
143
265
  ```
144
266
 
145
267
  .. warning::
146
-
147
- The ``new_commits_matrix``, ``release_commits_matrix`` and ``nuitka_matrix``
148
- variables in the block above are pretty-printed for readability. They are not
149
- actually formatted this way in the environment file, but inlined.
268
+ Fields with serialized lists and dictionaries, like ``new_commits_matrix``,
269
+ ``build_targets`` or ``nuitka_matrix``, are pretty-printed in the example above for
270
+ readability. They are inlined in the actual output and not formatted this way.
150
271
  """
151
272
 
152
273
  from __future__ import annotations
@@ -166,13 +287,14 @@ from random import randint
166
287
  from re import escape
167
288
  from typing import Any, Final, cast
168
289
 
169
- import gitignore_parser
170
290
  from bumpversion.config import get_configuration # type: ignore[import-untyped]
171
291
  from bumpversion.config.files import find_config_file # type: ignore[import-untyped]
172
292
  from bumpversion.show import resolve_name # type: ignore[import-untyped]
173
293
  from extra_platforms import is_github_ci
174
294
  from packaging.specifiers import SpecifierSet
175
295
  from packaging.version import Version
296
+ from py_walk import get_parser_from_file
297
+ from py_walk.models import Parser
176
298
  from pydriller import Commit, Git, Repository # type: ignore[import-untyped]
177
299
  from pyproject_metadata import ConfigurationError, StandardMetadata
178
300
  from wcmatch.glob import (
@@ -197,6 +319,8 @@ SHORT_SHA_LENGTH = 7
197
319
  depends on the size of the repository.
198
320
  """
199
321
 
322
+ MAILMAP_PATH = Path(".mailmap")
323
+
200
324
  GITIGNORE_PATH = Path(".gitignore")
201
325
 
202
326
  NUITKA_BUILD_TARGETS = {
@@ -213,13 +337,13 @@ NUITKA_BUILD_TARGETS = {
213
337
  "extension": "bin",
214
338
  },
215
339
  "macos-arm64": {
216
- "os": "macos-15",
340
+ "os": "macos-26",
217
341
  "platform_id": "macos",
218
342
  "arch": "arm64",
219
343
  "extension": "bin",
220
344
  },
221
345
  "macos-x64": {
222
- "os": "macos-13",
346
+ "os": "macos-15-intel",
223
347
  "platform_id": "macos",
224
348
  "arch": "x64",
225
349
  "extension": "bin",
@@ -269,6 +393,13 @@ Values are dictionaries with the following keys:
269
393
  """
270
394
 
271
395
 
396
+ FLAT_BUILD_TARGETS = [
397
+ {"target": target_id} | target_data
398
+ for target_id, target_data in NUITKA_BUILD_TARGETS.items()
399
+ ]
400
+ """List of build targets in a flat format, suitable for matrix inclusion."""
401
+
402
+
272
403
  WorkflowEvent = StrEnum(
273
404
  "WorkflowEvent",
274
405
  (
@@ -339,6 +470,7 @@ class TargetVersion(StrEnum):
339
470
  PY311 = "3.11"
340
471
  PY312 = "3.12"
341
472
  PY313 = "3.13"
473
+ PY314 = "3.14"
342
474
 
343
475
 
344
476
  MYPY_VERSION_MIN: Final = (3, 8)
@@ -349,6 +481,10 @@ MYPY_VERSION_MIN: Final = (3, 8)
349
481
  """
350
482
 
351
483
 
484
+ # Silence overly verbose debug messages from py-walk logger.
485
+ logging.getLogger("py_walk").setLevel(logging.WARNING)
486
+
487
+
352
488
  class JSONMetadata(json.JSONEncoder):
353
489
  """Custom JSON encoder for metadata serialization."""
354
490
 
@@ -408,10 +544,15 @@ class Metadata:
408
544
  logging.debug(json.dumps(context, indent=4))
409
545
  return context # type:ignore[no-any-return]
410
546
 
411
- def git_stash_count(self, git_repo: Git) -> int:
547
+ @cached_property
548
+ def git(self) -> Git:
549
+ """Return a PyDriller Git object."""
550
+ return Git(".")
551
+
552
+ def git_stash_count(self) -> int:
412
553
  """Returns the number of stashes."""
413
554
  count = int(
414
- git_repo.repo.git.rev_list(
555
+ self.git.repo.git.rev_list(
415
556
  "--walk-reflogs", "--ignore-missing", "--count", "refs/stash"
416
557
  )
417
558
  )
@@ -456,8 +597,7 @@ class Metadata:
456
597
  if not commits:
457
598
  return None
458
599
 
459
- git = Git(".")
460
- current_commit = git.repo.head.commit.hexsha
600
+ current_commit = self.git.repo.head.commit.hexsha
461
601
 
462
602
  # Check if we need to get back in time in the Git log and browse past commits.
463
603
  if len(commits) == 1: # type: ignore[arg-type]
@@ -485,17 +625,17 @@ class Metadata:
485
625
  # Save the initial commit reference and SHA of the repository. The
486
626
  # reference is either the canonical active branch name (i.e. ``main``), or
487
627
  # the commit SHA if the current HEAD commit is detached from a branch.
488
- if git.repo.head.is_detached:
628
+ if self.git.repo.head.is_detached:
489
629
  init_ref = current_commit
490
630
  else:
491
- init_ref = git.repo.active_branch.name
631
+ init_ref = self.git.repo.active_branch.name
492
632
  logging.debug(f"Initial commit reference: {init_ref}")
493
633
 
494
634
  # Try to stash local changes and check if we'll need to unstash them later.
495
- counter_before = self.git_stash_count(git)
635
+ counter_before = self.git_stash_count()
496
636
  logging.debug("Try to stash local changes before our series of checkouts.")
497
- git.repo.git.stash()
498
- counter_after = self.git_stash_count(git)
637
+ self.git.repo.git.stash()
638
+ counter_after = self.git_stash_count()
499
639
  logging.debug(
500
640
  "Stash counter changes after 'git stash' command: "
501
641
  f"{counter_before} -> {counter_after}"
@@ -516,7 +656,7 @@ class Metadata:
516
656
  for commit in commits:
517
657
  if past_commit_lookup:
518
658
  logging.debug(f"Checkout to commit {commit.hash}")
519
- git.checkout(commit.hash)
659
+ self.git.checkout(commit.hash)
520
660
 
521
661
  commit_metadata = {
522
662
  "commit": commit.hash,
@@ -534,10 +674,10 @@ class Metadata:
534
674
  # Restore the repository to its initial state.
535
675
  if past_commit_lookup:
536
676
  logging.debug(f"Restore repository to {init_ref}.")
537
- git.checkout(init_ref)
677
+ self.git.checkout(init_ref)
538
678
  if need_unstash:
539
679
  logging.debug("Unstash local changes that were previously saved.")
540
- git.repo.git.stash("pop")
680
+ self.git.repo.git.stash("pop")
541
681
 
542
682
  return matrix
543
683
 
@@ -570,6 +710,31 @@ class Metadata:
570
710
  return WorkflowEvent.pull_request
571
711
  return WorkflowEvent.push
572
712
 
713
+ @cached_property
714
+ def event_actor(self) -> str | None:
715
+ """Returns the GitHub login of the user that triggered the workflow run."""
716
+ return self.github_context.get("actor")
717
+
718
+ @cached_property
719
+ def event_sender_type(self) -> str | None:
720
+ """Returns the type of the user that triggered the workflow run."""
721
+ sender_type = self.github_context.get("event", {}).get("sender", {}).get("type")
722
+ return cast(str | None, sender_type)
723
+
724
+ @cached_property
725
+ def is_bot(self) -> bool:
726
+ """Returns ``True`` if the workflow was triggered by a bot or automated process.
727
+
728
+ This is useful to only run some jobs on human-triggered events. Or skip jobs
729
+ triggered by bots to avoid infinite loops.
730
+ """
731
+ if self.event_sender_type == "Bot" or self.event_actor in (
732
+ "dependabot[bot]",
733
+ "dependabot-preview[bot]",
734
+ ):
735
+ return True
736
+ return False
737
+
573
738
  @cached_property
574
739
  def commit_range(self) -> tuple[str, str] | None:
575
740
  """Range of commits bundled within the triggering event.
@@ -640,15 +805,34 @@ class Metadata:
640
805
  if not self.commit_range:
641
806
  return None
642
807
  start, end = self.commit_range
643
- # Remove the last commit, as the commit range is inclusive.
644
- return tuple(
645
- Repository(
646
- ".",
647
- from_commit=start,
648
- to_commit=end,
649
- order="reverse",
650
- ).traverse_commits(),
651
- )[:-1]
808
+
809
+ # Sanity check: make sure the start commit exists in the repository.
810
+ # XXX Even if we skip the start commit later on (because the range is
811
+ # inclusive), we still need to make sure it exists: PyDriller stills needs to
812
+ # find it to be able to traverse the commit history.
813
+ for commit_id in (start, end):
814
+ try:
815
+ _ = self.git.get_commit(commit_id)
816
+ except ValueError:
817
+ logging.error(
818
+ f"Cannot find commit {commit_id} in repository. "
819
+ "Repository was probably not checked out with enough depth. "
820
+ f"Current depth is {self.git.total_commits()}. "
821
+ )
822
+ logging.warning(
823
+ "Skipping metadata extraction of the range of new commits."
824
+ )
825
+ return None
826
+
827
+ commit_list = []
828
+ for index, commit in enumerate(
829
+ Repository(".", from_commit=start, to_commit=end).traverse_commits()
830
+ ):
831
+ # Skip the first commit because the commit range is inclusive.
832
+ if index == 0:
833
+ continue
834
+ commit_list.append(commit)
835
+ return tuple(commit_list)
652
836
 
653
837
  @cached_property
654
838
  def new_commits_matrix(self) -> Matrix | None:
@@ -697,10 +881,27 @@ class Metadata:
697
881
  else None
698
882
  )
699
883
 
884
+ @cached_property
885
+ def mailmap_exists(self) -> bool:
886
+ return MAILMAP_PATH.is_file()
887
+
700
888
  @cached_property
701
889
  def gitignore_exists(self) -> bool:
702
890
  return GITIGNORE_PATH.is_file()
703
891
 
892
+ @cached_property
893
+ def gitignore_parser(self) -> Parser | None:
894
+ """Returns a parser for the ``.gitignore`` file, if it exists."""
895
+ if self.gitignore_exists:
896
+ logging.debug(f"Parse {GITIGNORE_PATH}")
897
+ return get_parser_from_file(GITIGNORE_PATH)
898
+ return None
899
+
900
+ def gitignore_match(self, file_path: Path | str) -> bool:
901
+ if self.gitignore_parser and self.gitignore_parser.match(file_path):
902
+ return True
903
+ return False
904
+
704
905
  def glob_files(self, *patterns: str) -> list[Path]:
705
906
  """Return all file path matching the ``patterns``.
706
907
 
@@ -730,12 +931,6 @@ class Metadata:
730
931
  current_dir = Path.cwd()
731
932
  seen = set()
732
933
 
733
- # If the .gitignore file exists, we parse it to filter out ignored files.
734
- gitignore = None
735
- if self.gitignore_exists:
736
- logging.debug(f"Load {GITIGNORE_PATH} to filter out ignored files.")
737
- gitignore = gitignore_parser.parse_gitignore(GITIGNORE_PATH)
738
-
739
934
  for file_path in iglob(
740
935
  patterns,
741
936
  flags=NODIR | GLOBSTAR | DOTGLOB | GLOBTILDE | BRACE | FOLLOW | NEGATE,
@@ -765,7 +960,7 @@ class Metadata:
765
960
  continue
766
961
 
767
962
  # Skip files that are ignored by .gitignore.
768
- if gitignore and gitignore(file_path):
963
+ if self.gitignore_match(file_path):
769
964
  logging.debug(f"Skip file matching {GITIGNORE_PATH}: {file_path}")
770
965
  continue
771
966
 
@@ -775,12 +970,43 @@ class Metadata:
775
970
  @cached_property
776
971
  def python_files(self) -> list[Path]:
777
972
  """Returns a list of python files."""
778
- return self.glob_files("**/*.py", "!.venv/**")
973
+ return self.glob_files("**/*.{py,pyi,pyw,pyx,ipynb}")
974
+
975
+ @cached_property
976
+ def json_files(self) -> list[Path]:
977
+ """Returns a list of JSON files."""
978
+ return self.glob_files(
979
+ "**/*.{json,jsonc,json5}",
980
+ "**/.code-workspace",
981
+ "!**/package-lock.json",
982
+ )
983
+
984
+ @cached_property
985
+ def yaml_files(self) -> list[Path]:
986
+ """Returns a list of YAML files."""
987
+ return self.glob_files("**/*.{yaml,yml}")
988
+
989
+ @cached_property
990
+ def workflow_files(self) -> list[Path]:
991
+ """Returns a list of GitHub workflow files."""
992
+ return self.glob_files(".github/workflows/**/*.{yaml,yml}")
779
993
 
780
994
  @cached_property
781
995
  def doc_files(self) -> list[Path]:
782
996
  """Returns a list of doc files."""
783
- return self.glob_files("**/*.{md,markdown,rst,tex}", "!.venv/**")
997
+ return self.glob_files(
998
+ "**/*.{markdown,mdown,mkdn,mdwn,mkd,md,mdtxt,mdtext,rst,tex}"
999
+ )
1000
+
1001
+ @cached_property
1002
+ def markdown_files(self) -> list[Path]:
1003
+ """Returns a list of Markdown files."""
1004
+ return self.glob_files("**/*.{markdown,mdown,mkdn,mdwn,mkd,md,mdtxt,mdtext}")
1005
+
1006
+ @cached_property
1007
+ def zsh_files(self) -> list[Path]:
1008
+ """Returns a list of Zsh files."""
1009
+ return self.glob_files("**/*.{sh,zsh}", "**/.{zshrc,zprofile,zshenv,zlogin}")
784
1010
 
785
1011
  @cached_property
786
1012
  def is_python_project(self):
@@ -1025,8 +1251,8 @@ class Metadata:
1025
1251
  "os": [
1026
1252
  "ubuntu-24.04-arm",
1027
1253
  "ubuntu-24.04",
1028
- "macos-15",
1029
- "macos-13",
1254
+ "macos-26",
1255
+ "macos-15-intel",
1030
1256
  "windows-11-arm",
1031
1257
  "windows-2025",
1032
1258
  ],
@@ -1054,14 +1280,14 @@ class Metadata:
1054
1280
  },
1055
1281
  {
1056
1282
  "target": "macos-arm64",
1057
- "os": "macos-15",
1283
+ "os": "macos-26",
1058
1284
  "platform_id": "macos",
1059
1285
  "arch": "arm64",
1060
1286
  "extension": "bin",
1061
1287
  },
1062
1288
  {
1063
1289
  "target": "macos-x64",
1064
- "os": "macos-13",
1290
+ "os": "macos-15-intel",
1065
1291
  "platform_id": "macos",
1066
1292
  "arch": "x64",
1067
1293
  "extension": "bin",
@@ -1122,25 +1348,25 @@ class Metadata:
1122
1348
  "bin_name": "mpm-linux-x64-6f27db4.bin",
1123
1349
  },
1124
1350
  {
1125
- "os": "macos-15",
1351
+ "os": "macos-26",
1126
1352
  "entry_point": "mpm",
1127
1353
  "commit": "346ce664f055fbd042a25ee0b7e96702e95",
1128
1354
  "bin_name": "mpm-macos-arm64-346ce66.bin",
1129
1355
  },
1130
1356
  {
1131
- "os": "macos-15",
1357
+ "os": "macos-26",
1132
1358
  "entry_point": "mpm",
1133
1359
  "commit": "6f27db47612aaee06fdf08744b09a9f5f6c2",
1134
1360
  "bin_name": "mpm-macos-arm64-6f27db4.bin",
1135
1361
  },
1136
1362
  {
1137
- "os": "macos-13",
1363
+ "os": "macos-15-intel",
1138
1364
  "entry_point": "mpm",
1139
1365
  "commit": "346ce664f055fbd042a25ee0b7e96702e95",
1140
1366
  "bin_name": "mpm-macos-x64-346ce66.bin",
1141
1367
  },
1142
1368
  {
1143
- "os": "macos-13",
1369
+ "os": "macos-15-intel",
1144
1370
  "entry_point": "mpm",
1145
1371
  "commit": "6f27db47612aaee06fdf08744b09a9f5f6c2",
1146
1372
  "bin_name": "mpm-macos-x64-6f27db4.bin",
@@ -1186,8 +1412,8 @@ class Metadata:
1186
1412
  "os", tuple(map(itemgetter("os"), NUITKA_BUILD_TARGETS.values()))
1187
1413
  )
1188
1414
  # Augment each "os" entry with platform-specific data.
1189
- for target_id, target_data in NUITKA_BUILD_TARGETS.items():
1190
- matrix.add_includes({"target": target_id} | target_data)
1415
+ for target_data in FLAT_BUILD_TARGETS:
1416
+ matrix.add_includes(target_data)
1191
1417
 
1192
1418
  # Augment each entry point with some metadata.
1193
1419
  for cli_id, module_id, callable_id in self.script_entries:
@@ -1289,9 +1515,9 @@ class Metadata:
1289
1515
  - `None` into empty string
1290
1516
  - `bool` into lower-cased string
1291
1517
  - `Matrix` into JSON string
1292
- - `Iterable` of strings into a serialized space-separated string
1293
- - `Iterable` of `Path` into a serialized string whose items are space-separated
1294
- and double-quoted
1518
+ - `Iterable` of mixed strings and `Path` into a serialized space-separated
1519
+ string, where `Path` items are double-quoted
1520
+ - other `Iterable` into a JSON string
1295
1521
  """
1296
1522
  # Structured metadata to be rendered as JSON.
1297
1523
  if isinstance(value, Matrix):
@@ -1309,9 +1535,26 @@ class Metadata:
1309
1535
  raise NotImplementedError
1310
1536
 
1311
1537
  elif isinstance(value, Iterable):
1312
- # Cast all items to string, wrapping Path items with double-quotes.
1313
- items = ((f'"{i}"' if isinstance(i, Path) else str(i)) for i in value)
1314
- value = " ".join(items)
1538
+ # Cast all items to strings, wrapping Path items with double-quotes.
1539
+ if all(isinstance(i, (str, Path)) for i in value):
1540
+ items = (
1541
+ (f'"{i}"' if isinstance(i, Path) else str(i)) for i in value
1542
+ )
1543
+ value = " ".join(items)
1544
+ # XXX We only support iterables of dict[str, str] for now.
1545
+ else:
1546
+ assert all(
1547
+ isinstance(i, dict)
1548
+ and all(
1549
+ isinstance(k, str) and isinstance(v, str)
1550
+ for k, v in i.items()
1551
+ )
1552
+ for i in value
1553
+ ), f"Unsupported iterable value: {value!r}"
1554
+ value = json.dumps(value)
1555
+
1556
+ else:
1557
+ raise NotImplementedError(f"GitHub formatting for: {value!r}")
1315
1558
 
1316
1559
  return cast(str, value)
1317
1560
 
@@ -1321,11 +1564,18 @@ class Metadata:
1321
1564
  Defaults to GitHub dialect.
1322
1565
  """
1323
1566
  metadata: dict[str, Any] = {
1567
+ "is_bot": self.is_bot,
1324
1568
  "new_commits": self.new_commits_hash,
1325
1569
  "release_commits": self.release_commits_hash,
1570
+ "mailmap_exists": self.mailmap_exists,
1326
1571
  "gitignore_exists": self.gitignore_exists,
1327
1572
  "python_files": self.python_files,
1573
+ "json_files": self.json_files,
1574
+ "yaml_files": self.yaml_files,
1575
+ "workflow_files": self.workflow_files,
1328
1576
  "doc_files": self.doc_files,
1577
+ "markdown_files": self.markdown_files,
1578
+ "zsh_files": self.zsh_files,
1329
1579
  "is_python_project": self.is_python_project,
1330
1580
  "package_name": self.package_name,
1331
1581
  "blacken_docs_params": self.blacken_docs_params,
@@ -1337,6 +1587,7 @@ class Metadata:
1337
1587
  "release_notes": self.release_notes,
1338
1588
  "new_commits_matrix": self.new_commits_matrix,
1339
1589
  "release_commits_matrix": self.release_commits_matrix,
1590
+ "build_targets": FLAT_BUILD_TARGETS,
1340
1591
  "nuitka_matrix": self.nuitka_matrix,
1341
1592
  }
1342
1593
 
@@ -1353,7 +1604,7 @@ class Metadata:
1353
1604
  content += f"{env_name}={env_value}\n"
1354
1605
  else:
1355
1606
  # Use a random unique delimiter to encode multiline value:
1356
- delimiter = f"ghadelimiter_{randint(10**8, (10**9) - 1)}"
1607
+ delimiter = f"GHA_DELIMITER_{randint(10**8, (10**9) - 1)}"
1357
1608
  content += f"{env_name}<<{delimiter}\n{env_value}\n{delimiter}\n"
1358
1609
  else:
1359
1610
  assert dialect == Dialects.json
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gha-utils
3
- Version: 4.19.1
3
+ Version: 4.21.0
4
4
  Summary: ⚙️ CLI helpers for GitHub Actions + reuseable workflows
5
5
  Author-email: Kevin Deldycke <kevin@deldycke.com>
6
6
  Project-URL: Homepage, https://github.com/kdeldycke/workflows
@@ -18,10 +18,14 @@ Classifier: License :: OSI Approved :: GNU General Public License v2 or later (G
18
18
  Classifier: Operating System :: MacOS :: MacOS X
19
19
  Classifier: Operating System :: Microsoft :: Windows
20
20
  Classifier: Operating System :: POSIX :: Linux
21
+ Classifier: Operating System :: POSIX :: BSD :: FreeBSD
22
+ Classifier: Operating System :: POSIX :: BSD :: NetBSD
23
+ Classifier: Operating System :: POSIX :: BSD :: OpenBSD
21
24
  Classifier: Programming Language :: Python :: 3
22
25
  Classifier: Programming Language :: Python :: 3.11
23
26
  Classifier: Programming Language :: Python :: 3.12
24
27
  Classifier: Programming Language :: Python :: 3.13
28
+ Classifier: Programming Language :: Python :: 3.14
25
29
  Classifier: Programming Language :: Python :: Implementation :: CPython
26
30
  Classifier: Programming Language :: Unix Shell
27
31
  Classifier: Topic :: Documentation :: Sphinx
@@ -48,8 +52,8 @@ Requires-Dist: boltons>=24.0.0
48
52
  Requires-Dist: bump-my-version<1.1.1,>=0.32.2
49
53
  Requires-Dist: click-extra~=6.0.0
50
54
  Requires-Dist: extra-platforms~=3.2.0
51
- Requires-Dist: gitignore-parser~=0.1.13
52
55
  Requires-Dist: packaging~=25.0
56
+ Requires-Dist: py-walk~=0.3.3
53
57
  Requires-Dist: PyDriller~=2.6
54
58
  Requires-Dist: pyproject-metadata~=0.9.0
55
59
  Requires-Dist: pyyaml~=6.0.0
@@ -0,0 +1,14 @@
1
+ gha_utils/__init__.py,sha256=HCe5G0ysXtBmQ8s34Mi5DeYCvYLFSPYzWcV4pPfYdGw,866
2
+ gha_utils/__main__.py,sha256=Dck9BjpLXmIRS83k0mghAMcYVYiMiFLltQdfRuMSP_Q,1703
3
+ gha_utils/changelog.py,sha256=JR7iQrWjLoIOpVNe6iXQSyEii82_hM_zrYpR7QO_Uxo,5777
4
+ gha_utils/cli.py,sha256=hwvjKEUvctmtJL2aliV0dytF67uHjGVuUKv17j_lMlg,15235
5
+ gha_utils/mailmap.py,sha256=oQt3m0hj-mwg7WxsuJQXWeQTFjlkqTgRNjYsUv7dlYQ,7013
6
+ gha_utils/matrix.py,sha256=eBAU3bKrCif7FQ74EWhK_AwDcNUkGp8Om1NtlFdYJpI,12431
7
+ gha_utils/metadata.py,sha256=hySTDkjPS2NMKvFlmEmpy4F7rwrjmST_ZiygmF9OOZk,58839
8
+ gha_utils/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ gha_utils/test_plan.py,sha256=AE8Mf1vSQG5EZTytoTts-gzMwUg2Zy21gUwkMlzXT94,13394
10
+ gha_utils-4.21.0.dist-info/METADATA,sha256=tdjkQaFwLnJUNm-3K8lOi4crw_n_HQWn-CNtW6BaD08,21420
11
+ gha_utils-4.21.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
+ gha_utils-4.21.0.dist-info/entry_points.txt,sha256=8bJOwQYf9ZqsLhBR6gUCzvwLNI9f8tiiBrJ3AR0EK4o,54
13
+ gha_utils-4.21.0.dist-info/top_level.txt,sha256=C94Blb61YkkyPBwCdM3J_JPDjWH0lnKa5nGZeZ5M6yE,10
14
+ gha_utils-4.21.0.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- gha_utils/__init__.py,sha256=lMEtwh0IU3Npa59dfCNSMgI1ilQQl-4K09Qjz3bjXIg,866
2
- gha_utils/__main__.py,sha256=Dck9BjpLXmIRS83k0mghAMcYVYiMiFLltQdfRuMSP_Q,1703
3
- gha_utils/changelog.py,sha256=JR7iQrWjLoIOpVNe6iXQSyEii82_hM_zrYpR7QO_Uxo,5777
4
- gha_utils/cli.py,sha256=jmrSI05qfrkxcB3fQuiB6xf4eumXqg-MBiCITdeW104,15273
5
- gha_utils/mailmap.py,sha256=g3LQiPNjHsAgCbEYOJcQwdlXqxzmFh697vv2sxHZq-s,7014
6
- gha_utils/matrix.py,sha256=eBAU3bKrCif7FQ74EWhK_AwDcNUkGp8Om1NtlFdYJpI,12431
7
- gha_utils/metadata.py,sha256=lAnBC2p17cwpgzpTFw5qYjqvI29wsWtvMlBx4B3rFc4,53791
8
- gha_utils/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- gha_utils/test_plan.py,sha256=AE8Mf1vSQG5EZTytoTts-gzMwUg2Zy21gUwkMlzXT94,13394
10
- gha_utils-4.19.1.dist-info/METADATA,sha256=CTP2UDJIakJ9NC7QWc2JcLtoczOjznA1wX169iG9VH8,21212
11
- gha_utils-4.19.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
- gha_utils-4.19.1.dist-info/entry_points.txt,sha256=8bJOwQYf9ZqsLhBR6gUCzvwLNI9f8tiiBrJ3AR0EK4o,54
13
- gha_utils-4.19.1.dist-info/top_level.txt,sha256=C94Blb61YkkyPBwCdM3J_JPDjWH0lnKa5nGZeZ5M6yE,10
14
- gha_utils-4.19.1.dist-info/RECORD,,