11package build
22
33import (
4- "bytes"
54 "context"
65 "errors"
76 "fmt"
87 "io"
98 "net/http"
109 "net/url"
1110 "os"
12- "os/exec"
1311 "path/filepath"
1412 "strings"
1513 "time"
1614
1715 tea "github.com/charmbracelet/bubbletea"
1816 "github.com/stainless-api/stainless-api-cli/pkg/console"
17+ "github.com/stainless-api/stainless-api-cli/pkg/git"
1918 "github.com/stainless-api/stainless-api-cli/pkg/stainlessutils"
2019 "github.com/stainless-api/stainless-api-go"
2120)
@@ -27,6 +26,7 @@ type Model struct {
2726
2827 Client stainless.Client
2928 Ctx context.Context
29+ Branch string // Optional branch name for git checkout
3030 Downloads map [stainless.Target ]DownloadStatus // When a BuildTarget has a commit available, this target will download it, if it has been specified in the initialization.
3131 Err error // This will be populated if the model concludes with an error
3232}
@@ -43,11 +43,13 @@ type FetchBuildMsg stainless.Build
4343type ErrorMsg error
4444type DownloadMsg struct {
4545 Target stainless.Target
46+ // One of "not_started", "in_progress", "completed"
47+ Status string
4648 // One of "success", "failure',
4749 Conclusion string
4850}
4951
50- func NewModel (client stainless.Client , ctx context.Context , build stainless.Build , downloadPaths map [stainless.Target ]string ) Model {
52+ func NewModel (client stainless.Client , ctx context.Context , build stainless.Build , branch string , downloadPaths map [stainless.Target ]string ) Model {
5153 downloads := map [stainless.Target ]DownloadStatus {}
5254 for target , path := range downloadPaths {
5355 downloads [target ] = DownloadStatus {
@@ -60,6 +62,7 @@ func NewModel(client stainless.Client, ctx context.Context, build stainless.Buil
6062 Build : build ,
6163 Client : client ,
6264 Ctx : ctx ,
65+ Branch : branch ,
6366 Downloads : downloads ,
6467 }
6568}
@@ -108,7 +111,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
108111 status , _ , conclusion := buildTarget .StepInfo ("commit" )
109112 downloadable := status == "completed" && conclusion != "fatal"
110113 if download , ok := m .Downloads [target ]; ok && downloadable && download .Status == "not_started" {
111- download .Status = "started "
114+ download .Status = "in_progress "
112115 cmds = append (cmds , m .downloadTarget (target ))
113116 m .Downloads [target ] = download
114117 }
@@ -136,11 +139,12 @@ func (m Model) downloadTarget(target stainless.Target) tea.Cmd {
136139 if err != nil {
137140 return ErrorMsg (err )
138141 }
139- err = PullOutput (outputRes .Output , outputRes .URL , outputRes .Ref , m .Downloads [target ].Path , console .NewGroup (true ))
142+ err = PullOutput (outputRes .Output , outputRes .URL , outputRes .Ref , m .Branch , m . Downloads [target ].Path , console .NewGroup (true ))
140143 if err != nil {
141- return DownloadMsg {target , "failure" }
144+ console .Error (fmt .Sprintf ("Failed to download %s: %v" , target , err ))
145+ return DownloadMsg {target , "completed" , "failure" }
142146 }
143- return DownloadMsg {target , "success" }
147+ return DownloadMsg {target , "completed" , " success" }
144148 }
145149}
146150
@@ -216,8 +220,44 @@ func extractFilename(urlStr string, resp *http.Response) string {
216220 return extractFilenameFromURL (urlStr )
217221}
218222
223+ // checkoutBranchIfSafe attempts to checkout a branch if it's safe to do so.
224+ // Returns true if the branch was checked out, false if we should checkout the ref instead.
225+ func checkoutBranchIfSafe (targetDir , branch , ref string , targetGroup console.Group ) (bool , error ) {
226+ remoteBranch := "origin/" + branch
227+
228+ // Check if the remote branch exists and matches the ref
229+ remoteSHA , err := git .RevParse (targetDir , remoteBranch )
230+ if err != nil || remoteSHA != ref {
231+ // Remote branch doesn't exist or doesn't match ref, checkout ref instead
232+ return false , nil
233+ }
234+
235+ // Check if local branch exists
236+ localSHA , err := git .RevParse (targetDir , branch )
237+ if err != nil {
238+ // Local branch doesn't exist, create it tracking the remote
239+ targetGroup .Property ("checking out branch" , branch )
240+ if err := git .Checkout (targetDir , "-b" , branch , remoteBranch ); err != nil {
241+ return false , err
242+ }
243+ return true , nil
244+ }
245+
246+ // Local branch exists - only checkout if it points to the same SHA as ref
247+ if localSHA == ref {
248+ targetGroup .Property ("checking out branch" , branch )
249+ if err := git .Checkout (targetDir , branch ); err != nil {
250+ return false , err
251+ }
252+ return true , nil
253+ }
254+
255+ // Local branch exists but points to a different SHA, checkout ref instead to be safe
256+ return false , nil
257+ }
258+
219259// PullOutput handles downloading or cloning a build target output
220- func PullOutput (output , url , ref , targetDir string , targetGroup console.Group ) error {
260+ func PullOutput (output , url , ref , branch , targetDir string , targetGroup console.Group ) error {
221261 switch output {
222262 case "git" :
223263 // Extract repository name from git URL for directory name
@@ -251,51 +291,51 @@ func PullOutput(output, url, ref, targetDir string, targetGroup console.Group) e
251291 }
252292
253293 // Initialize git repository
254- cmd := exec .Command ("git" , "-C" , targetDir , "init" )
255- var stderr bytes.Buffer
256- cmd .Stdout = nil
257- cmd .Stderr = & stderr
258- if err := cmd .Run (); err != nil {
259- return fmt .Errorf ("git init failed: %v\n Git error: %s" , err , stderr .String ())
294+ if err := git .Init (targetDir ); err != nil {
295+ return err
260296 }
261297 }
262298
263299 {
264300 // Check if origin remote exists, add it if not present
265- cmd := exec .Command ("git" , "-C" , targetDir , "remote" , "get-url" , "origin" )
266- var stderr bytes.Buffer
267- cmd .Stdout = nil
268- cmd .Stderr = & stderr
269- if err := cmd .Run (); err != nil {
301+ if _ , err := git .RemoteGetURL (targetDir , "origin" ); err != nil {
270302 // Origin doesn't exist, add it with stripped auth
271303 targetGroup .Property ("adding remote origin" , stripHTTPAuth (url ))
272- addCmd := exec .Command ("git" , "-C" , targetDir , "remote" , "add" , "origin" , stripHTTPAuth (url ))
273- var addStderr bytes.Buffer
274- addCmd .Stdout = nil
275- addCmd .Stderr = & addStderr
276- if err := addCmd .Run (); err != nil {
277- return fmt .Errorf ("git remote add failed: %v\n Git error: %s" , err , addStderr .String ())
304+ if err := git .RemoteAdd (targetDir , "origin" , stripHTTPAuth (url )); err != nil {
305+ return err
278306 }
279307 }
280308
281309 targetGroup .Property ("fetching from" , stripHTTPAuth (url ))
282- cmd = exec .Command ("git" , "-C" , targetDir , "fetch" , url , ref )
283- cmd .Stdout = nil
284- cmd .Stderr = & stderr
285- if err := cmd .Run (); err != nil {
286- return fmt .Errorf ("git fetch failed: %v\n Git error: %s" , err , stderr .String ())
310+ // Fetch the specific ref
311+ if err := git .Fetch (targetDir , url , ref ); err != nil {
312+ return err
313+ }
314+
315+ // Also fetch the branch if provided, so we can check if it points to the same ref
316+ if branch != "" {
317+ // Branch fetch is best-effort - ignore errors
318+ _ = git .Fetch (targetDir , url , branch + ":refs/remotes/origin/" + branch )
287319 }
288320 }
289321
290- // Checkout the specific ref
322+ // Checkout the specific ref or branch
291323 {
292- targetGroup .Property ("checking out ref" , ref )
293- cmd := exec .Command ("git" , "-C" , targetDir , "checkout" , ref )
294- var stderr bytes.Buffer
295- cmd .Stdout = nil // Suppress git checkout output
296- cmd .Stderr = & stderr
297- if err := cmd .Run (); err != nil {
298- return fmt .Errorf ("git checkout failed: %v\n Git error: %s" , err , stderr .String ())
324+ checkedOutBranch := false
325+ if branch != "" {
326+ var err error
327+ checkedOutBranch , err = checkoutBranchIfSafe (targetDir , branch , ref , targetGroup )
328+ if err != nil {
329+ return err
330+ }
331+ }
332+
333+ if ! checkedOutBranch {
334+ // Checkout the ref directly (detached HEAD)
335+ targetGroup .Property ("checking out ref" , ref )
336+ if err := git .Checkout (targetDir , ref ); err != nil {
337+ return err
338+ }
299339 }
300340 }
301341
0 commit comments