11package cmd
22
33import (
4+ "context"
45 "errors"
56 "fmt"
67 "os"
78
89 "github.com/github/gh-stack/internal/config"
910 "github.com/github/gh-stack/internal/theme"
11+ "github.com/github/gh-stack/internal/update"
1012 "github.com/spf13/cobra"
1113)
1214
15+ type updateResult struct {
16+ notify update.Notification
17+ err error
18+ }
19+
1320func RootCmd () * cobra.Command {
14- cfg := config .New ()
21+ return newRootCmd (config .New (), startUpdateCheck )
22+ }
1523
24+ func newRootCmd (cfg * config.Config , startCheck func (context.Context , string ) <- chan updateResult ) * cobra.Command {
1625 root := & cobra.Command {
1726 Use : "stack <command>" ,
1827 Short : "Manage stacked branches and pull requests" ,
@@ -36,10 +45,31 @@ locally, then push to GitHub to create your stack of PRs.`,
3645 Version : Version ,
3746 SilenceUsage : true ,
3847 SilenceErrors : true ,
39- // Honor GH_STACK_THEME (auto|light|dark) before any command renders
40- PersistentPreRun : func (_ * cobra.Command , _ []string ) {
41- theme .ApplyOverride ()
42- },
48+ }
49+
50+ var updates <- chan updateResult
51+ root .PersistentPreRun = func (cmd * cobra.Command , _ []string ) {
52+ theme .ApplyOverride ()
53+ updates = nil
54+ if update .Enabled (root .Version ) && ! isHelpOrCompletionCommand (cmd ) {
55+ updates = startCheck (cmd .Context (), root .Version )
56+ }
57+ }
58+ root .PersistentPostRun = func (cmd * cobra.Command , _ []string ) {
59+ if cmd .Context ().Err () != nil {
60+ return
61+ }
62+ select {
63+ case result := <- updates :
64+ if result .notify != nil {
65+ result .err = errors .Join (result .err , result .notify (cmd .ErrOrStderr ()))
66+ }
67+ if result .err != nil && os .Getenv ("GH_DEBUG" ) != "" {
68+ fmt .Fprintf (cmd .ErrOrStderr (), "debug: gh-stack update notification: %v\n " , result .err )
69+ }
70+ default :
71+ // Never wait for a release check if the command finishes quickly
72+ }
4373 }
4474
4575 root .SetVersionTemplate ("gh stack version {{.Version}}\n " )
@@ -153,15 +183,39 @@ locally, then push to GitHub to create your stack of PRs.`,
153183 return root
154184}
155185
156- func Execute () {
157- cmd := RootCmd ()
186+ func startUpdateCheck (ctx context.Context , version string ) <- chan updateResult {
187+ results := make (chan updateResult , 1 )
188+ go func () {
189+ notify , err := update .Check (ctx , version )
190+ results <- updateResult {notify : notify , err : err }
191+ }()
192+ return results
193+ }
194+
195+ func isHelpOrCompletionCommand (cmd * cobra.Command ) bool {
196+ for c := cmd ; c != nil ; c = c .Parent () {
197+ switch c .Name () {
198+ case "help" , "completion" , cobra .ShellCompRequestCmd , cobra .ShellCompNoDescRequestCmd :
199+ return true
200+ }
201+ }
202+ return false
203+ }
204+
205+ func execute (cmd * cobra.Command , args []string ) error {
206+ ctx , cancel := context .WithCancel (context .Background ())
207+ defer cancel ()
158208
159209 // Wrap in a "gh" parent so help output shows "gh stack" instead of just "stack".
160210 wrapCmd := & cobra.Command {Use : "gh" , SilenceUsage : true , SilenceErrors : true }
161211 wrapCmd .AddCommand (cmd )
162- wrapCmd .SetArgs (append ([]string {"stack" }, os .Args [1 :]... ))
212+ wrapCmd .SetArgs (append ([]string {"stack" }, args ... ))
213+ return wrapCmd .ExecuteContext (ctx )
214+ }
163215
164- if err := wrapCmd .Execute (); err != nil {
216+ func Execute () {
217+ cmd := RootCmd ()
218+ if err := execute (cmd , os .Args [1 :]); err != nil {
165219 var exitErr * ExitError
166220 if errors .As (err , & exitErr ) {
167221 os .Exit (exitErr .Code )
0 commit comments