diff options
24 files changed, 506 insertions, 132 deletions
diff --git a/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/64424f05-b42b-4835-8afd-8495ae61345d/body b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/64424f05-b42b-4835-8afd-8495ae61345d/body new file mode 100644 index 0000000..08595d1 --- /dev/null +++ b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/64424f05-b42b-4835-8afd-8495ae61345d/body @@ -0,0 +1,8 @@ +Implemented. + +You can now list targets by dependency (not by date, but better for +most cases) with + be depend -t-1 --severity target ID +where ID is the uuid of any target bug, or with + be depend -t-1 --severity target $(be target --resolve TARGET) +where TARGET is the summary of any target bug. diff --git a/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/64424f05-b42b-4835-8afd-8495ae61345d/values b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/64424f05-b42b-4835-8afd-8495ae61345d/values new file mode 100644 index 0000000..3925aa2 --- /dev/null +++ b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/comments/64424f05-b42b-4835-8afd-8495ae61345d/values @@ -0,0 +1,11 @@ +Author: W. Trevor King <wking@drexel.edu> + + +Content-type: text/plain + + +Date: Sun, 06 Dec 2009 05:42:52 +0000 + + +In-reply-to: 4012c6cc-1300-4f6b-af0e-9176eedf8de7 + diff --git a/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/values b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/values index 7440b56..64928e8 100644 --- a/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/values +++ b/.be/bugs/22b6f620-d2f7-42a5-a02e-145733a4e366/values @@ -14,7 +14,7 @@ reporter: Gianluca Montecchi <gian@grys.it> severity: wishlist -status: assigned +status: fixed summary: Sorting targets chronologically diff --git a/.be/bugs/47c8fd5f-1f5a-4048-bef7-bb4c9a37c411/values b/.be/bugs/47c8fd5f-1f5a-4048-bef7-bb4c9a37c411/values new file mode 100644 index 0000000..f008963 --- /dev/null +++ b/.be/bugs/47c8fd5f-1f5a-4048-bef7-bb4c9a37c411/values @@ -0,0 +1,20 @@ +creator: W. Trevor King <wking@drexel.edu> + + +extra_strings: +- BLOCKED-BY:f51dc5a7-37b7-4ce1-859a-b7cb58be6494 +- BLOCKS:4fc71206-4285-417f-8a3c-ed6fb31bbbda +- BLOCKS:bd0ebb56-fb46-45bc-af08-1e4a94e8ef3c + + +severity: target + + +status: fixed + + +summary: '0.1' + + +time: Sun, 06 Dec 2009 00:37:15 +0000 + diff --git a/.be/bugs/4fc71206-4285-417f-8a3c-ed6fb31bbbda/values b/.be/bugs/4fc71206-4285-417f-8a3c-ed6fb31bbbda/values new file mode 100644 index 0000000..4eebcc4 --- /dev/null +++ b/.be/bugs/4fc71206-4285-417f-8a3c-ed6fb31bbbda/values @@ -0,0 +1,20 @@ +creator: W. Trevor King <wking@drexel.edu> + + +extra_strings: +- BLOCKED-BY:47c8fd5f-1f5a-4048-bef7-bb4c9a37c411 +- BLOCKED-BY:ee681951-f254-43d3-a53a-1b36ae415d5c +- BLOCKS:bd0ebb56-fb46-45bc-af08-1e4a94e8ef3c + + +severity: target + + +status: closed + + +summary: patch-52 + + +time: Sun, 06 Dec 2009 00:37:16 +0000 + diff --git a/.be/bugs/bd0ebb56-fb46-45bc-af08-1e4a94e8ef3c/values b/.be/bugs/bd0ebb56-fb46-45bc-af08-1e4a94e8ef3c/values new file mode 100644 index 0000000..e42beab --- /dev/null +++ b/.be/bugs/bd0ebb56-fb46-45bc-af08-1e4a94e8ef3c/values @@ -0,0 +1,20 @@ +creator: W. Trevor King <wking@drexel.edu> + + +extra_strings: +- BLOCKED-BY:47c8fd5f-1f5a-4048-bef7-bb4c9a37c411 +- BLOCKED-BY:4fc71206-4285-417f-8a3c-ed6fb31bbbda +- BLOCKED-BY:f5c06914-dc64-4658-8ec7-32a026a53f55 + + +severity: target + + +status: fixed + + +summary: '0.2' + + +time: Sun, 06 Dec 2009 00:37:15 +0000 + diff --git a/.be/bugs/ee681951-f254-43d3-a53a-1b36ae415d5c/values b/.be/bugs/ee681951-f254-43d3-a53a-1b36ae415d5c/values index 01b1768..2c8543e 100644 --- a/.be/bugs/ee681951-f254-43d3-a53a-1b36ae415d5c/values +++ b/.be/bugs/ee681951-f254-43d3-a53a-1b36ae415d5c/values @@ -1,6 +1,10 @@ creator: abentley +extra_strings: +- BLOCKS:4fc71206-4285-417f-8a3c-ed6fb31bbbda + + severity: minor @@ -9,6 +13,3 @@ status: closed summary: Support rcs configuration - -target: patch-52 - diff --git a/.be/bugs/f51dc5a7-37b7-4ce1-859a-b7cb58be6494/values b/.be/bugs/f51dc5a7-37b7-4ce1-859a-b7cb58be6494/values index 4e5613f..3e4747c 100644 --- a/.be/bugs/f51dc5a7-37b7-4ce1-859a-b7cb58be6494/values +++ b/.be/bugs/f51dc5a7-37b7-4ce1-859a-b7cb58be6494/values @@ -1,3 +1,7 @@ +extra_strings: +- BLOCKS:47c8fd5f-1f5a-4048-bef7-bb4c9a37c411 + + severity: fatal @@ -6,6 +10,3 @@ status: fixed summary: Can't create bugs - -target: '0.1' - diff --git a/.be/bugs/f5c06914-dc64-4658-8ec7-32a026a53f55/values b/.be/bugs/f5c06914-dc64-4658-8ec7-32a026a53f55/values index cf35f07..3c7b8d1 100644 --- a/.be/bugs/f5c06914-dc64-4658-8ec7-32a026a53f55/values +++ b/.be/bugs/f5c06914-dc64-4658-8ec7-32a026a53f55/values @@ -1,6 +1,10 @@ creator: abentley +extra_strings: +- BLOCKS:bd0ebb56-fb46-45bc-af08-1e4a94e8ef3c + + severity: minor @@ -9,6 +13,3 @@ status: fixed summary: Implement bug tree diff - -target: '0.2' - diff --git a/.be/version b/.be/version index 7bd05c2..29baa0e 100644 --- a/.be/version +++ b/.be/version @@ -1 +1 @@ -Bugs Everywhere Directory v1.2 +Bugs Everywhere Directory v1.3 @@ -1,9 +1,35 @@ December 5, 2009 + * targets are now a special type of bug (severity 'target'), so you + can do all the things you do with normal bugs to them as well + (e.g. comment on them, link them into dependency trees, etc.) + * new command `be due` to get/set bug due dates. * changes to `be diff` * exits with an error if required revision control is not possible. Previously it printed a message, but exitted with status 0. * removed options --new, --removed, --modified, --all * added options --uuids, --subscribe + Replace: + '--new' with '--uuids --subscribe DIR:new' + '--removed' with '--uuids --subscribe DIR:rem' + '--modified' with '--uuids --subscribe DIR:mod' + '--all' with '--uuids' + * changes to `be depend` + * added options --status, --severity + * changes to `be list` + * added blacklist capability to --status, --severity, --assigned + * removed options --target, --cur-target + Replace: + 'be list --target TARGET' with + 'be depend --status -closed,fixed,wontfix --severity -target \ + $(be target --resolve TARGET)' + 'be list --cur-target' with + 'be depend --status -closed,fixed,wontfix --severity -target \ + $(be target --resolve)' + * changes to `be target` + * added option --resolve + * removed option --list + Replace: + 'be target --list' with 'be list --status all --severity target' * assorted cleanups and bugfixes December 4, 2009 diff --git a/becommands/depend.py b/becommands/depend.py index f50d693..6336793 100644 --- a/becommands/depend.py +++ b/becommands/depend.py @@ -15,7 +15,7 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """Add/remove bug dependencies""" -from libbe import cmdutil, bugdir, tree +from libbe import cmdutil, bugdir, bug, tree import os, copy __desc__ = __doc__ @@ -69,8 +69,8 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): parser = get_parser() options, args = parser.parse_args(args) cmdutil.default_complete(options, args, parser, - bugid_args={0: lambda bug : bug.active==True, - 1: lambda bug : bug.active==True}) + bugid_args={0: lambda _bug : _bug.active==True, + 1: lambda _bug : _bug.active==True}) if options.repair == True: if len(args) > 0: @@ -82,7 +82,6 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): raise cmdutil.UsageError("Too many arguments.") elif len(args) == 2 and options.tree_depth != None: raise cmdutil.UsageError("Only one bug id used in tree mode.") - bd = bugdir.BugDir(from_disk=True, manipulate_encodings=manipulate_encodings) @@ -95,10 +94,17 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): for blockee,blocker in fixed]) return 0 + allowed_status_values = \ + cmdutil.select_values(options.status, bug.status_values) + allowed_severity_values = \ + cmdutil.select_values(options.severity, bug.severity_values) + bugA = cmdutil.bug_from_id(bd, args[0]) if options.tree_depth != None: - dtree = DependencyTree(bd, bugA, options.tree_depth) + dtree = DependencyTree(bd, bugA, options.tree_depth, + allowed_status_values, + allowed_severity_values) if len(dtree.blocked_by_tree()) > 0: print "%s blocked by:" % bugA.uuid for depth,node in dtree.blocked_by_tree().thread(): @@ -122,18 +128,18 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): if len(blocked_by) > 0: print "%s blocked by:" % bugA.uuid if options.show_status == True: - print '\n'.join(["%s\t%s" % (bug.uuid, bug.status) - for bug in blocked_by]) + print '\n'.join(["%s\t%s" % (_bug.uuid, _bug.status) + for _bug in blocked_by]) else: - print '\n'.join([bug.uuid for bug in blocked_by]) + print '\n'.join([_bug.uuid for _bug in blocked_by]) blocks = get_blocks(bd, bugA) if len(blocks) > 0: print "%s blocks:" % bugA.uuid if options.show_status == True: - print '\n'.join(["%s\t%s" % (bug.uuid, bug.status) - for bug in blocks]) + print '\n'.join(["%s\t%s" % (_bug.uuid, _bug.status) + for _bug in blocks]) else: - print '\n'.join([bug.uuid for bug in blocks]) + print '\n'.join([_bug.uuid for _bug in blocks]) def get_parser(): parser = cmdutil.CmdOptionParser("be depend BUG-ID [BUG-ID]\nor: be depend --repair") @@ -143,6 +149,10 @@ def get_parser(): parser.add_option("-s", "--show-status", action="store_true", dest="show_status", default=False, help="Show status of blocking bugs") + parser.add_option("--status", dest="status", metavar="STATUS", + help="Only show bugs matching the STATUS specifier") + parser.add_option("--severity", dest="severity", metavar="SEVERITY", + help="Only show bugs matching the SEVERITY specifier") parser.add_option("-t", "--tree-depth", metavar="DEPTH", default=None, type="int", dest="tree_depth", help="Print dependency tree rooted at BUG-ID with DEPTH levels of both blockers and blockees. Set DEPTH <= 0 to disable the depth limit.") @@ -158,6 +168,15 @@ If bug B is not specified, just print a list of bugs blocking (A). To search for bugs blocked by a particular bug, try $ be list --extra-strings BLOCKED-BY:<your-bug-uuid> +The --status and --severity options allow you to either blacklist or +whitelist values, for example + $ be list --status open,assigned +will only follow and print dependencies with open or assigned status. +You select blacklist mode by starting the list with a minus sign, for +example + $ be list --severity -target +which will only follow and print dependencies with non-target severity. + In repair mode, add the missing direction to any one-way links. The "|--" symbol in the repair-mode output is inspired by the @@ -239,7 +258,7 @@ def get_blocks(bugdir, bug): def get_blocked_by(bugdir, bug): """ - Return a list of bugs blocking the given bug blocks. + Return a list of bugs blocking the given bug. """ blocked_by = [] for uuid in _get_blocked_by(bug): @@ -310,10 +329,14 @@ class DependencyTree (object): """ Note: should probably be DependencyDiGraph. """ - def __init__(self, bugdir, root_bug, depth_limit=0): + def __init__(self, bugdir, root_bug, depth_limit=0, + allowed_status_values=None, + allowed_severity_values=None): self.bugdir = bugdir self.root_bug = root_bug self.depth_limit = depth_limit + self.allowed_status_values = allowed_status_values + self.allowed_severity_values = allowed_severity_values def _build_tree(self, child_fn): root = tree.Tree() root.bug = self.root_bug @@ -324,6 +347,12 @@ class DependencyTree (object): if self.depth_limit > 0 and node.depth == self.depth_limit: continue for bug in child_fn(self.bugdir, node.bug): + if self.allowed_status_values != None \ + and not bug.status in self.allowed_status_values: + continue + if self.allowed_severity_values != None \ + and not bug.severity in self.allowed_severity_values: + continue child = tree.Tree() child.bug = bug child.depth = node.depth+1 diff --git a/becommands/due.py b/becommands/due.py new file mode 100644 index 0000000..6f11ad4 --- /dev/null +++ b/becommands/due.py @@ -0,0 +1,106 @@ +# Copyright (C) 2009 W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +"""Set bug due dates""" +from libbe import cmdutil, bugdir, utility +__desc__ = __doc__ + +DUE_TAG="DUE:" + +def execute(args, manipulate_encodings=True, restrict_file_access=False): + """ + >>> import os + >>> bd = bugdir.SimpleBugDir() + >>> bd.save() + >>> os.chdir(bd.root) + >>> execute(["a"], manipulate_encodings=False) + No due date assigned. + >>> execute(["a", "Thu, 01 Jan 1970 00:00:00 +0000"], manipulate_encodings=False) + >>> execute(["a"], manipulate_encodings=False) + Thu, 01 Jan 1970 00:00:00 +0000 + >>> execute(["a", "none"], manipulate_encodings=False) # doctest: +NORMALIZE_WHITESPACE + >>> execute(["a"], manipulate_encodings=False) + No due date assigned. + >>> bd.cleanup() + """ + parser = get_parser() + options, args = parser.parse_args(args) + cmdutil.default_complete(options, args, parser, + bugid_args={0: lambda bug : bug.active==True}) + + if len(args) not in (1, 2): + raise cmdutil.UsageError('Incorrect number of arguments.') + bd = bugdir.BugDir(from_disk=True, + manipulate_encodings=manipulate_encodings) + bug = cmdutil.bug_from_id(bd, args[0]) + if len(args) == 1: + due_time = get_due(bug) + if due_time is None: + print "No due date assigned." + else: + print utility.time_to_str(due_time) + else: + if args[1] == "none": + remove_due(bug) + else: + due_time = utility.str_to_time(args[1]) + set_due(bug, due_time) + +def get_parser(): + parser = cmdutil.CmdOptionParser("be due BUG-ID [DATE]") + return parser + +longhelp=""" +If no DATE is specified, the bug's current due date is printed. If +DATE is specified, it will be assigned to the bug. +""" + +def help(): + return get_parser().help_str() + longhelp + +# internal helper functions + +def _generate_due_string(time): + return "%s%s" % (DUE_TAG, utility.time_to_str(time)) + +def _parse_due_string(string): + assert string.startswith(DUE_TAG) + return utility.str_to_time(string[len(DUE_TAG):]) + +# functions exposed to other modules + +def get_due(bug): + matched = [] + for line in bug.extra_strings: + if line.startswith(DUE_TAG): + matched.append(_parse_due_string(line)) + if len(matched) == 0: + return None + if len(matched) > 1: + raise Exception('Several due dates for %s?:\n %s' + % (bug.uuid, '\n '.join(matched))) + return matched[0] + +def remove_due(bug): + estrs = bug.extra_strings + for due_str in [s for s in estrs if s.startswith(DUE_TAG)]: + estrs.remove(due_str) + bug.extra_strings = estrs # reassign to notice change + +def set_due(bug, time): + remove_due(bug) + estrs = bug.extra_strings + estrs.append(_generate_due_string(time)) + bug.extra_strings = estrs # reassign to notice change diff --git a/becommands/html.py b/becommands/html.py index 622a531..a031188 100644 --- a/becommands/html.py +++ b/becommands/html.py @@ -177,7 +177,7 @@ class HTMLGen (object): 'shortname':self.bd.bug_shortname(bug), 'comment_entries':comment_entries, 'generation_time':self.generation_time} - for attr in ['uuid', 'severity', 'status', 'assigned', 'target', + for attr in ['uuid', 'severity', 'status', 'assigned', 'reporter', 'creator', 'time_string', 'summary']: template_info[attr] = self._escape(getattr(bug, attr)) self._write_file(self.bug_file % template_info, [fullpath]) @@ -272,7 +272,7 @@ class HTMLGen (object): if self.verbose: print "\tCreating bug entry for %s" % self.bd.bug_shortname(bug) template_info = {'shortname':self.bd.bug_shortname(bug)} - for attr in ['uuid', 'severity', 'status', 'assigned', 'target', + for attr in ['uuid', 'severity', 'status', 'assigned', 'reporter', 'creator', 'time_string', 'summary']: template_info[attr] = self._escape(getattr(bug, attr)) bug_entries.append(self.index_bug_entry % template_info) diff --git a/becommands/list.py b/becommands/list.py index 2749228..fa5647b 100644 --- a/becommands/list.py +++ b/becommands/list.py @@ -33,8 +33,7 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): >>> os.chdir(bd.root) >>> execute([], manipulate_encodings=False) a:om: Bug A - >>> execute(["--status", "all"], manipulate_encodings=False) - a:om: Bug A + >>> execute(["--status", "closed"], manipulate_encodings=False) b:cm: Bug B >>> bd.cleanup() """ @@ -60,7 +59,7 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): if options.status == "all": status = bug.status_values else: - status = options.status.split(',') + status = cmdutil.select_values(options.status, bug.status_values) else: status = [] if options.active == True: @@ -78,7 +77,8 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): if options.severity == "all": severity = bug.severity_values else: - severity = options.severity.split(',') + severity = cmdutil.select_values(options.severity, + bug.severity_values) else: severity = [] if options.wishlist == True: @@ -93,7 +93,14 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): if options.assigned == "all": assigned = "all" else: - assigned = options.assigned.split(',') + possible_assignees = [] + for _bug in bd: + if _bug.assigned != None \ + and not _bug.assigned in possible_assignees: + possible_assignees.append(_bug.assigned) + assigned = cmdutil.select_values(options.assigned, + possible_assignees) + print 'assigned', assigned else: assigned = [] if options.mine == True: @@ -103,18 +110,6 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): for i in range(len(assigned)): if assigned[i] == '-': assigned[i] = bd.user_id - # select target - if options.target != None: - if options.target == "all": - target = "all" - else: - target = options.target.split(',') - else: - target = [] - if options.cur_target == True: - target.append(bd.target) - if target == []: # set the default value - target = "all" if options.extra_strings != None: extra_string_regexps = [re.compile(x) for x in options.extra_strings.split(',')] @@ -125,8 +120,6 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): return False if assigned != "all" and not bug.assigned in assigned: return False - if target != "all" and not bug.target in target: - return False if options.extra_strings != None: if len(bug.extra_strings) == 0 and len(extra_string_regexps) > 0: return False @@ -167,29 +160,26 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): def get_parser(): parser = cmdutil.CmdOptionParser("be list [options]") - parser.add_option("-s", "--status", metavar="STATUS", dest="status", - help="List bugs matching STATUS", default=None) - parser.add_option("-v", "--severity", metavar="SEVERITY", dest="severity", - help="List bugs matching SEVERITY", default=None) + parser.add_option("--status", dest="status", metavar="STATUS", + help="Only show bugs matching the STATUS specifier") + parser.add_option("--severity", dest="severity", metavar="SEVERITY", + help="Only show bugs matching the SEVERITY specifier") parser.add_option("-a", "--assigned", metavar="ASSIGNED", dest="assigned", help="List bugs matching ASSIGNED", default=None) - parser.add_option("-t", "--target", metavar="TARGET", dest="target", - help="List bugs matching TARGET", default=None) parser.add_option("-e", "--extra-strings", metavar="STRINGS", dest="extra_strings", help="List bugs matching _all_ extra strings in comma-seperated list STRINGS. e.g. --extra-strings TAG:working,TAG:xml", default=None) parser.add_option("-S", "--sort", metavar="SORT-BY", dest="sort_by", help="Adjust bug-sort criteria with comma-separated list SORT-BY. e.g. \"--sort creator,time\". Available criteria: %s" % ','.join(AVAILABLE_CMPS), default=None) # boolean options. All but uuids and xml are special cases of long forms bools = (("u", "uuids", "Only print the bug UUIDS"), + ("x", "xml", "Dump as XML"), ("w", "wishlist", "List bugs with 'wishlist' severity"), ("i", "important", "List bugs with >= 'serious' severity"), ("A", "active", "List all active bugs"), ("U", "unconfirmed", "List unconfirmed bugs"), ("o", "open", "List open bugs"), ("T", "test", "List bugs in testing"), - ("m", "mine", "List bugs assigned to you"), - ("c", "cur-target", "List bugs for the current target"), - ("x", "xml", "Dump as XML")) + ("m", "mine", "List bugs assigned to you")) for s in bools: attr = s[1].replace('-','_') short = "-%c" % s[0] @@ -216,10 +206,12 @@ There are several criteria that you can filter by: * status * severity * assigned (who the bug is assigned to) - * target (bugfix deadline) Allowed values for each criterion may be given in a comma seperated list. The special string "all" may be used with any of these options -to match all values of the criterion. +to match all values of the criterion. As with the --status and +--severity options for `be depend`, starting the list with a minus +sign makes your selections a blacklist instead of the default +whitelist. status %s @@ -227,8 +219,6 @@ severity %s assigned free form, with the string '-' being a shortcut for yourself. -target - free form In addition, there are some shortcut options that set boolean flags. The boolean options are ignored if the matching string option is used. diff --git a/becommands/merge.py b/becommands/merge.py index 31c781f..8cf7e2f 100644 --- a/becommands/merge.py +++ b/becommands/merge.py @@ -51,7 +51,6 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): Severity : minor Status : open Assigned : - Target : Reporter : Creator : John Doe <jdoe@example.com> Created : ... @@ -96,7 +95,6 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): Severity : minor Status : closed Assigned : - Target : Reporter : Creator : Jane Doe <jdoe@example.com> Created : ... diff --git a/becommands/new.py b/becommands/new.py index 92d61e4..00e8a47 100644 --- a/becommands/new.py +++ b/becommands/new.py @@ -37,8 +37,8 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): True >>> print bug.severity minor - >>> bug.target == None - True + >>> print bug.status + open >>> bd.cleanup() """ parser = get_parser() diff --git a/becommands/show.py b/becommands/show.py index 557c63a..ab1708f 100644 --- a/becommands/show.py +++ b/becommands/show.py @@ -33,7 +33,6 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): Severity : minor Status : open Assigned : - Target : Reporter : Creator : John Doe <jdoe@example.com> Created : ... @@ -171,7 +170,7 @@ def output(ids, bd, as_xml=True, with_comments=True): if as_xml: lines.append(comment.xml(indent=4, shortname=bugname)) else: - lines.append(comment.string(shortname=shortname)) + lines.append(comment.string(shortname=bugname)) if spaces_left > 0: spaces_left -= 1 lines.append('') # add a blank line between bugs/comments diff --git a/becommands/target.py b/becommands/target.py index 9a202b1..fbc4b37 100644 --- a/becommands/target.py +++ b/becommands/target.py @@ -18,13 +18,14 @@ # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -"""Show or change a bug's target for fixing""" +"""Assorted bug target manipulations and queries""" from libbe import cmdutil, bugdir +from becommands import depend __desc__ = __doc__ def execute(args, manipulate_encodings=True, restrict_file_access=False): """ - >>> import os + >>> import os, StringIO, sys >>> bd = bugdir.SimpleBugDir() >>> os.chdir(bd.root) >>> execute(["a"], manipulate_encodings=False) @@ -32,8 +33,19 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): >>> execute(["a", "tomorrow"], manipulate_encodings=False) >>> execute(["a"], manipulate_encodings=False) tomorrow - >>> execute(["--list"], manipulate_encodings=False) + + >>> orig_stdout = sys.stdout + >>> tmp_stdout = StringIO.StringIO() + >>> sys.stdout = tmp_stdout + >>> execute(["--resolve", "tomorrow"], manipulate_encodings=False) + >>> sys.stdout = orig_stdout + >>> output = tmp_stdout.getvalue().strip() + >>> target = bd.bug_from_uuid(output) + >>> print target.summary tomorrow + >>> print target.severity + target + >>> execute(["a", "none"], manipulate_encodings=False) >>> execute(["a"], manipulate_encodings=False) No target assigned. @@ -44,52 +56,109 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): cmdutil.default_complete(options, args, parser, bugid_args={0: lambda bug : bug.active==True}) - if len(args) not in (1, 2): - if not (options.list == True and len(args) == 0): - raise cmdutil.UsageError + if (options.resolve == False and len(args) not in (1, 2)) \ + or (options.resolve == True and len(args) not in (0, 1)): + raise cmdutil.UsageError('Incorrect number of arguments.') bd = bugdir.BugDir(from_disk=True, manipulate_encodings=manipulate_encodings) - if options.list: - ts = set([bd.bug_from_uuid(bug).target for bug in bd.uuids()]) - for target in sorted(ts): - if target and isinstance(target,str): - print target + if options.resolve == True: + if len(args) == 0: + summary = None + else: + summary = args[0] + bug = bug_from_target_summary(bd, summary) + if bug == None: + print 'No target assigned.' + else: + print bug.uuid return bug = cmdutil.bug_from_id(bd, args[0]) if len(args) == 1: - if bug.target is None: + target = bug_target(bd, bug) + if target is None: print "No target assigned." else: - print bug.target + print target.summary else: - assert len(args) == 2 if args[1] == "none": - bug.target = None + target = remove_target(bd, bug) else: - bug.target = args[1] + target = add_target(bd, bug, args[1]) def get_parser(): - parser = cmdutil.CmdOptionParser("be target BUG-ID [TARGET]\nor: be target --list") - parser.add_option("-l", "--list", action="store_true", dest="list", - help="List all available targets and exit") + parser = cmdutil.CmdOptionParser("be target BUG-ID [TARGET]\nor: be target --resolve [TARGET]") + parser.add_option("-r", "--resolve", action="store_true", dest="resolve", + help="Print the UUID for the target bug whose summary matches TARGET. If TARGET is not given, print the UUID of the current bugdir target. If that is not set, don't print anything.", + default=False) return parser longhelp=""" -Show or change a bug's target for fixing. +Assorted bug target manipulations and queries. + +If no target is specified, the bug's current target is printed. If +TARGET is specified, it will be assigned to the bug, creating a new +target bug if necessary. -If no target is specified, the current value is printed. If a target -is specified, it will be assigned to the bug. +Targets are free-form; any text may be specified. They will generally +be milestone names or release numbers. The value "none" can be used +to unset the target. -Targets are freeform; any text may be specified. They will generally be -milestone names or release numbers. +In the alternative `be target --resolve TARGET` form, print the UUID +of the target-bug with summary TARGET. If target is not given, return +use the bugdir's current target (see `be set`). -The value "none" can be used to unset the target. +If you want to list all bugs blocking the current target, try + $ be depend --status -closed,fixed,wontfix --severity -target \ + $(be target --resolve) -In the alternative `be target --list` form print a list of all -currently specified targets. Note that bug status -(i.e. opened/closed) is ignored. If you want to list all bugs -matching a current target, see `be list --target TARGET'. +If you want to set the current bugdir target by summary (rather than +by UUID), try + $ be set target $(be target --resolve SUMMARY) """ def help(): return get_parser().help_str() + longhelp + +def bug_from_target_summary(bugdir, summary=None): + if summary == None: + if bugdir.target == None: + return None + else: + return bugdir.bug_from_uuid(bugdir.target) + matched = [] + for uuid in bugdir.uuids(): + bug = bugdir.bug_from_uuid(uuid) + if bug.severity == 'target' and bug.summary == summary: + matched.append(bug) + if len(matched) == 0: + return None + if len(matched) > 1: + raise Exception('Several targets with same summary: %s' + % '\n '.join([bug.uuid for bug in matched])) + return matched[0] + +def bug_target(bugdir, bug): + matched = [] + for blocked in depend.get_blocks(bugdir, bug): + if blocked.severity == 'target': + matched.append(blocked) + if len(matched) == 0: + return None + if len(matched) > 1: + raise Exception('This bug (%s) blocks several targets: %s' + % (bug.uuid, + '\n '.join([b.uuid for b in matched]))) + return matched[0] + +def remove_target(bugdir, bug): + target = bug_target(bugdir, bug) + depend.remove_block(target, bug) + return target + +def add_target(bugdir, bug, summary): + target = bug_from_target_summary(bugdir, summary) + if target == None: + target = bugdir.new_bug(summary=summary) + target.severity = 'target' + depend.add_block(target, bug) + return target diff --git a/interfaces/web/Bugs-Everywhere-Web/server.log b/interfaces/web/Bugs-Everywhere-Web/server.log deleted file mode 100644 index fe02ade..0000000 --- a/interfaces/web/Bugs-Everywhere-Web/server.log +++ /dev/null @@ -1,26 +0,0 @@ -2005/12/01 15:44:05 CONFIG INFO Server parameters: -2005/12/01 15:44:05 CONFIG INFO server.environment: production -2005/12/01 15:44:05 CONFIG INFO server.logToScreen: False -2005/12/01 15:44:05 CONFIG INFO server.logFile: server.log -2005/12/01 15:44:05 CONFIG INFO server.protocolVersion: HTTP/1.0 -2005/12/01 15:44:05 CONFIG INFO server.socketHost: -2005/12/01 15:44:05 CONFIG INFO server.socketPort: 8080 -2005/12/01 15:44:05 CONFIG INFO server.socketFile: -2005/12/01 15:44:05 CONFIG INFO server.reverseDNS: False -2005/12/01 15:44:05 CONFIG INFO server.socketQueueSize: 5 -2005/12/01 15:44:05 CONFIG INFO server.threadPool: 0 -2005/12/01 15:44:05 HTTP INFO Serving HTTP on http://localhost:8080/ -2005/12/01 15:44:17 HTTP INFO 127.0.0.1 - GET / HTTP/1.1 -2005/12/01 15:44:37 HTTP INFO 192.168.2.12 - GET / HTTP/1.1 -2005/12/01 15:44:42 HTTP INFO 192.168.2.12 - GET /be HTTP/1.1 -2005/12/01 15:44:43 HTTP INFO 192.168.2.12 - GET /be/301724b1-3853-4aff-8f23-44373df7cf1c HTTP/1.1 -2005/12/01 15:44:48 HTTP INFO 192.168.2.12 - GET /be/ HTTP/1.1 -2005/12/01 15:44:50 HTTP INFO 192.168.2.12 - GET / HTTP/1.1 -2005/12/01 15:44:53 HTTP INFO 192.168.2.12 - GET /devel/ HTTP/1.1 -2005/12/01 15:44:58 HTTP INFO 192.168.2.12 - GET / HTTP/1.1 -2005/12/01 15:52:57 HTTP INFO 127.0.0.1 - GET /devel HTTP/1.1 -2005/12/01 15:52:59 HTTP INFO 127.0.0.1 - GET /devel HTTP/1.1 -2005/12/01 15:53:25 HTTP INFO 127.0.0.1 - GET /devel HTTP/1.1 -2005/12/01 15:53:29 HTTP INFO <Ctrl-C> hit: shutting down server -2005/12/01 15:53:29 HTTP INFO HTTP Server shut down -2005/12/01 15:53:29 HTTP INFO CherryPy shut down diff --git a/interfaces/xml/be-xml-to-mbox b/interfaces/xml/be-xml-to-mbox index dc4524e..ef7b714 100755 --- a/interfaces/xml/be-xml-to-mbox +++ b/interfaces/xml/be-xml-to-mbox @@ -78,7 +78,6 @@ class Bug (LimitedAttrDict): u"severity", u"status", u"assigned", - u"target", u"reporter", u"creator", u"created", diff --git a/libbe/bug.py b/libbe/bug.py index 1a190c3..06c2cc5 100644 --- a/libbe/bug.py +++ b/libbe/bug.py @@ -56,6 +56,7 @@ class DiskAccessRequired (Exception): # in order of increasing severity. (name, description) pairs severity_def = ( + ("target", "The issue is a target or milestone, not a bug."), ("wishlist","A feature that could improve usefulness, but not a bug."), ("minor","The standard bug level."), ("serious","A bug that requires workarounds."), @@ -173,10 +174,6 @@ class Bug(settings_object.SavedSettingsObject): def active(self): return self.status in active_status_values - @_versioned_property(name="target", - doc="The deadline for fixing this bug") - def target(): return {} - @_versioned_property(name="creator", doc="The user who entered the bug into the system") def creator(): return {} @@ -295,7 +292,6 @@ class Bug(settings_object.SavedSettingsObject): ('severity', self.severity), ('status', self.status), ('assigned', self.assigned), - ('target', self.target), ('reporter', self.reporter), ('creator', self.creator), ('created', timestring), @@ -352,7 +348,7 @@ class Bug(settings_object.SavedSettingsObject): if bug.tag != 'bug': raise utility.InvalidXML( \ 'bug', bug, 'root element must be <comment>') - tags=['uuid','short-name','severity','status','assigned','target', + tags=['uuid','short-name','severity','status','assigned', 'reporter', 'creator','created','summary','extra-string'] self.explicit_attrs = [] uuid = None @@ -620,7 +616,6 @@ class Bug(settings_object.SavedSettingsObject): ("Severity", self.severity), ("Status", self.status), ("Assigned", self._setting_attr_string("assigned")), - ("Target", self._setting_attr_string("target")), ("Reporter", self._setting_attr_string("reporter")), ("Creator", self._setting_attr_string("creator")), ("Created", timestring)] @@ -820,7 +815,6 @@ def cmp_attr(bug_1, bug_2, attr, invert=False): cmp_uuid = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "uuid") cmp_creator = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "creator") cmp_assigned = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "assigned") -cmp_target = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "target") cmp_reporter = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "reporter") cmp_summary = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "summary") cmp_extra_strings = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "extra_strings") @@ -846,8 +840,7 @@ def cmp_comments(bug_1, bug_2): DEFAULT_CMP_FULL_CMP_LIST = \ (cmp_status, cmp_severity, cmp_assigned, cmp_time, cmp_creator, - cmp_reporter, cmp_target, cmp_comments, cmp_summary, cmp_uuid, - cmp_extra_strings) + cmp_reporter, cmp_comments, cmp_summary, cmp_uuid, cmp_extra_strings) class BugCompoundComparator (object): def __init__(self, cmp_list=DEFAULT_CMP_FULL_CMP_LIST): diff --git a/libbe/cmdutil.py b/libbe/cmdutil.py index dcd4ca9..b892bde 100644 --- a/libbe/cmdutil.py +++ b/libbe/cmdutil.py @@ -217,6 +217,59 @@ def underlined(instring): return "%s\n%s" % (instring, "="*len(instring)) +def select_values(string, possible_values, name="unkown"): + """ + This function allows the user to select values from a list of + possible values. The default is to select all the values: + + >>> select_values(None, ['abc', 'def', 'hij']) + ['abc', 'def', 'hij'] + + The user selects values with a comma-separated limit_string. + Prepending a minus sign to such a list denotes blacklist mode: + + >>> select_values('-abc,hij', ['abc', 'def', 'hij']) + ['def'] + + Without the leading -, the selection is in whitelist mode: + + >>> select_values('abc,hij', ['abc', 'def', 'hij']) + ['abc', 'hij'] + + In either case, appropriate errors are raised if on of the + user-values is not in the list of possible values. The name + parameter lets you make the error message more clear: + + >>> select_values('-xyz,hij', ['abc', 'def', 'hij'], name="foobar") + Traceback (most recent call last): + ... + UserError: Invalid foobar xyz + ['abc', 'def', 'hij'] + >>> select_values('xyz,hij', ['abc', 'def', 'hij'], name="foobar") + Traceback (most recent call last): + ... + UserError: Invalid foobar xyz + ['abc', 'def', 'hij'] + """ + possible_values = list(possible_values) # don't alter the original + if string == None: + pass + elif string.startswith('-'): + blacklisted_values = set(string[1:].split(',')) + for value in blacklisted_values: + if value not in possible_values: + raise UserError('Invalid %s %s\n %s' + % (name, value, possible_values)) + possible_values.remove(value) + else: + whitelisted_values = string.split(',') + for value in whitelisted_values: + if value not in possible_values: + raise UserError('Invalid %s %s\n %s' + % (name, value, possible_values)) + possible_values = whitelisted_values + return possible_values + def restrict_file_access(bugdir, path): """ Check that the file at path is inside bugdir.root. This is diff --git a/libbe/upgrade.py b/libbe/upgrade.py index 785249d..dc9d54f 100644 --- a/libbe/upgrade.py +++ b/libbe/upgrade.py @@ -22,6 +22,7 @@ import os, os.path import sys import libbe +import bug import encoding import mapfile import vcs @@ -31,7 +32,8 @@ if libbe.TESTING == True: # a list of all past versions BUGDIR_DISK_VERSIONS = ["Bugs Everywhere Tree 1 0", "Bugs Everywhere Directory v1.1", - "Bugs Everywhere Directory v1.2"] + "Bugs Everywhere Directory v1.2", + "Bugs Everywhere Directory v1.3"] # the current version BUGDIR_DISK_VERSION = BUGDIR_DISK_VERSIONS[-1] @@ -142,9 +144,63 @@ class Upgrade_1_1_to_1_2 (Upgrader): settings["vcs_name"] = settings.pop("rcs_name") mapfile.map_save(self.vcs, path, settings) +class Upgrade_1_2_to_1_3 (Upgrader): + initial_version = "Bugs Everywhere Directory v1.2" + final_version = "Bugs Everywhere Directory v1.3" + def __init__(self, *args, **kwargs): + Upgrader.__init__(self, *args, **kwargs) + self._targets = {} # key: target text,value: new target bug + path = self.get_path('settings') + settings = mapfile.map_load(self.vcs, path) + if 'vcs_name' in settings: + old_vcs = self.vcs + self.vcs = vcs.vcs_by_name(settings['vcs_name']) + self.vcs.root(self.root) + self.vcs.encoding = old_vcs.encoding + + def _target_bug(self, target_text): + if target_text not in self._targets: + _bug = bug.Bug(bugdir=self, summary=target_text) + # note: we're not a bugdir, but all Bug.save() needs is + # .root, .vcs, and .get_path(), which we have. + _bug.severity = 'target' + self._targets[target_text] = _bug + return self._targets[target_text] + + def _upgrade_bugdir_mapfile(self): + path = self.get_path('settings') + settings = mapfile.map_load(self.vcs, path) + if 'target' in settings: + settings['target'] = self._target_bug(settings['target']).uuid + mapfile.map_save(self.vcs, path, settings) + + def _upgrade_bug_mapfile(self, bug_uuid): + import becommands.depend + path = self.get_path('bugs', bug_uuid, 'values') + settings = mapfile.map_load(self.vcs, path) + if 'target' in settings: + target_bug = self._target_bug(settings['target']) + _bug = bug.Bug(bugdir=self, uuid=bug_uuid, from_disk=True) + # note: we're not a bugdir, but all Bug.load_settings() + # needs is .root, .vcs, and .get_path(), which we have. + becommands.depend.add_block(target_bug, _bug) + _bug.settings.pop('target') + _bug.save() + + def _upgrade(self): + """ + Bug value field "target" -> target bugs. + Bugdir value field "target" -> pointer to current target bug. + """ + for bug_uuid in os.listdir(self.get_path('bugs')): + self._upgrade_bug_mapfile(bug_uuid) + self._upgrade_bugdir_mapfile() + for _bug in self._targets.values(): + _bug.save() upgraders = [Upgrade_1_0_to_1_1, - Upgrade_1_1_to_1_2] + Upgrade_1_1_to_1_2, + Upgrade_1_2_to_1_3] upgrade_classes = {} for upgrader in upgraders: upgrade_classes[(upgrader.initial_version,upgrader.final_version)]=upgrader |