@@ -18,7 +18,9 @@ type RerunOptions struct {
1818 IO * iostreams.IOStreams
1919 BaseRepo func () (ghrepo.Interface , error )
2020
21- RunID string
21+ RunID string
22+ OnlyFailed bool
23+ JobID string
2224
2325 Prompt bool
2426}
@@ -37,12 +39,18 @@ func NewCmdRerun(f *cmdutil.Factory, runF func(*RerunOptions) error) *cobra.Comm
3739 // support `-R, --repo` override
3840 opts .BaseRepo = f .BaseRepo
3941
40- if len (args ) > 0 {
42+ if len (args ) == 0 && opts .JobID == "" {
43+ if ! opts .IO .CanPrompt () {
44+ return cmdutil .FlagErrorf ("`<run-id>` or `--job` required when not running interactively" )
45+ } else {
46+ opts .Prompt = true
47+ }
48+ } else if len (args ) > 0 {
4149 opts .RunID = args [0 ]
42- } else if ! opts . IO . CanPrompt () {
43- return cmdutil . FlagErrorf ( "run ID required when not running interactively" )
44- } else {
45- opts . Prompt = true
50+ }
51+
52+ if opts . RunID != "" && opts . JobID != "" {
53+ return cmdutil . FlagErrorf ( "specify only one of `<run-id>` or `--job`" )
4654 }
4755
4856 if runF != nil {
@@ -52,6 +60,9 @@ func NewCmdRerun(f *cmdutil.Factory, runF func(*RerunOptions) error) *cobra.Comm
5260 },
5361 }
5462
63+ cmd .Flags ().BoolVar (& opts .OnlyFailed , "failed" , false , "Rerun only failed jobs, including dependencies" )
64+ cmd .Flags ().StringVarP (& opts .JobID , "job" , "j" , "" , "Rerun a specific job from a run, including dependencies" )
65+
5566 return cmd
5667}
5768
@@ -67,10 +78,23 @@ func runRerun(opts *RerunOptions) error {
6778 return fmt .Errorf ("failed to determine base repo: %w" , err )
6879 }
6980
81+ cs := opts .IO .ColorScheme ()
82+
7083 runID := opts .RunID
84+ jobID := opts .JobID
85+ var selectedJob * shared.Job
86+
87+ if jobID != "" {
88+ opts .IO .StartProgressIndicator ()
89+ selectedJob , err = shared .GetJob (client , repo , jobID )
90+ opts .IO .StopProgressIndicator ()
91+ if err != nil {
92+ return fmt .Errorf ("failed to get job: %w" , err )
93+ }
94+ runID = fmt .Sprintf ("%d" , selectedJob .RunID )
95+ }
7196
7297 if opts .Prompt {
73- cs := opts .IO .ColorScheme ()
7498 runs , err := shared .GetRunsWithFilter (client , repo , nil , 10 , func (run shared.Run ) bool {
7599 if run .Status != shared .Completed {
76100 return false
@@ -83,38 +107,81 @@ func runRerun(opts *RerunOptions) error {
83107 return fmt .Errorf ("failed to get runs: %w" , err )
84108 }
85109 if len (runs ) == 0 {
86- return errors .New ("no recent runs have failed; please specify a specific run ID " )
110+ return errors .New ("no recent runs have failed; please specify a specific `< run-id>` " )
87111 }
88112 runID , err = shared .PromptForRun (cs , runs )
89113 if err != nil {
90114 return err
91115 }
92116 }
93117
94- opts .IO .StartProgressIndicator ()
95- run , err := shared .GetRun (client , repo , runID )
96- opts .IO .StopProgressIndicator ()
97- if err != nil {
98- return fmt .Errorf ("failed to get run: %w" , err )
118+ if opts .JobID != "" {
119+ err = rerunJob (client , repo , selectedJob )
120+ if err != nil {
121+ return err
122+ }
123+ if opts .IO .IsStdoutTTY () {
124+ fmt .Fprintf (opts .IO .Out , "%s Requested rerun of job %s on run %s\n " ,
125+ cs .SuccessIcon (),
126+ cs .Cyanf ("%d" , selectedJob .ID ),
127+ cs .Cyanf ("%d" , selectedJob .RunID ))
128+ }
129+ } else {
130+ opts .IO .StartProgressIndicator ()
131+ run , err := shared .GetRun (client , repo , runID )
132+ opts .IO .StopProgressIndicator ()
133+ if err != nil {
134+ return fmt .Errorf ("failed to get run: %w" , err )
135+ }
136+
137+ err = rerunRun (client , repo , run , opts .OnlyFailed )
138+ if err != nil {
139+ return err
140+ }
141+ if opts .IO .IsStdoutTTY () {
142+ onlyFailedMsg := ""
143+ if opts .OnlyFailed {
144+ onlyFailedMsg = "(failed jobs) "
145+ }
146+ fmt .Fprintf (opts .IO .Out , "%s Requested rerun %sof run %s\n " ,
147+ cs .SuccessIcon (),
148+ onlyFailedMsg ,
149+ cs .Cyanf ("%d" , run .ID ))
150+ }
99151 }
100152
101- path := fmt .Sprintf ("repos/%s/actions/runs/%d/rerun" , ghrepo .FullName (repo ), run .ID )
153+ return nil
154+ }
102155
103- err = client .REST (repo .RepoHost (), "POST" , path , nil , nil )
156+ func rerunRun (client * api.Client , repo ghrepo.Interface , run * shared.Run , onlyFailed bool ) error {
157+ runVerb := "rerun"
158+ if onlyFailed {
159+ runVerb = "rerun-failed-jobs"
160+ }
161+
162+ path := fmt .Sprintf ("repos/%s/actions/runs/%d/%s" , ghrepo .FullName (repo ), run .ID , runVerb )
163+
164+ err := client .REST (repo .RepoHost (), "POST" , path , nil , nil )
104165 if err != nil {
105166 var httpError api.HTTPError
106167 if errors .As (err , & httpError ) && httpError .StatusCode == 403 {
107- return fmt .Errorf ("run %d cannot be rerun; its workflow file may be broken. " , run .ID )
168+ return fmt .Errorf ("run %d cannot be rerun; its workflow file may be broken" , run .ID )
108169 }
109170 return fmt .Errorf ("failed to rerun: %w" , err )
110171 }
172+ return nil
173+ }
111174
112- if opts .IO .CanPrompt () {
113- cs := opts .IO .ColorScheme ()
114- fmt .Fprintf (opts .IO .Out , "%s Requested rerun of run %s\n " ,
115- cs .SuccessIcon (),
116- cs .Cyanf ("%d" , run .ID ))
117- }
175+ func rerunJob (client * api.Client , repo ghrepo.Interface , job * shared.Job ) error {
176+ path := fmt .Sprintf ("repos/%s/actions/jobs/%d/rerun" , ghrepo .FullName (repo ), job .ID )
118177
178+ err := client .REST (repo .RepoHost (), "POST" , path , nil , nil )
179+ if err != nil {
180+ var httpError api.HTTPError
181+ if errors .As (err , & httpError ) && httpError .StatusCode == 403 {
182+ return fmt .Errorf ("job %d cannot be rerun" , job .ID )
183+ }
184+ return fmt .Errorf ("failed to rerun: %w" , err )
185+ }
119186 return nil
120187}
0 commit comments