aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--NEWS16
-rw-r--r--becommands/assign.py11
-rw-r--r--becommands/close.py12
-rw-r--r--becommands/comment.py12
-rw-r--r--becommands/help.py6
-rw-r--r--becommands/inprogress.py12
-rw-r--r--becommands/open.py12
-rw-r--r--becommands/set.py10
-rw-r--r--becommands/severity.py11
-rw-r--r--becommands/show.py27
-rw-r--r--becommands/target.py11
-rw-r--r--becommands/upgrade.py11
-rw-r--r--beweb/beweb/controllers.py16
-rw-r--r--beweb/beweb/templates/edit_bug.kid17
-rw-r--r--libbe/bugdir.py22
-rw-r--r--libbe/cmdutil.py48
16 files changed, 225 insertions, 29 deletions
diff --git a/NEWS b/NEWS
index 847b015..9906398 100644
--- a/NEWS
+++ b/NEWS
@@ -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
diff --git a/becommands/assign.py b/becommands/assign.py
index f3db6aa..2308a12 100644
--- a/becommands/assign.py
+++ b/becommands/assign.py
@@ -37,6 +37,7 @@ def execute(args):
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 04ae4ba..3ced7eb 100644
--- a/becommands/close.py
+++ b/becommands/close.py
@@ -29,7 +29,19 @@ def execute(args):
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 37fd37d..4f0bf3b 100644
--- a/becommands/comment.py
+++ b/becommands/comment.py
@@ -46,7 +46,7 @@ def execute(args):
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
index cae8949..1402a2a 100644
--- a/becommands/help.py
+++ b/becommands/help.py
@@ -26,7 +26,11 @@ def execute(args):
if len(args) == 0:
print_command_list()
else:
- print cmdutil.help(args[0])
+ try:
+ print cmdutil.help(args[0])
+ except AttributeError:
+ print "No help available"
+
return
diff --git a/becommands/inprogress.py b/becommands/inprogress.py
index 214efa1..10d5cbd 100644
--- a/becommands/inprogress.py
+++ b/becommands/inprogress.py
@@ -29,7 +29,19 @@ def execute(args):
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/open.py b/becommands/open.py
index 19b8910..89067f8 100644
--- a/becommands/open.py
+++ b/becommands/open.py
@@ -29,7 +29,19 @@ def execute(args):
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..6f40b5f 100644
--- a/becommands/set.py
+++ b/becommands/set.py
@@ -32,6 +32,7 @@ def execute(args):
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..82ef7ca 100644
--- a/becommands/severity.py
+++ b/becommands/severity.py
@@ -35,6 +35,7 @@ def execute(args):
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..665efad 100644
--- a/becommands/target.py
+++ b/becommands/target.py
@@ -35,6 +35,7 @@ def execute(args):
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/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 b78ec06..b680d16 100644
--- a/libbe/bugdir.py
+++ b/libbe/bugdir.py
@@ -370,7 +370,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 b2c7f8a..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.