diff options
author | Aaron Bentley <abentley@panoramicfeedback.com> | 2006-04-06 12:54:06 -0400 |
---|---|---|
committer | Aaron Bentley <abentley@panoramicfeedback.com> | 2006-04-06 12:54:06 -0400 |
commit | 1d8631af1d0db514642b9a8f9411559abd73d060 (patch) | |
tree | b7a90513b6d9dc954c7430d18c6a2baa6263758a | |
parent | fe4ccf48aa2a2f4f085e2fa828cffa7795db594f (diff) | |
parent | e762576b97dc1c7ccbb7b0d07b94d9d42ec36b9d (diff) | |
download | bugseverywhere-1d8631af1d0db514642b9a8f9411559abd73d060.tar.gz |
Merge from upstream
30 files changed, 417 insertions, 82 deletions
diff --git a/.be/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/b8bbd433-9017-4c04-a038-2a7370a3adc7/body b/.be/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/b8bbd433-9017-4c04-a038-2a7370a3adc7/body new file mode 100644 index 0000000..8fd0355 --- /dev/null +++ b/.be/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/b8bbd433-9017-4c04-a038-2a7370a3adc7/body @@ -0,0 +1,2 @@ +Target takes no options, so it does no parsing. Bad. We should probably +use a framework more like bzr's. In any case, EVERY command should accept -h. diff --git a/.be/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/b8bbd433-9017-4c04-a038-2a7370a3adc7/values b/.be/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/b8bbd433-9017-4c04-a038-2a7370a3adc7/values new file mode 100644 index 0000000..5e923f7 --- /dev/null +++ b/.be/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/b8bbd433-9017-4c04-a038-2a7370a3adc7/values @@ -0,0 +1,14 @@ + + + +Date=Sat, 01 Apr 2006 18:32:47 +0000 + + + + + + +From=abentley + + + diff --git a/.be/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/values b/.be/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/values new file mode 100644 index 0000000..3b96b7b --- /dev/null +++ b/.be/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/values @@ -0,0 +1,35 @@ + + + +creator=abentley + + + + + + +severity=minor + + + + + + +status=open + + + + + + +summary=target (and others?) aren't parsed properly + + + + + + +time=Sat, 01 Apr 2006 18:29:33 +0000 + + + diff --git a/.be/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/37650981-1908-4c39-bae2-48e69c771120/body b/.be/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/37650981-1908-4c39-bae2-48e69c771120/body new file mode 100644 index 0000000..a27ff59 --- /dev/null +++ b/.be/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/37650981-1908-4c39-bae2-48e69c771120/body @@ -0,0 +1 @@ +Hmm. This is already done... diff --git a/.be/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/37650981-1908-4c39-bae2-48e69c771120/values b/.be/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/37650981-1908-4c39-bae2-48e69c771120/values new file mode 100644 index 0000000..a282359 --- /dev/null +++ b/.be/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/37650981-1908-4c39-bae2-48e69c771120/values @@ -0,0 +1,14 @@ + + + +Date=Fri, 31 Mar 2006 22:15:09 +0000 + + + + + + +From=abentley + + + diff --git a/.be/bugs/cb56c990-a757-4aef-9888-a30918a7b3d7/values b/.be/bugs/cb56c990-a757-4aef-9888-a30918a7b3d7/values index 4e346e7..790c8c6 100644 --- a/.be/bugs/cb56c990-a757-4aef-9888-a30918a7b3d7/values +++ b/.be/bugs/cb56c990-a757-4aef-9888-a30918a7b3d7/values @@ -15,7 +15,7 @@ severity=minor -status=open +status=closed diff --git a/.be/bugs/e0155831-499f-421a-ad02-cd15fc3fecf1/values b/.be/bugs/e0155831-499f-421a-ad02-cd15fc3fecf1/values index 754904a..6233efd 100644 --- a/.be/bugs/e0155831-499f-421a-ad02-cd15fc3fecf1/values +++ b/.be/bugs/e0155831-499f-421a-ad02-cd15fc3fecf1/values @@ -15,7 +15,7 @@ severity=minor -status=in-progress +status=closed @@ -1,3 +1,4 @@ Bugs Everywhere was written by: Aaron Bentley Oleg Romanyshyn +Thomas Gerigk @@ -1,2 +1,16 @@ +April 3, 2006 + * Handle replying to comments + * Better help handling (Thomas Gerigk) + +March 3, 2006 + * Better bzr compatibility + * Auto-commit support + +Jan 30, 2006 + * Creator support (Alexander Belchenko) + +Jan 26, 2006 + * Unicode support + December 3, 2005 -* Added new "webbe" web interface +* Added new "beweb" web interface @@ -47,15 +47,8 @@ Unimplemented becommands -if len(sys.argv) == 1 or sys.argv[1] in ('help', '--help', '-h'): - cmdlist = [] - print """Bugs Everywhere - Distributed bug tracking - -Supported commands""" - for name, module in cmdutil.iter_commands(): - cmdlist.append((name, module.__doc__)) - for name, desc in cmdlist: - print "be %s\n %s" % (name, desc) +if len(sys.argv) == 1 or sys.argv[1] in ('--help', '-h'): + print_command_list() else: try: try: diff --git a/becommands/assign.py b/becommands/assign.py index d682130..d7c2fca 100644 --- a/becommands/assign.py +++ b/becommands/assign.py @@ -26,17 +26,18 @@ def execute(args): >>> os.chdir(dir.dir) >>> dir.get_bug("a").assigned is None True - >>> execute(("a",)) + >>> execute(["a",]) >>> dir.get_bug("a").assigned == names.creator() True - >>> execute(("a", "someone")) + >>> execute(["a", "someone"]) >>> dir.get_bug("a").assigned - 'someone' - >>> execute(("a","none")) + u'someone' + >>> execute(["a","none"]) >>> dir.get_bug("a").assigned is None True >>> tests.clean_up() """ + options, args = get_parser().parse_args(args) assert(len(args) in (0, 1, 2)) if len(args) == 0: print help() @@ -51,10 +52,11 @@ def execute(args): bug.assigned = args[1] bug.save() +def get_parser(): + parser = cmdutil.CmdOptionParser("be assign bug-id [assignee]") + return parser -def help(): - return """be assign bug-id [assignee] - +longhelp = """ Assign a person to fix a bug. By default, the bug is self-assigned. If an assignee is specified, the bug @@ -65,3 +67,6 @@ appears in Creator fields. To un-assign a bug, specify "none" for the assignee. """ + +def help(): + return get_parser().help_str() + longhelp diff --git a/becommands/close.py b/becommands/close.py index 9d01a7f..2b28055 100644 --- a/becommands/close.py +++ b/becommands/close.py @@ -23,13 +23,25 @@ def execute(args): >>> dir = tests.simple_bug_dir() >>> os.chdir(dir.dir) >>> dir.get_bug("a").status - 'open' - >>> execute(("a",)) + u'open' + >>> execute(["a",]) >>> dir.get_bug("a").status - 'closed' + u'closed' >>> tests.clean_up() """ + options, args = get_parser().parse_args(args) assert(len(args) == 1) bug = cmdutil.get_bug(args[0]) bug.status = "closed" bug.save() + +def get_parser(): + parser = cmdutil.CmdOptionParser("be close bug-id") + return parser + +longhelp=""" +Close the bug identified by bug-id. +""" + +def help(): + return get_parser().help_str() + longhelp diff --git a/becommands/comment.py b/becommands/comment.py index c53fd87..4f0bf3b 100644 --- a/becommands/comment.py +++ b/becommands/comment.py @@ -26,7 +26,7 @@ def execute(args): >>> execute(["a", "This is a comment about a"]) >>> comment = dir.get_bug("a").list_comments()[0] >>> comment.body - 'This is a comment about a\\n' + u'This is a comment about a\\n' >>> comment.From == names.creator() True >>> comment.date <= int(time.time()) @@ -40,13 +40,13 @@ def execute(args): >>> os.environ["EDITOR"] = "echo 'I like cheese' > " >>> execute(["b"]) >>> dir.get_bug("b").list_comments()[0].body - 'I like cheese\\n' + u'I like cheese\\n' >>> tests.clean_up() """ options, args = get_parser().parse_args(args) if len(args) < 1: raise cmdutil.UsageError() - bug = cmdutil.get_bug(args[0]) + bug, parent_comment = cmdutil.get_bug_and_comment(args[0]) if len(args) == 1: try: body = utility.editor_string() @@ -61,15 +61,21 @@ def execute(args): body+='\n' comment = bugdir.new_comment(bug, body) + if parent_comment is not None: + comment.in_reply_to = parent_comment.uuid comment.save() def get_parser(): - parser = cmdutil.CmdOptionParser("be comment BUG-ID COMMENT") + parser = cmdutil.CmdOptionParser("be comment ID COMMENT") return parser longhelp=""" -Add a comment to a bug. +To add a comment to a bug, use the bug ID as the argument. To reply to another +comment, specify the comment name (as shown in "be show" output). + +$EDITOR is used to launch an editor. If unspecified, no comment will be +created.) """ def help(): diff --git a/becommands/help.py b/becommands/help.py new file mode 100644 index 0000000..1402a2a --- /dev/null +++ b/becommands/help.py @@ -0,0 +1,46 @@ +# Copyright (C) 2006 Thomas Gerigk +# <tgerigk@gmx.de> +# +# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +"""Print help for given subcommand""" +from libbe import bugdir, cmdutil, names, utility +def execute(args): + """ + Print help of specified command. + """ + options, args = get_parser().parse_args(args) + if len(args) > 1: + raise cmdutil.UserError("Too many arguments.") + if len(args) == 0: + print_command_list() + else: + try: + print cmdutil.help(args[0]) + except AttributeError: + print "No help available" + + return + + +def get_parser(): + parser = cmdutil.CmdOptionParser("be help [COMMAND]") + return parser + +longhelp=""" +Print help for specified command or list of all commands. +""" + +def help(): + return get_parser().help_str() + longhelp diff --git a/becommands/inprogress.py b/becommands/inprogress.py index 5b9f767..005bdbc 100644 --- a/becommands/inprogress.py +++ b/becommands/inprogress.py @@ -23,13 +23,25 @@ def execute(args): >>> dir = tests.simple_bug_dir() >>> os.chdir(dir.dir) >>> dir.get_bug("a").status - 'open' - >>> execute(("a",)) + u'open' + >>> execute(["a",]) >>> dir.get_bug("a").status - 'in-progress' + u'in-progress' >>> tests.clean_up() """ + options, args = get_parser().parse_args(args) assert(len(args) == 1) bug = cmdutil.get_bug(args[0]) bug.status = "in-progress" bug.save() + +def get_parser(): + parser = cmdutil.CmdOptionParser("be inprogress BUG-ID") + return parser + +longhelp=""" +Mark a bug as 'in-progress'. +""" + +def help(): + return get_parser().help_str() + longhelp diff --git a/becommands/new.py b/becommands/new.py index f16306d..7bd2382 100644 --- a/becommands/new.py +++ b/becommands/new.py @@ -27,12 +27,12 @@ def execute(args): Created bug with ID a >>> bug = list(dir.list())[0] >>> bug.summary - 'this is a test' + u'this is a test' >>> bug.creator = os.environ["LOGNAME"] >>> bug.time <= int(time.time()) True >>> bug.severity - 'minor' + u'minor' >>> bug.target == None True >>> tests.clean_up() diff --git a/becommands/open.py b/becommands/open.py index 7923091..d93eb61 100644 --- a/becommands/open.py +++ b/becommands/open.py @@ -23,13 +23,25 @@ def execute(args): >>> dir = tests.simple_bug_dir() >>> os.chdir(dir.dir) >>> dir.get_bug("b").status - 'closed' - >>> execute(("b",)) + u'closed' + >>> execute(["b",]) >>> dir.get_bug("b").status - 'open' + u'open' >>> tests.clean_up() """ + options, args = get_parser().parse_args(args) assert(len(args) == 1) bug = cmdutil.get_bug(args[0]) bug.status = "open" bug.save() + +def get_parser(): + parser = cmdutil.CmdOptionParser("be open BUG-ID") + return parser + +longhelp=""" +Mark a bug as 'open'. +""" + +def help(): + return get_parser().help_str() + longhelp diff --git a/becommands/set.py b/becommands/set.py index 2a977ca..a93cbf3 100644 --- a/becommands/set.py +++ b/becommands/set.py @@ -22,16 +22,17 @@ def execute(args): >>> import os >>> dir = tests.simple_bug_dir() >>> os.chdir(dir.dir) - >>> execute(("a",)) + >>> execute(["a",]) None - >>> execute(("a", "tomorrow")) - >>> execute(("a",)) + >>> execute(["a", "tomorrow"]) + >>> execute(["a",]) tomorrow - >>> execute(("a", "none")) - >>> execute(("a",)) + >>> execute(["a", "none"]) + >>> execute(["a",]) None >>> tests.clean_up() """ + options, args = get_parser().parse_args(args) if len(args) > 2: raise cmdutil.UserError("Too many arguments.") tree = cmdutil.bug_tree() @@ -49,9 +50,11 @@ def execute(args): del tree.settings[args[0]] tree.save_settings() -def help(): - return """be set [name] [value] +def get_parser(): + parser = cmdutil.CmdOptionParser("be set [name] [value]") + return parser +longhelp=""" Show or change per-tree settings. If name and value are supplied, the name is set to a new value. @@ -66,3 +69,6 @@ target To unset a setting, set it to "none". """ + +def help(): + return get_parser().help_str() + longhelp diff --git a/becommands/severity.py b/becommands/severity.py index 88d3f25..92c83fc 100644 --- a/becommands/severity.py +++ b/becommands/severity.py @@ -25,16 +25,17 @@ def execute(args): >>> import os >>> dir = tests.simple_bug_dir() >>> os.chdir(dir.dir) - >>> execute(("a",)) + >>> execute(["a",]) minor - >>> execute(("a", "wishlist")) - >>> execute(("a",)) + >>> execute(["a", "wishlist"]) + >>> execute(["a",]) wishlist - >>> execute(("a", "none")) + >>> execute(["a", "none"]) Traceback (most recent call last): UserError: Invalid severity level: none >>> tests.clean_up() """ + options, args = get_parser().parse_args(args) assert(len(args) in (0, 1, 2)) if len(args) == 0: print help() @@ -51,10 +52,11 @@ def execute(args): raise cmdutil.UserError ("Invalid severity level: %s" % e.value) bug.save() +def get_parser(): + parser = cmdutil.CmdOptionParser("be severity bug-id [severity]") + return parser -def help(): - return """be severity bug-id [severity] - +longhelp=""" Show or change a bug's severity level. If no severity is specified, the current value is printed. If a severity level @@ -67,3 +69,6 @@ wishlist: A feature that could improve usefulness, but not a bug. critical: A bug that prevents some features from working at all. fatal: A bug that makes the package unusable. """ + +def help(): + return get_parser().help_str() + longhelp diff --git a/becommands/show.py b/becommands/show.py index 9e60586..8e83a1f 100644 --- a/becommands/show.py +++ b/becommands/show.py @@ -19,9 +19,10 @@ from libbe import bugdir, cmdutil, utility import os def execute(args): - bug_dir = cmdutil.bug_tree() + options, args = get_parser().parse_args(args) if len(args) !=1: raise cmdutil.UserError("Please specify a bug id.") + bug_dir = cmdutil.bug_tree() bug = cmdutil.get_bug(args[0], bug_dir) print cmdutil.bug_summary(bug, list(bug_dir.list())).rstrip("\n") if bug.time is None: @@ -30,8 +31,22 @@ def execute(args): time_str = "%s (%s)" % (utility.handy_time(bug.time), utility.time_to_str(bug.time)) print "Created: %s" % time_str - for comment in bug.list_comments(): - print "--------- Comment ---------" - print "From: %s" % comment.From - print "Date: %s\n" % utility.time_to_str(comment.date) - print comment.body.rstrip('\n') + unique_name = cmdutil.unique_name(bug, bug_dir.list()) + comments = [] + name_map = {} + for c_name, comment in cmdutil.iter_comment_name(bug, unique_name): + name_map[comment.uuid] = c_name + comments.append(comment) + threaded = bugdir.thread_comments(comments) + cmdutil.print_threaded_comments(threaded, name_map) + +def get_parser(): + parser = cmdutil.CmdOptionParser("be show bug-id") + return parser + +longhelp=""" +Show all information about a bug. +""" + +def help(): + return get_parser().help_str() + longhelp diff --git a/becommands/target.py b/becommands/target.py index d077da5..f872abb 100644 --- a/becommands/target.py +++ b/becommands/target.py @@ -25,16 +25,17 @@ def execute(args): >>> import os >>> dir = tests.simple_bug_dir() >>> os.chdir(dir.dir) - >>> execute(("a",)) + >>> execute(["a",]) No target assigned. - >>> execute(("a", "tomorrow")) - >>> execute(("a",)) + >>> execute(["a", "tomorrow"]) + >>> execute(["a",]) tomorrow - >>> execute(("a", "none")) - >>> execute(("a",)) + >>> execute(["a", "none"]) + >>> execute(["a",]) No target assigned. >>> tests.clean_up() """ + options, args = get_parser().parse_args(args) assert(len(args) in (0, 1, 2)) if len(args) == 0: print help() @@ -52,10 +53,11 @@ def execute(args): bug.target = args[1] bug.save() +def get_parser(): + parser = cmdutil.CmdOptionParser("be target bug-id [target]") + return parser -def help(): - return """be target bug-id [target] - +longhelp=""" Show or change a bug's target for fixing. If no target is specified, the current value is printed. If a target @@ -66,3 +68,6 @@ milestone names or release numbers. The value "none" can be used to unset the target. """ + +def help(): + return get_parser().help_str() + longhelp diff --git a/becommands/upgrade.py b/becommands/upgrade.py index 0cbffa1..3dcb4eb 100644 --- a/becommands/upgrade.py +++ b/becommands/upgrade.py @@ -17,9 +17,10 @@ """Upgrade the bugs to the latest format""" import os.path import errno -from libbe import bugdir, rcs +from libbe import bugdir, rcs, cmdutil def execute(args): + options, args = get_parser().parse_args(args) root = bugdir.tree_root(".", old_version=True) for uuid in root.list_uuids(): old_bug = OldBug(root.bugs_path, uuid) @@ -98,5 +99,13 @@ class OldBug(object): else: rcs.set_file_contents(self.get_path(name), "%s\n" % value) +def get_parser(): + parser = cmdutil.CmdOptionParser("be upgrade") + return parser +longhelp=""" +Upgrade the bug storage to the latest format. +""" +def help(): + return get_parser().help_str() + longhelp diff --git a/beweb/beweb/controllers.py b/beweb/beweb/controllers.py index 6c43ecb..74c6a7d 100644 --- a/beweb/beweb/controllers.py +++ b/beweb/beweb/controllers.py @@ -13,9 +13,9 @@ def project_tree(project): except KeyError: raise Exception("Unknown project %s" % project) -def comment_url(project, bug, comment): +def comment_url(project, bug, comment, **kwargs): return turbogears.url("/project/%s/bug/%s/comment/%s" % - (project, bug, comment)) + (project, bug, comment), kwargs) class Comment(PrestHandler): @provide_action("action", "New comment") @@ -27,6 +27,18 @@ class Comment(PrestHandler): raise cherrypy.HTTPRedirect(comment_url(comment=comment.uuid, **comment_data)) + @provide_action("action", "Reply") + def reply_comment(self, comment_data, comment, *args, **kwargs): + bug_tree = project_tree(comment_data['project']) + bug = bug_tree.get_bug(comment_data['bug']) + reply_comment = new_comment(bug, "") + reply_comment.in_reply_to = comment.uuid + reply_comment.save() + reply_data = dict(comment_data) + del reply_data["comment"] + raise cherrypy.HTTPRedirect(comment_url(comment=reply_comment.uuid, + **reply_data)) + @provide_action("action", "Update") def update(self, comment_data, comment, comment_body, *args, **kwargs): comment.body = comment_body diff --git a/beweb/beweb/static/css/style.css b/beweb/beweb/static/css/style.css index 160beff..986950f 100644 --- a/beweb/beweb/static/css/style.css +++ b/beweb/beweb/static/css/style.css @@ -98,3 +98,7 @@ pre.traceback { font-family: Verdana, Ariel, Helvetica, sanserif; } +tr.even td +{ + background-color: #eee; +} diff --git a/beweb/beweb/templates/bugs.kid b/beweb/beweb/templates/bugs.kid index d4d783c..b83a593 100644 --- a/beweb/beweb/templates/bugs.kid +++ b/beweb/beweb/templates/bugs.kid @@ -3,14 +3,16 @@ from libbe.cmdutil import unique_name from beweb.controllers import bug_url, project_url, bug_list_url from beweb.config import people -def row_class(bug): +def row_class(bug, num): if not bug.active is True: return "closed" + elif num % 2 == 0: + return "even" else: - return "" + return "odd" -def match(bug, show_closed=False, search=None): +def match(bug, show_closed, search): if not show_closed and not bug.active: return False elif search is None: @@ -30,7 +32,7 @@ def match(bug, show_closed=False, search=None): <h1>Bug list for ${project_name}</h1> <table> <tr><td>ID</td><td>Status</td><td>Severity</td><td>Assigned To</td><td>Comments</td><td>Summary</td></tr> -<div py:for="bug in bugs" py:strip="True"><tr class="${row_class(bug)}" py:if="match(bug, show_closed, search)"><td><a href="${bug_url(project_id, bug.uuid)}">${unique_name(bug, bugs[:])}</a></td><td>${bug.status}</td><td>${bug.severity}</td><td>${people.get(bug.assigned, bug.assigned)}</td><td>${len(list(bug.iter_comment_ids()))}</td><td>${bug.summary}</td></tr> +<div py:for="num, bug in enumerate([b for b in bugs if match(b, show_closed, search)])" py:strip="True"><tr class="${row_class(bug, num)}"><td><a href="${bug_url(project_id, bug.uuid)}">${unique_name(bug, bugs[:])}</a></td><td>${bug.status}</td><td>${bug.severity}</td><td>${people.get(bug.assigned, bug.assigned)}</td><td>${len(list(bug.iter_comment_ids()))}</td><td>${bug.summary}</td></tr> </div> </table> <a href="${project_url()}">Project list</a> diff --git a/beweb/beweb/templates/edit_bug.kid b/beweb/beweb/templates/edit_bug.kid index 774334e..960866d 100644 --- a/beweb/beweb/templates/edit_bug.kid +++ b/beweb/beweb/templates/edit_bug.kid @@ -1,6 +1,6 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <?python -from libbe.bugdir import severity_levels, active_status, inactive_status +from libbe.bugdir import severity_levels, active_status, inactive_status, thread_comments from libbe.utility import time_to_str from beweb.controllers import bug_list_url, comment_url from beweb.config import people @@ -67,13 +67,13 @@ def soft_pre(text): <body> <h1>Edit bug</h1> -<form method="post"> +<form method="post" action="."> <table> <tr><td>Status</td><td>Severity</td><td>Assigned To</td><td>Summary</td></tr> <tr><td>${select_among("status", active_status+inactive_status, bug.status)}</td><td>${select_among("severity", severity_levels, bug.severity)}</td> <td>${select_among("assigned", people.keys()+[None], bug.assigned, people)}</td><td><input name="summary" value="${bug.summary}" size="80" /></td></tr> </table> -<div py:for="comment in bug.list_comments()" class="comment"> +<div py:def="show_comment(comment, children)" class="comment"> <insetbox> <table> <tr><td>From</td><td>${comment.From}</td></tr> @@ -81,7 +81,18 @@ def soft_pre(text): </table> <div py:content="soft_pre(comment.body)" py:strip="True"></div> <a href="${comment_url(project_id, bug.uuid, comment.uuid)}">Edit</a> + <a href="${comment_url(project_id, bug.uuid, comment.uuid, + action='Reply')}">Reply</a> </insetbox> + <div style="margin-left:20px;"> + <div py:for="child, grandchildren in children" py:strip="True"> + ${show_comment(child, grandchildren)} + </div> + </div> +</div> +<div py:for="comment, children in thread_comments(bug.list_comments())" + py:strip="True"> + ${show_comment(comment, children)} </div> <p><input type="submit" name="action" value="Update"/></p> <p><input type="submit" name="action" value="New comment"/></p> diff --git a/libbe/bugdir.py b/libbe/bugdir.py index 766ccd9..414b47e 100644 --- a/libbe/bugdir.py +++ b/libbe/bugdir.py @@ -242,6 +242,9 @@ class Bug(object): if self.time is not None: self.time = utility.str_to_time(self.time) + def __repr__(self): + return "Bug(uuid=%r)" % self.uuid + def get_path(self, file): return os.path.join(self.path, self.uuid, file) @@ -345,16 +348,18 @@ class Comment(object): self.date = utility.str_to_time(mapfile["Date"]) self.From = mapfile["From"] self.in_reply_to = mapfile.get("In-reply-to") + self.content_type = mapfile.get("Content-type", "text/plain") self.body = file(self.get_path("body")).read().decode("utf-8") else: self.date = None self.From = None self.in_reply_to = None + self.content_type = "text/plain" self.body = None def save(self): map_file = {"Date": utility.time_to_str(self.date)} - add_headers(self, map_file, ("From", "in_reply_to")) + add_headers(self, map_file, ("From", "in_reply_to", "content_type")) if not os.path.exists(self.get_path(None)): self.bug.rcs.mkdir(self.get_path(None)) map_save(self.bug.rcs, self.get_path("values"), map_file) @@ -367,7 +372,27 @@ class Comment(object): if name is None: return my_dir return os.path.join(my_dir, name) - + + +def thread_comments(comments): + child_map = {} + top_comments = [] + for comment in comments: + child_map[comment.uuid] = [] + for comment in comments: + if comment.in_reply_to is None or comment.in_reply_to not in child_map: + top_comments.append(comment) + continue + child_map[comment.in_reply_to].append(comment) + + def recurse_children(comment): + child_list = [] + for child in child_map[comment.uuid]: + child_list.append(recurse_children(child)) + return (comment, child_list) + return [recurse_children(c) for c in top_comments] + + def pyname_to_header(name): return name.capitalize().replace('_', '-') diff --git a/libbe/cmdutil.py b/libbe/cmdutil.py index 2f24490..079601e 100644 --- a/libbe/cmdutil.py +++ b/libbe/cmdutil.py @@ -19,6 +19,8 @@ import plugin import locale import os import optparse +from textwrap import TextWrapper +from StringIO import StringIO import utility def unique_name(bug, bugs): @@ -118,6 +120,34 @@ def raise_get_help(option, opt, value, parser): raise GetHelp +def iter_comment_name(bug, unique_name): + """Iterate through id, comment pairs, in date order. + (This is a user-friendly id, not the comment uuid) + """ + def key(comment): + return comment.date + for num, comment in enumerate(sorted(bug.list_comments(), key=key)): + yield ("%s:%d" % (unique_name, num+1), comment) + + +def comment_from_name(bug, unique_name, name): + """Use a comment name to look up a comment""" + for cur_name, comment in iter_comment_name(bug, unique_name): + if name == cur_name: + return comment + raise KeyError(name) + + +def get_bug_and_comment(identifier, bug_dir=None): + ids = identifier.split(':') + bug = get_bug(ids[0], bug_dir) + if len(ids) == 2: + comment = comment_from_name(bug, ids[0], identifier) + else: + comment = None + return bug, comment + + class CmdOptionParser(optparse.OptionParser): def __init__(self, usage): optparse.OptionParser.__init__(self, usage) @@ -148,6 +178,24 @@ def underlined(instring): return "%s\n%s" % (instring, "="*len(instring)) +def print_threaded_comments(comments, name_map, indent=""): + """Print a threaded display of comments""" + tw = TextWrapper(initial_indent = indent, subsequent_indent = indent, + width=80) + for comment, children in comments: + s = StringIO() + print >> s, "--------- Comment ---------" + print >> s, "Name: %s" % name_map[comment.uuid] + print >> s, "From: %s" % comment.From + print >> s, "Date: %s\n" % utility.time_to_str(comment.date) + print >> s, comment.body.rstrip('\n') + + s.seek(0) + for line in s: + print tw.fill(line).rstrip('\n') + print_threaded_comments(children, name_map, indent=indent+" ") + + def bug_tree(dir=None): """Retrieve the bug tree specified by the user. If no directory is specified, the current working directory is used. @@ -167,6 +215,15 @@ def bug_tree(dir=None): except bugdir.NoBugDir, e: raise UserErrorWrap(e) +def print_command_list(): + cmdlist = [] + print """Bugs Everywhere - Distributed bug tracking + +Supported commands""" + for name, module in iter_commands(): + cmdlist.append((name, module.__doc__)) + for name, desc in cmdlist: + print "be %s\n %s" % (name, desc) def _test(): import doctest diff --git a/libbe/diff.py b/libbe/diff.py index 82dc219..c1dc429 100644 --- a/libbe/diff.py +++ b/libbe/diff.py @@ -16,6 +16,7 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """Compare two bug trees""" from libbe import cmdutil, bugdir +from libbe.utility import time_to_str def diff(old_tree, new_tree): old_bug_map = old_tree.bug_map() @@ -85,9 +86,25 @@ def change_lines(old, new, attributes): def bug_changes(old, new, bugs): change_list = change_lines(old, new, ("time", "creator", "severity", "target", "summary", "status", "assigned")) - if len(change_list) == 0: + + old_comment_ids = list(old.iter_comment_ids()) + new_comment_ids = list(new.iter_comment_ids()) + change_strings = ["%s: %s -> %s" % f for f in change_list] + for comment_id in new_comment_ids: + if comment_id not in old_comment_ids: + summary = comment_summary(new.get_comment(comment_id), "new") + change_strings.append(summary) + for comment_id in old_comment_ids: + if comment_id not in new_comment_ids: + summary = comment_summary(new.get_comment(comment_id), "removed") + change_strings.append(summary) + + if len(change_strings) == 0: return None return "%s%s\n" % (cmdutil.bug_summary(new, bugs, shortlist=True), - "\n".join(["%s: %s -> %s" % f for f in change_list])) + "\n".join(change_strings)) +def comment_summary(comment, status): + return "%8s comment from %s on %s" % (status, comment.From, + time_to_str(comment.date)) diff --git a/libbe/mapfile.py b/libbe/mapfile.py index bbbd860..6a304fd 100644 --- a/libbe/mapfile.py +++ b/libbe/mapfile.py @@ -75,18 +75,18 @@ def parse(f): """ Parse a format-2 mapfile. >>> parse('\\n\\n\\nq=p\\n\\n\\n\\n')['q'] - 'p' + u'p' >>> parse('\\n\\nq=\\'p\\'\\n\\n\\n\\n')['q'] - "\'p\'" + u"\'p\'" >>> f = utility.FileString() >>> generate(f, {"a":"b", "c":"d", "e":"f"}) >>> dict = parse(f) >>> dict["a"] - 'b' + u'b' >>> dict["c"] - 'd' + u'd' >>> dict["e"] - 'f' + u'f' """ f = utility.get_file(f) result = {} |