Added becommands/merge to join duplicate bugs.
authorW. Trevor King <wking@drexel.edu>
Tue, 25 Nov 2008 02:20:56 +0000 (21:20 -0500)
committerW. Trevor King <wking@drexel.edu>
Tue, 25 Nov 2008 02:20:56 +0000 (21:20 -0500)
README.dev
becommands/merge.py [new file with mode: 0644]
libbe/bug.py
libbe/comment.py
test_usage.sh

index bb39ba5c43b81e1219c1aff8b05b9439d201d3fa..4cbf554b3ea7146dbe69bd99a854702b4a38829c 100644 (file)
@@ -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 <yourplugin>
+for example
+  be$ python test.py merge
+
diff --git a/becommands/merge.py b/becommands/merge.py
new file mode 100644 (file)
index 0000000..b079f2c
--- /dev/null
@@ -0,0 +1,157 @@
+# Copyright (C) 2005 Aaron Bentley and Panometrics, Inc.
+# <abentley@panoramicfeedback.com>
+#
+#    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 <jdoe@example.com>
+         Created : Wed, 31 Dec 1969 19:00 (Thu, 01 Jan 1970 00:00:00 +0000)
+    Bug A
+    --------- Comment ---------
+    Name: a:1
+    From: wking <wking@thor.yang.physics.drexel.edu>
+    Date: Thu, 01 Jan 1970 00:00:01 +0000
+    <BLANKLINE>
+    Testing
+    --------- Comment ---------
+    Name: a:2
+    From: wking <wking@thor.yang.physics.drexel.edu>
+    Date: Thu, 01 Jan 1970 00:00:02 +0000
+    <BLANKLINE>
+    Testing...
+    --------- Comment ---------
+    Name: a:3
+    From: wking <wking@thor.yang.physics.drexel.edu>
+    Date: Thu, 01 Jan 1970 00:00:03 +0000
+    <BLANKLINE>
+    Merged from bug b
+    --------- Comment ---------
+    Name: a:4
+    From: wking <wking@thor.yang.physics.drexel.edu>
+    Date: Thu, 01 Jan 1970 00:00:01 +0000
+    <BLANKLINE>
+    1 2
+    --------- Comment ---------
+    Name: a:5
+    From: wking <wking@thor.yang.physics.drexel.edu>
+    Date: Thu, 01 Jan 1970 00:00:02 +0000
+    <BLANKLINE>
+    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 <jdoe@example.com>
+         Created : Wed, 31 Dec 1969 19:00 (Thu, 01 Jan 1970 00:00:00 +0000)
+    Bug B
+    --------- Comment ---------
+    Name: b:1
+    From: wking <wking@thor.yang.physics.drexel.edu>
+    Date: Thu, 01 Jan 1970 00:00:01 +0000
+    <BLANKLINE>
+    1 2
+    --------- Comment ---------
+    Name: b:2
+    From: wking <wking@thor.yang.physics.drexel.edu>
+    Date: Thu, 01 Jan 1970 00:00:02 +0000
+    <BLANKLINE>
+    1 2 3 4
+    --------- Comment ---------
+    Name: b:3
+    From: wking <wking@thor.yang.physics.drexel.edu>
+    Date: Thu, 01 Jan 1970 00:00:03 +0000
+    <BLANKLINE>
+    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
index c75c968c29993677244a5b13bc26d69149d7794d..68cff7b7eb9085081f9ca992cb5dab7997784d4d 100644 (file)
@@ -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)
index c89fd9d836755d1459785bce5513dce15e8fc1a2..579e294909c48c2567c6bc968574406af7a8675d 100644 (file)
@@ -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
         <BLANKLINE>
           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):
         """
index 43b5d4d40160663b67460273bca80a0c734d750c..caa53883a60bea7e1b0b5e49cd1329ca96856a6f 100755 (executable)
@@ -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