@@ -20,6 +20,7 @@ type RerunOptions struct {
2020
2121 RunID string
2222 OnlyFailed bool
23+ JobID string
2324
2425 Prompt bool
2526}
@@ -38,12 +39,22 @@ func NewCmdRerun(f *cmdutil.Factory, runF func(*RerunOptions) error) *cobra.Comm
3839 // support `-R, --repo` override
3940 opts .BaseRepo = f .BaseRepo
4041
41- if len (args ) > 0 {
42+ if len (args ) == 0 && opts .JobID == "" {
43+ if ! opts .IO .CanPrompt () {
44+ return cmdutil .FlagErrorf ("run or job ID required when not running interactively" )
45+ } else {
46+ opts .Prompt = true
47+ }
48+ } else if len (args ) > 0 {
4249 opts .RunID = args [0 ]
43- } else if ! opts .IO .CanPrompt () {
44- return cmdutil .FlagErrorf ("run ID required when not running interactively" )
45- } else {
46- opts .Prompt = true
50+ }
51+
52+ if opts .RunID != "" && opts .JobID != "" {
53+ opts .RunID = ""
54+ if opts .IO .CanPrompt () {
55+ cs := opts .IO .ColorScheme ()
56+ fmt .Fprintf (opts .IO .ErrOut , "%s both run and job IDs specified; ignoring run ID\n " , cs .WarningIcon ())
57+ }
4758 }
4859
4960 if runF != nil {
@@ -54,6 +65,7 @@ func NewCmdRerun(f *cmdutil.Factory, runF func(*RerunOptions) error) *cobra.Comm
5465 }
5566
5667 cmd .Flags ().BoolVar (& opts .OnlyFailed , "failed" , false , "Rerun only failed jobs" )
68+ cmd .Flags ().StringVarP (& opts .JobID , "job" , "j" , "" , "Rerun a specific job from a run, including dependencies" )
5769
5870 return cmd
5971}
@@ -70,10 +82,23 @@ func runRerun(opts *RerunOptions) error {
7082 return fmt .Errorf ("failed to determine base repo: %w" , err )
7183 }
7284
85+ cs := opts .IO .ColorScheme ()
86+
7387 runID := opts .RunID
88+ jobID := opts .JobID
89+ var selectedJob * shared.Job
90+
91+ if jobID != "" {
92+ opts .IO .StartProgressIndicator ()
93+ selectedJob , err = shared .GetJob (client , repo , jobID )
94+ opts .IO .StopProgressIndicator ()
95+ if err != nil {
96+ return fmt .Errorf ("failed to get job: %w" , err )
97+ }
98+ runID = fmt .Sprintf ("%d" , selectedJob .RunID )
99+ }
74100
75101 if opts .Prompt {
76- cs := opts .IO .ColorScheme ()
77102 runs , err := shared .GetRunsWithFilter (client , repo , nil , 10 , func (run shared.Run ) bool {
78103 if run .Status != shared .Completed {
79104 return false
@@ -94,40 +119,73 @@ func runRerun(opts *RerunOptions) error {
94119 }
95120 }
96121
97- opts .IO .StartProgressIndicator ()
98- run , err := shared .GetRun (client , repo , runID )
99- opts .IO .StopProgressIndicator ()
100- if err != nil {
101- return fmt .Errorf ("failed to get run: %w" , err )
122+ if opts .JobID != "" {
123+ err = rerunJob (client , repo , selectedJob )
124+ if err != nil {
125+ return err
126+ }
127+ if opts .IO .CanPrompt () {
128+ fmt .Fprintf (opts .IO .Out , "%s Requested rerun of job %s on run %s\n " ,
129+ cs .SuccessIcon (),
130+ cs .Cyanf ("%d" , selectedJob .ID ),
131+ cs .Cyanf ("%d" , selectedJob .RunID ))
132+ }
133+ } else {
134+ opts .IO .StartProgressIndicator ()
135+ run , err := shared .GetRun (client , repo , runID )
136+ opts .IO .StopProgressIndicator ()
137+ if err != nil {
138+ return fmt .Errorf ("failed to get run: %w" , err )
139+ }
140+
141+ err = rerunRun (client , repo , run , opts .OnlyFailed )
142+ if err != nil {
143+ return err
144+ }
145+ if opts .IO .CanPrompt () {
146+ onlyFailedMsg := ""
147+ if opts .OnlyFailed {
148+ onlyFailedMsg = "(failed jobs) "
149+ }
150+ fmt .Fprintf (opts .IO .Out , "%s Requested rerun %sof run %s\n " ,
151+ cs .SuccessIcon (),
152+ onlyFailedMsg ,
153+ cs .Cyanf ("%d" , run .ID ))
154+ }
102155 }
103156
157+ return nil
158+ }
159+
160+ func rerunRun (client * api.Client , repo ghrepo.Interface , run * shared.Run , onlyFailed bool ) error {
104161 runVerb := "rerun"
105- if opts . OnlyFailed {
162+ if onlyFailed {
106163 runVerb = "rerun-failed-jobs"
107164 }
108165
109166 path := fmt .Sprintf ("repos/%s/actions/runs/%d/%s" , ghrepo .FullName (repo ), run .ID , runVerb )
110167
111- err = client .REST (repo .RepoHost (), "POST" , path , nil , nil )
168+ err : = client .REST (repo .RepoHost (), "POST" , path , nil , nil )
112169 if err != nil {
113170 var httpError api.HTTPError
114171 if errors .As (err , & httpError ) && httpError .StatusCode == 403 {
115- return fmt .Errorf ("run %d cannot be rerun; its workflow file may be broken. " , run .ID )
172+ return fmt .Errorf ("run %d cannot be rerun; its workflow file may be broken" , run .ID )
116173 }
117174 return fmt .Errorf ("failed to rerun: %w" , err )
118175 }
176+ return nil
177+ }
178+
179+ func rerunJob (client * api.Client , repo ghrepo.Interface , job * shared.Job ) error {
180+ path := fmt .Sprintf ("repos/%s/actions/jobs/%d/rerun" , ghrepo .FullName (repo ), job .ID )
119181
120- if opts . IO . CanPrompt () {
121- cs := opts . IO . ColorScheme ()
122- onlyFailedMsg := ""
123- if opts . OnlyFailed {
124- onlyFailedMsg = "(failed jobs) "
182+ err := client . REST ( repo . RepoHost (), "POST" , path , nil , nil )
183+ if err != nil {
184+ var httpError api. HTTPError
185+ if errors . As ( err , & httpError ) && httpError . StatusCode == 403 {
186+ return fmt . Errorf ( "job %d cannot be rerun" , job . ID )
125187 }
126- fmt .Fprintf (opts .IO .Out , "%s Requested rerun %sof run %s\n " ,
127- cs .SuccessIcon (),
128- onlyFailedMsg ,
129- cs .Cyanf ("%d" , run .ID ))
188+ return fmt .Errorf ("failed to rerun: %w" , err )
130189 }
131-
132190 return nil
133191}
0 commit comments