@@ -77,6 +77,25 @@ type Update struct {
7777 // CLI (`gh`) to be installed and authenticated in the local environment.
7878 OpenGhIssue bool
7979
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+
8099 // Temporary branches created during the update process. These are internal to the run
81100 // and are surfaced for transparency/debugging:
82101 // - AncestorBranch: clean scaffold generated from FromVersion
@@ -160,7 +179,7 @@ Resolve conflicts there, complete the merge locally, and push the branch.
160179// This helps apply new scaffolding changes while preserving custom code.
161180func (opts * Update ) Update () error {
162181 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 )
164183 if err := checkoutCmd .Run (); err != nil {
165184 return fmt .Errorf ("failed to checkout base branch %s: %w" , opts .FromBranch , err )
166185 }
@@ -219,7 +238,7 @@ func (opts *Update) Update() error {
219238 if opts .ShowCommits {
220239 log .Info ("Keeping commits history" )
221240 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 {
223242 return fmt .Errorf ("checkout %s: %w" , out , err )
224243 }
225244 } else {
@@ -233,8 +252,8 @@ func (opts *Update) Update() error {
233252 if opts .Push {
234253 if opts .Push {
235254 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 {
238257 return fmt .Errorf ("failed to push %s: %w" , out , err )
239258 }
240259 }
@@ -309,7 +328,7 @@ func (opts *Update) openGitHubIssue(hasConflicts bool) error {
309328}
310329
311330func (opts * Update ) cleanupTempBranches () {
312- _ = exec . Command ( "git" , "checkout" , opts .getOutputBranchName ()).Run ()
331+ _ = helpers . GitCmd ( opts . GitConfig , "checkout" , opts .getOutputBranchName ()).Run ()
313332
314333 branches := []string {
315334 opts .AncestorBranch ,
@@ -324,8 +343,8 @@ func (opts *Update) cleanupTempBranches() {
324343 continue
325344 }
326345 // 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 ()
329348 }
330349 }
331350}
@@ -345,7 +364,7 @@ func (opts *Update) preservePaths() {
345364 if p == "" {
346365 continue
347366 }
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 {
349368 log .Warn ("failed to restore preserved path" , "path" , p , "branch" , opts .FromBranch , "error" , err )
350369 }
351370 }
@@ -358,26 +377,26 @@ func (opts *Update) squashToOutputBranch(hasConflicts bool) error {
358377 out := opts .getOutputBranchName ()
359378
360379 // 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 {
362381 return fmt .Errorf ("checkout %s: %w" , opts .FromBranch , err )
363382 }
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 {
365384 return fmt .Errorf ("create/reset %s from %s: %w" , out , opts .FromBranch , err )
366385 }
367386
368387 // 2) clean worktree, then copy merge tree
369388 if err := helpers .CleanWorktree ("output branch" ); err != nil {
370389 return fmt .Errorf ("output branch: %w" , err )
371390 }
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 {
373392 return fmt .Errorf ("checkout %s content: %w" , "merge" , err )
374393 }
375394
376395 // 3) optionally restore preserved paths from base (tests assert on 'git restore …')
377396 opts .preservePaths ()
378397
379398 // 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 {
381400 return fmt .Errorf ("stage output: %w" , err )
382401 }
383402
@@ -404,7 +423,7 @@ func regenerateProjectWithVersion(version string) error {
404423// prepareAncestorBranch prepares the ancestor branch by checking it out,
405424// cleaning up the project files, and regenerating the project with the specified version.
406425func (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 {
408427 return fmt .Errorf ("failed to create %s from %s: %w" , opts .AncestorBranch , opts .FromBranch , err )
409428 }
410429 if err := cleanupBranch (); err != nil {
@@ -413,7 +432,7 @@ func (opts *Update) prepareAncestorBranch() error {
413432 if err := regenerateProjectWithVersion (opts .FromVersion ); err != nil {
414433 return fmt .Errorf ("failed to regenerate project with fromVersion %s: %w" , opts .FromVersion , err )
415434 }
416- gitCmd := exec . Command ( "git" , "add" , "--all" )
435+ gitCmd := helpers . GitCmd ( opts . GitConfig , "add" , "--all" )
417436 if err := gitCmd .Run (); err != nil {
418437 return fmt .Errorf ("failed to stage changes in %s: %w" , opts .AncestorBranch , err )
419438 }
@@ -508,17 +527,17 @@ func envWithPrefixedPath(dir string) []string {
508527// populates it with the user's actual project content from the default branch.
509528// This represents the current state of the user's project.
510529func (opts * Update ) prepareOriginalBranch () error {
511- gitCmd := exec . Command ( "git" , "checkout" , "-b" , opts .OriginalBranch )
530+ gitCmd := helpers . GitCmd ( opts . GitConfig , "checkout" , "-b" , opts .OriginalBranch )
512531 if err := gitCmd .Run (); err != nil {
513532 return fmt .Errorf ("failed to checkout branch %s: %w" , opts .OriginalBranch , err )
514533 }
515534
516- gitCmd = exec . Command ( "git" , "checkout" , opts .FromBranch , "--" , "." )
535+ gitCmd = helpers . GitCmd ( opts . GitConfig , "checkout" , opts .FromBranch , "--" , "." )
517536 if err := gitCmd .Run (); err != nil {
518537 return fmt .Errorf ("failed to checkout content from %s branch onto %s: %w" , opts .FromBranch , opts .OriginalBranch , err )
519538 }
520539
521- gitCmd = exec . Command ( "git" , "add" , "--all" )
540+ gitCmd = helpers . GitCmd ( opts . GitConfig , "add" , "--all" )
522541 if err := gitCmd .Run (); err != nil {
523542 return fmt .Errorf ("failed to stage all changes in current: %w" , err )
524543 }
@@ -535,13 +554,13 @@ func (opts *Update) prepareOriginalBranch() error {
535554// generates fresh scaffolding using the current (latest) CLI version.
536555// This represents what the project should look like with the new version.
537556func (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 )
539558 if err := gitCmd .Run (); err != nil {
540559 return fmt .Errorf ("failed to checkout %s branch off %s: %w" ,
541560 opts .UpgradeBranch , opts .AncestorBranch , err )
542561 }
543562
544- checkoutCmd := exec . Command ( "git" , "checkout" , opts .UpgradeBranch )
563+ checkoutCmd := helpers . GitCmd ( opts . GitConfig , "checkout" , opts .UpgradeBranch )
545564 if err := checkoutCmd .Run (); err != nil {
546565 return fmt .Errorf ("failed to checkout base branch %s: %w" , opts .UpgradeBranch , err )
547566 }
@@ -552,7 +571,7 @@ func (opts *Update) prepareUpgradeBranch() error {
552571 if err := regenerateProjectWithVersion (opts .ToVersion ); err != nil {
553572 return fmt .Errorf ("failed to regenerate project with version %s: %w" , opts .ToVersion , err )
554573 }
555- gitCmd = exec . Command ( "git" , "add" , "--all" )
574+ gitCmd = helpers . GitCmd ( opts . GitConfig , "add" , "--all" )
556575 if err := gitCmd .Run (); err != nil {
557576 return fmt .Errorf ("failed to stage changes in %s: %w" , opts .UpgradeBranch , err )
558577 }
@@ -566,17 +585,17 @@ func (opts *Update) prepareUpgradeBranch() error {
566585// mergeOriginalToUpgrade attempts to merge the upgrade branch
567586func (opts * Update ) mergeOriginalToUpgrade () (bool , error ) {
568587 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 {
570589 return hasConflicts , fmt .Errorf ("failed to create merge branch %s from %s: %w" ,
571590 opts .MergeBranch , opts .UpgradeBranch , err )
572591 }
573592
574- checkoutCmd := exec . Command ( "git" , "checkout" , opts .MergeBranch )
593+ checkoutCmd := helpers . GitCmd ( opts . GitConfig , "checkout" , opts .MergeBranch )
575594 if err := checkoutCmd .Run (); err != nil {
576595 return hasConflicts , fmt .Errorf ("failed to checkout base branch %s: %w" , opts .MergeBranch , err )
577596 }
578597
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 )
580599 err := mergeCmd .Run ()
581600 if err != nil {
582601 var exitErr * exec.ExitError
@@ -608,7 +627,7 @@ func (opts *Update) mergeOriginalToUpgrade() (bool, error) {
608627 }
609628
610629 // 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 {
612631 return hasConflicts , fmt .Errorf ("failed to stage merge results: %w" , err )
613632 }
614633
0 commit comments