@@ -214,30 +214,15 @@ func (r *ImageUpdateAutomationReconciler) Reconcile(ctx context.Context, req ctr
214214 }
215215
216216 // validate the git spec and default any values needed later, before proceeding
217- var ref * sourcev1.GitRepositoryRef
217+ var checkoutRef * sourcev1.GitRepositoryRef
218218 if gitSpec .Checkout != nil {
219- ref = & gitSpec .Checkout .Reference
220- tracelog .Info ("using git repository ref from .spec.git.checkout" , "ref" , ref )
219+ checkoutRef = & gitSpec .Checkout .Reference
220+ tracelog .Info ("using git repository ref from .spec.git.checkout" , "ref" , checkoutRef )
221221 } else if r := origin .Spec .Reference ; r != nil {
222- ref = r
223- tracelog .Info ("using git repository ref from GitRepository spec" , "ref" , ref )
222+ checkoutRef = r
223+ tracelog .Info ("using git repository ref from GitRepository spec" , "ref" , checkoutRef )
224224 } // else remain as `nil` and git.DefaultBranch will be used.
225225
226- var pushBranch string
227- if gitSpec .Push != nil {
228- pushBranch = gitSpec .Push .Branch
229- tracelog .Info ("using push branch from .spec.push.branch" , "branch" , pushBranch )
230- } else {
231- // Here's where it gets constrained. If there's no push branch
232- // given, then the checkout ref must include a branch, and
233- // that can be used.
234- if ref == nil || ref .Branch == "" {
235- return failWithError (fmt .Errorf ("Push branch not given explicitly, and cannot be inferred from .spec.git.checkout.ref or GitRepository .spec.ref" ))
236- }
237- pushBranch = ref .Branch
238- tracelog .Info ("using push branch from $ref.branch" , "branch" , pushBranch )
239- }
240-
241226 tmp , err := os .MkdirTemp ("" , fmt .Sprintf ("%s-%s" , originName .Namespace , originName .Name ))
242227 if err != nil {
243228 return failWithError (err )
@@ -248,42 +233,43 @@ func (r *ImageUpdateAutomationReconciler) Reconcile(ctx context.Context, req ctr
248233 }
249234 }()
250235
251- debuglog .Info ("attempting to clone git repository" , "gitrepository" , originName , "ref" , ref , "working" , tmp )
252-
253- authOpts , err := r .getAuthOpts (ctx , & origin )
254- if err != nil {
255- return failWithError (err )
256- }
257-
258- clientOpts := []gogit.ClientOption {gogit .WithDiskStorage ()}
259- if authOpts .Transport == git .HTTP {
260- clientOpts = append (clientOpts , gogit .WithInsecureCredentialsOverHTTP ())
236+ var pushBranch string
237+ var switchBranch bool
238+ if gitSpec .Push != nil {
239+ // We only need to switch branches when a branch has been specified in
240+ // the push spec and it is different than the one in the checkout ref.
241+ if gitSpec .Push .Branch != "" && gitSpec .Push .Branch != checkoutRef .Branch {
242+ pushBranch = gitSpec .Push .Branch
243+ switchBranch = true
244+ tracelog .Info ("using push branch from .spec.push.branch" , "branch" , pushBranch )
245+ }
246+ } else {
247+ // Here's where it gets constrained. If there's no push branch
248+ // given, then the checkout ref must include a branch, and
249+ // that can be used.
250+ if checkoutRef == nil || checkoutRef .Branch == "" {
251+ return failWithError (
252+ fmt .Errorf ("Push spec not provided, and cannot be inferred from .spec.git.checkout.ref or GitRepository .spec.ref" ),
253+ )
254+ }
255+ pushBranch = checkoutRef .Branch
256+ tracelog .Info ("using push branch from $ref.branch" , "branch" , pushBranch )
261257 }
262258
263- // If the push branch is different from the checkout ref, we need to
264- // have all the references downloaded at clone time, to ensure that
265- // SwitchBranch will have access to the target branch state. fluxcd/flux2#3384
266- //
267- // To always overwrite the push branch, the feature gate
268- // GitAllBranchReferences can be set to false, which will cause
269- // the SwitchBranch operation to ignore the remote branch state.
270- allReferences := r .features [features .GitAllBranchReferences ]
271- if pushBranch != ref .Branch {
272- clientOpts = append (clientOpts , gogit .WithSingleBranch (! allReferences ))
273- }
259+ debuglog .Info ("attempting to clone git repository" , "gitrepository" , originName , "ref" , checkoutRef , "working" , tmp )
274260
275- gitClient , err := gogit . NewClient ( tmp , authOpts , clientOpts ... )
261+ gitClient , err := r . constructGitClient ( ctx , & origin , tmp , switchBranch )
276262 if err != nil {
277263 return failWithError (err )
278264 }
279265 defer gitClient .Close ()
280266
281267 opts := repository.CloneConfig {}
282- if ref != nil {
283- opts .Tag = ref .Tag
284- opts .SemVer = ref .SemVer
285- opts .Commit = ref .Commit
286- opts .Branch = ref .Branch
268+ if checkoutRef != nil {
269+ opts .Tag = checkoutRef .Tag
270+ opts .SemVer = checkoutRef .SemVer
271+ opts .Commit = checkoutRef .Commit
272+ opts .Branch = checkoutRef .Branch
287273 }
288274
289275 if enabled , _ := r .features [features .GitShallowClone ]; enabled {
@@ -297,9 +283,9 @@ func (r *ImageUpdateAutomationReconciler) Reconcile(ctx context.Context, req ctr
297283 return failWithError (err )
298284 }
299285
300- // When there's a push spec , the pushed-to branch is where commits
286+ // When there's a push branch specified , the pushed-to branch is where commits
301287 // shall be made
302- if gitSpec . Push != nil && ! ( ref != nil && ref . Branch == pushBranch ) {
288+ if switchBranch {
303289 // Use the git operations timeout for the repo.
304290 fetchCtx , cancel := context .WithTimeout (ctx , origin .Spec .Timeout .Duration )
305291 defer cancel ()
@@ -352,7 +338,6 @@ func (r *ImageUpdateAutomationReconciler) Reconcile(ctx context.Context, req ctr
352338
353339 debuglog .Info ("ran updates to working dir" , "working" , tmp )
354340
355- var statusMessage string
356341 var signingEntity * openpgp.Entity
357342 if gitSpec .Commit .SigningKey != nil {
358343 if signingEntity , err = r .getSigningEntity (ctx , auto ); err != nil {
@@ -386,40 +371,80 @@ func (r *ImageUpdateAutomationReconciler) Reconcile(ctx context.Context, req ctr
386371 err = extgogit .ErrEmptyCommit
387372 }
388373
374+ var statusMessage strings.Builder
389375 if err != nil {
390376 if ! errors .Is (err , git .ErrNoStagedFiles ) && ! errors .Is (err , extgogit .ErrEmptyCommit ) {
391377 return failWithError (err )
392378 }
393379
394380 log .Info ("no changes made in working directory; no commit" )
395- statusMessage = "no updates made"
381+ statusMessage . WriteString ( "no updates made" )
396382
397383 if auto .Status .LastPushTime != nil && len (auto .Status .LastPushCommit ) >= 7 {
398- statusMessage = fmt .Sprintf ("%s; last commit %s at %s" , statusMessage , auto .Status .LastPushCommit [:7 ], auto .Status .LastPushTime .Format (time .RFC3339 ))
384+ statusMessage .WriteString (fmt .Sprintf ("; last commit %s at %s" ,
385+ auto .Status .LastPushCommit [:7 ], auto .Status .LastPushTime .Format (time .RFC3339 )))
399386 }
400387 } else {
401388 // Use the git operations timeout for the repo.
402389 pushCtx , cancel := context .WithTimeout (ctx , origin .Spec .Timeout .Duration )
403390 defer cancel ()
404- opts := repository.PushConfig {}
405- forcePush := r .features [features .GitForcePushBranch ]
406- if forcePush && pushBranch != ref .Branch {
407- opts .Force = true
391+
392+ var pushToBranch bool
393+ var pushWithRefspec bool
394+ // If a refspec is specified, then we need to perform a push using
395+ // that refspec.
396+ if gitSpec .Push != nil && gitSpec .Push .Refspec != "" {
397+ pushWithRefspec = true
408398 }
409- if err := gitClient .Push (pushCtx , opts ); err != nil {
410- return failWithError (err )
399+ // We need to push the commit to the push branch if one was specified, or if
400+ // no push config was specified, then we need to push to the branch we checked
401+ // out to.
402+ if (gitSpec .Push != nil && gitSpec .Push .Branch != "" ) || gitSpec .Push == nil {
403+ pushToBranch = true
404+ }
405+
406+ if pushToBranch {
407+ // If the force push feature flag is true and we are pushing to a
408+ // different branch than the one we checked out to, then force push
409+ // these changes.
410+ var pushConfig repository.PushConfig
411+ forcePush := r .features [features .GitForcePushBranch ]
412+ if forcePush && switchBranch {
413+ pushConfig .Force = true
414+ }
415+
416+ if err := gitClient .Push (pushCtx , pushConfig ); err != nil {
417+ return failWithError (err )
418+ }
419+ log .Info ("pushed commit to origin" , "revision" , rev , "branch" , pushBranch )
420+ statusMessage .WriteString (fmt .Sprintf ("commited and pushed commit '%s' to branch '%s'" , rev , pushBranch ))
411421 }
412422
413- r .event (ctx , auto , eventv1 .EventSeverityInfo , fmt .Sprintf ("Committed and pushed change %s to %s\n %s" , rev , pushBranch , message ))
414- log .Info ("pushed commit to origin" , "revision" , rev , "branch" , pushBranch )
423+ if pushWithRefspec {
424+ pushConfig := repository.PushConfig {
425+ Refspecs : []string {gitSpec .Push .Refspec },
426+ }
427+ if err := gitClient .Push (pushCtx , pushConfig ); err != nil {
428+ return failWithError (err )
429+ }
430+ log .Info ("pushed commit to origin" , "revision" , rev , "refspec" , gitSpec .Push .Refspec )
431+
432+ if pushToBranch {
433+ statusMessage .WriteString (fmt .Sprintf (" and using refspec '%s'" , gitSpec .Push .Refspec ))
434+ } else {
435+ statusMessage .WriteString (fmt .Sprintf ("committed and pushed commit '%s' using refspec '%s'" , rev , gitSpec .Push .Refspec ))
436+ }
437+ }
438+
439+ r .event (ctx , auto , eventv1 .EventSeverityInfo , fmt .Sprintf ("%s\n %s" , statusMessage .String (), message ))
440+
415441 auto .Status .LastPushCommit = rev
416442 auto .Status .LastPushTime = & metav1.Time {Time : start }
417- statusMessage = "committed and pushed " + rev + " to " + pushBranch
418443 }
419444
420445 // Getting to here is a successful run.
421446 auto .Status .LastAutomationRunTime = & metav1.Time {Time : start }
422- imagev1 .SetImageUpdateAutomationReadiness (& auto , metav1 .ConditionTrue , imagev1 .ReconciliationSucceededReason , statusMessage )
447+ imagev1 .SetImageUpdateAutomationReadiness (& auto , metav1 .ConditionTrue , imagev1 .ReconciliationSucceededReason , statusMessage . String () )
423448 if err := r .patchStatus (ctx , req , auto .Status ); err != nil {
424449 return ctrl.Result {Requeue : true }, err
425450 }
@@ -545,6 +570,38 @@ func (r *ImageUpdateAutomationReconciler) getAuthOpts(ctx context.Context, repos
545570 return opts , nil
546571}
547572
573+ // constructGitClient constructs and returns a new gogit client.
574+ func (r * ImageUpdateAutomationReconciler ) constructGitClient (ctx context.Context ,
575+ origin * sourcev1.GitRepository , repoDir string , switchBranch bool ) (* gogit.Client , error ) {
576+ authOpts , err := r .getAuthOpts (ctx , origin )
577+ if err != nil {
578+ return nil , err
579+ }
580+
581+ clientOpts := []gogit.ClientOption {gogit .WithDiskStorage ()}
582+ if authOpts .Transport == git .HTTP {
583+ clientOpts = append (clientOpts , gogit .WithInsecureCredentialsOverHTTP ())
584+ }
585+
586+ // If the push branch is different from the checkout ref, we need to
587+ // have all the references downloaded at clone time, to ensure that
588+ // SwitchBranch will have access to the target branch state. fluxcd/flux2#3384
589+ //
590+ // To always overwrite the push branch, the feature gate
591+ // GitAllBranchReferences can be set to false, which will cause
592+ // the SwitchBranch operation to ignore the remote branch state.
593+ allReferences := r .features [features .GitAllBranchReferences ]
594+ if switchBranch {
595+ clientOpts = append (clientOpts , gogit .WithSingleBranch (! allReferences ))
596+ }
597+
598+ gitClient , err := gogit .NewClient (repoDir , authOpts , clientOpts ... )
599+ if err != nil {
600+ return nil , err
601+ }
602+ return gitClient , nil
603+ }
604+
548605// getSigningEntity retrieves an OpenPGP entity referenced by the
549606// provided imagev1.ImageUpdateAutomation for git commit signing
550607func (r * ImageUpdateAutomationReconciler ) getSigningEntity (ctx context.Context , auto imagev1.ImageUpdateAutomation ) (* openpgp.Entity , error ) {
0 commit comments