@@ -182,6 +182,14 @@ def init_db(conn: sqlite3.Connection) -> None:
182182 plan TEXT NOT NULL,
183183 created_at TEXT DEFAULT (datetime('now'))
184184 );
185+
186+ CREATE TABLE IF NOT EXISTS apply_history (
187+ id INTEGER PRIMARY KEY AUTOINCREMENT,
188+ migration_plan_id INTEGER NOT NULL,
189+ applied_at TEXT DEFAULT (datetime('now')),
190+ notes TEXT,
191+ FOREIGN KEY (migration_plan_id) REFERENCES migration_plan(id)
192+ );
185193 """
186194 )
187195 conn .commit ()
@@ -941,9 +949,12 @@ def pick_move() -> None:
941949# ---------------------------------------------------------------------------
942950
943951
944- @app .command ("show-plan" )
945- def show_plan () -> None :
946- """Show current ``migration_plan`` entries (mirror/move/etc.)."""
952+ @app .command ("plan" )
953+ def plan () -> None :
954+ """Show the current migration *plan* (Terraform-style).
955+
956+ This is the main view of what will happen when you run ``repostat apply``.
957+ """
947958
948959 conn = get_conn ()
949960 cur = conn .cursor ()
@@ -953,6 +964,11 @@ def show_plan() -> None:
953964 console .rule ("Migration plan" )
954965 if not rows :
955966 console .print ("[yellow]No migration_plan entries yet.[/yellow]" )
967+ console .print ()
968+ console .print ("[bold]Next steps:[/bold]" )
969+ console .print ("- Use [cyan]repostat pick-mirror[/cyan] or [cyan]repostat pick-move[/cyan] to add items to the plan." )
970+ console .print ("- Run [cyan]repostat history[/cyan] to view previous apply runs." )
971+ console .print ("- Run [cyan]repostat refresh[/cyan] to sync GitHub / Forgejo / local state." )
956972 return
957973
958974 table = Table (show_header = True , header_style = "bold green" , box = box .SIMPLE )
@@ -974,13 +990,166 @@ def show_plan() -> None:
974990 )
975991
976992 console .print (table )
993+ console .print ()
994+ console .print ("[bold]Next steps:[/bold]" )
995+ console .print ("- Run [cyan]repostat apply[/cyan] to record an apply of this plan." )
996+ console .print ("- Run [cyan]repostat history[/cyan] to see previous apply runs." )
997+
998+
999+ # Alias: keep the old name for muscle memory
1000+ @app .command ("show-plan" )
1001+ def show_plan_alias () -> None :
1002+ """Alias for :func:`plan` (kept for muscle memory)."""
1003+
1004+ plan ()
1005+
1006+
1007+ # ---------------------------------------------------------------------------
1008+ # apply / history: terraform-style workflow
1009+ # ---------------------------------------------------------------------------
1010+
1011+
1012+ @app .command ()
1013+ def apply () -> None :
1014+ """Record an *apply* of the current migration plan.
1015+
1016+ This does **not** perform any network or Git operations yet; it simply
1017+ snapshots the current ``migration_plan`` table into ``apply_history`` so
1018+ that you can track when you considered a plan "applied".
1019+ """
1020+
1021+ conn = get_conn ()
1022+ cur = conn .cursor ()
1023+
1024+ cur .execute ("SELECT * FROM migration_plan ORDER BY created_at DESC, id DESC" )
1025+ rows = cur .fetchall ()
1026+
1027+ console .rule ("Apply migration plan" )
1028+ if not rows :
1029+ console .print ("[yellow]No migration_plan entries to apply.[/yellow]" )
1030+ console .print ()
1031+ console .print ("[bold]Next steps:[/bold]" )
1032+ console .print ("- Run [cyan]repostat plan[/cyan] (or [cyan]repostat show-plan[/cyan]) to see the current plan." )
1033+ console .print ("- Use [cyan]repostat pick-mirror[/cyan] or [cyan]repostat pick-move[/cyan] to build a plan." )
1034+ return
1035+
1036+ table = Table (show_header = True , header_style = "bold green" , box = box .SIMPLE )
1037+ table .add_column ("ID" , justify = "right" )
1038+ table .add_column ("Plan" )
1039+ table .add_column ("GitHub" )
1040+ table .add_column ("Forgejo" )
1041+ table .add_column ("Local path" )
1042+
1043+ for r in rows :
1044+ table .add_row (
1045+ str (r ["id" ]),
1046+ r ["plan" ],
1047+ r ["github_full_name" ] or "" ,
1048+ r ["forgejo_full_name" ] or "" ,
1049+ r ["local_path" ] or "" ,
1050+ )
1051+
1052+ console .print (table )
1053+
1054+ confirm = input ("Record this apply in history? [y/N]: " ).strip ().lower ()
1055+ if confirm not in {"y" , "yes" }:
1056+ console .print ("[yellow]Apply aborted; nothing recorded.[/yellow]" )
1057+ console .print ()
1058+ console .print ("[bold]Next steps:[/bold]" )
1059+ console .print ("- Run [cyan]repostat plan[/cyan] to review the plan again." )
1060+ console .print ("- Run [cyan]repostat history[/cyan] to inspect previous applies." )
1061+ return
1062+
1063+ for r in rows :
1064+ cur .execute (
1065+ "INSERT INTO apply_history (migration_plan_id, notes) VALUES (?, ?)" ,
1066+ (r ["id" ], "applied via `repostat apply`" ),
1067+ )
1068+ conn .commit ()
1069+
1070+ console .print ("[green]Apply recorded in history.[/green]" )
1071+ console .print ()
1072+ console .print ("[bold]Next steps:[/bold]" )
1073+ console .print ("- Run [cyan]repostat refresh[/cyan] to sync current repo state." )
1074+ console .print ("- Run [cyan]repostat history[/cyan] to view apply history." )
1075+
1076+
1077+ @app .command ()
1078+ def history () -> None :
1079+ """Show apply history for migration plans.
1080+
1081+ Each row represents a call to ``repostat apply`` and which plan entries
1082+ were included in that apply.
1083+ """
1084+
1085+ conn = get_conn ()
1086+ cur = conn .cursor ()
1087+
1088+ cur .execute (
1089+ """
1090+ SELECT
1091+ h.id AS history_id,
1092+ h.applied_at AS applied_at,
1093+ h.notes AS notes,
1094+ p.id AS plan_id,
1095+ p.plan AS plan,
1096+ p.github_full_name,
1097+ p.forgejo_full_name,
1098+ p.local_path
1099+ FROM apply_history h
1100+ JOIN migration_plan p ON p.id = h.migration_plan_id
1101+ ORDER BY h.applied_at DESC, h.id DESC
1102+ """
1103+ )
1104+ rows = cur .fetchall ()
1105+
1106+ console .rule ("Apply history" )
1107+ if not rows :
1108+ console .print ("[yellow]No applies recorded yet.[/yellow]" )
1109+ console .print ()
1110+ console .print ("[bold]Next steps:[/bold]" )
1111+ console .print ("- Run [cyan]repostat plan[/cyan] to see the current plan." )
1112+ console .print ("- Run [cyan]repostat apply[/cyan] to record an apply." )
1113+ return
1114+
1115+ table = Table (show_header = True , header_style = "bold green" , box = box .SIMPLE )
1116+ table .add_column ("History ID" , justify = "right" )
1117+ table .add_column ("Applied at" )
1118+ table .add_column ("Plan ID" , justify = "right" )
1119+ table .add_column ("Plan" )
1120+ table .add_column ("GitHub" )
1121+ table .add_column ("Forgejo" )
1122+ table .add_column ("Local path" )
1123+ table .add_column ("Notes" )
1124+
1125+ for r in rows :
1126+ table .add_row (
1127+ str (r ["history_id" ]),
1128+ r ["applied_at" ] or "" ,
1129+ str (r ["plan_id" ]),
1130+ r ["plan" ],
1131+ r ["github_full_name" ] or "" ,
1132+ r ["forgejo_full_name" ] or "" ,
1133+ r ["local_path" ] or "" ,
1134+ r ["notes" ] or "" ,
1135+ )
1136+
1137+ console .print (table )
1138+ console .print ()
1139+ console .print ("[bold]Next steps:[/bold]" )
1140+ console .print ("- Run [cyan]repostat plan[/cyan] to review the current plan." )
1141+ console .print ("- Run [cyan]repostat apply[/cyan] to record a new apply." )
9771142
9781143
9791144# ---------------------------------------------------------------------------
9801145# stats: quick overview / suggestions (and default command)
9811146# ---------------------------------------------------------------------------
9821147
9831148
1149+ # @app.command(): quick overview / suggestions (and default command)
1150+ # # ---------------------------------------------------------------------------
1151+
1152+
9841153@app .command ()
9851154def stats () -> None :
9861155 """Show a quick summary of what's in the DB and some suggestions."""
@@ -1007,6 +1176,8 @@ def stats() -> None:
10071176
10081177 cur .execute ("SELECT COUNT(*) FROM migration_plan" )
10091178 plan_count = int (cur .fetchone ()[0 ])
1179+ cur .execute ("SELECT COUNT(*) FROM apply_history" )
1180+ history_count = int (cur .fetchone ()[0 ])
10101181
10111182 console .rule ("Repo stats" )
10121183 table = Table (show_header = False , box = box .MINIMAL_DOUBLE_HEAD )
@@ -1017,6 +1188,7 @@ def stats() -> None:
10171188 table .add_row ("Local missing remote" , str (missing_remote ))
10181189 table .add_row ("Local missing README" , str (missing_readme ))
10191190 table .add_row ("Migration plan entries" , str (plan_count ))
1191+ table .add_row ("Apply history entries" , str (history_count ))
10201192 console .print (table )
10211193
10221194 console .print ()
@@ -1033,6 +1205,15 @@ def stats() -> None:
10331205 console .print (
10341206 "- Run [cyan]repostat show-missing[/cyan] and [cyan]repostat pick-newrepo[/cyan] to clean up local repos."
10351207 )
1208+ if plan_count == 0 :
1209+ console .print (
1210+ "- Build a plan with [cyan]repostat pick-mirror[/cyan] or [cyan]repostat pick-move[/cyan]."
1211+ )
1212+ else :
1213+ console .print ("- Review the current plan with [cyan]repostat plan[/cyan]." )
1214+ console .print ("- Record an apply with [cyan]repostat apply[/cyan]." )
1215+ if history_count :
1216+ console .print ("- Inspect previous applies with [cyan]repostat history[/cyan]." )
10361217 if gh_count and fj_count :
10371218 console .print (
10381219 "- Run [cyan]repostat show-only-github[/cyan] to see repos that exist only on GitHub."
0 commit comments