Skip to content

Commit 097f802

Browse files
jdclaude
andcommitted
feat(freeze): add scheduled freeze CLI commands
Add `mergify freeze` command group with list, create, update, and delete subcommands for managing scheduled merge freezes. Supports both Mergify application keys and GitHub tokens for authentication, auto-detects repository from git remote, and provides both table and JSON output. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Change-Id: I414d168066f7b5070e75520bdcbb8189d694ad72 Claude-Session-Id: 77d8be2d-854a-4d80-ac2d-4489fa50e31d
1 parent 89878c9 commit 097f802

File tree

7 files changed

+930
-0
lines changed

7 files changed

+930
-0
lines changed

mergify_cli/cli.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
from mergify_cli import VERSION
2525
from mergify_cli.ci import cli as ci_cli_mod
26+
from mergify_cli.freeze import cli as freeze_cli_mod
2627
from mergify_cli.stack import cli as stack_cli_mod
2728

2829

@@ -44,6 +45,7 @@ def cli(
4445

4546
cli.add_command(stack_cli_mod.stack)
4647
cli.add_command(ci_cli_mod.ci)
48+
cli.add_command(freeze_cli_mod.freeze)
4749

4850

4951
def main() -> None:

mergify_cli/freeze/__init__.py

Whitespace-only changes.

mergify_cli/freeze/api.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
from __future__ import annotations
2+
3+
import typing
4+
5+
6+
if typing.TYPE_CHECKING:
7+
import datetime
8+
import uuid
9+
10+
import httpx
11+
12+
13+
class ScheduledFreezeResponse(typing.TypedDict, total=False):
14+
id: typing.Required[str]
15+
reason: typing.Required[str]
16+
start: typing.Required[str]
17+
end: typing.Required[str | None]
18+
timezone: typing.Required[str]
19+
matching_conditions: typing.Required[list[str]]
20+
exclude_conditions: list[str]
21+
22+
23+
class ScheduledFreezePayload(typing.TypedDict, total=False):
24+
reason: typing.Required[str]
25+
start: typing.Required[str | None]
26+
end: typing.Required[str | None]
27+
timezone: typing.Required[str]
28+
matching_conditions: typing.Required[list[str]]
29+
exclude_conditions: list[str]
30+
31+
32+
async def list_freezes(
33+
client: httpx.AsyncClient,
34+
repository: str,
35+
) -> list[ScheduledFreezeResponse]:
36+
response = await client.get(
37+
f"/v1/repos/{repository}/scheduled_freeze",
38+
)
39+
data: dict[str, list[ScheduledFreezeResponse]] = response.json()
40+
return data["scheduled_freezes"]
41+
42+
43+
async def create_freeze(
44+
client: httpx.AsyncClient,
45+
repository: str,
46+
*,
47+
reason: str,
48+
timezone: str,
49+
matching_conditions: list[str],
50+
start: datetime.datetime | None = None,
51+
end: datetime.datetime | None = None,
52+
exclude_conditions: list[str] | None = None,
53+
) -> ScheduledFreezeResponse:
54+
payload: ScheduledFreezePayload = {
55+
"reason": reason,
56+
"start": start.isoformat() if start is not None else None,
57+
"end": end.isoformat() if end is not None else None,
58+
"timezone": timezone,
59+
"matching_conditions": matching_conditions,
60+
}
61+
if exclude_conditions:
62+
payload["exclude_conditions"] = exclude_conditions
63+
64+
response = await client.post(
65+
f"/v1/repos/{repository}/scheduled_freeze",
66+
json=payload,
67+
)
68+
return response.json() # type: ignore[no-any-return]
69+
70+
71+
async def update_freeze(
72+
client: httpx.AsyncClient,
73+
repository: str,
74+
freeze_id: uuid.UUID,
75+
*,
76+
reason: str,
77+
timezone: str,
78+
matching_conditions: list[str],
79+
start: datetime.datetime | None = None,
80+
end: datetime.datetime | None = None,
81+
exclude_conditions: list[str] | None = None,
82+
) -> ScheduledFreezeResponse:
83+
payload: ScheduledFreezePayload = {
84+
"reason": reason,
85+
"start": start.isoformat() if start is not None else None,
86+
"end": end.isoformat() if end is not None else None,
87+
"timezone": timezone,
88+
"matching_conditions": matching_conditions,
89+
}
90+
if exclude_conditions is not None:
91+
payload["exclude_conditions"] = exclude_conditions
92+
93+
response = await client.patch(
94+
f"/v1/repos/{repository}/scheduled_freeze/{freeze_id}",
95+
json=payload,
96+
)
97+
return response.json() # type: ignore[no-any-return]
98+
99+
100+
async def delete_freeze(
101+
client: httpx.AsyncClient,
102+
repository: str,
103+
freeze_id: uuid.UUID,
104+
*,
105+
delete_reason: str | None = None,
106+
) -> None:
107+
url = f"/v1/repos/{repository}/scheduled_freeze/{freeze_id}"
108+
if delete_reason is not None:
109+
await client.request(
110+
"DELETE",
111+
url,
112+
json={"delete_reason": delete_reason},
113+
)
114+
else:
115+
await client.delete(url)

0 commit comments

Comments
 (0)