1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
from cStringIO import StringIO
30
class TestCaseWithoutPropsHandler(tests.TestCaseWithTransport):
33
super(TestCaseWithoutPropsHandler, 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)
44
class LogCatcher(log.LogFormatter):
45
"""Pull log messages into a list rather than displaying them.
47
To simplify testing we save logged revisions here rather than actually
48
formatting anything, so that we can precisely check the result without
49
being dependent on the formatting.
55
super(LogCatcher, self).__init__(to_file=None)
58
def log_revision(self, revision):
59
self.revisions.append(revision)
62
class TestShowLog(tests.TestCaseWithTransport):
64
def checkDelta(self, delta, **kw):
65
"""Check the filenames touched by a delta are as expected.
67
Caller only have to pass in the list of files for each part, all
68
unspecified parts are considered empty (and checked as such).
70
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
71
# By default we expect an empty list
72
expected = kw.get(n, [])
73
# strip out only the path components
74
got = [x[0] for x in getattr(delta, n)]
75
self.assertEqual(expected, got)
77
def assertInvalidRevisonNumber(self, br, start, end):
79
self.assertRaises(errors.InvalidRevisionNumber,
81
start_revision=start, end_revision=end)
83
def test_cur_revno(self):
84
wt = self.make_branch_and_tree('.')
88
wt.commit('empty commit')
89
log.show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
91
# Since there is a single revision in the branch all the combinations
93
self.assertInvalidRevisonNumber(b, 2, 1)
94
self.assertInvalidRevisonNumber(b, 1, 2)
95
self.assertInvalidRevisonNumber(b, 0, 2)
96
self.assertInvalidRevisonNumber(b, 1, 0)
97
self.assertInvalidRevisonNumber(b, -1, 1)
98
self.assertInvalidRevisonNumber(b, 1, -1)
100
def test_empty_branch(self):
101
wt = self.make_branch_and_tree('.')
104
log.show_log(wt.branch, lf)
106
self.assertEqual([], lf.revisions)
108
def test_empty_commit(self):
109
wt = self.make_branch_and_tree('.')
111
wt.commit('empty commit')
113
log.show_log(wt.branch, lf, verbose=True)
115
self.assertEqual(1, len(revs))
116
self.assertEqual('1', revs[0].revno)
117
self.assertEqual('empty commit', revs[0].rev.message)
118
self.checkDelta(revs[0].delta)
120
def test_simple_commit(self):
121
wt = self.make_branch_and_tree('.')
122
wt.commit('empty commit')
123
self.build_tree(['hello'])
125
wt.commit('add one file',
126
committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
127
u'<test@example.com>')
129
log.show_log(wt.branch, lf, verbose=True)
130
self.assertEqual(2, len(lf.revisions))
131
# first one is most recent
132
log_entry = lf.revisions[0]
133
self.assertEqual('2', log_entry.revno)
134
self.assertEqual('add one file', log_entry.rev.message)
135
self.checkDelta(log_entry.delta, added=['hello'])
137
def test_commit_message_with_control_chars(self):
138
wt = self.make_branch_and_tree('.')
139
msg = u"All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
140
msg = msg.replace(u'\r', u'\n')
143
log.show_log(wt.branch, lf, verbose=True)
144
committed_msg = lf.revisions[0].rev.message
145
if msg == committed_msg:
146
raise tests.KnownFailure(
147
"Commit message was preserved, but it wasn't expected to be.")
148
self.assertNotEqual(msg, committed_msg)
149
self.assertTrue(len(committed_msg) > len(msg))
151
def test_commit_message_without_control_chars(self):
152
wt = self.make_branch_and_tree('.')
153
# escaped. As ElementTree apparently does some kind of
154
# newline conversion, neither LF (\x0A) nor CR (\x0D) are
155
# included in the test commit message, even though they are
156
# valid XML 1.0 characters.
157
msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
160
log.show_log(wt.branch, lf, verbose=True)
161
committed_msg = lf.revisions[0].rev.message
162
self.assertEqual(msg, committed_msg)
164
def test_deltas_in_merge_revisions(self):
165
"""Check deltas created for both mainline and merge revisions"""
166
wt = self.make_branch_and_tree('parent')
167
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
170
wt.commit(message='add file1 and file2')
171
self.run_bzr('branch parent child')
172
os.unlink('child/file1')
173
file('child/file2', 'wb').write('hello\n')
174
self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
177
self.run_bzr('merge ../child')
178
wt.commit('merge child branch')
182
lf.supports_merge_revisions = True
183
log.show_log(b, lf, verbose=True)
186
self.assertEqual(3, len(revs))
189
self.assertEqual('2', logentry.revno)
190
self.assertEqual('merge child branch', logentry.rev.message)
191
self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
194
self.assertEqual('1.1.1', logentry.revno)
195
self.assertEqual('remove file1 and modify file2', logentry.rev.message)
196
self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
199
self.assertEqual('1', logentry.revno)
200
self.assertEqual('add file1 and file2', logentry.rev.message)
201
self.checkDelta(logentry.delta, added=['file1', 'file2'])
204
def make_commits_with_trailing_newlines(wt):
205
"""Helper method for LogFormatter tests"""
208
open('a', 'wb').write('hello moto\n')
210
wt.commit('simple log message', rev_id='a1',
211
timestamp=1132586655.459960938, timezone=-6*3600,
212
committer='Joe Foo <joe@foo.com>')
213
open('b', 'wb').write('goodbye\n')
215
wt.commit('multiline\nlog\nmessage\n', rev_id='a2',
216
timestamp=1132586842.411175966, timezone=-6*3600,
217
committer='Joe Foo <joe@foo.com>',
218
authors=['Joe Bar <joe@bar.com>'])
220
open('c', 'wb').write('just another manic monday\n')
222
wt.commit('single line with trailing newline\n', rev_id='a3',
223
timestamp=1132587176.835228920, timezone=-6*3600,
224
committer = 'Joe Foo <joe@foo.com>')
228
def normalize_log(log):
229
"""Replaces the variable lines of logs with fixed lines"""
230
author = 'author: Dolor Sit <test@example.com>'
231
committer = 'committer: Lorem Ipsum <test@example.com>'
232
lines = log.splitlines(True)
233
for idx,line in enumerate(lines):
234
stripped_line = line.lstrip()
235
indent = ' ' * (len(line) - len(stripped_line))
236
if stripped_line.startswith('author:'):
237
lines[idx] = indent + author + '\n'
238
elif stripped_line.startswith('committer:'):
239
lines[idx] = indent + committer + '\n'
240
elif stripped_line.startswith('timestamp:'):
241
lines[idx] = indent + 'timestamp: Just now\n'
242
return ''.join(lines)
245
class TestShortLogFormatter(tests.TestCaseWithTransport):
247
def test_trailing_newlines(self):
248
wt = self.make_branch_and_tree('.')
249
b = make_commits_with_trailing_newlines(wt)
250
sio = self.make_utf8_encoded_stringio()
251
lf = log.ShortLogFormatter(to_file=sio)
253
self.assertEqualDiff("""\
254
3 Joe Foo\t2005-11-21
255
single line with trailing newline
257
2 Joe Bar\t2005-11-21
262
1 Joe Foo\t2005-11-21
268
def _prepare_tree_with_merges(self, with_tags=False):
269
wt = self.make_branch_and_memory_tree('.')
271
self.addCleanup(wt.unlock)
273
wt.commit('rev-1', rev_id='rev-1',
274
timestamp=1132586655, timezone=36000,
275
committer='Joe Foo <joe@foo.com>')
276
wt.commit('rev-merged', rev_id='rev-2a',
277
timestamp=1132586700, timezone=36000,
278
committer='Joe Foo <joe@foo.com>')
279
wt.set_parent_ids(['rev-1', 'rev-2a'])
280
wt.branch.set_last_revision_info(1, 'rev-1')
281
wt.commit('rev-2', rev_id='rev-2b',
282
timestamp=1132586800, timezone=36000,
283
committer='Joe Foo <joe@foo.com>')
286
branch.tags.set_tag('v0.2', 'rev-2b')
287
wt.commit('rev-3', rev_id='rev-3',
288
timestamp=1132586900, timezone=36000,
289
committer='Jane Foo <jane@foo.com>')
290
branch.tags.set_tag('v1.0rc1', 'rev-3')
291
branch.tags.set_tag('v1.0', 'rev-3')
294
def test_short_log_with_merges(self):
295
wt = self._prepare_tree_with_merges()
296
logfile = self.make_utf8_encoded_stringio()
297
formatter = log.ShortLogFormatter(to_file=logfile)
298
log.show_log(wt.branch, formatter)
299
self.assertEqualDiff("""\
300
2 Joe Foo\t2005-11-22 [merge]
303
1 Joe Foo\t2005-11-22
309
def test_short_log_with_merges_and_advice(self):
310
wt = self._prepare_tree_with_merges()
311
logfile = self.make_utf8_encoded_stringio()
312
formatter = log.ShortLogFormatter(to_file=logfile,
314
log.show_log(wt.branch, formatter)
315
self.assertEqualDiff("""\
316
2 Joe Foo\t2005-11-22 [merge]
319
1 Joe Foo\t2005-11-22
322
Use --include-merges or -n0 to see merged revisions.
326
def test_short_log_with_merges_and_range(self):
327
wt = self.make_branch_and_memory_tree('.')
329
self.addCleanup(wt.unlock)
331
wt.commit('rev-1', rev_id='rev-1',
332
timestamp=1132586655, timezone=36000,
333
committer='Joe Foo <joe@foo.com>')
334
wt.commit('rev-merged', rev_id='rev-2a',
335
timestamp=1132586700, timezone=36000,
336
committer='Joe Foo <joe@foo.com>')
337
wt.branch.set_last_revision_info(1, 'rev-1')
338
wt.set_parent_ids(['rev-1', 'rev-2a'])
339
wt.commit('rev-2b', rev_id='rev-2b',
340
timestamp=1132586800, timezone=36000,
341
committer='Joe Foo <joe@foo.com>')
342
wt.commit('rev-3a', rev_id='rev-3a',
343
timestamp=1132586800, timezone=36000,
344
committer='Joe Foo <joe@foo.com>')
345
wt.branch.set_last_revision_info(2, 'rev-2b')
346
wt.set_parent_ids(['rev-2b', 'rev-3a'])
347
wt.commit('rev-3b', rev_id='rev-3b',
348
timestamp=1132586800, timezone=36000,
349
committer='Joe Foo <joe@foo.com>')
350
logfile = self.make_utf8_encoded_stringio()
351
formatter = log.ShortLogFormatter(to_file=logfile)
352
log.show_log(wt.branch, formatter,
353
start_revision=2, end_revision=3)
354
self.assertEqualDiff("""\
355
3 Joe Foo\t2005-11-22 [merge]
358
2 Joe Foo\t2005-11-22 [merge]
364
def test_short_log_with_tags(self):
365
wt = self._prepare_tree_with_merges(with_tags=True)
366
logfile = self.make_utf8_encoded_stringio()
367
formatter = log.ShortLogFormatter(to_file=logfile)
368
log.show_log(wt.branch, formatter)
369
self.assertEqualDiff("""\
370
3 Jane Foo\t2005-11-22 {v1.0, v1.0rc1}
373
2 Joe Foo\t2005-11-22 {v0.2} [merge]
376
1 Joe Foo\t2005-11-22
382
def test_short_log_single_merge_revision(self):
383
wt = self.make_branch_and_memory_tree('.')
385
self.addCleanup(wt.unlock)
387
wt.commit('rev-1', rev_id='rev-1',
388
timestamp=1132586655, timezone=36000,
389
committer='Joe Foo <joe@foo.com>')
390
wt.commit('rev-merged', rev_id='rev-2a',
391
timestamp=1132586700, timezone=36000,
392
committer='Joe Foo <joe@foo.com>')
393
wt.set_parent_ids(['rev-1', 'rev-2a'])
394
wt.branch.set_last_revision_info(1, 'rev-1')
395
wt.commit('rev-2', rev_id='rev-2b',
396
timestamp=1132586800, timezone=36000,
397
committer='Joe Foo <joe@foo.com>')
398
logfile = self.make_utf8_encoded_stringio()
399
formatter = log.ShortLogFormatter(to_file=logfile)
400
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
402
rev = revspec.in_history(wtb)
403
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
404
self.assertEqualDiff("""\
405
1.1.1 Joe Foo\t2005-11-22
412
class TestShortLogFormatterWithMergeRevisions(tests.TestCaseWithTransport):
414
def test_short_merge_revs_log_with_merges(self):
415
wt = self.make_branch_and_memory_tree('.')
417
self.addCleanup(wt.unlock)
419
wt.commit('rev-1', rev_id='rev-1',
420
timestamp=1132586655, timezone=36000,
421
committer='Joe Foo <joe@foo.com>')
422
wt.commit('rev-merged', rev_id='rev-2a',
423
timestamp=1132586700, timezone=36000,
424
committer='Joe Foo <joe@foo.com>')
425
wt.set_parent_ids(['rev-1', 'rev-2a'])
426
wt.branch.set_last_revision_info(1, 'rev-1')
427
wt.commit('rev-2', rev_id='rev-2b',
428
timestamp=1132586800, timezone=36000,
429
committer='Joe Foo <joe@foo.com>')
430
logfile = self.make_utf8_encoded_stringio()
431
formatter = log.ShortLogFormatter(to_file=logfile, levels=0)
432
log.show_log(wt.branch, formatter)
433
# Note that the 1.1.1 indenting is in fact correct given that
434
# the revision numbers are right justified within 5 characters
435
# for mainline revnos and 9 characters for dotted revnos.
436
self.assertEqualDiff("""\
437
2 Joe Foo\t2005-11-22 [merge]
440
1.1.1 Joe Foo\t2005-11-22
443
1 Joe Foo\t2005-11-22
449
def test_short_merge_revs_log_single_merge_revision(self):
450
wt = self.make_branch_and_memory_tree('.')
452
self.addCleanup(wt.unlock)
454
wt.commit('rev-1', rev_id='rev-1',
455
timestamp=1132586655, timezone=36000,
456
committer='Joe Foo <joe@foo.com>')
457
wt.commit('rev-merged', rev_id='rev-2a',
458
timestamp=1132586700, timezone=36000,
459
committer='Joe Foo <joe@foo.com>')
460
wt.set_parent_ids(['rev-1', 'rev-2a'])
461
wt.branch.set_last_revision_info(1, 'rev-1')
462
wt.commit('rev-2', rev_id='rev-2b',
463
timestamp=1132586800, timezone=36000,
464
committer='Joe Foo <joe@foo.com>')
465
logfile = self.make_utf8_encoded_stringio()
466
formatter = log.ShortLogFormatter(to_file=logfile, levels=0)
467
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
469
rev = revspec.in_history(wtb)
470
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
471
self.assertEqualDiff("""\
472
1.1.1 Joe Foo\t2005-11-22
479
class TestLongLogFormatter(TestCaseWithoutPropsHandler):
481
def test_verbose_log(self):
482
"""Verbose log includes changed files
486
wt = self.make_branch_and_tree('.')
488
self.build_tree(['a'])
490
# XXX: why does a longer nick show up?
491
b.nick = 'test_verbose_log'
492
wt.commit(message='add a',
493
timestamp=1132711707,
495
committer='Lorem Ipsum <test@example.com>')
496
logfile = file('out.tmp', 'w+')
497
formatter = log.LongLogFormatter(to_file=logfile)
498
log.show_log(b, formatter, verbose=True)
501
log_contents = logfile.read()
502
self.assertEqualDiff('''\
503
------------------------------------------------------------
505
committer: Lorem Ipsum <test@example.com>
506
branch nick: test_verbose_log
507
timestamp: Wed 2005-11-23 12:08:27 +1000
515
def test_merges_are_indented_by_level(self):
516
wt = self.make_branch_and_tree('parent')
517
wt.commit('first post')
518
self.run_bzr('branch parent child')
519
self.run_bzr(['commit', '-m', 'branch 1', '--unchanged', 'child'])
520
self.run_bzr('branch child smallerchild')
521
self.run_bzr(['commit', '-m', 'branch 2', '--unchanged',
524
self.run_bzr('merge ../smallerchild')
525
self.run_bzr(['commit', '-m', 'merge branch 2'])
526
os.chdir('../parent')
527
self.run_bzr('merge ../child')
528
wt.commit('merge branch 1')
530
sio = self.make_utf8_encoded_stringio()
531
lf = log.LongLogFormatter(to_file=sio, levels=0)
532
log.show_log(b, lf, verbose=True)
533
the_log = normalize_log(sio.getvalue())
534
self.assertEqualDiff("""\
535
------------------------------------------------------------
537
committer: Lorem Ipsum <test@example.com>
542
------------------------------------------------------------
544
committer: Lorem Ipsum <test@example.com>
549
------------------------------------------------------------
551
committer: Lorem Ipsum <test@example.com>
552
branch nick: smallerchild
556
------------------------------------------------------------
558
committer: Lorem Ipsum <test@example.com>
563
------------------------------------------------------------
565
committer: Lorem Ipsum <test@example.com>
573
def test_verbose_merge_revisions_contain_deltas(self):
574
wt = self.make_branch_and_tree('parent')
575
self.build_tree(['parent/f1', 'parent/f2'])
577
wt.commit('first post')
578
self.run_bzr('branch parent child')
579
os.unlink('child/f1')
580
file('child/f2', 'wb').write('hello\n')
581
self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
584
self.run_bzr('merge ../child')
585
wt.commit('merge branch 1')
587
sio = self.make_utf8_encoded_stringio()
588
lf = log.LongLogFormatter(to_file=sio, levels=0)
589
log.show_log(b, lf, verbose=True)
590
the_log = normalize_log(sio.getvalue())
591
self.assertEqualDiff("""\
592
------------------------------------------------------------
594
committer: Lorem Ipsum <test@example.com>
603
------------------------------------------------------------
605
committer: Lorem Ipsum <test@example.com>
609
removed f1 and modified f2
614
------------------------------------------------------------
616
committer: Lorem Ipsum <test@example.com>
627
def test_trailing_newlines(self):
628
wt = self.make_branch_and_tree('.')
629
b = make_commits_with_trailing_newlines(wt)
630
sio = self.make_utf8_encoded_stringio()
631
lf = log.LongLogFormatter(to_file=sio)
633
self.assertEqualDiff("""\
634
------------------------------------------------------------
636
committer: Joe Foo <joe@foo.com>
638
timestamp: Mon 2005-11-21 09:32:56 -0600
640
single line with trailing newline
641
------------------------------------------------------------
643
author: Joe Bar <joe@bar.com>
644
committer: Joe Foo <joe@foo.com>
646
timestamp: Mon 2005-11-21 09:27:22 -0600
651
------------------------------------------------------------
653
committer: Joe Foo <joe@foo.com>
655
timestamp: Mon 2005-11-21 09:24:15 -0600
661
def test_author_in_log(self):
662
"""Log includes the author name if it's set in
663
the revision properties
665
wt = self.make_branch_and_tree('.')
667
self.build_tree(['a'])
669
b.nick = 'test_author_log'
670
wt.commit(message='add a',
671
timestamp=1132711707,
673
committer='Lorem Ipsum <test@example.com>',
674
authors=['John Doe <jdoe@example.com>',
675
'Jane Rey <jrey@example.com>'])
677
formatter = log.LongLogFormatter(to_file=sio)
678
log.show_log(b, formatter)
679
self.assertEqualDiff('''\
680
------------------------------------------------------------
682
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
683
committer: Lorem Ipsum <test@example.com>
684
branch nick: test_author_log
685
timestamp: Wed 2005-11-23 12:08:27 +1000
691
def test_properties_in_log(self):
692
"""Log includes the custom properties returned by the registered
695
wt = self.make_branch_and_tree('.')
697
self.build_tree(['a'])
699
b.nick = 'test_properties_in_log'
700
wt.commit(message='add a',
701
timestamp=1132711707,
703
committer='Lorem Ipsum <test@example.com>',
704
authors=['John Doe <jdoe@example.com>'])
706
formatter = log.LongLogFormatter(to_file=sio)
708
def trivial_custom_prop_handler(revision):
709
return {'test_prop':'test_value'}
711
log.properties_handler_registry.register(
712
'trivial_custom_prop_handler',
713
trivial_custom_prop_handler)
714
log.show_log(b, formatter)
716
log.properties_handler_registry.remove(
717
'trivial_custom_prop_handler')
718
self.assertEqualDiff('''\
719
------------------------------------------------------------
721
test_prop: test_value
722
author: John Doe <jdoe@example.com>
723
committer: Lorem Ipsum <test@example.com>
724
branch nick: test_properties_in_log
725
timestamp: Wed 2005-11-23 12:08:27 +1000
731
def test_properties_in_short_log(self):
732
"""Log includes the custom properties returned by the registered
735
wt = self.make_branch_and_tree('.')
737
self.build_tree(['a'])
739
b.nick = 'test_properties_in_short_log'
740
wt.commit(message='add a',
741
timestamp=1132711707,
743
committer='Lorem Ipsum <test@example.com>',
744
authors=['John Doe <jdoe@example.com>'])
746
formatter = log.ShortLogFormatter(to_file=sio)
748
def trivial_custom_prop_handler(revision):
749
return {'test_prop':'test_value'}
751
log.properties_handler_registry.register(
752
'trivial_custom_prop_handler',
753
trivial_custom_prop_handler)
754
log.show_log(b, formatter)
756
log.properties_handler_registry.remove(
757
'trivial_custom_prop_handler')
758
self.assertEqualDiff('''\
759
1 John Doe\t2005-11-23
760
test_prop: test_value
766
def test_error_in_properties_handler(self):
767
"""Log includes the custom properties returned by the registered
770
wt = self.make_branch_and_tree('.')
772
self.build_tree(['a'])
774
b.nick = 'test_author_log'
775
wt.commit(message='add a',
776
timestamp=1132711707,
778
committer='Lorem Ipsum <test@example.com>',
779
authors=['John Doe <jdoe@example.com>'],
780
revprops={'first_prop':'first_value'})
782
formatter = log.LongLogFormatter(to_file=sio)
784
def trivial_custom_prop_handler(revision):
785
raise StandardError("a test error")
787
log.properties_handler_registry.register(
788
'trivial_custom_prop_handler',
789
trivial_custom_prop_handler)
790
self.assertRaises(StandardError, log.show_log, b, formatter,)
792
log.properties_handler_registry.remove(
793
'trivial_custom_prop_handler')
795
def test_properties_handler_bad_argument(self):
796
wt = self.make_branch_and_tree('.')
798
self.build_tree(['a'])
800
b.nick = 'test_author_log'
801
wt.commit(message='add a',
802
timestamp=1132711707,
804
committer='Lorem Ipsum <test@example.com>',
805
authors=['John Doe <jdoe@example.com>'],
806
revprops={'a_prop':'test_value'})
808
formatter = log.LongLogFormatter(to_file=sio)
810
def bad_argument_prop_handler(revision):
811
return {'custom_prop_name':revision.properties['a_prop']}
813
log.properties_handler_registry.register(
814
'bad_argument_prop_handler',
815
bad_argument_prop_handler)
817
self.assertRaises(AttributeError, formatter.show_properties,
820
revision = b.repository.get_revision(b.last_revision())
821
formatter.show_properties(revision, '')
822
self.assertEqualDiff('''custom_prop_name: test_value\n''',
825
log.properties_handler_registry.remove(
826
'bad_argument_prop_handler')
829
class TestLongLogFormatterWithoutMergeRevisions(TestCaseWithoutPropsHandler):
831
def test_long_verbose_log(self):
832
"""Verbose log includes changed files
836
wt = self.make_branch_and_tree('.')
838
self.build_tree(['a'])
840
# XXX: why does a longer nick show up?
841
b.nick = 'test_verbose_log'
842
wt.commit(message='add a',
843
timestamp=1132711707,
845
committer='Lorem Ipsum <test@example.com>')
846
logfile = file('out.tmp', 'w+')
847
formatter = log.LongLogFormatter(to_file=logfile, levels=1)
848
log.show_log(b, formatter, verbose=True)
851
log_contents = logfile.read()
852
self.assertEqualDiff('''\
853
------------------------------------------------------------
855
committer: Lorem Ipsum <test@example.com>
856
branch nick: test_verbose_log
857
timestamp: Wed 2005-11-23 12:08:27 +1000
865
def test_long_verbose_contain_deltas(self):
866
wt = self.make_branch_and_tree('parent')
867
self.build_tree(['parent/f1', 'parent/f2'])
869
wt.commit('first post')
870
self.run_bzr('branch parent child')
871
os.unlink('child/f1')
872
file('child/f2', 'wb').write('hello\n')
873
self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
876
self.run_bzr('merge ../child')
877
wt.commit('merge branch 1')
879
sio = self.make_utf8_encoded_stringio()
880
lf = log.LongLogFormatter(to_file=sio, levels=1)
881
log.show_log(b, lf, verbose=True)
882
the_log = normalize_log(sio.getvalue())
883
self.assertEqualDiff("""\
884
------------------------------------------------------------
886
committer: Lorem Ipsum <test@example.com>
895
------------------------------------------------------------
897
committer: Lorem Ipsum <test@example.com>
908
def test_long_trailing_newlines(self):
909
wt = self.make_branch_and_tree('.')
910
b = make_commits_with_trailing_newlines(wt)
911
sio = self.make_utf8_encoded_stringio()
912
lf = log.LongLogFormatter(to_file=sio, levels=1)
914
self.assertEqualDiff("""\
915
------------------------------------------------------------
917
committer: Joe Foo <joe@foo.com>
919
timestamp: Mon 2005-11-21 09:32:56 -0600
921
single line with trailing newline
922
------------------------------------------------------------
924
author: Joe Bar <joe@bar.com>
925
committer: Joe Foo <joe@foo.com>
927
timestamp: Mon 2005-11-21 09:27:22 -0600
932
------------------------------------------------------------
934
committer: Joe Foo <joe@foo.com>
936
timestamp: Mon 2005-11-21 09:24:15 -0600
942
def test_long_author_in_log(self):
943
"""Log includes the author name if it's set in
944
the revision properties
946
wt = self.make_branch_and_tree('.')
948
self.build_tree(['a'])
950
b.nick = 'test_author_log'
951
wt.commit(message='add a',
952
timestamp=1132711707,
954
committer='Lorem Ipsum <test@example.com>',
955
authors=['John Doe <jdoe@example.com>'])
957
formatter = log.LongLogFormatter(to_file=sio, levels=1)
958
log.show_log(b, formatter)
959
self.assertEqualDiff('''\
960
------------------------------------------------------------
962
author: John Doe <jdoe@example.com>
963
committer: Lorem Ipsum <test@example.com>
964
branch nick: test_author_log
965
timestamp: Wed 2005-11-23 12:08:27 +1000
971
def test_long_properties_in_log(self):
972
"""Log includes the custom properties returned by the registered
975
wt = self.make_branch_and_tree('.')
977
self.build_tree(['a'])
979
b.nick = 'test_properties_in_log'
980
wt.commit(message='add a',
981
timestamp=1132711707,
983
committer='Lorem Ipsum <test@example.com>',
984
authors=['John Doe <jdoe@example.com>'])
986
formatter = log.LongLogFormatter(to_file=sio, levels=1)
988
def trivial_custom_prop_handler(revision):
989
return {'test_prop':'test_value'}
991
log.properties_handler_registry.register(
992
'trivial_custom_prop_handler',
993
trivial_custom_prop_handler)
994
log.show_log(b, formatter)
996
log.properties_handler_registry.remove(
997
'trivial_custom_prop_handler')
998
self.assertEqualDiff('''\
999
------------------------------------------------------------
1001
test_prop: test_value
1002
author: John Doe <jdoe@example.com>
1003
committer: Lorem Ipsum <test@example.com>
1004
branch nick: test_properties_in_log
1005
timestamp: Wed 2005-11-23 12:08:27 +1000
1012
class TestLineLogFormatter(tests.TestCaseWithTransport):
1014
def test_line_log(self):
1015
"""Line log should show revno
1019
wt = self.make_branch_and_tree('.')
1021
self.build_tree(['a'])
1023
b.nick = 'test-line-log'
1024
wt.commit(message='add a',
1025
timestamp=1132711707,
1027
committer='Line-Log-Formatter Tester <test@line.log>')
1028
logfile = file('out.tmp', 'w+')
1029
formatter = log.LineLogFormatter(to_file=logfile)
1030
log.show_log(b, formatter)
1033
log_contents = logfile.read()
1034
self.assertEqualDiff('1: Line-Log-Formatte... 2005-11-23 add a\n',
1037
def test_trailing_newlines(self):
1038
wt = self.make_branch_and_tree('.')
1039
b = make_commits_with_trailing_newlines(wt)
1040
sio = self.make_utf8_encoded_stringio()
1041
lf = log.LineLogFormatter(to_file=sio)
1043
self.assertEqualDiff("""\
1044
3: Joe Foo 2005-11-21 single line with trailing newline
1045
2: Joe Bar 2005-11-21 multiline
1046
1: Joe Foo 2005-11-21 simple log message
1050
def _prepare_tree_with_merges(self, with_tags=False):
1051
wt = self.make_branch_and_memory_tree('.')
1053
self.addCleanup(wt.unlock)
1055
wt.commit('rev-1', rev_id='rev-1',
1056
timestamp=1132586655, timezone=36000,
1057
committer='Joe Foo <joe@foo.com>')
1058
wt.commit('rev-merged', rev_id='rev-2a',
1059
timestamp=1132586700, timezone=36000,
1060
committer='Joe Foo <joe@foo.com>')
1061
wt.set_parent_ids(['rev-1', 'rev-2a'])
1062
wt.branch.set_last_revision_info(1, 'rev-1')
1063
wt.commit('rev-2', rev_id='rev-2b',
1064
timestamp=1132586800, timezone=36000,
1065
committer='Joe Foo <joe@foo.com>')
1068
branch.tags.set_tag('v0.2', 'rev-2b')
1069
wt.commit('rev-3', rev_id='rev-3',
1070
timestamp=1132586900, timezone=36000,
1071
committer='Jane Foo <jane@foo.com>')
1072
branch.tags.set_tag('v1.0rc1', 'rev-3')
1073
branch.tags.set_tag('v1.0', 'rev-3')
1076
def test_line_log_single_merge_revision(self):
1077
wt = self._prepare_tree_with_merges()
1078
logfile = self.make_utf8_encoded_stringio()
1079
formatter = log.LineLogFormatter(to_file=logfile)
1080
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
1082
rev = revspec.in_history(wtb)
1083
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
1084
self.assertEqualDiff("""\
1085
1.1.1: Joe Foo 2005-11-22 rev-merged
1089
def test_line_log_with_tags(self):
1090
wt = self._prepare_tree_with_merges(with_tags=True)
1091
logfile = self.make_utf8_encoded_stringio()
1092
formatter = log.LineLogFormatter(to_file=logfile)
1093
log.show_log(wt.branch, formatter)
1094
self.assertEqualDiff("""\
1095
3: Jane Foo 2005-11-22 {v1.0, v1.0rc1} rev-3
1096
2: Joe Foo 2005-11-22 [merge] {v0.2} rev-2
1097
1: Joe Foo 2005-11-22 rev-1
1101
class TestLineLogFormatterWithMergeRevisions(tests.TestCaseWithTransport):
1103
def test_line_merge_revs_log(self):
1104
"""Line log should show revno
1108
wt = self.make_branch_and_tree('.')
1110
self.build_tree(['a'])
1112
b.nick = 'test-line-log'
1113
wt.commit(message='add a',
1114
timestamp=1132711707,
1116
committer='Line-Log-Formatter Tester <test@line.log>')
1117
logfile = file('out.tmp', 'w+')
1118
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1119
log.show_log(b, formatter)
1122
log_contents = logfile.read()
1123
self.assertEqualDiff('1: Line-Log-Formatte... 2005-11-23 add a\n',
1126
def test_line_merge_revs_log_single_merge_revision(self):
1127
wt = self.make_branch_and_memory_tree('.')
1129
self.addCleanup(wt.unlock)
1131
wt.commit('rev-1', rev_id='rev-1',
1132
timestamp=1132586655, timezone=36000,
1133
committer='Joe Foo <joe@foo.com>')
1134
wt.commit('rev-merged', rev_id='rev-2a',
1135
timestamp=1132586700, timezone=36000,
1136
committer='Joe Foo <joe@foo.com>')
1137
wt.set_parent_ids(['rev-1', 'rev-2a'])
1138
wt.branch.set_last_revision_info(1, 'rev-1')
1139
wt.commit('rev-2', rev_id='rev-2b',
1140
timestamp=1132586800, timezone=36000,
1141
committer='Joe Foo <joe@foo.com>')
1142
logfile = self.make_utf8_encoded_stringio()
1143
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1144
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
1146
rev = revspec.in_history(wtb)
1147
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
1148
self.assertEqualDiff("""\
1149
1.1.1: Joe Foo 2005-11-22 rev-merged
1153
def test_line_merge_revs_log_with_merges(self):
1154
wt = self.make_branch_and_memory_tree('.')
1156
self.addCleanup(wt.unlock)
1158
wt.commit('rev-1', rev_id='rev-1',
1159
timestamp=1132586655, timezone=36000,
1160
committer='Joe Foo <joe@foo.com>')
1161
wt.commit('rev-merged', rev_id='rev-2a',
1162
timestamp=1132586700, timezone=36000,
1163
committer='Joe Foo <joe@foo.com>')
1164
wt.set_parent_ids(['rev-1', 'rev-2a'])
1165
wt.branch.set_last_revision_info(1, 'rev-1')
1166
wt.commit('rev-2', rev_id='rev-2b',
1167
timestamp=1132586800, timezone=36000,
1168
committer='Joe Foo <joe@foo.com>')
1169
logfile = self.make_utf8_encoded_stringio()
1170
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1171
log.show_log(wt.branch, formatter)
1172
self.assertEqualDiff("""\
1173
2: Joe Foo 2005-11-22 [merge] rev-2
1174
1.1.1: Joe Foo 2005-11-22 rev-merged
1175
1: Joe Foo 2005-11-22 rev-1
1179
class TestGetViewRevisions(tests.TestCaseWithTransport):
1181
def make_tree_with_commits(self):
1182
"""Create a tree with well-known revision ids"""
1183
wt = self.make_branch_and_tree('tree1')
1184
wt.commit('commit one', rev_id='1')
1185
wt.commit('commit two', rev_id='2')
1186
wt.commit('commit three', rev_id='3')
1187
mainline_revs = [None, '1', '2', '3']
1188
rev_nos = {'1': 1, '2': 2, '3': 3}
1189
return mainline_revs, rev_nos, wt
1191
def make_tree_with_merges(self):
1192
"""Create a tree with well-known revision ids and a merge"""
1193
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1194
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
1195
tree2.commit('four-a', rev_id='4a')
1196
wt.merge_from_branch(tree2.branch)
1197
wt.commit('four-b', rev_id='4b')
1198
mainline_revs.append('4b')
1201
return mainline_revs, rev_nos, wt
1203
def make_tree_with_many_merges(self):
1204
"""Create a tree with well-known revision ids"""
1205
wt = self.make_branch_and_tree('tree1')
1206
self.build_tree_contents([('tree1/f', '1\n')])
1207
wt.add(['f'], ['f-id'])
1208
wt.commit('commit one', rev_id='1')
1209
wt.commit('commit two', rev_id='2')
1211
tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
1212
self.build_tree_contents([('tree3/f', '1\n2\n3a\n')])
1213
tree3.commit('commit three a', rev_id='3a')
1215
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
1216
tree2.merge_from_branch(tree3.branch)
1217
tree2.commit('commit three b', rev_id='3b')
1219
wt.merge_from_branch(tree2.branch)
1220
wt.commit('commit three c', rev_id='3c')
1221
tree2.commit('four-a', rev_id='4a')
1223
wt.merge_from_branch(tree2.branch)
1224
wt.commit('four-b', rev_id='4b')
1226
mainline_revs = [None, '1', '2', '3c', '4b']
1227
rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
1228
full_rev_nos_for_reference = {
1231
'3a': '2.1.1', #first commit tree 3
1232
'3b': '2.2.1', # first commit tree 2
1233
'3c': '3', #merges 3b to main
1234
'4a': '2.2.2', # second commit tree 2
1235
'4b': '4', # merges 4a to main
1237
return mainline_revs, rev_nos, wt
1239
def test_get_view_revisions_forward(self):
1240
"""Test the get_view_revisions method"""
1241
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1243
self.addCleanup(wt.unlock)
1244
revisions = list(log.get_view_revisions(
1245
mainline_revs, rev_nos, wt.branch, 'forward'))
1246
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
1248
revisions2 = list(log.get_view_revisions(
1249
mainline_revs, rev_nos, wt.branch, 'forward',
1250
include_merges=False))
1251
self.assertEqual(revisions, revisions2)
1253
def test_get_view_revisions_reverse(self):
1254
"""Test the get_view_revisions with reverse"""
1255
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1257
self.addCleanup(wt.unlock)
1258
revisions = list(log.get_view_revisions(
1259
mainline_revs, rev_nos, wt.branch, 'reverse'))
1260
self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
1262
revisions2 = list(log.get_view_revisions(
1263
mainline_revs, rev_nos, wt.branch, 'reverse',
1264
include_merges=False))
1265
self.assertEqual(revisions, revisions2)
1267
def test_get_view_revisions_merge(self):
1268
"""Test get_view_revisions when there are merges"""
1269
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
1271
self.addCleanup(wt.unlock)
1272
revisions = list(log.get_view_revisions(
1273
mainline_revs, rev_nos, wt.branch, 'forward'))
1274
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
1275
('4b', '4', 0), ('4a', '3.1.1', 1)],
1277
revisions = list(log.get_view_revisions(
1278
mainline_revs, rev_nos, wt.branch, 'forward',
1279
include_merges=False))
1280
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
1284
def test_get_view_revisions_merge_reverse(self):
1285
"""Test get_view_revisions in reverse when there are merges"""
1286
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
1288
self.addCleanup(wt.unlock)
1289
revisions = list(log.get_view_revisions(
1290
mainline_revs, rev_nos, wt.branch, 'reverse'))
1291
self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
1292
('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
1294
revisions = list(log.get_view_revisions(
1295
mainline_revs, rev_nos, wt.branch, 'reverse',
1296
include_merges=False))
1297
self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
1301
def test_get_view_revisions_merge2(self):
1302
"""Test get_view_revisions when there are merges"""
1303
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
1305
self.addCleanup(wt.unlock)
1306
revisions = list(log.get_view_revisions(
1307
mainline_revs, rev_nos, wt.branch, 'forward'))
1308
expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
1309
('3a', '2.1.1', 1), ('3b', '2.2.1', 1), ('4b', '4', 0),
1311
self.assertEqual(expected, revisions)
1312
revisions = list(log.get_view_revisions(
1313
mainline_revs, rev_nos, wt.branch, 'forward',
1314
include_merges=False))
1315
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
1320
def test_file_id_for_range(self):
1321
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
1323
self.addCleanup(wt.unlock)
1325
def rev_from_rev_id(revid, branch):
1326
revspec = revisionspec.RevisionSpec.from_string('revid:%s' % revid)
1327
return revspec.in_history(branch)
1329
def view_revs(start_rev, end_rev, file_id, direction):
1330
revs = log.calculate_view_revisions(
1332
start_rev, # start_revision
1333
end_rev, # end_revision
1334
direction, # direction
1335
file_id, # specific_fileid
1336
True, # generate_merge_revisions
1340
rev_3a = rev_from_rev_id('3a', wt.branch)
1341
rev_4b = rev_from_rev_id('4b', wt.branch)
1342
self.assertEqual([('3c', '3', 0), ('3a', '2.1.1', 1)],
1343
view_revs(rev_3a, rev_4b, 'f-id', 'reverse'))
1344
# Note: 3c still appears before 3a here because of depth-based sorting
1345
self.assertEqual([('3c', '3', 0), ('3a', '2.1.1', 1)],
1346
view_revs(rev_3a, rev_4b, 'f-id', 'forward'))
1349
class TestGetRevisionsTouchingFileID(tests.TestCaseWithTransport):
1351
def create_tree_with_single_merge(self):
1352
"""Create a branch with a moderate layout.
1354
The revision graph looks like:
1362
In this graph, A introduced files f1 and f2 and f3.
1363
B modifies f1 and f3, and C modifies f2 and f3.
1364
D merges the changes from B and C and resolves the conflict for f3.
1366
# TODO: jam 20070218 This seems like it could really be done
1367
# with make_branch_and_memory_tree() if we could just
1368
# create the content of those files.
1369
# TODO: jam 20070218 Another alternative is that we would really
1370
# like to only create this tree 1 time for all tests that
1371
# use it. Since 'log' only uses the tree in a readonly
1372
# fashion, it seems a shame to regenerate an identical
1373
# tree for each test.
1374
tree = self.make_branch_and_tree('tree')
1376
self.addCleanup(tree.unlock)
1378
self.build_tree_contents([('tree/f1', 'A\n'),
1382
tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
1383
tree.commit('A', rev_id='A')
1385
self.build_tree_contents([('tree/f2', 'A\nC\n'),
1386
('tree/f3', 'A\nC\n'),
1388
tree.commit('C', rev_id='C')
1389
# Revert back to A to build the other history.
1390
tree.set_last_revision('A')
1391
tree.branch.set_last_revision_info(1, 'A')
1392
self.build_tree_contents([('tree/f1', 'A\nB\n'),
1394
('tree/f3', 'A\nB\n'),
1396
tree.commit('B', rev_id='B')
1397
tree.set_parent_ids(['B', 'C'])
1398
self.build_tree_contents([('tree/f1', 'A\nB\n'),
1399
('tree/f2', 'A\nC\n'),
1400
('tree/f3', 'A\nB\nC\n'),
1402
tree.commit('D', rev_id='D')
1404
# Switch to a read lock for this tree.
1405
# We still have an addCleanup(tree.unlock) pending
1410
def check_delta(self, delta, **kw):
1411
"""Check the filenames touched by a delta are as expected.
1413
Caller only have to pass in the list of files for each part, all
1414
unspecified parts are considered empty (and checked as such).
1416
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
1417
# By default we expect an empty list
1418
expected = kw.get(n, [])
1419
# strip out only the path components
1420
got = [x[0] for x in getattr(delta, n)]
1421
self.assertEqual(expected, got)
1423
def test_tree_with_single_merge(self):
1424
"""Make sure the tree layout is correct."""
1425
tree = self.create_tree_with_single_merge()
1426
rev_A_tree = tree.branch.repository.revision_tree('A')
1427
rev_B_tree = tree.branch.repository.revision_tree('B')
1428
rev_C_tree = tree.branch.repository.revision_tree('C')
1429
rev_D_tree = tree.branch.repository.revision_tree('D')
1431
self.check_delta(rev_B_tree.changes_from(rev_A_tree),
1432
modified=['f1', 'f3'])
1434
self.check_delta(rev_C_tree.changes_from(rev_A_tree),
1435
modified=['f2', 'f3'])
1437
self.check_delta(rev_D_tree.changes_from(rev_B_tree),
1438
modified=['f2', 'f3'])
1440
self.check_delta(rev_D_tree.changes_from(rev_C_tree),
1441
modified=['f1', 'f3'])
1443
def assertAllRevisionsForFileID(self, tree, file_id, revisions):
1444
"""Ensure _filter_revisions_touching_file_id returns the right values.
1446
Get the return value from _filter_revisions_touching_file_id and make
1447
sure they are correct.
1449
# The api for _filter_revisions_touching_file_id is a little crazy.
1450
# So we do the setup here.
1451
mainline = tree.branch.revision_history()
1452
mainline.insert(0, None)
1453
revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
1454
view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
1456
actual_revs = log._filter_revisions_touching_file_id(
1459
list(view_revs_iter))
1460
self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
1462
def test_file_id_f1(self):
1463
tree = self.create_tree_with_single_merge()
1464
# f1 should be marked as modified by revisions A and B
1465
self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
1467
def test_file_id_f2(self):
1468
tree = self.create_tree_with_single_merge()
1469
# f2 should be marked as modified by revisions A, C, and D
1470
# because D merged the changes from C.
1471
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1473
def test_file_id_f3(self):
1474
tree = self.create_tree_with_single_merge()
1475
# f3 should be marked as modified by revisions A, B, C, and D
1476
self.assertAllRevisionsForFileID(tree, 'f3-id', ['D', 'C', 'B', 'A'])
1478
def test_file_id_with_ghosts(self):
1479
# This is testing bug #209948, where having a ghost would cause
1480
# _filter_revisions_touching_file_id() to fail.
1481
tree = self.create_tree_with_single_merge()
1482
# We need to add a revision, so switch back to a write-locked tree
1483
# (still a single addCleanup(tree.unlock) pending).
1486
first_parent = tree.last_revision()
1487
tree.set_parent_ids([first_parent, 'ghost-revision-id'])
1488
self.build_tree_contents([('tree/f1', 'A\nB\nXX\n')])
1489
tree.commit('commit with a ghost', rev_id='XX')
1490
self.assertAllRevisionsForFileID(tree, 'f1-id', ['XX', 'B', 'A'])
1491
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1493
def test_unknown_file_id(self):
1494
tree = self.create_tree_with_single_merge()
1495
self.assertAllRevisionsForFileID(tree, 'unknown', [])
1497
def test_empty_branch_unknown_file_id(self):
1498
tree = self.make_branch_and_tree('tree')
1499
self.assertAllRevisionsForFileID(tree, 'unknown', [])
1502
class TestShowChangedRevisions(tests.TestCaseWithTransport):
1504
def test_show_changed_revisions_verbose(self):
1505
tree = self.make_branch_and_tree('tree_a')
1506
self.build_tree(['tree_a/foo'])
1508
tree.commit('bar', rev_id='bar-id')
1509
s = self.make_utf8_encoded_stringio()
1510
log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
1511
self.assertContainsRe(s.getvalue(), 'bar')
1512
self.assertNotContainsRe(s.getvalue(), 'foo')
1515
class TestLogFormatter(tests.TestCase):
1517
def test_short_committer(self):
1518
rev = revision.Revision('a-id')
1519
rev.committer = 'John Doe <jdoe@example.com>'
1520
lf = log.LogFormatter(None)
1521
self.assertEqual('John Doe', lf.short_committer(rev))
1522
rev.committer = 'John Smith <jsmith@example.com>'
1523
self.assertEqual('John Smith', lf.short_committer(rev))
1524
rev.committer = 'John Smith'
1525
self.assertEqual('John Smith', lf.short_committer(rev))
1526
rev.committer = 'jsmith@example.com'
1527
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1528
rev.committer = '<jsmith@example.com>'
1529
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1530
rev.committer = 'John Smith jsmith@example.com'
1531
self.assertEqual('John Smith', lf.short_committer(rev))
1533
def test_short_author(self):
1534
rev = revision.Revision('a-id')
1535
rev.committer = 'John Doe <jdoe@example.com>'
1536
lf = log.LogFormatter(None)
1537
self.assertEqual('John Doe', lf.short_author(rev))
1538
rev.properties['author'] = 'John Smith <jsmith@example.com>'
1539
self.assertEqual('John Smith', lf.short_author(rev))
1540
rev.properties['author'] = 'John Smith'
1541
self.assertEqual('John Smith', lf.short_author(rev))
1542
rev.properties['author'] = 'jsmith@example.com'
1543
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1544
rev.properties['author'] = '<jsmith@example.com>'
1545
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1546
rev.properties['author'] = 'John Smith jsmith@example.com'
1547
self.assertEqual('John Smith', lf.short_author(rev))
1548
del rev.properties['author']
1549
rev.properties['authors'] = ('John Smith <jsmith@example.com>\n'
1550
'Jane Rey <jrey@example.com>')
1551
self.assertEqual('John Smith', lf.short_author(rev))
1554
class TestReverseByDepth(tests.TestCase):
1555
"""Test reverse_by_depth behavior.
1557
This is used to present revisions in forward (oldest first) order in a nice
1560
The tests use lighter revision description to ease reading.
1563
def assertReversed(self, forward, backward):
1564
# Transform the descriptions to suit the API: tests use (revno, depth),
1565
# while the API expects (revid, revno, depth)
1566
def complete_revisions(l):
1567
"""Transform the description to suit the API.
1569
Tests use (revno, depth) whil the API expects (revid, revno, depth).
1570
Since the revid is arbitrary, we just duplicate revno
1572
return [ (r, r, d) for r, d in l]
1573
forward = complete_revisions(forward)
1574
backward= complete_revisions(backward)
1575
self.assertEqual(forward, log.reverse_by_depth(backward))
1578
def test_mainline_revisions(self):
1579
self.assertReversed([( '1', 0), ('2', 0)],
1580
[('2', 0), ('1', 0)])
1582
def test_merged_revisions(self):
1583
self.assertReversed([('1', 0), ('2', 0), ('2.2', 1), ('2.1', 1),],
1584
[('2', 0), ('2.1', 1), ('2.2', 1), ('1', 0),])
1585
def test_shifted_merged_revisions(self):
1586
"""Test irregular layout.
1588
Requesting revisions touching a file can produce "holes" in the depths.
1590
self.assertReversed([('1', 0), ('2', 0), ('1.1', 2), ('1.2', 2),],
1591
[('2', 0), ('1.2', 2), ('1.1', 2), ('1', 0),])
1593
def test_merged_without_child_revisions(self):
1594
"""Test irregular layout.
1596
Revision ranges can produce "holes" in the depths.
1598
# When a revision of higher depth doesn't follow one of lower depth, we
1599
# assume a lower depth one is virtually there
1600
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1601
[('4', 4), ('3', 3), ('2', 2), ('1', 2),])
1602
# So we get the same order after reversing below even if the original
1603
# revisions are not in the same order.
1604
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1605
[('3', 3), ('4', 4), ('2', 2), ('1', 2),])
1608
class TestHistoryChange(tests.TestCaseWithTransport):
1610
def setup_a_tree(self):
1611
tree = self.make_branch_and_tree('tree')
1613
self.addCleanup(tree.unlock)
1614
tree.commit('1a', rev_id='1a')
1615
tree.commit('2a', rev_id='2a')
1616
tree.commit('3a', rev_id='3a')
1619
def setup_ab_tree(self):
1620
tree = self.setup_a_tree()
1621
tree.set_last_revision('1a')
1622
tree.branch.set_last_revision_info(1, '1a')
1623
tree.commit('2b', rev_id='2b')
1624
tree.commit('3b', rev_id='3b')
1627
def setup_ac_tree(self):
1628
tree = self.setup_a_tree()
1629
tree.set_last_revision(revision.NULL_REVISION)
1630
tree.branch.set_last_revision_info(0, revision.NULL_REVISION)
1631
tree.commit('1c', rev_id='1c')
1632
tree.commit('2c', rev_id='2c')
1633
tree.commit('3c', rev_id='3c')
1636
def test_all_new(self):
1637
tree = self.setup_ab_tree()
1638
old, new = log.get_history_change('1a', '3a', tree.branch.repository)
1639
self.assertEqual([], old)
1640
self.assertEqual(['2a', '3a'], new)
1642
def test_all_old(self):
1643
tree = self.setup_ab_tree()
1644
old, new = log.get_history_change('3a', '1a', tree.branch.repository)
1645
self.assertEqual([], new)
1646
self.assertEqual(['2a', '3a'], old)
1648
def test_null_old(self):
1649
tree = self.setup_ab_tree()
1650
old, new = log.get_history_change(revision.NULL_REVISION,
1651
'3a', tree.branch.repository)
1652
self.assertEqual([], old)
1653
self.assertEqual(['1a', '2a', '3a'], new)
1655
def test_null_new(self):
1656
tree = self.setup_ab_tree()
1657
old, new = log.get_history_change('3a', revision.NULL_REVISION,
1658
tree.branch.repository)
1659
self.assertEqual([], new)
1660
self.assertEqual(['1a', '2a', '3a'], old)
1662
def test_diverged(self):
1663
tree = self.setup_ab_tree()
1664
old, new = log.get_history_change('3a', '3b', tree.branch.repository)
1665
self.assertEqual(old, ['2a', '3a'])
1666
self.assertEqual(new, ['2b', '3b'])
1668
def test_unrelated(self):
1669
tree = self.setup_ac_tree()
1670
old, new = log.get_history_change('3a', '3c', tree.branch.repository)
1671
self.assertEqual(old, ['1a', '2a', '3a'])
1672
self.assertEqual(new, ['1c', '2c', '3c'])
1674
def test_show_branch_change(self):
1675
tree = self.setup_ab_tree()
1677
log.show_branch_change(tree.branch, s, 3, '3a')
1678
self.assertContainsRe(s.getvalue(),
1679
'[*]{60}\nRemoved Revisions:\n(.|\n)*2a(.|\n)*3a(.|\n)*'
1680
'[*]{60}\n\nAdded Revisions:\n(.|\n)*2b(.|\n)*3b')
1682
def test_show_branch_change_no_change(self):
1683
tree = self.setup_ab_tree()
1685
log.show_branch_change(tree.branch, s, 3, '3b')
1686
self.assertEqual(s.getvalue(),
1687
'Nothing seems to have changed\n')
1689
def test_show_branch_change_no_old(self):
1690
tree = self.setup_ab_tree()
1692
log.show_branch_change(tree.branch, s, 2, '2b')
1693
self.assertContainsRe(s.getvalue(), 'Added Revisions:')
1694
self.assertNotContainsRe(s.getvalue(), 'Removed Revisions:')
1696
def test_show_branch_change_no_new(self):
1697
tree = self.setup_ab_tree()
1698
tree.branch.set_last_revision_info(2, '2b')
1700
log.show_branch_change(tree.branch, s, 3, '3b')
1701
self.assertContainsRe(s.getvalue(), 'Removed Revisions:')
1702
self.assertNotContainsRe(s.getvalue(), 'Added Revisions:')