@@ -10,6 +10,7 @@ use git::{
1010 after_text,
1111 git_branch_status,
1212 git_checkout_main,
13+ git_diff_stats,
1314 git_fetch,
1415 git_get_upstream,
1516 git_remote_main,
@@ -33,7 +34,7 @@ const CREATE_BACKUP: bool = false;
3334#[ derive( Parser ) ]
3435#[ command( author, version, about) ]
3536struct Args {
36- #[ arg( long, short, help = "Enable verbose output" ) ]
37+ #[ arg( long, short, global = true , help = "Enable verbose output" ) ]
3738 verbose : bool ,
3839
3940 /// Subcommand to run.
@@ -185,7 +186,7 @@ fn inner_main() -> Result<()> {
185186 }
186187 Some ( Command :: Status { fetch } ) => {
187188 state. try_auto_mount ( & repo, & current_branch) ?;
188- status ( state, & repo, & current_branch, fetch)
189+ status ( state, & repo, & current_branch, fetch, args . verbose )
189190 }
190191 Some ( Command :: Delete { branch_name } ) => state. delete_branch ( & repo, & branch_name) ,
191192 Some ( Command :: Cleanup { dry_run, all } ) => {
@@ -212,7 +213,7 @@ fn inner_main() -> Result<()> {
212213 }
213214 None => {
214215 state. try_auto_mount ( & repo, & current_branch) ?;
215- status ( state, & repo, & current_branch, false )
216+ status ( state, & repo, & current_branch, false , args . verbose )
216217 }
217218 }
218219}
@@ -279,6 +280,7 @@ fn recur_tree(
279280 depth : usize ,
280281 orig_branch : & str ,
281282 parent_branch : Option < & str > ,
283+ verbose : bool ,
282284) -> Result < ( ) > {
283285 let Ok ( branch_status) = git_branch_status ( parent_branch, & branch. name ) . with_context ( || {
284286 format ! (
@@ -301,84 +303,116 @@ fn recur_tree(
301303 print ! ( "{}" , "┃ " . truecolor( 55 , 55 , 50 ) ) ;
302304 }
303305
304- println ! (
305- "{} ({}) {}{}{}{}" ,
306- match ( is_current_branch, branch_status. is_descendent) {
307- ( true , true ) => branch. name. truecolor( 142 , 192 , 124 ) . bold( ) ,
308- ( true , false ) => branch. name. truecolor( 215 , 153 , 33 ) . bold( ) ,
309- ( false , true ) => branch. name. truecolor( 142 , 192 , 124 ) ,
310- ( false , false ) => branch. name. truecolor( 215 , 153 , 33 ) ,
311- } ,
312- branch_status. sha[ ..8 ] . truecolor( 215 , 153 , 33 ) ,
313- {
314- let details: String = if branch_status. exists {
315- if branch_status. is_descendent {
316- format!(
317- "{} {}" ,
318- "is stacked on" . truecolor( 90 , 120 , 87 ) ,
319- branch_status. parent_branch. yellow( )
320- )
306+ // Branch name coloring: green for synced, red for diverged, bold for current branch
307+ let branch_name_colored = match ( is_current_branch, branch_status. is_descendent ) {
308+ ( true , true ) => branch. name . truecolor ( 142 , 192 , 124 ) . bold ( ) ,
309+ ( true , false ) => branch. name . red ( ) . bold ( ) ,
310+ ( false , true ) => branch. name . truecolor ( 142 , 192 , 124 ) ,
311+ ( false , false ) => branch. name . red ( ) ,
312+ } ;
313+
314+ // Get diff stats from LKG ancestor to current branch
315+ let diff_stats = if let Some ( lkg_parent) = branch. lkg_parent . as_ref ( ) {
316+ match git_diff_stats ( lkg_parent, & branch_status. sha ) {
317+ Ok ( ( adds, dels) ) => format ! (
318+ " {} {}" ,
319+ format!( "+{}" , adds) . green( ) ,
320+ format!( "-{}" , dels) . red( )
321+ ) ,
322+ Err ( _) => String :: new ( ) , // Silently skip on error
323+ }
324+ } else {
325+ String :: new ( ) // No LKG = no stats (e.g., trunk root)
326+ } ;
327+
328+ if verbose {
329+ println ! (
330+ "{}{} ({}) {}{}{}{}" ,
331+ branch_name_colored,
332+ diff_stats,
333+ branch_status. sha[ ..8 ] . truecolor( 215 , 153 , 33 ) ,
334+ {
335+ let details: String = if branch_status. exists {
336+ if branch_status. is_descendent {
337+ format!(
338+ "{} {}" ,
339+ "is stacked on" . truecolor( 90 , 120 , 87 ) ,
340+ branch_status. parent_branch. yellow( )
341+ )
342+ } else {
343+ format!(
344+ "{} {}" ,
345+ "diverges from" . red( ) ,
346+ branch_status. parent_branch. yellow( )
347+ )
348+ }
321349 } else {
350+ "does not exist!" . bright_red( ) . to_string( )
351+ } ;
352+ details
353+ } ,
354+ {
355+ if let Some ( upstream_status) = branch_status. upstream_status {
322356 format!(
323- "{} {}" ,
324- "diverges from" . red( ) ,
325- branch_status. parent_branch. yellow( )
357+ " (upstream {} is {})" ,
358+ upstream_status. symbolic_name. truecolor( 88 , 88 , 88 ) ,
359+ if upstream_status. synced {
360+ "synced" . truecolor( 142 , 192 , 124 )
361+ } else {
362+ "not synced" . bright_red( )
363+ }
326364 )
365+ } else {
366+ format!( " ({})" , "no upstream" . truecolor( 215 , 153 , 33 ) )
327367 }
328- } else {
329- "does not exist!" . bright_red( ) . to_string( )
330- } ;
331- details
332- } ,
333- {
334- if let Some ( upstream_status) = branch_status. upstream_status {
335- format!(
336- " (upstream {} is {})" ,
337- upstream_status. symbolic_name. truecolor( 88 , 88 , 88 ) ,
338- if upstream_status. synced {
339- "synced" . truecolor( 142 , 192 , 124 )
340- } else {
341- "not synced" . bright_red( )
342- }
343- )
344- } else {
345- format!( " ({})" , "no upstream" . truecolor( 215 , 153 , 33 ) )
346- }
347- } ,
348- {
349- if let Some ( lkg_parent) = branch. lkg_parent. as_ref( ) {
350- format!( " (lkg parent {})" , lkg_parent[ ..8 ] . truecolor( 215 , 153 , 33 ) )
351- } else {
352- String :: new( )
368+ } ,
369+ {
370+ if let Some ( lkg_parent) = branch. lkg_parent. as_ref( ) {
371+ format!( " (lkg parent {})" , lkg_parent[ ..8 ] . truecolor( 215 , 153 , 33 ) )
372+ } else {
373+ String :: new( )
374+ }
375+ } ,
376+ match branch. stack_method {
377+ StackMethod :: ApplyMerge => " (apply-merge)" . truecolor( 142 , 192 , 124 ) ,
378+ StackMethod :: Merge => " (merge)" . truecolor( 142 , 192 , 124 ) ,
379+ } ,
380+ ) ;
381+ if let Some ( note) = & branch. note {
382+ print ! ( " " ) ;
383+ for _ in 0 ..depth {
384+ print ! ( "{}" , "┃ " . truecolor( 55 , 55 , 50 ) ) ;
353385 }
354- } ,
355- match branch. stack_method {
356- StackMethod :: ApplyMerge => " (apply-merge)" . truecolor( 142 , 192 , 124 ) ,
357- StackMethod :: Merge => " (merge)" . truecolor( 142 , 192 , 124 ) ,
358- } ,
359- ) ;
360- if let Some ( note) = & branch. note {
361- print ! ( " " ) ;
362- for _ in 0 ..depth {
363- print ! ( "{}" , "┃ " . truecolor( 55 , 55 , 50 ) ) ;
364- }
365386
366- let first_line = note. lines ( ) . next ( ) . unwrap_or ( "" ) ;
367- println ! (
368- " {} {}" ,
369- "›" . truecolor( 55 , 55 , 50 ) ,
370- if is_current_branch {
371- first_line. bright_blue( ) . bold( )
372- } else {
373- first_line. blue( )
374- }
375- ) ;
387+ let first_line = note. lines ( ) . next ( ) . unwrap_or ( "" ) ;
388+ println ! (
389+ " {} {}" ,
390+ "›" . truecolor( 55 , 55 , 50 ) ,
391+ if is_current_branch {
392+ first_line. bright_blue( ) . bold( )
393+ } else {
394+ first_line. blue( )
395+ }
396+ ) ;
397+ }
398+ } else {
399+ println ! ( "{}{}" , branch_name_colored, diff_stats) ;
376400 }
377401
378402 let mut branches_sorted = branch. branches . iter ( ) . collect :: < Vec < _ > > ( ) ;
403+ // Pre-compute is_ancestor results to avoid repeated git merge-base calls during sorting
404+ let ancestor_cache: std:: collections:: HashMap < & str , bool > = branches_sorted
405+ . iter ( )
406+ . map ( |b| {
407+ (
408+ b. name . as_str ( ) ,
409+ is_ancestor ( & b. name , orig_branch) . unwrap_or ( false ) ,
410+ )
411+ } )
412+ . collect ( ) ;
379413 branches_sorted. sort_by ( |& a, & b| {
380- let a_is_ancestor = is_ancestor ( & a. name , orig_branch ) . unwrap_or ( false ) ;
381- let b_is_ancestor = is_ancestor ( & b. name , orig_branch ) . unwrap_or ( false ) ;
414+ let a_is_ancestor = ancestor_cache . get ( a. name . as_str ( ) ) . copied ( ) . unwrap_or ( false ) ;
415+ let b_is_ancestor = ancestor_cache . get ( b. name . as_str ( ) ) . copied ( ) . unwrap_or ( false ) ;
382416 match ( a_is_ancestor, b_is_ancestor) {
383417 ( true , true ) => a. name . cmp ( & b. name ) ,
384418 ( true , false ) => std:: cmp:: Ordering :: Less ,
@@ -387,25 +421,23 @@ fn recur_tree(
387421 }
388422 } ) ;
389423 for child in branches_sorted {
390- recur_tree ( child, depth + 1 , orig_branch, Some ( branch. name . as_ref ( ) ) ) ?;
424+ recur_tree ( child, depth + 1 , orig_branch, Some ( branch. name . as_ref ( ) ) , verbose ) ?;
391425 }
392426 Ok ( ( ) )
393427}
394428
395- fn status ( mut state : State , repo : & str , orig_branch : & str , fetch : bool ) -> Result < ( ) > {
429+ fn status ( mut state : State , repo : & str , orig_branch : & str , fetch : bool , verbose : bool ) -> Result < ( ) > {
396430 if fetch {
397431 git_fetch ( ) ?;
398432 }
399- let trunk = state. ensure_trunk ( repo) ?;
433+ // ensure_trunk creates the tree if it doesn't exist
434+ let _trunk = state. ensure_trunk ( repo) ?;
400435
401- let Some ( tree) = state. get_tree_mut ( repo) else {
402- eprintln ! (
403- "No stack tree found for repo {repo}." ,
404- repo = repo. truecolor( 178 , 178 , 218 )
405- ) ;
406- return Ok ( ( ) ) ;
407- } ;
408- recur_tree ( tree, 0 , orig_branch, None ) ?;
436+ // Auto-cleanup any missing branches before displaying the tree
437+ state. auto_cleanup_missing_branches ( repo) ?;
438+
439+ let tree = state. get_tree_mut ( repo) . expect ( "tree exists after ensure_trunk" ) ;
440+ recur_tree ( tree, 0 , orig_branch, None , verbose) ?;
409441 if !state. branch_exists_in_tree ( repo, orig_branch) {
410442 eprintln ! (
411443 "The current branch {} is not in the stack tree." ,
0 commit comments