From e6d48051cbce1f74ec3c50fc0ccb72003258889e Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 24 Nov 2008 21:20:56 -0500 Subject: Added becommands/merge to join duplicate bugs. --- README.dev | 6 ++ becommands/merge.py | 157 ++++++++++++++++++++++++++++++++++++++++++++++++++++ libbe/bug.py | 37 +++++++++---- libbe/comment.py | 19 +++++-- test_usage.sh | 8 ++- 5 files changed, 209 insertions(+), 18 deletions(-) create mode 100644 becommands/merge.py diff --git a/README.dev b/README.dev index bb39ba5..4cbf554 100644 --- a/README.dev +++ b/README.dev @@ -26,3 +26,9 @@ consistent interface alter the parser (e.g. add some more options) before returning it. Again, you can just browse around in becommands to get a feel for things. + +Run any doctests in your plugin with + be$ python test.py +for example + be$ python test.py merge + diff --git a/becommands/merge.py b/becommands/merge.py new file mode 100644 index 0000000..b079f2c --- /dev/null +++ b/becommands/merge.py @@ -0,0 +1,157 @@ +# Copyright (C) 2005 Aaron Bentley and Panometrics, Inc. +# +# +# 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 +"""Merge duplicate bugs""" +from libbe import cmdutil, bugdir +import os, copy +__desc__ = __doc__ + +def execute(args): + """ + >>> from libbe import utility + >>> bd = bugdir.simple_bug_dir() + >>> a = bd.bug_from_shortname("a") + >>> a.comment_root.time = 0 + >>> dummy = a.new_comment("Testing") + >>> dummy.time = 1 + >>> dummy = dummy.new_reply("Testing...") + >>> dummy.time = 2 + >>> b = bd.bug_from_shortname("b") + >>> b.status = "open" + >>> b.comment_root.time = 0 + >>> dummy = b.new_comment("1 2") + >>> dummy.time = 1 + >>> dummy = dummy.new_reply("1 2 3 4") + >>> dummy.time = 2 + >>> bd.save() + >>> os.chdir(bd.root) + >>> execute(["a", "b"]) + Merging bugs a and b + >>> bd._clear_bugs() + >>> a = bd.bug_from_shortname("a") + >>> a.load_comments() + >>> mergeA = a.comment_from_shortname(":3") + >>> mergeA.time = 3 + >>> print a.string(show_comments=True) + ID : a + Short name : a + Severity : minor + Status : open + Assigned : + Target : + Creator : John Doe + Created : Wed, 31 Dec 1969 19:00 (Thu, 01 Jan 1970 00:00:00 +0000) + Bug A + --------- Comment --------- + Name: a:1 + From: wking + Date: Thu, 01 Jan 1970 00:00:01 +0000 + + Testing + --------- Comment --------- + Name: a:2 + From: wking + Date: Thu, 01 Jan 1970 00:00:02 +0000 + + Testing... + --------- Comment --------- + Name: a:3 + From: wking + Date: Thu, 01 Jan 1970 00:00:03 +0000 + + Merged from bug b + --------- Comment --------- + Name: a:4 + From: wking + Date: Thu, 01 Jan 1970 00:00:01 +0000 + + 1 2 + --------- Comment --------- + Name: a:5 + From: wking + Date: Thu, 01 Jan 1970 00:00:02 +0000 + + 1 2 3 4 + >>> b = bd.bug_from_shortname("b") + >>> b.load_comments() + >>> mergeB = b.comment_from_shortname(":3") + >>> mergeB.time = 3 + >>> print b.string(show_comments=True) + ID : b + Short name : b + Severity : minor + Status : closed + Assigned : + Target : + Creator : Jane Doe + Created : Wed, 31 Dec 1969 19:00 (Thu, 01 Jan 1970 00:00:00 +0000) + Bug B + --------- Comment --------- + Name: b:1 + From: wking + Date: Thu, 01 Jan 1970 00:00:01 +0000 + + 1 2 + --------- Comment --------- + Name: b:2 + From: wking + Date: Thu, 01 Jan 1970 00:00:02 +0000 + + 1 2 3 4 + --------- Comment --------- + Name: b:3 + From: wking + Date: Thu, 01 Jan 1970 00:00:03 +0000 + + Merged into bug a + >>> print b.status + closed + """ + options, args = get_parser().parse_args(args) + if len(args) < 2: + raise cmdutil.UserError("Please two bug ids.") + if len(args) > 2: + help() + raise cmdutil.UserError("Too many arguments.") + + bd = bugdir.BugDir(from_disk=True) + bugA = bd.bug_from_shortname(args[0]) + bugA.load_comments() + bugB = bd.bug_from_shortname(args[1]) + bugB.load_comments() + mergeA = bugA.new_comment("Merged from bug %s" % bugB.uuid) + newCommTree = copy.deepcopy(bugB.comment_root) + for comment in newCommTree.traverse(): + comment.bug = bugA + for comment in newCommTree: + mergeA.add_reply(comment, allow_time_inversion=True) + bugB.new_comment("Merged into bug %s" % bugA.uuid) + bugB.status = "closed" + bd.save() + print "Merging bugs %s and %s" % (bugA.uuid, bugB.uuid) + +def get_parser(): + parser = cmdutil.CmdOptionParser("be merge BUG-ID BUG-ID") + return parser + +longhelp=""" +The second bug (B) is merged into the first (A). This adds merge +comments to both bugs, closes B, and appends B's comment tree to A's +merge comment. +""" + +def help(): + return get_parser().help_str() + longhelp diff --git a/libbe/bug.py b/libbe/bug.py index c75c968..68cff7b 100644 --- a/libbe/bug.py +++ b/libbe/bug.py @@ -95,6 +95,22 @@ class Bug(object): active = property(_get_active) + def _get_comment_root(self): + if self._comment_root == None: + if self._comments_loaded == True: + self._comment_root = comment.loadComments(self) + else: + self._comment_root = comment.Comment(self, + uuid=comment.INVALID_UUID) + return self._comment_root + + def _set_comment_root(self, comment_root): + self._comment_root = comment_root + + _comment_root = None + comment_root = property(_get_comment_root, _set_comment_root, + doc="The trunk of the comment tree") + def __init__(self, bugdir=None, uuid=None, from_disk=False, load_comments=False, summary=None): self.bugdir = bugdir @@ -123,7 +139,6 @@ class Bug(object): self.severity = "minor" self.assigned = None self.time = int(time.time()) # only save to second precision - self.comment_root = comment.Comment(self, uuid=comment.INVALID_UUID) def __repr__(self): return "Bug(uuid=%r)" % self.uuid @@ -162,7 +177,7 @@ class Bug(object): statuschar = self.status[0] severitychar = self.severity[0] chars = "%c%c" % (statuschar, severitychar) - bugout = "%s:%s: %s" % (shortname, chars, self.summary.rstrip('\n')) + bugout = "%s:%s: %s" % (shortname,chars,self.summary.rstrip('\n')) if show_comments == True: if self._comments_loaded == False: @@ -203,9 +218,10 @@ class Bug(object): self.load_comments() def load_comments(self): - self.comment_root = comment.loadComments(self) + # clear _comment_root, so _get_comment_root returns a fresh version + self._comment_root = None self._comments_loaded = True - + def comments(self): if self._comments_loaded == False: self.load_comments() @@ -233,23 +249,22 @@ class Bug(object): path = self.get_path("values") mapfile.map_save(self.rcs, path, map) - if self._comments_loaded: - if len(self.comment_root) > 0: - self.rcs.mkdir(self.get_path("comments")) - comment.saveComments(self) + if len(self.comment_root) > 0: + self.rcs.mkdir(self.get_path("comments")) + comment.saveComments(self) def remove(self): - self.load_comments() self.comment_root.remove() path = self.get_path() self.rcs.recursive_remove(path) def new_comment(self, body=None): - comm = comment.comment_root.new_reply(body=body) + comm = self.comment_root.new_reply(body=body) return comm def comment_from_shortname(self, shortname, *args, **kwargs): - return self.comment_root.comment_from_shortname(shortname, *args, **kwargs) + return self.comment_root.comment_from_shortname(shortname, + *args, **kwargs) def comment_from_uuid(self, uuid): return self.comment_root.comment_from_uuid(uuid) diff --git a/libbe/comment.py b/libbe/comment.py index c89fd9d..579e294 100644 --- a/libbe/comment.py +++ b/libbe/comment.py @@ -73,6 +73,14 @@ def saveComments(bug): for comment in bug.comment_root.traverse(): comment.save() +class InvalidShortname(KeyError): + def __init__(self, shortname, shortnames): + msg = "Invalid shortname %s\n%s" % (shortname, shortnames) + KeyError.__init__(self, msg) + self.shortname = shortname + self.shortnames = shortnames + + class Comment(Tree): def __init__(self, bug=None, uuid=None, from_disk=False, in_reply_to=None, body=None): @@ -133,12 +141,12 @@ class Comment(Tree): def string(self, indent=0, shortname=None): """ >>> comm = Comment(bug=None, body="Some\\ninsightful\\nremarks\\n") - >>> comm.time = utility.str_to_time("Thu, 20 Nov 2008 15:55:11 +0000") + >>> comm.time = utility.str_to_time("Thu, 01 Jan 1970 00:00:00 +0000") >>> print comm.string(indent=2, shortname="com-1") --------- Comment --------- Name: com-1 From: - Date: Thu, 20 Nov 2008 15:55:11 +0000 + Date: Thu, 01 Jan 1970 00:00:00 +0000 Some insightful @@ -220,9 +228,7 @@ class Comment(Tree): path = comment.get_path() self.rcs.recursive_remove(path) - def add_reply(self, reply): - if reply.time != None and self.time != None: - assert reply.time >= self.time + def add_reply(self, reply, allow_time_inversion=False): if self.uuid != INVALID_UUID: reply.in_reply_to = self.uuid self.append(reply) @@ -362,7 +368,8 @@ class Comment(Tree): for cur_name, comment in self.comment_shortnames(*args, **kwargs): if comment_shortname == cur_name: return comment - raise KeyError(comment_shortname) + raise InvalidShortname(comment_shortname, + list(self.comment_shortnames(*args, **kwargs))) def comment_from_uuid(self, uuid): """ diff --git a/test_usage.sh b/test_usage.sh index 43b5d4d..caa5388 100755 --- a/test_usage.sh +++ b/test_usage.sh @@ -95,6 +95,8 @@ echo "$OUT" BUG=`echo "$OUT" | sed -n 's/Created bug with ID //p'` echo "Working with bug: $BUG" be comment $BUG "This is an argument" +be set user_id "$ID" # get tired of guessing user id for none RCS +be set # show settings be comment $BUG:1 "No it isn't" # comment on the first comment be show $BUG # show details on a given bug be close $BUG # set bug status to 'closed' @@ -102,7 +104,6 @@ be comment $BUG "It's closed, but I can still comment." be open $BUG # set bug status to 'open' be comment $BUG "Reopend, comment again" be status $BUG fixed # set bug status to 'fixed' -be show $BUG # show bug details & comments be list # list all open bugs be list --status fixed # list all fixed bugs be assign $BUG # assign the bug to yourself @@ -111,6 +112,11 @@ be assign $BUG 'Joe' # assign the bug to Joe be list -a Joe -s fixed # list the fixed bugs assigned to Joe be assign $BUG none # assign the bug to noone be diff # see what has changed +OUT=`be new 'also having too much fun'` +BUGB=`echo "$OUT" | sed -n 's/Created bug with ID //p'` +be comment $BUGB "Blissfully unaware of a similar bug" +be merge $BUG $BUGB # join BUGB to BUG +be show $BUG # show bug details & comments be remove $BUG # decide that you don't like that bug after all cd / rm -rf $TESTDIR -- cgit