1
# Copyright (C) 2005-2013, 2016 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
30
from ..sixish import (
35
class TestLogMixin(object):
37
def wt_commit(self, wt, message, **kwargs):
38
"""Use some mostly fixed values for commits to simplify tests.
40
Tests can use this function to get some commit attributes. The time
41
stamp is incremented at each commit.
43
if getattr(self, 'timestamp', None) is None:
44
self.timestamp = 1132617600 # Mon 2005-11-22 00:00:00 +0000
46
self.timestamp += 1 # 1 second between each commit
47
kwargs.setdefault('timestamp', self.timestamp)
48
kwargs.setdefault('timezone', 0) # UTC
49
kwargs.setdefault('committer', 'Joe Foo <joe@foo.com>')
51
return wt.commit(message, **kwargs)
54
class TestCaseForLogFormatter(tests.TestCaseWithTransport, TestLogMixin):
57
super(TestCaseForLogFormatter, self).setUp()
58
# keep a reference to the "current" custom prop. handler registry
59
self.properties_handler_registry = log.properties_handler_registry
60
# Use a clean registry for log
61
log.properties_handler_registry = registry.Registry()
64
log.properties_handler_registry = self.properties_handler_registry
65
self.addCleanup(restore)
67
def assertFormatterResult(self, result, branch, formatter_class,
68
formatter_kwargs=None, show_log_kwargs=None):
69
logfile = self.make_utf8_encoded_stringio()
70
if formatter_kwargs is None:
72
formatter = formatter_class(to_file=logfile, **formatter_kwargs)
73
if show_log_kwargs is None:
75
log.show_log(branch, formatter, **show_log_kwargs)
76
self.assertEqualDiff(result, logfile.getvalue())
78
def make_standard_commit(self, branch_nick, **kwargs):
79
wt = self.make_branch_and_tree('.')
81
self.addCleanup(wt.unlock)
82
self.build_tree(['a'])
84
wt.branch.nick = branch_nick
85
kwargs.setdefault('committer', 'Lorem Ipsum <test@example.com>')
86
kwargs.setdefault('authors', ['John Doe <jdoe@example.com>'])
87
self.wt_commit(wt, 'add a', **kwargs)
90
def make_commits_with_trailing_newlines(self, wt):
91
"""Helper method for LogFormatter tests"""
94
self.build_tree_contents([('a', 'hello moto\n')])
95
self.wt_commit(wt, 'simple log message', rev_id='a1')
96
self.build_tree_contents([('b', 'goodbye\n')])
98
self.wt_commit(wt, 'multiline\nlog\nmessage\n', rev_id='a2')
100
self.build_tree_contents([('c', 'just another manic monday\n')])
102
self.wt_commit(wt, 'single line with trailing newline\n', rev_id='a3')
105
def _prepare_tree_with_merges(self, with_tags=False):
106
wt = self.make_branch_and_memory_tree('.')
108
self.addCleanup(wt.unlock)
110
self.wt_commit(wt, 'rev-1', rev_id='rev-1')
111
self.wt_commit(wt, 'rev-merged', rev_id='rev-2a')
112
wt.set_parent_ids(['rev-1', 'rev-2a'])
113
wt.branch.set_last_revision_info(1, 'rev-1')
114
self.wt_commit(wt, 'rev-2', rev_id='rev-2b')
117
branch.tags.set_tag('v0.2', 'rev-2b')
118
self.wt_commit(wt, 'rev-3', rev_id='rev-3')
119
branch.tags.set_tag('v1.0rc1', 'rev-3')
120
branch.tags.set_tag('v1.0', 'rev-3')
124
class LogCatcher(log.LogFormatter):
125
"""Pull log messages into a list rather than displaying them.
127
To simplify testing we save logged revisions here rather than actually
128
formatting anything, so that we can precisely check the result without
129
being dependent on the formatting.
132
supports_merge_revisions = True
133
supports_delta = True
137
def __init__(self, *args, **kwargs):
138
kwargs.update(dict(to_file=None))
139
super(LogCatcher, self).__init__(*args, **kwargs)
142
def log_revision(self, revision):
143
self.revisions.append(revision)
146
class TestShowLog(tests.TestCaseWithTransport):
148
def checkDelta(self, delta, **kw):
149
"""Check the filenames touched by a delta are as expected.
151
Caller only have to pass in the list of files for each part, all
152
unspecified parts are considered empty (and checked as such).
154
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
155
# By default we expect an empty list
156
expected = kw.get(n, [])
157
# strip out only the path components
158
got = [x[0] for x in getattr(delta, n)]
159
self.assertEqual(expected, got)
161
def assertInvalidRevisonNumber(self, br, start, end):
163
self.assertRaises(errors.InvalidRevisionNumber,
164
log.show_log, br, lf,
165
start_revision=start, end_revision=end)
167
def test_cur_revno(self):
168
wt = self.make_branch_and_tree('.')
172
wt.commit('empty commit')
173
log.show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
175
# Since there is a single revision in the branch all the combinations
177
self.assertInvalidRevisonNumber(b, 2, 1)
178
self.assertInvalidRevisonNumber(b, 1, 2)
179
self.assertInvalidRevisonNumber(b, 0, 2)
180
self.assertInvalidRevisonNumber(b, 1, 0)
181
self.assertInvalidRevisonNumber(b, -1, 1)
182
self.assertInvalidRevisonNumber(b, 1, -1)
184
def test_empty_branch(self):
185
wt = self.make_branch_and_tree('.')
188
log.show_log(wt.branch, lf)
190
self.assertEqual([], lf.revisions)
192
def test_empty_commit(self):
193
wt = self.make_branch_and_tree('.')
195
wt.commit('empty commit')
197
log.show_log(wt.branch, lf, verbose=True)
199
self.assertEqual(1, len(revs))
200
self.assertEqual('1', revs[0].revno)
201
self.assertEqual('empty commit', revs[0].rev.message)
202
self.checkDelta(revs[0].delta)
204
def test_simple_commit(self):
205
wt = self.make_branch_and_tree('.')
206
wt.commit('empty commit')
207
self.build_tree(['hello'])
209
wt.commit('add one file',
210
committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
211
u'<test@example.com>')
213
log.show_log(wt.branch, lf, verbose=True)
214
self.assertEqual(2, len(lf.revisions))
215
# first one is most recent
216
log_entry = lf.revisions[0]
217
self.assertEqual('2', log_entry.revno)
218
self.assertEqual('add one file', log_entry.rev.message)
219
self.checkDelta(log_entry.delta, added=['hello'])
221
def test_commit_message_with_control_chars(self):
222
wt = self.make_branch_and_tree('.')
223
msg = u"All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
224
msg = msg.replace(u'\r', u'\n')
227
log.show_log(wt.branch, lf, verbose=True)
228
committed_msg = lf.revisions[0].rev.message
229
if wt.branch.repository._serializer.squashes_xml_invalid_characters:
230
self.assertNotEqual(msg, committed_msg)
231
self.assertTrue(len(committed_msg) > len(msg))
233
self.assertEqual(msg, committed_msg)
235
def test_commit_message_without_control_chars(self):
236
wt = self.make_branch_and_tree('.')
237
# escaped. As ElementTree apparently does some kind of
238
# newline conversion, neither LF (\x0A) nor CR (\x0D) are
239
# included in the test commit message, even though they are
240
# valid XML 1.0 characters.
241
msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
244
log.show_log(wt.branch, lf, verbose=True)
245
committed_msg = lf.revisions[0].rev.message
246
self.assertEqual(msg, committed_msg)
248
def test_deltas_in_merge_revisions(self):
249
"""Check deltas created for both mainline and merge revisions"""
250
wt = self.make_branch_and_tree('parent')
251
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
254
wt.commit(message='add file1 and file2')
255
self.run_bzr('branch parent child')
256
os.unlink('child/file1')
257
with file('child/file2', 'wb') as f: f.write('hello\n')
258
self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
261
self.run_bzr('merge ../child')
262
wt.commit('merge child branch')
266
lf.supports_merge_revisions = True
267
log.show_log(b, lf, verbose=True)
270
self.assertEqual(3, len(revs))
273
self.assertEqual('2', logentry.revno)
274
self.assertEqual('merge child branch', logentry.rev.message)
275
self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
278
self.assertEqual('1.1.1', logentry.revno)
279
self.assertEqual('remove file1 and modify file2', logentry.rev.message)
280
self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
283
self.assertEqual('1', logentry.revno)
284
self.assertEqual('add file1 and file2', logentry.rev.message)
285
self.checkDelta(logentry.delta, added=['file1', 'file2'])
288
class TestFormatSignatureValidity(tests.TestCaseWithTransport):
289
class UTFLoopbackGPGStrategy(gpg.LoopbackGPGStrategy):
290
def verify(self, content, testament):
291
return (gpg.SIGNATURE_VALID,
292
u'UTF8 Test \xa1\xb1\xc1\xd1\xe1\xf1 <jrandom@example.com>')
294
def has_signature_for_revision_id(self, revision_id):
297
def get_signature_text(self, revision_id):
300
def test_format_signature_validity_utf(self):
301
"""Check that GPG signatures containing UTF-8 names are formatted
303
# Monkey patch to use our UTF-8 generating GPGStrategy
304
self.overrideAttr(gpg, 'GPGStrategy', self.UTFLoopbackGPGStrategy)
305
wt = self.make_branch_and_tree('.')
306
revid = wt.commit('empty commit')
307
repo = wt.branch.repository
308
# Monkey patch out checking if this rev is actually signed, since we
309
# can't sign it without a heavier TestCase and LoopbackGPGStrategy
310
# doesn't care anyways.
311
self.overrideAttr(repo, 'has_signature_for_revision_id',
312
self.has_signature_for_revision_id)
313
self.overrideAttr(repo, 'get_signature_text', self.get_signature_text)
314
out = log.format_signature_validity(revid, repo)
316
u'valid signature from UTF8 Test \xa1\xb1\xc1\xd1\xe1\xf1 <jrandom@example.com>',
320
class TestShortLogFormatter(TestCaseForLogFormatter):
322
def test_trailing_newlines(self):
323
wt = self.make_branch_and_tree('.')
324
b = self.make_commits_with_trailing_newlines(wt)
325
self.assertFormatterResult("""\
326
3 Joe Foo\t2005-11-22
327
single line with trailing newline
329
2 Joe Foo\t2005-11-22
334
1 Joe Foo\t2005-11-22
338
b, log.ShortLogFormatter)
340
def test_short_log_with_merges(self):
341
wt = self._prepare_tree_with_merges()
342
self.assertFormatterResult("""\
343
2 Joe Foo\t2005-11-22 [merge]
346
1 Joe Foo\t2005-11-22
350
wt.branch, log.ShortLogFormatter)
352
def test_short_log_with_merges_and_advice(self):
353
wt = self._prepare_tree_with_merges()
354
self.assertFormatterResult("""\
355
2 Joe Foo\t2005-11-22 [merge]
358
1 Joe Foo\t2005-11-22
361
Use --include-merged or -n0 to see merged revisions.
363
wt.branch, log.ShortLogFormatter,
364
formatter_kwargs=dict(show_advice=True))
366
def test_short_log_with_merges_and_range(self):
367
wt = self._prepare_tree_with_merges()
368
self.wt_commit(wt, 'rev-3a', rev_id='rev-3a')
369
wt.branch.set_last_revision_info(2, 'rev-2b')
370
wt.set_parent_ids(['rev-2b', 'rev-3a'])
371
self.wt_commit(wt, 'rev-3b', rev_id='rev-3b')
372
self.assertFormatterResult("""\
373
3 Joe Foo\t2005-11-22 [merge]
376
2 Joe Foo\t2005-11-22 [merge]
380
wt.branch, log.ShortLogFormatter,
381
show_log_kwargs=dict(start_revision=2, end_revision=3))
383
def test_short_log_with_tags(self):
384
wt = self._prepare_tree_with_merges(with_tags=True)
385
self.assertFormatterResult("""\
386
3 Joe Foo\t2005-11-22 {v1.0, v1.0rc1}
389
2 Joe Foo\t2005-11-22 {v0.2} [merge]
392
1 Joe Foo\t2005-11-22
396
wt.branch, log.ShortLogFormatter)
398
def test_short_log_single_merge_revision(self):
399
wt = self._prepare_tree_with_merges()
400
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
401
rev = revspec.in_history(wt.branch)
402
self.assertFormatterResult("""\
403
1.1.1 Joe Foo\t2005-11-22
407
wt.branch, log.ShortLogFormatter,
408
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
410
def test_show_ids(self):
411
wt = self.make_branch_and_tree('parent')
412
self.build_tree(['parent/f1', 'parent/f2'])
414
self.wt_commit(wt, 'first post', rev_id='a')
415
child_wt = wt.controldir.sprout('child').open_workingtree()
416
self.wt_commit(child_wt, 'branch 1 changes', rev_id='b')
417
wt.merge_from_branch(child_wt.branch)
418
self.wt_commit(wt, 'merge branch 1', rev_id='c')
419
self.assertFormatterResult("""\
420
2 Joe Foo\t2005-11-22 [merge]
424
1.1.1 Joe Foo\t2005-11-22
428
1 Joe Foo\t2005-11-22
433
wt.branch, log.ShortLogFormatter,
434
formatter_kwargs=dict(levels=0,show_ids=True))
437
class TestShortLogFormatterWithMergeRevisions(TestCaseForLogFormatter):
439
def test_short_merge_revs_log_with_merges(self):
440
wt = self._prepare_tree_with_merges()
441
# Note that the 1.1.1 indenting is in fact correct given that
442
# the revision numbers are right justified within 5 characters
443
# for mainline revnos and 9 characters for dotted revnos.
444
self.assertFormatterResult("""\
445
2 Joe Foo\t2005-11-22 [merge]
448
1.1.1 Joe Foo\t2005-11-22
451
1 Joe Foo\t2005-11-22
455
wt.branch, log.ShortLogFormatter,
456
formatter_kwargs=dict(levels=0))
458
def test_short_merge_revs_log_single_merge_revision(self):
459
wt = self._prepare_tree_with_merges()
460
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
461
rev = revspec.in_history(wt.branch)
462
self.assertFormatterResult("""\
463
1.1.1 Joe Foo\t2005-11-22
467
wt.branch, log.ShortLogFormatter,
468
formatter_kwargs=dict(levels=0),
469
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
472
class TestLongLogFormatter(TestCaseForLogFormatter):
474
def test_verbose_log(self):
475
"""Verbose log includes changed files
479
wt = self.make_standard_commit('test_verbose_log', authors=[])
480
self.assertFormatterResult('''\
481
------------------------------------------------------------
483
committer: Lorem Ipsum <test@example.com>
484
branch nick: test_verbose_log
485
timestamp: Tue 2005-11-22 00:00:00 +0000
491
wt.branch, log.LongLogFormatter,
492
show_log_kwargs=dict(verbose=True))
494
def test_merges_are_indented_by_level(self):
495
wt = self.make_branch_and_tree('parent')
496
self.wt_commit(wt, 'first post')
497
child_wt = wt.controldir.sprout('child').open_workingtree()
498
self.wt_commit(child_wt, 'branch 1')
499
smallerchild_wt = wt.controldir.sprout('smallerchild').open_workingtree()
500
self.wt_commit(smallerchild_wt, 'branch 2')
501
child_wt.merge_from_branch(smallerchild_wt.branch)
502
self.wt_commit(child_wt, 'merge branch 2')
503
wt.merge_from_branch(child_wt.branch)
504
self.wt_commit(wt, 'merge branch 1')
505
self.assertFormatterResult("""\
506
------------------------------------------------------------
508
committer: Joe Foo <joe@foo.com>
510
timestamp: Tue 2005-11-22 00:00:04 +0000
513
------------------------------------------------------------
515
committer: Joe Foo <joe@foo.com>
517
timestamp: Tue 2005-11-22 00:00:03 +0000
520
------------------------------------------------------------
522
committer: Joe Foo <joe@foo.com>
523
branch nick: smallerchild
524
timestamp: Tue 2005-11-22 00:00:02 +0000
527
------------------------------------------------------------
529
committer: Joe Foo <joe@foo.com>
531
timestamp: Tue 2005-11-22 00:00:01 +0000
534
------------------------------------------------------------
536
committer: Joe Foo <joe@foo.com>
538
timestamp: Tue 2005-11-22 00:00:00 +0000
542
wt.branch, log.LongLogFormatter,
543
formatter_kwargs=dict(levels=0),
544
show_log_kwargs=dict(verbose=True))
546
def test_verbose_merge_revisions_contain_deltas(self):
547
wt = self.make_branch_and_tree('parent')
548
self.build_tree(['parent/f1', 'parent/f2'])
550
self.wt_commit(wt, 'first post')
551
child_wt = wt.controldir.sprout('child').open_workingtree()
552
os.unlink('child/f1')
553
self.build_tree_contents([('child/f2', 'hello\n')])
554
self.wt_commit(child_wt, 'removed f1 and modified f2')
555
wt.merge_from_branch(child_wt.branch)
556
self.wt_commit(wt, 'merge branch 1')
557
self.assertFormatterResult("""\
558
------------------------------------------------------------
560
committer: Joe Foo <joe@foo.com>
562
timestamp: Tue 2005-11-22 00:00:02 +0000
569
------------------------------------------------------------
571
committer: Joe Foo <joe@foo.com>
573
timestamp: Tue 2005-11-22 00:00:01 +0000
575
removed f1 and modified f2
580
------------------------------------------------------------
582
committer: Joe Foo <joe@foo.com>
584
timestamp: Tue 2005-11-22 00:00:00 +0000
591
wt.branch, log.LongLogFormatter,
592
formatter_kwargs=dict(levels=0),
593
show_log_kwargs=dict(verbose=True))
595
def test_trailing_newlines(self):
596
wt = self.make_branch_and_tree('.')
597
b = self.make_commits_with_trailing_newlines(wt)
598
self.assertFormatterResult("""\
599
------------------------------------------------------------
601
committer: Joe Foo <joe@foo.com>
603
timestamp: Tue 2005-11-22 00:00:02 +0000
605
single line with trailing newline
606
------------------------------------------------------------
608
committer: Joe Foo <joe@foo.com>
610
timestamp: Tue 2005-11-22 00:00:01 +0000
615
------------------------------------------------------------
617
committer: Joe Foo <joe@foo.com>
619
timestamp: Tue 2005-11-22 00:00:00 +0000
623
b, log.LongLogFormatter)
625
def test_author_in_log(self):
626
"""Log includes the author name if it's set in
627
the revision properties
629
wt = self.make_standard_commit('test_author_log',
630
authors=['John Doe <jdoe@example.com>',
631
'Jane Rey <jrey@example.com>'])
632
self.assertFormatterResult("""\
633
------------------------------------------------------------
635
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
636
committer: Lorem Ipsum <test@example.com>
637
branch nick: test_author_log
638
timestamp: Tue 2005-11-22 00:00:00 +0000
642
wt.branch, log.LongLogFormatter)
644
def test_properties_in_log(self):
645
"""Log includes the custom properties returned by the registered
648
wt = self.make_standard_commit('test_properties_in_log')
649
def trivial_custom_prop_handler(revision):
650
return {'test_prop':'test_value'}
652
# Cleaned up in setUp()
653
log.properties_handler_registry.register(
654
'trivial_custom_prop_handler',
655
trivial_custom_prop_handler)
656
self.assertFormatterResult("""\
657
------------------------------------------------------------
659
test_prop: test_value
660
author: John Doe <jdoe@example.com>
661
committer: Lorem Ipsum <test@example.com>
662
branch nick: test_properties_in_log
663
timestamp: Tue 2005-11-22 00:00:00 +0000
667
wt.branch, log.LongLogFormatter)
669
def test_properties_in_short_log(self):
670
"""Log includes the custom properties returned by the registered
673
wt = self.make_standard_commit('test_properties_in_short_log')
674
def trivial_custom_prop_handler(revision):
675
return {'test_prop':'test_value'}
677
log.properties_handler_registry.register(
678
'trivial_custom_prop_handler',
679
trivial_custom_prop_handler)
680
self.assertFormatterResult("""\
681
1 John Doe\t2005-11-22
682
test_prop: test_value
686
wt.branch, log.ShortLogFormatter)
688
def test_error_in_properties_handler(self):
689
"""Log includes the custom properties returned by the registered
692
wt = self.make_standard_commit('error_in_properties_handler',
693
revprops={'first_prop':'first_value'})
694
sio = self.make_utf8_encoded_stringio()
695
formatter = log.LongLogFormatter(to_file=sio)
696
def trivial_custom_prop_handler(revision):
697
raise Exception("a test error")
699
log.properties_handler_registry.register(
700
'trivial_custom_prop_handler',
701
trivial_custom_prop_handler)
702
self.assertRaises(Exception, log.show_log, wt.branch, formatter,)
704
def test_properties_handler_bad_argument(self):
705
wt = self.make_standard_commit('bad_argument',
706
revprops={'a_prop':'test_value'})
707
sio = self.make_utf8_encoded_stringio()
708
formatter = log.LongLogFormatter(to_file=sio)
709
def bad_argument_prop_handler(revision):
710
return {'custom_prop_name':revision.properties['a_prop']}
712
log.properties_handler_registry.register(
713
'bad_argument_prop_handler',
714
bad_argument_prop_handler)
716
self.assertRaises(AttributeError, formatter.show_properties,
719
revision = wt.branch.repository.get_revision(wt.branch.last_revision())
720
formatter.show_properties(revision, '')
721
self.assertEqualDiff('''custom_prop_name: test_value\n''',
724
def test_show_ids(self):
725
wt = self.make_branch_and_tree('parent')
726
self.build_tree(['parent/f1', 'parent/f2'])
728
self.wt_commit(wt, 'first post', rev_id='a')
729
child_wt = wt.controldir.sprout('child').open_workingtree()
730
self.wt_commit(child_wt, 'branch 1 changes', rev_id='b')
731
wt.merge_from_branch(child_wt.branch)
732
self.wt_commit(wt, 'merge branch 1', rev_id='c')
733
self.assertFormatterResult("""\
734
------------------------------------------------------------
739
committer: Joe Foo <joe@foo.com>
741
timestamp: Tue 2005-11-22 00:00:02 +0000
744
------------------------------------------------------------
748
committer: Joe Foo <joe@foo.com>
750
timestamp: Tue 2005-11-22 00:00:01 +0000
753
------------------------------------------------------------
756
committer: Joe Foo <joe@foo.com>
758
timestamp: Tue 2005-11-22 00:00:00 +0000
762
wt.branch, log.LongLogFormatter,
763
formatter_kwargs=dict(levels=0,show_ids=True))
766
class TestLongLogFormatterWithoutMergeRevisions(TestCaseForLogFormatter):
768
def test_long_verbose_log(self):
769
"""Verbose log includes changed files
773
wt = self.make_standard_commit('test_long_verbose_log', authors=[])
774
self.assertFormatterResult("""\
775
------------------------------------------------------------
777
committer: Lorem Ipsum <test@example.com>
778
branch nick: test_long_verbose_log
779
timestamp: Tue 2005-11-22 00:00:00 +0000
785
wt.branch, log.LongLogFormatter,
786
formatter_kwargs=dict(levels=1),
787
show_log_kwargs=dict(verbose=True))
789
def test_long_verbose_contain_deltas(self):
790
wt = self.make_branch_and_tree('parent')
791
self.build_tree(['parent/f1', 'parent/f2'])
793
self.wt_commit(wt, 'first post')
794
child_wt = wt.controldir.sprout('child').open_workingtree()
795
os.unlink('child/f1')
796
self.build_tree_contents([('child/f2', 'hello\n')])
797
self.wt_commit(child_wt, 'removed f1 and modified f2')
798
wt.merge_from_branch(child_wt.branch)
799
self.wt_commit(wt, 'merge branch 1')
800
self.assertFormatterResult("""\
801
------------------------------------------------------------
803
committer: Joe Foo <joe@foo.com>
805
timestamp: Tue 2005-11-22 00:00:02 +0000
812
------------------------------------------------------------
814
committer: Joe Foo <joe@foo.com>
816
timestamp: Tue 2005-11-22 00:00:00 +0000
823
wt.branch, log.LongLogFormatter,
824
formatter_kwargs=dict(levels=1),
825
show_log_kwargs=dict(verbose=True))
827
def test_long_trailing_newlines(self):
828
wt = self.make_branch_and_tree('.')
829
b = self.make_commits_with_trailing_newlines(wt)
830
self.assertFormatterResult("""\
831
------------------------------------------------------------
833
committer: Joe Foo <joe@foo.com>
835
timestamp: Tue 2005-11-22 00:00:02 +0000
837
single line with trailing newline
838
------------------------------------------------------------
840
committer: Joe Foo <joe@foo.com>
842
timestamp: Tue 2005-11-22 00:00:01 +0000
847
------------------------------------------------------------
849
committer: Joe Foo <joe@foo.com>
851
timestamp: Tue 2005-11-22 00:00:00 +0000
855
b, log.LongLogFormatter,
856
formatter_kwargs=dict(levels=1))
858
def test_long_author_in_log(self):
859
"""Log includes the author name if it's set in
860
the revision properties
862
wt = self.make_standard_commit('test_author_log')
863
self.assertFormatterResult("""\
864
------------------------------------------------------------
866
author: John Doe <jdoe@example.com>
867
committer: Lorem Ipsum <test@example.com>
868
branch nick: test_author_log
869
timestamp: Tue 2005-11-22 00:00:00 +0000
873
wt.branch, log.LongLogFormatter,
874
formatter_kwargs=dict(levels=1))
876
def test_long_properties_in_log(self):
877
"""Log includes the custom properties returned by the registered
880
wt = self.make_standard_commit('test_properties_in_log')
881
def trivial_custom_prop_handler(revision):
882
return {'test_prop':'test_value'}
884
log.properties_handler_registry.register(
885
'trivial_custom_prop_handler',
886
trivial_custom_prop_handler)
887
self.assertFormatterResult("""\
888
------------------------------------------------------------
890
test_prop: test_value
891
author: John Doe <jdoe@example.com>
892
committer: Lorem Ipsum <test@example.com>
893
branch nick: test_properties_in_log
894
timestamp: Tue 2005-11-22 00:00:00 +0000
898
wt.branch, log.LongLogFormatter,
899
formatter_kwargs=dict(levels=1))
902
class TestLineLogFormatter(TestCaseForLogFormatter):
904
def test_line_log(self):
905
"""Line log should show revno
909
wt = self.make_standard_commit('test-line-log',
910
committer='Line-Log-Formatter Tester <test@line.log>',
912
self.assertFormatterResult("""\
913
1: Line-Log-Formatte... 2005-11-22 add a
915
wt.branch, log.LineLogFormatter)
917
def test_trailing_newlines(self):
918
wt = self.make_branch_and_tree('.')
919
b = self.make_commits_with_trailing_newlines(wt)
920
self.assertFormatterResult("""\
921
3: Joe Foo 2005-11-22 single line with trailing newline
922
2: Joe Foo 2005-11-22 multiline
923
1: Joe Foo 2005-11-22 simple log message
925
b, log.LineLogFormatter)
927
def test_line_log_single_merge_revision(self):
928
wt = self._prepare_tree_with_merges()
929
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
930
rev = revspec.in_history(wt.branch)
931
self.assertFormatterResult("""\
932
1.1.1: Joe Foo 2005-11-22 rev-merged
934
wt.branch, log.LineLogFormatter,
935
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
937
def test_line_log_with_tags(self):
938
wt = self._prepare_tree_with_merges(with_tags=True)
939
self.assertFormatterResult("""\
940
3: Joe Foo 2005-11-22 {v1.0, v1.0rc1} rev-3
941
2: Joe Foo 2005-11-22 [merge] {v0.2} rev-2
942
1: Joe Foo 2005-11-22 rev-1
944
wt.branch, log.LineLogFormatter)
947
class TestLineLogFormatterWithMergeRevisions(TestCaseForLogFormatter):
949
def test_line_merge_revs_log(self):
950
"""Line log should show revno
954
wt = self.make_standard_commit('test-line-log',
955
committer='Line-Log-Formatter Tester <test@line.log>',
957
self.assertFormatterResult("""\
958
1: Line-Log-Formatte... 2005-11-22 add a
960
wt.branch, log.LineLogFormatter)
962
def test_line_merge_revs_log_single_merge_revision(self):
963
wt = self._prepare_tree_with_merges()
964
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
965
rev = revspec.in_history(wt.branch)
966
self.assertFormatterResult("""\
967
1.1.1: Joe Foo 2005-11-22 rev-merged
969
wt.branch, log.LineLogFormatter,
970
formatter_kwargs=dict(levels=0),
971
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
973
def test_line_merge_revs_log_with_merges(self):
974
wt = self._prepare_tree_with_merges()
975
self.assertFormatterResult("""\
976
2: Joe Foo 2005-11-22 [merge] rev-2
977
1.1.1: Joe Foo 2005-11-22 rev-merged
978
1: Joe Foo 2005-11-22 rev-1
980
wt.branch, log.LineLogFormatter,
981
formatter_kwargs=dict(levels=0))
984
class TestGnuChangelogFormatter(TestCaseForLogFormatter):
986
def test_gnu_changelog(self):
987
wt = self.make_standard_commit('nicky', authors=[])
988
self.assertFormatterResult('''\
989
2005-11-22 Lorem Ipsum <test@example.com>
994
wt.branch, log.GnuChangelogLogFormatter)
996
def test_with_authors(self):
997
wt = self.make_standard_commit('nicky',
998
authors=['Fooa Fooz <foo@example.com>',
999
'Bari Baro <bar@example.com>'])
1000
self.assertFormatterResult('''\
1001
2005-11-22 Fooa Fooz <foo@example.com>
1006
wt.branch, log.GnuChangelogLogFormatter)
1008
def test_verbose(self):
1009
wt = self.make_standard_commit('nicky')
1010
self.assertFormatterResult('''\
1011
2005-11-22 John Doe <jdoe@example.com>
1018
wt.branch, log.GnuChangelogLogFormatter,
1019
show_log_kwargs=dict(verbose=True))
1022
class TestShowChangedRevisions(tests.TestCaseWithTransport):
1024
def test_show_changed_revisions_verbose(self):
1025
tree = self.make_branch_and_tree('tree_a')
1026
self.build_tree(['tree_a/foo'])
1028
tree.commit('bar', rev_id='bar-id')
1029
s = self.make_utf8_encoded_stringio()
1030
log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
1031
self.assertContainsRe(s.getvalue(), 'bar')
1032
self.assertNotContainsRe(s.getvalue(), 'foo')
1035
class TestLogFormatter(tests.TestCase):
1038
super(TestLogFormatter, self).setUp()
1039
self.rev = revision.Revision('a-id')
1040
self.lf = log.LogFormatter(None)
1042
def test_short_committer(self):
1043
def assertCommitter(expected, committer):
1044
self.rev.committer = committer
1045
self.assertEqual(expected, self.lf.short_committer(self.rev))
1047
assertCommitter('John Doe', 'John Doe <jdoe@example.com>')
1048
assertCommitter('John Smith', 'John Smith <jsmith@example.com>')
1049
assertCommitter('John Smith', 'John Smith')
1050
assertCommitter('jsmith@example.com', 'jsmith@example.com')
1051
assertCommitter('jsmith@example.com', '<jsmith@example.com>')
1052
assertCommitter('John Smith', 'John Smith jsmith@example.com')
1054
def test_short_author(self):
1055
def assertAuthor(expected, author):
1056
self.rev.properties['author'] = author
1057
self.assertEqual(expected, self.lf.short_author(self.rev))
1059
assertAuthor('John Smith', 'John Smith <jsmith@example.com>')
1060
assertAuthor('John Smith', 'John Smith')
1061
assertAuthor('jsmith@example.com', 'jsmith@example.com')
1062
assertAuthor('jsmith@example.com', '<jsmith@example.com>')
1063
assertAuthor('John Smith', 'John Smith jsmith@example.com')
1065
def test_short_author_from_committer(self):
1066
self.rev.committer = 'John Doe <jdoe@example.com>'
1067
self.assertEqual('John Doe', self.lf.short_author(self.rev))
1069
def test_short_author_from_authors(self):
1070
self.rev.properties['authors'] = ('John Smith <jsmith@example.com>\n'
1071
'Jane Rey <jrey@example.com>')
1072
self.assertEqual('John Smith', self.lf.short_author(self.rev))
1075
class TestReverseByDepth(tests.TestCase):
1076
"""Test reverse_by_depth behavior.
1078
This is used to present revisions in forward (oldest first) order in a nice
1081
The tests use lighter revision description to ease reading.
1084
def assertReversed(self, forward, backward):
1085
# Transform the descriptions to suit the API: tests use (revno, depth),
1086
# while the API expects (revid, revno, depth)
1087
def complete_revisions(l):
1088
"""Transform the description to suit the API.
1090
Tests use (revno, depth) whil the API expects (revid, revno, depth).
1091
Since the revid is arbitrary, we just duplicate revno
1093
return [ (r, r, d) for r, d in l]
1094
forward = complete_revisions(forward)
1095
backward= complete_revisions(backward)
1096
self.assertEqual(forward, log.reverse_by_depth(backward))
1099
def test_mainline_revisions(self):
1100
self.assertReversed([( '1', 0), ('2', 0)],
1101
[('2', 0), ('1', 0)])
1103
def test_merged_revisions(self):
1104
self.assertReversed([('1', 0), ('2', 0), ('2.2', 1), ('2.1', 1),],
1105
[('2', 0), ('2.1', 1), ('2.2', 1), ('1', 0),])
1106
def test_shifted_merged_revisions(self):
1107
"""Test irregular layout.
1109
Requesting revisions touching a file can produce "holes" in the depths.
1111
self.assertReversed([('1', 0), ('2', 0), ('1.1', 2), ('1.2', 2),],
1112
[('2', 0), ('1.2', 2), ('1.1', 2), ('1', 0),])
1114
def test_merged_without_child_revisions(self):
1115
"""Test irregular layout.
1117
Revision ranges can produce "holes" in the depths.
1119
# When a revision of higher depth doesn't follow one of lower depth, we
1120
# assume a lower depth one is virtually there
1121
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1122
[('4', 4), ('3', 3), ('2', 2), ('1', 2),])
1123
# So we get the same order after reversing below even if the original
1124
# revisions are not in the same order.
1125
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1126
[('3', 3), ('4', 4), ('2', 2), ('1', 2),])
1129
class TestHistoryChange(tests.TestCaseWithTransport):
1131
def setup_a_tree(self):
1132
tree = self.make_branch_and_tree('tree')
1134
self.addCleanup(tree.unlock)
1135
tree.commit('1a', rev_id='1a')
1136
tree.commit('2a', rev_id='2a')
1137
tree.commit('3a', rev_id='3a')
1140
def setup_ab_tree(self):
1141
tree = self.setup_a_tree()
1142
tree.set_last_revision('1a')
1143
tree.branch.set_last_revision_info(1, '1a')
1144
tree.commit('2b', rev_id='2b')
1145
tree.commit('3b', rev_id='3b')
1148
def setup_ac_tree(self):
1149
tree = self.setup_a_tree()
1150
tree.set_last_revision(revision.NULL_REVISION)
1151
tree.branch.set_last_revision_info(0, revision.NULL_REVISION)
1152
tree.commit('1c', rev_id='1c')
1153
tree.commit('2c', rev_id='2c')
1154
tree.commit('3c', rev_id='3c')
1157
def test_all_new(self):
1158
tree = self.setup_ab_tree()
1159
old, new = log.get_history_change('1a', '3a', tree.branch.repository)
1160
self.assertEqual([], old)
1161
self.assertEqual(['2a', '3a'], new)
1163
def test_all_old(self):
1164
tree = self.setup_ab_tree()
1165
old, new = log.get_history_change('3a', '1a', tree.branch.repository)
1166
self.assertEqual([], new)
1167
self.assertEqual(['2a', '3a'], old)
1169
def test_null_old(self):
1170
tree = self.setup_ab_tree()
1171
old, new = log.get_history_change(revision.NULL_REVISION,
1172
'3a', tree.branch.repository)
1173
self.assertEqual([], old)
1174
self.assertEqual(['1a', '2a', '3a'], new)
1176
def test_null_new(self):
1177
tree = self.setup_ab_tree()
1178
old, new = log.get_history_change('3a', revision.NULL_REVISION,
1179
tree.branch.repository)
1180
self.assertEqual([], new)
1181
self.assertEqual(['1a', '2a', '3a'], old)
1183
def test_diverged(self):
1184
tree = self.setup_ab_tree()
1185
old, new = log.get_history_change('3a', '3b', tree.branch.repository)
1186
self.assertEqual(old, ['2a', '3a'])
1187
self.assertEqual(new, ['2b', '3b'])
1189
def test_unrelated(self):
1190
tree = self.setup_ac_tree()
1191
old, new = log.get_history_change('3a', '3c', tree.branch.repository)
1192
self.assertEqual(old, ['1a', '2a', '3a'])
1193
self.assertEqual(new, ['1c', '2c', '3c'])
1195
def test_show_branch_change(self):
1196
tree = self.setup_ab_tree()
1198
log.show_branch_change(tree.branch, s, 3, '3a')
1199
self.assertContainsRe(s.getvalue(),
1200
'[*]{60}\nRemoved Revisions:\n(.|\n)*2a(.|\n)*3a(.|\n)*'
1201
'[*]{60}\n\nAdded Revisions:\n(.|\n)*2b(.|\n)*3b')
1203
def test_show_branch_change_no_change(self):
1204
tree = self.setup_ab_tree()
1206
log.show_branch_change(tree.branch, s, 3, '3b')
1207
self.assertEqual(s.getvalue(),
1208
'Nothing seems to have changed\n')
1210
def test_show_branch_change_no_old(self):
1211
tree = self.setup_ab_tree()
1213
log.show_branch_change(tree.branch, s, 2, '2b')
1214
self.assertContainsRe(s.getvalue(), 'Added Revisions:')
1215
self.assertNotContainsRe(s.getvalue(), 'Removed Revisions:')
1217
def test_show_branch_change_no_new(self):
1218
tree = self.setup_ab_tree()
1219
tree.branch.set_last_revision_info(2, '2b')
1221
log.show_branch_change(tree.branch, s, 3, '3b')
1222
self.assertContainsRe(s.getvalue(), 'Removed Revisions:')
1223
self.assertNotContainsRe(s.getvalue(), 'Added Revisions:')
1226
class TestRevisionNotInBranch(TestCaseForLogFormatter):
1228
def setup_a_tree(self):
1229
tree = self.make_branch_and_tree('tree')
1231
self.addCleanup(tree.unlock)
1233
'committer': 'Joe Foo <joe@foo.com>',
1234
'timestamp': 1132617600, # Mon 2005-11-22 00:00:00 +0000
1235
'timezone': 0, # UTC
1237
tree.commit('commit 1a', rev_id='1a', **kwargs)
1238
tree.commit('commit 2a', rev_id='2a', **kwargs)
1239
tree.commit('commit 3a', rev_id='3a', **kwargs)
1242
def setup_ab_tree(self):
1243
tree = self.setup_a_tree()
1244
tree.set_last_revision('1a')
1245
tree.branch.set_last_revision_info(1, '1a')
1247
'committer': 'Joe Foo <joe@foo.com>',
1248
'timestamp': 1132617600, # Mon 2005-11-22 00:00:00 +0000
1249
'timezone': 0, # UTC
1251
tree.commit('commit 2b', rev_id='2b', **kwargs)
1252
tree.commit('commit 3b', rev_id='3b', **kwargs)
1255
def test_one_revision(self):
1256
tree = self.setup_ab_tree()
1258
rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
1259
log.show_log(tree.branch, lf, verbose=True, start_revision=rev,
1261
self.assertEqual(1, len(lf.revisions))
1262
self.assertEqual(None, lf.revisions[0].revno) # Out-of-branch
1263
self.assertEqual('3a', lf.revisions[0].rev.revision_id)
1265
def test_many_revisions(self):
1266
tree = self.setup_ab_tree()
1268
start_rev = revisionspec.RevisionInfo(tree.branch, None, '1a')
1269
end_rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
1270
log.show_log(tree.branch, lf, verbose=True, start_revision=start_rev,
1271
end_revision=end_rev)
1272
self.assertEqual(3, len(lf.revisions))
1273
self.assertEqual(None, lf.revisions[0].revno) # Out-of-branch
1274
self.assertEqual('3a', lf.revisions[0].rev.revision_id)
1275
self.assertEqual(None, lf.revisions[1].revno) # Out-of-branch
1276
self.assertEqual('2a', lf.revisions[1].rev.revision_id)
1277
self.assertEqual('1', lf.revisions[2].revno) # In-branch
1279
def test_long_format(self):
1280
tree = self.setup_ab_tree()
1281
start_rev = revisionspec.RevisionInfo(tree.branch, None, '1a')
1282
end_rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
1283
self.assertFormatterResult("""\
1284
------------------------------------------------------------
1286
committer: Joe Foo <joe@foo.com>
1288
timestamp: Tue 2005-11-22 00:00:00 +0000
1291
------------------------------------------------------------
1293
committer: Joe Foo <joe@foo.com>
1295
timestamp: Tue 2005-11-22 00:00:00 +0000
1298
------------------------------------------------------------
1300
committer: Joe Foo <joe@foo.com>
1302
timestamp: Tue 2005-11-22 00:00:00 +0000
1306
tree.branch, log.LongLogFormatter, show_log_kwargs={
1307
'start_revision': start_rev, 'end_revision': end_rev
1310
def test_short_format(self):
1311
tree = self.setup_ab_tree()
1312
start_rev = revisionspec.RevisionInfo(tree.branch, None, '1a')
1313
end_rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
1314
self.assertFormatterResult("""\
1323
1 Joe Foo\t2005-11-22
1327
tree.branch, log.ShortLogFormatter, show_log_kwargs={
1328
'start_revision': start_rev, 'end_revision': end_rev
1331
def test_line_format(self):
1332
tree = self.setup_ab_tree()
1333
start_rev = revisionspec.RevisionInfo(tree.branch, None, '1a')
1334
end_rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
1335
self.assertFormatterResult("""\
1336
Joe Foo 2005-11-22 commit 3a
1337
Joe Foo 2005-11-22 commit 2a
1338
1: Joe Foo 2005-11-22 commit 1a
1340
tree.branch, log.LineLogFormatter, show_log_kwargs={
1341
'start_revision': start_rev, 'end_revision': end_rev
1345
class TestLogWithBugs(TestCaseForLogFormatter, TestLogMixin):
1348
super(TestLogWithBugs, self).setUp()
1349
log.properties_handler_registry.register(
1350
'bugs_properties_handler',
1351
log._bugs_properties_handler)
1353
def make_commits_with_bugs(self):
1354
"""Helper method for LogFormatter tests"""
1355
tree = self.make_branch_and_tree(u'.')
1356
self.build_tree(['a', 'b'])
1358
self.wt_commit(tree, 'simple log message', rev_id='a1',
1359
revprops={'bugs': 'test://bug/id fixed'})
1361
self.wt_commit(tree, 'multiline\nlog\nmessage\n', rev_id='a2',
1362
authors=['Joe Bar <joe@bar.com>'],
1363
revprops={'bugs': 'test://bug/id fixed\n'
1364
'test://bug/2 fixed'})
1368
def test_long_bugs(self):
1369
tree = self.make_commits_with_bugs()
1370
self.assertFormatterResult("""\
1371
------------------------------------------------------------
1373
fixes bugs: test://bug/id test://bug/2
1374
author: Joe Bar <joe@bar.com>
1375
committer: Joe Foo <joe@foo.com>
1377
timestamp: Tue 2005-11-22 00:00:01 +0000
1382
------------------------------------------------------------
1384
fixes bug: test://bug/id
1385
committer: Joe Foo <joe@foo.com>
1387
timestamp: Tue 2005-11-22 00:00:00 +0000
1391
tree.branch, log.LongLogFormatter)
1393
def test_short_bugs(self):
1394
tree = self.make_commits_with_bugs()
1395
self.assertFormatterResult("""\
1396
2 Joe Bar\t2005-11-22
1397
fixes bugs: test://bug/id test://bug/2
1402
1 Joe Foo\t2005-11-22
1403
fixes bug: test://bug/id
1407
tree.branch, log.ShortLogFormatter)
1409
def test_wrong_bugs_property(self):
1410
tree = self.make_branch_and_tree(u'.')
1411
self.build_tree(['foo'])
1412
self.wt_commit(tree, 'simple log message', rev_id='a1',
1413
revprops={'bugs': 'test://bug/id invalid_value'})
1414
self.assertFormatterResult("""\
1415
1 Joe Foo\t2005-11-22
1419
tree.branch, log.ShortLogFormatter)
1421
def test_bugs_handler_present(self):
1422
self.properties_handler_registry.get('bugs_properties_handler')
1425
class TestLogForAuthors(TestCaseForLogFormatter):
1428
super(TestLogForAuthors, self).setUp()
1429
self.wt = self.make_standard_commit('nicky',
1430
authors=['John Doe <jdoe@example.com>',
1431
'Jane Rey <jrey@example.com>'])
1433
def assertFormatterResult(self, formatter, who, result):
1434
formatter_kwargs = dict()
1436
author_list_handler = log.author_list_registry.get(who)
1437
formatter_kwargs['author_list_handler'] = author_list_handler
1438
TestCaseForLogFormatter.assertFormatterResult(self, result,
1439
self.wt.branch, formatter, formatter_kwargs=formatter_kwargs)
1441
def test_line_default(self):
1442
self.assertFormatterResult(log.LineLogFormatter, None, """\
1443
1: John Doe 2005-11-22 add a
1446
def test_line_committer(self):
1447
self.assertFormatterResult(log.LineLogFormatter, 'committer', """\
1448
1: Lorem Ipsum 2005-11-22 add a
1451
def test_line_first(self):
1452
self.assertFormatterResult(log.LineLogFormatter, 'first', """\
1453
1: John Doe 2005-11-22 add a
1456
def test_line_all(self):
1457
self.assertFormatterResult(log.LineLogFormatter, 'all', """\
1458
1: John Doe, Jane Rey 2005-11-22 add a
1462
def test_short_default(self):
1463
self.assertFormatterResult(log.ShortLogFormatter, None, """\
1464
1 John Doe\t2005-11-22
1469
def test_short_committer(self):
1470
self.assertFormatterResult(log.ShortLogFormatter, 'committer', """\
1471
1 Lorem Ipsum\t2005-11-22
1476
def test_short_first(self):
1477
self.assertFormatterResult(log.ShortLogFormatter, 'first', """\
1478
1 John Doe\t2005-11-22
1483
def test_short_all(self):
1484
self.assertFormatterResult(log.ShortLogFormatter, 'all', """\
1485
1 John Doe, Jane Rey\t2005-11-22
1490
def test_long_default(self):
1491
self.assertFormatterResult(log.LongLogFormatter, None, """\
1492
------------------------------------------------------------
1494
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
1495
committer: Lorem Ipsum <test@example.com>
1497
timestamp: Tue 2005-11-22 00:00:00 +0000
1502
def test_long_committer(self):
1503
self.assertFormatterResult(log.LongLogFormatter, 'committer', """\
1504
------------------------------------------------------------
1506
committer: Lorem Ipsum <test@example.com>
1508
timestamp: Tue 2005-11-22 00:00:00 +0000
1513
def test_long_first(self):
1514
self.assertFormatterResult(log.LongLogFormatter, 'first', """\
1515
------------------------------------------------------------
1517
author: John Doe <jdoe@example.com>
1518
committer: Lorem Ipsum <test@example.com>
1520
timestamp: Tue 2005-11-22 00:00:00 +0000
1525
def test_long_all(self):
1526
self.assertFormatterResult(log.LongLogFormatter, 'all', """\
1527
------------------------------------------------------------
1529
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
1530
committer: Lorem Ipsum <test@example.com>
1532
timestamp: Tue 2005-11-22 00:00:00 +0000
1537
def test_gnu_changelog_default(self):
1538
self.assertFormatterResult(log.GnuChangelogLogFormatter, None, """\
1539
2005-11-22 John Doe <jdoe@example.com>
1545
def test_gnu_changelog_committer(self):
1546
self.assertFormatterResult(log.GnuChangelogLogFormatter, 'committer', """\
1547
2005-11-22 Lorem Ipsum <test@example.com>
1553
def test_gnu_changelog_first(self):
1554
self.assertFormatterResult(log.GnuChangelogLogFormatter, 'first', """\
1555
2005-11-22 John Doe <jdoe@example.com>
1561
def test_gnu_changelog_all(self):
1562
self.assertFormatterResult(log.GnuChangelogLogFormatter, 'all', """\
1563
2005-11-22 John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
1570
class TestLogExcludeAncestry(tests.TestCaseWithTransport):
1572
def make_branch_with_alternate_ancestries(self, relpath='.'):
1573
# See test_merge_sorted_exclude_ancestry below for the difference with
1574
# bt.per_branch.test_iter_merge_sorted_revision.
1575
# TestIterMergeSortedRevisionsBushyGraph.
1576
# make_branch_with_alternate_ancestries
1577
# and test_merge_sorted_exclude_ancestry
1578
# See the FIXME in assertLogRevnos too.
1579
builder = branchbuilder.BranchBuilder(self.get_transport(relpath))
1591
builder.start_series()
1592
builder.build_snapshot('1', None, [
1593
('add', ('', 'TREE_ROOT', 'directory', '')),])
1594
builder.build_snapshot('1.1.1', ['1'], [])
1595
builder.build_snapshot('2', ['1'], [])
1596
builder.build_snapshot('1.2.1', ['1.1.1'], [])
1597
builder.build_snapshot('1.1.2', ['1.1.1', '1.2.1'], [])
1598
builder.build_snapshot('3', ['2', '1.1.2'], [])
1599
builder.finish_series()
1600
br = builder.get_branch()
1602
self.addCleanup(br.unlock)
1605
def assertLogRevnos(self, expected_revnos, b, start, end,
1606
exclude_common_ancestry, generate_merge_revisions=True):
1607
# FIXME: the layering in log makes it hard to test intermediate levels,
1608
# I wish adding filters with their parameters was easier...
1610
iter_revs = log._calc_view_revisions(
1611
b, start, end, direction='reverse',
1612
generate_merge_revisions=generate_merge_revisions,
1613
exclude_common_ancestry=exclude_common_ancestry)
1614
self.assertEqual(expected_revnos,
1615
[revid for revid, revno, depth in iter_revs])
1617
def test_merge_sorted_exclude_ancestry(self):
1618
b = self.make_branch_with_alternate_ancestries()
1619
self.assertLogRevnos(['3', '1.1.2', '1.2.1', '1.1.1', '2', '1'],
1620
b, '1', '3', exclude_common_ancestry=False)
1621
# '2' is part of the '3' ancestry but not part of '1.1.1' ancestry so
1622
# it should be mentioned even if merge_sort order will make it appear
1624
self.assertLogRevnos(['3', '1.1.2', '1.2.1', '2'],
1625
b, '1.1.1', '3', exclude_common_ancestry=True)
1627
def test_merge_sorted_simple_revnos_exclude_ancestry(self):
1628
b = self.make_branch_with_alternate_ancestries()
1629
self.assertLogRevnos(['3', '2'],
1630
b, '1', '3', exclude_common_ancestry=True,
1631
generate_merge_revisions=False)
1632
self.assertLogRevnos(['3', '1.1.2', '1.2.1', '1.1.1', '2'],
1633
b, '1', '3', exclude_common_ancestry=True,
1634
generate_merge_revisions=True)
1637
class TestLogDefaults(TestCaseForLogFormatter):
1638
def test_default_log_level(self):
1640
Test to ensure that specifying 'levels=1' to make_log_request_dict
1641
doesn't get overwritten when using a LogFormatter that supports more
1645
wt = self._prepare_tree_with_merges()
1648
class CustomLogFormatter(log.LogFormatter):
1649
def __init__(self, *args, **kwargs):
1650
super(CustomLogFormatter, self).__init__(*args, **kwargs)
1652
def get_levels(self):
1653
# log formatter supports all levels:
1655
def log_revision(self, revision):
1656
self.revisions.append(revision)
1658
log_formatter = LogCatcher()
1659
# First request we don't specify number of levels, we should get a
1660
# sensible default (whatever the LogFormatter handles - which in this
1661
# case is 0/everything):
1662
request = log.make_log_request_dict(limit=10)
1663
log.Logger(b, request).show(log_formatter)
1664
# should have all three revisions:
1665
self.assertEqual(len(log_formatter.revisions), 3)
1668
log_formatter = LogCatcher()
1669
# now explicitly request mainline revisions only:
1670
request = log.make_log_request_dict(limit=10, levels=1)
1671
log.Logger(b, request).show(log_formatter)
1672
# should now only have 2 revisions:
1673
self.assertEqual(len(log_formatter.revisions), 2)