Skip to content

Commit c3dbb6e

Browse files
jdclaude
andcommitted
feat(stack): add hooks subcommand for status display
Add a new `stack hooks` command that displays the status of both git hooks and Claude hooks. The existing `stack setup` command becomes an alias for `stack hooks --setup`. - `mergify stack hooks` shows detailed status of all hooks - `mergify stack hooks --setup` installs/upgrades hooks - `mergify stack hooks --setup --force` forces wrapper reinstall - `mergify stack setup` works as before (backwards compatible) - `mergify stack setup --check` shows status (deprecated in favor of `hooks`) Co-Authored-By: Claude Opus 4.5 <[email protected]> Change-Id: I45910d02a514ed45f8b6d0becf58d36dce2a51f5 Claude-Session-Id: 3dabd187-059b-40b7-9a52-a2542a27c752
1 parent c5a89e6 commit c3dbb6e

File tree

3 files changed

+474
-58
lines changed

3 files changed

+474
-58
lines changed

mergify_cli/stack/cli.py

Lines changed: 110 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import asyncio
44
import os
5+
from typing import Any
56
from urllib import parse
67

78
import click
@@ -96,7 +97,109 @@ def github_server_to_context(
9697
)
9798

9899

99-
@stack.command(help="Configure the required git commit-msg hooks") # type: ignore[untyped-decorator]
100+
def _print_hooks_status(status: dict[str, Any]) -> None:
101+
"""Print hooks status in a formatted table."""
102+
needs_setup = False
103+
needs_force = False
104+
105+
# Git hooks section
106+
console.print("\nGit Hooks Status:\n")
107+
git_hooks = status["git_hooks"]
108+
109+
for hook_name, info in git_hooks.items():
110+
console.print(f" {hook_name}:")
111+
112+
wrapper_status = info["wrapper_status"]
113+
wrapper_path = info["wrapper_path"]
114+
115+
if wrapper_status == stack_setup_mod.WrapperStatus.INSTALLED:
116+
console.print(f" Wrapper: [green]installed[/] ({wrapper_path})")
117+
elif wrapper_status == stack_setup_mod.WrapperStatus.LEGACY:
118+
console.print(
119+
" Wrapper: [yellow]legacy[/] (needs --force to migrate)",
120+
)
121+
needs_force = True
122+
else: # MISSING
123+
console.print(" Wrapper: [red]not installed[/]")
124+
needs_setup = True
125+
126+
script_path = info["script_path"]
127+
if info["script_installed"]:
128+
if info["script_needs_update"]:
129+
console.print(f" Script: [yellow]needs update[/] ({script_path})")
130+
needs_setup = True
131+
else:
132+
console.print(f" Script: [green]up to date[/] ({script_path})")
133+
else:
134+
console.print(" Script: [red]not installed[/]")
135+
needs_setup = True
136+
137+
console.print()
138+
139+
# Claude hooks section
140+
console.print("Claude Hooks Status:\n")
141+
claude_hooks = status["claude_hooks"]
142+
143+
for script_name, script_info in claude_hooks["scripts"].items():
144+
console.print(f" {script_name}:")
145+
if script_info["installed"]:
146+
if script_info["needs_update"]:
147+
console.print(
148+
f" Script: [yellow]needs update[/] ({script_info['path']})",
149+
)
150+
needs_setup = True
151+
else:
152+
console.print(
153+
f" Script: [green]up to date[/] ({script_info['path']})",
154+
)
155+
else:
156+
console.print(" Script: [red]not installed[/]")
157+
needs_setup = True
158+
console.print()
159+
160+
console.print(" settings.json:")
161+
if claude_hooks["settings_installed"]:
162+
console.print(
163+
f" Hook: [green]configured[/] ({claude_hooks['settings_path']})",
164+
)
165+
else:
166+
console.print(" Hook: [red]not configured[/]")
167+
needs_setup = True
168+
console.print()
169+
170+
if needs_setup or needs_force:
171+
console.print("Run 'mergify stack hooks --setup' to install/upgrade hooks.")
172+
if needs_force:
173+
console.print(
174+
"Run 'mergify stack hooks --setup --force' to force reinstall wrappers.",
175+
)
176+
else:
177+
console.print("[green]All hooks are up to date.[/]")
178+
179+
180+
@stack.command(help="Show git hooks status and manage installation") # type: ignore[untyped-decorator]
181+
@click.option(
182+
"--setup",
183+
"do_setup",
184+
is_flag=True,
185+
help="Install or upgrade hooks",
186+
)
187+
@click.option(
188+
"--force",
189+
"-f",
190+
is_flag=True,
191+
help="Force reinstall wrappers (use with --setup)",
192+
)
193+
@utils.run_with_asyncio
194+
async def hooks(*, do_setup: bool, force: bool) -> None:
195+
if do_setup:
196+
await stack_setup_mod.stack_setup(force=force)
197+
else:
198+
status = await stack_setup_mod.get_hooks_status()
199+
_print_hooks_status(status)
200+
201+
202+
@stack.command(help="Configure git hooks (alias for 'stack hooks --setup')") # type: ignore[untyped-decorator]
100203
@click.option(
101204
"--force",
102205
"-f",
@@ -106,11 +209,15 @@ def github_server_to_context(
106209
@click.option(
107210
"--check",
108211
is_flag=True,
109-
help="Only check hook status without making changes",
212+
help="Check status only (use 'stack hooks' instead)",
110213
)
111214
@utils.run_with_asyncio
112215
async def setup(*, force: bool, check: bool) -> None:
113-
await stack_setup_mod.stack_setup(force=force, check_only=check)
216+
if check:
217+
status = await stack_setup_mod.get_hooks_status()
218+
_print_hooks_status(status)
219+
else:
220+
await stack_setup_mod.stack_setup(force=force)
114221

115222

116223
@stack.command(help="Edit the stack history") # type: ignore[untyped-decorator]

0 commit comments

Comments
 (0)