# HG changeset patch # User Jeff Hammel # Date 1717363658 25200 # Node ID 35b94053aafbf93e687a475342928412c0679fa4 # Parent 7f00458a94555bd8ab31259adf0a1f14f53965d2 update to use ssh for upstream src diff -r 7f00458a9455 -r 35b94053aafb setup.py --- a/setup.py Sun Jun 02 13:33:48 2024 -0700 +++ b/setup.py Sun Jun 02 14:27:38 2024 -0700 @@ -17,10 +17,11 @@ include_package_data=True, zip_safe=False, install_requires=[ + # TODO: lxml , required for mirror-hg 'martINI >= 0.6', 'netifaces', 'pexpect', - 'python-hglib', + 'python-hglib', # Alternative to investigate: https://pypi.org/project/hglib/ ], entry_points=""" # -*- Entry points: -*- diff -r 7f00458a9455 -r 35b94053aafb silvermirror/hg.py --- a/silvermirror/hg.py Sun Jun 02 13:33:48 2024 -0700 +++ b/silvermirror/hg.py Sun Jun 02 14:27:38 2024 -0700 @@ -7,21 +7,29 @@ # imports import argparse import hglib # http://mercurial.selenic.com/wiki/PythonHglib +import lxml.html import os +import subprocess import sys from hglib.error import ServerError - -_import_error = None -try: - import lxml.html -except ImportError as _import_error: - pass - +from urllib.parse import urlparse def clone(source, path): print ('Cloning {} -> {}'.format(source, path)) return hglib.clone(source, path) + +def pull(source, path): + """ + pull changes from host on path + """ + + if not os.path.isdir(path): + raise OSError('Not a directory: {}'.format(path)) + command = ['hg', 'pull', source] + subprocess.check_call(command, cwd=path) + + def update(source, path): """ get changes from host on path @@ -33,13 +41,32 @@ try: repo = hglib.open(path) print ('Updating {}'.format(path)) - repo.pull(source, update=True, insecure=True) + try: + repo.pull(source, update=True, insecure=True) + except TypeError: + # Traceback (most recent call last): + # File "/home/jhammel/k0s/bin/mirror-hg", line 33, in + # sys.exit(load_entry_point('silvermirror', 'console_scripts', 'mirror-hg')()) + # File "/home/jhammel/k0s/src/silvermirror/silvermirror/hg.py", line 116, in main + # update(source, dest) + # File "/home/jhammel/k0s/src/silvermirror/silvermirror/hg.py", line 33, in update + # repo.pull(source, update=True, insecure=True) + # File "/home/jhammel/k0s/lib/python3.10/site-packages/hglib/client.py", line 1318, in pull + # self.rawcommand(args, eh=eh) + # File "/home/jhammel/k0s/lib/python3.10/site-packages/hglib/client.py", line 258, in rawcommand + # ret = self.runcommand(args, inchannels, outchannels) + # File "/home/jhammel/k0s/lib/python3.10/site-packages/hglib/client.py", line 186, in runcommand + # if any(b('\0') in a for a in args): + # File "/home/jhammel/k0s/lib/python3.10/site-packages/hglib/client.py", line 186, in + # if any(b('\0') in a for a in args): + # TypeError: 'in ' requires string as left operand, not bytes + pull(source, path) except ServerError: repo = hglib.clone(source, path) return repo -def repositories(url): +def repositories_http(url): """ returns the list of repositories under a URL of an hg server """ @@ -48,6 +75,41 @@ repos = [i.text_content() for i in tds] return repos +def repositories_ssh(url): + """ + returns the list of repositories under a URL via ssh + """ + # This does require that ssh be able to run non-interactively + # ssh k0s.org ls -1 ~/hg + # This also requires that all contents in the directory + # are repositories + + # parse the URL + parsed = urlparse(url) + assert parsed.scheme.lower() == 'ssh' + host = parsed.netloc + path = parsed.path.strip('/') + + # Run ssh command + # TODO: support user@host + command = ['ssh', host, 'ls', '-1', path] + output = subprocess.check_output(command) + return output.decode().strip().splitlines() + + +def repositories(url): + """ + returns the list of hg repositories of a supported URL + (HTTP, TODO SSH) + """ + parsed = urlparse(url) + scheme = parsed.scheme.lower() + if parsed.scheme in ('http', 'https'): + return repositories_http(url) + elif parsed.scheme == 'ssh': + return repositories_ssh(url) + else: + raise ValueError('unsupported scheme: {}'.format(scheme)) def main(args=sys.argv[1:]): """CLI""" @@ -55,21 +117,17 @@ # parse command line parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('host', - help="URL of mercurial repository index page") + help="URL of mercurial repository index page") parser.add_argument('-d', '--directory', dest='directory', default=os.path.join(os.environ['HOME'], 'hg'), help="base directory to clone/update to [DEFAULT: %(default)s]") parser.add_argument('-r', '--repo', dest='repositories', nargs='+', help="repositories") # TODO: stub parser.add_argument('--list', dest='list', - action='store_true', default=False, - help="list repositories and exit") + action='store_true', default=False, + help="list repositories and exit") options = parser.parse_args(args) - if _import_error is not None: - # TODO: better error handling - parser.error("Must have hglib and lxml package to use, sorry:\n{}".format(_import_error)) - # kill trailing slash options.host = options.host.rstrip('/') @@ -81,12 +139,12 @@ return # clone/update repos to directory - if not os.path.exists(options.directory): - os.mkdir(options.directory) + os.makedirs(options.directory, exist_ok=True) for repo in repos: source = '{}/{}'.format(options.host, repo) dest = os.path.join(options.directory, repo) update(source, dest) + if __name__ == '__main__': sys.exit(main() or 0)