@@ -77,6 +77,25 @@ type Update struct {
77
77
// CLI (`gh`) to be installed and authenticated in the local environment.
78
78
OpenGhIssue bool
79
79
80
+ // GitConfig holds per-invocation Git settings applied to every `git` command via
81
+ // `git -c key=value`.
82
+ //
83
+ // Examples:
84
+ // []string{"merge.renameLimit=999999"} // improve rename detection during merges
85
+ // []string{"diff.renameLimit=999999"} // improve rename detection during diffs
86
+ // []string{"merge.conflictStyle=diff3"} // show ancestor in conflict markers
87
+ // []string{"rerere.enabled=true"} // reuse recorded resolutions
88
+ //
89
+ // Defaults:
90
+ // When no --git-config flags are provided, the updater adds:
91
+ // []string{"merge.renameLimit=999999", "diff.renameLimit=999999"}
92
+ //
93
+ // Behavior:
94
+ // • If one or more --git-config flags are supplied, those values are appended on top of the defaults.
95
+ // • To disable the defaults entirely, include a literal "disable", for example:
96
+ // --git-config disable --git-config rerere.enabled=true
97
+ GitConfig []string
98
+
80
99
// Temporary branches created during the update process. These are internal to the run
81
100
// and are surfaced for transparency/debugging:
82
101
// - AncestorBranch: clean scaffold generated from FromVersion
@@ -160,7 +179,7 @@ Resolve conflicts there, complete the merge locally, and push the branch.
160
179
// This helps apply new scaffolding changes while preserving custom code.
161
180
func (opts * Update ) Update () error {
162
181
log .Info ("Checking out base branch" , "branch" , opts .FromBranch )
163
- checkoutCmd := exec . Command ( "git" , "checkout" , opts .FromBranch )
182
+ checkoutCmd := helpers . GitCmd ( opts . GitConfig , "checkout" , opts .FromBranch )
164
183
if err := checkoutCmd .Run (); err != nil {
165
184
return fmt .Errorf ("failed to checkout base branch %s: %w" , opts .FromBranch , err )
166
185
}
@@ -219,7 +238,7 @@ func (opts *Update) Update() error {
219
238
if opts .ShowCommits {
220
239
log .Info ("Keeping commits history" )
221
240
out := opts .getOutputBranchName ()
222
- if err := exec . Command ( "git" , "checkout" , "-b" , out , opts .MergeBranch ).Run (); err != nil {
241
+ if err := helpers . GitCmd ( opts . GitConfig , "checkout" , "-b" , out , opts .MergeBranch ).Run (); err != nil {
223
242
return fmt .Errorf ("checkout %s: %w" , out , err )
224
243
}
225
244
} else {
@@ -233,8 +252,8 @@ func (opts *Update) Update() error {
233
252
if opts .Push {
234
253
if opts .Push {
235
254
out := opts .getOutputBranchName ()
236
- _ = exec . Command ( "git" , "checkout" , out ).Run ()
237
- if err := exec . Command ( "git" , "push" , "-u" , "origin" , out ).Run (); err != nil {
255
+ _ = helpers . GitCmd ( opts . GitConfig , "checkout" , out ).Run ()
256
+ if err := helpers . GitCmd ( opts . GitConfig , "push" , "-u" , "origin" , out ).Run (); err != nil {
238
257
return fmt .Errorf ("failed to push %s: %w" , out , err )
239
258
}
240
259
}
@@ -309,7 +328,7 @@ func (opts *Update) openGitHubIssue(hasConflicts bool) error {
309
328
}
310
329
311
330
func (opts * Update ) cleanupTempBranches () {
312
- _ = exec . Command ( "git" , "checkout" , opts .getOutputBranchName ()).Run ()
331
+ _ = helpers . GitCmd ( opts . GitConfig , "checkout" , opts .getOutputBranchName ()).Run ()
313
332
314
333
branches := []string {
315
334
opts .AncestorBranch ,
@@ -324,8 +343,8 @@ func (opts *Update) cleanupTempBranches() {
324
343
continue
325
344
}
326
345
// Delete only if it's a LOCAL branch.
327
- if err := exec . Command ( "git" , "show-ref" , "--verify" , "--quiet" , "refs/heads/" + b ).Run (); err == nil {
328
- _ = exec . Command ( "git" , "branch" , "-D" , b ).Run ()
346
+ if err := helpers . GitCmd ( opts . GitConfig , "show-ref" , "--verify" , "--quiet" , "refs/heads/" + b ).Run (); err == nil {
347
+ _ = helpers . GitCmd ( opts . GitConfig , "branch" , "-D" , b ).Run ()
329
348
}
330
349
}
331
350
}
@@ -345,7 +364,7 @@ func (opts *Update) preservePaths() {
345
364
if p == "" {
346
365
continue
347
366
}
348
- if err := exec . Command ( "git" , "checkout" , opts .FromBranch , "--" , p ).Run (); err != nil {
367
+ if err := helpers . GitCmd ( opts . GitConfig , "checkout" , opts .FromBranch , "--" , p ).Run (); err != nil {
349
368
log .Warn ("failed to restore preserved path" , "path" , p , "branch" , opts .FromBranch , "error" , err )
350
369
}
351
370
}
@@ -358,26 +377,26 @@ func (opts *Update) squashToOutputBranch(hasConflicts bool) error {
358
377
out := opts .getOutputBranchName ()
359
378
360
379
// 1) base -> out
361
- if err := exec . Command ( "git" , "checkout" , opts .FromBranch ).Run (); err != nil {
380
+ if err := helpers . GitCmd ( opts . GitConfig , "checkout" , opts .FromBranch ).Run (); err != nil {
362
381
return fmt .Errorf ("checkout %s: %w" , opts .FromBranch , err )
363
382
}
364
- if err := exec . Command ( "git" , "checkout" , "-B" , out , opts .FromBranch ).Run (); err != nil {
383
+ if err := helpers . GitCmd ( opts . GitConfig , "checkout" , "-B" , out , opts .FromBranch ).Run (); err != nil {
365
384
return fmt .Errorf ("create/reset %s from %s: %w" , out , opts .FromBranch , err )
366
385
}
367
386
368
387
// 2) clean worktree, then copy merge tree
369
388
if err := helpers .CleanWorktree ("output branch" ); err != nil {
370
389
return fmt .Errorf ("output branch: %w" , err )
371
390
}
372
- if err := exec . Command ( "git" , "checkout" , opts .MergeBranch , "--" , "." ).Run (); err != nil {
391
+ if err := helpers . GitCmd ( opts . GitConfig , "checkout" , opts .MergeBranch , "--" , "." ).Run (); err != nil {
373
392
return fmt .Errorf ("checkout %s content: %w" , "merge" , err )
374
393
}
375
394
376
395
// 3) optionally restore preserved paths from base (tests assert on 'git restore …')
377
396
opts .preservePaths ()
378
397
379
398
// 4) stage and single squashed commit
380
- if err := exec . Command ( "git" , "add" , "--all" ).Run (); err != nil {
399
+ if err := helpers . GitCmd ( opts . GitConfig , "add" , "--all" ).Run (); err != nil {
381
400
return fmt .Errorf ("stage output: %w" , err )
382
401
}
383
402
@@ -404,7 +423,7 @@ func regenerateProjectWithVersion(version string) error {
404
423
// prepareAncestorBranch prepares the ancestor branch by checking it out,
405
424
// cleaning up the project files, and regenerating the project with the specified version.
406
425
func (opts * Update ) prepareAncestorBranch () error {
407
- if err := exec . Command ( "git" , "checkout" , "-b" , opts .AncestorBranch , opts .FromBranch ).Run (); err != nil {
426
+ if err := helpers . GitCmd ( opts . GitConfig , "checkout" , "-b" , opts .AncestorBranch , opts .FromBranch ).Run (); err != nil {
408
427
return fmt .Errorf ("failed to create %s from %s: %w" , opts .AncestorBranch , opts .FromBranch , err )
409
428
}
410
429
if err := cleanupBranch (); err != nil {
@@ -413,7 +432,7 @@ func (opts *Update) prepareAncestorBranch() error {
413
432
if err := regenerateProjectWithVersion (opts .FromVersion ); err != nil {
414
433
return fmt .Errorf ("failed to regenerate project with fromVersion %s: %w" , opts .FromVersion , err )
415
434
}
416
- gitCmd := exec . Command ( "git" , "add" , "--all" )
435
+ gitCmd := helpers . GitCmd ( opts . GitConfig , "add" , "--all" )
417
436
if err := gitCmd .Run (); err != nil {
418
437
return fmt .Errorf ("failed to stage changes in %s: %w" , opts .AncestorBranch , err )
419
438
}
@@ -508,17 +527,17 @@ func envWithPrefixedPath(dir string) []string {
508
527
// populates it with the user's actual project content from the default branch.
509
528
// This represents the current state of the user's project.
510
529
func (opts * Update ) prepareOriginalBranch () error {
511
- gitCmd := exec . Command ( "git" , "checkout" , "-b" , opts .OriginalBranch )
530
+ gitCmd := helpers . GitCmd ( opts . GitConfig , "checkout" , "-b" , opts .OriginalBranch )
512
531
if err := gitCmd .Run (); err != nil {
513
532
return fmt .Errorf ("failed to checkout branch %s: %w" , opts .OriginalBranch , err )
514
533
}
515
534
516
- gitCmd = exec . Command ( "git" , "checkout" , opts .FromBranch , "--" , "." )
535
+ gitCmd = helpers . GitCmd ( opts . GitConfig , "checkout" , opts .FromBranch , "--" , "." )
517
536
if err := gitCmd .Run (); err != nil {
518
537
return fmt .Errorf ("failed to checkout content from %s branch onto %s: %w" , opts .FromBranch , opts .OriginalBranch , err )
519
538
}
520
539
521
- gitCmd = exec . Command ( "git" , "add" , "--all" )
540
+ gitCmd = helpers . GitCmd ( opts . GitConfig , "add" , "--all" )
522
541
if err := gitCmd .Run (); err != nil {
523
542
return fmt .Errorf ("failed to stage all changes in current: %w" , err )
524
543
}
@@ -535,13 +554,13 @@ func (opts *Update) prepareOriginalBranch() error {
535
554
// generates fresh scaffolding using the current (latest) CLI version.
536
555
// This represents what the project should look like with the new version.
537
556
func (opts * Update ) prepareUpgradeBranch () error {
538
- gitCmd := exec . Command ( "git" , "checkout" , "-b" , opts .UpgradeBranch , opts .AncestorBranch )
557
+ gitCmd := helpers . GitCmd ( opts . GitConfig , "checkout" , "-b" , opts .UpgradeBranch , opts .AncestorBranch )
539
558
if err := gitCmd .Run (); err != nil {
540
559
return fmt .Errorf ("failed to checkout %s branch off %s: %w" ,
541
560
opts .UpgradeBranch , opts .AncestorBranch , err )
542
561
}
543
562
544
- checkoutCmd := exec . Command ( "git" , "checkout" , opts .UpgradeBranch )
563
+ checkoutCmd := helpers . GitCmd ( opts . GitConfig , "checkout" , opts .UpgradeBranch )
545
564
if err := checkoutCmd .Run (); err != nil {
546
565
return fmt .Errorf ("failed to checkout base branch %s: %w" , opts .UpgradeBranch , err )
547
566
}
@@ -552,7 +571,7 @@ func (opts *Update) prepareUpgradeBranch() error {
552
571
if err := regenerateProjectWithVersion (opts .ToVersion ); err != nil {
553
572
return fmt .Errorf ("failed to regenerate project with version %s: %w" , opts .ToVersion , err )
554
573
}
555
- gitCmd = exec . Command ( "git" , "add" , "--all" )
574
+ gitCmd = helpers . GitCmd ( opts . GitConfig , "add" , "--all" )
556
575
if err := gitCmd .Run (); err != nil {
557
576
return fmt .Errorf ("failed to stage changes in %s: %w" , opts .UpgradeBranch , err )
558
577
}
@@ -566,17 +585,17 @@ func (opts *Update) prepareUpgradeBranch() error {
566
585
// mergeOriginalToUpgrade attempts to merge the upgrade branch
567
586
func (opts * Update ) mergeOriginalToUpgrade () (bool , error ) {
568
587
hasConflicts := false
569
- if err := exec . Command ( "git" , "checkout" , "-b" , opts .MergeBranch , opts .UpgradeBranch ).Run (); err != nil {
588
+ if err := helpers . GitCmd ( opts . GitConfig , "checkout" , "-b" , opts .MergeBranch , opts .UpgradeBranch ).Run (); err != nil {
570
589
return hasConflicts , fmt .Errorf ("failed to create merge branch %s from %s: %w" ,
571
590
opts .MergeBranch , opts .UpgradeBranch , err )
572
591
}
573
592
574
- checkoutCmd := exec . Command ( "git" , "checkout" , opts .MergeBranch )
593
+ checkoutCmd := helpers . GitCmd ( opts . GitConfig , "checkout" , opts .MergeBranch )
575
594
if err := checkoutCmd .Run (); err != nil {
576
595
return hasConflicts , fmt .Errorf ("failed to checkout base branch %s: %w" , opts .MergeBranch , err )
577
596
}
578
597
579
- mergeCmd := exec . Command ( "git" , "merge" , "--no-edit" , "--no-commit" , opts .OriginalBranch )
598
+ mergeCmd := helpers . GitCmd ( opts . GitConfig , "merge" , "--no-edit" , "--no-commit" , opts .OriginalBranch )
580
599
err := mergeCmd .Run ()
581
600
if err != nil {
582
601
var exitErr * exec.ExitError
@@ -608,7 +627,7 @@ func (opts *Update) mergeOriginalToUpgrade() (bool, error) {
608
627
}
609
628
610
629
// Step 4: Stage and commit
611
- if err := exec . Command ( "git" , "add" , "--all" ).Run (); err != nil {
630
+ if err := helpers . GitCmd ( opts . GitConfig , "add" , "--all" ).Run (); err != nil {
612
631
return hasConflicts , fmt .Errorf ("failed to stage merge results: %w" , err )
613
632
}
614
633
0 commit comments