44
55import datetime
66import pathlib
7+ import re
78import shlex
89import typing as t
910from collections .abc import Sequence
@@ -2361,12 +2362,16 @@ class GitRemoteCmd:
23612362 """Run commands directly for a git remote on a git repository."""
23622363
23632364 remote_name : str
2365+ fetch_url : str | None
2366+ push_url : str | None
23642367
23652368 def __init__ (
23662369 self ,
23672370 * ,
23682371 path : StrPath ,
23692372 remote_name : str ,
2373+ fetch_url : str | None = None ,
2374+ push_url : str | None = None ,
23702375 cmd : Git | None = None ,
23712376 ) -> None :
23722377 r"""Lite, typed, pythonic wrapper for git-remote(1).
@@ -2408,6 +2413,8 @@ def __init__(
24082413 self .cmd = cmd if isinstance (cmd , Git ) else Git (path = self .path )
24092414
24102415 self .remote_name = remote_name
2416+ self .fetch_url = fetch_url
2417+ self .push_url = push_url
24112418
24122419 def __repr__ (self ) -> str :
24132420 """Representation of a git remote for a git repository."""
@@ -2731,6 +2738,24 @@ def set_url(
27312738 )
27322739
27332740
2741+ GitRemoteManagerLiteral = Literal [
2742+ "--verbose" ,
2743+ "add" ,
2744+ "rename" ,
2745+ "remove" ,
2746+ "set-branches" ,
2747+ "set-head" ,
2748+ "set-branch" ,
2749+ "get-url" ,
2750+ "set-url" ,
2751+ "set-url --add" ,
2752+ "set-url --delete" ,
2753+ "prune" ,
2754+ "show" ,
2755+ "update" ,
2756+ ]
2757+
2758+
27342759class GitRemoteManager :
27352760 """Run commands directly related to git remotes of a git repo."""
27362761
@@ -2777,7 +2802,7 @@ def __repr__(self) -> str:
27772802
27782803 def run (
27792804 self ,
2780- command : GitRemoteCommandLiteral | None = None ,
2805+ command : GitRemoteManagerLiteral | None = None ,
27812806 local_flags : list [str ] | None = None ,
27822807 * ,
27832808 # Pass-through to run()
@@ -2860,6 +2885,16 @@ def show(
28602885 --------
28612886 >>> GitRemoteManager(path=example_git_repo.path).show()
28622887 'origin'
2888+
2889+ For the example below, add a remote:
2890+ >>> GitRemoteManager(path=example_git_repo.path).add(
2891+ ... name='my_remote', url=f'file:///dev/null'
2892+ ... )
2893+ ''
2894+
2895+ Retrieve a list of remote names:
2896+ >>> GitRemoteManager(path=example_git_repo.path).show().splitlines()
2897+ ['my_remote', 'origin']
28632898 """
28642899 local_flags : list [str ] = []
28652900 required_flags : list [str ] = []
@@ -2880,17 +2915,17 @@ def show(
28802915 log_in_real_time = log_in_real_time ,
28812916 )
28822917
2883- def _ls (self ) -> list [ str ] :
2884- """List remotes.
2918+ def _ls (self ) -> str :
2919+ r """List remotes (raw output) .
28852920
28862921 Examples
28872922 --------
28882923 >>> GitRemoteManager(path=example_git_repo.path)._ls()
2889- [ 'origin']
2924+ 'origin\tfile:///... (fetch)\norigin\tfile:///... (push)'
28902925 """
28912926 return self .run (
2892- "show " ,
2893- ). splitlines ()
2927+ "--verbose " ,
2928+ )
28942929
28952930 def ls (self ) -> QueryList [GitRemoteCmd ]:
28962931 """List remotes.
@@ -2899,14 +2934,56 @@ def ls(self) -> QueryList[GitRemoteCmd]:
28992934 --------
29002935 >>> GitRemoteManager(path=example_git_repo.path).ls()
29012936 [<GitRemoteCmd path=... remote_name=origin>]
2937+
2938+ For the example below, add a remote:
2939+ >>> GitRemoteManager(path=example_git_repo.path).add(
2940+ ... name='my_remote', url=f'file:///dev/null'
2941+ ... )
2942+ ''
2943+
2944+ >>> GitRemoteManager(path=example_git_repo.path).ls()
2945+ [<GitRemoteCmd path=... remote_name=my_remote>,
2946+ <GitRemoteCmd path=... remote_name=origin>]
29022947 """
2903- return QueryList (
2904- [
2905- GitRemoteCmd (path = self .path , remote_name = remote_name .lstrip ("* " ))
2906- for remote_name in self ._ls ()
2907- ],
2948+ remote_str = self ._ls ()
2949+ remote_pattern = re .compile (
2950+ r"""
2951+ (?P<name>\S+) # Remote name: one or more non-whitespace characters
2952+ \s+ # One or more whitespace characters
2953+ (?P<url>\S+) # URL: one or more non-whitespace characters
2954+ \s+ # One or more whitespace characters
2955+ \((?P<cmd_type>fetch|push)\) # 'fetch' or 'push' in parentheses
2956+ """ ,
2957+ re .VERBOSE | re .MULTILINE ,
29082958 )
29092959
2960+ remotes : dict [str , dict [str , str | None ]] = {}
2961+
2962+ for match_obj in remote_pattern .finditer (remote_str ):
2963+ name = match_obj .group ("name" )
2964+ url = match_obj .group ("url" )
2965+ cmd_type = match_obj .group ("cmd_type" )
2966+
2967+ if name not in remotes :
2968+ remotes [name ] = {}
2969+
2970+ remotes [name ][cmd_type ] = url
2971+
2972+ remote_cmds : list [GitRemoteCmd ] = []
2973+ for name , urls in remotes .items ():
2974+ fetch_url = urls .get ("fetch" )
2975+ push_url = urls .get ("push" )
2976+ remote_cmds .append (
2977+ GitRemoteCmd (
2978+ path = self .path ,
2979+ remote_name = name ,
2980+ fetch_url = fetch_url ,
2981+ push_url = push_url ,
2982+ )
2983+ )
2984+
2985+ return QueryList (remote_cmds )
2986+
29102987 def get (self , * args : t .Any , ** kwargs : t .Any ) -> GitRemoteCmd | None :
29112988 """Get remote via filter lookup.
29122989
0 commit comments