@@ -77,6 +77,24 @@ type Update struct {
7777 // CLI (`gh`) to be installed and authenticated in the local environment.
7878 OpenGhIssue bool
7979
80+ // GitConfig contains per-invocation Git configurations that are applied
81+ // to every `git` command via `git -c key=value`.
82+ //
83+ // Examples:
84+ // []string{"merge.renameLimit=999999"} // enable robust rename detection
85+ // []string{"diff.renameLimit=999999"} // improve diff rename detection
86+ // []string{"merge.conflictStyle=diff3"} // show ancestor in conflict markers
87+ // []string{"rerere.enabled=true"} // enable reuse recorded resolutions
88+ //
89+ // Defaults:
90+ // If not set by the user, it defaults to:
91+ // []string{"merge.renameLimit=999999", "diff.renameLimit=999999"}
92+ //
93+ // Behavior:
94+ // If the user passes --git-config flags, those values REPLACE the defaults.
95+ // To keep defaults and add more, explicitly re-specify them alongside extras.
96+ GitConfig []string
97+
8098 // Temporary branches created during the update process. These are internal to the run
8199 // and are surfaced for transparency/debugging:
82100 // - AncestorBranch: clean scaffold generated from FromVersion
@@ -160,7 +178,7 @@ Resolve conflicts there, complete the merge locally, and push the branch.
160178// This helps apply new scaffolding changes while preserving custom code.
161179func (opts * Update ) Update () error {
162180 log .Info ("Checking out base branch" , "branch" , opts .FromBranch )
163- checkoutCmd := exec . Command ( "git" , "checkout" , opts .FromBranch )
181+ checkoutCmd := helpers . GitCmd ( opts . GitConfig , "checkout" , opts .FromBranch )
164182 if err := checkoutCmd .Run (); err != nil {
165183 return fmt .Errorf ("failed to checkout base branch %s: %w" , opts .FromBranch , err )
166184 }
@@ -219,7 +237,7 @@ func (opts *Update) Update() error {
219237 if opts .ShowCommits {
220238 log .Info ("Keeping commits history" )
221239 out := opts .getOutputBranchName ()
222- if err := exec . Command ( "git" , "checkout" , "-b" , out , opts .MergeBranch ).Run (); err != nil {
240+ if err := helpers . GitCmd ( opts . GitConfig , "checkout" , "-b" , out , opts .MergeBranch ).Run (); err != nil {
223241 return fmt .Errorf ("checkout %s: %w" , out , err )
224242 }
225243 } else {
@@ -233,8 +251,8 @@ func (opts *Update) Update() error {
233251 if opts .Push {
234252 if opts .Push {
235253 out := opts .getOutputBranchName ()
236- _ = exec . Command ( "git" , "checkout" , out ).Run ()
237- if err := exec . Command ( "git" , "push" , "-u" , "origin" , out ).Run (); err != nil {
254+ _ = helpers . GitCmd ( opts . GitConfig , "checkout" , out ).Run ()
255+ if err := helpers . GitCmd ( opts . GitConfig , "push" , "-u" , "origin" , out ).Run (); err != nil {
238256 return fmt .Errorf ("failed to push %s: %w" , out , err )
239257 }
240258 }
@@ -309,7 +327,7 @@ func (opts *Update) openGitHubIssue(hasConflicts bool) error {
309327}
310328
311329func (opts * Update ) cleanupTempBranches () {
312- _ = exec . Command ( "git" , "checkout" , opts .getOutputBranchName ()).Run ()
330+ _ = helpers . GitCmd ( opts . GitConfig , "checkout" , opts .getOutputBranchName ()).Run ()
313331
314332 branches := []string {
315333 opts .AncestorBranch ,
@@ -324,8 +342,8 @@ func (opts *Update) cleanupTempBranches() {
324342 continue
325343 }
326344 // 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 ()
345+ if err := helpers . GitCmd ( opts . GitConfig , "show-ref" , "--verify" , "--quiet" , "refs/heads/" + b ).Run (); err == nil {
346+ _ = helpers . GitCmd ( opts . GitConfig , "branch" , "-D" , b ).Run ()
329347 }
330348 }
331349}
@@ -345,7 +363,7 @@ func (opts *Update) preservePaths() {
345363 if p == "" {
346364 continue
347365 }
348- if err := exec . Command ( "git" , "checkout" , opts .FromBranch , "--" , p ).Run (); err != nil {
366+ if err := helpers . GitCmd ( opts . GitConfig , "checkout" , opts .FromBranch , "--" , p ).Run (); err != nil {
349367 log .Warn ("failed to restore preserved path" , "path" , p , "branch" , opts .FromBranch , "error" , err )
350368 }
351369 }
@@ -358,26 +376,26 @@ func (opts *Update) squashToOutputBranch(hasConflicts bool) error {
358376 out := opts .getOutputBranchName ()
359377
360378 // 1) base -> out
361- if err := exec . Command ( "git" , "checkout" , opts .FromBranch ).Run (); err != nil {
379+ if err := helpers . GitCmd ( opts . GitConfig , "checkout" , opts .FromBranch ).Run (); err != nil {
362380 return fmt .Errorf ("checkout %s: %w" , opts .FromBranch , err )
363381 }
364- if err := exec . Command ( "git" , "checkout" , "-B" , out , opts .FromBranch ).Run (); err != nil {
382+ if err := helpers . GitCmd ( opts . GitConfig , "checkout" , "-B" , out , opts .FromBranch ).Run (); err != nil {
365383 return fmt .Errorf ("create/reset %s from %s: %w" , out , opts .FromBranch , err )
366384 }
367385
368386 // 2) clean worktree, then copy merge tree
369387 if err := helpers .CleanWorktree ("output branch" ); err != nil {
370388 return fmt .Errorf ("output branch: %w" , err )
371389 }
372- if err := exec . Command ( "git" , "checkout" , opts .MergeBranch , "--" , "." ).Run (); err != nil {
390+ if err := helpers . GitCmd ( opts . GitConfig , "checkout" , opts .MergeBranch , "--" , "." ).Run (); err != nil {
373391 return fmt .Errorf ("checkout %s content: %w" , "merge" , err )
374392 }
375393
376394 // 3) optionally restore preserved paths from base (tests assert on 'git restore …')
377395 opts .preservePaths ()
378396
379397 // 4) stage and single squashed commit
380- if err := exec . Command ( "git" , "add" , "--all" ).Run (); err != nil {
398+ if err := helpers . GitCmd ( opts . GitConfig , "add" , "--all" ).Run (); err != nil {
381399 return fmt .Errorf ("stage output: %w" , err )
382400 }
383401
@@ -404,7 +422,7 @@ func regenerateProjectWithVersion(version string) error {
404422// prepareAncestorBranch prepares the ancestor branch by checking it out,
405423// cleaning up the project files, and regenerating the project with the specified version.
406424func (opts * Update ) prepareAncestorBranch () error {
407- if err := exec . Command ( "git" , "checkout" , "-b" , opts .AncestorBranch , opts .FromBranch ).Run (); err != nil {
425+ if err := helpers . GitCmd ( opts . GitConfig , "checkout" , "-b" , opts .AncestorBranch , opts .FromBranch ).Run (); err != nil {
408426 return fmt .Errorf ("failed to create %s from %s: %w" , opts .AncestorBranch , opts .FromBranch , err )
409427 }
410428 if err := cleanupBranch (); err != nil {
@@ -413,7 +431,7 @@ func (opts *Update) prepareAncestorBranch() error {
413431 if err := regenerateProjectWithVersion (opts .FromVersion ); err != nil {
414432 return fmt .Errorf ("failed to regenerate project with fromVersion %s: %w" , opts .FromVersion , err )
415433 }
416- gitCmd := exec . Command ( "git" , "add" , "--all" )
434+ gitCmd := helpers . GitCmd ( opts . GitConfig , "add" , "--all" )
417435 if err := gitCmd .Run (); err != nil {
418436 return fmt .Errorf ("failed to stage changes in %s: %w" , opts .AncestorBranch , err )
419437 }
@@ -488,17 +506,17 @@ func envWithPrefixedPath(dir string) []string {
488506// populates it with the user's actual project content from the default branch.
489507// This represents the current state of the user's project.
490508func (opts * Update ) prepareOriginalBranch () error {
491- gitCmd := exec . Command ( "git" , "checkout" , "-b" , opts .OriginalBranch )
509+ gitCmd := helpers . GitCmd ( opts . GitConfig , "checkout" , "-b" , opts .OriginalBranch )
492510 if err := gitCmd .Run (); err != nil {
493511 return fmt .Errorf ("failed to checkout branch %s: %w" , opts .OriginalBranch , err )
494512 }
495513
496- gitCmd = exec . Command ( "git" , "checkout" , opts .FromBranch , "--" , "." )
514+ gitCmd = helpers . GitCmd ( opts . GitConfig , "checkout" , opts .FromBranch , "--" , "." )
497515 if err := gitCmd .Run (); err != nil {
498516 return fmt .Errorf ("failed to checkout content from %s branch onto %s: %w" , opts .FromBranch , opts .OriginalBranch , err )
499517 }
500518
501- gitCmd = exec . Command ( "git" , "add" , "--all" )
519+ gitCmd = helpers . GitCmd ( opts . GitConfig , "add" , "--all" )
502520 if err := gitCmd .Run (); err != nil {
503521 return fmt .Errorf ("failed to stage all changes in current: %w" , err )
504522 }
@@ -515,13 +533,13 @@ func (opts *Update) prepareOriginalBranch() error {
515533// generates fresh scaffolding using the current (latest) CLI version.
516534// This represents what the project should look like with the new version.
517535func (opts * Update ) prepareUpgradeBranch () error {
518- gitCmd := exec . Command ( "git" , "checkout" , "-b" , opts .UpgradeBranch , opts .AncestorBranch )
536+ gitCmd := helpers . GitCmd ( opts . GitConfig , "checkout" , "-b" , opts .UpgradeBranch , opts .AncestorBranch )
519537 if err := gitCmd .Run (); err != nil {
520538 return fmt .Errorf ("failed to checkout %s branch off %s: %w" ,
521539 opts .UpgradeBranch , opts .AncestorBranch , err )
522540 }
523541
524- checkoutCmd := exec . Command ( "git" , "checkout" , opts .UpgradeBranch )
542+ checkoutCmd := helpers . GitCmd ( opts . GitConfig , "checkout" , opts .UpgradeBranch )
525543 if err := checkoutCmd .Run (); err != nil {
526544 return fmt .Errorf ("failed to checkout base branch %s: %w" , opts .UpgradeBranch , err )
527545 }
@@ -532,7 +550,7 @@ func (opts *Update) prepareUpgradeBranch() error {
532550 if err := regenerateProjectWithVersion (opts .ToVersion ); err != nil {
533551 return fmt .Errorf ("failed to regenerate project with version %s: %w" , opts .ToVersion , err )
534552 }
535- gitCmd = exec . Command ( "git" , "add" , "--all" )
553+ gitCmd = helpers . GitCmd ( opts . GitConfig , "add" , "--all" )
536554 if err := gitCmd .Run (); err != nil {
537555 return fmt .Errorf ("failed to stage changes in %s: %w" , opts .UpgradeBranch , err )
538556 }
@@ -546,17 +564,17 @@ func (opts *Update) prepareUpgradeBranch() error {
546564// mergeOriginalToUpgrade attempts to merge the upgrade branch
547565func (opts * Update ) mergeOriginalToUpgrade () (bool , error ) {
548566 hasConflicts := false
549- if err := exec . Command ( "git" , "checkout" , "-b" , opts .MergeBranch , opts .UpgradeBranch ).Run (); err != nil {
567+ if err := helpers . GitCmd ( opts . GitConfig , "checkout" , "-b" , opts .MergeBranch , opts .UpgradeBranch ).Run (); err != nil {
550568 return hasConflicts , fmt .Errorf ("failed to create merge branch %s from %s: %w" ,
551569 opts .MergeBranch , opts .UpgradeBranch , err )
552570 }
553571
554- checkoutCmd := exec . Command ( "git" , "checkout" , opts .MergeBranch )
572+ checkoutCmd := helpers . GitCmd ( opts . GitConfig , "checkout" , opts .MergeBranch )
555573 if err := checkoutCmd .Run (); err != nil {
556574 return hasConflicts , fmt .Errorf ("failed to checkout base branch %s: %w" , opts .MergeBranch , err )
557575 }
558576
559- mergeCmd := exec . Command ( "git" , "merge" , "--no-edit" , "--no-commit" , opts .OriginalBranch )
577+ mergeCmd := helpers . GitCmd ( opts . GitConfig , "merge" , "--no-edit" , "--no-commit" , opts .OriginalBranch )
560578 err := mergeCmd .Run ()
561579 if err != nil {
562580 var exitErr * exec.ExitError
@@ -584,7 +602,7 @@ func (opts *Update) mergeOriginalToUpgrade() (bool, error) {
584602 runMakeTargets ()
585603
586604 // Step 4: Stage and commit
587- if err := exec . Command ( "git" , "add" , "--all" ).Run (); err != nil {
605+ if err := helpers . GitCmd ( opts . GitConfig , "add" , "--all" ).Run (); err != nil {
588606 return hasConflicts , fmt .Errorf ("failed to stage merge results: %w" , err )
589607 }
590608
0 commit comments