aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAaron Bentley <abentley@panoramicfeedback.com>2006-04-06 12:54:06 -0400
committerAaron Bentley <abentley@panoramicfeedback.com>2006-04-06 12:54:06 -0400
commit1d8631af1d0db514642b9a8f9411559abd73d060 (patch)
treeb7a90513b6d9dc954c7430d18c6a2baa6263758a
parentfe4ccf48aa2a2f4f085e2fa828cffa7795db594f (diff)
parente762576b97dc1c7ccbb7b0d07b94d9d42ec36b9d (diff)
downloadbugseverywhere-1d8631af1d0db514642b9a8f9411559abd73d060.tar.gz
Merge from upstream
-rw-r--r--.be/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/b8bbd433-9017-4c04-a038-2a7370a3adc7/body2
-rw-r--r--.be/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/comments/b8bbd433-9017-4c04-a038-2a7370a3adc7/values14
-rw-r--r--.be/bugs/2103f60c-36e5-4b05-b57c-8c6fee2d80d4/values35
-rw-r--r--.be/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/37650981-1908-4c39-bae2-48e69c771120/body1
-rw-r--r--.be/bugs/9a942b1d-a3b5-441d-8aef-b844700e1efa/comments/37650981-1908-4c39-bae2-48e69c771120/values14
-rw-r--r--.be/bugs/cb56c990-a757-4aef-9888-a30918a7b3d7/values2
-rw-r--r--.be/bugs/e0155831-499f-421a-ad02-cd15fc3fecf1/values2
-rw-r--r--AUTHORS1
-rw-r--r--NEWS16
-rwxr-xr-xbe11
-rw-r--r--becommands/assign.py19
-rw-r--r--becommands/close.py18
-rw-r--r--becommands/comment.py16
-rw-r--r--becommands/help.py46
-rw-r--r--becommands/inprogress.py18
-rw-r--r--becommands/new.py4
-rw-r--r--becommands/open.py18
-rw-r--r--becommands/set.py20
-rw-r--r--becommands/severity.py19
-rw-r--r--becommands/show.py27
-rw-r--r--becommands/target.py21
-rw-r--r--becommands/upgrade.py11
-rw-r--r--beweb/beweb/controllers.py16
-rw-r--r--beweb/beweb/static/css/style.css4
-rw-r--r--beweb/beweb/templates/bugs.kid10
-rw-r--r--beweb/beweb/templates/edit_bug.kid17
-rw-r--r--libbe/bugdir.py29
-rw-r--r--libbe/cmdutil.py57
-rw-r--r--libbe/diff.py21
-rw-r--r--libbe/mapfile.py10
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
diff --git a/AUTHORS b/AUTHORS
index 24ccd79..4e9413f 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,3 +1,4 @@
Bugs Everywhere was written by:
Aaron Bentley
Oleg Romanyshyn
+Thomas Gerigk
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/be b/be
index cfcfeed..8ad6a98 100755
--- a/be
+++ b/be
@@ -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 = {}