summaryrefslogtreecommitdiff
path: root/bzrlib/tests/test_log.py
diff options
context:
space:
mode:
Diffstat (limited to 'bzrlib/tests/test_log.py')
-rw-r--r--bzrlib/tests/test_log.py1638
1 files changed, 1638 insertions, 0 deletions
diff --git a/bzrlib/tests/test_log.py b/bzrlib/tests/test_log.py
new file mode 100644
index 0000000..08b2e91
--- /dev/null
+++ b/bzrlib/tests/test_log.py
@@ -0,0 +1,1638 @@
+# Copyright (C) 2005-2011 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
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+from cStringIO import StringIO
+
+from bzrlib import (
+ branchbuilder,
+ errors,
+ log,
+ registry,
+ revision,
+ revisionspec,
+ tests,
+ )
+
+
+class TestLogMixin(object):
+
+ def wt_commit(self, wt, message, **kwargs):
+ """Use some mostly fixed values for commits to simplify tests.
+
+ Tests can use this function to get some commit attributes. The time
+ stamp is incremented at each commit.
+ """
+ if getattr(self, 'timestamp', None) is None:
+ self.timestamp = 1132617600 # Mon 2005-11-22 00:00:00 +0000
+ else:
+ self.timestamp += 1 # 1 second between each commit
+ kwargs.setdefault('timestamp', self.timestamp)
+ kwargs.setdefault('timezone', 0) # UTC
+ kwargs.setdefault('committer', 'Joe Foo <joe@foo.com>')
+
+ return wt.commit(message, **kwargs)
+
+
+class TestCaseForLogFormatter(tests.TestCaseWithTransport, TestLogMixin):
+
+ def setUp(self):
+ super(TestCaseForLogFormatter, self).setUp()
+ # keep a reference to the "current" custom prop. handler registry
+ self.properties_handler_registry = log.properties_handler_registry
+ # Use a clean registry for log
+ log.properties_handler_registry = registry.Registry()
+
+ def restore():
+ log.properties_handler_registry = self.properties_handler_registry
+ self.addCleanup(restore)
+
+ def assertFormatterResult(self, result, branch, formatter_class,
+ formatter_kwargs=None, show_log_kwargs=None):
+ logfile = self.make_utf8_encoded_stringio()
+ if formatter_kwargs is None:
+ formatter_kwargs = {}
+ formatter = formatter_class(to_file=logfile, **formatter_kwargs)
+ if show_log_kwargs is None:
+ show_log_kwargs = {}
+ log.show_log(branch, formatter, **show_log_kwargs)
+ self.assertEqualDiff(result, logfile.getvalue())
+
+ def make_standard_commit(self, branch_nick, **kwargs):
+ wt = self.make_branch_and_tree('.')
+ wt.lock_write()
+ self.addCleanup(wt.unlock)
+ self.build_tree(['a'])
+ wt.add(['a'])
+ wt.branch.nick = branch_nick
+ kwargs.setdefault('committer', 'Lorem Ipsum <test@example.com>')
+ kwargs.setdefault('authors', ['John Doe <jdoe@example.com>'])
+ self.wt_commit(wt, 'add a', **kwargs)
+ return wt
+
+ def make_commits_with_trailing_newlines(self, wt):
+ """Helper method for LogFormatter tests"""
+ b = wt.branch
+ b.nick = 'test'
+ self.build_tree_contents([('a', 'hello moto\n')])
+ self.wt_commit(wt, 'simple log message', rev_id='a1')
+ self.build_tree_contents([('b', 'goodbye\n')])
+ wt.add('b')
+ self.wt_commit(wt, 'multiline\nlog\nmessage\n', rev_id='a2')
+
+ self.build_tree_contents([('c', 'just another manic monday\n')])
+ wt.add('c')
+ self.wt_commit(wt, 'single line with trailing newline\n', rev_id='a3')
+ return b
+
+ def _prepare_tree_with_merges(self, with_tags=False):
+ wt = self.make_branch_and_memory_tree('.')
+ wt.lock_write()
+ self.addCleanup(wt.unlock)
+ wt.add('')
+ self.wt_commit(wt, 'rev-1', rev_id='rev-1')
+ self.wt_commit(wt, 'rev-merged', rev_id='rev-2a')
+ wt.set_parent_ids(['rev-1', 'rev-2a'])
+ wt.branch.set_last_revision_info(1, 'rev-1')
+ self.wt_commit(wt, 'rev-2', rev_id='rev-2b')
+ if with_tags:
+ branch = wt.branch
+ branch.tags.set_tag('v0.2', 'rev-2b')
+ self.wt_commit(wt, 'rev-3', rev_id='rev-3')
+ branch.tags.set_tag('v1.0rc1', 'rev-3')
+ branch.tags.set_tag('v1.0', 'rev-3')
+ return wt
+
+
+class LogCatcher(log.LogFormatter):
+ """Pull log messages into a list rather than displaying them.
+
+ To simplify testing we save logged revisions here rather than actually
+ formatting anything, so that we can precisely check the result without
+ being dependent on the formatting.
+ """
+
+ supports_merge_revisions = True
+ supports_delta = True
+ supports_diff = True
+ preferred_levels = 0
+
+ def __init__(self, *args, **kwargs):
+ kwargs.update(dict(to_file=None))
+ super(LogCatcher, self).__init__(*args, **kwargs)
+ self.revisions = []
+
+ def log_revision(self, revision):
+ self.revisions.append(revision)
+
+
+class TestShowLog(tests.TestCaseWithTransport):
+
+ def checkDelta(self, delta, **kw):
+ """Check the filenames touched by a delta are as expected.
+
+ Caller only have to pass in the list of files for each part, all
+ unspecified parts are considered empty (and checked as such).
+ """
+ for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
+ # By default we expect an empty list
+ expected = kw.get(n, [])
+ # strip out only the path components
+ got = [x[0] for x in getattr(delta, n)]
+ self.assertEqual(expected, got)
+
+ def assertInvalidRevisonNumber(self, br, start, end):
+ lf = LogCatcher()
+ self.assertRaises(errors.InvalidRevisionNumber,
+ log.show_log, br, lf,
+ start_revision=start, end_revision=end)
+
+ def test_cur_revno(self):
+ wt = self.make_branch_and_tree('.')
+ b = wt.branch
+
+ lf = LogCatcher()
+ wt.commit('empty commit')
+ log.show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
+
+ # Since there is a single revision in the branch all the combinations
+ # below should fail.
+ self.assertInvalidRevisonNumber(b, 2, 1)
+ self.assertInvalidRevisonNumber(b, 1, 2)
+ self.assertInvalidRevisonNumber(b, 0, 2)
+ self.assertInvalidRevisonNumber(b, 1, 0)
+ self.assertInvalidRevisonNumber(b, -1, 1)
+ self.assertInvalidRevisonNumber(b, 1, -1)
+
+ def test_empty_branch(self):
+ wt = self.make_branch_and_tree('.')
+
+ lf = LogCatcher()
+ log.show_log(wt.branch, lf)
+ # no entries yet
+ self.assertEqual([], lf.revisions)
+
+ def test_empty_commit(self):
+ wt = self.make_branch_and_tree('.')
+
+ wt.commit('empty commit')
+ lf = LogCatcher()
+ log.show_log(wt.branch, lf, verbose=True)
+ revs = lf.revisions
+ self.assertEqual(1, len(revs))
+ self.assertEqual('1', revs[0].revno)
+ self.assertEqual('empty commit', revs[0].rev.message)
+ self.checkDelta(revs[0].delta)
+
+ def test_simple_commit(self):
+ wt = self.make_branch_and_tree('.')
+ wt.commit('empty commit')
+ self.build_tree(['hello'])
+ wt.add('hello')
+ wt.commit('add one file',
+ committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
+ u'<test@example.com>')
+ lf = LogCatcher()
+ log.show_log(wt.branch, lf, verbose=True)
+ self.assertEqual(2, len(lf.revisions))
+ # first one is most recent
+ log_entry = lf.revisions[0]
+ self.assertEqual('2', log_entry.revno)
+ self.assertEqual('add one file', log_entry.rev.message)
+ self.checkDelta(log_entry.delta, added=['hello'])
+
+ def test_commit_message_with_control_chars(self):
+ wt = self.make_branch_and_tree('.')
+ msg = u"All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
+ msg = msg.replace(u'\r', u'\n')
+ wt.commit(msg)
+ lf = LogCatcher()
+ log.show_log(wt.branch, lf, verbose=True)
+ committed_msg = lf.revisions[0].rev.message
+ if wt.branch.repository._serializer.squashes_xml_invalid_characters:
+ self.assertNotEqual(msg, committed_msg)
+ self.assertTrue(len(committed_msg) > len(msg))
+ else:
+ self.assertEqual(msg, committed_msg)
+
+ def test_commit_message_without_control_chars(self):
+ wt = self.make_branch_and_tree('.')
+ # escaped. As ElementTree apparently does some kind of
+ # newline conversion, neither LF (\x0A) nor CR (\x0D) are
+ # included in the test commit message, even though they are
+ # valid XML 1.0 characters.
+ msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
+ wt.commit(msg)
+ lf = LogCatcher()
+ log.show_log(wt.branch, lf, verbose=True)
+ committed_msg = lf.revisions[0].rev.message
+ self.assertEqual(msg, committed_msg)
+
+ def test_deltas_in_merge_revisions(self):
+ """Check deltas created for both mainline and merge revisions"""
+ wt = self.make_branch_and_tree('parent')
+ self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
+ wt.add('file1')
+ wt.add('file2')
+ wt.commit(message='add file1 and file2')
+ self.run_bzr('branch parent child')
+ os.unlink('child/file1')
+ with file('child/file2', 'wb') as f: f.write('hello\n')
+ self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
+ 'child'])
+ os.chdir('parent')
+ self.run_bzr('merge ../child')
+ wt.commit('merge child branch')
+ os.chdir('..')
+ b = wt.branch
+ lf = LogCatcher()
+ lf.supports_merge_revisions = True
+ log.show_log(b, lf, verbose=True)
+
+ revs = lf.revisions
+ self.assertEqual(3, len(revs))
+
+ logentry = revs[0]
+ self.assertEqual('2', logentry.revno)
+ self.assertEqual('merge child branch', logentry.rev.message)
+ self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
+
+ logentry = revs[1]
+ self.assertEqual('1.1.1', logentry.revno)
+ self.assertEqual('remove file1 and modify file2', logentry.rev.message)
+ self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
+
+ logentry = revs[2]
+ self.assertEqual('1', logentry.revno)
+ self.assertEqual('add file1 and file2', logentry.rev.message)
+ self.checkDelta(logentry.delta, added=['file1', 'file2'])
+
+
+class TestShortLogFormatter(TestCaseForLogFormatter):
+
+ def test_trailing_newlines(self):
+ wt = self.make_branch_and_tree('.')
+ b = self.make_commits_with_trailing_newlines(wt)
+ self.assertFormatterResult("""\
+ 3 Joe Foo\t2005-11-22
+ single line with trailing newline
+
+ 2 Joe Foo\t2005-11-22
+ multiline
+ log
+ message
+
+ 1 Joe Foo\t2005-11-22
+ simple log message
+
+""",
+ b, log.ShortLogFormatter)
+
+ def test_short_log_with_merges(self):
+ wt = self._prepare_tree_with_merges()
+ self.assertFormatterResult("""\
+ 2 Joe Foo\t2005-11-22 [merge]
+ rev-2
+
+ 1 Joe Foo\t2005-11-22
+ rev-1
+
+""",
+ wt.branch, log.ShortLogFormatter)
+
+ def test_short_log_with_merges_and_advice(self):
+ wt = self._prepare_tree_with_merges()
+ self.assertFormatterResult("""\
+ 2 Joe Foo\t2005-11-22 [merge]
+ rev-2
+
+ 1 Joe Foo\t2005-11-22
+ rev-1
+
+Use --include-merged or -n0 to see merged revisions.
+""",
+ wt.branch, log.ShortLogFormatter,
+ formatter_kwargs=dict(show_advice=True))
+
+ def test_short_log_with_merges_and_range(self):
+ wt = self._prepare_tree_with_merges()
+ self.wt_commit(wt, 'rev-3a', rev_id='rev-3a')
+ wt.branch.set_last_revision_info(2, 'rev-2b')
+ wt.set_parent_ids(['rev-2b', 'rev-3a'])
+ self.wt_commit(wt, 'rev-3b', rev_id='rev-3b')
+ self.assertFormatterResult("""\
+ 3 Joe Foo\t2005-11-22 [merge]
+ rev-3b
+
+ 2 Joe Foo\t2005-11-22 [merge]
+ rev-2
+
+""",
+ wt.branch, log.ShortLogFormatter,
+ show_log_kwargs=dict(start_revision=2, end_revision=3))
+
+ def test_short_log_with_tags(self):
+ wt = self._prepare_tree_with_merges(with_tags=True)
+ self.assertFormatterResult("""\
+ 3 Joe Foo\t2005-11-22 {v1.0, v1.0rc1}
+ rev-3
+
+ 2 Joe Foo\t2005-11-22 {v0.2} [merge]
+ rev-2
+
+ 1 Joe Foo\t2005-11-22
+ rev-1
+
+""",
+ wt.branch, log.ShortLogFormatter)
+
+ def test_short_log_single_merge_revision(self):
+ wt = self._prepare_tree_with_merges()
+ revspec = revisionspec.RevisionSpec.from_string('1.1.1')
+ rev = revspec.in_history(wt.branch)
+ self.assertFormatterResult("""\
+ 1.1.1 Joe Foo\t2005-11-22
+ rev-merged
+
+""",
+ wt.branch, log.ShortLogFormatter,
+ show_log_kwargs=dict(start_revision=rev, end_revision=rev))
+
+ def test_show_ids(self):
+ wt = self.make_branch_and_tree('parent')
+ self.build_tree(['parent/f1', 'parent/f2'])
+ wt.add(['f1','f2'])
+ self.wt_commit(wt, 'first post', rev_id='a')
+ child_wt = wt.bzrdir.sprout('child').open_workingtree()
+ self.wt_commit(child_wt, 'branch 1 changes', rev_id='b')
+ wt.merge_from_branch(child_wt.branch)
+ self.wt_commit(wt, 'merge branch 1', rev_id='c')
+ self.assertFormatterResult("""\
+ 2 Joe Foo\t2005-11-22 [merge]
+ revision-id:c
+ merge branch 1
+
+ 1.1.1 Joe Foo\t2005-11-22
+ revision-id:b
+ branch 1 changes
+
+ 1 Joe Foo\t2005-11-22
+ revision-id:a
+ first post
+
+""",
+ wt.branch, log.ShortLogFormatter,
+ formatter_kwargs=dict(levels=0,show_ids=True))
+
+
+class TestShortLogFormatterWithMergeRevisions(TestCaseForLogFormatter):
+
+ def test_short_merge_revs_log_with_merges(self):
+ wt = self._prepare_tree_with_merges()
+ # Note that the 1.1.1 indenting is in fact correct given that
+ # the revision numbers are right justified within 5 characters
+ # for mainline revnos and 9 characters for dotted revnos.
+ self.assertFormatterResult("""\
+ 2 Joe Foo\t2005-11-22 [merge]
+ rev-2
+
+ 1.1.1 Joe Foo\t2005-11-22
+ rev-merged
+
+ 1 Joe Foo\t2005-11-22
+ rev-1
+
+""",
+ wt.branch, log.ShortLogFormatter,
+ formatter_kwargs=dict(levels=0))
+
+ def test_short_merge_revs_log_single_merge_revision(self):
+ wt = self._prepare_tree_with_merges()
+ revspec = revisionspec.RevisionSpec.from_string('1.1.1')
+ rev = revspec.in_history(wt.branch)
+ self.assertFormatterResult("""\
+ 1.1.1 Joe Foo\t2005-11-22
+ rev-merged
+
+""",
+ wt.branch, log.ShortLogFormatter,
+ formatter_kwargs=dict(levels=0),
+ show_log_kwargs=dict(start_revision=rev, end_revision=rev))
+
+
+class TestLongLogFormatter(TestCaseForLogFormatter):
+
+ def test_verbose_log(self):
+ """Verbose log includes changed files
+
+ bug #4676
+ """
+ wt = self.make_standard_commit('test_verbose_log', authors=[])
+ self.assertFormatterResult('''\
+------------------------------------------------------------
+revno: 1
+committer: Lorem Ipsum <test@example.com>
+branch nick: test_verbose_log
+timestamp: Tue 2005-11-22 00:00:00 +0000
+message:
+ add a
+added:
+ a
+''',
+ wt.branch, log.LongLogFormatter,
+ show_log_kwargs=dict(verbose=True))
+
+ def test_merges_are_indented_by_level(self):
+ wt = self.make_branch_and_tree('parent')
+ self.wt_commit(wt, 'first post')
+ child_wt = wt.bzrdir.sprout('child').open_workingtree()
+ self.wt_commit(child_wt, 'branch 1')
+ smallerchild_wt = wt.bzrdir.sprout('smallerchild').open_workingtree()
+ self.wt_commit(smallerchild_wt, 'branch 2')
+ child_wt.merge_from_branch(smallerchild_wt.branch)
+ self.wt_commit(child_wt, 'merge branch 2')
+ wt.merge_from_branch(child_wt.branch)
+ self.wt_commit(wt, 'merge branch 1')
+ self.assertFormatterResult("""\
+------------------------------------------------------------
+revno: 2 [merge]
+committer: Joe Foo <joe@foo.com>
+branch nick: parent
+timestamp: Tue 2005-11-22 00:00:04 +0000
+message:
+ merge branch 1
+ ------------------------------------------------------------
+ revno: 1.1.2 [merge]
+ committer: Joe Foo <joe@foo.com>
+ branch nick: child
+ timestamp: Tue 2005-11-22 00:00:03 +0000
+ message:
+ merge branch 2
+ ------------------------------------------------------------
+ revno: 1.2.1
+ committer: Joe Foo <joe@foo.com>
+ branch nick: smallerchild
+ timestamp: Tue 2005-11-22 00:00:02 +0000
+ message:
+ branch 2
+ ------------------------------------------------------------
+ revno: 1.1.1
+ committer: Joe Foo <joe@foo.com>
+ branch nick: child
+ timestamp: Tue 2005-11-22 00:00:01 +0000
+ message:
+ branch 1
+------------------------------------------------------------
+revno: 1
+committer: Joe Foo <joe@foo.com>
+branch nick: parent
+timestamp: Tue 2005-11-22 00:00:00 +0000
+message:
+ first post
+""",
+ wt.branch, log.LongLogFormatter,
+ formatter_kwargs=dict(levels=0),
+ show_log_kwargs=dict(verbose=True))
+
+ def test_verbose_merge_revisions_contain_deltas(self):
+ wt = self.make_branch_and_tree('parent')
+ self.build_tree(['parent/f1', 'parent/f2'])
+ wt.add(['f1','f2'])
+ self.wt_commit(wt, 'first post')
+ child_wt = wt.bzrdir.sprout('child').open_workingtree()
+ os.unlink('child/f1')
+ self.build_tree_contents([('child/f2', 'hello\n')])
+ self.wt_commit(child_wt, 'removed f1 and modified f2')
+ wt.merge_from_branch(child_wt.branch)
+ self.wt_commit(wt, 'merge branch 1')
+ self.assertFormatterResult("""\
+------------------------------------------------------------
+revno: 2 [merge]
+committer: Joe Foo <joe@foo.com>
+branch nick: parent
+timestamp: Tue 2005-11-22 00:00:02 +0000
+message:
+ merge branch 1
+removed:
+ f1
+modified:
+ f2
+ ------------------------------------------------------------
+ revno: 1.1.1
+ committer: Joe Foo <joe@foo.com>
+ branch nick: child
+ timestamp: Tue 2005-11-22 00:00:01 +0000
+ message:
+ removed f1 and modified f2
+ removed:
+ f1
+ modified:
+ f2
+------------------------------------------------------------
+revno: 1
+committer: Joe Foo <joe@foo.com>
+branch nick: parent
+timestamp: Tue 2005-11-22 00:00:00 +0000
+message:
+ first post
+added:
+ f1
+ f2
+""",
+ wt.branch, log.LongLogFormatter,
+ formatter_kwargs=dict(levels=0),
+ show_log_kwargs=dict(verbose=True))
+
+ def test_trailing_newlines(self):
+ wt = self.make_branch_and_tree('.')
+ b = self.make_commits_with_trailing_newlines(wt)
+ self.assertFormatterResult("""\
+------------------------------------------------------------
+revno: 3
+committer: Joe Foo <joe@foo.com>
+branch nick: test
+timestamp: Tue 2005-11-22 00:00:02 +0000
+message:
+ single line with trailing newline
+------------------------------------------------------------
+revno: 2
+committer: Joe Foo <joe@foo.com>
+branch nick: test
+timestamp: Tue 2005-11-22 00:00:01 +0000
+message:
+ multiline
+ log
+ message
+------------------------------------------------------------
+revno: 1
+committer: Joe Foo <joe@foo.com>
+branch nick: test
+timestamp: Tue 2005-11-22 00:00:00 +0000
+message:
+ simple log message
+""",
+ b, log.LongLogFormatter)
+
+ def test_author_in_log(self):
+ """Log includes the author name if it's set in
+ the revision properties
+ """
+ wt = self.make_standard_commit('test_author_log',
+ authors=['John Doe <jdoe@example.com>',
+ 'Jane Rey <jrey@example.com>'])
+ self.assertFormatterResult("""\
+------------------------------------------------------------
+revno: 1
+author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
+committer: Lorem Ipsum <test@example.com>
+branch nick: test_author_log
+timestamp: Tue 2005-11-22 00:00:00 +0000
+message:
+ add a
+""",
+ wt.branch, log.LongLogFormatter)
+
+ def test_properties_in_log(self):
+ """Log includes the custom properties returned by the registered
+ handlers.
+ """
+ wt = self.make_standard_commit('test_properties_in_log')
+ def trivial_custom_prop_handler(revision):
+ return {'test_prop':'test_value'}
+
+ # Cleaned up in setUp()
+ log.properties_handler_registry.register(
+ 'trivial_custom_prop_handler',
+ trivial_custom_prop_handler)
+ self.assertFormatterResult("""\
+------------------------------------------------------------
+revno: 1
+test_prop: test_value
+author: John Doe <jdoe@example.com>
+committer: Lorem Ipsum <test@example.com>
+branch nick: test_properties_in_log
+timestamp: Tue 2005-11-22 00:00:00 +0000
+message:
+ add a
+""",
+ wt.branch, log.LongLogFormatter)
+
+ def test_properties_in_short_log(self):
+ """Log includes the custom properties returned by the registered
+ handlers.
+ """
+ wt = self.make_standard_commit('test_properties_in_short_log')
+ def trivial_custom_prop_handler(revision):
+ return {'test_prop':'test_value'}
+
+ log.properties_handler_registry.register(
+ 'trivial_custom_prop_handler',
+ trivial_custom_prop_handler)
+ self.assertFormatterResult("""\
+ 1 John Doe\t2005-11-22
+ test_prop: test_value
+ add a
+
+""",
+ wt.branch, log.ShortLogFormatter)
+
+ def test_error_in_properties_handler(self):
+ """Log includes the custom properties returned by the registered
+ handlers.
+ """
+ wt = self.make_standard_commit('error_in_properties_handler',
+ revprops={'first_prop':'first_value'})
+ sio = self.make_utf8_encoded_stringio()
+ formatter = log.LongLogFormatter(to_file=sio)
+ def trivial_custom_prop_handler(revision):
+ raise StandardError("a test error")
+
+ log.properties_handler_registry.register(
+ 'trivial_custom_prop_handler',
+ trivial_custom_prop_handler)
+ self.assertRaises(StandardError, log.show_log, wt.branch, formatter,)
+
+ def test_properties_handler_bad_argument(self):
+ wt = self.make_standard_commit('bad_argument',
+ revprops={'a_prop':'test_value'})
+ sio = self.make_utf8_encoded_stringio()
+ formatter = log.LongLogFormatter(to_file=sio)
+ def bad_argument_prop_handler(revision):
+ return {'custom_prop_name':revision.properties['a_prop']}
+
+ log.properties_handler_registry.register(
+ 'bad_argument_prop_handler',
+ bad_argument_prop_handler)
+
+ self.assertRaises(AttributeError, formatter.show_properties,
+ 'a revision', '')
+
+ revision = wt.branch.repository.get_revision(wt.branch.last_revision())
+ formatter.show_properties(revision, '')
+ self.assertEqualDiff('''custom_prop_name: test_value\n''',
+ sio.getvalue())
+
+ def test_show_ids(self):
+ wt = self.make_branch_and_tree('parent')
+ self.build_tree(['parent/f1', 'parent/f2'])
+ wt.add(['f1','f2'])
+ self.wt_commit(wt, 'first post', rev_id='a')
+ child_wt = wt.bzrdir.sprout('child').open_workingtree()
+ self.wt_commit(child_wt, 'branch 1 changes', rev_id='b')
+ wt.merge_from_branch(child_wt.branch)
+ self.wt_commit(wt, 'merge branch 1', rev_id='c')
+ self.assertFormatterResult("""\
+------------------------------------------------------------
+revno: 2 [merge]
+revision-id: c
+parent: a
+parent: b
+committer: Joe Foo <joe@foo.com>
+branch nick: parent
+timestamp: Tue 2005-11-22 00:00:02 +0000
+message:
+ merge branch 1
+ ------------------------------------------------------------
+ revno: 1.1.1
+ revision-id: b
+ parent: a
+ committer: Joe Foo <joe@foo.com>
+ branch nick: child
+ timestamp: Tue 2005-11-22 00:00:01 +0000
+ message:
+ branch 1 changes
+------------------------------------------------------------
+revno: 1
+revision-id: a
+committer: Joe Foo <joe@foo.com>
+branch nick: parent
+timestamp: Tue 2005-11-22 00:00:00 +0000
+message:
+ first post
+""",
+ wt.branch, log.LongLogFormatter,
+ formatter_kwargs=dict(levels=0,show_ids=True))
+
+
+class TestLongLogFormatterWithoutMergeRevisions(TestCaseForLogFormatter):
+
+ def test_long_verbose_log(self):
+ """Verbose log includes changed files
+
+ bug #4676
+ """
+ wt = self.make_standard_commit('test_long_verbose_log', authors=[])
+ self.assertFormatterResult("""\
+------------------------------------------------------------
+revno: 1
+committer: Lorem Ipsum <test@example.com>
+branch nick: test_long_verbose_log
+timestamp: Tue 2005-11-22 00:00:00 +0000
+message:
+ add a
+added:
+ a
+""",
+ wt.branch, log.LongLogFormatter,
+ formatter_kwargs=dict(levels=1),
+ show_log_kwargs=dict(verbose=True))
+
+ def test_long_verbose_contain_deltas(self):
+ wt = self.make_branch_and_tree('parent')
+ self.build_tree(['parent/f1', 'parent/f2'])
+ wt.add(['f1','f2'])
+ self.wt_commit(wt, 'first post')
+ child_wt = wt.bzrdir.sprout('child').open_workingtree()
+ os.unlink('child/f1')
+ self.build_tree_contents([('child/f2', 'hello\n')])
+ self.wt_commit(child_wt, 'removed f1 and modified f2')
+ wt.merge_from_branch(child_wt.branch)
+ self.wt_commit(wt, 'merge branch 1')
+ self.assertFormatterResult("""\
+------------------------------------------------------------
+revno: 2 [merge]
+committer: Joe Foo <joe@foo.com>
+branch nick: parent
+timestamp: Tue 2005-11-22 00:00:02 +0000
+message:
+ merge branch 1
+removed:
+ f1
+modified:
+ f2
+------------------------------------------------------------
+revno: 1
+committer: Joe Foo <joe@foo.com>
+branch nick: parent
+timestamp: Tue 2005-11-22 00:00:00 +0000
+message:
+ first post
+added:
+ f1
+ f2
+""",
+ wt.branch, log.LongLogFormatter,
+ formatter_kwargs=dict(levels=1),
+ show_log_kwargs=dict(verbose=True))
+
+ def test_long_trailing_newlines(self):
+ wt = self.make_branch_and_tree('.')
+ b = self.make_commits_with_trailing_newlines(wt)
+ self.assertFormatterResult("""\
+------------------------------------------------------------
+revno: 3
+committer: Joe Foo <joe@foo.com>
+branch nick: test
+timestamp: Tue 2005-11-22 00:00:02 +0000
+message:
+ single line with trailing newline
+------------------------------------------------------------
+revno: 2
+committer: Joe Foo <joe@foo.com>
+branch nick: test
+timestamp: Tue 2005-11-22 00:00:01 +0000
+message:
+ multiline
+ log
+ message
+------------------------------------------------------------
+revno: 1
+committer: Joe Foo <joe@foo.com>
+branch nick: test
+timestamp: Tue 2005-11-22 00:00:00 +0000
+message:
+ simple log message
+""",
+ b, log.LongLogFormatter,
+ formatter_kwargs=dict(levels=1))
+
+ def test_long_author_in_log(self):
+ """Log includes the author name if it's set in
+ the revision properties
+ """
+ wt = self.make_standard_commit('test_author_log')
+ self.assertFormatterResult("""\
+------------------------------------------------------------
+revno: 1
+author: John Doe <jdoe@example.com>
+committer: Lorem Ipsum <test@example.com>
+branch nick: test_author_log
+timestamp: Tue 2005-11-22 00:00:00 +0000
+message:
+ add a
+""",
+ wt.branch, log.LongLogFormatter,
+ formatter_kwargs=dict(levels=1))
+
+ def test_long_properties_in_log(self):
+ """Log includes the custom properties returned by the registered
+ handlers.
+ """
+ wt = self.make_standard_commit('test_properties_in_log')
+ def trivial_custom_prop_handler(revision):
+ return {'test_prop':'test_value'}
+
+ log.properties_handler_registry.register(
+ 'trivial_custom_prop_handler',
+ trivial_custom_prop_handler)
+ self.assertFormatterResult("""\
+------------------------------------------------------------
+revno: 1
+test_prop: test_value
+author: John Doe <jdoe@example.com>
+committer: Lorem Ipsum <test@example.com>
+branch nick: test_properties_in_log
+timestamp: Tue 2005-11-22 00:00:00 +0000
+message:
+ add a
+""",
+ wt.branch, log.LongLogFormatter,
+ formatter_kwargs=dict(levels=1))
+
+
+class TestLineLogFormatter(TestCaseForLogFormatter):
+
+ def test_line_log(self):
+ """Line log should show revno
+
+ bug #5162
+ """
+ wt = self.make_standard_commit('test-line-log',
+ committer='Line-Log-Formatter Tester <test@line.log>',
+ authors=[])
+ self.assertFormatterResult("""\
+1: Line-Log-Formatte... 2005-11-22 add a
+""",
+ wt.branch, log.LineLogFormatter)
+
+ def test_trailing_newlines(self):
+ wt = self.make_branch_and_tree('.')
+ b = self.make_commits_with_trailing_newlines(wt)
+ self.assertFormatterResult("""\
+3: Joe Foo 2005-11-22 single line with trailing newline
+2: Joe Foo 2005-11-22 multiline
+1: Joe Foo 2005-11-22 simple log message
+""",
+ b, log.LineLogFormatter)
+
+ def test_line_log_single_merge_revision(self):
+ wt = self._prepare_tree_with_merges()
+ revspec = revisionspec.RevisionSpec.from_string('1.1.1')
+ rev = revspec.in_history(wt.branch)
+ self.assertFormatterResult("""\
+1.1.1: Joe Foo 2005-11-22 rev-merged
+""",
+ wt.branch, log.LineLogFormatter,
+ show_log_kwargs=dict(start_revision=rev, end_revision=rev))
+
+ def test_line_log_with_tags(self):
+ wt = self._prepare_tree_with_merges(with_tags=True)
+ self.assertFormatterResult("""\
+3: Joe Foo 2005-11-22 {v1.0, v1.0rc1} rev-3
+2: Joe Foo 2005-11-22 [merge] {v0.2} rev-2
+1: Joe Foo 2005-11-22 rev-1
+""",
+ wt.branch, log.LineLogFormatter)
+
+
+class TestLineLogFormatterWithMergeRevisions(TestCaseForLogFormatter):
+
+ def test_line_merge_revs_log(self):
+ """Line log should show revno
+
+ bug #5162
+ """
+ wt = self.make_standard_commit('test-line-log',
+ committer='Line-Log-Formatter Tester <test@line.log>',
+ authors=[])
+ self.assertFormatterResult("""\
+1: Line-Log-Formatte... 2005-11-22 add a
+""",
+ wt.branch, log.LineLogFormatter)
+
+ def test_line_merge_revs_log_single_merge_revision(self):
+ wt = self._prepare_tree_with_merges()
+ revspec = revisionspec.RevisionSpec.from_string('1.1.1')
+ rev = revspec.in_history(wt.branch)
+ self.assertFormatterResult("""\
+1.1.1: Joe Foo 2005-11-22 rev-merged
+""",
+ wt.branch, log.LineLogFormatter,
+ formatter_kwargs=dict(levels=0),
+ show_log_kwargs=dict(start_revision=rev, end_revision=rev))
+
+ def test_line_merge_revs_log_with_merges(self):
+ wt = self._prepare_tree_with_merges()
+ self.assertFormatterResult("""\
+2: Joe Foo 2005-11-22 [merge] rev-2
+ 1.1.1: Joe Foo 2005-11-22 rev-merged
+1: Joe Foo 2005-11-22 rev-1
+""",
+ wt.branch, log.LineLogFormatter,
+ formatter_kwargs=dict(levels=0))
+
+
+class TestGnuChangelogFormatter(TestCaseForLogFormatter):
+
+ def test_gnu_changelog(self):
+ wt = self.make_standard_commit('nicky', authors=[])
+ self.assertFormatterResult('''\
+2005-11-22 Lorem Ipsum <test@example.com>
+
+\tadd a
+
+''',
+ wt.branch, log.GnuChangelogLogFormatter)
+
+ def test_with_authors(self):
+ wt = self.make_standard_commit('nicky',
+ authors=['Fooa Fooz <foo@example.com>',
+ 'Bari Baro <bar@example.com>'])
+ self.assertFormatterResult('''\
+2005-11-22 Fooa Fooz <foo@example.com>
+
+\tadd a
+
+''',
+ wt.branch, log.GnuChangelogLogFormatter)
+
+ def test_verbose(self):
+ wt = self.make_standard_commit('nicky')
+ self.assertFormatterResult('''\
+2005-11-22 John Doe <jdoe@example.com>
+
+\t* a:
+
+\tadd a
+
+''',
+ wt.branch, log.GnuChangelogLogFormatter,
+ show_log_kwargs=dict(verbose=True))
+
+
+class TestShowChangedRevisions(tests.TestCaseWithTransport):
+
+ def test_show_changed_revisions_verbose(self):
+ tree = self.make_branch_and_tree('tree_a')
+ self.build_tree(['tree_a/foo'])
+ tree.add('foo')
+ tree.commit('bar', rev_id='bar-id')
+ s = self.make_utf8_encoded_stringio()
+ log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
+ self.assertContainsRe(s.getvalue(), 'bar')
+ self.assertNotContainsRe(s.getvalue(), 'foo')
+
+
+class TestLogFormatter(tests.TestCase):
+
+ def setUp(self):
+ super(TestLogFormatter, self).setUp()
+ self.rev = revision.Revision('a-id')
+ self.lf = log.LogFormatter(None)
+
+ def test_short_committer(self):
+ def assertCommitter(expected, committer):
+ self.rev.committer = committer
+ self.assertEqual(expected, self.lf.short_committer(self.rev))
+
+ assertCommitter('John Doe', 'John Doe <jdoe@example.com>')
+ assertCommitter('John Smith', 'John Smith <jsmith@example.com>')
+ assertCommitter('John Smith', 'John Smith')
+ assertCommitter('jsmith@example.com', 'jsmith@example.com')
+ assertCommitter('jsmith@example.com', '<jsmith@example.com>')
+ assertCommitter('John Smith', 'John Smith jsmith@example.com')
+
+ def test_short_author(self):
+ def assertAuthor(expected, author):
+ self.rev.properties['author'] = author
+ self.assertEqual(expected, self.lf.short_author(self.rev))
+
+ assertAuthor('John Smith', 'John Smith <jsmith@example.com>')
+ assertAuthor('John Smith', 'John Smith')
+ assertAuthor('jsmith@example.com', 'jsmith@example.com')
+ assertAuthor('jsmith@example.com', '<jsmith@example.com>')
+ assertAuthor('John Smith', 'John Smith jsmith@example.com')
+
+ def test_short_author_from_committer(self):
+ self.rev.committer = 'John Doe <jdoe@example.com>'
+ self.assertEqual('John Doe', self.lf.short_author(self.rev))
+
+ def test_short_author_from_authors(self):
+ self.rev.properties['authors'] = ('John Smith <jsmith@example.com>\n'
+ 'Jane Rey <jrey@example.com>')
+ self.assertEqual('John Smith', self.lf.short_author(self.rev))
+
+
+class TestReverseByDepth(tests.TestCase):
+ """Test reverse_by_depth behavior.
+
+ This is used to present revisions in forward (oldest first) order in a nice
+ layout.
+
+ The tests use lighter revision description to ease reading.
+ """
+
+ def assertReversed(self, forward, backward):
+ # Transform the descriptions to suit the API: tests use (revno, depth),
+ # while the API expects (revid, revno, depth)
+ def complete_revisions(l):
+ """Transform the description to suit the API.
+
+ Tests use (revno, depth) whil the API expects (revid, revno, depth).
+ Since the revid is arbitrary, we just duplicate revno
+ """
+ return [ (r, r, d) for r, d in l]
+ forward = complete_revisions(forward)
+ backward= complete_revisions(backward)
+ self.assertEqual(forward, log.reverse_by_depth(backward))
+
+
+ def test_mainline_revisions(self):
+ self.assertReversed([( '1', 0), ('2', 0)],
+ [('2', 0), ('1', 0)])
+
+ def test_merged_revisions(self):
+ self.assertReversed([('1', 0), ('2', 0), ('2.2', 1), ('2.1', 1),],
+ [('2', 0), ('2.1', 1), ('2.2', 1), ('1', 0),])
+ def test_shifted_merged_revisions(self):
+ """Test irregular layout.
+
+ Requesting revisions touching a file can produce "holes" in the depths.
+ """
+ self.assertReversed([('1', 0), ('2', 0), ('1.1', 2), ('1.2', 2),],
+ [('2', 0), ('1.2', 2), ('1.1', 2), ('1', 0),])
+
+ def test_merged_without_child_revisions(self):
+ """Test irregular layout.
+
+ Revision ranges can produce "holes" in the depths.
+ """
+ # When a revision of higher depth doesn't follow one of lower depth, we
+ # assume a lower depth one is virtually there
+ self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
+ [('4', 4), ('3', 3), ('2', 2), ('1', 2),])
+ # So we get the same order after reversing below even if the original
+ # revisions are not in the same order.
+ self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
+ [('3', 3), ('4', 4), ('2', 2), ('1', 2),])
+
+
+class TestHistoryChange(tests.TestCaseWithTransport):
+
+ def setup_a_tree(self):
+ tree = self.make_branch_and_tree('tree')
+ tree.lock_write()
+ self.addCleanup(tree.unlock)
+ tree.commit('1a', rev_id='1a')
+ tree.commit('2a', rev_id='2a')
+ tree.commit('3a', rev_id='3a')
+ return tree
+
+ def setup_ab_tree(self):
+ tree = self.setup_a_tree()
+ tree.set_last_revision('1a')
+ tree.branch.set_last_revision_info(1, '1a')
+ tree.commit('2b', rev_id='2b')
+ tree.commit('3b', rev_id='3b')
+ return tree
+
+ def setup_ac_tree(self):
+ tree = self.setup_a_tree()
+ tree.set_last_revision(revision.NULL_REVISION)
+ tree.branch.set_last_revision_info(0, revision.NULL_REVISION)
+ tree.commit('1c', rev_id='1c')
+ tree.commit('2c', rev_id='2c')
+ tree.commit('3c', rev_id='3c')
+ return tree
+
+ def test_all_new(self):
+ tree = self.setup_ab_tree()
+ old, new = log.get_history_change('1a', '3a', tree.branch.repository)
+ self.assertEqual([], old)
+ self.assertEqual(['2a', '3a'], new)
+
+ def test_all_old(self):
+ tree = self.setup_ab_tree()
+ old, new = log.get_history_change('3a', '1a', tree.branch.repository)
+ self.assertEqual([], new)
+ self.assertEqual(['2a', '3a'], old)
+
+ def test_null_old(self):
+ tree = self.setup_ab_tree()
+ old, new = log.get_history_change(revision.NULL_REVISION,
+ '3a', tree.branch.repository)
+ self.assertEqual([], old)
+ self.assertEqual(['1a', '2a', '3a'], new)
+
+ def test_null_new(self):
+ tree = self.setup_ab_tree()
+ old, new = log.get_history_change('3a', revision.NULL_REVISION,
+ tree.branch.repository)
+ self.assertEqual([], new)
+ self.assertEqual(['1a', '2a', '3a'], old)
+
+ def test_diverged(self):
+ tree = self.setup_ab_tree()
+ old, new = log.get_history_change('3a', '3b', tree.branch.repository)
+ self.assertEqual(old, ['2a', '3a'])
+ self.assertEqual(new, ['2b', '3b'])
+
+ def test_unrelated(self):
+ tree = self.setup_ac_tree()
+ old, new = log.get_history_change('3a', '3c', tree.branch.repository)
+ self.assertEqual(old, ['1a', '2a', '3a'])
+ self.assertEqual(new, ['1c', '2c', '3c'])
+
+ def test_show_branch_change(self):
+ tree = self.setup_ab_tree()
+ s = StringIO()
+ log.show_branch_change(tree.branch, s, 3, '3a')
+ self.assertContainsRe(s.getvalue(),
+ '[*]{60}\nRemoved Revisions:\n(.|\n)*2a(.|\n)*3a(.|\n)*'
+ '[*]{60}\n\nAdded Revisions:\n(.|\n)*2b(.|\n)*3b')
+
+ def test_show_branch_change_no_change(self):
+ tree = self.setup_ab_tree()
+ s = StringIO()
+ log.show_branch_change(tree.branch, s, 3, '3b')
+ self.assertEqual(s.getvalue(),
+ 'Nothing seems to have changed\n')
+
+ def test_show_branch_change_no_old(self):
+ tree = self.setup_ab_tree()
+ s = StringIO()
+ log.show_branch_change(tree.branch, s, 2, '2b')
+ self.assertContainsRe(s.getvalue(), 'Added Revisions:')
+ self.assertNotContainsRe(s.getvalue(), 'Removed Revisions:')
+
+ def test_show_branch_change_no_new(self):
+ tree = self.setup_ab_tree()
+ tree.branch.set_last_revision_info(2, '2b')
+ s = StringIO()
+ log.show_branch_change(tree.branch, s, 3, '3b')
+ self.assertContainsRe(s.getvalue(), 'Removed Revisions:')
+ self.assertNotContainsRe(s.getvalue(), 'Added Revisions:')
+
+
+class TestRevisionNotInBranch(TestCaseForLogFormatter):
+
+ def setup_a_tree(self):
+ tree = self.make_branch_and_tree('tree')
+ tree.lock_write()
+ self.addCleanup(tree.unlock)
+ kwargs = {
+ 'committer': 'Joe Foo <joe@foo.com>',
+ 'timestamp': 1132617600, # Mon 2005-11-22 00:00:00 +0000
+ 'timezone': 0, # UTC
+ }
+ tree.commit('commit 1a', rev_id='1a', **kwargs)
+ tree.commit('commit 2a', rev_id='2a', **kwargs)
+ tree.commit('commit 3a', rev_id='3a', **kwargs)
+ return tree
+
+ def setup_ab_tree(self):
+ tree = self.setup_a_tree()
+ tree.set_last_revision('1a')
+ tree.branch.set_last_revision_info(1, '1a')
+ kwargs = {
+ 'committer': 'Joe Foo <joe@foo.com>',
+ 'timestamp': 1132617600, # Mon 2005-11-22 00:00:00 +0000
+ 'timezone': 0, # UTC
+ }
+ tree.commit('commit 2b', rev_id='2b', **kwargs)
+ tree.commit('commit 3b', rev_id='3b', **kwargs)
+ return tree
+
+ def test_one_revision(self):
+ tree = self.setup_ab_tree()
+ lf = LogCatcher()
+ rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
+ log.show_log(tree.branch, lf, verbose=True, start_revision=rev,
+ end_revision=rev)
+ self.assertEqual(1, len(lf.revisions))
+ self.assertEqual(None, lf.revisions[0].revno) # Out-of-branch
+ self.assertEqual('3a', lf.revisions[0].rev.revision_id)
+
+ def test_many_revisions(self):
+ tree = self.setup_ab_tree()
+ lf = LogCatcher()
+ start_rev = revisionspec.RevisionInfo(tree.branch, None, '1a')
+ end_rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
+ log.show_log(tree.branch, lf, verbose=True, start_revision=start_rev,
+ end_revision=end_rev)
+ self.assertEqual(3, len(lf.revisions))
+ self.assertEqual(None, lf.revisions[0].revno) # Out-of-branch
+ self.assertEqual('3a', lf.revisions[0].rev.revision_id)
+ self.assertEqual(None, lf.revisions[1].revno) # Out-of-branch
+ self.assertEqual('2a', lf.revisions[1].rev.revision_id)
+ self.assertEqual('1', lf.revisions[2].revno) # In-branch
+
+ def test_long_format(self):
+ tree = self.setup_ab_tree()
+ start_rev = revisionspec.RevisionInfo(tree.branch, None, '1a')
+ end_rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
+ self.assertFormatterResult("""\
+------------------------------------------------------------
+revision-id: 3a
+committer: Joe Foo <joe@foo.com>
+branch nick: tree
+timestamp: Tue 2005-11-22 00:00:00 +0000
+message:
+ commit 3a
+------------------------------------------------------------
+revision-id: 2a
+committer: Joe Foo <joe@foo.com>
+branch nick: tree
+timestamp: Tue 2005-11-22 00:00:00 +0000
+message:
+ commit 2a
+------------------------------------------------------------
+revno: 1
+committer: Joe Foo <joe@foo.com>
+branch nick: tree
+timestamp: Tue 2005-11-22 00:00:00 +0000
+message:
+ commit 1a
+""",
+ tree.branch, log.LongLogFormatter, show_log_kwargs={
+ 'start_revision': start_rev, 'end_revision': end_rev
+ })
+
+ def test_short_format(self):
+ tree = self.setup_ab_tree()
+ start_rev = revisionspec.RevisionInfo(tree.branch, None, '1a')
+ end_rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
+ self.assertFormatterResult("""\
+ Joe Foo\t2005-11-22
+ revision-id:3a
+ commit 3a
+
+ Joe Foo\t2005-11-22
+ revision-id:2a
+ commit 2a
+
+ 1 Joe Foo\t2005-11-22
+ commit 1a
+
+""",
+ tree.branch, log.ShortLogFormatter, show_log_kwargs={
+ 'start_revision': start_rev, 'end_revision': end_rev
+ })
+
+ def test_line_format(self):
+ tree = self.setup_ab_tree()
+ start_rev = revisionspec.RevisionInfo(tree.branch, None, '1a')
+ end_rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
+ self.assertFormatterResult("""\
+Joe Foo 2005-11-22 commit 3a
+Joe Foo 2005-11-22 commit 2a
+1: Joe Foo 2005-11-22 commit 1a
+""",
+ tree.branch, log.LineLogFormatter, show_log_kwargs={
+ 'start_revision': start_rev, 'end_revision': end_rev
+ })
+
+
+class TestLogWithBugs(TestCaseForLogFormatter, TestLogMixin):
+
+ def setUp(self):
+ TestCaseForLogFormatter.setUp(self)
+ log.properties_handler_registry.register(
+ 'bugs_properties_handler',
+ log._bugs_properties_handler)
+
+ def make_commits_with_bugs(self):
+ """Helper method for LogFormatter tests"""
+ tree = self.make_branch_and_tree(u'.')
+ self.build_tree(['a', 'b'])
+ tree.add('a')
+ self.wt_commit(tree, 'simple log message', rev_id='a1',
+ revprops={'bugs': 'test://bug/id fixed'})
+ tree.add('b')
+ self.wt_commit(tree, 'multiline\nlog\nmessage\n', rev_id='a2',
+ authors=['Joe Bar <joe@bar.com>'],
+ revprops={'bugs': 'test://bug/id fixed\n'
+ 'test://bug/2 fixed'})
+ return tree
+
+
+ def test_long_bugs(self):
+ tree = self.make_commits_with_bugs()
+ self.assertFormatterResult("""\
+------------------------------------------------------------
+revno: 2
+fixes bugs: test://bug/id test://bug/2
+author: Joe Bar <joe@bar.com>
+committer: Joe Foo <joe@foo.com>
+branch nick: work
+timestamp: Tue 2005-11-22 00:00:01 +0000
+message:
+ multiline
+ log
+ message
+------------------------------------------------------------
+revno: 1
+fixes bug: test://bug/id
+committer: Joe Foo <joe@foo.com>
+branch nick: work
+timestamp: Tue 2005-11-22 00:00:00 +0000
+message:
+ simple log message
+""",
+ tree.branch, log.LongLogFormatter)
+
+ def test_short_bugs(self):
+ tree = self.make_commits_with_bugs()
+ self.assertFormatterResult("""\
+ 2 Joe Bar\t2005-11-22
+ fixes bugs: test://bug/id test://bug/2
+ multiline
+ log
+ message
+
+ 1 Joe Foo\t2005-11-22
+ fixes bug: test://bug/id
+ simple log message
+
+""",
+ tree.branch, log.ShortLogFormatter)
+
+ def test_wrong_bugs_property(self):
+ tree = self.make_branch_and_tree(u'.')
+ self.build_tree(['foo'])
+ self.wt_commit(tree, 'simple log message', rev_id='a1',
+ revprops={'bugs': 'test://bug/id invalid_value'})
+ self.assertFormatterResult("""\
+ 1 Joe Foo\t2005-11-22
+ simple log message
+
+""",
+ tree.branch, log.ShortLogFormatter)
+
+ def test_bugs_handler_present(self):
+ self.properties_handler_registry.get('bugs_properties_handler')
+
+
+class TestLogForAuthors(TestCaseForLogFormatter):
+
+ def setUp(self):
+ TestCaseForLogFormatter.setUp(self)
+ self.wt = self.make_standard_commit('nicky',
+ authors=['John Doe <jdoe@example.com>',
+ 'Jane Rey <jrey@example.com>'])
+
+ def assertFormatterResult(self, formatter, who, result):
+ formatter_kwargs = dict()
+ if who is not None:
+ author_list_handler = log.author_list_registry.get(who)
+ formatter_kwargs['author_list_handler'] = author_list_handler
+ TestCaseForLogFormatter.assertFormatterResult(self, result,
+ self.wt.branch, formatter, formatter_kwargs=formatter_kwargs)
+
+ def test_line_default(self):
+ self.assertFormatterResult(log.LineLogFormatter, None, """\
+1: John Doe 2005-11-22 add a
+""")
+
+ def test_line_committer(self):
+ self.assertFormatterResult(log.LineLogFormatter, 'committer', """\
+1: Lorem Ipsum 2005-11-22 add a
+""")
+
+ def test_line_first(self):
+ self.assertFormatterResult(log.LineLogFormatter, 'first', """\
+1: John Doe 2005-11-22 add a
+""")
+
+ def test_line_all(self):
+ self.assertFormatterResult(log.LineLogFormatter, 'all', """\
+1: John Doe, Jane Rey 2005-11-22 add a
+""")
+
+
+ def test_short_default(self):
+ self.assertFormatterResult(log.ShortLogFormatter, None, """\
+ 1 John Doe\t2005-11-22
+ add a
+
+""")
+
+ def test_short_committer(self):
+ self.assertFormatterResult(log.ShortLogFormatter, 'committer', """\
+ 1 Lorem Ipsum\t2005-11-22
+ add a
+
+""")
+
+ def test_short_first(self):
+ self.assertFormatterResult(log.ShortLogFormatter, 'first', """\
+ 1 John Doe\t2005-11-22
+ add a
+
+""")
+
+ def test_short_all(self):
+ self.assertFormatterResult(log.ShortLogFormatter, 'all', """\
+ 1 John Doe, Jane Rey\t2005-11-22
+ add a
+
+""")
+
+ def test_long_default(self):
+ self.assertFormatterResult(log.LongLogFormatter, None, """\
+------------------------------------------------------------
+revno: 1
+author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
+committer: Lorem Ipsum <test@example.com>
+branch nick: nicky
+timestamp: Tue 2005-11-22 00:00:00 +0000
+message:
+ add a
+""")
+
+ def test_long_committer(self):
+ self.assertFormatterResult(log.LongLogFormatter, 'committer', """\
+------------------------------------------------------------
+revno: 1
+committer: Lorem Ipsum <test@example.com>
+branch nick: nicky
+timestamp: Tue 2005-11-22 00:00:00 +0000
+message:
+ add a
+""")
+
+ def test_long_first(self):
+ self.assertFormatterResult(log.LongLogFormatter, 'first', """\
+------------------------------------------------------------
+revno: 1
+author: John Doe <jdoe@example.com>
+committer: Lorem Ipsum <test@example.com>
+branch nick: nicky
+timestamp: Tue 2005-11-22 00:00:00 +0000
+message:
+ add a
+""")
+
+ def test_long_all(self):
+ self.assertFormatterResult(log.LongLogFormatter, 'all', """\
+------------------------------------------------------------
+revno: 1
+author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
+committer: Lorem Ipsum <test@example.com>
+branch nick: nicky
+timestamp: Tue 2005-11-22 00:00:00 +0000
+message:
+ add a
+""")
+
+ def test_gnu_changelog_default(self):
+ self.assertFormatterResult(log.GnuChangelogLogFormatter, None, """\
+2005-11-22 John Doe <jdoe@example.com>
+
+\tadd a
+
+""")
+
+ def test_gnu_changelog_committer(self):
+ self.assertFormatterResult(log.GnuChangelogLogFormatter, 'committer', """\
+2005-11-22 Lorem Ipsum <test@example.com>
+
+\tadd a
+
+""")
+
+ def test_gnu_changelog_first(self):
+ self.assertFormatterResult(log.GnuChangelogLogFormatter, 'first', """\
+2005-11-22 John Doe <jdoe@example.com>
+
+\tadd a
+
+""")
+
+ def test_gnu_changelog_all(self):
+ self.assertFormatterResult(log.GnuChangelogLogFormatter, 'all', """\
+2005-11-22 John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
+
+\tadd a
+
+""")
+
+
+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, generate_merge_revisions=True):
+ # FIXME: the layering in log makes it hard to test intermediate levels,
+ # I wish adding filters with their parameters was easier...
+ # -- vila 20100413
+ iter_revs = log._calc_view_revisions(
+ b, start, end, direction='reverse',
+ generate_merge_revisions=generate_merge_revisions,
+ 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', exclude_common_ancestry=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', exclude_common_ancestry=True)
+
+ def test_merge_sorted_simple_revnos_exclude_ancestry(self):
+ b = self.make_branch_with_alternate_ancestries()
+ self.assertLogRevnos(['3', '2'],
+ b, '1', '3', exclude_common_ancestry=True,
+ generate_merge_revisions=False)
+ self.assertLogRevnos(['3', '1.1.2', '1.2.1', '1.1.1', '2'],
+ b, '1', '3', exclude_common_ancestry=True,
+ generate_merge_revisions=True)
+
+
+class TestLogDefaults(TestCaseForLogFormatter):
+ def test_default_log_level(self):
+ """
+ Test to ensure that specifying 'levels=1' to make_log_request_dict
+ doesn't get overwritten when using a LogFormatter that supports more
+ detail.
+ Fixes bug #747958.
+ """
+ wt = self._prepare_tree_with_merges()
+ b = wt.branch
+
+ class CustomLogFormatter(log.LogFormatter):
+ def __init__(self, *args, **kwargs):
+ super(CustomLogFormatter, self).__init__(*args, **kwargs)
+ self.revisions = []
+ def get_levels(self):
+ # log formatter supports all levels:
+ return 0
+ def log_revision(self, revision):
+ self.revisions.append(revision)
+
+ log_formatter = LogCatcher()
+ # First request we don't specify number of levels, we should get a
+ # sensible default (whatever the LogFormatter handles - which in this
+ # case is 0/everything):
+ request = log.make_log_request_dict(limit=10)
+ log.Logger(b, request).show(log_formatter)
+ # should have all three revisions:
+ self.assertEquals(len(log_formatter.revisions), 3)
+
+ del log_formatter
+ log_formatter = LogCatcher()
+ # now explicitly request mainline revisions only:
+ request = log.make_log_request_dict(limit=10, levels=1)
+ log.Logger(b, request).show(log_formatter)
+ # should now only have 2 revisions:
+ self.assertEquals(len(log_formatter.revisions), 2)
+