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
37
class TestLogMixin(object):
39
def wt_commit(self, wt, message, **kwargs):
40
"""Use some mostly fixed values for commits to simplify tests.
42
Tests can use this function to get some commit attributes. The time
43
stamp is incremented at each commit.
45
if getattr(self, 'timestamp', None) is None:
46
self.timestamp = 1132617600 # Mon 2005-11-22 00:00:00 +0000
48
self.timestamp += 1 # 1 second between each commit
49
kwargs.setdefault('timestamp', self.timestamp)
50
kwargs.setdefault('timezone', 0) # UTC
51
kwargs.setdefault('committer', 'Joe Foo <joe@foo.com>')
53
return wt.commit(message, **kwargs)
56
class TestCaseForLogFormatter(tests.TestCaseWithTransport, TestLogMixin):
59
super(TestCaseForLogFormatter, self).setUp()
60
# keep a reference to the "current" custom prop. handler registry
61
self.properties_handler_registry = log.properties_handler_registry
62
# Use a clean registry for log
63
log.properties_handler_registry = registry.Registry()
66
log.properties_handler_registry = self.properties_handler_registry
67
self.addCleanup(restore)
69
def assertFormatterResult(self, result, branch, formatter_class,
70
formatter_kwargs=None, show_log_kwargs=None):
71
logfile = self.make_utf8_encoded_stringio()
72
if formatter_kwargs is None:
74
formatter = formatter_class(to_file=logfile, **formatter_kwargs)
75
if show_log_kwargs is None:
77
log.show_log(branch, formatter, **show_log_kwargs)
78
self.assertEqualDiff(result, logfile.getvalue())
80
def make_standard_commit(self, branch_nick, **kwargs):
81
wt = self.make_branch_and_tree('.')
83
self.addCleanup(wt.unlock)
84
self.build_tree(['a'])
86
wt.branch.nick = branch_nick
87
kwargs.setdefault('committer', 'Lorem Ipsum <test@example.com>')
88
kwargs.setdefault('authors', ['John Doe <jdoe@example.com>'])
89
self.wt_commit(wt, 'add a', **kwargs)
92
def make_commits_with_trailing_newlines(self, wt):
93
"""Helper method for LogFormatter tests"""
96
self.build_tree_contents([('a', b'hello moto\n')])
97
self.wt_commit(wt, 'simple log message', rev_id=b'a1')
98
self.build_tree_contents([('b', b'goodbye\n')])
100
self.wt_commit(wt, 'multiline\nlog\nmessage\n', rev_id=b'a2')
102
self.build_tree_contents([('c', b'just another manic monday\n')])
104
self.wt_commit(wt, 'single line with trailing newline\n', rev_id=b'a3')
107
def _prepare_tree_with_merges(self, with_tags=False):
108
wt = self.make_branch_and_memory_tree('.')
110
self.addCleanup(wt.unlock)
112
self.wt_commit(wt, 'rev-1', rev_id=b'rev-1')
113
self.wt_commit(wt, 'rev-merged', rev_id=b'rev-2a')
114
wt.set_parent_ids([b'rev-1', b'rev-2a'])
115
wt.branch.set_last_revision_info(1, b'rev-1')
116
self.wt_commit(wt, 'rev-2', rev_id=b'rev-2b')
119
branch.tags.set_tag('v0.2', b'rev-2b')
120
self.wt_commit(wt, 'rev-3', rev_id=b'rev-3')
121
branch.tags.set_tag('v1.0rc1', b'rev-3')
122
branch.tags.set_tag('v1.0', b'rev-3')
126
class LogCatcher(log.LogFormatter):
127
"""Pull log messages into a list rather than displaying them.
129
To simplify testing we save logged revisions here rather than actually
130
formatting anything, so that we can precisely check the result without
131
being dependent on the formatting.
134
supports_merge_revisions = True
135
supports_delta = True
139
def __init__(self, *args, **kwargs):
140
kwargs.update(dict(to_file=None))
141
super(LogCatcher, self).__init__(*args, **kwargs)
144
def log_revision(self, revision):
145
self.revisions.append(revision)
148
class TestShowLog(tests.TestCaseWithTransport):
150
def checkDelta(self, delta, **kw):
151
"""Check the filenames touched by a delta are as expected.
153
Caller only have to pass in the list of files for each part, all
154
unspecified parts are considered empty (and checked as such).
156
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
157
# By default we expect an empty list
158
expected = kw.get(n, [])
159
# strip out only the path components
160
got = [x.path[1] or x.path[0] for x in getattr(delta, n)]
161
self.assertEqual(expected, got)
163
def assertInvalidRevisonNumber(self, br, start, end):
165
self.assertRaises(errors.InvalidRevisionNumber,
166
log.show_log, br, lf,
167
start_revision=start, end_revision=end)
169
def test_cur_revno(self):
170
wt = self.make_branch_and_tree('.')
174
wt.commit('empty commit')
175
log.show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
177
# Since there is a single revision in the branch all the combinations
179
self.assertInvalidRevisonNumber(b, 2, 1)
180
self.assertInvalidRevisonNumber(b, 1, 2)
181
self.assertInvalidRevisonNumber(b, 0, 2)
182
self.assertInvalidRevisonNumber(b, -1, 1)
183
self.assertInvalidRevisonNumber(b, 1, -1)
184
self.assertInvalidRevisonNumber(b, 1, 0)
186
def test_empty_branch(self):
187
wt = self.make_branch_and_tree('.')
190
log.show_log(wt.branch, lf)
192
self.assertEqual([], lf.revisions)
194
def test_empty_commit(self):
195
wt = self.make_branch_and_tree('.')
197
wt.commit('empty commit')
199
log.show_log(wt.branch, lf, verbose=True)
201
self.assertEqual(1, len(revs))
202
self.assertEqual('1', revs[0].revno)
203
self.assertEqual('empty commit', revs[0].rev.message)
204
self.checkDelta(revs[0].delta)
206
def test_simple_commit(self):
207
wt = self.make_branch_and_tree('.')
208
wt.commit('empty commit')
209
self.build_tree(['hello'])
211
wt.commit('add one file',
212
committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
213
u'<test@example.com>')
215
log.show_log(wt.branch, lf, verbose=True)
216
self.assertEqual(2, len(lf.revisions))
217
# first one is most recent
218
log_entry = lf.revisions[0]
219
self.assertEqual('2', log_entry.revno)
220
self.assertEqual('add one file', log_entry.rev.message)
221
self.checkDelta(log_entry.delta, added=['hello'])
223
def test_commit_message_with_control_chars(self):
224
wt = self.make_branch_and_tree('.')
225
msg = u"All 8-bit chars: " + ''.join([chr(x) for x in range(256)])
226
msg = msg.replace(u'\r', u'\n')
229
log.show_log(wt.branch, lf, verbose=True)
230
committed_msg = lf.revisions[0].rev.message
231
if wt.branch.repository._serializer.squashes_xml_invalid_characters:
232
self.assertNotEqual(msg, committed_msg)
233
self.assertTrue(len(committed_msg) > len(msg))
235
self.assertEqual(msg, committed_msg)
237
def test_commit_message_without_control_chars(self):
238
wt = self.make_branch_and_tree('.')
239
# escaped. As ElementTree apparently does some kind of
240
# newline conversion, neither LF (\x0A) nor CR (\x0D) are
241
# included in the test commit message, even though they are
242
# valid XML 1.0 characters.
243
msg = "\x09" + ''.join([chr(x) for x in range(0x20, 256)])
246
log.show_log(wt.branch, lf, verbose=True)
247
committed_msg = lf.revisions[0].rev.message
248
self.assertEqual(msg, committed_msg)
250
def test_deltas_in_merge_revisions(self):
251
"""Check deltas created for both mainline and merge revisions"""
252
wt = self.make_branch_and_tree('parent')
253
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
256
wt.commit(message='add file1 and file2')
257
self.run_bzr('branch parent child')
258
os.unlink('child/file1')
259
with open('child/file2', 'wb') as f:
261
self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
264
self.run_bzr('merge ../child')
265
wt.commit('merge child branch')
269
lf.supports_merge_revisions = True
270
log.show_log(b, lf, verbose=True)
273
self.assertEqual(3, len(revs))
276
self.assertEqual('2', logentry.revno)
277
self.assertEqual('merge child branch', logentry.rev.message)
278
self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
281
self.assertEqual('1.1.1', logentry.revno)
282
self.assertEqual('remove file1 and modify file2', logentry.rev.message)
283
self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
286
self.assertEqual('1', logentry.revno)
287
self.assertEqual('add file1 and file2', logentry.rev.message)
288
self.checkDelta(logentry.delta, added=['file1', 'file2'])
290
def test_bug_842695_log_restricted_to_dir(self):
291
# Comments here indicate revision numbers in trunk # VVVVV
292
trunk = self.make_branch_and_tree('this')
293
trunk.commit('initial trunk') # 1
294
adder = trunk.controldir.sprout('adder').open_workingtree()
295
merger = trunk.controldir.sprout('merger').open_workingtree()
296
self.build_tree_contents([
298
('adder/dir/file', b'foo'),
300
adder.add(['dir', 'dir/file'])
301
adder.commit('added dir') # 1.1.1
302
trunk.merge_from_branch(adder.branch)
303
trunk.commit('merged adder into trunk') # 2
304
merger.merge_from_branch(trunk.branch)
305
merger.commit('merged trunk into merger') # 1.2.1
306
# Commits are processed in increments of 200 revisions, so
307
# make sure the two merges into trunk are in different chunks.
309
trunk.commit('intermediate commit %d' % i) # 3-202
310
trunk.merge_from_branch(merger.branch)
311
trunk.commit('merged merger into trunk') # 203
312
file_id = trunk.path2id('dir')
314
lf.supports_merge_revisions = True
315
log.show_log(trunk.branch, lf, file_id)
317
self.assertEqual(['2', '1.1.1'], [r.revno for r in lf.revisions])
318
except AssertionError:
319
raise tests.KnownFailure("bug #842695")
322
class TestFormatSignatureValidity(tests.TestCaseWithTransport):
324
def verify_revision_signature(self, revid, gpg_strategy):
325
return (gpg.SIGNATURE_VALID,
326
u'UTF8 Test \xa1\xb1\xc1\xd1\xe1\xf1 <jrandom@example.com>')
328
def test_format_signature_validity_utf(self):
329
"""Check that GPG signatures containing UTF-8 names are formatted
331
wt = self.make_branch_and_tree('.')
332
revid = wt.commit('empty commit')
333
repo = wt.branch.repository
334
# Monkey patch out checking if this rev is actually signed, since we
335
# can't sign it without a heavier TestCase and LoopbackGPGStrategy
336
# doesn't care anyways.
337
self.overrideAttr(repo, 'verify_revision_signature',
338
self.verify_revision_signature)
339
out = log.format_signature_validity(revid, wt.branch)
341
u'valid signature from UTF8 Test \xa1\xb1\xc1\xd1\xe1\xf1 <jrandom@example.com>',
345
class TestShortLogFormatter(TestCaseForLogFormatter):
347
def test_trailing_newlines(self):
348
wt = self.make_branch_and_tree('.')
349
b = self.make_commits_with_trailing_newlines(wt)
350
self.assertFormatterResult(b"""\
351
3 Joe Foo\t2005-11-22
352
single line with trailing newline
354
2 Joe Foo\t2005-11-22
359
1 Joe Foo\t2005-11-22
363
b, log.ShortLogFormatter)
365
def test_short_log_with_merges(self):
366
wt = self._prepare_tree_with_merges()
367
self.assertFormatterResult(b"""\
368
2 Joe Foo\t2005-11-22 [merge]
371
1 Joe Foo\t2005-11-22
375
wt.branch, log.ShortLogFormatter)
377
def test_short_log_with_merges_and_advice(self):
378
wt = self._prepare_tree_with_merges()
379
self.assertFormatterResult(b"""\
380
2 Joe Foo\t2005-11-22 [merge]
383
1 Joe Foo\t2005-11-22
386
Use --include-merged or -n0 to see merged revisions.
388
wt.branch, log.ShortLogFormatter,
389
formatter_kwargs=dict(show_advice=True))
391
def test_short_log_with_merges_and_range(self):
392
wt = self._prepare_tree_with_merges()
393
self.wt_commit(wt, 'rev-3a', rev_id=b'rev-3a')
394
wt.branch.set_last_revision_info(2, b'rev-2b')
395
wt.set_parent_ids([b'rev-2b', b'rev-3a'])
396
self.wt_commit(wt, 'rev-3b', rev_id=b'rev-3b')
397
self.assertFormatterResult(b"""\
398
3 Joe Foo\t2005-11-22 [merge]
401
2 Joe Foo\t2005-11-22 [merge]
405
wt.branch, log.ShortLogFormatter,
406
show_log_kwargs=dict(start_revision=2, end_revision=3))
408
def test_short_log_with_tags(self):
409
wt = self._prepare_tree_with_merges(with_tags=True)
410
self.assertFormatterResult(b"""\
411
3 Joe Foo\t2005-11-22 {v1.0, v1.0rc1}
414
2 Joe Foo\t2005-11-22 {v0.2} [merge]
417
1 Joe Foo\t2005-11-22
421
wt.branch, log.ShortLogFormatter)
423
def test_short_log_single_merge_revision(self):
424
wt = self._prepare_tree_with_merges()
425
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
426
rev = revspec.in_history(wt.branch)
427
self.assertFormatterResult(b"""\
428
1.1.1 Joe Foo\t2005-11-22
432
wt.branch, log.ShortLogFormatter,
433
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
435
def test_show_ids(self):
436
wt = self.make_branch_and_tree('parent')
437
self.build_tree(['parent/f1', 'parent/f2'])
439
self.wt_commit(wt, 'first post', rev_id=b'a')
440
child_wt = wt.controldir.sprout('child').open_workingtree()
441
self.wt_commit(child_wt, 'branch 1 changes', rev_id=b'b')
442
wt.merge_from_branch(child_wt.branch)
443
self.wt_commit(wt, 'merge branch 1', rev_id=b'c')
444
self.assertFormatterResult(b"""\
445
2 Joe Foo\t2005-11-22 [merge]
449
1.1.1 Joe Foo\t2005-11-22
453
1 Joe Foo\t2005-11-22
458
wt.branch, log.ShortLogFormatter,
459
formatter_kwargs=dict(levels=0, show_ids=True))
462
class TestShortLogFormatterWithMergeRevisions(TestCaseForLogFormatter):
464
def test_short_merge_revs_log_with_merges(self):
465
wt = self._prepare_tree_with_merges()
466
# Note that the 1.1.1 indenting is in fact correct given that
467
# the revision numbers are right justified within 5 characters
468
# for mainline revnos and 9 characters for dotted revnos.
469
self.assertFormatterResult(b"""\
470
2 Joe Foo\t2005-11-22 [merge]
473
1.1.1 Joe Foo\t2005-11-22
476
1 Joe Foo\t2005-11-22
480
wt.branch, log.ShortLogFormatter,
481
formatter_kwargs=dict(levels=0))
483
def test_short_merge_revs_log_single_merge_revision(self):
484
wt = self._prepare_tree_with_merges()
485
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
486
rev = revspec.in_history(wt.branch)
487
self.assertFormatterResult(b"""\
488
1.1.1 Joe Foo\t2005-11-22
492
wt.branch, log.ShortLogFormatter,
493
formatter_kwargs=dict(levels=0),
494
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
497
class TestLongLogFormatter(TestCaseForLogFormatter):
499
def test_verbose_log(self):
500
"""Verbose log includes changed files
504
wt = self.make_standard_commit('test_verbose_log', authors=[])
505
self.assertFormatterResult(b'''\
506
------------------------------------------------------------
508
committer: Lorem Ipsum <test@example.com>
509
branch nick: test_verbose_log
510
timestamp: Tue 2005-11-22 00:00:00 +0000
516
wt.branch, log.LongLogFormatter,
517
show_log_kwargs=dict(verbose=True))
519
def test_merges_are_indented_by_level(self):
520
wt = self.make_branch_and_tree('parent')
521
self.wt_commit(wt, 'first post')
522
child_wt = wt.controldir.sprout('child').open_workingtree()
523
self.wt_commit(child_wt, 'branch 1')
524
smallerchild_wt = wt.controldir.sprout(
525
'smallerchild').open_workingtree()
526
self.wt_commit(smallerchild_wt, 'branch 2')
527
child_wt.merge_from_branch(smallerchild_wt.branch)
528
self.wt_commit(child_wt, 'merge branch 2')
529
wt.merge_from_branch(child_wt.branch)
530
self.wt_commit(wt, 'merge branch 1')
531
self.assertFormatterResult(b"""\
532
------------------------------------------------------------
534
committer: Joe Foo <joe@foo.com>
536
timestamp: Tue 2005-11-22 00:00:04 +0000
539
------------------------------------------------------------
541
committer: Joe Foo <joe@foo.com>
543
timestamp: Tue 2005-11-22 00:00:03 +0000
546
------------------------------------------------------------
548
committer: Joe Foo <joe@foo.com>
549
branch nick: smallerchild
550
timestamp: Tue 2005-11-22 00:00:02 +0000
553
------------------------------------------------------------
555
committer: Joe Foo <joe@foo.com>
557
timestamp: Tue 2005-11-22 00:00:01 +0000
560
------------------------------------------------------------
562
committer: Joe Foo <joe@foo.com>
564
timestamp: Tue 2005-11-22 00:00:00 +0000
568
wt.branch, log.LongLogFormatter,
569
formatter_kwargs=dict(levels=0),
570
show_log_kwargs=dict(verbose=True))
572
def test_verbose_merge_revisions_contain_deltas(self):
573
wt = self.make_branch_and_tree('parent')
574
self.build_tree(['parent/f1', 'parent/f2'])
576
self.wt_commit(wt, 'first post')
577
child_wt = wt.controldir.sprout('child').open_workingtree()
578
os.unlink('child/f1')
579
self.build_tree_contents([('child/f2', b'hello\n')])
580
self.wt_commit(child_wt, 'removed f1 and modified f2')
581
wt.merge_from_branch(child_wt.branch)
582
self.wt_commit(wt, 'merge branch 1')
583
self.assertFormatterResult(b"""\
584
------------------------------------------------------------
586
committer: Joe Foo <joe@foo.com>
588
timestamp: Tue 2005-11-22 00:00:02 +0000
595
------------------------------------------------------------
597
committer: Joe Foo <joe@foo.com>
599
timestamp: Tue 2005-11-22 00:00:01 +0000
601
removed f1 and modified f2
606
------------------------------------------------------------
608
committer: Joe Foo <joe@foo.com>
610
timestamp: Tue 2005-11-22 00:00:00 +0000
617
wt.branch, log.LongLogFormatter,
618
formatter_kwargs=dict(levels=0),
619
show_log_kwargs=dict(verbose=True))
621
def test_trailing_newlines(self):
622
wt = self.make_branch_and_tree('.')
623
b = self.make_commits_with_trailing_newlines(wt)
624
self.assertFormatterResult(b"""\
625
------------------------------------------------------------
627
committer: Joe Foo <joe@foo.com>
629
timestamp: Tue 2005-11-22 00:00:02 +0000
631
single line with trailing newline
632
------------------------------------------------------------
634
committer: Joe Foo <joe@foo.com>
636
timestamp: Tue 2005-11-22 00:00:01 +0000
641
------------------------------------------------------------
643
committer: Joe Foo <joe@foo.com>
645
timestamp: Tue 2005-11-22 00:00:00 +0000
649
b, log.LongLogFormatter)
651
def test_author_in_log(self):
652
"""Log includes the author name if it's set in
653
the revision properties
655
wt = self.make_standard_commit('test_author_log',
656
authors=['John Doe <jdoe@example.com>',
657
'Jane Rey <jrey@example.com>'])
658
self.assertFormatterResult(b"""\
659
------------------------------------------------------------
661
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
662
committer: Lorem Ipsum <test@example.com>
663
branch nick: test_author_log
664
timestamp: Tue 2005-11-22 00:00:00 +0000
668
wt.branch, log.LongLogFormatter)
670
def test_properties_in_log(self):
671
"""Log includes the custom properties returned by the registered
674
wt = self.make_standard_commit('test_properties_in_log')
676
def trivial_custom_prop_handler(revision):
677
return {'test_prop': 'test_value'}
679
# Cleaned up in setUp()
680
log.properties_handler_registry.register(
681
'trivial_custom_prop_handler',
682
trivial_custom_prop_handler)
683
self.assertFormatterResult(b"""\
684
------------------------------------------------------------
686
test_prop: test_value
687
author: John Doe <jdoe@example.com>
688
committer: Lorem Ipsum <test@example.com>
689
branch nick: test_properties_in_log
690
timestamp: Tue 2005-11-22 00:00:00 +0000
694
wt.branch, log.LongLogFormatter)
696
def test_properties_in_short_log(self):
697
"""Log includes the custom properties returned by the registered
700
wt = self.make_standard_commit('test_properties_in_short_log')
702
def trivial_custom_prop_handler(revision):
703
return {'test_prop': 'test_value'}
705
log.properties_handler_registry.register(
706
'trivial_custom_prop_handler',
707
trivial_custom_prop_handler)
708
self.assertFormatterResult(b"""\
709
1 John Doe\t2005-11-22
710
test_prop: test_value
714
wt.branch, log.ShortLogFormatter)
716
def test_error_in_properties_handler(self):
717
"""Log includes the custom properties returned by the registered
720
wt = self.make_standard_commit('error_in_properties_handler',
721
revprops={u'first_prop': 'first_value'})
722
sio = self.make_utf8_encoded_stringio()
723
formatter = log.LongLogFormatter(to_file=sio)
725
def trivial_custom_prop_handler(revision):
726
raise Exception("a test error")
728
log.properties_handler_registry.register(
729
'trivial_custom_prop_handler',
730
trivial_custom_prop_handler)
731
log.show_log(wt.branch, formatter)
732
self.assertContainsRe(
733
sio.getvalue(), b'brz: ERROR: Exception: a test error')
735
def test_properties_handler_bad_argument(self):
736
wt = self.make_standard_commit('bad_argument',
737
revprops={u'a_prop': 'test_value'})
738
sio = self.make_utf8_encoded_stringio()
739
formatter = log.LongLogFormatter(to_file=sio)
741
def bad_argument_prop_handler(revision):
742
return {'custom_prop_name': revision.properties['a_prop']}
744
log.properties_handler_registry.register(
745
'bad_argument_prop_handler',
746
bad_argument_prop_handler)
748
self.assertRaises(AttributeError, formatter.show_properties,
751
revision = wt.branch.repository.get_revision(wt.branch.last_revision())
752
formatter.show_properties(revision, '')
753
self.assertEqualDiff(b'custom_prop_name: test_value\n',
756
def test_show_ids(self):
757
wt = self.make_branch_and_tree('parent')
758
self.build_tree(['parent/f1', 'parent/f2'])
760
self.wt_commit(wt, 'first post', rev_id=b'a')
761
child_wt = wt.controldir.sprout('child').open_workingtree()
762
self.wt_commit(child_wt, 'branch 1 changes', rev_id=b'b')
763
wt.merge_from_branch(child_wt.branch)
764
self.wt_commit(wt, 'merge branch 1', rev_id=b'c')
765
self.assertFormatterResult(b"""\
766
------------------------------------------------------------
771
committer: Joe Foo <joe@foo.com>
773
timestamp: Tue 2005-11-22 00:00:02 +0000
776
------------------------------------------------------------
780
committer: Joe Foo <joe@foo.com>
782
timestamp: Tue 2005-11-22 00:00:01 +0000
785
------------------------------------------------------------
788
committer: Joe Foo <joe@foo.com>
790
timestamp: Tue 2005-11-22 00:00:00 +0000
794
wt.branch, log.LongLogFormatter,
795
formatter_kwargs=dict(levels=0, show_ids=True))
798
class TestLongLogFormatterWithoutMergeRevisions(TestCaseForLogFormatter):
800
def test_long_verbose_log(self):
801
"""Verbose log includes changed files
805
wt = self.make_standard_commit('test_long_verbose_log', authors=[])
806
self.assertFormatterResult(b"""\
807
------------------------------------------------------------
809
committer: Lorem Ipsum <test@example.com>
810
branch nick: test_long_verbose_log
811
timestamp: Tue 2005-11-22 00:00:00 +0000
817
wt.branch, log.LongLogFormatter,
818
formatter_kwargs=dict(levels=1),
819
show_log_kwargs=dict(verbose=True))
821
def test_long_verbose_contain_deltas(self):
822
wt = self.make_branch_and_tree('parent')
823
self.build_tree(['parent/f1', 'parent/f2'])
825
self.wt_commit(wt, 'first post')
826
child_wt = wt.controldir.sprout('child').open_workingtree()
827
os.unlink('child/f1')
828
self.build_tree_contents([('child/f2', b'hello\n')])
829
self.wt_commit(child_wt, 'removed f1 and modified f2')
830
wt.merge_from_branch(child_wt.branch)
831
self.wt_commit(wt, 'merge branch 1')
832
self.assertFormatterResult(b"""\
833
------------------------------------------------------------
835
committer: Joe Foo <joe@foo.com>
837
timestamp: Tue 2005-11-22 00:00:02 +0000
844
------------------------------------------------------------
846
committer: Joe Foo <joe@foo.com>
848
timestamp: Tue 2005-11-22 00:00:00 +0000
855
wt.branch, log.LongLogFormatter,
856
formatter_kwargs=dict(levels=1),
857
show_log_kwargs=dict(verbose=True))
859
def test_long_trailing_newlines(self):
860
wt = self.make_branch_and_tree('.')
861
b = self.make_commits_with_trailing_newlines(wt)
862
self.assertFormatterResult(b"""\
863
------------------------------------------------------------
865
committer: Joe Foo <joe@foo.com>
867
timestamp: Tue 2005-11-22 00:00:02 +0000
869
single line with trailing newline
870
------------------------------------------------------------
872
committer: Joe Foo <joe@foo.com>
874
timestamp: Tue 2005-11-22 00:00:01 +0000
879
------------------------------------------------------------
881
committer: Joe Foo <joe@foo.com>
883
timestamp: Tue 2005-11-22 00:00:00 +0000
887
b, log.LongLogFormatter,
888
formatter_kwargs=dict(levels=1))
890
def test_long_author_in_log(self):
891
"""Log includes the author name if it's set in
892
the revision properties
894
wt = self.make_standard_commit('test_author_log')
895
self.assertFormatterResult(b"""\
896
------------------------------------------------------------
898
author: John Doe <jdoe@example.com>
899
committer: Lorem Ipsum <test@example.com>
900
branch nick: test_author_log
901
timestamp: Tue 2005-11-22 00:00:00 +0000
905
wt.branch, log.LongLogFormatter,
906
formatter_kwargs=dict(levels=1))
908
def test_long_properties_in_log(self):
909
"""Log includes the custom properties returned by the registered
912
wt = self.make_standard_commit('test_properties_in_log')
914
def trivial_custom_prop_handler(revision):
915
return {'test_prop': 'test_value'}
917
log.properties_handler_registry.register(
918
'trivial_custom_prop_handler',
919
trivial_custom_prop_handler)
920
self.assertFormatterResult(b"""\
921
------------------------------------------------------------
923
test_prop: test_value
924
author: John Doe <jdoe@example.com>
925
committer: Lorem Ipsum <test@example.com>
926
branch nick: test_properties_in_log
927
timestamp: Tue 2005-11-22 00:00:00 +0000
931
wt.branch, log.LongLogFormatter,
932
formatter_kwargs=dict(levels=1))
935
class TestLineLogFormatter(TestCaseForLogFormatter):
937
def test_line_log(self):
938
"""Line log should show revno
942
wt = self.make_standard_commit('test-line-log',
943
committer='Line-Log-Formatter Tester <test@line.log>',
945
self.assertFormatterResult(b"""\
946
1: Line-Log-Formatte... 2005-11-22 add a
948
wt.branch, log.LineLogFormatter)
950
def test_trailing_newlines(self):
951
wt = self.make_branch_and_tree('.')
952
b = self.make_commits_with_trailing_newlines(wt)
953
self.assertFormatterResult(b"""\
954
3: Joe Foo 2005-11-22 single line with trailing newline
955
2: Joe Foo 2005-11-22 multiline
956
1: Joe Foo 2005-11-22 simple log message
958
b, log.LineLogFormatter)
960
def test_line_log_single_merge_revision(self):
961
wt = self._prepare_tree_with_merges()
962
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
963
rev = revspec.in_history(wt.branch)
964
self.assertFormatterResult(b"""\
965
1.1.1: Joe Foo 2005-11-22 rev-merged
967
wt.branch, log.LineLogFormatter,
968
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
970
def test_line_log_with_tags(self):
971
wt = self._prepare_tree_with_merges(with_tags=True)
972
self.assertFormatterResult(b"""\
973
3: Joe Foo 2005-11-22 {v1.0, v1.0rc1} rev-3
974
2: Joe Foo 2005-11-22 [merge] {v0.2} rev-2
975
1: Joe Foo 2005-11-22 rev-1
977
wt.branch, log.LineLogFormatter)
980
class TestLineLogFormatterWithMergeRevisions(TestCaseForLogFormatter):
982
def test_line_merge_revs_log(self):
983
"""Line log should show revno
987
wt = self.make_standard_commit('test-line-log',
988
committer='Line-Log-Formatter Tester <test@line.log>',
990
self.assertFormatterResult(b"""\
991
1: Line-Log-Formatte... 2005-11-22 add a
993
wt.branch, log.LineLogFormatter)
995
def test_line_merge_revs_log_single_merge_revision(self):
996
wt = self._prepare_tree_with_merges()
997
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
998
rev = revspec.in_history(wt.branch)
999
self.assertFormatterResult(b"""\
1000
1.1.1: Joe Foo 2005-11-22 rev-merged
1002
wt.branch, log.LineLogFormatter,
1003
formatter_kwargs=dict(levels=0),
1004
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
1006
def test_line_merge_revs_log_with_merges(self):
1007
wt = self._prepare_tree_with_merges()
1008
self.assertFormatterResult(b"""\
1009
2: Joe Foo 2005-11-22 [merge] rev-2
1010
1.1.1: Joe Foo 2005-11-22 rev-merged
1011
1: Joe Foo 2005-11-22 rev-1
1013
wt.branch, log.LineLogFormatter,
1014
formatter_kwargs=dict(levels=0))
1017
class TestGnuChangelogFormatter(TestCaseForLogFormatter):
1019
def test_gnu_changelog(self):
1020
wt = self.make_standard_commit('nicky', authors=[])
1021
self.assertFormatterResult(b'''\
1022
2005-11-22 Lorem Ipsum <test@example.com>
1027
wt.branch, log.GnuChangelogLogFormatter)
1029
def test_with_authors(self):
1030
wt = self.make_standard_commit('nicky',
1031
authors=['Fooa Fooz <foo@example.com>',
1032
'Bari Baro <bar@example.com>'])
1033
self.assertFormatterResult(b'''\
1034
2005-11-22 Fooa Fooz <foo@example.com>
1039
wt.branch, log.GnuChangelogLogFormatter)
1041
def test_verbose(self):
1042
wt = self.make_standard_commit('nicky')
1043
self.assertFormatterResult(b'''\
1044
2005-11-22 John Doe <jdoe@example.com>
1051
wt.branch, log.GnuChangelogLogFormatter,
1052
show_log_kwargs=dict(verbose=True))
1055
class TestShowChangedRevisions(tests.TestCaseWithTransport):
1057
def test_show_changed_revisions_verbose(self):
1058
tree = self.make_branch_and_tree('tree_a')
1059
self.build_tree(['tree_a/foo'])
1061
tree.commit('bar', rev_id=b'bar-id')
1062
s = self.make_utf8_encoded_stringio()
1063
log.show_changed_revisions(tree.branch, [], [b'bar-id'], s)
1064
self.assertContainsRe(s.getvalue(), b'bar')
1065
self.assertNotContainsRe(s.getvalue(), b'foo')
1068
class TestLogFormatter(tests.TestCase):
1071
super(TestLogFormatter, self).setUp()
1072
self.rev = revision.Revision(b'a-id')
1073
self.lf = log.LogFormatter(None)
1075
def test_short_committer(self):
1076
def assertCommitter(expected, committer):
1077
self.rev.committer = committer
1078
self.assertEqual(expected, self.lf.short_committer(self.rev))
1080
assertCommitter('John Doe', 'John Doe <jdoe@example.com>')
1081
assertCommitter('John Smith', 'John Smith <jsmith@example.com>')
1082
assertCommitter('John Smith', 'John Smith')
1083
assertCommitter('jsmith@example.com', 'jsmith@example.com')
1084
assertCommitter('jsmith@example.com', '<jsmith@example.com>')
1085
assertCommitter('John Smith', 'John Smith jsmith@example.com')
1087
def test_short_author(self):
1088
def assertAuthor(expected, author):
1089
self.rev.properties['author'] = author
1090
self.assertEqual(expected, self.lf.short_author(self.rev))
1092
assertAuthor('John Smith', 'John Smith <jsmith@example.com>')
1093
assertAuthor('John Smith', 'John Smith')
1094
assertAuthor('jsmith@example.com', 'jsmith@example.com')
1095
assertAuthor('jsmith@example.com', '<jsmith@example.com>')
1096
assertAuthor('John Smith', 'John Smith jsmith@example.com')
1098
def test_short_author_from_committer(self):
1099
self.rev.committer = 'John Doe <jdoe@example.com>'
1100
self.assertEqual('John Doe', self.lf.short_author(self.rev))
1102
def test_short_author_from_authors(self):
1103
self.rev.properties['authors'] = ('John Smith <jsmith@example.com>\n'
1104
'Jane Rey <jrey@example.com>')
1105
self.assertEqual('John Smith', self.lf.short_author(self.rev))
1108
class TestReverseByDepth(tests.TestCase):
1109
"""Test reverse_by_depth behavior.
1111
This is used to present revisions in forward (oldest first) order in a nice
1114
The tests use lighter revision description to ease reading.
1117
def assertReversed(self, forward, backward):
1118
# Transform the descriptions to suit the API: tests use (revno, depth),
1119
# while the API expects (revid, revno, depth)
1120
def complete_revisions(l):
1121
"""Transform the description to suit the API.
1123
Tests use (revno, depth) whil the API expects (revid, revno, depth).
1124
Since the revid is arbitrary, we just duplicate revno
1126
return [(r, r, d) for r, d in l]
1127
forward = complete_revisions(forward)
1128
backward = complete_revisions(backward)
1129
self.assertEqual(forward, log.reverse_by_depth(backward))
1131
def test_mainline_revisions(self):
1132
self.assertReversed([('1', 0), ('2', 0)],
1133
[('2', 0), ('1', 0)])
1135
def test_merged_revisions(self):
1136
self.assertReversed([('1', 0), ('2', 0), ('2.2', 1), ('2.1', 1), ],
1137
[('2', 0), ('2.1', 1), ('2.2', 1), ('1', 0), ])
1139
def test_shifted_merged_revisions(self):
1140
"""Test irregular layout.
1142
Requesting revisions touching a file can produce "holes" in the depths.
1144
self.assertReversed([('1', 0), ('2', 0), ('1.1', 2), ('1.2', 2), ],
1145
[('2', 0), ('1.2', 2), ('1.1', 2), ('1', 0), ])
1147
def test_merged_without_child_revisions(self):
1148
"""Test irregular layout.
1150
Revision ranges can produce "holes" in the depths.
1152
# When a revision of higher depth doesn't follow one of lower depth, we
1153
# assume a lower depth one is virtually there
1154
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1155
[('4', 4), ('3', 3), ('2', 2), ('1', 2), ])
1156
# So we get the same order after reversing below even if the original
1157
# revisions are not in the same order.
1158
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1159
[('3', 3), ('4', 4), ('2', 2), ('1', 2), ])
1162
class TestHistoryChange(tests.TestCaseWithTransport):
1164
def setup_a_tree(self):
1165
tree = self.make_branch_and_tree('tree')
1167
self.addCleanup(tree.unlock)
1168
tree.commit('1a', rev_id=b'1a')
1169
tree.commit('2a', rev_id=b'2a')
1170
tree.commit('3a', rev_id=b'3a')
1173
def setup_ab_tree(self):
1174
tree = self.setup_a_tree()
1175
tree.set_last_revision(b'1a')
1176
tree.branch.set_last_revision_info(1, b'1a')
1177
tree.commit('2b', rev_id=b'2b')
1178
tree.commit('3b', rev_id=b'3b')
1181
def setup_ac_tree(self):
1182
tree = self.setup_a_tree()
1183
tree.set_last_revision(revision.NULL_REVISION)
1184
tree.branch.set_last_revision_info(0, revision.NULL_REVISION)
1185
tree.commit('1c', rev_id=b'1c')
1186
tree.commit('2c', rev_id=b'2c')
1187
tree.commit('3c', rev_id=b'3c')
1190
def test_all_new(self):
1191
tree = self.setup_ab_tree()
1192
old, new = log.get_history_change(b'1a', b'3a', tree.branch.repository)
1193
self.assertEqual([], old)
1194
self.assertEqual([b'2a', b'3a'], new)
1196
def test_all_old(self):
1197
tree = self.setup_ab_tree()
1198
old, new = log.get_history_change(b'3a', b'1a', tree.branch.repository)
1199
self.assertEqual([], new)
1200
self.assertEqual([b'2a', b'3a'], old)
1202
def test_null_old(self):
1203
tree = self.setup_ab_tree()
1204
old, new = log.get_history_change(revision.NULL_REVISION,
1205
b'3a', tree.branch.repository)
1206
self.assertEqual([], old)
1207
self.assertEqual([b'1a', b'2a', b'3a'], new)
1209
def test_null_new(self):
1210
tree = self.setup_ab_tree()
1211
old, new = log.get_history_change(b'3a', revision.NULL_REVISION,
1212
tree.branch.repository)
1213
self.assertEqual([], new)
1214
self.assertEqual([b'1a', b'2a', b'3a'], old)
1216
def test_diverged(self):
1217
tree = self.setup_ab_tree()
1218
old, new = log.get_history_change(b'3a', b'3b', tree.branch.repository)
1219
self.assertEqual(old, [b'2a', b'3a'])
1220
self.assertEqual(new, [b'2b', b'3b'])
1222
def test_unrelated(self):
1223
tree = self.setup_ac_tree()
1224
old, new = log.get_history_change(b'3a', b'3c', tree.branch.repository)
1225
self.assertEqual(old, [b'1a', b'2a', b'3a'])
1226
self.assertEqual(new, [b'1c', b'2c', b'3c'])
1228
def test_show_branch_change(self):
1229
tree = self.setup_ab_tree()
1231
log.show_branch_change(tree.branch, s, 3, b'3a')
1232
self.assertContainsRe(s.getvalue(),
1233
'[*]{60}\nRemoved Revisions:\n(.|\n)*2a(.|\n)*3a(.|\n)*'
1234
'[*]{60}\n\nAdded Revisions:\n(.|\n)*2b(.|\n)*3b')
1236
def test_show_branch_change_no_change(self):
1237
tree = self.setup_ab_tree()
1239
log.show_branch_change(tree.branch, s, 3, b'3b')
1240
self.assertEqual(s.getvalue(),
1241
'Nothing seems to have changed\n')
1243
def test_show_branch_change_no_old(self):
1244
tree = self.setup_ab_tree()
1246
log.show_branch_change(tree.branch, s, 2, b'2b')
1247
self.assertContainsRe(s.getvalue(), 'Added Revisions:')
1248
self.assertNotContainsRe(s.getvalue(), 'Removed Revisions:')
1250
def test_show_branch_change_no_new(self):
1251
tree = self.setup_ab_tree()
1252
tree.branch.set_last_revision_info(2, b'2b')
1254
log.show_branch_change(tree.branch, s, 3, b'3b')
1255
self.assertContainsRe(s.getvalue(), 'Removed Revisions:')
1256
self.assertNotContainsRe(s.getvalue(), 'Added Revisions:')
1259
class TestRevisionNotInBranch(TestCaseForLogFormatter):
1261
def setup_a_tree(self):
1262
tree = self.make_branch_and_tree('tree')
1264
self.addCleanup(tree.unlock)
1266
'committer': 'Joe Foo <joe@foo.com>',
1267
'timestamp': 1132617600, # Mon 2005-11-22 00:00:00 +0000
1268
'timezone': 0, # UTC
1270
tree.commit('commit 1a', rev_id=b'1a', **kwargs)
1271
tree.commit('commit 2a', rev_id=b'2a', **kwargs)
1272
tree.commit('commit 3a', rev_id=b'3a', **kwargs)
1275
def setup_ab_tree(self):
1276
tree = self.setup_a_tree()
1277
tree.set_last_revision(b'1a')
1278
tree.branch.set_last_revision_info(1, b'1a')
1280
'committer': 'Joe Foo <joe@foo.com>',
1281
'timestamp': 1132617600, # Mon 2005-11-22 00:00:00 +0000
1282
'timezone': 0, # UTC
1284
tree.commit('commit 2b', rev_id=b'2b', **kwargs)
1285
tree.commit('commit 3b', rev_id=b'3b', **kwargs)
1288
def test_one_revision(self):
1289
tree = self.setup_ab_tree()
1291
rev = revisionspec.RevisionInfo(tree.branch, None, b'3a')
1292
log.show_log(tree.branch, lf, verbose=True, start_revision=rev,
1294
self.assertEqual(1, len(lf.revisions))
1295
self.assertEqual(None, lf.revisions[0].revno) # Out-of-branch
1296
self.assertEqual(b'3a', lf.revisions[0].rev.revision_id)
1298
def test_many_revisions(self):
1299
tree = self.setup_ab_tree()
1301
start_rev = revisionspec.RevisionInfo(tree.branch, None, b'1a')
1302
end_rev = revisionspec.RevisionInfo(tree.branch, None, b'3a')
1303
log.show_log(tree.branch, lf, verbose=True, start_revision=start_rev,
1304
end_revision=end_rev)
1305
self.assertEqual(3, len(lf.revisions))
1306
self.assertEqual(None, lf.revisions[0].revno) # Out-of-branch
1307
self.assertEqual(b'3a', lf.revisions[0].rev.revision_id)
1308
self.assertEqual(None, lf.revisions[1].revno) # Out-of-branch
1309
self.assertEqual(b'2a', lf.revisions[1].rev.revision_id)
1310
self.assertEqual('1', lf.revisions[2].revno) # In-branch
1312
def test_long_format(self):
1313
tree = self.setup_ab_tree()
1314
start_rev = revisionspec.RevisionInfo(tree.branch, None, b'1a')
1315
end_rev = revisionspec.RevisionInfo(tree.branch, None, b'3a')
1316
self.assertFormatterResult(b"""\
1317
------------------------------------------------------------
1319
committer: Joe Foo <joe@foo.com>
1321
timestamp: Tue 2005-11-22 00:00:00 +0000
1324
------------------------------------------------------------
1326
committer: Joe Foo <joe@foo.com>
1328
timestamp: Tue 2005-11-22 00:00:00 +0000
1331
------------------------------------------------------------
1333
committer: Joe Foo <joe@foo.com>
1335
timestamp: Tue 2005-11-22 00:00:00 +0000
1339
tree.branch, log.LongLogFormatter, show_log_kwargs={
1340
'start_revision': start_rev, 'end_revision': end_rev
1343
def test_short_format(self):
1344
tree = self.setup_ab_tree()
1345
start_rev = revisionspec.RevisionInfo(tree.branch, None, b'1a')
1346
end_rev = revisionspec.RevisionInfo(tree.branch, None, b'3a')
1347
self.assertFormatterResult(b"""\
1356
1 Joe Foo\t2005-11-22
1360
tree.branch, log.ShortLogFormatter, show_log_kwargs={
1361
'start_revision': start_rev, 'end_revision': end_rev
1364
def test_line_format(self):
1365
tree = self.setup_ab_tree()
1366
start_rev = revisionspec.RevisionInfo(tree.branch, None, b'1a')
1367
end_rev = revisionspec.RevisionInfo(tree.branch, None, b'3a')
1368
self.assertFormatterResult(b"""\
1369
Joe Foo 2005-11-22 commit 3a
1370
Joe Foo 2005-11-22 commit 2a
1371
1: Joe Foo 2005-11-22 commit 1a
1373
tree.branch, log.LineLogFormatter, show_log_kwargs={
1374
'start_revision': start_rev, 'end_revision': end_rev
1378
class TestLogWithBugs(TestCaseForLogFormatter, TestLogMixin):
1381
super(TestLogWithBugs, self).setUp()
1382
log.properties_handler_registry.register(
1383
'bugs_properties_handler',
1384
log._bugs_properties_handler)
1386
def make_commits_with_bugs(self):
1387
"""Helper method for LogFormatter tests"""
1388
tree = self.make_branch_and_tree(u'.')
1389
self.build_tree(['a', 'b'])
1391
self.wt_commit(tree, 'simple log message', rev_id=b'a1',
1392
revprops={u'bugs': 'test://bug/id fixed'})
1394
self.wt_commit(tree, 'multiline\nlog\nmessage\n', rev_id=b'a2',
1395
authors=['Joe Bar <joe@bar.com>'],
1396
revprops={u'bugs': 'test://bug/id fixed\n'
1397
'test://bug/2 fixed'})
1400
def test_bug_broken(self):
1401
tree = self.make_branch_and_tree(u'.')
1402
self.build_tree(['a', 'b'])
1404
self.wt_commit(tree, 'simple log message', rev_id=b'a1',
1405
revprops={u'bugs': 'test://bua g/id fixed'})
1407
logfile = self.make_utf8_encoded_stringio()
1408
formatter = log.LongLogFormatter(to_file=logfile)
1409
log.show_log(tree.branch, formatter)
1411
self.assertContainsRe(
1413
b'brz: ERROR: breezy.bugtracker.InvalidLineInBugsProperty: '
1414
b'Invalid line in bugs property: \'test://bua g/id fixed\'')
1416
text = logfile.getvalue()
1417
self.assertEqualDiff(
1418
text[text.index(b'-' * 60):],
1420
------------------------------------------------------------
1422
committer: Joe Foo <joe@foo.com>
1424
timestamp: Tue 2005-11-22 00:00:00 +0000
1429
def test_long_bugs(self):
1430
tree = self.make_commits_with_bugs()
1431
self.assertFormatterResult(b"""\
1432
------------------------------------------------------------
1434
fixes bugs: test://bug/id test://bug/2
1435
author: Joe Bar <joe@bar.com>
1436
committer: Joe Foo <joe@foo.com>
1438
timestamp: Tue 2005-11-22 00:00:01 +0000
1443
------------------------------------------------------------
1445
fixes bug: test://bug/id
1446
committer: Joe Foo <joe@foo.com>
1448
timestamp: Tue 2005-11-22 00:00:00 +0000
1452
tree.branch, log.LongLogFormatter)
1454
def test_short_bugs(self):
1455
tree = self.make_commits_with_bugs()
1456
self.assertFormatterResult(b"""\
1457
2 Joe Bar\t2005-11-22
1458
fixes bugs: test://bug/id test://bug/2
1463
1 Joe Foo\t2005-11-22
1464
fixes bug: test://bug/id
1468
tree.branch, log.ShortLogFormatter)
1470
def test_wrong_bugs_property(self):
1471
tree = self.make_branch_and_tree(u'.')
1472
self.build_tree(['foo'])
1473
self.wt_commit(tree, 'simple log message', rev_id=b'a1',
1474
revprops={u'bugs': 'test://bug/id invalid_value'})
1476
logfile = self.make_utf8_encoded_stringio()
1477
formatter = log.ShortLogFormatter(to_file=logfile)
1478
log.show_log(tree.branch, formatter)
1480
lines = logfile.getvalue().splitlines()
1483
lines[0], b' 1 Joe Foo\t2005-11-22')
1487
b'brz: ERROR: breezy.bugtracker.InvalidBugStatus: Invalid '
1488
b'bug status: \'invalid_value\'')
1490
self.assertEqual(lines[-2], b" simple log message")
1492
def test_bugs_handler_present(self):
1493
self.properties_handler_registry.get('bugs_properties_handler')
1496
class TestLogForAuthors(TestCaseForLogFormatter):
1499
super(TestLogForAuthors, self).setUp()
1500
self.wt = self.make_standard_commit('nicky',
1501
authors=['John Doe <jdoe@example.com>',
1502
'Jane Rey <jrey@example.com>'])
1504
def assertFormatterResult(self, formatter, who, result):
1505
formatter_kwargs = dict()
1507
author_list_handler = log.author_list_registry.get(who)
1508
formatter_kwargs['author_list_handler'] = author_list_handler
1509
TestCaseForLogFormatter.assertFormatterResult(self, result,
1510
self.wt.branch, formatter, formatter_kwargs=formatter_kwargs)
1512
def test_line_default(self):
1513
self.assertFormatterResult(log.LineLogFormatter, None, b"""\
1514
1: John Doe 2005-11-22 add a
1517
def test_line_committer(self):
1518
self.assertFormatterResult(log.LineLogFormatter, 'committer', b"""\
1519
1: Lorem Ipsum 2005-11-22 add a
1522
def test_line_first(self):
1523
self.assertFormatterResult(log.LineLogFormatter, 'first', b"""\
1524
1: John Doe 2005-11-22 add a
1527
def test_line_all(self):
1528
self.assertFormatterResult(log.LineLogFormatter, 'all', b"""\
1529
1: John Doe, Jane Rey 2005-11-22 add a
1532
def test_short_default(self):
1533
self.assertFormatterResult(log.ShortLogFormatter, None, b"""\
1534
1 John Doe\t2005-11-22
1539
def test_short_committer(self):
1540
self.assertFormatterResult(log.ShortLogFormatter, 'committer', b"""\
1541
1 Lorem Ipsum\t2005-11-22
1546
def test_short_first(self):
1547
self.assertFormatterResult(log.ShortLogFormatter, 'first', b"""\
1548
1 John Doe\t2005-11-22
1553
def test_short_all(self):
1554
self.assertFormatterResult(log.ShortLogFormatter, 'all', b"""\
1555
1 John Doe, Jane Rey\t2005-11-22
1560
def test_long_default(self):
1561
self.assertFormatterResult(log.LongLogFormatter, None, b"""\
1562
------------------------------------------------------------
1564
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
1565
committer: Lorem Ipsum <test@example.com>
1567
timestamp: Tue 2005-11-22 00:00:00 +0000
1572
def test_long_committer(self):
1573
self.assertFormatterResult(log.LongLogFormatter, 'committer', b"""\
1574
------------------------------------------------------------
1576
committer: Lorem Ipsum <test@example.com>
1578
timestamp: Tue 2005-11-22 00:00:00 +0000
1583
def test_long_first(self):
1584
self.assertFormatterResult(log.LongLogFormatter, 'first', b"""\
1585
------------------------------------------------------------
1587
author: John Doe <jdoe@example.com>
1588
committer: Lorem Ipsum <test@example.com>
1590
timestamp: Tue 2005-11-22 00:00:00 +0000
1595
def test_long_all(self):
1596
self.assertFormatterResult(log.LongLogFormatter, 'all', b"""\
1597
------------------------------------------------------------
1599
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
1600
committer: Lorem Ipsum <test@example.com>
1602
timestamp: Tue 2005-11-22 00:00:00 +0000
1607
def test_gnu_changelog_default(self):
1608
self.assertFormatterResult(log.GnuChangelogLogFormatter, None, b"""\
1609
2005-11-22 John Doe <jdoe@example.com>
1615
def test_gnu_changelog_committer(self):
1616
self.assertFormatterResult(log.GnuChangelogLogFormatter, 'committer', b"""\
1617
2005-11-22 Lorem Ipsum <test@example.com>
1623
def test_gnu_changelog_first(self):
1624
self.assertFormatterResult(log.GnuChangelogLogFormatter, 'first', b"""\
1625
2005-11-22 John Doe <jdoe@example.com>
1631
def test_gnu_changelog_all(self):
1632
self.assertFormatterResult(log.GnuChangelogLogFormatter, 'all', b"""\
1633
2005-11-22 John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
1640
class TestLogExcludeAncestry(tests.TestCaseWithTransport):
1642
def make_branch_with_alternate_ancestries(self, relpath='.'):
1643
# See test_merge_sorted_exclude_ancestry below for the difference with
1644
# bt.per_branch.test_iter_merge_sorted_revision.
1645
# TestIterMergeSortedRevisionsBushyGraph.
1646
# make_branch_with_alternate_ancestries
1647
# and test_merge_sorted_exclude_ancestry
1648
# See the FIXME in assertLogRevnos too.
1649
builder = branchbuilder.BranchBuilder(self.get_transport(relpath))
1661
builder.start_series()
1662
builder.build_snapshot(None, [
1663
('add', ('', b'TREE_ROOT', 'directory', '')), ],
1665
builder.build_snapshot([b'1'], [], revision_id=b'1.1.1')
1666
builder.build_snapshot([b'1'], [], revision_id=b'2')
1667
builder.build_snapshot([b'1.1.1'], [], revision_id=b'1.2.1')
1668
builder.build_snapshot([b'1.1.1', b'1.2.1'], [], revision_id=b'1.1.2')
1669
builder.build_snapshot([b'2', b'1.1.2'], [], revision_id=b'3')
1670
builder.finish_series()
1671
br = builder.get_branch()
1673
self.addCleanup(br.unlock)
1676
def assertLogRevnos(self, expected_revnos, b, start, end,
1677
exclude_common_ancestry, generate_merge_revisions=True):
1678
# FIXME: the layering in log makes it hard to test intermediate levels,
1679
# I wish adding filters with their parameters was easier...
1681
iter_revs = log._calc_view_revisions(
1682
b, start, end, direction='reverse',
1683
generate_merge_revisions=generate_merge_revisions,
1684
exclude_common_ancestry=exclude_common_ancestry)
1685
self.assertEqual(expected_revnos,
1686
[revid for revid, revno, depth in iter_revs])
1688
def test_merge_sorted_exclude_ancestry(self):
1689
b = self.make_branch_with_alternate_ancestries()
1690
self.assertLogRevnos([b'3', b'1.1.2', b'1.2.1', b'1.1.1', b'2', b'1'],
1691
b, b'1', b'3', exclude_common_ancestry=False)
1692
# '2' is part of the '3' ancestry but not part of '1.1.1' ancestry so
1693
# it should be mentioned even if merge_sort order will make it appear
1695
self.assertLogRevnos([b'3', b'1.1.2', b'1.2.1', b'2'],
1696
b, b'1.1.1', b'3', exclude_common_ancestry=True)
1698
def test_merge_sorted_simple_revnos_exclude_ancestry(self):
1699
b = self.make_branch_with_alternate_ancestries()
1700
self.assertLogRevnos([b'3', b'2'],
1701
b, b'1', b'3', exclude_common_ancestry=True,
1702
generate_merge_revisions=False)
1703
self.assertLogRevnos([b'3', b'1.1.2', b'1.2.1', b'1.1.1', b'2'],
1704
b, b'1', b'3', exclude_common_ancestry=True,
1705
generate_merge_revisions=True)
1708
class TestLogDefaults(TestCaseForLogFormatter):
1709
def test_default_log_level(self):
1711
Test to ensure that specifying 'levels=1' to make_log_request_dict
1712
doesn't get overwritten when using a LogFormatter that supports more
1716
wt = self._prepare_tree_with_merges()
1719
class CustomLogFormatter(log.LogFormatter):
1720
def __init__(self, *args, **kwargs):
1721
super(CustomLogFormatter, self).__init__(*args, **kwargs)
1724
def get_levels(self):
1725
# log formatter supports all levels:
1728
def log_revision(self, revision):
1729
self.revisions.append(revision)
1731
log_formatter = LogCatcher()
1732
# First request we don't specify number of levels, we should get a
1733
# sensible default (whatever the LogFormatter handles - which in this
1734
# case is 0/everything):
1735
request = log.make_log_request_dict(limit=10)
1736
log.Logger(b, request).show(log_formatter)
1737
# should have all three revisions:
1738
self.assertEqual(len(log_formatter.revisions), 3)
1741
log_formatter = LogCatcher()
1742
# now explicitly request mainline revisions only:
1743
request = log.make_log_request_dict(limit=10, levels=1)
1744
log.Logger(b, request).show(log_formatter)
1745
# should now only have 2 revisions:
1746
self.assertEqual(len(log_formatter.revisions), 2)