From 1a9967deb561a903f2b3651221140112cb754d9f Mon Sep 17 00:00:00 2001 From: Jan Beich Date: Wed, 7 Jan 2015 10:42:15 +0000 Subject: [PATCH 01/12] search_statuses: allow quoting special characters Quoting uses percent encoding and the `%` symbol itself must be quoted with extra `%` for configparser. --- bugz/cli.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bugz/cli.py b/bugz/cli.py index 8b71ed1..fb48f24 100644 --- a/bugz/cli.py +++ b/bugz/cli.py @@ -5,6 +5,7 @@ import sys import textwrap import xmlrpc.client +import urllib.parse try: import readline @@ -627,7 +628,8 @@ def search(conn): if 'all' not in d['status']: params['status'] = d['status'] elif 'search_statuses' in d: - params['status'] = d['search_statuses'] + params['status'] = [ urllib.parse.unquote(s) + for s in d['search_statuses'] ] if 'terms' in d: params['summary'] = d['terms'] search_term = ' '.join(d['terms']).strip() From e2ad658b36556b505617965a6b3ed4ad08fc37a0 Mon Sep 17 00:00:00 2001 From: Jan Beich Date: Wed, 7 Jan 2015 10:42:15 +0000 Subject: [PATCH 02/12] Add configuration for FreeBSD Bugzilla --- pybugz.d/freebsd.conf | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 pybugz.d/freebsd.conf diff --git a/pybugz.d/freebsd.conf b/pybugz.d/freebsd.conf new file mode 100644 index 0000000..9d56fef --- /dev/null +++ b/pybugz.d/freebsd.conf @@ -0,0 +1,3 @@ +[FreeBSD] +base = https://bugs.freebsd.org/bugzilla/xmlrpc.cgi +search_statuses = New Open In%%20Progress UNCONFIRMED From 6efbcffeb70523e85075f51702a55d8f3718c0be Mon Sep 17 00:00:00 2001 From: tom Date: Wed, 26 Aug 2015 20:26:18 +0100 Subject: [PATCH 03/12] whitespace --- lbugz | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbugz b/lbugz index d8b9828..26b8bda 100755 --- a/lbugz +++ b/lbugz @@ -13,7 +13,7 @@ This is based on a patch from Mike Frysinger . import os import subprocess import sys - + args = sys.argv path = os.path.normpath(os.path.dirname(os.path.realpath(__file__))) pkg = os.path.join(path, 'bugz') From b92a71b78ee1ffe3d2a6b8e530bc9589a00b2f75 Mon Sep 17 00:00:00 2001 From: Tal Wrii Date: Wed, 26 Aug 2015 21:07:12 +0100 Subject: [PATCH 04/12] Add a --format option --- bugz/argparser.py | 5 +++++ bugz/cli.py | 33 ++++++++++++++++----------------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/bugz/argparser.py b/bugz/argparser.py index efcb6a7..23e2118 100644 --- a/bugz/argparser.py +++ b/bugz/argparser.py @@ -294,6 +294,11 @@ def make_arg_parser(): search_parser.add_argument('--show-severity', action='store_true', help='show severity of bugs') + search_parser.add_argument( + '--format', + type=str, + help='custom format found bugs. Format: {bug[field]} (see --json)', + default=None) search_parser.set_defaults(func=bugz.cli.search) return parser diff --git a/bugz/cli.py b/bugz/cli.py index 8b71ed1..e2b19d6 100644 --- a/bugz/cli.py +++ b/bugz/cli.py @@ -18,24 +18,23 @@ def list_bugs(buglist, conn): - for bug in buglist: - bugid = bug['id'] - status = bug['status'] - priority = bug['priority'] - severity = bug['severity'] - assignee = bug['assigned_to'].split('@')[0] - desc = bug['summary'] - line = '%s' % (bugid) - if hasattr(conn, 'show_status'): - line = '%s %-12s' % (line, status) - if hasattr(conn, 'show_priority'): - line = '%s %-12s' % (line, priority) - if hasattr(conn, 'show_severity'): - line = '%s %-12s' % (line, severity) - line = '%s %-20s' % (line, assignee) - line = '%s %s' % (line, desc) - print(line[:conn.columns]) + fmt = conn.format + for bug in buglist: + bug['short_assigned_to'] = bug['assigned_to'].split('@')[0] + + if fmt is None: + fmt = '{bug[id]}' + if hasattr(conn, 'show_status'): + fmt += ' {bug[status]:>12}' + if hasattr(conn, 'show_priority'): + fmt += ' {bug[priority]:>12}' + if hasattr(conn, 'show_severity'): + fmt += ' {bug[severity]:>12}' + fmt += ' {bug[short_assigned_to]:>20}' + fmt += ' {bug[summary]}' + + print(fmt.format(bug=bug)[:conn.columns]) log_info("%i bug(s) found." % len(buglist)) From 7d3f65c55caf8cf26803d4be875a72d49d1b3230 Mon Sep 17 00:00:00 2001 From: Tal Wrii Date: Wed, 26 Aug 2015 21:16:11 +0100 Subject: [PATCH 05/12] --json argument to search. Useful for programmtic usage and discovering field for format --- bugz/argparser.py | 6 ++++++ bugz/cli.py | 18 ++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/bugz/argparser.py b/bugz/argparser.py index 23e2118..c4b5ce4 100644 --- a/bugz/argparser.py +++ b/bugz/argparser.py @@ -299,6 +299,12 @@ def make_arg_parser(): type=str, help='custom format found bugs. Format: {bug[field]} (see --json)', default=None) + search_parser.add_argument( + '--json', + action='store_true', + help='format results as newline separated json records', + default=False) + search_parser.set_defaults(func=bugz.cli.search) return parser diff --git a/bugz/cli.py b/bugz/cli.py index e2b19d6..963c833 100644 --- a/bugz/cli.py +++ b/bugz/cli.py @@ -1,24 +1,26 @@ import getpass +import json import os import re import subprocess import sys import textwrap + import xmlrpc.client +from bugz.exceptions import BugzError +from bugz.log import log_debug, log_info +from bugz.utils import block_edit, get_content_type + try: import readline except ImportError: pass -from bugz.exceptions import BugzError -from bugz.log import log_debug, log_info -from bugz.utils import block_edit, get_content_type def list_bugs(buglist, conn): - fmt = conn.format for bug in buglist: bug['short_assigned_to'] = bug['assigned_to'].split('@')[0] @@ -37,6 +39,12 @@ def list_bugs(buglist, conn): print(fmt.format(bug=bug)[:conn.columns]) log_info("%i bug(s) found." % len(buglist)) +def json_bugs(buglist): + for bug in buglist: + for k, v in list(bug.items()): + if isinstance(v, xmlrpc.client.DateTime): + bug[k] = str(v) + print(json.dumps(bug)) def prompt_for_bug(conn): """ Prompt for the information for a bug @@ -645,6 +653,8 @@ def search(conn): if not len(result): log_info('No bugs found.') + elif conn.json: + json_bugs(result) else: list_bugs(result, conn) From 79dbee4f6ed07a2f408a37a431afa007939964a8 Mon Sep 17 00:00:00 2001 From: Tal Wrii Date: Fri, 16 Oct 2015 19:07:29 +0100 Subject: [PATCH 06/12] add a command to list products Has --json and --format argument like search --- bugz/argparser.py | 14 ++++++++++++++ bugz/cli.py | 41 +++++++++++++++++++++++++---------------- 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/bugz/argparser.py b/bugz/argparser.py index c4b5ce4..a0ccf32 100644 --- a/bugz/argparser.py +++ b/bugz/argparser.py @@ -239,6 +239,20 @@ def make_arg_parser(): help='default answer to confirmation question') post_parser.set_defaults(func=bugz.cli.post) + products_parser = subparsers.add_parser('products', + argument_default=argparse.SUPPRESS, help='list available products') + products_parser.set_defaults(func=bugz.cli.products) + products_parser.add_argument( + '--json', + action='store_true', + help='format results as newline separated json records', + default=False) + products_parser.add_argument( + '--format', + type=str, + help='custom format. Format: {product[field]} (see --json)', + default=None) + search_parser = subparsers.add_parser('search', argument_default=argparse.SUPPRESS, help='search for bugs in bugzilla') diff --git a/bugz/cli.py b/bugz/cli.py index 963c833..3390df7 100644 --- a/bugz/cli.py +++ b/bugz/cli.py @@ -17,29 +17,26 @@ except ImportError: pass - - - def list_bugs(buglist, conn): fmt = conn.format + if fmt is None: + fmt = '{bug[id]}' + if hasattr(conn, 'show_status'): + fmt += ' {bug[status]:>12}' + if hasattr(conn, 'show_priority'): + fmt += ' {bug[priority]:>12}' + if hasattr(conn, 'show_severity'): + fmt += ' {bug[severity]:>12}' + fmt += ' {bug[short_assigned_to]:>20}' + fmt += ' {bug[summary]}' + for bug in buglist: bug['short_assigned_to'] = bug['assigned_to'].split('@')[0] - if fmt is None: - fmt = '{bug[id]}' - if hasattr(conn, 'show_status'): - fmt += ' {bug[status]:>12}' - if hasattr(conn, 'show_priority'): - fmt += ' {bug[priority]:>12}' - if hasattr(conn, 'show_severity'): - fmt += ' {bug[severity]:>12}' - fmt += ' {bug[short_assigned_to]:>20}' - fmt += ' {bug[summary]}' - print(fmt.format(bug=bug)[:conn.columns]) log_info("%i bug(s) found." % len(buglist)) -def json_bugs(buglist): +def json_records(buglist): for bug in buglist: for k, v in list(bug.items()): if isinstance(v, xmlrpc.client.DateTime): @@ -616,6 +613,18 @@ def post(conn): log_info('Bug %d submitted' % result['id']) +def products(conn): + product_ids = conn.call_bz(conn.bz.Product.get_accessible_products, dict())['ids'] + products = conn.call_bz(conn.bz.Product.get, dict(ids=product_ids))['products'] + fmt = conn.format + if fmt is None: + fmt = '{product[name]}' + if conn.json: + json_records(products) + else: + for product in products: + print(fmt.format(product=product)[:conn.columns]) + def search(conn): """Performs a search on the bugzilla database with the keywords given on the title (or the body if specified). @@ -654,7 +663,7 @@ def search(conn): if not len(result): log_info('No bugs found.') elif conn.json: - json_bugs(result) + json_records(result) else: list_bugs(result, conn) From 295b14b39e8d3aa07ab60467d29a0bf1d11f97d0 Mon Sep 17 00:00:00 2001 From: Tal Wrii Date: Fri, 16 Oct 2015 19:18:00 +0100 Subject: [PATCH 07/12] Add a components command With --json and --format arguments Lists all the components and their products --- bugz/argparser.py | 16 ++++++++++++++++ bugz/cli.py | 22 +++++++++++++++++++--- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/bugz/argparser.py b/bugz/argparser.py index a0ccf32..ed2d3b2 100644 --- a/bugz/argparser.py +++ b/bugz/argparser.py @@ -253,6 +253,22 @@ def make_arg_parser(): help='custom format. Format: {product[field]} (see --json)', default=None) + components_parser = subparsers.add_parser('components', + argument_default=argparse.SUPPRESS, help='list available components') + components_parser.set_defaults(func=bugz.cli.components) + components_parser.add_argument( + '--json', + action='store_true', + help='format results as newline separated json records', + default=False) + components_parser.add_argument( + '--format', + type=str, + help='custom format. Format: {product[field]} (see --json)', + default=None) + + + search_parser = subparsers.add_parser('search', argument_default=argparse.SUPPRESS, help='search for bugs in bugzilla') diff --git a/bugz/cli.py b/bugz/cli.py index 3390df7..0a8ada6 100644 --- a/bugz/cli.py +++ b/bugz/cli.py @@ -612,10 +612,8 @@ def post(conn): result = conn.call_bz(conn.bz.Bug.create, params) log_info('Bug %d submitted' % result['id']) - def products(conn): - product_ids = conn.call_bz(conn.bz.Product.get_accessible_products, dict())['ids'] - products = conn.call_bz(conn.bz.Product.get, dict(ids=product_ids))['products'] + products = fetch_products(conn) fmt = conn.format if fmt is None: fmt = '{product[name]}' @@ -625,6 +623,24 @@ def products(conn): for product in products: print(fmt.format(product=product)[:conn.columns]) +def components(conn): + products = fetch_products(conn) + fmt = conn.format + if fmt is None: + fmt = '{product[name]:>20} {component[name]:>20} {component[description]:>20}' + if conn.json: + json_records(products) + else: + for product in products: + for component in product['components']: + print(fmt.format(product=product, component=component)[:conn.columns]) + + + +def fetch_products(conn): + product_ids = conn.call_bz(conn.bz.Product.get_accessible_products, dict())['ids'] + return conn.call_bz(conn.bz.Product.get, dict(ids=product_ids))['products'] + def search(conn): """Performs a search on the bugzilla database with the keywords given on the title (or the body if specified). From f5a3bbfa2bffcc6632fb6a6969e493d30e8e58b1 Mon Sep 17 00:00:00 2001 From: Tal Wrii Date: Tue, 20 Oct 2015 15:25:37 +0100 Subject: [PATCH 08/12] actually pay attention to is_patch argument --- bugz/argparser.py | 2 +- bugz/cli.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bugz/argparser.py b/bugz/argparser.py index ed2d3b2..f726057 100644 --- a/bugz/argparser.py +++ b/bugz/argparser.py @@ -56,7 +56,7 @@ def make_arg_parser(): attach_parser.add_argument('-p', '--patch', action='store_true', help='attachment is a patch', - dest='is_patch') + dest='is_patch', default=False) attach_parser.add_argument('-t', '--title', help='a short description of the attachment (default: filename).', dest='summary') diff --git a/bugz/cli.py b/bugz/cli.py index 0a8ada6..cb362f9 100644 --- a/bugz/cli.py +++ b/bugz/cli.py @@ -291,8 +291,9 @@ def attach(conn): if not is_patch: params['content_type'] = content_type params['comment'] = comment - params['is_patch'] = is_patch + params['is_patch'] = False login(conn) + result = conn.call_bz(conn.bz.Bug.add_attachment, params) attachid = result['ids'][0] log_info('{0} ({1}) has been attached to bug {2}'.format( From 2426cc42650a2217d2f14466cdbeb72450c46c84 Mon Sep 17 00:00:00 2001 From: Tal Wrii Date: Fri, 23 Oct 2015 14:26:24 +0100 Subject: [PATCH 09/12] fix thinkos --- bugz/cli.py | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/bugz/cli.py b/bugz/cli.py index 2723b77..7416cdb 100644 --- a/bugz/cli.py +++ b/bugz/cli.py @@ -40,15 +40,15 @@ except ImportError: pass -def list_bugs(buglist, conn): - fmt = conn.format +def list_bugs(buglist, settings): + fmt = settings.format if fmt is None: fmt = '{bug[id]}' - if hasattr(conn, 'show_status'): + if hasattr(settings, 'show_status'): fmt += ' {bug[status]:>12}' - if hasattr(conn, 'show_priority'): + if hasattr(settings, 'show_priority'): fmt += ' {bug[priority]:>12}' - if hasattr(conn, 'show_severity'): + if hasattr(settings, 'show_severity'): fmt += ' {bug[severity]:>12}' fmt += ' {bug[short_assigned_to]:>20}' fmt += ' {bug[summary]}' @@ -56,7 +56,7 @@ def list_bugs(buglist, conn): for bug in buglist: bug['short_assigned_to'] = bug['assigned_to'].split('@')[0] - print(fmt.format(bug=bug)[:conn.columns]) + print(fmt.format(bug=bug)[:settings.columns]) log_info("%i bug(s) found." % len(buglist)) def json_records(buglist): @@ -635,28 +635,32 @@ def post(settings): result = settings.call_bz(settings.bz.Bug.create, params) log_info('Bug %d submitted' % result['id']) -def products(conn): - products = fetch_products(conn) - fmt = conn.format +def products(settings): + products = fetch_products(settings) + fmt = settings.format if fmt is None: fmt = '{product[name]}' - if conn.json: + if settings.json: json_records(products) else: for product in products: - print(fmt.format(product=product)[:conn.columns]) + print(fmt.format(product=product)[:settings.columns]) -def components(conn): - products = fetch_products(conn) - fmt = conn.format +def components(settings): + products = fetch_products(settings) + fmt = settings.format if fmt is None: fmt = '{product[name]:>20} {component[name]:>20} {component[description]:>20}' - if conn.json: + if settings.json: json_records(products) else: for product in products: for component in product['components']: - print(fmt.format(product=product, component=component)[:conn.columns]) + print(fmt.format(product=product, component=component)[:settings.columns]) + +def fetch_products(settings): + product_ids = settings.call_bz(settings.bz.Product.get_accessible_products, dict())['ids'] + return settings.call_bz(settings.bz.Product.get, dict(ids=product_ids))['products'] def search(settings): """Performs a search on the bugzilla database with @@ -676,8 +680,8 @@ def search(settings): if 'all' not in d['status']: params['status'] = d['status'] elif 'search_statuses' in d: - params['status'] = [urllib.parse.unquote(s) - for s in d['search_statuses']] + params['status'] = [urllib.parse.unquote(s) + for s in d['search_statuses']] if 'terms' in d: params['summary'] = d['terms'] @@ -695,8 +699,8 @@ def search(settings): if not len(result): log_info('No bugs found.') - elif conn.json: - json_records(result) + elif settings.json: + json_records(result) else: list_bugs(result, settings) From 949083fe0ee4ce79669e48852c75b351dbb453d4 Mon Sep 17 00:00:00 2001 From: Tal Wrii Date: Fri, 23 Oct 2015 14:29:23 +0100 Subject: [PATCH 10/12] gitignore -- ignore emacs configuration files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 8f3ee2a..13d1421 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ *~ tags build +.dir-locals.el From 6eb386914d115ae61ab0c24a256b745e67534c0b Mon Sep 17 00:00:00 2001 From: Tal Wrii Date: Fri, 23 Oct 2015 15:18:02 +0100 Subject: [PATCH 11/12] Allow negative status filtering This is not by manually searching the bugs. This also allows empty searches... occassionally listing all the bugs is useful --- bugz/cli.py | 6 +++--- bugz/cli_argparser.py | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/bugz/cli.py b/bugz/cli.py index 7416cdb..0b1cdd3 100644 --- a/bugz/cli.py +++ b/bugz/cli.py @@ -685,9 +685,6 @@ def search(settings): if 'terms' in d: params['summary'] = d['terms'] - if not params: - raise BugzError('Please give search terms or options.') - log_info('Searching for bugs meeting the following criteria:') for key in params: log_info(' {0:<20} = {1}'.format(key, params[key])) @@ -697,6 +694,9 @@ def search(settings): result = settings.call_bz(settings.bz.Bug.search, params)['bugs'] + if hasattr(settings, 'not_status'): + result = list(b for b in result if b['status'] not in settings.not_status) + if not len(result): log_info('No bugs found.') elif settings.json: diff --git a/bugz/cli_argparser.py b/bugz/cli_argparser.py index a604562..d1084d7 100644 --- a/bugz/cli_argparser.py +++ b/bugz/cli_argparser.py @@ -322,6 +322,10 @@ def make_arg_parser(): action='append', help='restrict by status ' '(one or more, use all for all statuses)') + search_parser.add_argument('-S', '--not-status', + action='append', + help='exclude by status ' + '(one or more, use all for all statuses)') search_parser.add_argument('-v', '--version', action='append', help='restrict by version (one or more)') From 1578f8beea1f74e38fcbdf912850a9c2f8ec87eb Mon Sep 17 00:00:00 2001 From: Tal Wrii Date: Fri, 23 Oct 2015 16:03:35 +0100 Subject: [PATCH 12/12] modify unassign command command to reset a bug to its default owner --- bugz/cli.py | 5 +++++ bugz/cli_argparser.py | 3 +++ 2 files changed, 8 insertions(+) diff --git a/bugz/cli.py b/bugz/cli.py index 0b1cdd3..82cfaa5 100644 --- a/bugz/cli.py +++ b/bugz/cli.py @@ -423,6 +423,9 @@ def modify(settings): raise BugzError('unable to read file: %s: %s' % (settings.comment_from, error)) + if hasattr(settings, 'assigned_to') and hasattr(settings, 'unassign'): + raise BugzError('--assigned-to and --unassign cannot be used together') + if hasattr(settings, 'comment_editor'): settings.comment = block_edit('Enter comment:') @@ -432,6 +435,8 @@ def modify(settings): params['alias'] = settings.alias if hasattr(settings, 'assigned_to'): params['assigned_to'] = settings.assigned_to + if hasattr(settings, 'unassign'): + params['reset_assigned_to'] = True if hasattr(settings, 'blocks_add'): if 'blocks' not in params: params['blocks'] = {} diff --git a/bugz/cli_argparser.py b/bugz/cli_argparser.py index d1084d7..dca9125 100644 --- a/bugz/cli_argparser.py +++ b/bugz/cli_argparser.py @@ -192,6 +192,9 @@ def make_arg_parser(): modify_parser.add_argument('-t', '--title', dest='summary', help='set title of bug') + modify_parser.add_argument('-u', '--unassign', + dest='unassign', action='store_true', + help='Reassign the bug to default owner') modify_parser.add_argument('-U', '--url', help='set URL field of bug') modify_parser.add_argument('-v', '--version',