X-Git-Url: http://git.ozlabs.org/?a=blobdiff_plain;f=apps%2Fpatchwork%2Fbin%2Fpwclient;h=a31099da099e6671297659c36cd638302d212ccb;hb=84e00517d15bfd536d64bd15ed1669116e2ca7fa;hp=2104f593f9ca37618d33cc4bee0530366aaea76a;hpb=ce4e9c7096be1222592700cdad84cf35895159da;p=patchwork diff --git a/apps/patchwork/bin/pwclient b/apps/patchwork/bin/pwclient index 2104f59..a31099d 100755 --- a/apps/patchwork/bin/pwclient +++ b/apps/patchwork/bin/pwclient @@ -22,12 +22,13 @@ import os import sys import xmlrpclib -import getopt +import argparse import string import tempfile import subprocess import base64 import ConfigParser +import shutil # Default Patchwork remote XML-RPC server URL # This script will check the PW_XMLRPC_URL environment variable @@ -321,23 +322,188 @@ def patch_id_from_hash(rpc, project, hash): patch = rpc.patch_get_by_hash(hash) if patch == {}: - return None + sys.stderr.write("No patch has the hash provided\n") + sys.exit(1) - return patch['id'] + patch_id = patch['id'] + # be super paranoid + try: + patch_id = int(patch_id) + except: + sys.stderr.write("Invalid patch ID obtained from server\n") + sys.exit(1) + return patch_id auth_actions = ['update'] -def main(): - try: - opts, args = getopt.getopt(sys.argv[2:], 's:p:w:d:n:c:h:m:') - except getopt.GetoptError, err: - print str(err) - usage() +# unfortunately we currently have to revert to this ugly hack.. +class _RecursiveHelpAction(argparse._HelpAction): + + def __call__(self, parser, namespace, values, option_string=None): + parser.print_help() + print + + subparsers_actions = [ + action for action in parser._actions + if isinstance(action, argparse._SubParsersAction) + ] + for subparsers_action in subparsers_actions: + for choice, subparser in subparsers_action.choices.items(): + # gross but the whole thing is.. + if (len(subparser._actions) == 2 \ + and ['hash', 'id'] == [a.dest for a in subparser._actions])\ + or len(subparser._actions) == 0: + continue + print("command '{}'".format(choice)) + print(subparser.format_help()) + + parser.exit() +def main(): + hash_parser = argparse.ArgumentParser(add_help=False, version=False) + hash_parser_x = hash_parser.add_mutually_exclusive_group(required=True) + hash_parser_x.add_argument( + '-h', metavar='HASH', dest='hash', action='store', required=False, + help='''Lookup by patch hash''' + ) + hash_parser_x.add_argument( + 'id', metavar='ID', nargs='?', action='store', type=int, + help='Patch ID', + ) + + filter_parser = argparse.ArgumentParser(add_help=False, version=False) + filter_parser.add_argument( + '-s', metavar='STATE', + help='''Filter by patch state (e.g., 'New', 'Accepted', etc.)''' + ) + filter_parser.add_argument( + '-p', metavar='PROJECT', + help='''Filter by project name (see 'projects' for list)''' + ) + filter_parser.add_argument( + '-w', metavar='WHO', + help='''Filter by submitter (name, e-mail substring search)''' + ) + filter_parser.add_argument( + '-d', metavar='WHO', + help='''Filter by delegate (name, e-mail substring search)''' + ) + filter_parser.add_argument( + '-n', metavar='MAX#', + type=int, + help='''Restrict number of results''' + ) + filter_parser.add_argument( + '-m', metavar='MESSAGEID', + help='''Filter by Message-Id''' + ) + filter_parser.add_argument( + 'patch_name', metavar='STR', nargs='?', + help='substring to search for patches by name', + ) + + action_parser = argparse.ArgumentParser( + prog='pwclient', + add_help=False, + version=False, + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog='''(apply | get | info | view | update) (-h HASH | ID)''', + ) + action_parser.add_argument( + '--help', + #action='help', + action=_RecursiveHelpAction, + help='''Print this help text''' + ) + + subparsers = action_parser.add_subparsers( + title='Commands', + metavar='' + ) + apply_parser = subparsers.add_parser( + 'apply', parents=[hash_parser], + add_help=False, + help='''Apply a patch (in the current dir, using -p1)''' + ) + apply_parser.set_defaults(subcmd='apply') + git_am_parser = subparsers.add_parser( + 'git-am', parents=[hash_parser], + add_help=False, + help='''Apply a patch to current git branch using "git am".''' + ) + git_am_parser.set_defaults(subcmd='git-am') + git_am_parser.add_argument( + '-s', '--signoff', + action='store_true', + help='''pass --signoff to git-am''' + ) + get_parser = subparsers.add_parser( + 'get', parents=[hash_parser], + add_help=False, + help='''Download a patch and save it locally''' + ) + get_parser.set_defaults(subcmd='get') + info_parser = subparsers.add_parser( + 'info', parents=[hash_parser], + add_help=False, + help='''Display patchwork info about a given patch ID''' + ) + info_parser.set_defaults(subcmd='info') + projects_parser = subparsers.add_parser( + 'projects', + add_help=False, + help='''List all projects''' + ) + projects_parser.set_defaults(subcmd='projects') + states_parser = subparsers.add_parser( + 'states', + add_help=False, + help='''Show list of potential patch states''' + ) + states_parser.set_defaults(subcmd='states') + view_parser = subparsers.add_parser( + 'view', parents=[hash_parser], + add_help=False, + help='''View a patch''' + ) + view_parser.set_defaults(subcmd='view') + update_parser = subparsers.add_parser( + 'update', parents=[hash_parser], + add_help=False, + help='''Update patch''' + ) + update_parser.set_defaults(subcmd='update') + update_parser.add_argument( + '-c', metavar='COMMIT-REF', + help='''commit reference hash''' + ) + update_parser.add_argument( + '-s', metavar='STATE', + required=True, + help='''Set patch state (e.g., 'Accepted', 'Superseded' etc.)''' + ) + + list_parser = subparsers.add_parser("list", + add_help=False, + #aliases=['search'], + parents=[filter_parser], + help='''List patches, using the optional filters specified + below and an optional substring to search for patches + by name''' + ) + list_parser.set_defaults(subcmd='list') + search_parser = subparsers.add_parser("search", + add_help=False, + parents=[filter_parser], + help='''Alias for "list"''' + ) + search_parser.set_defaults(subcmd='list') if len(sys.argv) < 2: - usage() + action_parser.print_help() + sys.exit(0) - action = sys.argv[1].lower() + args = action_parser.parse_args() + args=dict(vars(args)) # set defaults filt = Filter() @@ -346,60 +512,98 @@ def main(): project_str = "" commit_str = "" state_str = "" - hash_str = "" + hash_str = None msgid_str = "" + id_str = None url = DEFAULT_URL + action = args.get('subcmd') + + if args.get('s'): + state_str = args.get('s') + if args.get('p'): + project_str = args.get('p') + if args.get('w'): + submitter_str = args.get('w') + if args.get('d'): + delegate_str = args.get('d') + if args.get('c'): + commit_str = args.get('c') + if args.get('hash'): + hash_str = args.get('hash') + if args.get('id'): + id_str = args.get('id') + if args.get('m'): + msgid_str = args.get('m') + if args.get('n') != None: + try: + filt.add("max_count", args.get('n')) + except: + sys.stderr.write("Invalid maximum count '%s'\n" % args.get('n')) + action_parser.print_help() + sys.exit(1) + + # grab settings from config files config = ConfigParser.ConfigParser() config.read([CONFIG_FILE]) - # grab settings from config files - if config.has_option('base', 'url'): - url = config.get('base', 'url') - - if config.has_option('base', 'project'): - project_str = config.get('base', 'project') - - for name, value in opts: - if name == '-s': - state_str = value - elif name == '-p': - project_str = value - elif name == '-w': - submitter_str = value - elif name == '-d': - delegate_str = value - elif name == '-c': - commit_str = value - elif name == '-h': - hash_str = value - elif name == '-m': - msgid_str = value - elif name == '-n': - try: - filt.add("max_count", int(value)) - except: - sys.stderr.write("Invalid maximum count '%s'\n" % value) - usage() - else: - sys.stderr.write("Unknown option '%s'\n" % name) + if not config.has_section('options'): + sys.stderr.write('~/.pwclientrc is in the old format. Migrating it...') + + old_project = config.get('base','project') + + new_config = ConfigParser.ConfigParser() + new_config.add_section('options') + + new_config.set('options','default',old_project) + new_config.add_section(old_project) + + new_config.set(old_project,'url',config.get('base','url')) + if config.has_option('auth', 'username'): + new_config.set(old_project,'username',config.get('auth','username')) + if config.has_option('auth', 'password'): + new_config.set(old_project,'password',config.get('auth','password')) + + old_config_file = CONFIG_FILE + '.orig' + shutil.copy2(CONFIG_FILE,old_config_file) + + with open(CONFIG_FILE, 'wb') as fd: + new_config.write(fd) + + sys.stderr.write(' Done.\n') + sys.stderr.write('Your old ~/.pwclientrc was saved to %s\n' % old_config_file) + sys.stderr.write('and was converted to the new format. You may want to\n') + sys.stderr.write('inspect it before continuing.\n') + sys.exit(1) + + if not project_str: + try: + project_str = config.get('options', 'default') + except: + sys.stderr.write("No default project configured in ~/.pwclientrc\n") usage() - if len(args) > 1: - sys.stderr.write("Too many arguments specified\n") - usage() + if not config.has_section(project_str): + sys.stderr.write("No section for project %s\n" % project_str) + sys.exit(1) + + if not config.has_option(project_str, 'url'): + sys.stderr.write("No URL for project %s\n" % project_str) + sys.exit(1) + + url = config.get(project_str, 'url') (username, password) = (None, None) transport = None if action in auth_actions: - if config.has_option('auth', 'username') and \ - config.has_option('auth', 'password'): + if config.has_option(project_str, 'username') and \ + config.has_option(project_str, 'password'): use_https = url.startswith('https') transport = BasicHTTPAuthTransport( \ - config.get('auth', 'username'), - config.get('auth', 'password'), + config.get(project_str, 'username'), + config.get(project_str, 'password'), use_https) else: @@ -423,16 +627,16 @@ def main(): sys.exit(1) patch_id = None + # hash_str and id_str are mutually exclusive if hash_str: patch_id = patch_id_from_hash(rpc, project_str, hash_str) - if patch_id is None: - sys.stderr.write("No patch has the hash provided\n") - sys.exit(1) - + else: + # id_str from argparse is an int + patch_id = id_str if action == 'list' or action == 'search': - if len(args) > 0: - filt.add("name__icontains", args[0]) + if args.get('patch_name') != None: + filt.add("name__icontains", args.get('patch_name')) action_list(rpc, filt, submitter_str, delegate_str) elif action.startswith('project'): @@ -442,53 +646,26 @@ def main(): action_states(rpc) elif action == 'view': - try: - patch_id = patch_id or int(args[0]) - except: - sys.stderr.write("Invalid patch ID given\n") - sys.exit(1) - s = rpc.patch_get_mbox(patch_id) if len(s) > 0: print unicode(s).encode("utf-8") elif action in ('get', 'save', 'info'): - try: - patch_id = patch_id or int(args[0]) - except: - sys.stderr.write("Invalid patch ID given\n") - sys.exit(1) - if action == 'info': action_info(rpc, patch_id) else: action_get(rpc, patch_id) elif action == 'apply': - try: - patch_id = patch_id or int(args[0]) - except: - sys.stderr.write("Invalid patch ID given\n") - sys.exit(1) - action_apply(rpc, patch_id) elif action == 'git-am': - try: - patch_id = patch_id or int(args[0]) - except: - sys.stderr.write("Invalid patch ID given\n") - sys.exit(1) - - action_apply(rpc, patch_id, ['git', 'am']) + cmd = ['git', 'am'] + if args.get('signoff'): + cmd.append('-s') + action_apply(rpc, patch_id, cmd) elif action == 'update': - try: - patch_id = patch_id or int(args[0]) - except: - sys.stderr.write("Invalid patch ID given\n") - sys.exit(1) - action_update_patch(rpc, patch_id, state = state_str, commit = commit_str)