X-Git-Url: http://git.ozlabs.org/?a=blobdiff_plain;f=apps%2Fpatchwork%2Fbin%2Fpwclient;h=70d4f82932728dbf43a00e14ea0f14e7ebdd923b;hb=235238fdfbfbf4e201a36c0ea57576bf63bc3536;hp=a58e949afc3148d6fe1b86b43e4d02476e5985da;hpb=a4875c47a1c3df9d7654169b8d68ad185585d236;p=patchwork diff --git a/apps/patchwork/bin/pwclient b/apps/patchwork/bin/pwclient index a58e949..70d4f82 100755 --- a/apps/patchwork/bin/pwclient +++ b/apps/patchwork/bin/pwclient @@ -29,6 +29,7 @@ import subprocess import base64 import ConfigParser import shutil +import re # Default Patchwork remote XML-RPC server URL # This script will check the PW_XMLRPC_URL environment variable @@ -68,7 +69,7 @@ class Filter: else: self.d['state_id'] = id - if self.project != "": + if self.project != None: id = project_id_by_name(rpc, self.project) if id == 0: sys.stderr.write("Note: No Project found matching %s, " \ @@ -134,17 +135,34 @@ def person_ids_by_name(rpc, name): people = rpc.person_list(name, 0) return map(lambda x: x['id'], people) -def list_patches(patches): +def list_patches(patches, format_str=None): """Dump a list of patches to stdout.""" - print("%-7s %-12s %s" % ("ID", "State", "Name")) - print("%-7s %-12s %s" % ("--", "-----", "----")) - for patch in patches: - print("%-7d %-12s %s" % (patch['id'], patch['state'], patch['name'])) + if format_str: + format_field_re = re.compile("%{([a-z0-9_]+)}") -def action_list(rpc, filter, submitter_str, delegate_str): + def patch_field(matchobj): + fieldname = matchobj.group(1) + + if fieldname == "_msgid_": + # naive way to strip < and > from message-id + val = string.strip(str(patch["msgid"]), "<>") + else: + val = str(patch[fieldname]) + + return val + + for patch in patches: + print(format_field_re.sub(patch_field, format_str)) + else: + print("%-7s %-12s %s" % ("ID", "State", "Name")) + print("%-7s %-12s %s" % ("--", "-----", "----")) + for patch in patches: + print("%-7d %-12s %s" % (patch['id'], patch['state'], patch['name'])) + +def action_list(rpc, filter, submitter_str, delegate_str, format_str=None): filter.resolve_ids(rpc) - if submitter_str != "": + if submitter_str != None: ids = person_ids_by_name(rpc, submitter_str) if len(ids) == 0: sys.stderr.write("Note: Nobody found matching *%s*\n" % \ @@ -158,10 +176,10 @@ def action_list(rpc, filter, submitter_str, delegate_str): f = filter f.add("submitter_id", id) patches = rpc.patch_list(f.d) - list_patches(patches) + list_patches(patches, format_str) return - if delegate_str != "": + if delegate_str != None: ids = person_ids_by_name(rpc, delegate_str) if len(ids) == 0: sys.stderr.write("Note: Nobody found matching *%s*\n" % \ @@ -174,11 +192,11 @@ def action_list(rpc, filter, submitter_str, delegate_str): f = filter f.add("delegate_id", id) patches = rpc.patch_list(f.d) - list_patches(patches) + list_patches(patches, format_str) return patches = rpc.patch_list(filter.d) - list_patches(patches) + list_patches(patches, format_str) def action_projects(rpc): projects = rpc.project_list("", 0) @@ -251,6 +269,7 @@ def action_apply(rpc, patch_id, apply_cmd=None): if len(s) > 0: proc = subprocess.Popen(apply_cmd, stdin = subprocess.PIPE) proc.communicate(unicode(s).encode('utf-8')) + return proc.returncode else: sys.stderr.write("Error: No patch content found\n") sys.exit(1) @@ -317,11 +336,13 @@ class _RecursiveHelpAction(argparse._HelpAction): action for action in parser._actions if isinstance(action, argparse._SubParsersAction) ] + hash_n_id_actions = set(['hash', 'id', 'help']) 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])\ + if (len(subparser._actions) == 3 \ + and set([a.dest for a in subparser._actions]) \ + == hash_n_id_actions) \ or len(subparser._actions) == 0: continue print("command '{}'".format(choice)) @@ -331,15 +352,18 @@ class _RecursiveHelpAction(argparse._HelpAction): 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, + hash_parser.add_argument( + '-h', metavar='HASH', dest='hash', action='store', help='''Lookup by patch hash''' ) - hash_parser_x.add_argument( - 'id', metavar='ID', nargs='?', action='store', type=int, + hash_parser.add_argument( + 'id', metavar='ID', nargs='*', action='store', type=int, help='Patch ID', ) + hash_parser.add_argument( + '-p', metavar='PROJECT', + help='''Lookup patch in project''' + ) filter_parser = argparse.ArgumentParser(add_help=False, version=False) filter_parser.add_argument( @@ -367,17 +391,27 @@ def main(): '-m', metavar='MESSAGEID', help='''Filter by Message-Id''' ) + filter_parser.add_argument( + '-f', metavar='FORMAT', + help='''Print output in the given format. You can use tags matching ''' + '''fields, e.g. %%{id}, %%{state}, or %%{msgid}.''' + ) filter_parser.add_argument( 'patch_name', metavar='STR', nargs='?', help='substring to search for patches by name', ) + help_parser = argparse.ArgumentParser(add_help=False, version=False) + help_parser.add_argument( + '--help', action='help', help=argparse.SUPPRESS, + #help='''show this help message and exit''' + ) action_parser = argparse.ArgumentParser( prog='pwclient', add_help=False, version=False, formatter_class=argparse.RawDescriptionHelpFormatter, - epilog='''(apply | get | info | view | update) (-h HASH | ID)''', + epilog='''(apply | get | info | view | update) (-h HASH | ID [ID ...])''', ) action_parser.add_argument( '--help', @@ -391,30 +425,30 @@ def main(): metavar='' ) apply_parser = subparsers.add_parser( - 'apply', parents=[hash_parser], + 'apply', parents=[hash_parser, help_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], + 'git-am', parents=[hash_parser, help_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.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], + 'get', parents=[hash_parser, help_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], + 'info', parents=[hash_parser, help_parser], add_help=False, help='''Display patchwork info about a given patch ID''' ) @@ -432,17 +466,17 @@ def main(): ) states_parser.set_defaults(subcmd='states') view_parser = subparsers.add_parser( - 'view', parents=[hash_parser], + 'view', parents=[hash_parser, help_parser], add_help=False, help='''View a patch''' ) view_parser.set_defaults(subcmd='view') update_parser = subparsers.add_parser( - 'update', parents=[hash_parser], + 'update', parents=[hash_parser, help_parser], add_help=False, - help='''Update patch''' + help='''Update patch''', + epilog='''Using a COMMIT-REF allows for only one ID to be specified''', ) - update_parser.set_defaults(subcmd='update') update_parser.add_argument( '-c', metavar='COMMIT-REF', help='''commit reference hash''' @@ -452,11 +486,11 @@ def main(): required=True, help='''Set patch state (e.g., 'Accepted', 'Superseded' etc.)''' ) - + update_parser.set_defaults(subcmd='update') list_parser = subparsers.add_parser("list", add_help=False, #aliases=['search'], - parents=[filter_parser], + parents=[filter_parser, help_parser], help='''List patches, using the optional filters specified below and an optional substring to search for patches by name''' @@ -464,47 +498,51 @@ def main(): list_parser.set_defaults(subcmd='list') search_parser = subparsers.add_parser("search", add_help=False, - parents=[filter_parser], + parents=[filter_parser, help_parser], help='''Alias for "list"''' ) + # Poor man's argparse aliases: + # We register the "search" parser but effectively use "list" for the + # help-text. search_parser.set_defaults(subcmd='list') if len(sys.argv) < 2: action_parser.print_help() sys.exit(0) args = action_parser.parse_args() - args=dict(vars(args)) + args = dict(vars(args)) + action = args.get('subcmd') + + if args.get('hash') and len(args.get('id')): + # mimic mutual exclusive group + sys.stderr.write("Error: [-h HASH] and [ID [ID ...]] " + + "are mutually exlusive\n") + locals()[action + '_parser'].print_help() + sys.exit(1) # set defaults filt = Filter() - submitter_str = "" - delegate_str = "" - project_str = "" - commit_str = "" - state_str = "" - hash_str = None - msgid_str = "" - id_str = None + commit_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') + state_str = args.get('s') + project_str = args.get('p') + submitter_str = args.get('w') + delegate_str = args.get('d') + format_str = args.get('f') + hash_str = args.get('hash') + patch_ids = args.get('id') + msgid_str = args.get('m') if args.get('c'): + # update multiple IDs with a single commit-hash does not make sense + if action == 'update' and patch_ids and len(patch_ids) > 1: + sys.stderr.write( + "Declining update with COMMIT-REF on multiple IDs\n" + ) + update_parser.print_help() + sys.exit(1) 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')) @@ -513,6 +551,8 @@ def main(): action_parser.print_help() sys.exit(1) + do_signoff = args.get('signoff') + # grab settings from config files config = ConfigParser.ConfigParser() config.read([CONFIG_FILE]) @@ -557,14 +597,16 @@ def main(): 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) + if not do_signoff and config.has_option('options', 'signoff'): + do_signoff = config.getboolean('options', 'signoff') + if not do_signoff and config.has_option(project_str, 'signoff'): + do_signoff = config.getboolean(project_str, 'signoff') url = config.get(project_str, 'url') - (username, password) = (None, None) transport = None if action in auth_actions: if config.has_option(project_str, 'username') and \ @@ -597,18 +639,28 @@ def main(): sys.stderr.write("Unable to connect to %s\n" % url) 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) - else: - # id_str from argparse is an int - patch_id = id_str + # It should be safe to assume hash_str is not zero, but who knows.. + if hash_str != None: + patch_ids = [patch_id_from_hash(rpc, project_str, hash_str)] + + # helper for non_empty() to print correct helptext + h = locals()[action + '_parser'] + + # Require either hash_str or IDs for + def non_empty(h, patch_ids): + """Error out if no patch IDs were specified""" + if patch_ids == None or len(patch_ids) < 1: + sys.stderr.write("Error: Missing Argument! " + + "Either [-h HASH] or [ID [ID ...]] are required\n") + if h: + h.print_help() + sys.exit(1) + return patch_ids if action == 'list' or action == 'search': if args.get('patch_name') != None: filt.add("name__icontains", args.get('patch_name')) - action_list(rpc, filt, submitter_str, delegate_str) + action_list(rpc, filt, submitter_str, delegate_str, format_str) elif action.startswith('project'): action_projects(rpc) @@ -617,28 +669,41 @@ def main(): action_states(rpc) elif action == 'view': - s = rpc.patch_get_mbox(patch_id) - if len(s) > 0: - print unicode(s).encode("utf-8") + for patch_id in non_empty(h, patch_ids): + s = rpc.patch_get_mbox(patch_id) + if len(s) > 0: + print unicode(s).encode("utf-8") - elif action in ('get', 'save', 'info'): - if action == 'info': + elif action == 'info': + for patch_id in non_empty(h, patch_ids): action_info(rpc, patch_id) - else: + + elif action == 'get': + for patch_id in non_empty(h, patch_ids): action_get(rpc, patch_id) elif action == 'apply': - action_apply(rpc, patch_id) + for patch_id in non_empty(h, patch_ids): + ret = action_apply(rpc, patch_id) + if ret: + sys.stderr.write("Apply failed with exit status %d\n" % ret) + sys.exit(1) - elif action == 'git-am': + elif action == 'git_am': cmd = ['git', 'am'] - if args.get('signoff'): + if do_signoff: cmd.append('-s') - action_apply(rpc, patch_id, cmd) + for patch_id in non_empty(h, patch_ids): + ret = action_apply(rpc, patch_id, cmd) + if ret: + sys.stderr.write("'git am' failed with exit status %d\n" % ret) + sys.exit(1) elif action == 'update': - action_update_patch(rpc, patch_id, state = state_str, - commit = commit_str) + for patch_id in non_empty(h, patch_ids): + action_update_patch(rpc, patch_id, state = state_str, + commit = commit_str + ) else: sys.stderr.write("Unknown action '%s'\n" % action)