mergify-cli 2026.5.5.1__py3-none-win_amd64.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.
Files changed (127) hide show
  1. mergify_cli/__init__.py +20 -0
  2. mergify_cli/__main__.py +22 -0
  3. mergify_cli/_globals.py +30 -0
  4. mergify_cli/ci/__init__.py +0 -0
  5. mergify_cli/ci/cli.py +391 -0
  6. mergify_cli/ci/detector.py +297 -0
  7. mergify_cli/ci/git_refs/__init__.py +0 -0
  8. mergify_cli/ci/git_refs/detector.py +173 -0
  9. mergify_cli/ci/github_event.py +36 -0
  10. mergify_cli/ci/junit_processing/__init__.py +0 -0
  11. mergify_cli/ci/junit_processing/cli.py +306 -0
  12. mergify_cli/ci/junit_processing/junit.py +283 -0
  13. mergify_cli/ci/junit_processing/quarantine.py +149 -0
  14. mergify_cli/ci/junit_processing/upload.py +64 -0
  15. mergify_cli/ci/queue/__init__.py +0 -0
  16. mergify_cli/ci/queue/metadata.py +88 -0
  17. mergify_cli/ci/queue/notes.py +74 -0
  18. mergify_cli/ci/scopes/__init__.py +0 -0
  19. mergify_cli/ci/scopes/changed_files.py +120 -0
  20. mergify_cli/ci/scopes/cli.py +286 -0
  21. mergify_cli/ci/scopes/config/__init__.py +20 -0
  22. mergify_cli/ci/scopes/config/root.py +40 -0
  23. mergify_cli/ci/scopes/config/scopes.py +81 -0
  24. mergify_cli/ci/scopes/exceptions.py +5 -0
  25. mergify_cli/cli.py +107 -0
  26. mergify_cli/dym.py +43 -0
  27. mergify_cli/exit_codes.py +39 -0
  28. mergify_cli/freeze/__init__.py +0 -0
  29. mergify_cli/freeze/api.py +125 -0
  30. mergify_cli/freeze/cli.py +363 -0
  31. mergify_cli/github_types.py +29 -0
  32. mergify_cli/queue/__init__.py +0 -0
  33. mergify_cli/queue/api.py +161 -0
  34. mergify_cli/queue/cli.py +586 -0
  35. mergify_cli/stack/__init__.py +0 -0
  36. mergify_cli/stack/approvals.py +190 -0
  37. mergify_cli/stack/changes.py +378 -0
  38. mergify_cli/stack/checkout.py +113 -0
  39. mergify_cli/stack/cli.py +697 -0
  40. mergify_cli/stack/edit.py +51 -0
  41. mergify_cli/stack/hooks/scripts/commit-msg.sh +91 -0
  42. mergify_cli/stack/hooks/scripts/post-commit.sh +41 -0
  43. mergify_cli/stack/hooks/scripts/pre-push.sh +53 -0
  44. mergify_cli/stack/hooks/scripts/prepare-commit-msg.sh +109 -0
  45. mergify_cli/stack/hooks/wrappers/commit-msg +11 -0
  46. mergify_cli/stack/hooks/wrappers/post-commit +14 -0
  47. mergify_cli/stack/hooks/wrappers/pre-push +11 -0
  48. mergify_cli/stack/hooks/wrappers/prepare-commit-msg +11 -0
  49. mergify_cli/stack/list.py +488 -0
  50. mergify_cli/stack/list_schema.py +82 -0
  51. mergify_cli/stack/move.py +97 -0
  52. mergify_cli/stack/new.py +79 -0
  53. mergify_cli/stack/note.py +138 -0
  54. mergify_cli/stack/open.py +125 -0
  55. mergify_cli/stack/push.py +1390 -0
  56. mergify_cli/stack/reorder.py +286 -0
  57. mergify_cli/stack/replay.py +233 -0
  58. mergify_cli/stack/setup.py +249 -0
  59. mergify_cli/stack/slug.py +208 -0
  60. mergify_cli/stack/squash.py +166 -0
  61. mergify_cli/stack/sync.py +347 -0
  62. mergify_cli/tests/__init__.py +0 -0
  63. mergify_cli/tests/ci/__init__.py +0 -0
  64. mergify_cli/tests/ci/fixtures/junit_example.xml +47 -0
  65. mergify_cli/tests/ci/fixtures/junit_example_nested_single_suite.xml +7 -0
  66. mergify_cli/tests/ci/fixtures/junit_example_single_suite.xml +12 -0
  67. mergify_cli/tests/ci/fixtures/report.xml +15 -0
  68. mergify_cli/tests/ci/fixtures/report_all_pass.xml +8 -0
  69. mergify_cli/tests/ci/fixtures/report_invalid.xml +1 -0
  70. mergify_cli/tests/ci/fixtures/report_mixed.xml +15 -0
  71. mergify_cli/tests/ci/fixtures/report_no_testcases.xml +5 -0
  72. mergify_cli/tests/ci/git_refs/test_git_refs_detector.py +398 -0
  73. mergify_cli/tests/ci/junit_processing/__init__.py +0 -0
  74. mergify_cli/tests/ci/junit_processing/test_check_failing_spans.py +223 -0
  75. mergify_cli/tests/ci/junit_processing/test_cli.py +947 -0
  76. mergify_cli/tests/ci/junit_processing/test_upload.py +111 -0
  77. mergify_cli/tests/ci/pull_request.json +449 -0
  78. mergify_cli/tests/ci/push_event.json +46 -0
  79. mergify_cli/tests/ci/queue/__init__.py +0 -0
  80. mergify_cli/tests/ci/queue/test_metadata.py +181 -0
  81. mergify_cli/tests/ci/queue/test_notes.py +136 -0
  82. mergify_cli/tests/ci/scopes/__init__.py +0 -0
  83. mergify_cli/tests/ci/scopes/test_changed_files.py +246 -0
  84. mergify_cli/tests/ci/scopes/test_cli.py +756 -0
  85. mergify_cli/tests/ci/test_cli.py +719 -0
  86. mergify_cli/tests/ci/test_cli_exit_codes.py +53 -0
  87. mergify_cli/tests/ci/test_detector.py +546 -0
  88. mergify_cli/tests/ci/test_github_event.py +75 -0
  89. mergify_cli/tests/ci/test_junit.py +945 -0
  90. mergify_cli/tests/conftest.py +156 -0
  91. mergify_cli/tests/freeze/__init__.py +0 -0
  92. mergify_cli/tests/freeze/test_cli.py +514 -0
  93. mergify_cli/tests/queue/__init__.py +0 -0
  94. mergify_cli/tests/queue/test_cli.py +528 -0
  95. mergify_cli/tests/queue/test_show.py +363 -0
  96. mergify_cli/tests/queue/test_skill.py +79 -0
  97. mergify_cli/tests/stack/__init__.py +0 -0
  98. mergify_cli/tests/stack/test_approvals.py +713 -0
  99. mergify_cli/tests/stack/test_checkout.py +235 -0
  100. mergify_cli/tests/stack/test_edit.py +203 -0
  101. mergify_cli/tests/stack/test_list.py +771 -0
  102. mergify_cli/tests/stack/test_move.py +333 -0
  103. mergify_cli/tests/stack/test_new.py +206 -0
  104. mergify_cli/tests/stack/test_note.py +292 -0
  105. mergify_cli/tests/stack/test_open.py +441 -0
  106. mergify_cli/tests/stack/test_push.py +3063 -0
  107. mergify_cli/tests/stack/test_reorder.py +361 -0
  108. mergify_cli/tests/stack/test_replay.py +353 -0
  109. mergify_cli/tests/stack/test_setup.py +517 -0
  110. mergify_cli/tests/stack/test_slug.py +115 -0
  111. mergify_cli/tests/stack/test_squash.py +496 -0
  112. mergify_cli/tests/stack/test_squash_cli.py +53 -0
  113. mergify_cli/tests/stack/test_sync.py +1220 -0
  114. mergify_cli/tests/test_cli.py +150 -0
  115. mergify_cli/tests/test_dym.py +73 -0
  116. mergify_cli/tests/test_exit_code_contract.py +66 -0
  117. mergify_cli/tests/test_exit_codes.py +42 -0
  118. mergify_cli/tests/test_port_status.py +158 -0
  119. mergify_cli/tests/test_utils.py +398 -0
  120. mergify_cli/tests/utils.py +252 -0
  121. mergify_cli/utils.py +475 -0
  122. mergify_cli-2026.5.5.1.data/scripts/mergify.exe +0 -0
  123. mergify_cli-2026.5.5.1.dist-info/METADATA +94 -0
  124. mergify_cli-2026.5.5.1.dist-info/RECORD +127 -0
  125. mergify_cli-2026.5.5.1.dist-info/WHEEL +4 -0
  126. mergify_cli-2026.5.5.1.dist-info/licenses/LICENSE +176 -0
  127. mergify_cli-2026.5.5.1.dist-info/sboms/mergify-cli.cyclonedx.json +6725 -0
@@ -0,0 +1,20 @@
1
+ #
2
+ # Copyright © 2021-2026 Mergify SAS
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may
5
+ # not use this file except in compliance with the License. You may obtain
6
+ # a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+ # License for the specific language governing permissions and limitations
14
+ # under the License.
15
+
16
+ from __future__ import annotations
17
+
18
+ from mergify_cli._globals import VERSION as VERSION
19
+ from mergify_cli._globals import console as console
20
+ from mergify_cli._globals import console_error as console_error
@@ -0,0 +1,22 @@
1
+ #
2
+ # Copyright © 2021-2026 Mergify SAS
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may
5
+ # not use this file except in compliance with the License. You may obtain
6
+ # a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+ # License for the specific language governing permissions and limitations
14
+ # under the License.
15
+
16
+ from __future__ import annotations
17
+
18
+ from mergify_cli.cli import main
19
+
20
+
21
+ if __name__ == "__main__":
22
+ main()
@@ -0,0 +1,30 @@
1
+ #
2
+ # Copyright © 2021-2026 Mergify SAS
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may
5
+ # not use this file except in compliance with the License. You may obtain
6
+ # a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+ # License for the specific language governing permissions and limitations
14
+ # under the License.
15
+
16
+ from __future__ import annotations
17
+
18
+ import importlib.metadata
19
+
20
+ import rich.console
21
+
22
+
23
+ console = rich.console.Console(log_path=False, log_time=False)
24
+
25
+ VERSION = importlib.metadata.version("mergify-cli")
26
+
27
+
28
+ def console_error(message: str) -> None:
29
+ """Print a consistently formatted error message."""
30
+ console.print(f"error: {message}", style="red", markup=False)
File without changes
mergify_cli/ci/cli.py ADDED
@@ -0,0 +1,391 @@
1
+ from __future__ import annotations
2
+
3
+ import glob
4
+ import json
5
+ import os
6
+ import pathlib
7
+ import shlex
8
+ import uuid
9
+
10
+ import click
11
+
12
+ from mergify_cli import utils
13
+ from mergify_cli.ci import detector
14
+ from mergify_cli.ci.git_refs import detector as git_refs_detector
15
+ from mergify_cli.ci.junit_processing import cli as junit_processing_cli
16
+ from mergify_cli.ci.queue import metadata as queue_metadata
17
+ from mergify_cli.ci.scopes import cli as scopes_cli
18
+ from mergify_cli.ci.scopes import exceptions as scopes_exc
19
+ from mergify_cli.dym import DYMGroup
20
+ from mergify_cli.exit_codes import ExitCode
21
+
22
+
23
+ def _expand_junit_patterns(
24
+ ctx: click.Context,
25
+ param: click.Parameter,
26
+ value: tuple[str, ...],
27
+ ) -> tuple[str, ...]:
28
+ # Accept raw glob patterns and expand them here so callers don't have to
29
+ # rely on shell expansion — preferable for large test suites.
30
+ results: dict[str, None] = {}
31
+ for entry in value:
32
+ literal = pathlib.Path(entry)
33
+ # Existing literal paths take precedence so filenames that happen to
34
+ # contain glob metacharacters (e.g. `report[1].xml`) keep working.
35
+ if literal.is_file():
36
+ results.setdefault(entry, None)
37
+ continue
38
+
39
+ if glob.has_magic(entry):
40
+ matches = [
41
+ match
42
+ for match in glob.iglob(entry, recursive=True) # noqa: PTH207
43
+ if pathlib.Path(match).is_file()
44
+ ]
45
+ if not matches:
46
+ raise click.BadParameter(
47
+ f"Pattern '{entry}' did not match any file.\n\n"
48
+ "This usually indicates that a previous CI step failed to generate the test results.\n"
49
+ "Please check if your test execution step completed successfully and produced the expected output files.",
50
+ ctx=ctx,
51
+ param=param,
52
+ )
53
+
54
+ results.update(dict.fromkeys(matches))
55
+ continue
56
+
57
+ if literal.is_dir():
58
+ raise click.BadParameter(
59
+ f"'{entry}' is a directory, not a JUnit XML file.\n\n"
60
+ "Pass a file path or a quoted glob pattern (e.g. 'reports/**/*.xml') instead.",
61
+ ctx=ctx,
62
+ param=param,
63
+ )
64
+
65
+ raise click.BadParameter(
66
+ f"JUnit XML file '{entry}' does not exist.\n\n"
67
+ "This usually indicates that a previous CI step failed to generate the test results.\n"
68
+ "Please check if your test execution step completed successfully and produced the expected output file.",
69
+ ctx=ctx,
70
+ param=param,
71
+ )
72
+
73
+ return tuple(results)
74
+
75
+
76
+ def _process_tests_target_branch(
77
+ _ctx: click.Context,
78
+ _param: click.Parameter,
79
+ value: str | None,
80
+ ) -> str | None:
81
+ """Process the tests_target_branch parameter to strip refs/heads/ prefix from GITHUB_REF."""
82
+ return value.removeprefix("refs/heads/") if value else value
83
+
84
+
85
+ @click.group(
86
+ cls=DYMGroup,
87
+ invoke_without_command=True,
88
+ help="Mergify's CI related commands",
89
+ )
90
+ @click.pass_context
91
+ def ci(ctx: click.Context) -> None:
92
+ if ctx.invoked_subcommand is None:
93
+ click.echo(ctx.get_help())
94
+
95
+
96
+ @ci.command(help="Upload JUnit XML reports", deprecated="Use `junit-process` instead")
97
+ @click.option(
98
+ "--api-url",
99
+ "-u",
100
+ help="URL of the Mergify API",
101
+ required=True,
102
+ envvar="MERGIFY_API_URL",
103
+ default=utils.MERGIFY_API_DEFAULT_URL,
104
+ show_default=True,
105
+ )
106
+ @click.option(
107
+ "--token",
108
+ "-t",
109
+ help="CI Issues Application Key",
110
+ required=True,
111
+ envvar="MERGIFY_TOKEN",
112
+ )
113
+ @click.option(
114
+ "--repository",
115
+ "-r",
116
+ help="Repository full name (owner/repo)",
117
+ required=True,
118
+ default=detector.get_github_repository,
119
+ )
120
+ @click.option(
121
+ "--test-framework",
122
+ help="Test framework",
123
+ )
124
+ @click.option(
125
+ "--test-language",
126
+ help="Test language",
127
+ )
128
+ @click.option(
129
+ "--tests-target-branch",
130
+ "-ttb",
131
+ help="The branch used to check if failing tests can be ignored with Mergify's Quarantine.",
132
+ required=True,
133
+ default=detector.get_tests_target_branch,
134
+ callback=_process_tests_target_branch,
135
+ )
136
+ @click.option(
137
+ "--test-exit-code",
138
+ "-e",
139
+ help="Exit code of the test runner process. Used to detect silent failures where the runner crashed but the JUnit report appears clean.",
140
+ type=int,
141
+ required=False,
142
+ default=None,
143
+ envvar="MERGIFY_TEST_EXIT_CODE",
144
+ )
145
+ @click.argument(
146
+ "files",
147
+ nargs=-1,
148
+ required=True,
149
+ callback=_expand_junit_patterns,
150
+ )
151
+ @utils.run_with_asyncio
152
+ async def junit_upload(
153
+ *,
154
+ api_url: str,
155
+ token: str,
156
+ repository: str,
157
+ test_framework: str | None,
158
+ test_language: str | None,
159
+ tests_target_branch: str,
160
+ test_exit_code: int | None,
161
+ files: tuple[str, ...],
162
+ ) -> None:
163
+ await junit_processing_cli.process_junit_files(
164
+ api_url=api_url,
165
+ token=token,
166
+ repository=repository,
167
+ test_framework=test_framework,
168
+ test_language=test_language,
169
+ tests_target_branch=tests_target_branch,
170
+ files=files,
171
+ test_exit_code=test_exit_code,
172
+ )
173
+
174
+
175
+ @ci.command(
176
+ help=(
177
+ "Upload JUnit XML reports and ignore failed tests with Mergify's CI"
178
+ " Insights Quarantine.\n\nFILES can be literal paths or quoted glob"
179
+ " patterns (e.g. 'reports/**/*.xml'); quoting lets Mergify expand the"
180
+ " pattern rather than the shell, which is recommended for large test"
181
+ " suites."
182
+ ),
183
+ short_help=(
184
+ "Upload JUnit XML reports and ignore failed tests with Mergify's CI"
185
+ " Insights Quarantine"
186
+ ),
187
+ )
188
+ @click.option(
189
+ "--api-url",
190
+ "-u",
191
+ help="URL of the Mergify API",
192
+ required=True,
193
+ envvar="MERGIFY_API_URL",
194
+ default=utils.MERGIFY_API_DEFAULT_URL,
195
+ show_default=True,
196
+ )
197
+ @click.option(
198
+ "--token",
199
+ "-t",
200
+ help="CI Issues Application Key",
201
+ required=True,
202
+ envvar="MERGIFY_TOKEN",
203
+ )
204
+ @click.option(
205
+ "--repository",
206
+ "-r",
207
+ help="Repository full name (owner/repo)",
208
+ required=True,
209
+ default=detector.get_github_repository,
210
+ )
211
+ @click.option(
212
+ "--test-framework",
213
+ help="Test framework",
214
+ )
215
+ @click.option(
216
+ "--test-language",
217
+ help="Test language",
218
+ )
219
+ @click.option(
220
+ "--tests-target-branch",
221
+ "-ttb",
222
+ help="The branch used to check if failing tests can be ignored with Mergify's Quarantine.",
223
+ required=True,
224
+ default=detector.get_tests_target_branch,
225
+ callback=_process_tests_target_branch,
226
+ )
227
+ @click.option(
228
+ "--test-exit-code",
229
+ "-e",
230
+ help="Exit code of the test runner process. Used to detect silent failures where the runner crashed but the JUnit report appears clean.",
231
+ type=int,
232
+ required=False,
233
+ default=None,
234
+ envvar="MERGIFY_TEST_EXIT_CODE",
235
+ )
236
+ @click.argument(
237
+ "files",
238
+ nargs=-1,
239
+ required=True,
240
+ callback=_expand_junit_patterns,
241
+ )
242
+ @utils.run_with_asyncio
243
+ async def junit_process(
244
+ *,
245
+ api_url: str,
246
+ token: str,
247
+ repository: str,
248
+ test_framework: str | None,
249
+ test_language: str | None,
250
+ tests_target_branch: str,
251
+ test_exit_code: int | None,
252
+ files: tuple[str, ...],
253
+ ) -> None:
254
+ await junit_processing_cli.process_junit_files(
255
+ api_url=api_url,
256
+ token=token,
257
+ repository=repository,
258
+ test_framework=test_framework,
259
+ test_language=test_language,
260
+ tests_target_branch=tests_target_branch,
261
+ files=files,
262
+ test_exit_code=test_exit_code,
263
+ )
264
+
265
+
266
+ @ci.command(
267
+ help="""Give the base/head git references of the pull request""",
268
+ short_help="""Give the base/head git references of the pull request""",
269
+ )
270
+ @click.option(
271
+ "--format",
272
+ "output_format",
273
+ type=click.Choice(["text", "shell", "json"]),
274
+ default="text",
275
+ show_default=True,
276
+ help=(
277
+ "Output format. 'text' is human-readable. "
278
+ "'shell' emits MERGIFY_GIT_REFS_{BASE,HEAD,SOURCE}=... lines for `eval`. "
279
+ "'json' emits a single-line JSON object."
280
+ ),
281
+ )
282
+ def git_refs(output_format: str) -> None:
283
+ ref = git_refs_detector.detect()
284
+
285
+ if output_format == "shell":
286
+ click.echo(f"MERGIFY_GIT_REFS_BASE={shlex.quote(ref.base or '')}")
287
+ click.echo(f"MERGIFY_GIT_REFS_HEAD={shlex.quote(ref.head)}")
288
+ click.echo(f"MERGIFY_GIT_REFS_SOURCE={shlex.quote(ref.source)}")
289
+ elif output_format == "json":
290
+ click.echo(
291
+ json.dumps({"base": ref.base, "head": ref.head, "source": ref.source}),
292
+ )
293
+ else:
294
+ click.echo(f"Base: {ref.base}")
295
+ click.echo(f"Head: {ref.head}")
296
+
297
+ ref.maybe_write_to_github_outputs()
298
+ ref.maybe_write_to_buildkite_metadata()
299
+
300
+
301
+ @ci.command(
302
+ help="""Give the list scope impacted by changed files""",
303
+ short_help="""Give the list scope impacted by changed files""",
304
+ )
305
+ @click.option(
306
+ "--config",
307
+ "config_path",
308
+ type=click.Path(dir_okay=False),
309
+ envvar="MERGIFY_CONFIG_PATH",
310
+ default=detector.get_mergify_config_path,
311
+ help="Path to YAML config file.",
312
+ )
313
+ @click.option(
314
+ "--base",
315
+ help="The base git reference to use to look for changed files",
316
+ )
317
+ @click.option(
318
+ "--head",
319
+ help="The head git reference to use to look for changed files",
320
+ )
321
+ @click.option(
322
+ "--write",
323
+ "-w",
324
+ type=click.Path(),
325
+ help="Write the detected scopes to a file (json).",
326
+ )
327
+ def scopes(
328
+ config_path: str | None,
329
+ write: str | None = None,
330
+ head: str | None = None,
331
+ base: str | None = None,
332
+ ) -> None:
333
+ # Empty envvar (MERGIFY_CONFIG_PATH="") should fall back to autodetect
334
+ if config_path is not None and not config_path:
335
+ config_path = detector.get_mergify_config_path()
336
+
337
+ if config_path is None:
338
+ locations = ", ".join(detector.MERGIFY_CONFIG_PATHS)
339
+ msg = f"Mergify configuration file not found. Looked in: {locations}"
340
+ raise utils.MergifyError(msg, exit_code=ExitCode.CONFIGURATION_ERROR)
341
+
342
+ if not pathlib.Path(config_path).is_file():
343
+ msg = f"Config file '{config_path}' does not exist."
344
+ raise utils.MergifyError(msg, exit_code=ExitCode.CONFIGURATION_ERROR)
345
+
346
+ if base or head:
347
+ ref = git_refs_detector.References(
348
+ base=base,
349
+ head=head or "HEAD",
350
+ source="manual",
351
+ )
352
+ else:
353
+ ref = git_refs_detector.detect()
354
+
355
+ try:
356
+ scopes = scopes_cli.detect(
357
+ config_path=config_path,
358
+ references=ref,
359
+ )
360
+ except scopes_exc.ScopesError as e:
361
+ raise utils.MergifyError(
362
+ str(e),
363
+ exit_code=ExitCode.CONFIGURATION_ERROR,
364
+ ) from e
365
+
366
+ if write is not None:
367
+ scopes.save_to_file(write)
368
+
369
+
370
+ @ci.command(
371
+ help="""Output merge queue batch metadata from the current pull request event""",
372
+ short_help="""Output merge queue batch metadata""",
373
+ )
374
+ def queue_info() -> None:
375
+ metadata = queue_metadata.detect()
376
+ if metadata is None:
377
+ raise utils.MergifyError(
378
+ "Not running in a merge queue context. "
379
+ "This command must be run on a merge queue draft pull request.",
380
+ exit_code=ExitCode.INVALID_STATE,
381
+ )
382
+
383
+ click.echo(json.dumps(metadata, indent=2))
384
+
385
+ gha = os.environ.get("GITHUB_OUTPUT")
386
+ if gha:
387
+ delimiter = f"ghadelimiter_{uuid.uuid4()}"
388
+ with pathlib.Path(gha).open("a", encoding="utf-8") as fh:
389
+ fh.write(
390
+ f"queue_metadata<<{delimiter}\n{json.dumps(metadata)}\n{delimiter}\n",
391
+ )