@@ -10,6 +10,20 @@ import (
1010 "time"
1111)
1212
13+ // doltRetry runs fn up to 3 times with backoff (1s, 2s) between attempts.
14+ func doltRetry (fn func () error ) error {
15+ var err error
16+ for i := 0 ; i < 3 ; i ++ {
17+ if i > 0 {
18+ time .Sleep (time .Duration (i ) * time .Second )
19+ }
20+ if err = fn (); err == nil {
21+ return nil
22+ }
23+ }
24+ return err
25+ }
26+
1327// DoltHubToken returns the DoltHub API token from the environment.
1428func DoltHubToken () string {
1529 return os .Getenv ("DOLTHUB_TOKEN" )
@@ -48,45 +62,60 @@ func PushWithSync(dbDir string, stdout io.Writer) error {
4862}
4963
5064func pushRemote (dbDir , remote string ) error {
51- ctx , cancel := context .WithTimeout (context .Background (), 60 * time .Second )
52- defer cancel ()
53- cmd := exec .CommandContext (ctx , "dolt" , "push" , remote , "main" )
54- cmd .Dir = dbDir
55- output , err := cmd .CombinedOutput ()
56- if err != nil {
57- return fmt .Errorf ("dolt push %s main: %w (%s)" , remote , err , strings .TrimSpace (string (output )))
58- }
59- return nil
65+ return doltRetry (func () error {
66+ ctx , cancel := context .WithTimeout (context .Background (), 60 * time .Second )
67+ defer cancel ()
68+ cmd := exec .CommandContext (ctx , "dolt" , "push" , remote , "main" )
69+ cmd .Dir = dbDir
70+ output , err := cmd .CombinedOutput ()
71+ if err != nil {
72+ return fmt .Errorf ("dolt push %s main: %w (%s)" , remote , err , strings .TrimSpace (string (output )))
73+ }
74+ return nil
75+ })
6076}
6177
6278func pullRemote (dbDir , remote string ) error {
63- ctx , cancel := context .WithTimeout (context .Background (), 60 * time .Second )
64- defer cancel ()
65- cmd := exec .CommandContext (ctx , "dolt" , "pull" , remote , "main" )
66- cmd .Dir = dbDir
67- output , err := cmd .CombinedOutput ()
68- if err != nil {
69- return fmt .Errorf ("dolt pull %s main: %w (%s)" , remote , err , strings .TrimSpace (string (output )))
70- }
71- return nil
79+ return doltRetry (func () error {
80+ ctx , cancel := context .WithTimeout (context .Background (), 60 * time .Second )
81+ defer cancel ()
82+ cmd := exec .CommandContext (ctx , "dolt" , "pull" , remote , "main" )
83+ cmd .Dir = dbDir
84+ output , err := cmd .CombinedOutput ()
85+ if err != nil {
86+ return fmt .Errorf ("dolt pull %s main: %w (%s)" , remote , err , strings .TrimSpace (string (output )))
87+ }
88+ return nil
89+ })
7290}
7391
7492// PullUpstream pulls the latest changes from the upstream remote.
7593func PullUpstream (dbDir string ) error {
7694 return pullRemote (dbDir , "upstream" )
7795}
7896
97+ // ResetMainToUpstream fetches upstream and hard-resets local main to match.
98+ // Used in PR mode where main should always mirror upstream exactly.
99+ func ResetMainToUpstream (dbDir string ) error {
100+ if err := FetchRemote (dbDir , "upstream" ); err != nil {
101+ return err
102+ }
103+ return doltExec (dbDir , "reset" , "--hard" , "upstream/main" )
104+ }
105+
79106// FetchRemote fetches the latest refs from a named remote without merging.
80107func FetchRemote (dbDir , remote string ) error {
81- ctx , cancel := context .WithTimeout (context .Background (), 60 * time .Second )
82- defer cancel ()
83- cmd := exec .CommandContext (ctx , "dolt" , "fetch" , remote )
84- cmd .Dir = dbDir
85- output , err := cmd .CombinedOutput ()
86- if err != nil {
87- return fmt .Errorf ("dolt fetch %s: %w (%s)" , remote , err , strings .TrimSpace (string (output )))
88- }
89- return nil
108+ return doltRetry (func () error {
109+ ctx , cancel := context .WithTimeout (context .Background (), 60 * time .Second )
110+ defer cancel ()
111+ cmd := exec .CommandContext (ctx , "dolt" , "fetch" , remote )
112+ cmd .Dir = dbDir
113+ output , err := cmd .CombinedOutput ()
114+ if err != nil {
115+ return fmt .Errorf ("dolt fetch %s: %w (%s)" , remote , err , strings .TrimSpace (string (output )))
116+ }
117+ return nil
118+ })
90119}
91120
92121// doltSQLScript executes a SQL script against a dolt database directory.
@@ -103,16 +132,17 @@ func doltSQLScript(dbDir, script string) error {
103132 }
104133 _ = tmpFile .Close ()
105134
106- ctx , cancel := context .WithTimeout (context .Background (), 30 * time .Second )
107- defer cancel ()
108-
109- cmd := exec .CommandContext (ctx , "dolt" , "sql" , "--file" , tmpFile .Name ())
110- cmd .Dir = dbDir
111- output , err := cmd .CombinedOutput ()
112- if err != nil {
113- return fmt .Errorf ("%w (output: %s)" , err , strings .TrimSpace (string (output )))
114- }
115- return nil
135+ return doltRetry (func () error {
136+ ctx , cancel := context .WithTimeout (context .Background (), 30 * time .Second )
137+ defer cancel ()
138+ cmd := exec .CommandContext (ctx , "dolt" , "sql" , "--file" , tmpFile .Name ())
139+ cmd .Dir = dbDir
140+ output , err := cmd .CombinedOutput ()
141+ if err != nil {
142+ return fmt .Errorf ("%w (output: %s)" , err , strings .TrimSpace (string (output )))
143+ }
144+ return nil
145+ })
116146}
117147
118148// BranchName returns the conventional branch name for a PR-mode mutation.
@@ -153,36 +183,65 @@ func CheckoutBranch(dbDir, branch string) error {
153183 return doltExec (dbDir , "checkout" , branch )
154184}
155185
186+ // CheckoutBranchFrom creates a branch from startPoint (deleting any existing
187+ // branch first), then checks it out. Used in PR mode to ensure branches start
188+ // from a clean upstream-aligned base rather than a potentially dirty HEAD.
189+ func CheckoutBranchFrom (dbDir , branch , startPoint string ) error {
190+ exists , err := BranchExists (dbDir , branch )
191+ if err != nil {
192+ return fmt .Errorf ("checking branch %s: %w" , branch , err )
193+ }
194+ if exists {
195+ // Delete and recreate from the specified start point so the branch
196+ // doesn't carry stale commits from a previous dirty main.
197+ if err := doltExec (dbDir , "branch" , "-D" , branch ); err != nil {
198+ return fmt .Errorf ("deleting stale branch %s: %w" , branch , err )
199+ }
200+ }
201+ if err := doltExec (dbDir , "branch" , branch , startPoint ); err != nil {
202+ return fmt .Errorf ("creating branch %s from %s: %w" , branch , startPoint , err )
203+ }
204+ return doltExec (dbDir , "checkout" , branch )
205+ }
206+
156207// CheckoutMain switches the working directory back to the main branch.
157208func CheckoutMain (dbDir string ) error {
158209 return doltExec (dbDir , "checkout" , "main" )
159210}
160211
161212// doltExec runs a dolt CLI command in the given database directory.
162213func doltExec (dbDir string , args ... string ) error {
163- ctx , cancel := context .WithTimeout (context .Background (), 30 * time .Second )
164- defer cancel ()
165- cmd := exec .CommandContext (ctx , "dolt" , args ... )
166- cmd .Dir = dbDir
167- output , err := cmd .CombinedOutput ()
168- if err != nil {
169- return fmt .Errorf ("dolt %s: %w (%s)" , strings .Join (args , " " ), err , strings .TrimSpace (string (output )))
170- }
171- return nil
214+ return doltRetry (func () error {
215+ ctx , cancel := context .WithTimeout (context .Background (), 30 * time .Second )
216+ defer cancel ()
217+ cmd := exec .CommandContext (ctx , "dolt" , args ... )
218+ cmd .Dir = dbDir
219+ output , err := cmd .CombinedOutput ()
220+ if err != nil {
221+ return fmt .Errorf ("dolt %s: %w (%s)" , strings .Join (args , " " ), err , strings .TrimSpace (string (output )))
222+ }
223+ return nil
224+ })
172225}
173226
174227// PushBranch force-pushes a named branch to origin.
175228// Force is always used because wl/* branches on the user's own fork may
176229// have diverged history after redo operations (unclaim then re-claim, etc.).
177230func PushBranch (dbDir , branch string , stdout io.Writer ) error {
178- ctx , cancel := context .WithTimeout (context .Background (), 60 * time .Second )
179- defer cancel ()
180- cmd := exec .CommandContext (ctx , "dolt" , "push" , "--force" , "origin" , branch )
181- cmd .Dir = dbDir
182- output , err := cmd .CombinedOutput ()
231+ err := doltRetry (func () error {
232+ ctx , cancel := context .WithTimeout (context .Background (), 60 * time .Second )
233+ defer cancel ()
234+ cmd := exec .CommandContext (ctx , "dolt" , "push" , "--force" , "origin" , branch )
235+ cmd .Dir = dbDir
236+ output , err := cmd .CombinedOutput ()
237+ if err != nil {
238+ return fmt .Errorf ("push branch %s: %w (%s)" , branch , err , strings .TrimSpace (string (output )))
239+ }
240+ return nil
241+ })
183242 if err != nil {
184- fmt .Fprintf (stdout , " warning: push branch %s to origin failed: %v (%s) \n " , branch , err , strings . TrimSpace ( string ( output )) )
185- return fmt . Errorf ( "push branch %s: %w" , branch , err )
243+ fmt .Fprintf (stdout , " warning: %v \n " , err )
244+ return err
186245 }
187246 fmt .Fprintf (stdout , " Pushed branch %s to origin\n " , branch )
188247 return nil
@@ -236,15 +295,17 @@ func DeleteBranch(dbDir, branch string) error {
236295
237296// DeleteRemoteBranch deletes a branch on a named remote using refspec syntax.
238297func DeleteRemoteBranch (dbDir , remote , branch string ) error {
239- ctx , cancel := context .WithTimeout (context .Background (), 60 * time .Second )
240- defer cancel ()
241- cmd := exec .CommandContext (ctx , "dolt" , "push" , remote , ":" + branch )
242- cmd .Dir = dbDir
243- output , err := cmd .CombinedOutput ()
244- if err != nil {
245- return fmt .Errorf ("dolt push %s :%s: %w (%s)" , remote , branch , err , strings .TrimSpace (string (output )))
246- }
247- return nil
298+ return doltRetry (func () error {
299+ ctx , cancel := context .WithTimeout (context .Background (), 60 * time .Second )
300+ defer cancel ()
301+ cmd := exec .CommandContext (ctx , "dolt" , "push" , remote , ":" + branch )
302+ cmd .Dir = dbDir
303+ output , err := cmd .CombinedOutput ()
304+ if err != nil {
305+ return fmt .Errorf ("dolt push %s :%s: %w (%s)" , remote , branch , err , strings .TrimSpace (string (output )))
306+ }
307+ return nil
308+ })
248309}
249310
250311// EnsureGitHubRemote adds a "github" Dolt remote pointing to the rig's
@@ -286,18 +347,24 @@ func PushBranchToRemote(dbDir, remote, branch string, stdout io.Writer) error {
286347
287348// PushBranchToRemoteForce pushes a branch to a named remote, optionally with --force.
288349func PushBranchToRemoteForce (dbDir , remote , branch string , force bool , stdout io.Writer ) error {
289- ctx , cancel := context .WithTimeout (context .Background (), 60 * time .Second )
290- defer cancel ()
291- args := []string {"push" }
292- if force {
293- args = append (args , "--force" )
294- }
295- args = append (args , remote , branch )
296- cmd := exec .CommandContext (ctx , "dolt" , args ... )
297- cmd .Dir = dbDir
298- output , err := cmd .CombinedOutput ()
350+ err := doltRetry (func () error {
351+ ctx , cancel := context .WithTimeout (context .Background (), 60 * time .Second )
352+ defer cancel ()
353+ args := []string {"push" }
354+ if force {
355+ args = append (args , "--force" )
356+ }
357+ args = append (args , remote , branch )
358+ cmd := exec .CommandContext (ctx , "dolt" , args ... )
359+ cmd .Dir = dbDir
360+ output , err := cmd .CombinedOutput ()
361+ if err != nil {
362+ return fmt .Errorf ("dolt push %s %s: %w (%s)" , remote , branch , err , strings .TrimSpace (string (output )))
363+ }
364+ return nil
365+ })
299366 if err != nil {
300- return fmt . Errorf ( "dolt push %s %s: %w (%s)" , remote , branch , err , strings . TrimSpace ( string ( output )))
367+ return err
301368 }
302369 fmt .Fprintf (stdout , " Pushed branch %s to %s\n " , branch , remote )
303370 return nil
@@ -382,14 +449,18 @@ func QueryItemStatusAsOf(dbDir, wantedID, ref string) string {
382449
383450// DoltSQLQuery executes a SQL query and returns the raw CSV output.
384451func DoltSQLQuery (dbDir , query string ) (string , error ) {
385- ctx , cancel := context .WithTimeout (context .Background (), 15 * time .Second )
386- defer cancel ()
387-
388- cmd := exec .CommandContext (ctx , "dolt" , "sql" , "-r" , "csv" , "-q" , query )
389- cmd .Dir = dbDir
390- output , err := cmd .CombinedOutput ()
391- if err != nil {
392- return "" , fmt .Errorf ("dolt sql query failed: %w (%s)" , err , strings .TrimSpace (string (output )))
393- }
394- return string (output ), nil
452+ var result string
453+ err := doltRetry (func () error {
454+ ctx , cancel := context .WithTimeout (context .Background (), 15 * time .Second )
455+ defer cancel ()
456+ cmd := exec .CommandContext (ctx , "dolt" , "sql" , "-r" , "csv" , "-q" , query )
457+ cmd .Dir = dbDir
458+ output , err := cmd .CombinedOutput ()
459+ if err != nil {
460+ return fmt .Errorf ("dolt sql query failed: %w (%s)" , err , strings .TrimSpace (string (output )))
461+ }
462+ result = string (output )
463+ return nil
464+ })
465+ return result , err
395466}
0 commit comments