@@ -24,7 +24,7 @@ mod stats;
2424mod sync;
2525mod tui;
2626#[ derive( Parser ) ]
27- #[ command( author, version, about) ]
27+ #[ command( author, version, about, infer_subcommands = true ) ]
2828struct Args {
2929 #[ arg( long, short, global = true , help = "Enable verbose output" ) ]
3030 verbose : bool ,
@@ -52,10 +52,9 @@ enum Command {
5252 /// Whether to fetch the latest changes from the remote before showing the status.
5353 #[ arg( long, short, default_value_t = false ) ]
5454 fetch : bool ,
55- /// Launch interactive TUI mode for branch navigation and checkout.
56- #[ arg( long, short = 'i' , default_value_t = false ) ]
57- interactive : bool ,
5855 } ,
56+ /// Launch interactive TUI mode for branch navigation and checkout.
57+ Interactive ,
5958 /// Open the git-stack state file in an editor for manual editing.
6059 Edit ,
6160 /// Restack your active branch onto its parent branch.
@@ -352,7 +351,7 @@ fn inner_main() -> Result<()> {
352351 Some ( Command :: Mount { parent_branch } ) => {
353352 state. mount ( & git_repo, & repo, & current_branch, parent_branch)
354353 }
355- Some ( Command :: Status { fetch, interactive } ) => {
354+ Some ( Command :: Status { fetch } ) => {
356355 state. try_auto_mount ( & git_repo, & repo, & current_branch) ?;
357356 status (
358357 & git_repo,
@@ -361,9 +360,12 @@ fn inner_main() -> Result<()> {
361360 & current_branch,
362361 fetch,
363362 args. verbose ,
364- interactive,
365363 )
366364 }
365+ Some ( Command :: Interactive ) => {
366+ state. try_auto_mount ( & git_repo, & repo, & current_branch) ?;
367+ interactive ( & git_repo, state, & repo, & current_branch, args. verbose )
368+ }
367369 Some ( Command :: Delete { branch_name } ) => state. delete_branch ( & repo, & branch_name) ,
368370 Some ( Command :: Cleanup { dry_run, all } ) => {
369371 state. cleanup_missing_branches ( & git_repo, & repo, dry_run, all)
@@ -419,7 +421,6 @@ fn inner_main() -> Result<()> {
419421 & current_branch,
420422 false ,
421423 args. verbose ,
422- false , // interactive
423424 )
424425 }
425426 }
@@ -501,7 +502,6 @@ fn status(
501502 orig_branch : & str ,
502503 fetch : bool ,
503504 verbose : bool ,
504- interactive : bool ,
505505) -> Result < ( ) > {
506506 if fetch {
507507 git_fetch ( ) ?;
@@ -533,22 +533,58 @@ fn status(
533533 & display_authors,
534534 ) ;
535535
536- if interactive {
537- // Run TUI and handle checkout if user selected a branch
538- if let Some ( branch_to_checkout) = tui:: run_tui ( renderable, verbose) ? {
539- run_git ( & [ "checkout" , & branch_to_checkout] ) ?;
540- }
541- } else {
542- // Render to CLI
543- render:: render_cli ( & renderable, verbose) ;
536+ // Render to CLI
537+ render:: render_cli ( & renderable, verbose) ;
544538
545- if !state. branch_exists_in_tree ( repo, orig_branch) {
546- eprintln ! (
547- "The current branch {} is not in the stack tree." ,
548- orig_branch. red( )
549- ) ;
550- eprintln ! ( "Run `git stack mount <parent_branch>` to add it." ) ;
551- }
539+ if !state. branch_exists_in_tree ( repo, orig_branch) {
540+ eprintln ! (
541+ "The current branch {} is not in the stack tree." ,
542+ orig_branch. red( )
543+ ) ;
544+ eprintln ! ( "Run `git stack mount <parent_branch>` to add it." ) ;
545+ }
546+
547+ state. save_state ( ) ?;
548+ Ok ( ( ) )
549+ }
550+
551+ fn interactive (
552+ git_repo : & GitRepo ,
553+ mut state : State ,
554+ repo : & str ,
555+ orig_branch : & str ,
556+ verbose : bool ,
557+ ) -> Result < ( ) > {
558+ // ensure_trunk creates the tree if it doesn't exist (no-op if no remote)
559+ let _trunk = state. ensure_trunk ( git_repo, repo) ;
560+
561+ // Auto-cleanup any missing branches before displaying the tree
562+ state. auto_cleanup_missing_branches ( git_repo, repo) ?;
563+
564+ // Try to fetch PR info from GitHub (graceful degradation on failure)
565+ let pr_cache = fetch_pr_cache ( git_repo) ;
566+
567+ // Load display_authors for filtering (show other authors dimmed)
568+ let display_authors = github:: load_display_authors ( ) ;
569+
570+ let Some ( tree) = state. get_tree ( repo) else {
571+ println ! ( "No stack configured for this repository." ) ;
572+ return Ok ( ( ) ) ;
573+ } ;
574+
575+ // Compute renderable tree
576+ let renderable = render:: compute_renderable_tree (
577+ git_repo,
578+ tree,
579+ orig_branch,
580+ verbose,
581+ pr_cache. as_ref ( ) ,
582+ & display_authors,
583+ ) ;
584+
585+ // Run TUI and handle checkout if user selected a branch
586+ if let Some ( branch_to_checkout) = tui:: run_tui ( renderable, verbose) ? {
587+ run_git ( & [ "checkout" , & branch_to_checkout] ) ?;
552588 }
553589
554590 state. save_state ( ) ?;
@@ -676,11 +712,7 @@ fn squash_branch(
676712}
677713
678714/// Handle the --continue flag for resuming a squash operation.
679- fn handle_squash_continue (
680- git_repo : & GitRepo ,
681- state : & mut State ,
682- repo : & str ,
683- ) -> Result < ( ) > {
715+ fn handle_squash_continue ( git_repo : & GitRepo , state : & mut State , repo : & str ) -> Result < ( ) > {
684716 let pending = state
685717 . get_pending_squash ( repo)
686718 . ok_or_else ( || anyhow ! ( "No pending squash operation to continue." ) ) ?
@@ -690,7 +722,10 @@ fn handle_squash_continue(
690722 let status_output = run_git ( & [ "status" , "--porcelain" ] ) ?;
691723 let has_conflicts = status_output
692724 . output ( )
693- . map ( |s| s. lines ( ) . any ( |line| line. starts_with ( "UU" ) || line. starts_with ( "AA" ) ) )
725+ . map ( |s| {
726+ s. lines ( )
727+ . any ( |line| line. starts_with ( "UU" ) || line. starts_with ( "AA" ) )
728+ } )
694729 . unwrap_or ( false ) ;
695730
696731 if has_conflicts {
@@ -719,11 +754,7 @@ fn handle_squash_continue(
719754}
720755
721756/// Handle the --abort flag for aborting a squash operation.
722- fn handle_squash_abort (
723- git_repo : & GitRepo ,
724- state : & mut State ,
725- repo : & str ,
726- ) -> Result < ( ) > {
757+ fn handle_squash_abort ( git_repo : & GitRepo , state : & mut State , repo : & str ) -> Result < ( ) > {
727758 let pending = state
728759 . get_pending_squash ( repo)
729760 . ok_or_else ( || anyhow ! ( "No pending squash operation to abort." ) ) ?
@@ -1001,8 +1032,7 @@ fn sync_pr_bases_after_restack(git_repo: &GitRepo, state: &State, repo: &str) ->
10011032 . get_tree ( repo)
10021033 . ok_or_else ( || anyhow ! ( "No stack tree found" ) ) ?;
10031034
1004- let trunk =
1005- crate :: git:: git_trunk ( git_repo) . ok_or_else ( || anyhow ! ( "No remote configured" ) ) ?;
1035+ let trunk = crate :: git:: git_trunk ( git_repo) . ok_or_else ( || anyhow ! ( "No remote configured" ) ) ?;
10061036
10071037 // Collect branches with depth for bottom-up processing
10081038 let branches_with_depth = collect_branches_with_depth ( tree, & tree. name , 0 ) ;
@@ -1228,8 +1258,7 @@ fn handle_import_command(
12281258 }
12291259
12301260 let client = GitHubClient :: from_env ( & repo_id) ?;
1231- let trunk =
1232- crate :: git:: git_trunk ( git_repo) . ok_or_else ( || anyhow ! ( "No remote configured" ) ) ?;
1261+ let trunk = crate :: git:: git_trunk ( git_repo) . ok_or_else ( || anyhow ! ( "No remote configured" ) ) ?;
12331262
12341263 // Ensure trunk exists in tree
12351264 state. ensure_trunk ( git_repo, repo) ;
@@ -1533,8 +1562,8 @@ fn handle_pr_command(
15331562 state. try_auto_mount ( git_repo, repo, & branch_name) ?;
15341563
15351564 // Check if this is the trunk branch (can't create PR for main)
1536- let trunk = crate :: git :: git_trunk ( git_repo )
1537- . ok_or_else ( || anyhow ! ( "No remote configured" ) ) ?;
1565+ let trunk =
1566+ crate :: git :: git_trunk ( git_repo ) . ok_or_else ( || anyhow ! ( "No remote configured" ) ) ?;
15381567 if branch_name == trunk. main_branch {
15391568 bail ! (
15401569 "Cannot create a PR for the trunk branch '{}'." ,
@@ -1706,8 +1735,8 @@ fn handle_pr_command(
17061735 PrAction :: Sync { all, dry_run } => {
17071736 use github:: UpdatePrRequest ;
17081737
1709- let trunk = crate :: git :: git_trunk ( git_repo )
1710- . ok_or_else ( || anyhow ! ( "No remote configured" ) ) ?;
1738+ let trunk =
1739+ crate :: git :: git_trunk ( git_repo ) . ok_or_else ( || anyhow ! ( "No remote configured" ) ) ?;
17111740
17121741 // Get branches to sync with depth for bottom-up processing
17131742 let branches_to_sync: Vec < ( String , String , usize ) > = if all {
0 commit comments