Rev 2580: (Kent Gibson) Fix bug #4663: bzr log -r to support selecting merge revisions. in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Wed Jul 4 05:10:14 BST 2007


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

------------------------------------------------------------
revno: 2580
revision-id: pqm at pqm.ubuntu.com-20070704041011-o8aw2m812hzhz8yr
parent: pqm at pqm.ubuntu.com-20070704024013-cwx0ld720nvdfb8q
parent: andrew.bennetts at canonical.com-20070704031237-faouhtp7ckc4m69t
committer: Canonical.com Patch Queue Manager<pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Wed 2007-07-04 05:10:11 +0100
message:
  (Kent Gibson) Fix bug #4663: bzr log -r to support selecting merge revisions.
modified:
  NEWS                           NEWS-20050323055033-4e00b5db738777ff
  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/test_log.py       testlog.py-20050728115707-1a514809d7d49309
    ------------------------------------------------------------
    revno: 2578.2.1
    merged: andrew.bennetts at canonical.com-20070704031237-faouhtp7ckc4m69t
    parent: pqm at pqm.ubuntu.com-20070703184919-6iz3vo2rx42smf25
    parent: warthog618 at gmail.com-20070504104926-dmz6x2gw7ydb8i7p
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: kent-bug-4663
    timestamp: Wed 2007-07-04 13:12:37 +1000
    message:
      Merge Kent Gibson's fix for bug #4663, resolving conflicts.
    ------------------------------------------------------------
    revno: 2466.8.1.3.1
    merged: warthog618 at gmail.com-20070504104926-dmz6x2gw7ydb8i7p
    parent: warthog618 at gmail.com-20070429034630-p3xbitz2xyl6pbie
    committer: Kent Gibson <warthog618 at gmail.com>
    branch nick: log_range
    timestamp: Fri 2007-05-04 18:49:26 +0800
    message:
      Fix ``bzr log -r`` to support selecting merge revisions.
=== modified file 'NEWS'
--- a/NEWS	2007-07-03 18:00:34 +0000
+++ b/NEWS	2007-07-04 03:12:37 +0000
@@ -20,6 +20,10 @@
       via a proxy to enable SSL tunneling.
      (Vincent Ladeuil, #120678)
 
+    * Fix ``bzr log -r`` to support selecting merge revisions, both 
+      individually and as part of revision ranges.
+      (Kent Gibson, #4663)
+
   IMPROVEMENTS:
 
     * The --lsprof-file option now dumps a text rendering of the profiling

=== modified file 'bzrlib/builtins.py'
--- a/bzrlib/builtins.py	2007-07-04 01:53:26 +0000
+++ b/bzrlib/builtins.py	2007-07-04 04:10:11 +0000
@@ -1640,7 +1640,7 @@
                 rev1 = None
                 rev2 = None
             elif len(revision) == 1:
-                rev1 = rev2 = revision[0].in_history(b).revno
+                rev1 = rev2 = revision[0].in_history(b)
             elif len(revision) == 2:
                 if revision[1].get_branch() != revision[0].get_branch():
                     # b is taken from revision[0].get_branch(), and
@@ -1649,27 +1649,12 @@
                     raise errors.BzrCommandError(
                         "Log doesn't accept two revisions in different"
                         " branches.")
-                if revision[0].spec is None:
-                    # missing begin-range means first revision
-                    rev1 = 1
-                else:
-                    rev1 = revision[0].in_history(b).revno
-
-                if revision[1].spec is None:
-                    # missing end-range means last known revision
-                    rev2 = b.revno()
-                else:
-                    rev2 = revision[1].in_history(b).revno
+                rev1 = revision[0].in_history(b)
+                rev2 = revision[1].in_history(b)
             else:
                 raise errors.BzrCommandError(
                     'bzr log --revision takes one or two values.')
 
-            # By this point, the revision numbers are converted to the +ve
-            # form if they were supplied in the -ve form, so we can do
-            # this comparison in relative safety
-            if rev1 > rev2:
-                (rev2, rev1) = (rev1, rev2)
-
             if log_format is None:
                 log_format = log.log_formatter_registry.get_default(b)
 

=== modified file 'bzrlib/log.py'
--- a/bzrlib/log.py	2007-07-02 15:01:18 +0000
+++ b/bzrlib/log.py	2007-07-04 03:12:37 +0000
@@ -59,6 +59,9 @@
     symbol_versioning,
     )
 import bzrlib.errors as errors
+from bzrlib.revisionspec import(
+    RevisionInfo
+    )
 from bzrlib.symbol_versioning import (
     deprecated_method,
     zero_eleven,
@@ -115,7 +118,6 @@
         revno += 1
 
 
-
 def _enumerate_history(branch):
     rh = []
     revno = 1
@@ -202,32 +204,14 @@
     else:
         searchRE = None
 
-    which_revs = _enumerate_history(branch)
-    
-    if start_revision is None:
-        start_revision = 1
-    else:
-        branch.check_real_revno(start_revision)
-    
-    if end_revision is None:
-        end_revision = len(which_revs)
-    else:
-        branch.check_real_revno(end_revision)
-
-    # list indexes are 0-based; revisions are 1-based
-    cut_revs = which_revs[(start_revision-1):(end_revision)]
-    if not cut_revs:
+    mainline_revs, rev_nos, start_rev_id, end_rev_id = \
+        _get_mainline_revs(branch, start_revision, end_revision)
+    if not mainline_revs:
         return
 
-    # convert the revision history to a dictionary:
-    rev_nos = dict((k, v) for v, k in cut_revs)
-
-    # override the mainline to look like the revision history.
-    mainline_revs = [revision_id for index, revision_id in cut_revs]
-    if cut_revs[0][0] == 1:
-        mainline_revs.insert(0, None)
-    else:
-        mainline_revs.insert(0, which_revs[start_revision-2][1])
+    if direction == 'reverse':
+        start_rev_id, end_rev_id = end_rev_id, start_rev_id
+        
     legacy_lf = getattr(lf, 'log_revision', None) is None
     if legacy_lf:
         # pre-0.17 formatters use show for mainline revisions.
@@ -250,13 +234,14 @@
                                            False)
     view_revs_iter = get_view_revisions(mainline_revs, rev_nos, branch,
                           direction, include_merges=generate_merge_revisions)
+    view_revisions = _filter_revision_range(list(view_revs_iter),
+                                            start_rev_id,
+                                            end_rev_id)
     if specific_fileid:
-        view_revisions = _get_revisions_touching_file_id(branch,
+        view_revisions = _filter_revisions_touching_file_id(branch,
                                                          specific_fileid,
                                                          mainline_revs,
-                                                         view_revs_iter)
-    else:
-        view_revisions = list(view_revs_iter)
+                                                         view_revisions)
 
     rev_tag_dict = {}
     generate_tags = getattr(lf, 'supports_tags', False)
@@ -318,10 +303,125 @@
                 break
 
 
-def _get_revisions_touching_file_id(branch, file_id, mainline_revisions,
-                                    view_revs_iter):
+def _get_mainline_revs(branch, start_revision, end_revision):
+    """Get the mainline revisions from the branch.
+    
+    Generates the list of mainline revisions for the branch.
+    
+    :param  branch: The branch containing the revisions. 
+
+    :param  start_revision: The first revision to be logged.
+            For backwards compatibility this may be a mainline integer revno,
+            but for merge revision support a RevisionInfo is expected.
+
+    :param  end_revision: The last revision to be logged.
+            For backwards compatibility this may be a mainline integer revno,
+            but for merge revision support a RevisionInfo is expected.
+
+    :return: A (mainline_revs, rev_nos, start_rev_id, end_rev_id) tuple.
+    """
+    which_revs = _enumerate_history(branch)
+    if not which_revs:
+        return None, None, None, None
+
+    # For mainline generation, map start_revision and end_revision to 
+    # mainline revnos. If the revision is not on the mainline choose the 
+    # appropriate extreme of the mainline instead - the extra will be 
+    # filtered later.
+    # Also map the revisions to rev_ids, to be used in the later filtering
+    # stage.
+    start_rev_id = None 
+    if start_revision is None:
+        start_revno = 1
+    else:
+        if isinstance(start_revision,RevisionInfo):
+            start_rev_id = start_revision.rev_id
+            start_revno = start_revision.revno or 1
+        else:
+            branch.check_real_revno(start_revision)
+            start_revno = start_revision
+    
+    end_rev_id = None
+    if end_revision is None:
+        end_revno = len(which_revs)
+    else:
+        if isinstance(end_revision,RevisionInfo):
+            end_rev_id = end_revision.rev_id
+            end_revno = end_revision.revno or len(which_revs)
+        else:
+            branch.check_real_revno(end_revision)
+            end_revno = end_revision
+
+    if start_revno > end_revno:
+        from bzrlib.errors import BzrCommandError
+        raise BzrCommandError("Start revision must be older than "
+                              "the end revision.")
+
+    # list indexes are 0-based; revisions are 1-based
+    cut_revs = which_revs[(start_revno-1):(end_revno)]
+    if not cut_revs:
+        return None, None, None, None
+
+    # convert the revision history to a dictionary:
+    rev_nos = dict((k, v) for v, k in cut_revs)
+
+    # override the mainline to look like the revision history.
+    mainline_revs = [revision_id for index, revision_id in cut_revs]
+    if cut_revs[0][0] == 1:
+        mainline_revs.insert(0, None)
+    else:
+        mainline_revs.insert(0, which_revs[start_revno-2][1])
+    return mainline_revs, rev_nos, start_rev_id, end_rev_id
+
+
+def _filter_revision_range(view_revisions, start_rev_id, end_rev_id):
+    """Filter view_revisions based on revision ranges.
+
+    :param view_revisions: A list of (revision_id, dotted_revno, merge_depth) 
+            tuples to be filtered.
+
+    :param start_rev_id: If not NONE specifies the first revision to be logged.
+            If NONE then all revisions up to the end_rev_id are logged.
+
+    :param end_rev_id: If not NONE specifies the last revision to be logged.
+            If NONE then all revisions up to the end of the log are logged.
+
+    :return: The filtered view_revisions.
+    """
+    if start_rev_id or end_rev_id: 
+        revision_ids = [r for r, n, d in view_revisions]
+        if start_rev_id:
+            start_index = revision_ids.index(start_rev_id)
+        else:
+            start_index = 0
+        if start_rev_id == end_rev_id:
+            end_index = start_index
+        else:
+            if end_rev_id:
+                end_index = revision_ids.index(end_rev_id)
+            else:
+                end_index = len(view_revisions) - 1
+        # To include the revisions merged into the last revision, 
+        # extend end_rev_id down to, but not including, the next rev
+        # with the same or lesser merge_depth
+        end_merge_depth = view_revisions[end_index][2]
+        try:
+            for index in xrange(end_index+1, len(view_revisions)+1):
+                if view_revisions[index][2] <= end_merge_depth:
+                    end_index = index - 1
+                    break
+        except IndexError:
+            # if the search falls off the end then log to the end as well
+            end_index = len(view_revisions) - 1
+        view_revisions = view_revisions[start_index:end_index+1]
+    return view_revisions
+
+
+def _filter_revisions_touching_file_id(branch, file_id, mainline_revisions,
+                                       view_revs_iter):
     """Return the list of revision ids which touch a given file id.
 
+    The function filters view_revisions and returns a subset.
     This includes the revisions which directly change the file id,
     and the revisions which merge these changes. So if the
     revision graph is::

=== modified file 'bzrlib/tests/blackbox/test_log.py'
--- a/bzrlib/tests/blackbox/test_log.py	2007-07-02 15:01:18 +0000
+++ b/bzrlib/tests/blackbox/test_log.py	2007-07-04 03:12:37 +0000
@@ -93,6 +93,12 @@
         log = self.run_bzr("log -r 1..3")[0]
         self.assertEqualDiff(self.full_log, log)
 
+    def test_log_reversed_revspecs(self):
+        self._prepare()
+        self.run_bzr_error(('bzr: ERROR: Start revision must be older than '
+                            'the end revision.\n',),
+                           'log', '-r3..1')
+
     def test_log_revno_n_path(self):
         os.mkdir('branch1')
         os.chdir('branch1')
@@ -148,6 +154,8 @@
         self.run_bzr('commit -m merge_branch_1')
         log = self.run_bzr("log -r-1")[0]
         self.assertContainsRe(log, r'    tags: tag1')
+        log = self.run_bzr("log -r3.1.1")[0]
+        self.assertContainsRe(log, r'    tags: tag1')
 
     def test_log_limit(self):
         self._prepare()
@@ -156,7 +164,157 @@
         self.assertTrue('revno: 2\n' in log)
         self.assertTrue('revno: 3\n' in log)
 
-
+class TestLogMerges(ExternalBase):
+
+    def _prepare(self):
+        self.build_tree(['parent/'])
+        self.run_bzr('init', 'parent')
+        self.run_bzr('commit', '-m', 'first post', '--unchanged', 'parent')
+        self.run_bzr('branch', 'parent', 'child')
+        self.run_bzr('commit', '-m', 'branch 1', '--unchanged', 'child')
+        self.run_bzr('branch', 'child', 'smallerchild')
+        self.run_bzr('commit', '-m', 'branch 2', '--unchanged', 'smallerchild')
+        os.chdir('child')
+        self.run_bzr('merge', '../smallerchild')
+        self.run_bzr('commit', '-m', 'merge branch 2')
+        os.chdir('../parent')
+        self.run_bzr('merge', '../child')
+        self.run_bzr('commit', '-m', 'merge branch 1')
+
+    def test_merges_are_indented_by_level(self):
+        self._prepare()
+        out,err = self.run_bzr('log')
+        # the log will look something like:
+#        self.assertEqual("""\
+#------------------------------------------------------------
+#revno: 2
+#committer: Robert Collins <foo at example.com>
+#branch nick: parent
+#timestamp: Tue 2006-03-28 22:31:40 +1100
+#message:
+#  merge branch 1
+#    ------------------------------------------------------------
+#    revno: 1.1.2  
+#    committer: Robert Collins <foo at example.com>
+#    branch nick: child
+#    timestamp: Tue 2006-03-28 22:31:40 +1100
+#    message:
+#      merge branch 2
+#        ------------------------------------------------------------
+#        revno: 1.1.1.1
+#        committer: Robert Collins <foo at example.com>
+#        branch nick: smallerchild
+#        timestamp: Tue 2006-03-28 22:31:40 +1100
+#        message:
+#          branch 2
+#    ------------------------------------------------------------
+#    revno: 1.1.1
+#    committer: Robert Collins <foo at example.com>
+#    branch nick: child
+#    timestamp: Tue 2006-03-28 22:31:40 +1100
+#    message:
+#      branch 1
+#------------------------------------------------------------
+#revno: 1
+#committer: Robert Collins <foo at example.com>
+#branch nick: parent
+#timestamp: Tue 2006-03-28 22:31:39 +1100
+#message:
+#  first post
+#""", out)
+        # but we dont have a nice pattern matcher hooked up yet, so:
+        # we check for the indenting of the commit message and the 
+        # revision numbers 
+        self.assertTrue('revno: 2' in out)
+        self.assertTrue('  merge branch 1' in out)
+        self.assertTrue('    revno: 1.1.2' in out)
+        self.assertTrue('      merge branch 2' in out)
+        self.assertTrue('        revno: 1.1.1.1' in out)
+        self.assertTrue('          branch 2' in out)
+        self.assertTrue('    revno: 1.1.1' in out)
+        self.assertTrue('      branch 1' in out)
+        self.assertTrue('revno: 1\n' in out)
+        self.assertTrue('  first post' in out)
+        self.assertEqual('', err)
+
+    def test_merges_single_merge_rev(self):
+        self._prepare()
+        out,err = self.run_bzr('log', '-r1.1.2')
+        # the log will look something like:
+#        self.assertEqual("""\
+#    ------------------------------------------------------------
+#    revno: 1.1.2  
+#    committer: Robert Collins <foo at example.com>
+#    branch nick: child
+#    timestamp: Tue 2006-03-28 22:31:40 +1100
+#    message:
+#      merge branch 2
+#        ------------------------------------------------------------
+#        revno: 1.1.1.1
+#        committer: Robert Collins <foo at example.com>
+#        branch nick: smallerchild
+#        timestamp: Tue 2006-03-28 22:31:40 +1100
+#        message:
+#          branch 2
+#""", out)
+        # but we dont have a nice pattern matcher hooked up yet, so:
+        # we check for the indenting of the commit message and the 
+        # revision numbers 
+        self.assertTrue('revno: 2' not in out)
+        self.assertTrue('  merge branch 1' not in out)
+        self.assertTrue('    revno: 1.1.2' in out)
+        self.assertTrue('      merge branch 2' in out)
+        self.assertTrue('        revno: 1.1.1.1' in out)
+        self.assertTrue('          branch 2' in out)
+        self.assertTrue('    revno: 1.1.1\n' not in out)
+        self.assertTrue('      branch 1' not in out)
+        self.assertTrue('revno: 1\n' not in out)
+        self.assertTrue('  first post' not in out)
+        self.assertEqual('', err)
+
+    def test_merges_partial_range(self):
+        self._prepare()
+        out,err = self.run_bzr('log', '-r1.1.1..1.1.2')
+        # the log will look something like:
+#        self.assertEqual("""\
+#    ------------------------------------------------------------
+#    revno: 1.1.2  
+#    committer: Robert Collins <foo at example.com>
+#    branch nick: child
+#    timestamp: Tue 2006-03-28 22:31:40 +1100
+#    message:
+#      merge branch 2
+#        ------------------------------------------------------------
+#        revno: 1.1.1.1
+#        committer: Robert Collins <foo at example.com>
+#        branch nick: smallerchild
+#        timestamp: Tue 2006-03-28 22:31:40 +1100
+#        message:
+#          branch 2
+#    ------------------------------------------------------------
+#    revno: 1.1.1
+#    committer: Robert Collins <foo at example.com>
+#    branch nick: child
+#    timestamp: Tue 2006-03-28 22:31:40 +1100
+#    message:
+#      branch 1
+#""", out)
+        # but we dont have a nice pattern matcher hooked up yet, so:
+        # we check for the indenting of the commit message and the 
+        # revision numbers 
+        self.assertTrue('revno: 2' not in out)
+        self.assertTrue('  merge branch 1' not in out)
+        self.assertTrue('    revno: 1.1.2' in out)
+        self.assertTrue('      merge branch 2' in out)
+        self.assertTrue('        revno: 1.1.1.1' in out)
+        self.assertTrue('          branch 2' in out)
+        self.assertTrue('    revno: 1.1.1' in out)
+        self.assertTrue('      branch 1' in out)
+        self.assertTrue('revno: 1\n' not in out)
+        self.assertTrue('  first post' not in out)
+        self.assertEqual('', err)
+
+ 
 class TestLogEncodings(TestCaseInTempDir):
 
     _mu = u'\xb5'
@@ -285,7 +443,7 @@
         os.chdir('parent')
         self.run_bzr('merge', '../child')
         self.run_bzr('commit', '-m', 'merge child branch')
-        
+       
         log = self.run_bzr('log', 'file1')[0]
         self.assertContainsRe(log, 'revno: 1\n')
         self.assertNotContainsRe(log, 'revno: 2\n')
@@ -304,3 +462,27 @@
         self.assertContainsRe(log, 'revno: 3\n')
         self.assertNotContainsRe(log, 'revno: 3.1.1\n')
         self.assertNotContainsRe(log, 'revno: 4\n')
+        log = self.run_bzr('log', '-r3.1.1', 'file2')[0]
+        self.assertNotContainsRe(log, 'revno: 1\n')
+        self.assertNotContainsRe(log, 'revno: 2\n')
+        self.assertNotContainsRe(log, 'revno: 3\n')
+        self.assertContainsRe(log, 'revno: 3.1.1\n')
+        self.assertNotContainsRe(log, 'revno: 4\n')
+        log = self.run_bzr('log', '-r4', 'file2')[0]
+        self.assertNotContainsRe(log, 'revno: 1\n')
+        self.assertNotContainsRe(log, 'revno: 2\n')
+        self.assertNotContainsRe(log, 'revno: 3\n')
+        self.assertContainsRe(log, 'revno: 3.1.1\n')
+        self.assertContainsRe(log, 'revno: 4\n')
+        log = self.run_bzr('log', '-r3..', 'file2')[0]
+        self.assertNotContainsRe(log, 'revno: 1\n')
+        self.assertNotContainsRe(log, 'revno: 2\n')
+        self.assertNotContainsRe(log, 'revno: 3\n')
+        self.assertContainsRe(log, 'revno: 3.1.1\n')
+        self.assertContainsRe(log, 'revno: 4\n')
+        log = self.run_bzr('log', '-r..3', 'file2')[0]
+        self.assertNotContainsRe(log, 'revno: 1\n')
+        self.assertContainsRe(log, 'revno: 2\n')
+        self.assertNotContainsRe(log, 'revno: 3\n')
+        self.assertNotContainsRe(log, 'revno: 3.1.1\n')
+        self.assertNotContainsRe(log, 'revno: 4\n')

=== modified file 'bzrlib/tests/test_log.py'
--- a/bzrlib/tests/test_log.py	2007-07-02 15:01:18 +0000
+++ b/bzrlib/tests/test_log.py	2007-07-04 03:12:37 +0000
@@ -710,9 +710,9 @@
         self.assertEqual([], delta.removed)
 
     def assertAllRevisionsForFileID(self, tree, file_id, revisions):
-        """Make sure _get_revisions_touching_file_id returns the right values.
+        """Make sure _filter_revisions_touching_file_id returns the right values.
 
-        Get the return value from _get_revisions_touching_file_id and make
+        Get the return value from _filter_revisions_touching_file_id and make
         sure they are correct.
         """
         # The api for _get_revisions_touching_file_id is a little crazy,
@@ -722,9 +722,11 @@
         revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
         view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
                                                 'reverse', True)
-        actual_revs = log._get_revisions_touching_file_id(tree.branch, file_id,
-                                                          mainline,
-                                                          view_revs_iter)
+        actual_revs = log._filter_revisions_touching_file_id(
+                            tree.branch, 
+                            file_id,
+                            mainline,
+                            list(view_revs_iter))
         self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
 
     def test_file_id_f1(self):




More information about the bazaar-commits mailing list