@@ -255,7 +255,7 @@ class GithubRepository():
255255class Source (Enum ):
256256 DIRECTORY = 1
257257 LOCAL_REPO = 2
258- GITHUB_REPO = 3
258+ REMOTE_GIT_REPO = 3
259259 OTHER_URL = 4
260260 UNKNOWN = 5
261261 # Cloned from remote source before searching (rather than github API)
@@ -309,11 +309,14 @@ class LoadedSource:
309309 self .content = SourceDir (source , self .type )
310310 self .local_clone = None
311311 self .local_clone_fetched = False
312- if self .type == Source .GITHUB_REPO :
312+ if self .type == Source .REMOTE_GIT_REPO :
313313 local = _get_local_clone (source )
314314 if local :
315315 self .local_clone = SourceDir (local , Source .GIT_LOCAL_CLONE )
316- self .local_clone .parent_source = self
316+ else :
317+ self .local_clone = copy_remote_git_source (InstInfo (None , source ))
318+ self .content = self .local_clone
319+ self .local_clone .parent_source = self
317320
318321 def __repr__ (self ):
319322 return f'<Source { self .type } , { self .original_source } >'
@@ -344,8 +347,9 @@ class SourceDir():
344347 self .contents = populate_local_dir (self .location )
345348 elif self .srctype in [Source .LOCAL_REPO , Source .GIT_LOCAL_CLONE ]:
346349 self .contents = populate_local_repo (self .location , parent = self , parent_source = self .parent_source )
347- elif self .srctype == Source .GITHUB_REPO :
348- self .contents = populate_github_repo (self .location )
350+ elif self .srctype == Source .REMOTE_GIT_REPO :
351+ self .contents = copy_remote_git_source (InstInfo (self .name , self .location )).contents
352+
349353 else :
350354 raise Exception ("populate method undefined for {self.srctype}" )
351355 # Ensure the relative path of the contents is inherited.
@@ -398,19 +402,18 @@ class SourceFile():
398402
399403
400404class InstInfo :
401- def __init__ (self , name : str , location : str , git_url : str , source_dir : SourceDir = None ):
405+ def __init__ (self , name : str , location : str , source_dir : SourceDir = None ):
402406 self .name = name
403407 self .source_loc = str (location ) # Used for 'git clone'
404408 self .source_dir = source_dir # Use this insead of source_loc to only fetch once.
405- self .git_url : str = git_url # API access for github repos
406409 self .srctype : Source = Source .get_type (location )
407410 self .entry : SourceFile = None # relative to source_loc or subdir
408411 self .deps : str = None
409412 self .subdir : str = None
410413 self .commit : str = None
411414
412415 def __repr__ (self ):
413- return (f'InstInfo({ self .name } , { self .source_loc } , { self . git_url } , '
416+ return (f'InstInfo({ self .name } , { self .source_loc } , '
414417 f'{ self .entry } , { self .deps } , { self .subdir } )' )
415418
416419 def get_repo_commit (self ) -> Union [str , None ]:
@@ -422,26 +425,9 @@ class InstInfo:
422425 return None
423426 return git .stdout .splitlines ()[0 ]
424427
425- if self .srctype == Source .GITHUB_REPO :
426- parsed_url = urlparse (self .source_loc )
427- if 'github.com' not in parsed_url .netloc :
428- return None
429- if len (parsed_url .path .split ('/' )) < 2 :
430- return None
431- start = 1
432- # Maybe we were passed an api.github.com/repo/<user> url
433- if 'api' in parsed_url .netloc :
434- start += 1
435- repo_user = parsed_url .path .split ('/' )[start ]
436- repo_name = parsed_url .path .split ('/' )[start + 1 ]
437- api_url = f'{ API_GITHUB_COM } /repos/{ repo_user } /{ repo_name } /commits?ref=HEAD'
438- r = urlopen (api_url , timeout = 5 )
439- if r .status != 200 :
440- return None
441- try :
442- return json .loads (r .read ().decode ())['0' ]['sha' ]
443- except :
444- return None
428+ if self .srctype == Source .REMOTE_GIT_REPO :
429+ # The remote git source is not accessed directly. Use the local clone.
430+ assert False
445431
446432 def get_inst_details (self , permissive : bool = False ) -> bool :
447433 """Search the source_loc for plugin install details.
@@ -461,7 +447,7 @@ class InstInfo:
461447 if self .srctype in [Source .DIRECTORY , Source .LOCAL_REPO ,
462448 Source .GIT_LOCAL_CLONE ]:
463449 depth = 5
464- elif self .srctype == Source .GITHUB_REPO :
450+ elif self .srctype == Source .REMOTE_GIT_REPO :
465451 depth = 1
466452
467453 def search_dir (self , sub : SourceDir , subdir : bool ,
@@ -522,7 +508,7 @@ class InstInfo:
522508 # Fall back to cloning and searching the local copy instead.
523509 except HTTPError :
524510 result = None
525- if self .srctype == Source .GITHUB_REPO :
511+ if self .srctype == Source .REMOTE_GIT_REPO :
526512 # clone source to reckless dir
527513 target = copy_remote_git_source (self )
528514 if not target :
@@ -654,91 +640,6 @@ def populate_local_repo(path: str, parent=None, parent_source=None) -> list:
654640 return basedir .contents
655641
656642
657- def source_element_from_repo_api (member : dict ):
658- # api accessed via <repo>/contents/
659- if 'type' in member and 'name' in member and 'git_url' in member :
660- if member ['type' ] == 'dir' :
661- return SourceDir (member ['git_url' ], srctype = Source .GITHUB_REPO ,
662- name = member ['name' ])
663- elif member ['type' ] == 'file' :
664- # Likely a submodule
665- if member ['size' ] == 0 :
666- return SourceDir (None , srctype = Source .GITHUB_REPO ,
667- name = member ['name' ])
668- return SourceFile (member ['name' ])
669- elif member ['type' ] == 'commit' :
670- # No path is given by the api here
671- return SourceDir (None , srctype = Source .GITHUB_REPO ,
672- name = member ['name' ])
673- # git_url with <repo>/tree/ presents results a little differently
674- elif 'type' in member and 'path' in member and 'url' in member :
675- if member ['type' ] not in ['tree' , 'blob' ]:
676- log .debug (f' skipping { member ["path" ]} type={ member ["type" ]} ' )
677- if member ['type' ] == 'tree' :
678- return SourceDir (member ['url' ], srctype = Source .GITHUB_REPO ,
679- name = member ['path' ])
680- elif member ['type' ] == 'blob' :
681- # This can be a submodule
682- if member ['size' ] == 0 :
683- return SourceDir (member ['git_url' ], srctype = Source .GITHUB_REPO ,
684- name = member ['name' ])
685- return SourceFile (member ['path' ])
686- elif member ['type' ] == 'commit' :
687- # No path is given by the api here
688- return SourceDir (None , srctype = Source .GITHUB_REPO ,
689- name = member ['name' ])
690- return None
691-
692-
693- def populate_github_repo (url : str ) -> list :
694- """populate one level of a github repository via REST API"""
695- # Forces search to clone remote repos (for blackbox testing)
696- if GITHUB_API_FALLBACK :
697- with tempfile .NamedTemporaryFile () as tmp :
698- raise HTTPError (url , 403 , 'simulated ratelimit' , {}, tmp )
699- # FIXME: This probably contains leftover cruft.
700- repo = url .split ('/' )
701- while '' in repo :
702- repo .remove ('' )
703- repo_name = None
704- parsed_url = urlparse (url .removesuffix ('.git' ))
705- if 'github.com' not in parsed_url .netloc :
706- return None
707- if len (parsed_url .path .split ('/' )) < 2 :
708- return None
709- start = 1
710- # Maybe we were passed an api.github.com/repo/<user> url
711- if 'api' in parsed_url .netloc :
712- start += 1
713- repo_user = parsed_url .path .split ('/' )[start ]
714- repo_name = parsed_url .path .split ('/' )[start + 1 ]
715-
716- # Get details from the github API.
717- if API_GITHUB_COM in url :
718- api_url = url
719- else :
720- api_url = f'{ API_GITHUB_COM } /repos/{ repo_user } /{ repo_name } /contents/'
721-
722- git_url = api_url
723- if "api.github.com" in git_url :
724- # This lets us redirect to handle blackbox testing
725- log .debug (f'fetching from gh API: { git_url } ' )
726- git_url = (API_GITHUB_COM + git_url .split ("api.github.com" )[- 1 ])
727- # Ratelimiting occurs for non-authenticated GH API calls at 60 in 1 hour.
728- r = urlopen (git_url , timeout = 5 )
729- if r .status != 200 :
730- return False
731- if 'git/tree' in git_url :
732- tree = json .loads (r .read ().decode ())['tree' ]
733- else :
734- tree = json .loads (r .read ().decode ())
735- contents = []
736- for sub in tree :
737- if source_element_from_repo_api (sub ):
738- contents .append (source_element_from_repo_api (sub ))
739- return contents
740-
741-
742643def copy_remote_git_source (github_source : InstInfo , verbose : bool = True ) -> SourceDir :
743644 """clone or fetch & checkout a local copy of a remote git repo"""
744645 user , repo = Source .get_github_user_repo (github_source .source_loc )
@@ -1329,22 +1230,30 @@ def _source_search(name: str, src: LoadedSource) -> Union[InstInfo, None]:
13291230 """Identify source type, retrieve contents, and populate InstInfo
13301231 if the relevant contents are found."""
13311232 root_dir = src .content
1332- source = InstInfo (name , root_dir .location , None )
1233+ source = InstInfo (name , root_dir .location )
13331234
13341235 # If a local clone of a github source already exists, prefer searching
13351236 # that instead of accessing the github API.
1336- if src .type == Source .GITHUB_REPO :
1237+ if src .type == Source .REMOTE_GIT_REPO :
13371238 if src .local_clone :
13381239 if not src .local_clone_fetched :
13391240 # FIXME: Pass the LoadedSource here?
13401241 if _git_update (src .original_source , src .local_clone .location ):
13411242 src .local_clone_fetched = True
13421243 log .debug (f'fetching local clone of { src .original_source } ' )
13431244 log .debug (f"Using local clone of { src } : { src .local_clone .location } " )
1245+
1246+ # FIXME: ideally, the InstInfo object would have a concept of the
1247+ # original LoadedSource and get_inst_details would follow the local clone
13441248 source .source_loc = str (src .local_clone .location )
13451249 source .srctype = Source .GIT_LOCAL_CLONE
13461250
13471251 if source .get_inst_details (permissive = True ):
1252+ # If we have a local clone, report back the original location and type,
1253+ # not the clone that was traversed.
1254+ if source .srctype is Source .GIT_LOCAL_CLONE :
1255+ source .source_loc = src .original_source
1256+ source .srctype = src .type
13481257 return source
13491258 return None
13501259
@@ -1354,9 +1263,11 @@ def _git_clone(src: InstInfo, dest: Union[PosixPath, str], verbose: bool=True) -
13541263 log .info (f'cloning { src .srctype } { src } ' )
13551264 else :
13561265 log .debug (f'cloning { src .srctype } { src } ' )
1357- if src .srctype == Source .GITHUB_REPO :
1358- assert 'github.com' in src .source_loc
1359- source = f"{ GITHUB_COM } " + src .source_loc .split ("github.com" )[- 1 ]
1266+ if src .srctype == Source .REMOTE_GIT_REPO :
1267+ if 'github.com' in src .source_loc :
1268+ source = f"{ GITHUB_COM } " + src .source_loc .split ("github.com" )[- 1 ]
1269+ else :
1270+ source = src .source_loc
13601271 elif src .srctype in [Source .LOCAL_REPO , Source .OTHER_URL ,
13611272 Source .GIT_LOCAL_CLONE ]:
13621273 source = src .source_loc
@@ -1375,8 +1286,14 @@ def _git_clone(src: InstInfo, dest: Union[PosixPath, str], verbose: bool=True) -
13751286
13761287
13771288def _git_update (github_source : str , local_copy : PosixPath ):
1289+
1290+ if 'github.com' in github_source :
1291+ source = GITHUB_COM + github_source .split ('github.com' )[- 1 ]
1292+ else :
1293+ source = github_source
1294+
13781295 # Ensure this is the correct source
1379- git = run (['git' , 'remote' , 'set-url' , 'origin' , github_source ],
1296+ git = run (['git' , 'remote' , 'set-url' , 'origin' , source ],
13801297 cwd = str (local_copy ), stdout = PIPE , stderr = PIPE , text = True ,
13811298 check = False , timeout = 60 )
13821299 assert git .returncode == 0
@@ -1401,7 +1318,7 @@ def _git_update(github_source: str, local_copy: PosixPath):
14011318 default_branch = git .stdout .splitlines ()[0 ]
14021319 if default_branch not in ['origin/master' , 'origin/main' ]:
14031320 log .debug (f'UNUSUAL: fetched default branch { default_branch } for '
1404- f'{ github_source } ' )
1321+ f'{ source } ' )
14051322
14061323 # Checkout default branch
14071324 git = run (['git' , 'checkout' , default_branch ],
@@ -1448,7 +1365,7 @@ def _checkout_commit(orig_src: InstInfo,
14481365 cloned_src : InstInfo ,
14491366 cloned_path : PosixPath ):
14501367 # Check out and verify commit/tag if source was a repository
1451- if orig_src .srctype in [Source .LOCAL_REPO , Source .GITHUB_REPO ,
1368+ if orig_src .srctype in [Source .LOCAL_REPO , Source .REMOTE_GIT_REPO ,
14521369 Source .OTHER_URL , Source .GIT_LOCAL_CLONE ]:
14531370 if orig_src .commit :
14541371 log .debug (f"Checking out { orig_src .commit } " )
@@ -1515,7 +1432,7 @@ def _install_plugin(src: InstInfo) -> Union[InstInfo, None]:
15151432 f" { full_source_path } " ))
15161433 create_dir (clone_path )
15171434 shutil .copytree (full_source_path , plugin_path )
1518- elif src .srctype in [Source .LOCAL_REPO , Source .GITHUB_REPO ,
1435+ elif src .srctype in [Source .LOCAL_REPO , Source .REMOTE_GIT_REPO ,
15191436 Source .OTHER_URL , Source .GIT_LOCAL_CLONE ]:
15201437 # clone git repository to /tmp/reckless-...
15211438 if not _git_clone (src , plugin_path ):
@@ -1548,7 +1465,7 @@ def _install_plugin(src: InstInfo) -> Union[InstInfo, None]:
15481465 inst_check_src .source_dir = clone .content
15491466 inst_check_src .source_dir .parent_source = clone
15501467
1551- if src .srctype == Source .GITHUB_REPO :
1468+ if src .srctype == Source .REMOTE_GIT_REPO :
15521469 inst_check_src .srctype = Source .GIT_LOCAL_CLONE
15531470 else :
15541471 inst_check_src .srctype = clone .type
@@ -1727,7 +1644,7 @@ def install(plugin_name: str) -> Union[str, None]:
17271644 src = None
17281645 if direct_location :
17291646 log .debug (f"install of { name } requested from { direct_location } " )
1730- src = InstInfo (name , direct_location , name )
1647+ src = InstInfo (name , direct_location )
17311648 # Treating a local git repo as a directory allows testing
17321649 # uncommitted changes.
17331650 if src and src .srctype == Source .LOCAL_REPO :
@@ -1779,7 +1696,7 @@ def install(plugin_name: str) -> Union[str, None]:
17791696
17801697
17811698def uninstall (plugin_name : str ) -> str :
1782- """dDisables plugin and deletes the plugin's reckless dir. Returns the
1699+ """Disables plugin and deletes the plugin's reckless dir. Returns the
17831700 status of the uninstall attempt."""
17841701 assert isinstance (plugin_name , str )
17851702 log .debug (f'Uninstalling plugin { plugin_name } ' )
@@ -1807,7 +1724,7 @@ def search(plugin_name: str) -> Union[InstInfo, None]:
18071724
18081725 for src in RECKLESS_SOURCES :
18091726 # Search repos named after the plugin before collections
1810- if src .type == Source .GITHUB_REPO :
1727+ if src .type == Source .REMOTE_GIT_REPO :
18111728 if src .original_source .split ('/' )[- 1 ].lower ().removesuffix ('.git' ) == plugin_name .lower ():
18121729 ordered_sources .remove (src )
18131730 ordered_sources .insert (0 , src )
@@ -1821,7 +1738,7 @@ def search(plugin_name: str) -> Union[InstInfo, None]:
18211738 log .debug (f'cannot search { source .type } { source .original_source } ' )
18221739 continue
18231740 if source .type in [Source .DIRECTORY , Source .LOCAL_REPO ,
1824- Source .GITHUB_REPO , Source .OTHER_URL ]:
1741+ Source .REMOTE_GIT_REPO , Source .OTHER_URL ]:
18251742 found = _source_search (plugin_name , source )
18261743 if found :
18271744 log .debug (f"{ found } , { found .srctype } " )
@@ -2101,7 +2018,7 @@ def update_plugin(plugin_name: str) -> tuple:
21012018 return (None , UpdateStatus .REFUSING_UPDATE )
21022019
21032020 src = InstInfo (plugin_name ,
2104- metadata ['original source' ], None )
2021+ metadata ['original source' ])
21052022 if not src .get_inst_details ():
21062023 log .error (f'cannot locate { plugin_name } in original source { metadata ["original_source" ]} ' )
21072024 return (None , UpdateStatus .ERROR )
@@ -2252,7 +2169,7 @@ def find_plugin_candidates(source: Union[LoadedSource, SourceDir], depth=2) -> l
22522169 assert s .srctype == source .srctype , f'source dir { s .name } , { s .srctype } did not inherit { source .srctype } from { source .name } '
22532170 assert s .parent_source == source .parent_source , f'source dir { s .name } did not inherit parent { source .parent_source } from { source .name } '
22542171
2255- guess = InstInfo (source .name , source .location , None , source_dir = source )
2172+ guess = InstInfo (source .name , source .location , source_dir = source )
22562173 guess .srctype = source .srctype
22572174 manifest = None
22582175 if guess .get_inst_details ():
@@ -2292,11 +2209,10 @@ def available_plugins() -> list:
22922209 log .debug (f'confusing source: { source .type } ' )
22932210 continue
22942211 # It takes too many API calls to query for installable plugins accurately.
2295- if source .type == Source .GITHUB_REPO and not source .local_clone :
2212+ if source .type == Source .REMOTE_GIT_REPO and not source .local_clone :
22962213 # FIXME: ignoring non-cloned repos for now.
22972214 log .debug (f'cloning { source .original_source } in order to search' )
22982215 clone = copy_remote_git_source (InstInfo (None ,
2299- source .original_source ,
23002216 source .original_source ,
23012217 source_dir = source .content ),
23022218 verbose = False )
@@ -2538,7 +2454,6 @@ if __name__ == '__main__':
25382454 LIGHTNING_CONFIG = args .conf
25392455 RECKLESS_CONFIG = load_config (reckless_dir = str (RECKLESS_DIR ),
25402456 network = NETWORK )
2541- RECKLESS_SOURCES = load_sources ()
25422457 API_GITHUB_COM = 'https://api.github.com'
25432458 GITHUB_COM = 'https://github.com'
25442459 # Used for blackbox testing to avoid hitting github servers
@@ -2551,6 +2466,7 @@ if __name__ == '__main__':
25512466 if 'GITHUB_API_FALLBACK' in os .environ :
25522467 GITHUB_API_FALLBACK = os .environ ['GITHUB_API_FALLBACK' ]
25532468
2469+ RECKLESS_SOURCES = load_sources ()
25542470 if 'targets' in args : # and len(args.targets) > 0:
25552471 if args .func .__name__ == 'help_alias' :
25562472 log .add_result (args .func (args .targets ))
0 commit comments