@@ -397,24 +397,32 @@ func (r *Repository) Fetch(ctx context.Context, specific ...string) (err error)
397397 refSpecs = append (refSpecs , refSpec )
398398 }
399399
400- if err := r . FetchContext ( ctx , & git.FetchOptions {
400+ opts := & git.FetchOptions {
401401 RemoteName : r .remote .Name ,
402402 Auth : r .auth ,
403403 CABundle : r .caBundle ,
404404 InsecureSkipTLS : r .insecureSkipTLS ,
405405 RefSpecs : refSpecs ,
406406 Prune : true ,
407407 Tags : plumbing .NoTags ,
408- Depth : 1 ,
409- }); err != nil && ! errors .Is (err , git .NoErrAlreadyUpToDate ) {
408+ }
409+
410+ // For bare repositories (i.e., repositories without a working tree),
411+ // limit the fetch to a depth of 1 so that only the latest commit is
412+ // retrieved. Since no checkout will occur, a full history is unnecessary
413+ // and would only increase network transfer and storage usage.
414+ if ! r .isNormalRepo {
415+ opts .Depth = 1
416+ }
417+
418+ if err := r .FetchContext (ctx , opts ); err != nil && ! errors .Is (err , git .NoErrAlreadyUpToDate ) {
410419 return err
411420 }
412421
413422 allRefs , err := r .References ()
414423 if err != nil {
415424 return err
416425 }
417-
418426 if err := allRefs .ForEach (func (ref * plumbing.Reference ) error {
419427 // we're only interested in updates to remotes
420428 if ! ref .Name ().IsRemote () {
@@ -433,6 +441,67 @@ func (r *Repository) Fetch(ctx context.Context, specific ...string) (err error)
433441 return err
434442 }
435443
444+ // Get all existing local branch refs
445+ allLocalRefs , err := r .Storer .IterReferences ()
446+ if err != nil {
447+ return fmt .Errorf ("failed to get local refs: %w" , err )
448+ }
449+
450+ existingHeads := make (map [string ]struct {})
451+ if err := allLocalRefs .ForEach (func (ref * plumbing.Reference ) error {
452+ if ref .Name ().IsBranch () {
453+ branchName := strings .TrimPrefix (ref .Name ().String (), "refs/heads/" )
454+ existingHeads [branchName ] = struct {}{}
455+ }
456+ return nil
457+ }); err != nil {
458+ return fmt .Errorf ("failed to iterate local refs: %w" , err )
459+ }
460+
461+ // Update refs for branches that exist on remote
462+ for name , hash := range updatedRefs {
463+ ref := plumbing .NewHashReference (plumbing .NewBranchReferenceName (name ), hash )
464+ if err := r .Storer .SetReference (ref ); err != nil {
465+ return fmt .Errorf ("failed to set reference %s: %w" , name , err )
466+ }
467+ delete (existingHeads , name )
468+ }
469+
470+ // Remove local refs for branches that no longer exist on remote
471+ for branchName := range existingHeads {
472+ refName := plumbing .NewBranchReferenceName (branchName )
473+ // Check if this branch is one we're tracking
474+ isTracked := false
475+ for _ , head := range heads {
476+ if refMatch (branchName , head ) {
477+ isTracked = true
478+ break
479+ }
480+ }
481+ if isTracked {
482+ if err := r .Storer .RemoveReference (refName ); err != nil {
483+ return fmt .Errorf ("failed to remove reference %s: %w" , branchName , err )
484+ }
485+ }
486+ }
487+
488+ if opts .Depth == 1 {
489+ // When performing a depth=1 fetch, maintain the shallow boundary in the object store.
490+ // For shallow clones, we track which commits are shallow to prevent operations that
491+ // require complete commit history from proceeding.
492+ shallows , err := r .Storer .Shallow ()
493+ if err != nil {
494+ return fmt .Errorf ("failed to get shallows: %w" , err )
495+ }
496+ shallows = slices .AppendSeq (shallows , maps .Values (updatedRefs ))
497+ slices .SortFunc (shallows , func (a plumbing.Hash , b plumbing.Hash ) int {
498+ return a .Compare (b .Bytes ())
499+ })
500+ if err := r .Storer .SetShallow (slices .Compact (shallows )); err != nil {
501+ return fmt .Errorf ("failed to set shallows: %w" , err )
502+ }
503+ }
504+
436505 return nil
437506}
438507
0 commit comments