Skip to content

Commit cc785fc

Browse files
committed
feat: add repowise delete CLI command
Lists repos in a numbered table, lets user pick one, confirms, then cleans FTS and CASCADE-deletes the repo. Supports --force.
1 parent 9583e82 commit cc785fc

File tree

2 files changed

+141
-0
lines changed

2 files changed

+141
-0
lines changed
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
"""``repowise delete`` — remove a repository and all its generated data."""
2+
3+
from __future__ import annotations
4+
5+
import click
6+
from rich.table import Table
7+
8+
from repowise.cli.helpers import (
9+
console,
10+
get_db_url_for_repo,
11+
get_repowise_dir,
12+
resolve_repo_path,
13+
run_async,
14+
)
15+
16+
17+
@click.command("delete")
18+
@click.argument("repo_id", required=False, default=None)
19+
@click.option("--force", "-f", is_flag=True, default=False, help="Skip confirmation prompt.")
20+
@click.argument("path", required=False, default=None)
21+
def delete_command(repo_id: str | None, force: bool, path: str | None) -> None:
22+
"""Delete a repository and all its generated data."""
23+
repo_path = resolve_repo_path(path)
24+
repowise_dir = get_repowise_dir(repo_path)
25+
26+
if not repowise_dir.exists():
27+
console.print("[yellow]No .repowise/ directory found. Run 'repowise init' first.[/yellow]")
28+
return
29+
30+
db_path = repowise_dir / "wiki.db"
31+
if not db_path.exists():
32+
console.print("[yellow]Database not found.[/yellow]")
33+
return
34+
35+
async def _run() -> None:
36+
from sqlalchemy import func, select
37+
38+
from repowise.core.persistence import (
39+
Repository,
40+
create_engine,
41+
create_session_factory,
42+
delete_repository,
43+
get_session,
44+
list_page_ids,
45+
)
46+
from repowise.core.persistence.models import Page
47+
from repowise.core.persistence.search import FullTextSearch
48+
49+
url = get_db_url_for_repo(repo_path)
50+
engine = create_engine(url)
51+
sf = create_session_factory(engine)
52+
53+
# List all repos with page counts
54+
async with get_session(sf) as session:
55+
result = await session.execute(
56+
select(
57+
Repository.id,
58+
Repository.name,
59+
Repository.local_path,
60+
func.count(Page.id).label("page_count"),
61+
)
62+
.outerjoin(Page, Page.repository_id == Repository.id)
63+
.group_by(Repository.id)
64+
.order_by(Repository.updated_at.desc())
65+
)
66+
repos = list(result.all())
67+
68+
if not repos:
69+
console.print("[yellow]No repositories found in the database.[/yellow]")
70+
await engine.dispose()
71+
return
72+
73+
# If no repo_id given, let the user pick
74+
target_id = repo_id
75+
if target_id is None:
76+
table = Table(title="Repositories")
77+
table.add_column("#", style="cyan", justify="right")
78+
table.add_column("Name", style="bold")
79+
table.add_column("Path")
80+
table.add_column("Pages", justify="right")
81+
table.add_column("ID", style="dim")
82+
83+
for i, (rid, name, lpath, pcount) in enumerate(repos, 1):
84+
table.add_row(str(i), name, lpath, str(pcount), rid[:12])
85+
86+
console.print(table)
87+
choice = click.prompt(
88+
"Enter number to delete (or 'q' to quit)",
89+
default="q",
90+
)
91+
if choice.lower() == "q":
92+
await engine.dispose()
93+
return
94+
try:
95+
idx = int(choice) - 1
96+
if idx < 0 or idx >= len(repos):
97+
raise ValueError
98+
target_id = repos[idx][0]
99+
except (ValueError, IndexError):
100+
console.print("[red]Invalid selection.[/red]")
101+
await engine.dispose()
102+
return
103+
104+
# Find the target repo info
105+
target = next((r for r in repos if r[0] == target_id), None)
106+
if target is None:
107+
console.print(f"[red]Repository {target_id} not found.[/red]")
108+
await engine.dispose()
109+
return
110+
111+
rid, name, lpath, pcount = target
112+
console.print(
113+
f"\nAbout to delete [bold]{name}[/bold] ({lpath}) — "
114+
f"[yellow]{pcount} pages[/yellow] will be removed."
115+
)
116+
117+
if not force:
118+
if not click.confirm("Are you sure?", default=False):
119+
console.print("Cancelled.")
120+
await engine.dispose()
121+
return
122+
123+
# Collect page IDs, clean FTS, delete repo
124+
async with get_session(sf) as session:
125+
page_ids = await list_page_ids(session, rid)
126+
127+
fts = FullTextSearch(engine)
128+
await fts.delete_many(page_ids)
129+
130+
await delete_repository(session, rid)
131+
132+
console.print(
133+
f"[bold green]Deleted[/bold green] {name} — "
134+
f"{len(page_ids)} pages removed."
135+
)
136+
137+
await engine.dispose()
138+
139+
run_async(_run())

packages/cli/src/repowise/cli/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from repowise.cli import __version__
88
from repowise.cli.commands.claude_md_cmd import claude_md_command
99
from repowise.cli.commands.dead_code_cmd import dead_code_command
10+
from repowise.cli.commands.delete_cmd import delete_command
1011
from repowise.cli.commands.decision_cmd import decision_group
1112
from repowise.cli.commands.doctor_cmd import doctor_command
1213
from repowise.cli.commands.export_cmd import export_command
@@ -27,6 +28,7 @@ def cli() -> None:
2728

2829

2930
cli.add_command(init_command)
31+
cli.add_command(delete_command)
3032
cli.add_command(claude_md_command)
3133
cli.add_command(update_command)
3234
cli.add_command(dead_code_command)

0 commit comments

Comments
 (0)