Rev 5190: (vila) Add --exclude-common-ancestry log option (Vincent Ladeuil) in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Wed Apr 28 10:40:28 BST 2010


At file:///home/pqm/archives/thelove/bzr/%2Btrunk/

------------------------------------------------------------
revno: 5190 [merge]
revision-id: pqm at pqm.ubuntu.com-20100428094023-7504mlou1qk28r9n
parent: pqm at pqm.ubuntu.com-20100428082334-tmv91z1h0d99ls8s
parent: v.ladeuil+lp at free.fr-20100428071036-rxmh2nfnh7h504d6
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Wed 2010-04-28 10:40:23 +0100
message:
  (vila) Add --exclude-common-ancestry log option (Vincent Ladeuil)
modified:
  NEWS                           NEWS-20050323055033-4e00b5db738777ff
  bzrlib/branch.py               branch.py-20050309040759-e4baf4e0d046576e
  bzrlib/builtins.py             builtins.py-20050830033751-fc01482b9ca23183
  bzrlib/log.py                  log.py-20050505065812-c40ce11702fe5fb1
  bzrlib/tests/blackbox/test_log.py test_log.py-20060112090212-78f6ea560c868e24
  bzrlib/tests/per_branch/test_iter_merge_sorted_revisions.py test_merge_sorted_re-20090121004847-to3gvjwigstu93eh-1
  bzrlib/tests/per_repository_reference/__init__.py __init__.py-20080220025549-nnm2s80it1lvcwnc-2
  bzrlib/tests/test_log.py       testlog.py-20050728115707-1a514809d7d49309
=== modified file 'NEWS'
--- a/NEWS	2010-04-28 07:03:38 +0000
+++ b/NEWS	2010-04-28 09:40:23 +0000
@@ -34,6 +34,11 @@
   better with sudo.
   (Martin <gzlist at googlemail.com>, Parth Malwankar, #376388)
 
+* ``bzr log --exclude-common-ancestry -r X..Y`` displays the revisions that
+  are part of Y ancestry but not part of X ancestry (aka the graph
+  difference).
+  (Vincent Ladeuil, #320119)
+
 * ``bzr selftest --parallel=fork`` wait for its children avoiding zombies.
   (Vincent Ladeuil, #566670)
 
@@ -153,7 +158,8 @@
   (Andrew Bennetts)
 
 * When invoked with a range revision, ``bzr log`` doesn't show revisions
-  that are not part of the ancestry anymore.
+  that are not part of the Y revisions ancestry anymore when invoked with
+  -rX..Y.
   (Vincent Ladeuil, #474807)
 
 Improvements

=== modified file 'bzrlib/branch.py'
--- a/bzrlib/branch.py	2010-04-28 07:03:38 +0000
+++ b/bzrlib/branch.py	2010-04-28 09:40:23 +0000
@@ -417,6 +417,8 @@
             * 'include' - the stop revision is the last item in the result
             * 'with-merges' - include the stop revision and all of its
               merged revisions in the result
+            * 'with-merges-without-common-ancestry' - filter out revisions 
+              that are in both ancestries
         :param direction: either 'reverse' or 'forward':
             * reverse means return the start_revision_id first, i.e.
               start at the most recent revision and go backwards in history
@@ -453,7 +455,7 @@
             stop_revision_id, stop_rule)
         # Make sure we don't return revisions that are not part of the
         # start_revision_id ancestry.
-        filtered = self._filter_non_ancestors(filtered)
+        filtered = self._filter_start_non_ancestors(filtered)
         if direction == 'reverse':
             return filtered
         if direction == 'forward':
@@ -496,6 +498,18 @@
                        node.end_of_merge)
                 if rev_id == stop_revision_id:
                     return
+        elif stop_rule == 'with-merges-without-common-ancestry':
+            # We want to exclude all revisions that are already part of the
+            # stop_revision_id ancestry.
+            graph = self.repository.get_graph()
+            ancestors = graph.find_unique_ancestors(start_revision_id,
+                                                    [stop_revision_id])
+            for node in rev_iter:
+                rev_id = node.key[-1]
+                if rev_id not in ancestors:
+                    continue
+                yield (rev_id, node.merge_depth, node.revno,
+                       node.end_of_merge)
         elif stop_rule == 'with-merges':
             stop_rev = self.repository.get_revision(stop_revision_id)
             if stop_rev.parent_ids:
@@ -524,7 +538,7 @@
         else:
             raise ValueError('invalid stop_rule %r' % stop_rule)
 
-    def _filter_non_ancestors(self, rev_iter):
+    def _filter_start_non_ancestors(self, rev_iter):
         # If we started from a dotted revno, we want to consider it as a tip
         # and don't want to yield revisions that are not part of its
         # ancestry. Given the order guaranteed by the merge sort, we will see

=== modified file 'bzrlib/builtins.py'
--- a/bzrlib/builtins.py	2010-04-28 07:03:38 +0000
+++ b/bzrlib/builtins.py	2010-04-28 09:40:23 +0000
@@ -2297,6 +2297,10 @@
                    help='Show changes made in each revision as a patch.'),
             Option('include-merges',
                    help='Show merged revisions like --levels 0 does.'),
+            Option('exclude-common-ancestry',
+                   help='Display only the revisions that are not part'
+                   ' of both ancestries (require -rX..Y)'
+                   )
             ]
     encoding_type = 'replace'
 
@@ -2312,13 +2316,19 @@
             message=None,
             limit=None,
             show_diff=False,
-            include_merges=False):
+            include_merges=False,
+            exclude_common_ancestry=False,
+            ):
         from bzrlib.log import (
             Logger,
             make_log_request_dict,
             _get_info_for_log_files,
             )
         direction = (forward and 'forward') or 'reverse'
+        if (exclude_common_ancestry
+            and (revision is None or len(revision) != 2)):
+            raise errors.BzrCommandError(
+                '--exclude-common-ancestry requires -r with two revisions')
         if include_merges:
             if levels is None:
                 levels = 0
@@ -2417,7 +2427,9 @@
             direction=direction, specific_fileids=file_ids,
             start_revision=rev1, end_revision=rev2, limit=limit,
             message_search=message, delta_type=delta_type,
-            diff_type=diff_type, _match_using_deltas=match_using_deltas)
+            diff_type=diff_type, _match_using_deltas=match_using_deltas,
+            exclude_common_ancestry=exclude_common_ancestry,
+            )
         Logger(b, rqst).show(lf)
 
 

=== modified file 'bzrlib/log.py'
--- a/bzrlib/log.py	2010-04-14 10:38:57 +0000
+++ b/bzrlib/log.py	2010-04-14 12:30:17 +0000
@@ -220,14 +220,18 @@
     'direction': 'reverse',
     'levels': 1,
     'generate_tags': True,
+    'exclude_common_ancestry': False,
     '_match_using_deltas': True,
     }
 
 
 def make_log_request_dict(direction='reverse', specific_fileids=None,
-    start_revision=None, end_revision=None, limit=None,
-    message_search=None, levels=1, generate_tags=True, delta_type=None,
-    diff_type=None, _match_using_deltas=True):
+                          start_revision=None, end_revision=None, limit=None,
+                          message_search=None, levels=1, generate_tags=True,
+                          delta_type=None,
+                          diff_type=None, _match_using_deltas=True,
+                          exclude_common_ancestry=False,
+                          ):
     """Convenience function for making a logging request dictionary.
 
     Using this function may make code slightly safer by ensuring
@@ -271,6 +275,9 @@
       algorithm used for matching specific_fileids. This parameter
       may be removed in the future so bzrlib client code should NOT
       use it.
+
+    :param exclude_common_ancestry: Whether -rX..Y should be interpreted as a
+      range operator or as a graph difference.
     """
     return {
         'direction': direction,
@@ -283,6 +290,7 @@
         'generate_tags': generate_tags,
         'delta_type': delta_type,
         'diff_type': diff_type,
+        'exclude_common_ancestry': exclude_common_ancestry,
         # Add 'private' attributes for features that may be deprecated
         '_match_using_deltas': _match_using_deltas,
     }
@@ -459,7 +467,8 @@
             self.branch, self.start_rev_id, self.end_rev_id,
             rqst.get('direction'),
             generate_merge_revisions=generate_merge_revisions,
-            delayed_graph_generation=delayed_graph_generation)
+            delayed_graph_generation=delayed_graph_generation,
+            exclude_common_ancestry=rqst.get('exclude_common_ancestry'))
 
         # Apply the other filters
         return make_log_rev_iterator(self.branch, view_revisions,
@@ -474,7 +483,8 @@
         rqst = self.rqst
         view_revisions = _calc_view_revisions(
             self.branch, self.start_rev_id, self.end_rev_id,
-            rqst.get('direction'), generate_merge_revisions=True)
+            rqst.get('direction'), generate_merge_revisions=True,
+            exclude_common_ancestry=rqst.get('exclude_common_ancestry'))
         if not isinstance(view_revisions, list):
             view_revisions = list(view_revisions)
         view_revisions = _filter_revisions_touching_file_id(self.branch,
@@ -485,12 +495,18 @@
 
 
 def _calc_view_revisions(branch, start_rev_id, end_rev_id, direction,
-    generate_merge_revisions, delayed_graph_generation=False):
+                         generate_merge_revisions,
+                         delayed_graph_generation=False,
+                         exclude_common_ancestry=False,
+                         ):
     """Calculate the revisions to view.
 
     :return: An iterator of (revision_id, dotted_revno, merge_depth) tuples OR
              a list of the same tuples.
     """
+    if (exclude_common_ancestry and start_rev_id == end_rev_id):
+        raise errors.BzrCommandError(
+            '--exclude-common-ancestry requires two different revisions')
     if direction not in ('reverse', 'forward'):
         raise ValueError('invalid direction %r' % direction)
     br_revno, br_rev_id = branch.last_revision_info()
@@ -511,7 +527,8 @@
             iter_revs = reversed(iter_revs)
     else:
         iter_revs = _generate_all_revisions(branch, start_rev_id, end_rev_id,
-                                            direction, delayed_graph_generation)
+                                            direction, delayed_graph_generation,
+                                            exclude_common_ancestry)
         if direction == 'forward':
             iter_revs = _rebase_merge_depth(reverse_by_depth(list(iter_revs)))
     return iter_revs
@@ -542,7 +559,8 @@
 
 
 def _generate_all_revisions(branch, start_rev_id, end_rev_id, direction,
-                            delayed_graph_generation):
+                            delayed_graph_generation,
+                            exclude_common_ancestry=False):
     # On large trees, generating the merge graph can take 30-60 seconds
     # so we delay doing it until a merge is detected, incrementally
     # returning initial (non-merge) revisions while we can.
@@ -594,7 +612,8 @@
     # indented at the end seems slightly nicer in that case.
     view_revisions = chain(iter(initial_revisions),
         _graph_view_revisions(branch, start_rev_id, end_rev_id,
-                              rebase_initial_depths=(direction == 'reverse')))
+                              rebase_initial_depths=(direction == 'reverse'),
+                              exclude_common_ancestry=exclude_common_ancestry))
     return view_revisions
 
 
@@ -659,7 +678,8 @@
 
 
 def _graph_view_revisions(branch, start_rev_id, end_rev_id,
-                          rebase_initial_depths=True):
+                          rebase_initial_depths=True,
+                          exclude_common_ancestry=False):
     """Calculate revisions to view including merges, newest to oldest.
 
     :param branch: the branch
@@ -669,9 +689,13 @@
       revision is found?
     :return: An iterator of (revision_id, dotted_revno, merge_depth) tuples.
     """
+    if exclude_common_ancestry:
+        stop_rule = 'with-merges-without-common-ancestry'
+    else:
+        stop_rule = 'with-merges'
     view_revisions = branch.iter_merge_sorted_revisions(
         start_revision_id=end_rev_id, stop_revision_id=start_rev_id,
-        stop_rule="with-merges")
+        stop_rule=stop_rule)
     if not rebase_initial_depths:
         for (rev_id, merge_depth, revno, end_of_merge
              ) in view_revisions:

=== modified file 'bzrlib/tests/blackbox/test_log.py'
--- a/bzrlib/tests/blackbox/test_log.py	2010-03-24 14:15:01 +0000
+++ b/bzrlib/tests/blackbox/test_log.py	2010-04-14 12:30:17 +0000
@@ -365,6 +365,18 @@
                             'options are "utc", "original", "local".'],
                            ['log', '--timezone', 'foo'])
 
+    def test_log_exclude_ancestry_no_range(self):
+        self.make_linear_branch()
+        self.run_bzr_error(['bzr: ERROR: --exclude-common-ancestry'
+                            ' requires -r with two revisions'],
+                           ['log', '--exclude-common-ancestry'])
+
+    def test_log_exclude_ancestry_single_revision(self):
+        self.make_merged_branch()
+        self.run_bzr_error(['bzr: ERROR: --exclude-common-ancestry'
+                            ' requires two different revisions'],
+                           ['log', '--exclude-common-ancestry',
+                            '-r1.1.1..1.1.1'])
 
 class TestLogTags(TestLog):
 

=== modified file 'bzrlib/tests/per_branch/test_iter_merge_sorted_revisions.py'
--- a/bzrlib/tests/per_branch/test_iter_merge_sorted_revisions.py	2010-04-02 15:05:24 +0000
+++ b/bzrlib/tests/per_branch/test_iter_merge_sorted_revisions.py	2010-04-28 07:10:36 +0000
@@ -229,6 +229,37 @@
         self.addCleanup(br.unlock)
         return br
 
+    def make_branch_with_alternate_ancestries(self, relpath='.'):
+        # See test_merge_sorted_exclude_ancestry below for the difference with
+        # bt.test_log.TestLogExcludeAncestry.
+        # make_branch_with_alternate_ancestries and
+        # test_merge_sorted_exclude_ancestry
+        # See the FIXME in assertLogRevnos there too.
+        builder = self.make_branch_builder(relpath)
+        # 1
+        # |\
+        # | 1.1.1
+        # | /| \
+        # 2  |  |
+        # |  |  1.2.1
+        # |  | /
+        # |  1.1.2
+        # | /
+        # 3
+        builder.start_series()
+        builder.build_snapshot('1', None, [
+            ('add', ('', 'TREE_ROOT', 'directory', '')),])
+        builder.build_snapshot('1.1.1', ['1'], [])
+        builder.build_snapshot('2', ['1', '1.1.1'], [])
+        builder.build_snapshot('1.2.1', ['1.1.1'], [])
+        builder.build_snapshot('1.1.2', ['1.1.1', '1.2.1'], [])
+        builder.build_snapshot('3', ['2', '1.1.2'], [])
+        builder.finish_series()
+        br = builder.get_branch()
+        br.lock_read()
+        self.addCleanup(br.unlock)
+        return br
+
     def assertIterRevids(self, expected, branch, *args, **kwargs):
         # We don't care about depths and revnos here, only about returning the
         # right revids.
@@ -259,3 +290,16 @@
         self.assertIterRevids(['2.2.1', '2.1.1', '2', '1'],
                               branch, start_revision_id='2.2.1',
                               stop_rule='with-merges')
+
+    def test_merge_sorted_exclude_ancestry(self):
+        branch = self.make_branch_with_alternate_ancestries()
+        self.assertIterRevids(['3', '1.1.2', '1.2.1', '2', '1.1.1', '1'],
+                              branch)
+        # '2' is not part of the ancestry even if merge_sort order will make it
+        # appear before 1.1.1
+        self.assertIterRevids(['1.1.2', '1.2.1'],
+                              branch,
+                              stop_rule='with-merges-without-common-ancestry',
+                              start_revision_id='1.1.2',
+                              stop_revision_id='1.1.1')
+

=== modified file 'bzrlib/tests/per_repository_reference/__init__.py'
--- a/bzrlib/tests/per_repository_reference/__init__.py	2010-04-16 06:51:59 +0000
+++ b/bzrlib/tests/per_repository_reference/__init__.py	2010-04-28 06:21:33 +0000
@@ -1,4 +1,4 @@
-# Copyright (C) 2008, 2009 Canonical Ltd
+# Copyright (C) 2008, 2009, 2010 Canonical Ltd
 #
 # 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

=== modified file 'bzrlib/tests/test_log.py'
--- a/bzrlib/tests/test_log.py	2010-03-25 08:14:04 +0000
+++ b/bzrlib/tests/test_log.py	2010-04-28 07:10:36 +0000
@@ -18,6 +18,7 @@
 from cStringIO import StringIO
 
 from bzrlib import (
+    branchbuilder,
     errors,
     log,
     registry,
@@ -1541,3 +1542,62 @@
 
     def test_bugs_handler_present(self):
         self.properties_handler_registry.get('bugs_properties_handler')
+
+class TestLogExcludeAncestry(tests.TestCaseWithTransport):
+
+    def make_branch_with_alternate_ancestries(self, relpath='.'):
+        # See test_merge_sorted_exclude_ancestry below for the difference with
+        # bt.per_branch.test_iter_merge_sorted_revision.
+        # TestIterMergeSortedRevisionsBushyGraph. 
+        # make_branch_with_alternate_ancestries
+        # and test_merge_sorted_exclude_ancestry
+        # See the FIXME in assertLogRevnos too.
+        builder = branchbuilder.BranchBuilder(self.get_transport(relpath))
+        # 1
+        # |\
+        # 2 \
+        # |  |
+        # |  1.1.1
+        # |  | \
+        # |  |  1.2.1
+        # |  | /
+        # |  1.1.2
+        # | /
+        # 3
+        builder.start_series()
+        builder.build_snapshot('1', None, [
+            ('add', ('', 'TREE_ROOT', 'directory', '')),])
+        builder.build_snapshot('1.1.1', ['1'], [])
+        builder.build_snapshot('2', ['1'], [])
+        builder.build_snapshot('1.2.1', ['1.1.1'], [])
+        builder.build_snapshot('1.1.2', ['1.1.1', '1.2.1'], [])
+        builder.build_snapshot('3', ['2', '1.1.2'], [])
+        builder.finish_series()
+        br = builder.get_branch()
+        br.lock_read()
+        self.addCleanup(br.unlock)
+        return br
+
+    def assertLogRevnos(self, expected_revnos, b, start, end,
+                        exclude_common_ancestry):
+        # FIXME: the layering in log makes it hard to test intermediate levels,
+        # I wish adding filters with their parameters were easier...
+        # -- vila 20100413
+        iter_revs = log._calc_view_revisions(
+            b, start, end, direction='reverse',
+            generate_merge_revisions=True,
+            exclude_common_ancestry=exclude_common_ancestry)
+        self.assertEqual(expected_revnos,
+                         [revid for revid, revno, depth in iter_revs])
+
+    def test_merge_sorted_exclude_ancestry(self):
+        b = self.make_branch_with_alternate_ancestries()
+        self.assertLogRevnos(['3', '1.1.2', '1.2.1', '1.1.1', '2', '1'],
+                             b, '1', '3', False)
+        # '2' is part of the '3' ancestry but not part of '1.1.1' ancestry so
+        # it should be mentioned even if merge_sort order will make it appear
+        # after 1.1.1
+        self.assertLogRevnos(['3', '1.1.2', '1.2.1', '2'],
+                             b, '1.1.1', '3', True)
+
+




More information about the bazaar-commits mailing list