29
29
30
30
31
31
if TYPE_CHECKING :
32
- from pygit2 import Signature
32
+ from pygit2 import Oid , Signature
33
33
from pygit2 .remote import Remote # type: ignore
34
34
from pygit2 .repository import Repository
35
35
@@ -551,7 +551,8 @@ def _merge_remote_branch(
551
551
raise SCMError ("Unknown merge analysis result" )
552
552
553
553
@contextmanager
554
- def get_remote (self , url : str ) -> Generator ["Remote" , None , None ]:
554
+ def _get_remote (self , url : str ) -> Generator ["Remote" , None , None ]:
555
+ """Return a pygit2.Remote suitable for the specified Git URL or remote name."""
555
556
try :
556
557
remote = self .repo .remotes [url ]
557
558
url = remote .url
@@ -577,57 +578,84 @@ def fetch_refspecs(
577
578
progress : Callable [["GitProgressEvent" ], None ] = None ,
578
579
** kwargs ,
579
580
) -> Mapping [str , SyncStatus ]:
581
+ import fnmatch
582
+
580
583
from pygit2 import GitError
581
584
582
585
from .callbacks import RemoteCallbacks
583
586
584
- if isinstance (refspecs , str ):
585
- refspecs = [refspecs ]
587
+ refspecs = self ._refspecs_list (refspecs , force = force )
586
588
587
- with self . get_remote ( url ) as remote :
588
- fetch_refspecs : List [ str ] = []
589
- for refspec in refspecs :
590
- if ":" in refspec :
591
- lh , rh = refspec . split ( ":" )
592
- else :
593
- lh = rh = refspec
594
- if not rh . startswith ( "refs/" ) :
595
- rh = f"refs/heads/ { rh } "
596
- if not lh . startswith ( "refs/" ) :
597
- lh = f" refs/heads/ { lh } "
598
- rh = rh [ len ( "refs/" ) :]
599
- refspec = f"+ { lh } :refs/remotes/ { remote . name } / { rh } "
600
- fetch_refspecs . append ( refspec )
601
-
602
- logger . debug ( "fetch_refspecs: %s" , fetch_refspecs )
589
+ # libgit2 rejects diverged refs but does not have a callback to notify
590
+ # when a ref was rejected so we have to determine whether no callback
591
+ # means up to date or rejected
592
+ def _default_status (
593
+ src : str , dst : str , remote_refs : Dict [ str , "Oid" ]
594
+ ) -> SyncStatus :
595
+ try :
596
+ if remote_refs [ src ] != self . repo . references [ dst ]. target :
597
+ return SyncStatus . DIVERGED
598
+ except KeyError :
599
+ # remote_refs lookup is skipped when force is set, refs cannot
600
+ # be diverged on force
601
+ pass
602
+ return SyncStatus . UP_TO_DATE
603
+
604
+ with self . _get_remote ( url ) as remote :
603
605
with reraise (
604
606
GitError ,
605
607
SCMError (f"Git failed to fetch ref from '{ url } '" ),
606
608
):
607
609
with RemoteCallbacks (progress = progress ) as cb :
610
+ remote_refs : Dict [str , "Oid" ] = (
611
+ {
612
+ head ["name" ]: head ["oid" ]
613
+ for head in remote .ls_remotes (callbacks = cb )
614
+ }
615
+ if not force
616
+ else {}
617
+ )
608
618
remote .fetch (
609
- refspecs = fetch_refspecs ,
619
+ refspecs = refspecs ,
610
620
callbacks = cb ,
621
+ message = "fetch" ,
611
622
)
612
623
613
624
result : Dict [str , "SyncStatus" ] = {}
614
- for refspec in fetch_refspecs :
615
- _ , rh = refspec .split (":" )
616
- if not rh .endswith ("*" ):
617
- refname = rh .split ("/" , 3 )[- 1 ]
618
- refname = f"refs/{ refname } "
619
- result [refname ] = self ._merge_remote_branch (
620
- rh , refname , force , on_diverged
621
- )
622
- continue
623
- rh = rh .rstrip ("*" ).rstrip ("/" ) + "/"
624
- for branch in self .iter_refs (base = rh ):
625
- refname = f"refs/{ branch [len (rh ):]} "
626
- result [refname ] = self ._merge_remote_branch (
627
- branch , refname , force , on_diverged
628
- )
625
+ for refspec in refspecs :
626
+ lh , rh = refspec .split (":" )
627
+ if lh .endswith ("*" ):
628
+ assert rh .endswith ("*" )
629
+ lh_prefix = lh [:- 1 ]
630
+ rh_prefix = rh [:- 1 ]
631
+ for refname in remote_refs :
632
+ if fnmatch .fnmatch (refname , lh ):
633
+ src = refname
634
+ dst = f"{ rh_prefix } { refname [len (lh_prefix ):]} "
635
+ result [dst ] = cb .result .get (
636
+ src , _default_status (src , dst , remote_refs )
637
+ )
638
+ else :
639
+ result [rh ] = cb .result .get (lh , _default_status (lh , rh , remote_refs ))
640
+
629
641
return result
630
642
643
+ @staticmethod
644
+ def _refspecs_list (
645
+ refspecs : Union [str , Iterable [str ]],
646
+ force : bool = False ,
647
+ ) -> List [str ]:
648
+ if isinstance (refspecs , str ):
649
+ if force and not refspecs .startswith ("+" ):
650
+ refspecs = f"+{ refspecs } "
651
+ return [refspecs ]
652
+ if force :
653
+ return [
654
+ (refspec if refspec .startswith ("+" ) else f"+{ refspec } " )
655
+ for refspec in refspecs
656
+ ]
657
+ return list (refspecs )
658
+
631
659
def _stash_iter (self , ref : str ):
632
660
raise NotImplementedError
633
661
0 commit comments