88 "io"
99 "os"
1010 "strings"
11+ "text/tabwriter"
1112 "time"
1213
1314 "github.com/recallnet/mainline/internal/domain"
@@ -187,10 +188,12 @@ Flags:
187188
188189 var repoPath string
189190 var asJSON bool
191+ var asTable bool
190192 var limit int
191193
192194 fs .StringVar (& repoPath , "repo" , "." , "repository path" )
193195 fs .BoolVar (& asJSON , "json" , false , "output json" )
196+ fs .BoolVar (& asTable , "table" , false , "render a compact human-readable table" )
194197 fs .IntVar (& limit , "events" , 5 , "number of recent events to show" )
195198
196199 if err := fs .Parse (args ); err != nil {
@@ -207,6 +210,9 @@ Flags:
207210 encoder .SetIndent ("" , " " )
208211 return encoder .Encode (result )
209212 }
213+ if asTable {
214+ return renderStatusTable (stdout , result )
215+ }
210216
211217 return renderStatus (stdout , result )
212218}
@@ -450,6 +456,150 @@ func renderStatus(stdout *stepPrinter, result statusResult) error {
450456 return nil
451457}
452458
459+ func renderStatusTable (stdout * stepPrinter , result statusResult ) error {
460+ stdout .Section ("Queue" )
461+ stdout .Line ("Repo: %s" , result .RepositoryRoot )
462+ stdout .Line ("State: %s" , result .QueueSummary .Headline )
463+ if result .ProtectedBranchSHA != "" {
464+ stdout .Line ("Protected: %s @ %s" , result .ProtectedBranch , shortenSHA (result .ProtectedBranchSHA ))
465+ } else {
466+ stdout .Line ("Protected: %s" , result .ProtectedBranch )
467+ }
468+ if result .ProtectedUpstream .HasUpstream {
469+ stdout .Line ("Upstream: %s (ahead %d, behind %d)" , result .ProtectedUpstream .Upstream , result .ProtectedUpstream .AheadCount , result .ProtectedUpstream .BehindCount )
470+ }
471+ if result .ProtectedWorktreeActivity != nil {
472+ stdout .Line ("Activity: %s" , result .ProtectedWorktreeActivity .Summary )
473+ }
474+
475+ tw := tabwriter .NewWriter (stdout .Raw (), 0 , 0 , 2 , ' ' , 0 )
476+ _ , _ = fmt .Fprintln (tw , "" )
477+ _ , _ = fmt .Fprintln (tw , "TYPE\t ID\t STATUS\t STAGE\t BRANCH\t TARGET" )
478+
479+ rows := statusTableRows (result )
480+ if len (rows ) == 0 {
481+ _ , _ = fmt .Fprintln (tw , "-\t -\t idle\t -\t -\t -" )
482+ } else {
483+ for _ , row := range rows {
484+ _ , _ = fmt .Fprintf (tw , "%s\t %s\t %s\t %s\t %s\t %s\n " , row [0 ], row [1 ], row [2 ], row [3 ], row [4 ], row [5 ])
485+ }
486+ }
487+ if err := tw .Flush (); err != nil {
488+ return err
489+ }
490+
491+ stdout .Line ("" )
492+ stdout .Line ("Counts: queued_submissions=%d running_submissions=%d blocked_submissions=%d queued_publishes=%d running_publishes=%d" ,
493+ result .Counts .QueuedSubmissions ,
494+ result .Counts .RunningSubmissions ,
495+ result .Counts .BlockSubmissions ,
496+ result .Counts .QueuedPublishes ,
497+ result .Counts .RunningPublishes ,
498+ )
499+ if len (result .Alerts ) > 0 {
500+ for _ , alert := range result .Alerts {
501+ stdout .Line ("Alert: %s" , alert )
502+ }
503+ }
504+ return nil
505+ }
506+
507+ func statusTableRows (result statusResult ) [][]string {
508+ rows := make ([][]string , 0 , len (result .ActivePublishes )+ len (result .ActiveSubmissions )+ 2 )
509+ for _ , request := range result .ActivePublishes {
510+ rows = append (rows , []string {
511+ "publish" ,
512+ fmt .Sprintf ("%d" , request .ID ),
513+ string (request .Status ),
514+ emptyDash (request .ActiveStage ),
515+ "-" ,
516+ shortenSHA (request .TargetSHA ),
517+ })
518+ }
519+ for _ , submission := range result .ActiveSubmissions {
520+ rows = append (rows , []string {
521+ "submit" ,
522+ fmt .Sprintf ("%d" , submission .ID ),
523+ string (submission .Status ),
524+ submissionStage (submission ),
525+ submissionDisplayRef (submission .integrationSubmission ()),
526+ shortenSHA (nonEmpty (submission .ProtectedTipSHA , submission .SourceSHA )),
527+ })
528+ }
529+ if result .LatestSubmission != nil && ! containsStatusRow (rows , "submit" , result .LatestSubmission .ID ) {
530+ rows = append (rows , []string {
531+ "submit" ,
532+ fmt .Sprintf ("%d" , result .LatestSubmission .ID ),
533+ string (result .LatestSubmission .Status ),
534+ submissionStage (* result .LatestSubmission ),
535+ submissionDisplayRef (result .LatestSubmission .integrationSubmission ()),
536+ shortenSHA (nonEmpty (result .LatestSubmission .ProtectedTipSHA , result .LatestSubmission .SourceSHA )),
537+ })
538+ }
539+ if result .LatestPublish != nil && ! containsStatusRow (rows , "publish" , result .LatestPublish .ID ) {
540+ rows = append (rows , []string {
541+ "publish" ,
542+ fmt .Sprintf ("%d" , result .LatestPublish .ID ),
543+ string (result .LatestPublish .Status ),
544+ emptyDash (result .LatestPublish .ActiveStage ),
545+ "-" ,
546+ shortenSHA (result .LatestPublish .TargetSHA ),
547+ })
548+ }
549+ return rows
550+ }
551+
552+ func containsStatusRow (rows [][]string , rowType string , id int64 ) bool {
553+ needle := fmt .Sprintf ("%d" , id )
554+ for _ , row := range rows {
555+ if len (row ) >= 2 && row [0 ] == rowType && row [1 ] == needle {
556+ return true
557+ }
558+ }
559+ return false
560+ }
561+
562+ func submissionStage (submission statusSubmission ) string {
563+ switch submission .Status {
564+ case domain .SubmissionStatusQueued :
565+ return "queued"
566+ case domain .SubmissionStatusRunning :
567+ return "integrating"
568+ case domain .SubmissionStatusBlocked :
569+ return "blocked"
570+ case domain .SubmissionStatusSucceeded :
571+ if submission .Outcome != "" {
572+ return string (submission .Outcome )
573+ }
574+ return "integrated"
575+ default :
576+ if submission .Outcome != "" {
577+ return string (submission .Outcome )
578+ }
579+ return "-"
580+ }
581+ }
582+
583+ func shortenSHA (sha string ) string {
584+ trimmed := strings .TrimSpace (sha )
585+ if len (trimmed ) > 8 {
586+ return trimmed [:8 ]
587+ }
588+ if trimmed == "" {
589+ return "-"
590+ }
591+ return trimmed
592+ }
593+
594+ func nonEmpty (values ... string ) string {
595+ for _ , value := range values {
596+ if strings .TrimSpace (value ) != "" {
597+ return value
598+ }
599+ }
600+ return ""
601+ }
602+
453603func buildStatusRebaseGuidance (cfg policy.File , comparison git.BranchComparison , protectedStatus git.BranchStatus , repoPath string , branch string ) * statusRebaseGuidance {
454604 guidance := & statusRebaseGuidance {
455605 NeedsRebase : comparison .BehindCount > 0 ,
0 commit comments