1
# Copyright (C) 2005 by Canonical Ltd
1
# Copyright (C) 2005-2011 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
# GNU General Public License for more details.
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
18
from cStringIO import StringIO
20
from bzrlib.selftest import BzrTestBase, TestCaseInTempDir
21
from bzrlib.log import LogFormatter, show_log, LongLogFormatter
22
from bzrlib.branch import Branch
23
from bzrlib.errors import InvalidRevisionNumber
25
class _LogEntry(object):
26
# should probably move into bzrlib.log?
30
class LogCatcher(LogFormatter):
31
"""Pull log messages into list rather than displaying them.
33
For ease of testing we save log messages here rather than actually
34
formatting them, so that we can precisely check the result without
35
being too dependent on the exact formatting.
37
We should also test the LogFormatter.
31
class TestLogMixin(object):
33
def wt_commit(self, wt, message, **kwargs):
34
"""Use some mostly fixed values for commits to simplify tests.
36
Tests can use this function to get some commit attributes. The time
37
stamp is incremented at each commit.
39
if getattr(self, 'timestamp', None) is None:
40
self.timestamp = 1132617600 # Mon 2005-11-22 00:00:00 +0000
42
self.timestamp += 1 # 1 second between each commit
43
kwargs.setdefault('timestamp', self.timestamp)
44
kwargs.setdefault('timezone', 0) # UTC
45
kwargs.setdefault('committer', 'Joe Foo <joe@foo.com>')
47
return wt.commit(message, **kwargs)
50
class TestCaseForLogFormatter(tests.TestCaseWithTransport, TestLogMixin):
53
super(TestCaseForLogFormatter, self).setUp()
54
# keep a reference to the "current" custom prop. handler registry
55
self.properties_handler_registry = log.properties_handler_registry
56
# Use a clean registry for log
57
log.properties_handler_registry = registry.Registry()
60
log.properties_handler_registry = self.properties_handler_registry
61
self.addCleanup(restore)
63
def assertFormatterResult(self, result, branch, formatter_class,
64
formatter_kwargs=None, show_log_kwargs=None):
65
logfile = self.make_utf8_encoded_stringio()
66
if formatter_kwargs is None:
68
formatter = formatter_class(to_file=logfile, **formatter_kwargs)
69
if show_log_kwargs is None:
71
log.show_log(branch, formatter, **show_log_kwargs)
72
self.assertEqualDiff(result, logfile.getvalue())
74
def make_standard_commit(self, branch_nick, **kwargs):
75
wt = self.make_branch_and_tree('.')
77
self.addCleanup(wt.unlock)
78
self.build_tree(['a'])
80
wt.branch.nick = branch_nick
81
kwargs.setdefault('committer', 'Lorem Ipsum <test@example.com>')
82
kwargs.setdefault('authors', ['John Doe <jdoe@example.com>'])
83
self.wt_commit(wt, 'add a', **kwargs)
86
def make_commits_with_trailing_newlines(self, wt):
87
"""Helper method for LogFormatter tests"""
90
self.build_tree_contents([('a', 'hello moto\n')])
91
self.wt_commit(wt, 'simple log message', rev_id='a1')
92
self.build_tree_contents([('b', 'goodbye\n')])
94
self.wt_commit(wt, 'multiline\nlog\nmessage\n', rev_id='a2')
96
self.build_tree_contents([('c', 'just another manic monday\n')])
98
self.wt_commit(wt, 'single line with trailing newline\n', rev_id='a3')
101
def _prepare_tree_with_merges(self, with_tags=False):
102
wt = self.make_branch_and_memory_tree('.')
104
self.addCleanup(wt.unlock)
106
self.wt_commit(wt, 'rev-1', rev_id='rev-1')
107
self.wt_commit(wt, 'rev-merged', rev_id='rev-2a')
108
wt.set_parent_ids(['rev-1', 'rev-2a'])
109
wt.branch.set_last_revision_info(1, 'rev-1')
110
self.wt_commit(wt, 'rev-2', rev_id='rev-2b')
113
branch.tags.set_tag('v0.2', 'rev-2b')
114
self.wt_commit(wt, 'rev-3', rev_id='rev-3')
115
branch.tags.set_tag('v1.0rc1', 'rev-3')
116
branch.tags.set_tag('v1.0', 'rev-3')
120
class LogCatcher(log.LogFormatter):
121
"""Pull log messages into a list rather than displaying them.
123
To simplify testing we save logged revisions here rather than actually
124
formatting anything, so that we can precisely check the result without
125
being dependent on the formatting.
40
super(LogCatcher, self).__init__(to_file=None)
44
def show(self, revno, rev, delta):
52
class SimpleLogTest(TestCaseInTempDir):
128
supports_merge_revisions = True
129
supports_delta = True
133
def __init__(self, *args, **kwargs):
134
kwargs.update(dict(to_file=None))
135
super(LogCatcher, self).__init__(*args, **kwargs)
138
def log_revision(self, revision):
139
self.revisions.append(revision)
142
class TestShowLog(tests.TestCaseWithTransport):
54
144
def checkDelta(self, delta, **kw):
55
"""Check the filenames touched by a delta are as expected."""
145
"""Check the filenames touched by a delta are as expected.
147
Caller only have to pass in the list of files for each part, all
148
unspecified parts are considered empty (and checked as such).
56
150
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
151
# By default we expect an empty list
57
152
expected = kw.get(n, [])
59
# tests are written with unix paths; fix them up for windows
61
expected = [x.replace('/', os.sep) for x in expected]
63
153
# strip out only the path components
64
154
got = [x[0] for x in getattr(delta, n)]
65
self.assertEquals(expected, got)
67
def test_cur_revno(self):
68
b = Branch('.', init=True)
71
b.commit('empty commit')
72
show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
73
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
74
start_revision=2, end_revision=1)
75
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
76
start_revision=1, end_revision=2)
77
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
78
start_revision=0, end_revision=2)
79
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
80
start_revision=1, end_revision=0)
81
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
82
start_revision=-1, end_revision=1)
83
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
84
start_revision=1, end_revision=-1)
86
def test_cur_revno(self):
87
b = Branch.initialize('.')
90
b.commit('empty commit')
91
show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
92
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
93
start_revision=2, end_revision=1)
94
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
95
start_revision=1, end_revision=2)
96
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
97
start_revision=0, end_revision=2)
98
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
99
start_revision=1, end_revision=0)
100
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
101
start_revision=-1, end_revision=1)
102
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
103
start_revision=1, end_revision=-1)
105
def test_simple_log(self):
106
eq = self.assertEquals
108
b = Branch.initialize('.')
155
self.assertEqual(expected, got)
157
def assertInvalidRevisonNumber(self, br, start, end):
159
self.assertRaises(errors.InvalidRevisionNumber,
160
log.show_log, br, lf,
161
start_revision=start, end_revision=end)
163
def test_cur_revno(self):
164
wt = self.make_branch_and_tree('.')
168
wt.commit('empty commit')
169
log.show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
171
# Since there is a single revision in the branch all the combinations
173
self.assertInvalidRevisonNumber(b, 2, 1)
174
self.assertInvalidRevisonNumber(b, 1, 2)
175
self.assertInvalidRevisonNumber(b, 0, 2)
176
self.assertInvalidRevisonNumber(b, 1, 0)
177
self.assertInvalidRevisonNumber(b, -1, 1)
178
self.assertInvalidRevisonNumber(b, 1, -1)
180
def test_empty_branch(self):
181
wt = self.make_branch_and_tree('.')
184
log.show_log(wt.branch, lf)
116
b.commit('empty commit')
186
self.assertEqual([], lf.revisions)
188
def test_empty_commit(self):
189
wt = self.make_branch_and_tree('.')
191
wt.commit('empty commit')
117
192
lf = LogCatcher()
118
show_log(b, lf, verbose=True)
120
eq(lf.logs[0].revno, 1)
121
eq(lf.logs[0].rev.message, 'empty commit')
123
self.log('log delta: %r' % d)
193
log.show_log(wt.branch, lf, verbose=True)
195
self.assertEqual(1, len(revs))
196
self.assertEqual('1', revs[0].revno)
197
self.assertEqual('empty commit', revs[0].rev.message)
198
self.checkDelta(revs[0].delta)
200
def test_simple_commit(self):
201
wt = self.make_branch_and_tree('.')
202
wt.commit('empty commit')
127
203
self.build_tree(['hello'])
129
b.commit('add one file')
132
# log using regular thing
133
show_log(b, LongLogFormatter(lf))
135
for l in lf.readlines():
138
# get log as data structure
205
wt.commit('add one file',
206
committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
207
u'<test@example.com>')
139
208
lf = LogCatcher()
140
show_log(b, lf, verbose=True)
142
self.log('log entries:')
143
for logentry in lf.logs:
144
self.log('%4d %s' % (logentry.revno, logentry.rev.message))
209
log.show_log(wt.branch, lf, verbose=True)
210
self.assertEqual(2, len(lf.revisions))
146
211
# first one is most recent
147
logentry = lf.logs[0]
148
eq(logentry.revno, 2)
149
eq(logentry.rev.message, 'add one file')
151
self.log('log 2 delta: %r' % d)
152
# self.checkDelta(d, added=['hello'])
154
# commit a log message with control characters
155
msg = "All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
158
show_log(b, lf, verbose=True)
159
committed_msg = lf.logs[0].rev.message
160
self.log("escaped commit message: %r", committed_msg)
161
self.assert_(msg != committed_msg)
162
self.assert_(len(committed_msg) > len(msg))
212
log_entry = lf.revisions[0]
213
self.assertEqual('2', log_entry.revno)
214
self.assertEqual('add one file', log_entry.rev.message)
215
self.checkDelta(log_entry.delta, added=['hello'])
217
def test_commit_message_with_control_chars(self):
218
wt = self.make_branch_and_tree('.')
219
msg = u"All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
220
msg = msg.replace(u'\r', u'\n')
223
log.show_log(wt.branch, lf, verbose=True)
224
committed_msg = lf.revisions[0].rev.message
225
if wt.branch.repository._serializer.squashes_xml_invalid_characters:
226
self.assertNotEqual(msg, committed_msg)
227
self.assertTrue(len(committed_msg) > len(msg))
229
self.assertEqual(msg, committed_msg)
231
def test_commit_message_without_control_chars(self):
232
wt = self.make_branch_and_tree('.')
233
# escaped. As ElementTree apparently does some kind of
234
# newline conversion, neither LF (\x0A) nor CR (\x0D) are
235
# included in the test commit message, even though they are
236
# valid XML 1.0 characters.
237
msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
240
log.show_log(wt.branch, lf, verbose=True)
241
committed_msg = lf.revisions[0].rev.message
242
self.assertEqual(msg, committed_msg)
244
def test_deltas_in_merge_revisions(self):
245
"""Check deltas created for both mainline and merge revisions"""
246
wt = self.make_branch_and_tree('parent')
247
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
250
wt.commit(message='add file1 and file2')
251
self.run_bzr('branch parent child')
252
os.unlink('child/file1')
253
with file('child/file2', 'wb') as f: f.write('hello\n')
254
self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
257
self.run_bzr('merge ../child')
258
wt.commit('merge child branch')
262
lf.supports_merge_revisions = True
263
log.show_log(b, lf, verbose=True)
266
self.assertEqual(3, len(revs))
269
self.assertEqual('2', logentry.revno)
270
self.assertEqual('merge child branch', logentry.rev.message)
271
self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
274
self.assertEqual('1.1.1', logentry.revno)
275
self.assertEqual('remove file1 and modify file2', logentry.rev.message)
276
self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
279
self.assertEqual('1', logentry.revno)
280
self.assertEqual('add file1 and file2', logentry.rev.message)
281
self.checkDelta(logentry.delta, added=['file1', 'file2'])
284
class TestShortLogFormatter(TestCaseForLogFormatter):
286
def test_trailing_newlines(self):
287
wt = self.make_branch_and_tree('.')
288
b = self.make_commits_with_trailing_newlines(wt)
289
self.assertFormatterResult("""\
290
3 Joe Foo\t2005-11-22
291
single line with trailing newline
293
2 Joe Foo\t2005-11-22
298
1 Joe Foo\t2005-11-22
302
b, log.ShortLogFormatter)
304
def test_short_log_with_merges(self):
305
wt = self._prepare_tree_with_merges()
306
self.assertFormatterResult("""\
307
2 Joe Foo\t2005-11-22 [merge]
310
1 Joe Foo\t2005-11-22
314
wt.branch, log.ShortLogFormatter)
316
def test_short_log_with_merges_and_advice(self):
317
wt = self._prepare_tree_with_merges()
318
self.assertFormatterResult("""\
319
2 Joe Foo\t2005-11-22 [merge]
322
1 Joe Foo\t2005-11-22
325
Use --include-merged or -n0 to see merged revisions.
327
wt.branch, log.ShortLogFormatter,
328
formatter_kwargs=dict(show_advice=True))
330
def test_short_log_with_merges_and_range(self):
331
wt = self._prepare_tree_with_merges()
332
self.wt_commit(wt, 'rev-3a', rev_id='rev-3a')
333
wt.branch.set_last_revision_info(2, 'rev-2b')
334
wt.set_parent_ids(['rev-2b', 'rev-3a'])
335
self.wt_commit(wt, 'rev-3b', rev_id='rev-3b')
336
self.assertFormatterResult("""\
337
3 Joe Foo\t2005-11-22 [merge]
340
2 Joe Foo\t2005-11-22 [merge]
344
wt.branch, log.ShortLogFormatter,
345
show_log_kwargs=dict(start_revision=2, end_revision=3))
347
def test_short_log_with_tags(self):
348
wt = self._prepare_tree_with_merges(with_tags=True)
349
self.assertFormatterResult("""\
350
3 Joe Foo\t2005-11-22 {v1.0, v1.0rc1}
353
2 Joe Foo\t2005-11-22 {v0.2} [merge]
356
1 Joe Foo\t2005-11-22
360
wt.branch, log.ShortLogFormatter)
362
def test_short_log_single_merge_revision(self):
363
wt = self._prepare_tree_with_merges()
364
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
365
rev = revspec.in_history(wt.branch)
366
self.assertFormatterResult("""\
367
1.1.1 Joe Foo\t2005-11-22
371
wt.branch, log.ShortLogFormatter,
372
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
374
def test_show_ids(self):
375
wt = self.make_branch_and_tree('parent')
376
self.build_tree(['parent/f1', 'parent/f2'])
378
self.wt_commit(wt, 'first post', rev_id='a')
379
child_wt = wt.bzrdir.sprout('child').open_workingtree()
380
self.wt_commit(child_wt, 'branch 1 changes', rev_id='b')
381
wt.merge_from_branch(child_wt.branch)
382
self.wt_commit(wt, 'merge branch 1', rev_id='c')
383
self.assertFormatterResult("""\
384
2 Joe Foo\t2005-11-22 [merge]
388
1.1.1 Joe Foo\t2005-11-22
392
1 Joe Foo\t2005-11-22
397
wt.branch, log.ShortLogFormatter,
398
formatter_kwargs=dict(levels=0,show_ids=True))
401
class TestShortLogFormatterWithMergeRevisions(TestCaseForLogFormatter):
403
def test_short_merge_revs_log_with_merges(self):
404
wt = self._prepare_tree_with_merges()
405
# Note that the 1.1.1 indenting is in fact correct given that
406
# the revision numbers are right justified within 5 characters
407
# for mainline revnos and 9 characters for dotted revnos.
408
self.assertFormatterResult("""\
409
2 Joe Foo\t2005-11-22 [merge]
412
1.1.1 Joe Foo\t2005-11-22
415
1 Joe Foo\t2005-11-22
419
wt.branch, log.ShortLogFormatter,
420
formatter_kwargs=dict(levels=0))
422
def test_short_merge_revs_log_single_merge_revision(self):
423
wt = self._prepare_tree_with_merges()
424
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
425
rev = revspec.in_history(wt.branch)
426
self.assertFormatterResult("""\
427
1.1.1 Joe Foo\t2005-11-22
431
wt.branch, log.ShortLogFormatter,
432
formatter_kwargs=dict(levels=0),
433
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
436
class TestLongLogFormatter(TestCaseForLogFormatter):
438
def test_verbose_log(self):
439
"""Verbose log includes changed files
443
wt = self.make_standard_commit('test_verbose_log', authors=[])
444
self.assertFormatterResult('''\
445
------------------------------------------------------------
447
committer: Lorem Ipsum <test@example.com>
448
branch nick: test_verbose_log
449
timestamp: Tue 2005-11-22 00:00:00 +0000
455
wt.branch, log.LongLogFormatter,
456
show_log_kwargs=dict(verbose=True))
458
def test_merges_are_indented_by_level(self):
459
wt = self.make_branch_and_tree('parent')
460
self.wt_commit(wt, 'first post')
461
child_wt = wt.bzrdir.sprout('child').open_workingtree()
462
self.wt_commit(child_wt, 'branch 1')
463
smallerchild_wt = wt.bzrdir.sprout('smallerchild').open_workingtree()
464
self.wt_commit(smallerchild_wt, 'branch 2')
465
child_wt.merge_from_branch(smallerchild_wt.branch)
466
self.wt_commit(child_wt, 'merge branch 2')
467
wt.merge_from_branch(child_wt.branch)
468
self.wt_commit(wt, 'merge branch 1')
469
self.assertFormatterResult("""\
470
------------------------------------------------------------
472
committer: Joe Foo <joe@foo.com>
474
timestamp: Tue 2005-11-22 00:00:04 +0000
477
------------------------------------------------------------
479
committer: Joe Foo <joe@foo.com>
481
timestamp: Tue 2005-11-22 00:00:03 +0000
484
------------------------------------------------------------
486
committer: Joe Foo <joe@foo.com>
487
branch nick: smallerchild
488
timestamp: Tue 2005-11-22 00:00:02 +0000
491
------------------------------------------------------------
493
committer: Joe Foo <joe@foo.com>
495
timestamp: Tue 2005-11-22 00:00:01 +0000
498
------------------------------------------------------------
500
committer: Joe Foo <joe@foo.com>
502
timestamp: Tue 2005-11-22 00:00:00 +0000
506
wt.branch, log.LongLogFormatter,
507
formatter_kwargs=dict(levels=0),
508
show_log_kwargs=dict(verbose=True))
510
def test_verbose_merge_revisions_contain_deltas(self):
511
wt = self.make_branch_and_tree('parent')
512
self.build_tree(['parent/f1', 'parent/f2'])
514
self.wt_commit(wt, 'first post')
515
child_wt = wt.bzrdir.sprout('child').open_workingtree()
516
os.unlink('child/f1')
517
self.build_tree_contents([('child/f2', 'hello\n')])
518
self.wt_commit(child_wt, 'removed f1 and modified f2')
519
wt.merge_from_branch(child_wt.branch)
520
self.wt_commit(wt, 'merge branch 1')
521
self.assertFormatterResult("""\
522
------------------------------------------------------------
524
committer: Joe Foo <joe@foo.com>
526
timestamp: Tue 2005-11-22 00:00:02 +0000
533
------------------------------------------------------------
535
committer: Joe Foo <joe@foo.com>
537
timestamp: Tue 2005-11-22 00:00:01 +0000
539
removed f1 and modified f2
544
------------------------------------------------------------
546
committer: Joe Foo <joe@foo.com>
548
timestamp: Tue 2005-11-22 00:00:00 +0000
555
wt.branch, log.LongLogFormatter,
556
formatter_kwargs=dict(levels=0),
557
show_log_kwargs=dict(verbose=True))
559
def test_trailing_newlines(self):
560
wt = self.make_branch_and_tree('.')
561
b = self.make_commits_with_trailing_newlines(wt)
562
self.assertFormatterResult("""\
563
------------------------------------------------------------
565
committer: Joe Foo <joe@foo.com>
567
timestamp: Tue 2005-11-22 00:00:02 +0000
569
single line with trailing newline
570
------------------------------------------------------------
572
committer: Joe Foo <joe@foo.com>
574
timestamp: Tue 2005-11-22 00:00:01 +0000
579
------------------------------------------------------------
581
committer: Joe Foo <joe@foo.com>
583
timestamp: Tue 2005-11-22 00:00:00 +0000
587
b, log.LongLogFormatter)
589
def test_author_in_log(self):
590
"""Log includes the author name if it's set in
591
the revision properties
593
wt = self.make_standard_commit('test_author_log',
594
authors=['John Doe <jdoe@example.com>',
595
'Jane Rey <jrey@example.com>'])
596
self.assertFormatterResult("""\
597
------------------------------------------------------------
599
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
600
committer: Lorem Ipsum <test@example.com>
601
branch nick: test_author_log
602
timestamp: Tue 2005-11-22 00:00:00 +0000
606
wt.branch, log.LongLogFormatter)
608
def test_properties_in_log(self):
609
"""Log includes the custom properties returned by the registered
612
wt = self.make_standard_commit('test_properties_in_log')
613
def trivial_custom_prop_handler(revision):
614
return {'test_prop':'test_value'}
616
# Cleaned up in setUp()
617
log.properties_handler_registry.register(
618
'trivial_custom_prop_handler',
619
trivial_custom_prop_handler)
620
self.assertFormatterResult("""\
621
------------------------------------------------------------
623
test_prop: test_value
624
author: John Doe <jdoe@example.com>
625
committer: Lorem Ipsum <test@example.com>
626
branch nick: test_properties_in_log
627
timestamp: Tue 2005-11-22 00:00:00 +0000
631
wt.branch, log.LongLogFormatter)
633
def test_properties_in_short_log(self):
634
"""Log includes the custom properties returned by the registered
637
wt = self.make_standard_commit('test_properties_in_short_log')
638
def trivial_custom_prop_handler(revision):
639
return {'test_prop':'test_value'}
641
log.properties_handler_registry.register(
642
'trivial_custom_prop_handler',
643
trivial_custom_prop_handler)
644
self.assertFormatterResult("""\
645
1 John Doe\t2005-11-22
646
test_prop: test_value
650
wt.branch, log.ShortLogFormatter)
652
def test_error_in_properties_handler(self):
653
"""Log includes the custom properties returned by the registered
656
wt = self.make_standard_commit('error_in_properties_handler',
657
revprops={'first_prop':'first_value'})
658
sio = self.make_utf8_encoded_stringio()
659
formatter = log.LongLogFormatter(to_file=sio)
660
def trivial_custom_prop_handler(revision):
661
raise StandardError("a test error")
663
log.properties_handler_registry.register(
664
'trivial_custom_prop_handler',
665
trivial_custom_prop_handler)
666
self.assertRaises(StandardError, log.show_log, wt.branch, formatter,)
668
def test_properties_handler_bad_argument(self):
669
wt = self.make_standard_commit('bad_argument',
670
revprops={'a_prop':'test_value'})
671
sio = self.make_utf8_encoded_stringio()
672
formatter = log.LongLogFormatter(to_file=sio)
673
def bad_argument_prop_handler(revision):
674
return {'custom_prop_name':revision.properties['a_prop']}
676
log.properties_handler_registry.register(
677
'bad_argument_prop_handler',
678
bad_argument_prop_handler)
680
self.assertRaises(AttributeError, formatter.show_properties,
683
revision = wt.branch.repository.get_revision(wt.branch.last_revision())
684
formatter.show_properties(revision, '')
685
self.assertEqualDiff('''custom_prop_name: test_value\n''',
688
def test_show_ids(self):
689
wt = self.make_branch_and_tree('parent')
690
self.build_tree(['parent/f1', 'parent/f2'])
692
self.wt_commit(wt, 'first post', rev_id='a')
693
child_wt = wt.bzrdir.sprout('child').open_workingtree()
694
self.wt_commit(child_wt, 'branch 1 changes', rev_id='b')
695
wt.merge_from_branch(child_wt.branch)
696
self.wt_commit(wt, 'merge branch 1', rev_id='c')
697
self.assertFormatterResult("""\
698
------------------------------------------------------------
703
committer: Joe Foo <joe@foo.com>
705
timestamp: Tue 2005-11-22 00:00:02 +0000
708
------------------------------------------------------------
712
committer: Joe Foo <joe@foo.com>
714
timestamp: Tue 2005-11-22 00:00:01 +0000
717
------------------------------------------------------------
720
committer: Joe Foo <joe@foo.com>
722
timestamp: Tue 2005-11-22 00:00:00 +0000
726
wt.branch, log.LongLogFormatter,
727
formatter_kwargs=dict(levels=0,show_ids=True))
730
class TestLongLogFormatterWithoutMergeRevisions(TestCaseForLogFormatter):
732
def test_long_verbose_log(self):
733
"""Verbose log includes changed files
737
wt = self.make_standard_commit('test_long_verbose_log', authors=[])
738
self.assertFormatterResult("""\
739
------------------------------------------------------------
741
committer: Lorem Ipsum <test@example.com>
742
branch nick: test_long_verbose_log
743
timestamp: Tue 2005-11-22 00:00:00 +0000
749
wt.branch, log.LongLogFormatter,
750
formatter_kwargs=dict(levels=1),
751
show_log_kwargs=dict(verbose=True))
753
def test_long_verbose_contain_deltas(self):
754
wt = self.make_branch_and_tree('parent')
755
self.build_tree(['parent/f1', 'parent/f2'])
757
self.wt_commit(wt, 'first post')
758
child_wt = wt.bzrdir.sprout('child').open_workingtree()
759
os.unlink('child/f1')
760
self.build_tree_contents([('child/f2', 'hello\n')])
761
self.wt_commit(child_wt, 'removed f1 and modified f2')
762
wt.merge_from_branch(child_wt.branch)
763
self.wt_commit(wt, 'merge branch 1')
764
self.assertFormatterResult("""\
765
------------------------------------------------------------
767
committer: Joe Foo <joe@foo.com>
769
timestamp: Tue 2005-11-22 00:00:02 +0000
776
------------------------------------------------------------
778
committer: Joe Foo <joe@foo.com>
780
timestamp: Tue 2005-11-22 00:00:00 +0000
787
wt.branch, log.LongLogFormatter,
788
formatter_kwargs=dict(levels=1),
789
show_log_kwargs=dict(verbose=True))
791
def test_long_trailing_newlines(self):
792
wt = self.make_branch_and_tree('.')
793
b = self.make_commits_with_trailing_newlines(wt)
794
self.assertFormatterResult("""\
795
------------------------------------------------------------
797
committer: Joe Foo <joe@foo.com>
799
timestamp: Tue 2005-11-22 00:00:02 +0000
801
single line with trailing newline
802
------------------------------------------------------------
804
committer: Joe Foo <joe@foo.com>
806
timestamp: Tue 2005-11-22 00:00:01 +0000
811
------------------------------------------------------------
813
committer: Joe Foo <joe@foo.com>
815
timestamp: Tue 2005-11-22 00:00:00 +0000
819
b, log.LongLogFormatter,
820
formatter_kwargs=dict(levels=1))
822
def test_long_author_in_log(self):
823
"""Log includes the author name if it's set in
824
the revision properties
826
wt = self.make_standard_commit('test_author_log')
827
self.assertFormatterResult("""\
828
------------------------------------------------------------
830
author: John Doe <jdoe@example.com>
831
committer: Lorem Ipsum <test@example.com>
832
branch nick: test_author_log
833
timestamp: Tue 2005-11-22 00:00:00 +0000
837
wt.branch, log.LongLogFormatter,
838
formatter_kwargs=dict(levels=1))
840
def test_long_properties_in_log(self):
841
"""Log includes the custom properties returned by the registered
844
wt = self.make_standard_commit('test_properties_in_log')
845
def trivial_custom_prop_handler(revision):
846
return {'test_prop':'test_value'}
848
log.properties_handler_registry.register(
849
'trivial_custom_prop_handler',
850
trivial_custom_prop_handler)
851
self.assertFormatterResult("""\
852
------------------------------------------------------------
854
test_prop: test_value
855
author: John Doe <jdoe@example.com>
856
committer: Lorem Ipsum <test@example.com>
857
branch nick: test_properties_in_log
858
timestamp: Tue 2005-11-22 00:00:00 +0000
862
wt.branch, log.LongLogFormatter,
863
formatter_kwargs=dict(levels=1))
866
class TestLineLogFormatter(TestCaseForLogFormatter):
868
def test_line_log(self):
869
"""Line log should show revno
873
wt = self.make_standard_commit('test-line-log',
874
committer='Line-Log-Formatter Tester <test@line.log>',
876
self.assertFormatterResult("""\
877
1: Line-Log-Formatte... 2005-11-22 add a
879
wt.branch, log.LineLogFormatter)
881
def test_trailing_newlines(self):
882
wt = self.make_branch_and_tree('.')
883
b = self.make_commits_with_trailing_newlines(wt)
884
self.assertFormatterResult("""\
885
3: Joe Foo 2005-11-22 single line with trailing newline
886
2: Joe Foo 2005-11-22 multiline
887
1: Joe Foo 2005-11-22 simple log message
889
b, log.LineLogFormatter)
891
def test_line_log_single_merge_revision(self):
892
wt = self._prepare_tree_with_merges()
893
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
894
rev = revspec.in_history(wt.branch)
895
self.assertFormatterResult("""\
896
1.1.1: Joe Foo 2005-11-22 rev-merged
898
wt.branch, log.LineLogFormatter,
899
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
901
def test_line_log_with_tags(self):
902
wt = self._prepare_tree_with_merges(with_tags=True)
903
self.assertFormatterResult("""\
904
3: Joe Foo 2005-11-22 {v1.0, v1.0rc1} rev-3
905
2: Joe Foo 2005-11-22 [merge] {v0.2} rev-2
906
1: Joe Foo 2005-11-22 rev-1
908
wt.branch, log.LineLogFormatter)
911
class TestLineLogFormatterWithMergeRevisions(TestCaseForLogFormatter):
913
def test_line_merge_revs_log(self):
914
"""Line log should show revno
918
wt = self.make_standard_commit('test-line-log',
919
committer='Line-Log-Formatter Tester <test@line.log>',
921
self.assertFormatterResult("""\
922
1: Line-Log-Formatte... 2005-11-22 add a
924
wt.branch, log.LineLogFormatter)
926
def test_line_merge_revs_log_single_merge_revision(self):
927
wt = self._prepare_tree_with_merges()
928
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
929
rev = revspec.in_history(wt.branch)
930
self.assertFormatterResult("""\
931
1.1.1: Joe Foo 2005-11-22 rev-merged
933
wt.branch, log.LineLogFormatter,
934
formatter_kwargs=dict(levels=0),
935
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
937
def test_line_merge_revs_log_with_merges(self):
938
wt = self._prepare_tree_with_merges()
939
self.assertFormatterResult("""\
940
2: Joe Foo 2005-11-22 [merge] rev-2
941
1.1.1: Joe Foo 2005-11-22 rev-merged
942
1: Joe Foo 2005-11-22 rev-1
944
wt.branch, log.LineLogFormatter,
945
formatter_kwargs=dict(levels=0))
948
class TestGnuChangelogFormatter(TestCaseForLogFormatter):
950
def test_gnu_changelog(self):
951
wt = self.make_standard_commit('nicky', authors=[])
952
self.assertFormatterResult('''\
953
2005-11-22 Lorem Ipsum <test@example.com>
958
wt.branch, log.GnuChangelogLogFormatter)
960
def test_with_authors(self):
961
wt = self.make_standard_commit('nicky',
962
authors=['Fooa Fooz <foo@example.com>',
963
'Bari Baro <bar@example.com>'])
964
self.assertFormatterResult('''\
965
2005-11-22 Fooa Fooz <foo@example.com>
970
wt.branch, log.GnuChangelogLogFormatter)
972
def test_verbose(self):
973
wt = self.make_standard_commit('nicky')
974
self.assertFormatterResult('''\
975
2005-11-22 John Doe <jdoe@example.com>
982
wt.branch, log.GnuChangelogLogFormatter,
983
show_log_kwargs=dict(verbose=True))
986
class TestShowChangedRevisions(tests.TestCaseWithTransport):
988
def test_show_changed_revisions_verbose(self):
989
tree = self.make_branch_and_tree('tree_a')
990
self.build_tree(['tree_a/foo'])
992
tree.commit('bar', rev_id='bar-id')
993
s = self.make_utf8_encoded_stringio()
994
log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
995
self.assertContainsRe(s.getvalue(), 'bar')
996
self.assertNotContainsRe(s.getvalue(), 'foo')
999
class TestLogFormatter(tests.TestCase):
1002
super(TestLogFormatter, self).setUp()
1003
self.rev = revision.Revision('a-id')
1004
self.lf = log.LogFormatter(None)
1006
def test_short_committer(self):
1007
def assertCommitter(expected, committer):
1008
self.rev.committer = committer
1009
self.assertEqual(expected, self.lf.short_committer(self.rev))
1011
assertCommitter('John Doe', 'John Doe <jdoe@example.com>')
1012
assertCommitter('John Smith', 'John Smith <jsmith@example.com>')
1013
assertCommitter('John Smith', 'John Smith')
1014
assertCommitter('jsmith@example.com', 'jsmith@example.com')
1015
assertCommitter('jsmith@example.com', '<jsmith@example.com>')
1016
assertCommitter('John Smith', 'John Smith jsmith@example.com')
1018
def test_short_author(self):
1019
def assertAuthor(expected, author):
1020
self.rev.properties['author'] = author
1021
self.assertEqual(expected, self.lf.short_author(self.rev))
1023
assertAuthor('John Smith', 'John Smith <jsmith@example.com>')
1024
assertAuthor('John Smith', 'John Smith')
1025
assertAuthor('jsmith@example.com', 'jsmith@example.com')
1026
assertAuthor('jsmith@example.com', '<jsmith@example.com>')
1027
assertAuthor('John Smith', 'John Smith jsmith@example.com')
1029
def test_short_author_from_committer(self):
1030
self.rev.committer = 'John Doe <jdoe@example.com>'
1031
self.assertEqual('John Doe', self.lf.short_author(self.rev))
1033
def test_short_author_from_authors(self):
1034
self.rev.properties['authors'] = ('John Smith <jsmith@example.com>\n'
1035
'Jane Rey <jrey@example.com>')
1036
self.assertEqual('John Smith', self.lf.short_author(self.rev))
1039
class TestReverseByDepth(tests.TestCase):
1040
"""Test reverse_by_depth behavior.
1042
This is used to present revisions in forward (oldest first) order in a nice
1045
The tests use lighter revision description to ease reading.
1048
def assertReversed(self, forward, backward):
1049
# Transform the descriptions to suit the API: tests use (revno, depth),
1050
# while the API expects (revid, revno, depth)
1051
def complete_revisions(l):
1052
"""Transform the description to suit the API.
1054
Tests use (revno, depth) whil the API expects (revid, revno, depth).
1055
Since the revid is arbitrary, we just duplicate revno
1057
return [ (r, r, d) for r, d in l]
1058
forward = complete_revisions(forward)
1059
backward= complete_revisions(backward)
1060
self.assertEqual(forward, log.reverse_by_depth(backward))
1063
def test_mainline_revisions(self):
1064
self.assertReversed([( '1', 0), ('2', 0)],
1065
[('2', 0), ('1', 0)])
1067
def test_merged_revisions(self):
1068
self.assertReversed([('1', 0), ('2', 0), ('2.2', 1), ('2.1', 1),],
1069
[('2', 0), ('2.1', 1), ('2.2', 1), ('1', 0),])
1070
def test_shifted_merged_revisions(self):
1071
"""Test irregular layout.
1073
Requesting revisions touching a file can produce "holes" in the depths.
1075
self.assertReversed([('1', 0), ('2', 0), ('1.1', 2), ('1.2', 2),],
1076
[('2', 0), ('1.2', 2), ('1.1', 2), ('1', 0),])
1078
def test_merged_without_child_revisions(self):
1079
"""Test irregular layout.
1081
Revision ranges can produce "holes" in the depths.
1083
# When a revision of higher depth doesn't follow one of lower depth, we
1084
# assume a lower depth one is virtually there
1085
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1086
[('4', 4), ('3', 3), ('2', 2), ('1', 2),])
1087
# So we get the same order after reversing below even if the original
1088
# revisions are not in the same order.
1089
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1090
[('3', 3), ('4', 4), ('2', 2), ('1', 2),])
1093
class TestHistoryChange(tests.TestCaseWithTransport):
1095
def setup_a_tree(self):
1096
tree = self.make_branch_and_tree('tree')
1098
self.addCleanup(tree.unlock)
1099
tree.commit('1a', rev_id='1a')
1100
tree.commit('2a', rev_id='2a')
1101
tree.commit('3a', rev_id='3a')
1104
def setup_ab_tree(self):
1105
tree = self.setup_a_tree()
1106
tree.set_last_revision('1a')
1107
tree.branch.set_last_revision_info(1, '1a')
1108
tree.commit('2b', rev_id='2b')
1109
tree.commit('3b', rev_id='3b')
1112
def setup_ac_tree(self):
1113
tree = self.setup_a_tree()
1114
tree.set_last_revision(revision.NULL_REVISION)
1115
tree.branch.set_last_revision_info(0, revision.NULL_REVISION)
1116
tree.commit('1c', rev_id='1c')
1117
tree.commit('2c', rev_id='2c')
1118
tree.commit('3c', rev_id='3c')
1121
def test_all_new(self):
1122
tree = self.setup_ab_tree()
1123
old, new = log.get_history_change('1a', '3a', tree.branch.repository)
1124
self.assertEqual([], old)
1125
self.assertEqual(['2a', '3a'], new)
1127
def test_all_old(self):
1128
tree = self.setup_ab_tree()
1129
old, new = log.get_history_change('3a', '1a', tree.branch.repository)
1130
self.assertEqual([], new)
1131
self.assertEqual(['2a', '3a'], old)
1133
def test_null_old(self):
1134
tree = self.setup_ab_tree()
1135
old, new = log.get_history_change(revision.NULL_REVISION,
1136
'3a', tree.branch.repository)
1137
self.assertEqual([], old)
1138
self.assertEqual(['1a', '2a', '3a'], new)
1140
def test_null_new(self):
1141
tree = self.setup_ab_tree()
1142
old, new = log.get_history_change('3a', revision.NULL_REVISION,
1143
tree.branch.repository)
1144
self.assertEqual([], new)
1145
self.assertEqual(['1a', '2a', '3a'], old)
1147
def test_diverged(self):
1148
tree = self.setup_ab_tree()
1149
old, new = log.get_history_change('3a', '3b', tree.branch.repository)
1150
self.assertEqual(old, ['2a', '3a'])
1151
self.assertEqual(new, ['2b', '3b'])
1153
def test_unrelated(self):
1154
tree = self.setup_ac_tree()
1155
old, new = log.get_history_change('3a', '3c', tree.branch.repository)
1156
self.assertEqual(old, ['1a', '2a', '3a'])
1157
self.assertEqual(new, ['1c', '2c', '3c'])
1159
def test_show_branch_change(self):
1160
tree = self.setup_ab_tree()
1162
log.show_branch_change(tree.branch, s, 3, '3a')
1163
self.assertContainsRe(s.getvalue(),
1164
'[*]{60}\nRemoved Revisions:\n(.|\n)*2a(.|\n)*3a(.|\n)*'
1165
'[*]{60}\n\nAdded Revisions:\n(.|\n)*2b(.|\n)*3b')
1167
def test_show_branch_change_no_change(self):
1168
tree = self.setup_ab_tree()
1170
log.show_branch_change(tree.branch, s, 3, '3b')
1171
self.assertEqual(s.getvalue(),
1172
'Nothing seems to have changed\n')
1174
def test_show_branch_change_no_old(self):
1175
tree = self.setup_ab_tree()
1177
log.show_branch_change(tree.branch, s, 2, '2b')
1178
self.assertContainsRe(s.getvalue(), 'Added Revisions:')
1179
self.assertNotContainsRe(s.getvalue(), 'Removed Revisions:')
1181
def test_show_branch_change_no_new(self):
1182
tree = self.setup_ab_tree()
1183
tree.branch.set_last_revision_info(2, '2b')
1185
log.show_branch_change(tree.branch, s, 3, '3b')
1186
self.assertContainsRe(s.getvalue(), 'Removed Revisions:')
1187
self.assertNotContainsRe(s.getvalue(), 'Added Revisions:')
1190
class TestRevisionNotInBranch(TestCaseForLogFormatter):
1192
def setup_a_tree(self):
1193
tree = self.make_branch_and_tree('tree')
1195
self.addCleanup(tree.unlock)
1197
'committer': 'Joe Foo <joe@foo.com>',
1198
'timestamp': 1132617600, # Mon 2005-11-22 00:00:00 +0000
1199
'timezone': 0, # UTC
1201
tree.commit('commit 1a', rev_id='1a', **kwargs)
1202
tree.commit('commit 2a', rev_id='2a', **kwargs)
1203
tree.commit('commit 3a', rev_id='3a', **kwargs)
1206
def setup_ab_tree(self):
1207
tree = self.setup_a_tree()
1208
tree.set_last_revision('1a')
1209
tree.branch.set_last_revision_info(1, '1a')
1211
'committer': 'Joe Foo <joe@foo.com>',
1212
'timestamp': 1132617600, # Mon 2005-11-22 00:00:00 +0000
1213
'timezone': 0, # UTC
1215
tree.commit('commit 2b', rev_id='2b', **kwargs)
1216
tree.commit('commit 3b', rev_id='3b', **kwargs)
1219
def test_one_revision(self):
1220
tree = self.setup_ab_tree()
1222
rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
1223
log.show_log(tree.branch, lf, verbose=True, start_revision=rev,
1225
self.assertEqual(1, len(lf.revisions))
1226
self.assertEqual(None, lf.revisions[0].revno) # Out-of-branch
1227
self.assertEqual('3a', lf.revisions[0].rev.revision_id)
1229
def test_many_revisions(self):
1230
tree = self.setup_ab_tree()
1232
start_rev = revisionspec.RevisionInfo(tree.branch, None, '1a')
1233
end_rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
1234
log.show_log(tree.branch, lf, verbose=True, start_revision=start_rev,
1235
end_revision=end_rev)
1236
self.assertEqual(3, len(lf.revisions))
1237
self.assertEqual(None, lf.revisions[0].revno) # Out-of-branch
1238
self.assertEqual('3a', lf.revisions[0].rev.revision_id)
1239
self.assertEqual(None, lf.revisions[1].revno) # Out-of-branch
1240
self.assertEqual('2a', lf.revisions[1].rev.revision_id)
1241
self.assertEqual('1', lf.revisions[2].revno) # In-branch
1243
def test_long_format(self):
1244
tree = self.setup_ab_tree()
1245
start_rev = revisionspec.RevisionInfo(tree.branch, None, '1a')
1246
end_rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
1247
self.assertFormatterResult("""\
1248
------------------------------------------------------------
1250
committer: Joe Foo <joe@foo.com>
1252
timestamp: Tue 2005-11-22 00:00:00 +0000
1255
------------------------------------------------------------
1257
committer: Joe Foo <joe@foo.com>
1259
timestamp: Tue 2005-11-22 00:00:00 +0000
1262
------------------------------------------------------------
1264
committer: Joe Foo <joe@foo.com>
1266
timestamp: Tue 2005-11-22 00:00:00 +0000
1270
tree.branch, log.LongLogFormatter, show_log_kwargs={
1271
'start_revision': start_rev, 'end_revision': end_rev
1274
def test_short_format(self):
1275
tree = self.setup_ab_tree()
1276
start_rev = revisionspec.RevisionInfo(tree.branch, None, '1a')
1277
end_rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
1278
self.assertFormatterResult("""\
1287
1 Joe Foo\t2005-11-22
1291
tree.branch, log.ShortLogFormatter, show_log_kwargs={
1292
'start_revision': start_rev, 'end_revision': end_rev
1295
def test_line_format(self):
1296
tree = self.setup_ab_tree()
1297
start_rev = revisionspec.RevisionInfo(tree.branch, None, '1a')
1298
end_rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
1299
self.assertFormatterResult("""\
1300
Joe Foo 2005-11-22 commit 3a
1301
Joe Foo 2005-11-22 commit 2a
1302
1: Joe Foo 2005-11-22 commit 1a
1304
tree.branch, log.LineLogFormatter, show_log_kwargs={
1305
'start_revision': start_rev, 'end_revision': end_rev
1309
class TestLogWithBugs(TestCaseForLogFormatter, TestLogMixin):
1312
TestCaseForLogFormatter.setUp(self)
1313
log.properties_handler_registry.register(
1314
'bugs_properties_handler',
1315
log._bugs_properties_handler)
1317
def make_commits_with_bugs(self):
1318
"""Helper method for LogFormatter tests"""
1319
tree = self.make_branch_and_tree(u'.')
1320
self.build_tree(['a', 'b'])
1322
self.wt_commit(tree, 'simple log message', rev_id='a1',
1323
revprops={'bugs': 'test://bug/id fixed'})
1325
self.wt_commit(tree, 'multiline\nlog\nmessage\n', rev_id='a2',
1326
authors=['Joe Bar <joe@bar.com>'],
1327
revprops={'bugs': 'test://bug/id fixed\n'
1328
'test://bug/2 fixed'})
1332
def test_long_bugs(self):
1333
tree = self.make_commits_with_bugs()
1334
self.assertFormatterResult("""\
1335
------------------------------------------------------------
1337
fixes bugs: test://bug/id test://bug/2
1338
author: Joe Bar <joe@bar.com>
1339
committer: Joe Foo <joe@foo.com>
1341
timestamp: Tue 2005-11-22 00:00:01 +0000
1346
------------------------------------------------------------
1348
fixes bug: test://bug/id
1349
committer: Joe Foo <joe@foo.com>
1351
timestamp: Tue 2005-11-22 00:00:00 +0000
1355
tree.branch, log.LongLogFormatter)
1357
def test_short_bugs(self):
1358
tree = self.make_commits_with_bugs()
1359
self.assertFormatterResult("""\
1360
2 Joe Bar\t2005-11-22
1361
fixes bugs: test://bug/id test://bug/2
1366
1 Joe Foo\t2005-11-22
1367
fixes bug: test://bug/id
1371
tree.branch, log.ShortLogFormatter)
1373
def test_wrong_bugs_property(self):
1374
tree = self.make_branch_and_tree(u'.')
1375
self.build_tree(['foo'])
1376
self.wt_commit(tree, 'simple log message', rev_id='a1',
1377
revprops={'bugs': 'test://bug/id invalid_value'})
1378
self.assertFormatterResult("""\
1379
1 Joe Foo\t2005-11-22
1383
tree.branch, log.ShortLogFormatter)
1385
def test_bugs_handler_present(self):
1386
self.properties_handler_registry.get('bugs_properties_handler')
1389
class TestLogForAuthors(TestCaseForLogFormatter):
1392
TestCaseForLogFormatter.setUp(self)
1393
self.wt = self.make_standard_commit('nicky',
1394
authors=['John Doe <jdoe@example.com>',
1395
'Jane Rey <jrey@example.com>'])
1397
def assertFormatterResult(self, formatter, who, result):
1398
formatter_kwargs = dict()
1400
author_list_handler = log.author_list_registry.get(who)
1401
formatter_kwargs['author_list_handler'] = author_list_handler
1402
TestCaseForLogFormatter.assertFormatterResult(self, result,
1403
self.wt.branch, formatter, formatter_kwargs=formatter_kwargs)
1405
def test_line_default(self):
1406
self.assertFormatterResult(log.LineLogFormatter, None, """\
1407
1: John Doe 2005-11-22 add a
1410
def test_line_committer(self):
1411
self.assertFormatterResult(log.LineLogFormatter, 'committer', """\
1412
1: Lorem Ipsum 2005-11-22 add a
1415
def test_line_first(self):
1416
self.assertFormatterResult(log.LineLogFormatter, 'first', """\
1417
1: John Doe 2005-11-22 add a
1420
def test_line_all(self):
1421
self.assertFormatterResult(log.LineLogFormatter, 'all', """\
1422
1: John Doe, Jane Rey 2005-11-22 add a
1426
def test_short_default(self):
1427
self.assertFormatterResult(log.ShortLogFormatter, None, """\
1428
1 John Doe\t2005-11-22
1433
def test_short_committer(self):
1434
self.assertFormatterResult(log.ShortLogFormatter, 'committer', """\
1435
1 Lorem Ipsum\t2005-11-22
1440
def test_short_first(self):
1441
self.assertFormatterResult(log.ShortLogFormatter, 'first', """\
1442
1 John Doe\t2005-11-22
1447
def test_short_all(self):
1448
self.assertFormatterResult(log.ShortLogFormatter, 'all', """\
1449
1 John Doe, Jane Rey\t2005-11-22
1454
def test_long_default(self):
1455
self.assertFormatterResult(log.LongLogFormatter, None, """\
1456
------------------------------------------------------------
1458
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
1459
committer: Lorem Ipsum <test@example.com>
1461
timestamp: Tue 2005-11-22 00:00:00 +0000
1466
def test_long_committer(self):
1467
self.assertFormatterResult(log.LongLogFormatter, 'committer', """\
1468
------------------------------------------------------------
1470
committer: Lorem Ipsum <test@example.com>
1472
timestamp: Tue 2005-11-22 00:00:00 +0000
1477
def test_long_first(self):
1478
self.assertFormatterResult(log.LongLogFormatter, 'first', """\
1479
------------------------------------------------------------
1481
author: John Doe <jdoe@example.com>
1482
committer: Lorem Ipsum <test@example.com>
1484
timestamp: Tue 2005-11-22 00:00:00 +0000
1489
def test_long_all(self):
1490
self.assertFormatterResult(log.LongLogFormatter, 'all', """\
1491
------------------------------------------------------------
1493
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
1494
committer: Lorem Ipsum <test@example.com>
1496
timestamp: Tue 2005-11-22 00:00:00 +0000
1501
def test_gnu_changelog_default(self):
1502
self.assertFormatterResult(log.GnuChangelogLogFormatter, None, """\
1503
2005-11-22 John Doe <jdoe@example.com>
1509
def test_gnu_changelog_committer(self):
1510
self.assertFormatterResult(log.GnuChangelogLogFormatter, 'committer', """\
1511
2005-11-22 Lorem Ipsum <test@example.com>
1517
def test_gnu_changelog_first(self):
1518
self.assertFormatterResult(log.GnuChangelogLogFormatter, 'first', """\
1519
2005-11-22 John Doe <jdoe@example.com>
1525
def test_gnu_changelog_all(self):
1526
self.assertFormatterResult(log.GnuChangelogLogFormatter, 'all', """\
1527
2005-11-22 John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
1534
class TestLogExcludeAncestry(tests.TestCaseWithTransport):
1536
def make_branch_with_alternate_ancestries(self, relpath='.'):
1537
# See test_merge_sorted_exclude_ancestry below for the difference with
1538
# bt.per_branch.test_iter_merge_sorted_revision.
1539
# TestIterMergeSortedRevisionsBushyGraph.
1540
# make_branch_with_alternate_ancestries
1541
# and test_merge_sorted_exclude_ancestry
1542
# See the FIXME in assertLogRevnos too.
1543
builder = branchbuilder.BranchBuilder(self.get_transport(relpath))
1555
builder.start_series()
1556
builder.build_snapshot('1', None, [
1557
('add', ('', 'TREE_ROOT', 'directory', '')),])
1558
builder.build_snapshot('1.1.1', ['1'], [])
1559
builder.build_snapshot('2', ['1'], [])
1560
builder.build_snapshot('1.2.1', ['1.1.1'], [])
1561
builder.build_snapshot('1.1.2', ['1.1.1', '1.2.1'], [])
1562
builder.build_snapshot('3', ['2', '1.1.2'], [])
1563
builder.finish_series()
1564
br = builder.get_branch()
1566
self.addCleanup(br.unlock)
1569
def assertLogRevnos(self, expected_revnos, b, start, end,
1570
exclude_common_ancestry, generate_merge_revisions=True):
1571
# FIXME: the layering in log makes it hard to test intermediate levels,
1572
# I wish adding filters with their parameters was easier...
1574
iter_revs = log._calc_view_revisions(
1575
b, start, end, direction='reverse',
1576
generate_merge_revisions=generate_merge_revisions,
1577
exclude_common_ancestry=exclude_common_ancestry)
1578
self.assertEqual(expected_revnos,
1579
[revid for revid, revno, depth in iter_revs])
1581
def test_merge_sorted_exclude_ancestry(self):
1582
b = self.make_branch_with_alternate_ancestries()
1583
self.assertLogRevnos(['3', '1.1.2', '1.2.1', '1.1.1', '2', '1'],
1584
b, '1', '3', exclude_common_ancestry=False)
1585
# '2' is part of the '3' ancestry but not part of '1.1.1' ancestry so
1586
# it should be mentioned even if merge_sort order will make it appear
1588
self.assertLogRevnos(['3', '1.1.2', '1.2.1', '2'],
1589
b, '1.1.1', '3', exclude_common_ancestry=True)
1591
def test_merge_sorted_simple_revnos_exclude_ancestry(self):
1592
b = self.make_branch_with_alternate_ancestries()
1593
self.assertLogRevnos(['3', '2'],
1594
b, '1', '3', exclude_common_ancestry=True,
1595
generate_merge_revisions=False)
1596
self.assertLogRevnos(['3', '1.1.2', '1.2.1', '1.1.1', '2'],
1597
b, '1', '3', exclude_common_ancestry=True,
1598
generate_merge_revisions=True)
1601
class TestLogDefaults(TestCaseForLogFormatter):
1602
def test_default_log_level(self):
1604
Test to ensure that specifying 'levels=1' to make_log_request_dict
1605
doesn't get overwritten when using a LogFormatter that supports more
1609
wt = self._prepare_tree_with_merges()
1612
class CustomLogFormatter(log.LogFormatter):
1613
def __init__(self, *args, **kwargs):
1614
super(CustomLogFormatter, self).__init__(*args, **kwargs)
1616
def get_levels(self):
1617
# log formatter supports all levels:
1619
def log_revision(self, revision):
1620
self.revisions.append(revision)
1622
log_formatter = LogCatcher()
1623
# First request we don't specify number of levels, we should get a
1624
# sensible default (whatever the LogFormatter handles - which in this
1625
# case is 0/everything):
1626
request = log.make_log_request_dict(limit=10)
1627
log.Logger(b, request).show(log_formatter)
1628
# should have all three revisions:
1629
self.assertEquals(len(log_formatter.revisions), 3)
1632
log_formatter = LogCatcher()
1633
# now explicitly request mainline revisions only:
1634
request = log.make_log_request_dict(limit=10, levels=1)
1635
log.Logger(b, request).show(log_formatter)
1636
# should now only have 2 revisions:
1637
self.assertEquals(len(log_formatter.revisions), 2)