1
# Copyright (C) 2005 by Canonical Ltd
1
# Copyright (C) 2005, 2006, 2007 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
19
from bzrlib.selftest import BzrTestBase
20
from bzrlib.log import LogFormatter, show_log, LongLogFormatter
21
from bzrlib.branch import Branch
23
class _LogEntry(object):
24
# should probably move into bzrlib.log?
28
class LogCatcher(LogFormatter):
29
"""Pull log messages into list rather than displaying them.
31
For ease of testing we save log messages here rather than actually
32
formatting them, so that we can precisely check the result without
33
being too dependent on the exact formatting.
35
We should also test the LogFormatter.
18
from cStringIO import StringIO
30
class TestCaseForLogFormatter(tests.TestCaseWithTransport):
33
super(TestCaseForLogFormatter, self).setUp()
34
# keep a reference to the "current" custom prop. handler registry
35
self.properties_handler_registry = log.properties_handler_registry
36
# Use a clean registry for log
37
log.properties_handler_registry = registry.Registry()
40
log.properties_handler_registry = self.properties_handler_registry
41
self.addCleanup(restore)
43
def assertFormatterResult(self, result, branch, formatter_class,
44
formatter_kwargs=None, show_log_kwargs=None,
46
logfile = self.make_utf8_encoded_stringio()
47
if formatter_kwargs is None:
49
formatter = formatter_class(to_file=logfile, **formatter_kwargs)
50
if show_log_kwargs is None:
52
log.show_log(branch, formatter, **show_log_kwargs)
53
log_content = logfile.getvalue()
55
log_content = normalize_log(log_content)
56
self.assertEqualDiff(result, log_content)
58
def make_standard_commit(self, branch_nick, **kwargs):
59
wt = self.make_branch_and_tree('.')
61
self.addCleanup(wt.unlock)
62
self.build_tree(['a'])
64
wt.branch.nick = branch_nick
66
kwargs.setdefault('message', 'add a')
67
kwargs.setdefault('timestamp', 1132711707)
68
kwargs.setdefault('timezone', 36000)
69
kwargs.setdefault('committer', 'Lorem Ipsum <test@example.com>')
70
kwargs.setdefault('authors', ['John Doe <jdoe@example.com>'])
74
def _prepare_tree_with_merges(self, with_tags=False):
75
wt = self.make_branch_and_memory_tree('.')
77
self.addCleanup(wt.unlock)
79
wt.commit('rev-1', rev_id='rev-1',
80
timestamp=1132586655, timezone=36000,
81
committer='Joe Foo <joe@foo.com>')
82
wt.commit('rev-merged', rev_id='rev-2a',
83
timestamp=1132586700, timezone=36000,
84
committer='Joe Foo <joe@foo.com>')
85
wt.set_parent_ids(['rev-1', 'rev-2a'])
86
wt.branch.set_last_revision_info(1, 'rev-1')
87
wt.commit('rev-2', rev_id='rev-2b',
88
timestamp=1132586800, timezone=36000,
89
committer='Joe Foo <joe@foo.com>')
92
branch.tags.set_tag('v0.2', 'rev-2b')
93
wt.commit('rev-3', rev_id='rev-3',
94
timestamp=1132586900, timezone=36000,
95
committer='Jane Foo <jane@foo.com>')
96
branch.tags.set_tag('v1.0rc1', 'rev-3')
97
branch.tags.set_tag('v1.0', 'rev-3')
103
class LogCatcher(log.LogFormatter):
104
"""Pull log messages into a list rather than displaying them.
106
To simplify testing we save logged revisions here rather than actually
107
formatting anything, so that we can precisely check the result without
108
being dependent on the formatting.
111
supports_delta = True
37
113
def __init__(self):
38
114
super(LogCatcher, self).__init__(to_file=None)
42
def show(self, revno, rev, delta):
50
class SimpleLogTest(BzrTestBase):
117
def log_revision(self, revision):
118
self.revisions.append(revision)
121
class TestShowLog(tests.TestCaseWithTransport):
51
123
def checkDelta(self, delta, **kw):
52
"""Check the filenames touched by a delta are as expected."""
124
"""Check the filenames touched by a delta are as expected.
126
Caller only have to pass in the list of files for each part, all
127
unspecified parts are considered empty (and checked as such).
53
129
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
130
# By default we expect an empty list
54
131
expected = kw.get(n, [])
56
# tests are written with unix paths; fix them up for windows
58
expected = [x.replace('/', os.sep) for x in expected]
60
132
# strip out only the path components
61
133
got = [x[0] for x in getattr(delta, n)]
62
self.assertEquals(expected, got)
66
eq = self.assertEquals
69
b = Branch('.', init=True)
134
self.assertEqual(expected, got)
136
def assertInvalidRevisonNumber(self, br, start, end):
138
self.assertRaises(errors.InvalidRevisionNumber,
139
log.show_log, br, lf,
140
start_revision=start, end_revision=end)
142
def test_cur_revno(self):
143
wt = self.make_branch_and_tree('.')
147
wt.commit('empty commit')
148
log.show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
150
# Since there is a single revision in the branch all the combinations
152
self.assertInvalidRevisonNumber(b, 2, 1)
153
self.assertInvalidRevisonNumber(b, 1, 2)
154
self.assertInvalidRevisonNumber(b, 0, 2)
155
self.assertInvalidRevisonNumber(b, 1, 0)
156
self.assertInvalidRevisonNumber(b, -1, 1)
157
self.assertInvalidRevisonNumber(b, 1, -1)
159
def test_empty_branch(self):
160
wt = self.make_branch_and_tree('.')
163
log.show_log(wt.branch, lf)
77
b.commit('empty commit')
165
self.assertEqual([], lf.revisions)
167
def test_empty_commit(self):
168
wt = self.make_branch_and_tree('.')
170
wt.commit('empty commit')
79
show_log(b, lf, verbose=True)
81
eq(lf.logs[0].revno, 1)
82
eq(lf.logs[0].rev.message, 'empty commit')
84
self.log('log delta: %r' % d)
172
log.show_log(wt.branch, lf, verbose=True)
174
self.assertEqual(1, len(revs))
175
self.assertEqual('1', revs[0].revno)
176
self.assertEqual('empty commit', revs[0].rev.message)
177
self.checkDelta(revs[0].delta)
179
def test_simple_commit(self):
180
wt = self.make_branch_and_tree('.')
181
wt.commit('empty commit')
88
182
self.build_tree(['hello'])
90
b.commit('add one file')
91
# log using regular thing
92
show_log(b, LongLogFormatter(self.TEST_LOG))
94
# get log as data structure
184
wt.commit('add one file',
185
committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
186
u'<test@example.com>')
96
show_log(b, lf, verbose=True)
98
self.log('log entries:')
99
for logentry in lf.logs:
100
self.log('%4d %s' % (logentry.revno, logentry.rev.message))
188
log.show_log(wt.branch, lf, verbose=True)
189
self.assertEqual(2, len(lf.revisions))
102
190
# first one is most recent
103
logentry = lf.logs[0]
104
eq(logentry.revno, 2)
105
eq(logentry.rev.message, 'add one file')
107
self.log('log 2 delta: %r' % d)
108
# self.checkDelta(d, added=['hello'])
191
log_entry = lf.revisions[0]
192
self.assertEqual('2', log_entry.revno)
193
self.assertEqual('add one file', log_entry.rev.message)
194
self.checkDelta(log_entry.delta, added=['hello'])
196
def test_commit_message_with_control_chars(self):
197
wt = self.make_branch_and_tree('.')
198
msg = u"All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
199
msg = msg.replace(u'\r', u'\n')
202
log.show_log(wt.branch, lf, verbose=True)
203
committed_msg = lf.revisions[0].rev.message
204
if wt.branch.repository._serializer.squashes_xml_invalid_characters:
205
self.assertNotEqual(msg, committed_msg)
206
self.assertTrue(len(committed_msg) > len(msg))
208
self.assertEqual(msg, committed_msg)
210
def test_commit_message_without_control_chars(self):
211
wt = self.make_branch_and_tree('.')
212
# escaped. As ElementTree apparently does some kind of
213
# newline conversion, neither LF (\x0A) nor CR (\x0D) are
214
# included in the test commit message, even though they are
215
# valid XML 1.0 characters.
216
msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
219
log.show_log(wt.branch, lf, verbose=True)
220
committed_msg = lf.revisions[0].rev.message
221
self.assertEqual(msg, committed_msg)
223
def test_deltas_in_merge_revisions(self):
224
"""Check deltas created for both mainline and merge revisions"""
225
wt = self.make_branch_and_tree('parent')
226
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
229
wt.commit(message='add file1 and file2')
230
self.run_bzr('branch parent child')
231
os.unlink('child/file1')
232
file('child/file2', 'wb').write('hello\n')
233
self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
236
self.run_bzr('merge ../child')
237
wt.commit('merge child branch')
241
lf.supports_merge_revisions = True
242
log.show_log(b, lf, verbose=True)
245
self.assertEqual(3, len(revs))
248
self.assertEqual('2', logentry.revno)
249
self.assertEqual('merge child branch', logentry.rev.message)
250
self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
253
self.assertEqual('1.1.1', logentry.revno)
254
self.assertEqual('remove file1 and modify file2', logentry.rev.message)
255
self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
258
self.assertEqual('1', logentry.revno)
259
self.assertEqual('add file1 and file2', logentry.rev.message)
260
self.checkDelta(logentry.delta, added=['file1', 'file2'])
263
def make_commits_with_trailing_newlines(wt):
264
"""Helper method for LogFormatter tests"""
267
open('a', 'wb').write('hello moto\n')
269
wt.commit('simple log message', rev_id='a1',
270
timestamp=1132586655.459960938, timezone=-6*3600,
271
committer='Joe Foo <joe@foo.com>')
272
open('b', 'wb').write('goodbye\n')
274
wt.commit('multiline\nlog\nmessage\n', rev_id='a2',
275
timestamp=1132586842.411175966, timezone=-6*3600,
276
committer='Joe Foo <joe@foo.com>',
277
authors=['Joe Bar <joe@bar.com>'])
279
open('c', 'wb').write('just another manic monday\n')
281
wt.commit('single line with trailing newline\n', rev_id='a3',
282
timestamp=1132587176.835228920, timezone=-6*3600,
283
committer = 'Joe Foo <joe@foo.com>')
287
def normalize_log(log):
288
"""Replaces the variable lines of logs with fixed lines"""
289
author = 'author: Dolor Sit <test@example.com>'
290
committer = 'committer: Lorem Ipsum <test@example.com>'
291
lines = log.splitlines(True)
292
for idx,line in enumerate(lines):
293
stripped_line = line.lstrip()
294
indent = ' ' * (len(line) - len(stripped_line))
295
if stripped_line.startswith('author:'):
296
lines[idx] = indent + author + '\n'
297
elif stripped_line.startswith('committer:'):
298
lines[idx] = indent + committer + '\n'
299
elif stripped_line.startswith('timestamp:'):
300
lines[idx] = indent + 'timestamp: Just now\n'
301
return ''.join(lines)
304
class TestShortLogFormatter(TestCaseForLogFormatter):
306
def test_trailing_newlines(self):
307
wt = self.make_branch_and_tree('.')
308
b = make_commits_with_trailing_newlines(wt)
309
self.assertFormatterResult("""\
310
3 Joe Foo\t2005-11-21
311
single line with trailing newline
313
2 Joe Bar\t2005-11-21
318
1 Joe Foo\t2005-11-21
322
b, log.ShortLogFormatter)
324
def test_short_log_with_merges(self):
325
wt = self._prepare_tree_with_merges()
326
self.assertFormatterResult("""\
327
2 Joe Foo\t2005-11-22 [merge]
330
1 Joe Foo\t2005-11-22
334
wt.branch, log.ShortLogFormatter)
336
def test_short_log_with_merges_and_advice(self):
337
wt = self._prepare_tree_with_merges()
338
self.assertFormatterResult("""\
339
2 Joe Foo\t2005-11-22 [merge]
342
1 Joe Foo\t2005-11-22
345
Use --include-merges or -n0 to see merged revisions.
347
wt.branch, log.ShortLogFormatter,
348
formatter_kwargs=dict(show_advice=True))
350
def test_short_log_with_merges_and_range(self):
351
wt = self.make_branch_and_memory_tree('.')
353
self.addCleanup(wt.unlock)
355
wt.commit('rev-1', rev_id='rev-1',
356
timestamp=1132586655, timezone=36000,
357
committer='Joe Foo <joe@foo.com>')
358
wt.commit('rev-merged', rev_id='rev-2a',
359
timestamp=1132586700, timezone=36000,
360
committer='Joe Foo <joe@foo.com>')
361
wt.branch.set_last_revision_info(1, 'rev-1')
362
wt.set_parent_ids(['rev-1', 'rev-2a'])
363
wt.commit('rev-2b', rev_id='rev-2b',
364
timestamp=1132586800, timezone=36000,
365
committer='Joe Foo <joe@foo.com>')
366
wt.commit('rev-3a', rev_id='rev-3a',
367
timestamp=1132586800, timezone=36000,
368
committer='Joe Foo <joe@foo.com>')
369
wt.branch.set_last_revision_info(2, 'rev-2b')
370
wt.set_parent_ids(['rev-2b', 'rev-3a'])
371
wt.commit('rev-3b', rev_id='rev-3b',
372
timestamp=1132586800, timezone=36000,
373
committer='Joe Foo <joe@foo.com>')
374
self.assertFormatterResult("""\
375
3 Joe Foo\t2005-11-22 [merge]
378
2 Joe Foo\t2005-11-22 [merge]
382
wt.branch, log.ShortLogFormatter,
383
show_log_kwargs=dict(start_revision=2, end_revision=3))
385
def test_short_log_with_tags(self):
386
wt = self._prepare_tree_with_merges(with_tags=True)
387
self.assertFormatterResult("""\
388
3 Jane Foo\t2005-11-22 {v1.0, v1.0rc1}
391
2 Joe Foo\t2005-11-22 {v0.2} [merge]
394
1 Joe Foo\t2005-11-22
398
wt.branch, log.ShortLogFormatter)
400
def test_short_log_single_merge_revision(self):
401
wt = self.make_branch_and_memory_tree('.')
403
self.addCleanup(wt.unlock)
405
wt.commit('rev-1', rev_id='rev-1',
406
timestamp=1132586655, timezone=36000,
407
committer='Joe Foo <joe@foo.com>')
408
wt.commit('rev-merged', rev_id='rev-2a',
409
timestamp=1132586700, timezone=36000,
410
committer='Joe Foo <joe@foo.com>')
411
wt.set_parent_ids(['rev-1', 'rev-2a'])
412
wt.branch.set_last_revision_info(1, 'rev-1')
413
wt.commit('rev-2', rev_id='rev-2b',
414
timestamp=1132586800, timezone=36000,
415
committer='Joe Foo <joe@foo.com>')
416
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
417
rev = revspec.in_history(wt.branch)
418
self.assertFormatterResult("""\
419
1.1.1 Joe Foo\t2005-11-22
423
wt.branch, log.ShortLogFormatter,
424
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
427
class TestShortLogFormatterWithMergeRevisions(TestCaseForLogFormatter):
429
def test_short_merge_revs_log_with_merges(self):
430
wt = self.make_branch_and_memory_tree('.')
432
self.addCleanup(wt.unlock)
434
wt.commit('rev-1', rev_id='rev-1',
435
timestamp=1132586655, timezone=36000,
436
committer='Joe Foo <joe@foo.com>')
437
wt.commit('rev-merged', rev_id='rev-2a',
438
timestamp=1132586700, timezone=36000,
439
committer='Joe Foo <joe@foo.com>')
440
wt.set_parent_ids(['rev-1', 'rev-2a'])
441
wt.branch.set_last_revision_info(1, 'rev-1')
442
wt.commit('rev-2', rev_id='rev-2b',
443
timestamp=1132586800, timezone=36000,
444
committer='Joe Foo <joe@foo.com>')
445
# Note that the 1.1.1 indenting is in fact correct given that
446
# the revision numbers are right justified within 5 characters
447
# for mainline revnos and 9 characters for dotted revnos.
448
self.assertFormatterResult("""\
449
2 Joe Foo\t2005-11-22 [merge]
452
1.1.1 Joe Foo\t2005-11-22
455
1 Joe Foo\t2005-11-22
459
wt.branch, log.ShortLogFormatter,
460
formatter_kwargs=dict(levels=0))
462
def test_short_merge_revs_log_single_merge_revision(self):
463
wt = self.make_branch_and_memory_tree('.')
465
self.addCleanup(wt.unlock)
467
wt.commit('rev-1', rev_id='rev-1',
468
timestamp=1132586655, timezone=36000,
469
committer='Joe Foo <joe@foo.com>')
470
wt.commit('rev-merged', rev_id='rev-2a',
471
timestamp=1132586700, timezone=36000,
472
committer='Joe Foo <joe@foo.com>')
473
wt.set_parent_ids(['rev-1', 'rev-2a'])
474
wt.branch.set_last_revision_info(1, 'rev-1')
475
wt.commit('rev-2', rev_id='rev-2b',
476
timestamp=1132586800, timezone=36000,
477
committer='Joe Foo <joe@foo.com>')
478
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
479
rev = revspec.in_history(wt.branch)
480
self.assertFormatterResult("""\
481
1.1.1 Joe Foo\t2005-11-22
485
wt.branch, log.ShortLogFormatter,
486
formatter_kwargs=dict(levels=0),
487
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
490
class TestLongLogFormatter(TestCaseForLogFormatter):
492
def test_verbose_log(self):
493
"""Verbose log includes changed files
497
wt = self.make_standard_commit('test_verbose_log', authors=[])
498
self.assertFormatterResult('''\
499
------------------------------------------------------------
501
committer: Lorem Ipsum <test@example.com>
502
branch nick: test_verbose_log
503
timestamp: Wed 2005-11-23 12:08:27 +1000
509
wt.branch, log.LongLogFormatter,
510
show_log_kwargs=dict(verbose=True))
512
def test_merges_are_indented_by_level(self):
513
wt = self.make_branch_and_tree('parent')
514
wt.commit('first post')
515
self.run_bzr('branch parent child')
516
self.run_bzr(['commit', '-m', 'branch 1', '--unchanged', 'child'])
517
self.run_bzr('branch child smallerchild')
518
self.run_bzr(['commit', '-m', 'branch 2', '--unchanged',
521
self.run_bzr('merge ../smallerchild')
522
self.run_bzr(['commit', '-m', 'merge branch 2'])
523
os.chdir('../parent')
524
self.run_bzr('merge ../child')
525
wt.commit('merge branch 1')
526
self.assertFormatterResult("""\
527
------------------------------------------------------------
529
committer: Lorem Ipsum <test@example.com>
534
------------------------------------------------------------
536
committer: Lorem Ipsum <test@example.com>
541
------------------------------------------------------------
543
committer: Lorem Ipsum <test@example.com>
544
branch nick: smallerchild
548
------------------------------------------------------------
550
committer: Lorem Ipsum <test@example.com>
555
------------------------------------------------------------
557
committer: Lorem Ipsum <test@example.com>
563
wt.branch, log.LongLogFormatter,
564
formatter_kwargs=dict(levels=0),
565
show_log_kwargs=dict(verbose=True),
568
def test_verbose_merge_revisions_contain_deltas(self):
569
wt = self.make_branch_and_tree('parent')
570
self.build_tree(['parent/f1', 'parent/f2'])
572
wt.commit('first post')
573
self.run_bzr('branch parent child')
574
os.unlink('child/f1')
575
file('child/f2', 'wb').write('hello\n')
576
self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
579
self.run_bzr('merge ../child')
580
wt.commit('merge branch 1')
581
self.assertFormatterResult("""\
582
------------------------------------------------------------
584
committer: Lorem Ipsum <test@example.com>
593
------------------------------------------------------------
595
committer: Lorem Ipsum <test@example.com>
599
removed f1 and modified f2
604
------------------------------------------------------------
606
committer: Lorem Ipsum <test@example.com>
615
wt.branch, log.LongLogFormatter,
616
formatter_kwargs=dict(levels=0),
617
show_log_kwargs=dict(verbose=True),
620
def test_trailing_newlines(self):
621
wt = self.make_branch_and_tree('.')
622
b = make_commits_with_trailing_newlines(wt)
623
self.assertFormatterResult("""\
624
------------------------------------------------------------
626
committer: Joe Foo <joe@foo.com>
628
timestamp: Mon 2005-11-21 09:32:56 -0600
630
single line with trailing newline
631
------------------------------------------------------------
633
author: Joe Bar <joe@bar.com>
634
committer: Joe Foo <joe@foo.com>
636
timestamp: Mon 2005-11-21 09:27:22 -0600
641
------------------------------------------------------------
643
committer: Joe Foo <joe@foo.com>
645
timestamp: Mon 2005-11-21 09:24:15 -0600
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("""\
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: Wed 2005-11-23 12:08:27 +1000
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')
675
def trivial_custom_prop_handler(revision):
676
return {'test_prop':'test_value'}
678
# Cleaned up in setUp()
679
log.properties_handler_registry.register(
680
'trivial_custom_prop_handler',
681
trivial_custom_prop_handler)
682
self.assertFormatterResult("""\
683
------------------------------------------------------------
685
test_prop: test_value
686
author: John Doe <jdoe@example.com>
687
committer: Lorem Ipsum <test@example.com>
688
branch nick: test_properties_in_log
689
timestamp: Wed 2005-11-23 12:08:27 +1000
693
wt.branch, log.LongLogFormatter)
695
def test_properties_in_short_log(self):
696
"""Log includes the custom properties returned by the registered
699
wt = self.make_standard_commit('test_properties_in_short_log')
700
def trivial_custom_prop_handler(revision):
701
return {'test_prop':'test_value'}
703
log.properties_handler_registry.register(
704
'trivial_custom_prop_handler',
705
trivial_custom_prop_handler)
706
self.assertFormatterResult("""\
707
1 John Doe\t2005-11-23
708
test_prop: test_value
712
wt.branch, log.ShortLogFormatter)
714
def test_error_in_properties_handler(self):
715
"""Log includes the custom properties returned by the registered
718
wt = self.make_standard_commit('error_in_properties_handler',
719
revprops={'first_prop':'first_value'})
720
sio = self.make_utf8_encoded_stringio()
721
formatter = log.LongLogFormatter(to_file=sio)
722
def trivial_custom_prop_handler(revision):
723
raise StandardError("a test error")
725
log.properties_handler_registry.register(
726
'trivial_custom_prop_handler',
727
trivial_custom_prop_handler)
728
self.assertRaises(StandardError, log.show_log, wt.branch, formatter,)
730
def test_properties_handler_bad_argument(self):
731
wt = self.make_standard_commit('bad_argument',
732
revprops={'a_prop':'test_value'})
733
sio = self.make_utf8_encoded_stringio()
734
formatter = log.LongLogFormatter(to_file=sio)
735
def bad_argument_prop_handler(revision):
736
return {'custom_prop_name':revision.properties['a_prop']}
738
log.properties_handler_registry.register(
739
'bad_argument_prop_handler',
740
bad_argument_prop_handler)
742
self.assertRaises(AttributeError, formatter.show_properties,
745
revision = wt.branch.repository.get_revision(wt.branch.last_revision())
746
formatter.show_properties(revision, '')
747
self.assertEqualDiff('''custom_prop_name: test_value\n''',
751
class TestLongLogFormatterWithoutMergeRevisions(TestCaseForLogFormatter):
753
def test_long_verbose_log(self):
754
"""Verbose log includes changed files
758
wt = self.make_standard_commit('test_long_verbose_log', authors=[])
759
self.assertFormatterResult("""\
760
------------------------------------------------------------
762
committer: Lorem Ipsum <test@example.com>
763
branch nick: test_long_verbose_log
764
timestamp: Wed 2005-11-23 12:08:27 +1000
770
wt.branch, log.LongLogFormatter,
771
formatter_kwargs=dict(levels=1),
772
show_log_kwargs=dict(verbose=True))
774
def test_long_verbose_contain_deltas(self):
775
wt = self.make_branch_and_tree('parent')
776
self.build_tree(['parent/f1', 'parent/f2'])
778
wt.commit('first post')
779
child_wt = wt.bzrdir.sprout('child').open_workingtree()
780
os.unlink('child/f1')
781
self.build_tree_contents([('child/f2', 'hello\n')])
782
child_wt.commit('removed f1 and modified f2')
783
wt.merge_from_branch(child_wt.branch)
784
wt.commit('merge branch 1')
785
self.assertFormatterResult("""\
786
------------------------------------------------------------
788
committer: Lorem Ipsum <test@example.com>
797
------------------------------------------------------------
799
committer: Lorem Ipsum <test@example.com>
808
wt.branch, log.LongLogFormatter,
809
formatter_kwargs=dict(levels=1),
810
show_log_kwargs=dict(verbose=True),
813
def test_long_trailing_newlines(self):
814
wt = self.make_branch_and_tree('.')
815
b = make_commits_with_trailing_newlines(wt)
816
self.assertFormatterResult("""\
817
------------------------------------------------------------
819
committer: Joe Foo <joe@foo.com>
821
timestamp: Mon 2005-11-21 09:32:56 -0600
823
single line with trailing newline
824
------------------------------------------------------------
826
author: Joe Bar <joe@bar.com>
827
committer: Joe Foo <joe@foo.com>
829
timestamp: Mon 2005-11-21 09:27:22 -0600
834
------------------------------------------------------------
836
committer: Joe Foo <joe@foo.com>
838
timestamp: Mon 2005-11-21 09:24:15 -0600
842
b, log.LongLogFormatter,
843
formatter_kwargs=dict(levels=1))
845
def test_long_author_in_log(self):
846
"""Log includes the author name if it's set in
847
the revision properties
849
wt = self.make_standard_commit('test_author_log')
850
self.assertFormatterResult("""\
851
------------------------------------------------------------
853
author: John Doe <jdoe@example.com>
854
committer: Lorem Ipsum <test@example.com>
855
branch nick: test_author_log
856
timestamp: Wed 2005-11-23 12:08:27 +1000
860
wt.branch, log.LongLogFormatter,
861
formatter_kwargs=dict(levels=1))
863
def test_long_properties_in_log(self):
864
"""Log includes the custom properties returned by the registered
867
wt = self.make_standard_commit('test_properties_in_log')
868
def trivial_custom_prop_handler(revision):
869
return {'test_prop':'test_value'}
871
log.properties_handler_registry.register(
872
'trivial_custom_prop_handler',
873
trivial_custom_prop_handler)
874
self.assertFormatterResult("""\
875
------------------------------------------------------------
877
test_prop: test_value
878
author: John Doe <jdoe@example.com>
879
committer: Lorem Ipsum <test@example.com>
880
branch nick: test_properties_in_log
881
timestamp: Wed 2005-11-23 12:08:27 +1000
885
wt.branch, log.LongLogFormatter,
886
formatter_kwargs=dict(levels=1))
889
class TestLineLogFormatter(TestCaseForLogFormatter):
891
def test_line_log(self):
892
"""Line log should show revno
896
wt = self.make_standard_commit('test-line-log',
897
committer='Line-Log-Formatter Tester <test@line.log>',
899
self.assertFormatterResult("""\
900
1: Line-Log-Formatte... 2005-11-23 add a
902
wt.branch, log.LineLogFormatter)
904
def test_trailing_newlines(self):
905
wt = self.make_branch_and_tree('.')
906
b = make_commits_with_trailing_newlines(wt)
907
self.assertFormatterResult("""\
908
3: Joe Foo 2005-11-21 single line with trailing newline
909
2: Joe Bar 2005-11-21 multiline
910
1: Joe Foo 2005-11-21 simple log message
912
b, log.LineLogFormatter)
914
def test_line_log_single_merge_revision(self):
915
wt = self._prepare_tree_with_merges()
916
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
917
rev = revspec.in_history(wt.branch)
918
self.assertFormatterResult("""\
919
1.1.1: Joe Foo 2005-11-22 rev-merged
921
wt.branch, log.LineLogFormatter,
922
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
924
def test_line_log_with_tags(self):
925
wt = self._prepare_tree_with_merges(with_tags=True)
926
self.assertFormatterResult("""\
927
3: Jane Foo 2005-11-22 {v1.0, v1.0rc1} rev-3
928
2: Joe Foo 2005-11-22 [merge] {v0.2} rev-2
929
1: Joe Foo 2005-11-22 rev-1
931
wt.branch, log.LineLogFormatter)
934
class TestLineLogFormatterWithMergeRevisions(TestCaseForLogFormatter):
936
def test_line_merge_revs_log(self):
937
"""Line log should show revno
941
wt = self.make_standard_commit('test-line-log',
942
committer='Line-Log-Formatter Tester <test@line.log>',
944
self.assertFormatterResult("""\
945
1: Line-Log-Formatte... 2005-11-23 add a
947
wt.branch, log.LineLogFormatter)
949
def test_line_merge_revs_log_single_merge_revision(self):
950
wt = self.make_branch_and_memory_tree('.')
952
self.addCleanup(wt.unlock)
954
wt.commit('rev-1', rev_id='rev-1',
955
timestamp=1132586655, timezone=36000,
956
committer='Joe Foo <joe@foo.com>')
957
wt.commit('rev-merged', rev_id='rev-2a',
958
timestamp=1132586700, timezone=36000,
959
committer='Joe Foo <joe@foo.com>')
960
wt.set_parent_ids(['rev-1', 'rev-2a'])
961
wt.branch.set_last_revision_info(1, 'rev-1')
962
wt.commit('rev-2', rev_id='rev-2b',
963
timestamp=1132586800, timezone=36000,
964
committer='Joe Foo <joe@foo.com>')
965
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
966
rev = revspec.in_history(wt.branch)
967
self.assertFormatterResult("""\
968
1.1.1: Joe Foo 2005-11-22 rev-merged
970
wt.branch, log.LineLogFormatter,
971
formatter_kwargs=dict(levels=0),
972
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
974
def test_line_merge_revs_log_with_merges(self):
975
wt = self.make_branch_and_memory_tree('.')
977
self.addCleanup(wt.unlock)
979
wt.commit('rev-1', rev_id='rev-1',
980
timestamp=1132586655, timezone=36000,
981
committer='Joe Foo <joe@foo.com>')
982
wt.commit('rev-merged', rev_id='rev-2a',
983
timestamp=1132586700, timezone=36000,
984
committer='Joe Foo <joe@foo.com>')
985
wt.set_parent_ids(['rev-1', 'rev-2a'])
986
wt.branch.set_last_revision_info(1, 'rev-1')
987
wt.commit('rev-2', rev_id='rev-2b',
988
timestamp=1132586800, timezone=36000,
989
committer='Joe Foo <joe@foo.com>')
990
self.assertFormatterResult("""\
991
2: Joe Foo 2005-11-22 [merge] rev-2
992
1.1.1: Joe Foo 2005-11-22 rev-merged
993
1: Joe Foo 2005-11-22 rev-1
995
wt.branch, log.LineLogFormatter,
996
formatter_kwargs=dict(levels=0))
999
class TestGetViewRevisions(tests.TestCaseWithTransport):
1001
def make_tree_with_commits(self):
1002
"""Create a tree with well-known revision ids"""
1003
wt = self.make_branch_and_tree('tree1')
1004
wt.commit('commit one', rev_id='1')
1005
wt.commit('commit two', rev_id='2')
1006
wt.commit('commit three', rev_id='3')
1007
mainline_revs = [None, '1', '2', '3']
1008
rev_nos = {'1': 1, '2': 2, '3': 3}
1009
return mainline_revs, rev_nos, wt
1011
def make_tree_with_merges(self):
1012
"""Create a tree with well-known revision ids and a merge"""
1013
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1014
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
1015
tree2.commit('four-a', rev_id='4a')
1016
wt.merge_from_branch(tree2.branch)
1017
wt.commit('four-b', rev_id='4b')
1018
mainline_revs.append('4b')
1021
return mainline_revs, rev_nos, wt
1023
def make_branch_with_many_merges(self):
1024
"""Create a tree with well-known revision ids"""
1025
builder = self.make_branch_builder('tree1')
1026
builder.start_series()
1027
builder.build_snapshot('1', None, [
1028
('add', ('', 'TREE_ROOT', 'directory', '')),
1029
('add', ('f', 'f-id', 'file', '1\n'))])
1030
builder.build_snapshot('2', ['1'], [])
1031
builder.build_snapshot('3a', ['2'], [
1032
('modify', ('f-id', '1\n2\n3a\n'))])
1033
builder.build_snapshot('3b', ['2', '3a'], [
1034
('modify', ('f-id', '1\n2\n3a\n'))])
1035
builder.build_snapshot('3c', ['2', '3b'], [
1036
('modify', ('f-id', '1\n2\n3a\n'))])
1037
builder.build_snapshot('4a', ['3b'], [])
1038
builder.build_snapshot('4b', ['3c', '4a'], [])
1039
builder.finish_series()
1053
mainline_revs = [None, '1', '2', '3c', '4b']
1054
rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
1055
full_rev_nos_for_reference = {
1058
'3a': '2.1.1', #first commit tree 3
1059
'3b': '2.2.1', # first commit tree 2
1060
'3c': '3', #merges 3b to main
1061
'4a': '2.2.2', # second commit tree 2
1062
'4b': '4', # merges 4a to main
1064
return mainline_revs, rev_nos, builder.get_branch()
1066
def test_get_view_revisions_forward(self):
1067
"""Test the get_view_revisions method"""
1068
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1070
self.addCleanup(wt.unlock)
1071
revisions = list(log.get_view_revisions(
1072
mainline_revs, rev_nos, wt.branch, 'forward'))
1073
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
1075
revisions2 = list(log.get_view_revisions(
1076
mainline_revs, rev_nos, wt.branch, 'forward',
1077
include_merges=False))
1078
self.assertEqual(revisions, revisions2)
1080
def test_get_view_revisions_reverse(self):
1081
"""Test the get_view_revisions with reverse"""
1082
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1084
self.addCleanup(wt.unlock)
1085
revisions = list(log.get_view_revisions(
1086
mainline_revs, rev_nos, wt.branch, 'reverse'))
1087
self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
1089
revisions2 = list(log.get_view_revisions(
1090
mainline_revs, rev_nos, wt.branch, 'reverse',
1091
include_merges=False))
1092
self.assertEqual(revisions, revisions2)
1094
def test_get_view_revisions_merge(self):
1095
"""Test get_view_revisions when there are merges"""
1096
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
1098
self.addCleanup(wt.unlock)
1099
revisions = list(log.get_view_revisions(
1100
mainline_revs, rev_nos, wt.branch, 'forward'))
1101
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
1102
('4b', '4', 0), ('4a', '3.1.1', 1)],
1104
revisions = list(log.get_view_revisions(
1105
mainline_revs, rev_nos, wt.branch, 'forward',
1106
include_merges=False))
1107
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
1111
def test_get_view_revisions_merge_reverse(self):
1112
"""Test get_view_revisions in reverse when there are merges"""
1113
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
1115
self.addCleanup(wt.unlock)
1116
revisions = list(log.get_view_revisions(
1117
mainline_revs, rev_nos, wt.branch, 'reverse'))
1118
self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
1119
('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
1121
revisions = list(log.get_view_revisions(
1122
mainline_revs, rev_nos, wt.branch, 'reverse',
1123
include_merges=False))
1124
self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
1128
def test_get_view_revisions_merge2(self):
1129
"""Test get_view_revisions when there are merges"""
1130
mainline_revs, rev_nos, b = self.make_branch_with_many_merges()
1132
self.addCleanup(b.unlock)
1133
revisions = list(log.get_view_revisions(
1134
mainline_revs, rev_nos, b, 'forward'))
1135
expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
1136
('3b', '2.2.1', 1), ('3a', '2.1.1', 2), ('4b', '4', 0),
1138
self.assertEqual(expected, revisions)
1139
revisions = list(log.get_view_revisions(
1140
mainline_revs, rev_nos, b, 'forward',
1141
include_merges=False))
1142
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
1147
def test_file_id_for_range(self):
1148
mainline_revs, rev_nos, b = self.make_branch_with_many_merges()
1150
self.addCleanup(b.unlock)
1152
def rev_from_rev_id(revid, branch):
1153
revspec = revisionspec.RevisionSpec.from_string('revid:%s' % revid)
1154
return revspec.in_history(branch)
1156
def view_revs(start_rev, end_rev, file_id, direction):
1157
revs = log.calculate_view_revisions(
1159
start_rev, # start_revision
1160
end_rev, # end_revision
1161
direction, # direction
1162
file_id, # specific_fileid
1163
True, # generate_merge_revisions
1167
rev_3a = rev_from_rev_id('3a', b)
1168
rev_4b = rev_from_rev_id('4b', b)
1169
self.assertEqual([('3c', '3', 0), ('3b', '2.2.1', 1), ('3a', '2.1.1', 2)],
1170
view_revs(rev_3a, rev_4b, 'f-id', 'reverse'))
1171
# Note: 3c still appears before 3a here because of depth-based sorting
1172
self.assertEqual([('3c', '3', 0), ('3b', '2.2.1', 1), ('3a', '2.1.1', 2)],
1173
view_revs(rev_3a, rev_4b, 'f-id', 'forward'))
1176
class TestGetRevisionsTouchingFileID(tests.TestCaseWithTransport):
1178
def create_tree_with_single_merge(self):
1179
"""Create a branch with a moderate layout.
1181
The revision graph looks like:
1189
In this graph, A introduced files f1 and f2 and f3.
1190
B modifies f1 and f3, and C modifies f2 and f3.
1191
D merges the changes from B and C and resolves the conflict for f3.
1193
# TODO: jam 20070218 This seems like it could really be done
1194
# with make_branch_and_memory_tree() if we could just
1195
# create the content of those files.
1196
# TODO: jam 20070218 Another alternative is that we would really
1197
# like to only create this tree 1 time for all tests that
1198
# use it. Since 'log' only uses the tree in a readonly
1199
# fashion, it seems a shame to regenerate an identical
1200
# tree for each test.
1201
tree = self.make_branch_and_tree('tree')
1203
self.addCleanup(tree.unlock)
1205
self.build_tree_contents([('tree/f1', 'A\n'),
1209
tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
1210
tree.commit('A', rev_id='A')
1212
self.build_tree_contents([('tree/f2', 'A\nC\n'),
1213
('tree/f3', 'A\nC\n'),
1215
tree.commit('C', rev_id='C')
1216
# Revert back to A to build the other history.
1217
tree.set_last_revision('A')
1218
tree.branch.set_last_revision_info(1, 'A')
1219
self.build_tree_contents([('tree/f1', 'A\nB\n'),
1221
('tree/f3', 'A\nB\n'),
1223
tree.commit('B', rev_id='B')
1224
tree.set_parent_ids(['B', 'C'])
1225
self.build_tree_contents([('tree/f1', 'A\nB\n'),
1226
('tree/f2', 'A\nC\n'),
1227
('tree/f3', 'A\nB\nC\n'),
1229
tree.commit('D', rev_id='D')
1231
# Switch to a read lock for this tree.
1232
# We still have an addCleanup(tree.unlock) pending
1237
def check_delta(self, delta, **kw):
1238
"""Check the filenames touched by a delta are as expected.
1240
Caller only have to pass in the list of files for each part, all
1241
unspecified parts are considered empty (and checked as such).
1243
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
1244
# By default we expect an empty list
1245
expected = kw.get(n, [])
1246
# strip out only the path components
1247
got = [x[0] for x in getattr(delta, n)]
1248
self.assertEqual(expected, got)
1250
def test_tree_with_single_merge(self):
1251
"""Make sure the tree layout is correct."""
1252
tree = self.create_tree_with_single_merge()
1253
rev_A_tree = tree.branch.repository.revision_tree('A')
1254
rev_B_tree = tree.branch.repository.revision_tree('B')
1255
rev_C_tree = tree.branch.repository.revision_tree('C')
1256
rev_D_tree = tree.branch.repository.revision_tree('D')
1258
self.check_delta(rev_B_tree.changes_from(rev_A_tree),
1259
modified=['f1', 'f3'])
1261
self.check_delta(rev_C_tree.changes_from(rev_A_tree),
1262
modified=['f2', 'f3'])
1264
self.check_delta(rev_D_tree.changes_from(rev_B_tree),
1265
modified=['f2', 'f3'])
1267
self.check_delta(rev_D_tree.changes_from(rev_C_tree),
1268
modified=['f1', 'f3'])
1270
def assertAllRevisionsForFileID(self, tree, file_id, revisions):
1271
"""Ensure _filter_revisions_touching_file_id returns the right values.
1273
Get the return value from _filter_revisions_touching_file_id and make
1274
sure they are correct.
1276
# The api for _filter_revisions_touching_file_id is a little crazy.
1277
# So we do the setup here.
1278
mainline = tree.branch.revision_history()
1279
mainline.insert(0, None)
1280
revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
1281
view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
1283
actual_revs = log._filter_revisions_touching_file_id(
1286
list(view_revs_iter))
1287
self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
1289
def test_file_id_f1(self):
1290
tree = self.create_tree_with_single_merge()
1291
# f1 should be marked as modified by revisions A and B
1292
self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
1294
def test_file_id_f2(self):
1295
tree = self.create_tree_with_single_merge()
1296
# f2 should be marked as modified by revisions A, C, and D
1297
# because D merged the changes from C.
1298
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1300
def test_file_id_f3(self):
1301
tree = self.create_tree_with_single_merge()
1302
# f3 should be marked as modified by revisions A, B, C, and D
1303
self.assertAllRevisionsForFileID(tree, 'f3-id', ['D', 'C', 'B', 'A'])
1305
def test_file_id_with_ghosts(self):
1306
# This is testing bug #209948, where having a ghost would cause
1307
# _filter_revisions_touching_file_id() to fail.
1308
tree = self.create_tree_with_single_merge()
1309
# We need to add a revision, so switch back to a write-locked tree
1310
# (still a single addCleanup(tree.unlock) pending).
1313
first_parent = tree.last_revision()
1314
tree.set_parent_ids([first_parent, 'ghost-revision-id'])
1315
self.build_tree_contents([('tree/f1', 'A\nB\nXX\n')])
1316
tree.commit('commit with a ghost', rev_id='XX')
1317
self.assertAllRevisionsForFileID(tree, 'f1-id', ['XX', 'B', 'A'])
1318
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1320
def test_unknown_file_id(self):
1321
tree = self.create_tree_with_single_merge()
1322
self.assertAllRevisionsForFileID(tree, 'unknown', [])
1324
def test_empty_branch_unknown_file_id(self):
1325
tree = self.make_branch_and_tree('tree')
1326
self.assertAllRevisionsForFileID(tree, 'unknown', [])
1329
class TestShowChangedRevisions(tests.TestCaseWithTransport):
1331
def test_show_changed_revisions_verbose(self):
1332
tree = self.make_branch_and_tree('tree_a')
1333
self.build_tree(['tree_a/foo'])
1335
tree.commit('bar', rev_id='bar-id')
1336
s = self.make_utf8_encoded_stringio()
1337
log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
1338
self.assertContainsRe(s.getvalue(), 'bar')
1339
self.assertNotContainsRe(s.getvalue(), 'foo')
1342
class TestLogFormatter(tests.TestCase):
1344
def test_short_committer(self):
1345
rev = revision.Revision('a-id')
1346
rev.committer = 'John Doe <jdoe@example.com>'
1347
lf = log.LogFormatter(None)
1348
self.assertEqual('John Doe', lf.short_committer(rev))
1349
rev.committer = 'John Smith <jsmith@example.com>'
1350
self.assertEqual('John Smith', lf.short_committer(rev))
1351
rev.committer = 'John Smith'
1352
self.assertEqual('John Smith', lf.short_committer(rev))
1353
rev.committer = 'jsmith@example.com'
1354
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1355
rev.committer = '<jsmith@example.com>'
1356
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1357
rev.committer = 'John Smith jsmith@example.com'
1358
self.assertEqual('John Smith', lf.short_committer(rev))
1360
def test_short_author(self):
1361
rev = revision.Revision('a-id')
1362
rev.committer = 'John Doe <jdoe@example.com>'
1363
lf = log.LogFormatter(None)
1364
self.assertEqual('John Doe', lf.short_author(rev))
1365
rev.properties['author'] = 'John Smith <jsmith@example.com>'
1366
self.assertEqual('John Smith', lf.short_author(rev))
1367
rev.properties['author'] = 'John Smith'
1368
self.assertEqual('John Smith', lf.short_author(rev))
1369
rev.properties['author'] = 'jsmith@example.com'
1370
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1371
rev.properties['author'] = '<jsmith@example.com>'
1372
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1373
rev.properties['author'] = 'John Smith jsmith@example.com'
1374
self.assertEqual('John Smith', lf.short_author(rev))
1375
del rev.properties['author']
1376
rev.properties['authors'] = ('John Smith <jsmith@example.com>\n'
1377
'Jane Rey <jrey@example.com>')
1378
self.assertEqual('John Smith', lf.short_author(rev))
1381
class TestReverseByDepth(tests.TestCase):
1382
"""Test reverse_by_depth behavior.
1384
This is used to present revisions in forward (oldest first) order in a nice
1387
The tests use lighter revision description to ease reading.
1390
def assertReversed(self, forward, backward):
1391
# Transform the descriptions to suit the API: tests use (revno, depth),
1392
# while the API expects (revid, revno, depth)
1393
def complete_revisions(l):
1394
"""Transform the description to suit the API.
1396
Tests use (revno, depth) whil the API expects (revid, revno, depth).
1397
Since the revid is arbitrary, we just duplicate revno
1399
return [ (r, r, d) for r, d in l]
1400
forward = complete_revisions(forward)
1401
backward= complete_revisions(backward)
1402
self.assertEqual(forward, log.reverse_by_depth(backward))
1405
def test_mainline_revisions(self):
1406
self.assertReversed([( '1', 0), ('2', 0)],
1407
[('2', 0), ('1', 0)])
1409
def test_merged_revisions(self):
1410
self.assertReversed([('1', 0), ('2', 0), ('2.2', 1), ('2.1', 1),],
1411
[('2', 0), ('2.1', 1), ('2.2', 1), ('1', 0),])
1412
def test_shifted_merged_revisions(self):
1413
"""Test irregular layout.
1415
Requesting revisions touching a file can produce "holes" in the depths.
1417
self.assertReversed([('1', 0), ('2', 0), ('1.1', 2), ('1.2', 2),],
1418
[('2', 0), ('1.2', 2), ('1.1', 2), ('1', 0),])
1420
def test_merged_without_child_revisions(self):
1421
"""Test irregular layout.
1423
Revision ranges can produce "holes" in the depths.
1425
# When a revision of higher depth doesn't follow one of lower depth, we
1426
# assume a lower depth one is virtually there
1427
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1428
[('4', 4), ('3', 3), ('2', 2), ('1', 2),])
1429
# So we get the same order after reversing below even if the original
1430
# revisions are not in the same order.
1431
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1432
[('3', 3), ('4', 4), ('2', 2), ('1', 2),])
1435
class TestHistoryChange(tests.TestCaseWithTransport):
1437
def setup_a_tree(self):
1438
tree = self.make_branch_and_tree('tree')
1440
self.addCleanup(tree.unlock)
1441
tree.commit('1a', rev_id='1a')
1442
tree.commit('2a', rev_id='2a')
1443
tree.commit('3a', rev_id='3a')
1446
def setup_ab_tree(self):
1447
tree = self.setup_a_tree()
1448
tree.set_last_revision('1a')
1449
tree.branch.set_last_revision_info(1, '1a')
1450
tree.commit('2b', rev_id='2b')
1451
tree.commit('3b', rev_id='3b')
1454
def setup_ac_tree(self):
1455
tree = self.setup_a_tree()
1456
tree.set_last_revision(revision.NULL_REVISION)
1457
tree.branch.set_last_revision_info(0, revision.NULL_REVISION)
1458
tree.commit('1c', rev_id='1c')
1459
tree.commit('2c', rev_id='2c')
1460
tree.commit('3c', rev_id='3c')
1463
def test_all_new(self):
1464
tree = self.setup_ab_tree()
1465
old, new = log.get_history_change('1a', '3a', tree.branch.repository)
1466
self.assertEqual([], old)
1467
self.assertEqual(['2a', '3a'], new)
1469
def test_all_old(self):
1470
tree = self.setup_ab_tree()
1471
old, new = log.get_history_change('3a', '1a', tree.branch.repository)
1472
self.assertEqual([], new)
1473
self.assertEqual(['2a', '3a'], old)
1475
def test_null_old(self):
1476
tree = self.setup_ab_tree()
1477
old, new = log.get_history_change(revision.NULL_REVISION,
1478
'3a', tree.branch.repository)
1479
self.assertEqual([], old)
1480
self.assertEqual(['1a', '2a', '3a'], new)
1482
def test_null_new(self):
1483
tree = self.setup_ab_tree()
1484
old, new = log.get_history_change('3a', revision.NULL_REVISION,
1485
tree.branch.repository)
1486
self.assertEqual([], new)
1487
self.assertEqual(['1a', '2a', '3a'], old)
1489
def test_diverged(self):
1490
tree = self.setup_ab_tree()
1491
old, new = log.get_history_change('3a', '3b', tree.branch.repository)
1492
self.assertEqual(old, ['2a', '3a'])
1493
self.assertEqual(new, ['2b', '3b'])
1495
def test_unrelated(self):
1496
tree = self.setup_ac_tree()
1497
old, new = log.get_history_change('3a', '3c', tree.branch.repository)
1498
self.assertEqual(old, ['1a', '2a', '3a'])
1499
self.assertEqual(new, ['1c', '2c', '3c'])
1501
def test_show_branch_change(self):
1502
tree = self.setup_ab_tree()
1504
log.show_branch_change(tree.branch, s, 3, '3a')
1505
self.assertContainsRe(s.getvalue(),
1506
'[*]{60}\nRemoved Revisions:\n(.|\n)*2a(.|\n)*3a(.|\n)*'
1507
'[*]{60}\n\nAdded Revisions:\n(.|\n)*2b(.|\n)*3b')
1509
def test_show_branch_change_no_change(self):
1510
tree = self.setup_ab_tree()
1512
log.show_branch_change(tree.branch, s, 3, '3b')
1513
self.assertEqual(s.getvalue(),
1514
'Nothing seems to have changed\n')
1516
def test_show_branch_change_no_old(self):
1517
tree = self.setup_ab_tree()
1519
log.show_branch_change(tree.branch, s, 2, '2b')
1520
self.assertContainsRe(s.getvalue(), 'Added Revisions:')
1521
self.assertNotContainsRe(s.getvalue(), 'Removed Revisions:')
1523
def test_show_branch_change_no_new(self):
1524
tree = self.setup_ab_tree()
1525
tree.branch.set_last_revision_info(2, '2b')
1527
log.show_branch_change(tree.branch, s, 3, '3b')
1528
self.assertContainsRe(s.getvalue(), 'Removed Revisions:')
1529
self.assertNotContainsRe(s.getvalue(), 'Added Revisions:')
1533
class TestLogWithBugs(TestCaseForLogFormatter):
1536
TestCaseForLogFormatter.setUp(self)
1537
log.properties_handler_registry.register(
1538
'bugs_properties_handler',
1539
log._bugs_properties_handler)
1541
def make_commits_with_bugs(self):
1542
"""Helper method for LogFormatter tests"""
1543
tree = self.make_branch_and_tree(u'.')
1544
self.build_tree(['a', 'b'])
1546
tree.commit('simple log message', rev_id='a1',
1547
timestamp=1132586655.459960938, timezone=-6*3600,
1548
committer='Joe Foo <joe@foo.com>',
1549
revprops={'bugs': 'test://bug/id fixed'})
1551
tree.commit('multiline\nlog\nmessage\n', rev_id='a2',
1552
timestamp=1132586842.411175966, timezone=-6*3600,
1553
committer='Joe Foo <joe@foo.com>',
1554
authors=['Joe Bar <joe@bar.com>'],
1555
revprops={'bugs': 'test://bug/id fixed\n'
1556
'test://bug/2 fixed'})
1560
def test_long_bugs(self):
1561
tree = self.make_commits_with_bugs()
1562
self.assertFormatterResult("""\
1563
------------------------------------------------------------
1565
fixes bug(s): test://bug/id test://bug/2
1566
author: Joe Bar <joe@bar.com>
1567
committer: Joe Foo <joe@foo.com>
1569
timestamp: Mon 2005-11-21 09:27:22 -0600
1574
------------------------------------------------------------
1576
fixes bug(s): test://bug/id
1577
committer: Joe Foo <joe@foo.com>
1579
timestamp: Mon 2005-11-21 09:24:15 -0600
1583
tree.branch, log.LongLogFormatter)
1585
def test_short_bugs(self):
1586
tree = self.make_commits_with_bugs()
1587
self.assertFormatterResult("""\
1588
2 Joe Bar\t2005-11-21
1589
fixes bug(s): test://bug/id test://bug/2
1594
1 Joe Foo\t2005-11-21
1595
fixes bug(s): test://bug/id
1599
tree.branch, log.ShortLogFormatter)
1601
def test_wrong_bugs_property(self):
1602
tree = self.make_branch_and_tree(u'.')
1603
self.build_tree(['foo'])
1604
tree.commit('simple log message', rev_id='a1',
1605
timestamp=1132586655.459960938, timezone=-6*3600,
1606
committer='Joe Foo <joe@foo.com>',
1607
revprops={'bugs': 'test://bug/id invalid_value'})
1608
self.assertFormatterResult("""\
1609
1 Joe Foo\t2005-11-21
1613
tree.branch, log.ShortLogFormatter)
1615
def test_bugs_handler_present(self):
1616
self.properties_handler_registry.get('bugs_properties_handler')