From 41c41df6ed2385308a07590245fd96d2a85fb384 Mon Sep 17 00:00:00 2001 From: Gleb Smirnoff Date: Thu, 21 Dec 2023 14:58:43 -0800 Subject: [PATCH 01/11] modify: fix -F together with -C --- bugz/cli.py | 5 ++++- bugz/utils.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/bugz/cli.py b/bugz/cli.py index 9ef1dc3..55a95e9 100644 --- a/bugz/cli.py +++ b/bugz/cli.py @@ -415,13 +415,16 @@ def modify(settings): except IOError as error: raise BugzError('unable to read file: %s: %s' % (settings.comment_from, error)) + else: + settings.comment = '' if hasattr(settings, 'assigned_to') and \ hasattr(settings, 'reset_assigned_to'): raise BugzError('--assigned-to and --unassign cannot be used together') if hasattr(settings, 'comment_editor'): - settings.comment = block_edit('Enter comment:') + settings.comment = block_edit('Enter comment:', + comment_from=settings.comment) params = {} params['ids'] = [settings.bugid] diff --git a/bugz/utils.py b/bugz/utils.py index f6d7996..15dfef6 100644 --- a/bugz/utils.py +++ b/bugz/utils.py @@ -109,7 +109,7 @@ def block_edit(comment, comment_from=''): initial_text = '\n'.join(['BUGZ: %s' % line for line in comment.splitlines()]) new_text = launch_editor(BUGZ_COMMENT_TEMPLATE % initial_text, - comment_from) + comment_from=comment_from) if new_text.strip(): return new_text From 3b6f1e2de649479fe861b99d75ee030633ce6fd3 Mon Sep 17 00:00:00 2001 From: Gleb Smirnoff Date: Thu, 21 Dec 2023 15:27:55 -0800 Subject: [PATCH 02/11] modify: add -Q to quote last comment(s) in the invoked editor This allows to quote as many most recent bug comments in your reply as you need. --- bugz/cli.py | 44 ++++++++++++++++++++++++++++++++++--------- bugz/cli_argparser.py | 3 +++ bugz/utils.py | 9 ++++++--- 3 files changed, 44 insertions(+), 12 deletions(-) diff --git a/bugz/cli.py b/bugz/cli.py index 55a95e9..0bea673 100644 --- a/bugz/cli.py +++ b/bugz/cli.py @@ -17,6 +17,7 @@ """ +import datetime import getpass import os import re @@ -422,10 +423,6 @@ def modify(settings): hasattr(settings, 'reset_assigned_to'): raise BugzError('--assigned-to and --unassign cannot be used together') - if hasattr(settings, 'comment_editor'): - settings.comment = block_edit('Enter comment:', - comment_from=settings.comment) - params = {} params['ids'] = [settings.bugid] if hasattr(settings, 'alias'): @@ -456,10 +453,6 @@ def modify(settings): if 'cc' not in params: params['cc'] = {} params['cc']['remove'] = settings.cc_remove - if hasattr(settings, 'comment'): - if 'comment' not in params: - params['comment'] = {} - params['comment']['body'] = settings.comment if hasattr(settings, 'component'): params['component'] = settings.component if hasattr(settings, 'dupe_of'): @@ -525,9 +518,42 @@ def modify(settings): params['status'] = 'RESOLVED' params['resolution'] = 'INVALID' + check_auth(settings) + + if hasattr(settings, 'comment_editor'): + quotes='' + if hasattr(settings, 'quote'): + bug_comments = settings.call_bz(settings.bz.Bug.comments, params) + bug_comments = bug_comments['bugs']['%s' % settings.bugid]\ + ['comments'][-settings.quote:] + wrapper = textwrap.TextWrapper(width=settings.columns, + break_long_words=False, + break_on_hyphens=False) + for comment in bug_comments: + what = comment['text'] + if what is None: + continue + who = comment['creator'] + when = comment['time'] + when = datetime.datetime.strptime(str(when), '%Y%m%dT%H:%M:%S') + quotes += 'On %s, %s wrote:\n' % (when.strftime('%+ UTC'), who) + for line in what.splitlines(): + if len(line) < settings.columns: + quotes += '> %s\n' % line + else: + for shortline in wrapper.wrap(line): + quotes += '> %s\n' % shortline + settings.comment = block_edit('Enter comment:', + comment_from=settings.comment, + quotes=quotes) + + if hasattr(settings, 'comment'): + if 'comment' not in params: + params['comment'] = {} + params['comment']['body'] = settings.comment + if len(params) < 2: raise BugzError('No changes were specified') - check_auth(settings) result = settings.call_bz(settings.bz.Bug.update, params) for bug in result['bugs']: changes = bug['changes'] diff --git a/bugz/cli_argparser.py b/bugz/cli_argparser.py index 48fd964..d652989 100644 --- a/bugz/cli_argparser.py +++ b/bugz/cli_argparser.py @@ -190,6 +190,9 @@ def make_arg_parser(): help='change the priority for this bug') modify_parser.add_argument('--product', help='change the product for this bug') + modify_parser.add_argument('-Q', '--quote', + action='count', + help='quote most recent comment(s) with -C') modify_parser.add_argument('-r', '--resolution', help='set new resolution ' '(if status = RESOLVED)') diff --git a/bugz/utils.py b/bugz/utils.py index 15dfef6..a52aeb7 100644 --- a/bugz/utils.py +++ b/bugz/utils.py @@ -72,7 +72,8 @@ def terminal_width(): return width -def launch_editor(initial_text, comment_from='', comment_prefix='BUGZ:'): +def launch_editor(initial_text, comment_from='', comment_prefix='BUGZ:', + quotes=''): """Launch an editor with some default text. Lifted from Mercurial 0.9. @@ -80,6 +81,7 @@ def launch_editor(initial_text, comment_from='', comment_prefix='BUGZ:'): """ (fd, name) = tempfile.mkstemp("bugz") f = os.fdopen(fd, "w") + f.write(quotes) f.write(comment_from) f.write(initial_text) f.close() @@ -98,7 +100,7 @@ def launch_editor(initial_text, comment_from='', comment_prefix='BUGZ:'): return '' -def block_edit(comment, comment_from=''): +def block_edit(comment, comment_from='', quotes=''): editor = (os.environ.get('BUGZ_EDITOR') or os.environ.get('EDITOR')) if not editor: @@ -109,7 +111,8 @@ def block_edit(comment, comment_from=''): initial_text = '\n'.join(['BUGZ: %s' % line for line in comment.splitlines()]) new_text = launch_editor(BUGZ_COMMENT_TEMPLATE % initial_text, - comment_from=comment_from) + comment_from=comment_from, + quotes=quotes) if new_text.strip(): return new_text From 6b67f32bd4658edf4dca7618edd72352fc1ac945 Mon Sep 17 00:00:00 2001 From: Gleb Smirnoff Date: Wed, 27 Dec 2023 00:15:24 -0800 Subject: [PATCH 03/11] get: show Name Surname for Reporter, Assignee and Cc list --- bugz/cli.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/bugz/cli.py b/bugz/cli.py index 0bea673..b05f015 100644 --- a/bugz/cli.py +++ b/bugz/cli.py @@ -245,21 +245,21 @@ def show_bug_info(bug, settings): 'priority': 'Priority', 'severity': 'Severity', 'target_milestone': 'TargetMilestone', - 'assigned_to': 'AssignedTo', + 'assigned_to_detail': 'AssignedTo', 'url': 'URL', 'whiteboard': 'Whiteboard', 'keywords': 'Keywords', 'depends_on': 'dependsOn', 'blocks': 'Blocks', 'creation_time': 'Reported', - 'creator': 'Reporter', + 'creator_detail': 'Reporter', 'last_change_time': 'Updated', - 'cc': 'CC', + 'cc_detail': 'CC', 'see_also': 'See Also', } - SkipFields = ['assigned_to_detail', 'cc_detail', 'creator_detail', 'id', - 'is_confirmed', 'is_creator_accessible', 'is_cc_accessible', - 'is_open', 'update_token'] + SkipFields = ['assigned_to', 'cc', 'creator', 'id', 'is_confirmed', + 'is_creator_accessible', 'is_cc_accessible', 'is_open', + 'update_token'] for field in bug: if field in SkipFields: @@ -269,7 +269,12 @@ def show_bug_info(bug, settings): else: desc = field value = bug[field] - if field in ['cc', 'see_also']: + if field in ['assigned_to_detail', 'creator_detail']: + print('%-12s: %s <%s>' % (desc, value['real_name'], value['email'])) + elif field == 'cc_detail': + for cc in value: + print('%-12s: %s <%s>' % (desc, cc['real_name'], cc['email'])) + elif field == 'see_also': for x in value: print('%-12s: %s' % (desc, x)) elif isinstance(value, list): From 3d8843af02c3569baa4919f65e84399c2c5b5368 Mon Sep 17 00:00:00 2001 From: Gleb Smirnoff Date: Wed, 27 Dec 2023 00:56:03 -0800 Subject: [PATCH 04/11] get/modify: print formatted date & time. Use default '%+ UTC' as Bugzilla WebUI does. Provide key/config option to override that. --- bugz/cli.py | 24 ++++++++++++++++++------ bugz/cli_argparser.py | 2 ++ bugz/settings.py | 8 ++++++++ 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/bugz/cli.py b/bugz/cli.py index b05f015..c4ab700 100644 --- a/bugz/cli.py +++ b/bugz/cli.py @@ -231,6 +231,14 @@ def prompt_for_bug(settings): log_info('Append command (optional): %s' % settings.append_command) +def parsetime(when): + return datetime.datetime.strptime(str(when), '%Y%m%dT%H:%M:%S') + + +def printtime(dt, settings): + return dt.strftime(settings.timeformat) + + def show_bug_info(bug, settings): FieldMap = { 'alias': 'Alias', @@ -260,6 +268,7 @@ def show_bug_info(bug, settings): SkipFields = ['assigned_to', 'cc', 'creator', 'id', 'is_confirmed', 'is_creator_accessible', 'is_cc_accessible', 'is_open', 'update_token'] + TimeFields = ['last_change_time', 'creation_time'] for field in bug: if field in SkipFields: @@ -268,7 +277,10 @@ def show_bug_info(bug, settings): desc = FieldMap[field] else: desc = field - value = bug[field] + if field in TimeFields: + value = printtime(parsetime(bug[field]), settings) + else: + value = bug[field] if field in ['assigned_to_detail', 'creator_detail']: print('%-12s: %s <%s>' % (desc, value['real_name'], value['email'])) elif field == 'cc_detail': @@ -308,9 +320,9 @@ def show_bug_info(bug, settings): break_on_hyphens=False) for comment in bug_comments: who = comment['creator'] - when = comment['time'] + when = parsetime(comment['time']) what = comment['text'] - print('[Comment #%d] %s : %s' % (i, who, when)) + print('[Comment #%d] %s : %s' % (i, who, printtime(when, settings))) print('-' * (settings.columns - 1)) if what is None: @@ -539,9 +551,9 @@ def modify(settings): if what is None: continue who = comment['creator'] - when = comment['time'] - when = datetime.datetime.strptime(str(when), '%Y%m%dT%H:%M:%S') - quotes += 'On %s, %s wrote:\n' % (when.strftime('%+ UTC'), who) + when = parsetime(comment['time']) + quotes += 'On %s, %s wrote:\n' % (printtime(when, settings), + who) for line in what.splitlines(): if len(line) < settings.columns: quotes += '> %s\n' % line diff --git a/bugz/cli_argparser.py b/bugz/cli_argparser.py index d652989..01c2216 100644 --- a/bugz/cli_argparser.py +++ b/bugz/cli_argparser.py @@ -14,6 +14,8 @@ def make_arg_parser(): 'configuration file') parser.add_argument('-b', '--base', help='base URL of Bugzilla') + parser.add_argument('-t', '--timeformat', + help='Time format (default: %%+ UTC), see strftime(3)') parser.add_argument('-u', '--user', help='username') parser.add_argument('-p', '--password', diff --git a/bugz/settings.py b/bugz/settings.py index c45481c..5eac42a 100644 --- a/bugz/settings.py +++ b/bugz/settings.py @@ -43,6 +43,14 @@ def __init__(self, args, config): self.connection, 'component') + if not hasattr(self, 'timeformat'): + if config.has_option(self.connection, 'timeformat'): + self.timeformat = get_config_option(config.get, + self.connection, + 'timeformat') + else: + self.timeformat = '%+ UTC' + if not hasattr(self, 'user'): if config.has_option(self.connection, 'user'): self.user = get_config_option(config.get, From 357b7059caa807329742a970f71b22ca4eccc5b1 Mon Sep 17 00:00:00 2001 From: Gleb Smirnoff Date: Wed, 27 Dec 2023 01:17:42 -0800 Subject: [PATCH 05/11] get: print real names in comment headers Keep track of real names as we parse the bug and reuse the dictionary as we print list of comments. Should work unless somebody has commented and then unsubscribed from Cc, in that case fallback to just email. --- bugz/cli.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/bugz/cli.py b/bugz/cli.py index c4ab700..adff6c2 100644 --- a/bugz/cli.py +++ b/bugz/cli.py @@ -269,6 +269,7 @@ def show_bug_info(bug, settings): 'is_creator_accessible', 'is_cc_accessible', 'is_open', 'update_token'] TimeFields = ['last_change_time', 'creation_time'] + user_detail = {} for field in bug: if field in SkipFields: @@ -283,9 +284,11 @@ def show_bug_info(bug, settings): value = bug[field] if field in ['assigned_to_detail', 'creator_detail']: print('%-12s: %s <%s>' % (desc, value['real_name'], value['email'])) + user_detail[value['email']] = value elif field == 'cc_detail': for cc in value: print('%-12s: %s <%s>' % (desc, cc['real_name'], cc['email'])) + user_detail[cc['email']] = cc elif field == 'see_also': for x in value: print('%-12s: %s' % (desc, x)) @@ -319,10 +322,15 @@ def show_bug_info(bug, settings): break_long_words=False, break_on_hyphens=False) for comment in bug_comments: - who = comment['creator'] + if comment['creator'] in user_detail: + who = '%s <%s>' % ( + user_detail[comment['creator']]['real_name'], + comment['creator']) + else: + who = comment['creator'] when = parsetime(comment['time']) what = comment['text'] - print('[Comment #%d] %s : %s' % (i, who, printtime(when, settings))) + print('[Comment #%d] %s %s' % (i, who, printtime(when, settings))) print('-' * (settings.columns - 1)) if what is None: From 449c80d47d4890e2106dbc5aa4e3872cb988f857 Mon Sep 17 00:00:00 2001 From: Gleb Smirnoff Date: Wed, 27 Dec 2023 13:03:43 -0800 Subject: [PATCH 06/11] get: print comment # aligned to the right With this change and preceding real name and date/time changes a comment header gets as close to the WebUI as possible. --- bugz/cli.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/bugz/cli.py b/bugz/cli.py index adff6c2..552124b 100644 --- a/bugz/cli.py +++ b/bugz/cli.py @@ -329,10 +329,15 @@ def show_bug_info(bug, settings): else: who = comment['creator'] when = parsetime(comment['time']) - what = comment['text'] - print('[Comment #%d] %s %s' % (i, who, printtime(when, settings))) + header_left = '%s %s' % (who, printtime(when, settings)) + header_right = '[Comment %d]' % i + space = settings.columns - len(header_left) - len(header_right) - 3 + if space < 0: + space = 0 + print(header_left, ' ' * space, header_right) print('-' * (settings.columns - 1)) + what = comment['text'] if what is None: what = '' From 225fa31a66c0008aa66b6b43818d330d1cc636a4 Mon Sep 17 00:00:00 2001 From: Gleb Smirnoff Date: Wed, 27 Dec 2023 13:10:34 -0800 Subject: [PATCH 07/11] get: don't print comment count in header Not a useful header and makes it closer to WebUI. --- bugz/cli.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bugz/cli.py b/bugz/cli.py index 552124b..6ab8ea2 100644 --- a/bugz/cli.py +++ b/bugz/cli.py @@ -315,7 +315,6 @@ def show_bug_info(bug, settings): params = {'ids': [bug['id']]} bug_comments = settings.call_bz(settings.bz.Bug.comments, params) bug_comments = bug_comments['bugs']['%s' % bug['id']]['comments'] - print('%-12s: %d' % ('Comments', len(bug_comments))) print() i = 0 wrapper = textwrap.TextWrapper(width=settings.columns, From 3dfcf9621f31c1d578f6d2fcf80480f00d35e14a Mon Sep 17 00:00:00 2001 From: Gleb Smirnoff Date: Wed, 27 Dec 2023 13:24:43 -0800 Subject: [PATCH 08/11] get: don't print bug description as 'Comment 0' --- bugz/cli.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bugz/cli.py b/bugz/cli.py index 6ab8ea2..88e710f 100644 --- a/bugz/cli.py +++ b/bugz/cli.py @@ -329,7 +329,10 @@ def show_bug_info(bug, settings): who = comment['creator'] when = parsetime(comment['time']) header_left = '%s %s' % (who, printtime(when, settings)) - header_right = '[Comment %d]' % i + if i == 0: + header_right = 'Description' + else: + header_right = '[Comment %d]' % i space = settings.columns - len(header_left) - len(header_right) - 3 if space < 0: space = 0 From 79818973609009c077752bb070d4ff383f2380b4 Mon Sep 17 00:00:00 2001 From: Gleb Smirnoff Date: Wed, 27 Dec 2023 13:44:33 -0800 Subject: [PATCH 09/11] get: mix bug history with bug comments in a chronological order, just like WebUI does. Try our best with pretty-printing bug changes and fall back to printing the hash in the worst case. --- bugz/cli.py | 76 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 59 insertions(+), 17 deletions(-) diff --git a/bugz/cli.py b/bugz/cli.py index 88e710f..8a241e0 100644 --- a/bugz/cli.py +++ b/bugz/cli.py @@ -253,6 +253,7 @@ def show_bug_info(bug, settings): 'priority': 'Priority', 'severity': 'Severity', 'target_milestone': 'TargetMilestone', + 'assigned_to': 'AssignedTo', 'assigned_to_detail': 'AssignedTo', 'url': 'URL', 'whiteboard': 'Whiteboard', @@ -315,30 +316,70 @@ def show_bug_info(bug, settings): params = {'ids': [bug['id']]} bug_comments = settings.call_bz(settings.bz.Bug.comments, params) bug_comments = bug_comments['bugs']['%s' % bug['id']]['comments'] + for comment in bug_comments: + comment['when'] = parsetime(comment['time']) + del comment['time'] + comment['who'] = comment['creator'] + del comment['creator'] + bug_history = settings.call_bz(settings.bz.Bug.history, params) + assert(bug_history['bugs'][0]['id'] == bug['id']) + bug_history = bug_history['bugs'][0]['history'] + for change in bug_history: + change['when'] = parsetime(change['when']) + bug_comments += bug_history + bug_comments.sort(key=lambda c: (c['when'], 'changes' in c)) print() i = 0 wrapper = textwrap.TextWrapper(width=settings.columns, break_long_words=False, break_on_hyphens=False) for comment in bug_comments: - if comment['creator'] in user_detail: - who = '%s <%s>' % ( - user_detail[comment['creator']]['real_name'], - comment['creator']) - else: - who = comment['creator'] - when = parsetime(comment['time']) - header_left = '%s %s' % (who, printtime(when, settings)) - if i == 0: - header_right = 'Description' - else: - header_right = '[Comment %d]' % i - space = settings.columns - len(header_left) - len(header_right) - 3 - if space < 0: - space = 0 - print(header_left, ' ' * space, header_right) - print('-' * (settings.columns - 1)) + # Header, who & when + if comment == bug_comments[0] or \ + prev['when'] != comment['when'] or \ + prev['who'] != comment['who']: + if comment['who'] in user_detail: + who = '%s <%s>' % ( + user_detail[comment['who']]['real_name'], + comment['who']) + else: + who = comment['who'] + when = comment['when'] + header_left = '%s %s' % (who, printtime(when, settings)) + if i == 0: + header_right = 'Description' + elif 'changes' in comment: + header_right = '' + else: + header_right = '[Comment %d]' % i + space = settings.columns - len(header_left) - \ + len(header_right) - 3 + if space < 0: + space = 0 + print(header_left, ' ' * space, header_right) + print('-' * (settings.columns - 1)) + + # A change from Bug.history + if 'changes' in comment: + for change in comment['changes']: + if change['field_name'] in FieldMap: + desc = FieldMap[change['field_name']] + else: + desc = change['field_name'] + if change['removed'] and change['added']: + print('%s: %s → %s' % (desc, change['removed'], + change['added'])) + elif change['added']: + print('%s: %s' % (desc, change['added'])) + elif change['removed']: + print('REMOVED %s: %s ' % (desc, change['removed'])) + else: + print(change) + prev = comment + print() + continue + # A comment from Bug.comments what = comment['text'] if what is None: what = '' @@ -351,6 +392,7 @@ def show_bug_info(bug, settings): for shortline in wrapper.wrap(line): print(shortline) print() + prev = comment i += 1 From a583724a6233bb03b14dd7d5db0be3c4741bc357 Mon Sep 17 00:00:00 2001 From: Gleb Smirnoff Date: Wed, 17 Jan 2024 12:19:07 -0800 Subject: [PATCH 10/11] attachments: enable request by a bug ID and more features --- bugz/cli.py | 56 ++++++++++++++++++++++++++++++------------- bugz/cli_argparser.py | 20 ++++++++++++---- 2 files changed, 55 insertions(+), 21 deletions(-) diff --git a/bugz/cli.py b/bugz/cli.py index 8a241e0..29cffa9 100644 --- a/bugz/cli.py +++ b/bugz/cli.py @@ -438,33 +438,55 @@ def attach(settings): def attachment(settings): - """ Download or view an attachment given the id.""" - log_info('Getting attachment %s' % settings.attachid) + """ Download or view an attachment(s) given the attachment or bug id.""" params = {} - params['attachment_ids'] = [settings.attachid] + if hasattr(settings, 'bug'): + params['ids'] = [settings.id] + log_info('Getting attachment(s) for bug %s' % settings.id) + else: + params['attachment_ids'] = [settings.id] + log_info('Getting attachment %s' % settings.id) check_auth(settings) + results = settings.call_bz(settings.bz.Bug.attachments, params) - result = settings.call_bz(settings.bz.Bug.attachments, params) - result = result['attachments'][settings.attachid] - view = hasattr(settings, 'view') + if hasattr(settings, 'bug'): + results = results['bugs'][settings.id] + else: + results = [ results['attachments'][settings.id] ] + + if hasattr(settings, 'patch_only'): + results = list(filter(lambda x : x['is_patch'], results)) + + if hasattr(settings, 'skip_obsolete'): + results = list(filter(lambda x : not x['is_obsolete'], results)) + + if not results: + return + + if hasattr(settings, 'most_recent'): + results = [ results[-1] ] + view = hasattr(settings, 'view') action = {True: 'Viewing', False: 'Saving'} - log_info('%s attachment: "%s"' % - (action[view], result['file_name'])) - safe_filename = os.path.basename(re.sub(r'\.\.', '', + + for result in results: + log_info('%s%s attachment: "%s"' % (action[view], + ' obsolete' if result['is_obsolete'] else '', + result['file_name'])) + safe_filename = os.path.basename(re.sub(r'\.\.', '', result['file_name'])) - if view: - print(result['data'].data.decode('utf-8')) - else: - if os.path.exists(result['file_name']): - raise RuntimeError('Filename already exists') + if view: + print(result['data'].data.decode('utf-8')) + else: + if os.path.exists(result['file_name']): + raise RuntimeError('Filename already exists') - fd = open(safe_filename, 'wb') - fd.write(result['data'].data) - fd.close() + fd = open(safe_filename, 'wb') + fd.write(result['data'].data) + fd.close() def get(settings): diff --git a/bugz/cli_argparser.py b/bugz/cli_argparser.py index 01c2216..786ce7b 100644 --- a/bugz/cli_argparser.py +++ b/bugz/cli_argparser.py @@ -80,13 +80,25 @@ def make_arg_parser(): attachment_parser = subparsers.add_parser('attachment', argument_default=argparse.SUPPRESS, - help='get an attachment ' + help='get an attachment(s) ' 'from Bugzilla') - attachment_parser.add_argument('attachid', - help='the ID of the attachment') + attachment_parser.add_argument('id', + help='the ID of the attachment or bug') + attachment_parser.add_argument('-b', '--bug', + action='store_true', + help='the ID is a bug') + attachment_parser.add_argument('-r', '--most-recent', + action='store_true', + help='get only most recent attachment') + attachment_parser.add_argument('-p', '--patch-only', + action='store_true', + help='get only patch attachment(s)') + attachment_parser.add_argument('-o', '--skip-obsolete', + action='store_true', + help='get only not obsolete attachment(s)') attachment_parser.add_argument('-v', '--view', action="store_true", - help='print attachment rather than save') + help='print attachment(s) rather than save') attachment_parser.set_defaults(func=bugz.cli.attachment) connections_parser = subparsers.add_parser('connections', From c45eb4b51bcb9a621ecb0448671dbdb4dbc5e724 Mon Sep 17 00:00:00 2001 From: Gleb Smirnoff Date: Fri, 1 Mar 2024 20:03:27 -0800 Subject: [PATCH 11/11] attach: recognize *.diff and *.patch as text/plain Also, make these two extensions automatically raise the patch flag. Also, make the patch flag set content type to text/plain, unless mimetypes was able to say something. P.S. Python's mimetypes module is really helpless. --- bugz/cli.py | 16 ++++++++++++++-- bugz/utils.py | 5 ----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/bugz/cli.py b/bugz/cli.py index 29cffa9..a78a9d9 100644 --- a/bugz/cli.py +++ b/bugz/cli.py @@ -19,6 +19,7 @@ import datetime import getpass +import mimetypes import os import re import subprocess @@ -37,7 +38,7 @@ from bugz.settings import Settings from bugz.exceptions import BugzError from bugz.log import log_error, log_info -from bugz.utils import block_edit, get_content_type +from bugz.utils import block_edit def check_bugz_token(): @@ -408,8 +409,19 @@ def attach(settings): if not os.path.exists(filename): raise BugzError('File not found: %s' % filename) + if is_patch is None and \ + (filename.endswith('.diff') or filename.endswith('.patch')): + content_type = 'text/plain' + is_patch = 1 + + if content_type is None: + content_type = mimetypes.guess_type(filename)[0] + if content_type is None: - content_type = get_content_type(filename) + if is_patch is None: + content_type = 'application/octet-stream' + else: + content_type = 'text/plain' if comment is None: comment = block_edit('Enter optional long description of attachment') diff --git a/bugz/utils.py b/bugz/utils.py index a52aeb7..7823896 100644 --- a/bugz/utils.py +++ b/bugz/utils.py @@ -1,4 +1,3 @@ -import mimetypes import os import re import sys @@ -23,10 +22,6 @@ # -def get_content_type(filename): - return mimetypes.guess_type(filename)[0] or 'application/octet-stream' - - def raw_input_block(): """ Allows multiple line input until a Ctrl+D is detected.