Bumped to version 1.0.1
[be.git] / libbe / diff.py
index f82dbfa76e816c360be5948a11dac2a48664b01a..4c24073d891c4799ad17bcc937def561efe032ee 100644 (file)
@@ -1,22 +1,25 @@
-# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc.
+# Copyright (C) 2005-2011 Aaron Bentley <abentley@panoramicfeedback.com>
+#                         Chris Ball <cjb@laptop.org>
 #                         Gianluca Montecchi <gian@grys.it>
 #                         W. Trevor King <wking@drexel.edu>
 #
-# 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 file is part of Bugs Everywhere.
 #
-# 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.
+# Bugs Everywhere 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.
 #
-# 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.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+# Bugs Everywhere 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 Bugs Everywhere.  If not, see <http://www.gnu.org/licenses/>.
 
-"""Compare two bug trees."""
+"""Tools for comparing two :class:`libbe.bug.BugDir`\s.
+"""
 
 import difflib
 import types
@@ -30,8 +33,7 @@ from libbe.util.utility import time_to_str
 
 
 class SubscriptionType (libbe.util.tree.Tree):
-    """
-    Trees of subscription types to allow users to select exactly what
+    """Trees of subscription types to allow users to select exactly what
     notifications they want to subscribe to.
     """
     def __init__(self, type_name, *args, **kwargs):
@@ -80,7 +82,11 @@ def type_from_name(name, type_root, default=None, default_ok=False):
     raise InvalidType(name, type_root)
 
 class Subscription (object):
-    """
+    """A user subscription.
+
+    Examples
+    --------
+
     >>> subscriptions = [Subscription('XYZ', 'all'),
     ...                  Subscription('DIR', 'new'),
     ...                  Subscription('ABC', BUG_TYPE_ALL),]
@@ -112,7 +118,11 @@ class Subscription (object):
         return '<Subscription: %s (%s)>' % (self.id, self.type)
 
 def subscriptions_from_string(string=None, subscription_sep=',', id_sep=':'):
-    """
+    """Provide a simple way for non-Python interfaces to read in subscriptions.
+
+    Examples
+    --------
+
     >>> subscriptions_from_string(None)
     [<Subscription: DIR (all)>]
     >>> subscriptions_from_string('DIR:new,DIR:rem,ABC:all,XYZ:all')
@@ -135,8 +145,11 @@ def subscriptions_from_string(string=None, subscription_sep=',', id_sep=':'):
     return subscriptions
 
 class DiffTree (libbe.util.tree.Tree):
-    """
-    A tree holding difference data for easy report generation.
+    """A tree holding difference data for easy report generation.
+
+    Examples
+    --------
+
     >>> bugdir = DiffTree('bugdir')
     >>> bdsettings = DiffTree('settings', data='target: None -> 1.0')
     >>> bugdir.append(bdsettings)
@@ -251,8 +264,11 @@ class DiffTree (libbe.util.tree.Tree):
         return data_part
 
 class Diff (object):
-    """
-    Difference tree generator for BugDirs.
+    """Difference tree generator for BugDirs.
+
+    Examples
+    --------
+
     >>> import copy
     >>> bd = libbe.bugdir.SimpleBugDir(memory=True)
     >>> bd_new = copy.deepcopy(bd)
@@ -367,32 +383,68 @@ class Diff (object):
         old_uuids.extend([s for s in subscribed_bugs
                           if self.old_bugdir.has_bug(s)])
         old_uuids = sorted(set(old_uuids))
+
         added = []
         removed = []
         modified = []
-        for uuid in new_uuids:
-            new_bug = self.new_bugdir.bug_from_uuid(uuid)
-            try:
-                old_bug = self.old_bugdir.bug_from_uuid(uuid)
-            except KeyError:
+        if hasattr(self.old_bugdir, 'changed'):
+            # take advantage of a RevisionedBugDir-style changed() method
+            new_ids,mod_ids,rem_ids = self.old_bugdir.changed()
+            for id in new_ids:
+                for a_id in self.new_bugdir.storage.ancestors(id):
+                    if a_id.count('/') == 0:
+                        if a_id in [b.id.storage() for b in added]:
+                            break
+                        try:
+                            bug = self.new_bugdir.bug_from_uuid(a_id)
+                            added.append(bug)
+                        except libbe.bugdir.NoBugMatches:
+                            pass
+            for id in rem_ids:
+                for a_id in self.old_bugdir.storage.ancestors(id):
+                    if a_id.count('/') == 0:
+                        if a_id in [b.id.storage() for b in removed]:
+                            break
+                        try:
+                            bug = self.old_bugdir.bug_from_uuid(a_id)
+                            removed.append(bug)
+                        except libbe.bugdir.NoBugMatches:
+                            pass
+            for id in mod_ids:
+                for a_id in self.new_bugdir.storage.ancestors(id):
+                    if a_id.count('/') == 0:
+                        if a_id in [b[0].id.storage() for b in modified]:
+                            break
+                        try:
+                            new_bug = self.new_bugdir.bug_from_uuid(a_id)
+                            old_bug = self.old_bugdir.bug_from_uuid(a_id)
+                            modified.append((old_bug, new_bug))
+                        except libbe.bugdir.NoBugMatches:
+                            pass
+        else:
+            for uuid in new_uuids:
+                new_bug = self.new_bugdir.bug_from_uuid(uuid)
+                try:
+                    old_bug = self.old_bugdir.bug_from_uuid(uuid)
+                except KeyError:
+                    if BUGDIR_TYPE_ALL in bugdir_types \
+                            or BUGDIR_TYPE_NEW in bugdir_types \
+                            or uuid in subscribed_bugs:
+                        added.append(new_bug)
+                    continue
                 if BUGDIR_TYPE_ALL in bugdir_types \
-                        or BUGDIR_TYPE_NEW in bugdir_types \
+                        or BUGDIR_TYPE_MOD in bugdir_types \
                         or uuid in subscribed_bugs:
-                    added.append(new_bug)
-                continue
-            if BUGDIR_TYPE_ALL in bugdir_types \
-                    or BUGDIR_TYPE_MOD in bugdir_types \
-                    or uuid in subscribed_bugs:
-                if old_bug.storage != None and old_bug.storage.is_readable():
-                    old_bug.load_comments()
-                if new_bug.storage != None and new_bug.storage.is_readable():
-                    new_bug.load_comments()
-                if old_bug != new_bug:
-                    modified.append((old_bug, new_bug))
-        for uuid in old_uuids:
-            if not self.new_bugdir.has_bug(uuid):
-                old_bug = self.old_bugdir.bug_from_uuid(uuid)
-                removed.append(old_bug)
+                    if old_bug.storage != None and old_bug.storage.is_readable():
+                        old_bug.load_comments()
+                    if new_bug.storage != None and new_bug.storage.is_readable():
+                        new_bug.load_comments()
+                    if old_bug != new_bug:
+                        modified.append((old_bug, new_bug))
+            for uuid in old_uuids:
+                if not self.new_bugdir.has_bug(uuid):
+                    old_bug = self.old_bugdir.bug_from_uuid(uuid)
+                    removed.append(old_bug)
         added.sort()
         removed.sort()
         modified.sort(self._bug_modified_cmp)
@@ -635,4 +687,7 @@ class Diff (object):
         return self._comment_summary_string(new_comment)
     def comment_body_change_string(self, bodies):
         old_body,new_body = bodies
-        return difflib.unified_diff(old_body, new_body)
+        return ''.join(difflib.unified_diff(
+                old_body.splitlines(True),
+                new_body.splitlines(True),
+                'before', 'after'))