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
# clean up the registry in log
37
log.properties_handler_registry = registry.Registry()
40
super(TestCaseWithoutPropsHandler, self)._cleanup()
41
# restore the custom properties handler registry
42
log.properties_handler_registry = self.properties_handler_registry
45
class LogCatcher(log.LogFormatter):
46
"""Pull log messages into list rather than displaying them.
48
For ease of testing we save log messages here rather than actually
49
formatting them, so that we can precisely check the result without
50
being too dependent on the exact formatting.
52
We should also test the LogFormatter.
58
super(LogCatcher, self).__init__(to_file=None)
61
def log_revision(self, revision):
62
self.logs.append(revision)
65
class TestShowLog(tests.TestCaseWithTransport):
67
def checkDelta(self, delta, **kw):
68
"""Check the filenames touched by a delta are as expected.
70
Caller only have to pass in the list of files for each part, all
71
unspecified parts are considered empty (and checked as such).
73
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
74
# By default we expect an empty list
75
expected = kw.get(n, [])
76
# strip out only the path components
77
got = [x[0] for x in getattr(delta, n)]
78
self.assertEqual(expected, got)
80
def assertInvalidRevisonNumber(self, br, start, end):
82
self.assertRaises(errors.InvalidRevisionNumber,
84
start_revision=start, end_revision=end)
86
def test_cur_revno(self):
87
wt = self.make_branch_and_tree('.')
91
wt.commit('empty commit')
92
log.show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
94
# Since there is a single revision in the branch all the combinations
96
self.assertInvalidRevisonNumber(b, 2, 1)
97
self.assertInvalidRevisonNumber(b, 1, 2)
98
self.assertInvalidRevisonNumber(b, 0, 2)
99
self.assertInvalidRevisonNumber(b, 1, 0)
100
self.assertInvalidRevisonNumber(b, -1, 1)
101
self.assertInvalidRevisonNumber(b, 1, -1)
103
def test_empty_branch(self):
104
wt = self.make_branch_and_tree('.')
107
log.show_log(wt.branch, lf)
109
self.assertEqual([], lf.logs)
111
def test_empty_commit(self):
112
wt = self.make_branch_and_tree('.')
114
wt.commit('empty commit')
116
log.show_log(wt.branch, lf, verbose=True)
117
self.assertEqual(1, len(lf.logs))
118
self.assertEqual('1', lf.logs[0].revno)
119
self.assertEqual('empty commit', lf.logs[0].rev.message)
120
self.checkDelta(lf.logs[0].delta)
122
def test_simple_commit(self):
123
wt = self.make_branch_and_tree('.')
124
wt.commit('empty commit')
125
self.build_tree(['hello'])
127
wt.commit('add one file',
128
committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
129
u'<test@example.com>')
131
log.show_log(wt.branch, lf, verbose=True)
132
self.assertEqual(2, len(lf.logs))
133
# first one is most recent
134
log_entry = lf.logs[0]
135
self.assertEqual('2', log_entry.revno)
136
self.assertEqual('add one file', log_entry.rev.message)
137
self.checkDelta(log_entry.delta, added=['hello'])
139
def test_commit_message_with_control_chars(self):
140
wt = self.make_branch_and_tree('.')
141
msg = u"All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
142
msg = msg.replace(u'\r', u'\n')
145
log.show_log(wt.branch, lf, verbose=True)
146
committed_msg = lf.logs[0].rev.message
147
self.assertNotEqual(msg, committed_msg)
148
self.assertTrue(len(committed_msg) > len(msg))
150
def test_commit_message_without_control_chars(self):
151
wt = self.make_branch_and_tree('.')
152
# escaped. As ElementTree apparently does some kind of
153
# newline conversion, neither LF (\x0A) nor CR (\x0D) are
154
# included in the test commit message, even though they are
155
# valid XML 1.0 characters.
156
msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
159
log.show_log(wt.branch, lf, verbose=True)
160
committed_msg = lf.logs[0].rev.message
161
self.assertEqual(msg, committed_msg)
163
def test_deltas_in_merge_revisions(self):
164
"""Check deltas created for both mainline and merge revisions"""
165
wt = self.make_branch_and_tree('parent')
166
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
169
wt.commit(message='add file1 and file2')
170
self.run_bzr('branch parent child')
171
os.unlink('child/file1')
172
file('child/file2', 'wb').write('hello\n')
173
self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
176
self.run_bzr('merge ../child')
177
wt.commit('merge child branch')
181
lf.supports_merge_revisions = True
182
log.show_log(b, lf, verbose=True)
184
self.assertEqual(3, len(lf.logs))
186
logentry = lf.logs[0]
187
self.assertEqual('2', logentry.revno)
188
self.assertEqual('merge child branch', logentry.rev.message)
189
self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
191
logentry = lf.logs[1]
192
self.assertEqual('1.1.1', logentry.revno)
193
self.assertEqual('remove file1 and modify file2', logentry.rev.message)
194
self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
196
logentry = lf.logs[2]
197
self.assertEqual('1', logentry.revno)
198
self.assertEqual('add file1 and file2', logentry.rev.message)
199
self.checkDelta(logentry.delta, added=['file1', 'file2'])
202
def make_commits_with_trailing_newlines(wt):
203
"""Helper method for LogFormatter tests"""
206
open('a', 'wb').write('hello moto\n')
208
wt.commit('simple log message', rev_id='a1',
209
timestamp=1132586655.459960938, timezone=-6*3600,
210
committer='Joe Foo <joe@foo.com>')
211
open('b', 'wb').write('goodbye\n')
213
wt.commit('multiline\nlog\nmessage\n', rev_id='a2',
214
timestamp=1132586842.411175966, timezone=-6*3600,
215
committer='Joe Foo <joe@foo.com>',
216
authors=['Joe Bar <joe@bar.com>'])
218
open('c', 'wb').write('just another manic monday\n')
220
wt.commit('single line with trailing newline\n', rev_id='a3',
221
timestamp=1132587176.835228920, timezone=-6*3600,
222
committer = 'Joe Foo <joe@foo.com>')
226
def normalize_log(log):
227
"""Replaces the variable lines of logs with fixed lines"""
228
author = 'author: Dolor Sit <test@example.com>'
229
committer = 'committer: Lorem Ipsum <test@example.com>'
230
lines = log.splitlines(True)
231
for idx,line in enumerate(lines):
232
stripped_line = line.lstrip()
233
indent = ' ' * (len(line) - len(stripped_line))
234
if stripped_line.startswith('author:'):
235
lines[idx] = indent + author + '\n'
236
elif stripped_line.startswith('committer:'):
237
lines[idx] = indent + committer + '\n'
238
elif stripped_line.startswith('timestamp:'):
239
lines[idx] = indent + 'timestamp: Just now\n'
240
return ''.join(lines)
243
class TestShortLogFormatter(tests.TestCaseWithTransport):
245
def test_trailing_newlines(self):
246
wt = self.make_branch_and_tree('.')
247
b = make_commits_with_trailing_newlines(wt)
248
sio = self.make_utf8_encoded_stringio()
249
lf = log.ShortLogFormatter(to_file=sio)
251
self.assertEqualDiff("""\
252
3 Joe Foo\t2005-11-21
253
single line with trailing newline
255
2 Joe Bar\t2005-11-21
260
1 Joe Foo\t2005-11-21
266
def _prepare_tree_with_merges(self, with_tags=False):
267
wt = self.make_branch_and_memory_tree('.')
269
self.addCleanup(wt.unlock)
271
wt.commit('rev-1', rev_id='rev-1',
272
timestamp=1132586655, timezone=36000,
273
committer='Joe Foo <joe@foo.com>')
274
wt.commit('rev-merged', rev_id='rev-2a',
275
timestamp=1132586700, timezone=36000,
276
committer='Joe Foo <joe@foo.com>')
277
wt.set_parent_ids(['rev-1', 'rev-2a'])
278
wt.branch.set_last_revision_info(1, 'rev-1')
279
wt.commit('rev-2', rev_id='rev-2b',
280
timestamp=1132586800, timezone=36000,
281
committer='Joe Foo <joe@foo.com>')
284
branch.tags.set_tag('v0.2', 'rev-2b')
285
wt.commit('rev-3', rev_id='rev-3',
286
timestamp=1132586900, timezone=36000,
287
committer='Jane Foo <jane@foo.com>')
288
branch.tags.set_tag('v1.0rc1', 'rev-3')
289
branch.tags.set_tag('v1.0', 'rev-3')
292
def test_short_log_with_merges(self):
293
wt = self._prepare_tree_with_merges()
294
logfile = self.make_utf8_encoded_stringio()
295
formatter = log.ShortLogFormatter(to_file=logfile)
296
log.show_log(wt.branch, formatter)
297
self.assertEqualDiff("""\
298
2 Joe Foo\t2005-11-22 [merge]
301
1 Joe Foo\t2005-11-22
307
def test_short_log_with_merges_and_advice(self):
308
wt = self._prepare_tree_with_merges()
309
logfile = self.make_utf8_encoded_stringio()
310
formatter = log.ShortLogFormatter(to_file=logfile,
312
log.show_log(wt.branch, formatter)
313
self.assertEqualDiff("""\
314
2 Joe Foo\t2005-11-22 [merge]
317
1 Joe Foo\t2005-11-22
320
Use --include-merges or -n0 to see merged revisions.
324
def test_short_log_with_merges_and_range(self):
325
wt = self.make_branch_and_memory_tree('.')
327
self.addCleanup(wt.unlock)
329
wt.commit('rev-1', rev_id='rev-1',
330
timestamp=1132586655, timezone=36000,
331
committer='Joe Foo <joe@foo.com>')
332
wt.commit('rev-merged', rev_id='rev-2a',
333
timestamp=1132586700, timezone=36000,
334
committer='Joe Foo <joe@foo.com>')
335
wt.branch.set_last_revision_info(1, 'rev-1')
336
wt.set_parent_ids(['rev-1', 'rev-2a'])
337
wt.commit('rev-2b', rev_id='rev-2b',
338
timestamp=1132586800, timezone=36000,
339
committer='Joe Foo <joe@foo.com>')
340
wt.commit('rev-3a', rev_id='rev-3a',
341
timestamp=1132586800, timezone=36000,
342
committer='Joe Foo <joe@foo.com>')
343
wt.branch.set_last_revision_info(2, 'rev-2b')
344
wt.set_parent_ids(['rev-2b', 'rev-3a'])
345
wt.commit('rev-3b', rev_id='rev-3b',
346
timestamp=1132586800, timezone=36000,
347
committer='Joe Foo <joe@foo.com>')
348
logfile = self.make_utf8_encoded_stringio()
349
formatter = log.ShortLogFormatter(to_file=logfile)
350
log.show_log(wt.branch, formatter,
351
start_revision=2, end_revision=3)
352
self.assertEqualDiff("""\
353
3 Joe Foo\t2005-11-22 [merge]
356
2 Joe Foo\t2005-11-22 [merge]
362
def test_short_log_with_tags(self):
363
wt = self._prepare_tree_with_merges(with_tags=True)
364
logfile = self.make_utf8_encoded_stringio()
365
formatter = log.ShortLogFormatter(to_file=logfile)
366
log.show_log(wt.branch, formatter)
367
self.assertEqualDiff("""\
368
3 Jane Foo\t2005-11-22 {v1.0, v1.0rc1}
371
2 Joe Foo\t2005-11-22 {v0.2} [merge]
374
1 Joe Foo\t2005-11-22
380
def test_short_log_single_merge_revision(self):
381
wt = self.make_branch_and_memory_tree('.')
383
self.addCleanup(wt.unlock)
385
wt.commit('rev-1', rev_id='rev-1',
386
timestamp=1132586655, timezone=36000,
387
committer='Joe Foo <joe@foo.com>')
388
wt.commit('rev-merged', rev_id='rev-2a',
389
timestamp=1132586700, timezone=36000,
390
committer='Joe Foo <joe@foo.com>')
391
wt.set_parent_ids(['rev-1', 'rev-2a'])
392
wt.branch.set_last_revision_info(1, 'rev-1')
393
wt.commit('rev-2', rev_id='rev-2b',
394
timestamp=1132586800, timezone=36000,
395
committer='Joe Foo <joe@foo.com>')
396
logfile = self.make_utf8_encoded_stringio()
397
formatter = log.ShortLogFormatter(to_file=logfile)
398
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
400
rev = revspec.in_history(wtb)
401
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
402
self.assertEqualDiff("""\
403
1.1.1 Joe Foo\t2005-11-22
410
class TestShortLogFormatterWithMergeRevisions(tests.TestCaseWithTransport):
412
def test_short_merge_revs_log_with_merges(self):
413
wt = self.make_branch_and_memory_tree('.')
415
self.addCleanup(wt.unlock)
417
wt.commit('rev-1', rev_id='rev-1',
418
timestamp=1132586655, timezone=36000,
419
committer='Joe Foo <joe@foo.com>')
420
wt.commit('rev-merged', rev_id='rev-2a',
421
timestamp=1132586700, timezone=36000,
422
committer='Joe Foo <joe@foo.com>')
423
wt.set_parent_ids(['rev-1', 'rev-2a'])
424
wt.branch.set_last_revision_info(1, 'rev-1')
425
wt.commit('rev-2', rev_id='rev-2b',
426
timestamp=1132586800, timezone=36000,
427
committer='Joe Foo <joe@foo.com>')
428
logfile = self.make_utf8_encoded_stringio()
429
formatter = log.ShortLogFormatter(to_file=logfile, levels=0)
430
log.show_log(wt.branch, formatter)
431
# Note that the 1.1.1 indenting is in fact correct given that
432
# the revision numbers are right justified within 5 characters
433
# for mainline revnos and 9 characters for dotted revnos.
434
self.assertEqualDiff("""\
435
2 Joe Foo\t2005-11-22 [merge]
438
1.1.1 Joe Foo\t2005-11-22
441
1 Joe Foo\t2005-11-22
447
def test_short_merge_revs_log_single_merge_revision(self):
448
wt = self.make_branch_and_memory_tree('.')
450
self.addCleanup(wt.unlock)
452
wt.commit('rev-1', rev_id='rev-1',
453
timestamp=1132586655, timezone=36000,
454
committer='Joe Foo <joe@foo.com>')
455
wt.commit('rev-merged', rev_id='rev-2a',
456
timestamp=1132586700, timezone=36000,
457
committer='Joe Foo <joe@foo.com>')
458
wt.set_parent_ids(['rev-1', 'rev-2a'])
459
wt.branch.set_last_revision_info(1, 'rev-1')
460
wt.commit('rev-2', rev_id='rev-2b',
461
timestamp=1132586800, timezone=36000,
462
committer='Joe Foo <joe@foo.com>')
463
logfile = self.make_utf8_encoded_stringio()
464
formatter = log.ShortLogFormatter(to_file=logfile, levels=0)
465
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
467
rev = revspec.in_history(wtb)
468
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
469
self.assertEqualDiff("""\
470
1.1.1 Joe Foo\t2005-11-22
477
class TestLongLogFormatter(TestCaseWithoutPropsHandler):
479
def test_verbose_log(self):
480
"""Verbose log includes changed files
484
wt = self.make_branch_and_tree('.')
486
self.build_tree(['a'])
488
# XXX: why does a longer nick show up?
489
b.nick = 'test_verbose_log'
490
wt.commit(message='add a',
491
timestamp=1132711707,
493
committer='Lorem Ipsum <test@example.com>')
494
logfile = file('out.tmp', 'w+')
495
formatter = log.LongLogFormatter(to_file=logfile)
496
log.show_log(b, formatter, verbose=True)
499
log_contents = logfile.read()
500
self.assertEqualDiff('''\
501
------------------------------------------------------------
503
committer: Lorem Ipsum <test@example.com>
504
branch nick: test_verbose_log
505
timestamp: Wed 2005-11-23 12:08:27 +1000
513
def test_merges_are_indented_by_level(self):
514
wt = self.make_branch_and_tree('parent')
515
wt.commit('first post')
516
self.run_bzr('branch parent child')
517
self.run_bzr(['commit', '-m', 'branch 1', '--unchanged', 'child'])
518
self.run_bzr('branch child smallerchild')
519
self.run_bzr(['commit', '-m', 'branch 2', '--unchanged',
522
self.run_bzr('merge ../smallerchild')
523
self.run_bzr(['commit', '-m', 'merge branch 2'])
524
os.chdir('../parent')
525
self.run_bzr('merge ../child')
526
wt.commit('merge branch 1')
528
sio = self.make_utf8_encoded_stringio()
529
lf = log.LongLogFormatter(to_file=sio, levels=0)
530
log.show_log(b, lf, verbose=True)
531
the_log = normalize_log(sio.getvalue())
532
self.assertEqualDiff("""\
533
------------------------------------------------------------
535
committer: Lorem Ipsum <test@example.com>
540
------------------------------------------------------------
542
committer: Lorem Ipsum <test@example.com>
547
------------------------------------------------------------
549
committer: Lorem Ipsum <test@example.com>
550
branch nick: smallerchild
554
------------------------------------------------------------
556
committer: Lorem Ipsum <test@example.com>
561
------------------------------------------------------------
563
committer: Lorem Ipsum <test@example.com>
571
def test_verbose_merge_revisions_contain_deltas(self):
572
wt = self.make_branch_and_tree('parent')
573
self.build_tree(['parent/f1', 'parent/f2'])
575
wt.commit('first post')
576
self.run_bzr('branch parent child')
577
os.unlink('child/f1')
578
file('child/f2', 'wb').write('hello\n')
579
self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
582
self.run_bzr('merge ../child')
583
wt.commit('merge branch 1')
585
sio = self.make_utf8_encoded_stringio()
586
lf = log.LongLogFormatter(to_file=sio, levels=0)
587
log.show_log(b, lf, verbose=True)
588
the_log = normalize_log(sio.getvalue())
589
self.assertEqualDiff("""\
590
------------------------------------------------------------
592
committer: Lorem Ipsum <test@example.com>
601
------------------------------------------------------------
603
committer: Lorem Ipsum <test@example.com>
607
removed f1 and modified f2
612
------------------------------------------------------------
614
committer: Lorem Ipsum <test@example.com>
625
def test_trailing_newlines(self):
626
wt = self.make_branch_and_tree('.')
627
b = make_commits_with_trailing_newlines(wt)
628
sio = self.make_utf8_encoded_stringio()
629
lf = log.LongLogFormatter(to_file=sio)
631
self.assertEqualDiff("""\
632
------------------------------------------------------------
634
committer: Joe Foo <joe@foo.com>
636
timestamp: Mon 2005-11-21 09:32:56 -0600
638
single line with trailing newline
639
------------------------------------------------------------
641
author: Joe Bar <joe@bar.com>
642
committer: Joe Foo <joe@foo.com>
644
timestamp: Mon 2005-11-21 09:27:22 -0600
649
------------------------------------------------------------
651
committer: Joe Foo <joe@foo.com>
653
timestamp: Mon 2005-11-21 09:24:15 -0600
659
def test_author_in_log(self):
660
"""Log includes the author name if it's set in
661
the revision properties
663
wt = self.make_branch_and_tree('.')
665
self.build_tree(['a'])
667
b.nick = 'test_author_log'
668
wt.commit(message='add a',
669
timestamp=1132711707,
671
committer='Lorem Ipsum <test@example.com>',
672
authors=['John Doe <jdoe@example.com>',
673
'Jane Rey <jrey@example.com>'])
675
formatter = log.LongLogFormatter(to_file=sio)
676
log.show_log(b, formatter)
677
self.assertEqualDiff('''\
678
------------------------------------------------------------
680
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
681
committer: Lorem Ipsum <test@example.com>
682
branch nick: test_author_log
683
timestamp: Wed 2005-11-23 12:08:27 +1000
689
def test_properties_in_log(self):
690
"""Log includes the custom properties returned by the registered
693
wt = self.make_branch_and_tree('.')
695
self.build_tree(['a'])
697
b.nick = 'test_properties_in_log'
698
wt.commit(message='add a',
699
timestamp=1132711707,
701
committer='Lorem Ipsum <test@example.com>',
702
authors=['John Doe <jdoe@example.com>'])
704
formatter = log.LongLogFormatter(to_file=sio)
706
def trivial_custom_prop_handler(revision):
707
return {'test_prop':'test_value'}
709
log.properties_handler_registry.register(
710
'trivial_custom_prop_handler',
711
trivial_custom_prop_handler)
712
log.show_log(b, formatter)
714
log.properties_handler_registry.remove(
715
'trivial_custom_prop_handler')
716
self.assertEqualDiff('''\
717
------------------------------------------------------------
719
test_prop: test_value
720
author: John Doe <jdoe@example.com>
721
committer: Lorem Ipsum <test@example.com>
722
branch nick: test_properties_in_log
723
timestamp: Wed 2005-11-23 12:08:27 +1000
729
def test_properties_in_short_log(self):
730
"""Log includes the custom properties returned by the registered
733
wt = self.make_branch_and_tree('.')
735
self.build_tree(['a'])
737
b.nick = 'test_properties_in_short_log'
738
wt.commit(message='add a',
739
timestamp=1132711707,
741
committer='Lorem Ipsum <test@example.com>',
742
authors=['John Doe <jdoe@example.com>'])
744
formatter = log.ShortLogFormatter(to_file=sio)
746
def trivial_custom_prop_handler(revision):
747
return {'test_prop':'test_value'}
749
log.properties_handler_registry.register(
750
'trivial_custom_prop_handler',
751
trivial_custom_prop_handler)
752
log.show_log(b, formatter)
754
log.properties_handler_registry.remove(
755
'trivial_custom_prop_handler')
756
self.assertEqualDiff('''\
757
1 John Doe\t2005-11-23
758
test_prop: test_value
764
def test_error_in_properties_handler(self):
765
"""Log includes the custom properties returned by the registered
768
wt = self.make_branch_and_tree('.')
770
self.build_tree(['a'])
772
b.nick = 'test_author_log'
773
wt.commit(message='add a',
774
timestamp=1132711707,
776
committer='Lorem Ipsum <test@example.com>',
777
authors=['John Doe <jdoe@example.com>'],
778
revprops={'first_prop':'first_value'})
780
formatter = log.LongLogFormatter(to_file=sio)
782
def trivial_custom_prop_handler(revision):
783
raise StandardError("a test error")
785
log.properties_handler_registry.register(
786
'trivial_custom_prop_handler',
787
trivial_custom_prop_handler)
788
self.assertRaises(StandardError, log.show_log, b, formatter,)
790
log.properties_handler_registry.remove(
791
'trivial_custom_prop_handler')
793
def test_properties_handler_bad_argument(self):
794
wt = self.make_branch_and_tree('.')
796
self.build_tree(['a'])
798
b.nick = 'test_author_log'
799
wt.commit(message='add a',
800
timestamp=1132711707,
802
committer='Lorem Ipsum <test@example.com>',
803
authors=['John Doe <jdoe@example.com>'],
804
revprops={'a_prop':'test_value'})
806
formatter = log.LongLogFormatter(to_file=sio)
808
def bad_argument_prop_handler(revision):
809
return {'custom_prop_name':revision.properties['a_prop']}
811
log.properties_handler_registry.register(
812
'bad_argument_prop_handler',
813
bad_argument_prop_handler)
815
self.assertRaises(AttributeError, formatter.show_properties,
818
revision = b.repository.get_revision(b.last_revision())
819
formatter.show_properties(revision, '')
820
self.assertEqualDiff('''custom_prop_name: test_value\n''',
823
log.properties_handler_registry.remove(
824
'bad_argument_prop_handler')
827
class TestLongLogFormatterWithoutMergeRevisions(TestCaseWithoutPropsHandler):
829
def test_long_verbose_log(self):
830
"""Verbose log includes changed files
834
wt = self.make_branch_and_tree('.')
836
self.build_tree(['a'])
838
# XXX: why does a longer nick show up?
839
b.nick = 'test_verbose_log'
840
wt.commit(message='add a',
841
timestamp=1132711707,
843
committer='Lorem Ipsum <test@example.com>')
844
logfile = file('out.tmp', 'w+')
845
formatter = log.LongLogFormatter(to_file=logfile, levels=1)
846
log.show_log(b, formatter, verbose=True)
849
log_contents = logfile.read()
850
self.assertEqualDiff('''\
851
------------------------------------------------------------
853
committer: Lorem Ipsum <test@example.com>
854
branch nick: test_verbose_log
855
timestamp: Wed 2005-11-23 12:08:27 +1000
863
def test_long_verbose_contain_deltas(self):
864
wt = self.make_branch_and_tree('parent')
865
self.build_tree(['parent/f1', 'parent/f2'])
867
wt.commit('first post')
868
self.run_bzr('branch parent child')
869
os.unlink('child/f1')
870
file('child/f2', 'wb').write('hello\n')
871
self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
874
self.run_bzr('merge ../child')
875
wt.commit('merge branch 1')
877
sio = self.make_utf8_encoded_stringio()
878
lf = log.LongLogFormatter(to_file=sio, levels=1)
879
log.show_log(b, lf, verbose=True)
880
the_log = normalize_log(sio.getvalue())
881
self.assertEqualDiff("""\
882
------------------------------------------------------------
884
committer: Lorem Ipsum <test@example.com>
893
------------------------------------------------------------
895
committer: Lorem Ipsum <test@example.com>
906
def test_long_trailing_newlines(self):
907
wt = self.make_branch_and_tree('.')
908
b = make_commits_with_trailing_newlines(wt)
909
sio = self.make_utf8_encoded_stringio()
910
lf = log.LongLogFormatter(to_file=sio, levels=1)
912
self.assertEqualDiff("""\
913
------------------------------------------------------------
915
committer: Joe Foo <joe@foo.com>
917
timestamp: Mon 2005-11-21 09:32:56 -0600
919
single line with trailing newline
920
------------------------------------------------------------
922
author: Joe Bar <joe@bar.com>
923
committer: Joe Foo <joe@foo.com>
925
timestamp: Mon 2005-11-21 09:27:22 -0600
930
------------------------------------------------------------
932
committer: Joe Foo <joe@foo.com>
934
timestamp: Mon 2005-11-21 09:24:15 -0600
940
def test_long_author_in_log(self):
941
"""Log includes the author name if it's set in
942
the revision properties
944
wt = self.make_branch_and_tree('.')
946
self.build_tree(['a'])
948
b.nick = 'test_author_log'
949
wt.commit(message='add a',
950
timestamp=1132711707,
952
committer='Lorem Ipsum <test@example.com>',
953
authors=['John Doe <jdoe@example.com>'])
955
formatter = log.LongLogFormatter(to_file=sio, levels=1)
956
log.show_log(b, formatter)
957
self.assertEqualDiff('''\
958
------------------------------------------------------------
960
author: John Doe <jdoe@example.com>
961
committer: Lorem Ipsum <test@example.com>
962
branch nick: test_author_log
963
timestamp: Wed 2005-11-23 12:08:27 +1000
969
def test_long_properties_in_log(self):
970
"""Log includes the custom properties returned by the registered
973
wt = self.make_branch_and_tree('.')
975
self.build_tree(['a'])
977
b.nick = 'test_properties_in_log'
978
wt.commit(message='add a',
979
timestamp=1132711707,
981
committer='Lorem Ipsum <test@example.com>',
982
authors=['John Doe <jdoe@example.com>'])
984
formatter = log.LongLogFormatter(to_file=sio, levels=1)
986
def trivial_custom_prop_handler(revision):
987
return {'test_prop':'test_value'}
989
log.properties_handler_registry.register(
990
'trivial_custom_prop_handler',
991
trivial_custom_prop_handler)
992
log.show_log(b, formatter)
994
log.properties_handler_registry.remove(
995
'trivial_custom_prop_handler')
996
self.assertEqualDiff('''\
997
------------------------------------------------------------
999
test_prop: test_value
1000
author: John Doe <jdoe@example.com>
1001
committer: Lorem Ipsum <test@example.com>
1002
branch nick: test_properties_in_log
1003
timestamp: Wed 2005-11-23 12:08:27 +1000
1010
class TestLineLogFormatter(tests.TestCaseWithTransport):
1012
def test_line_log(self):
1013
"""Line log should show revno
1017
wt = self.make_branch_and_tree('.')
1019
self.build_tree(['a'])
1021
b.nick = 'test-line-log'
1022
wt.commit(message='add a',
1023
timestamp=1132711707,
1025
committer='Line-Log-Formatter Tester <test@line.log>')
1026
logfile = file('out.tmp', 'w+')
1027
formatter = log.LineLogFormatter(to_file=logfile)
1028
log.show_log(b, formatter)
1031
log_contents = logfile.read()
1032
self.assertEqualDiff('1: Line-Log-Formatte... 2005-11-23 add a\n',
1035
def test_trailing_newlines(self):
1036
wt = self.make_branch_and_tree('.')
1037
b = make_commits_with_trailing_newlines(wt)
1038
sio = self.make_utf8_encoded_stringio()
1039
lf = log.LineLogFormatter(to_file=sio)
1041
self.assertEqualDiff("""\
1042
3: Joe Foo 2005-11-21 single line with trailing newline
1043
2: Joe Bar 2005-11-21 multiline
1044
1: Joe Foo 2005-11-21 simple log message
1048
def _prepare_tree_with_merges(self, with_tags=False):
1049
wt = self.make_branch_and_memory_tree('.')
1051
self.addCleanup(wt.unlock)
1053
wt.commit('rev-1', rev_id='rev-1',
1054
timestamp=1132586655, timezone=36000,
1055
committer='Joe Foo <joe@foo.com>')
1056
wt.commit('rev-merged', rev_id='rev-2a',
1057
timestamp=1132586700, timezone=36000,
1058
committer='Joe Foo <joe@foo.com>')
1059
wt.set_parent_ids(['rev-1', 'rev-2a'])
1060
wt.branch.set_last_revision_info(1, 'rev-1')
1061
wt.commit('rev-2', rev_id='rev-2b',
1062
timestamp=1132586800, timezone=36000,
1063
committer='Joe Foo <joe@foo.com>')
1066
branch.tags.set_tag('v0.2', 'rev-2b')
1067
wt.commit('rev-3', rev_id='rev-3',
1068
timestamp=1132586900, timezone=36000,
1069
committer='Jane Foo <jane@foo.com>')
1070
branch.tags.set_tag('v1.0rc1', 'rev-3')
1071
branch.tags.set_tag('v1.0', 'rev-3')
1074
def test_line_log_single_merge_revision(self):
1075
wt = self._prepare_tree_with_merges()
1076
logfile = self.make_utf8_encoded_stringio()
1077
formatter = log.LineLogFormatter(to_file=logfile)
1078
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
1080
rev = revspec.in_history(wtb)
1081
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
1082
self.assertEqualDiff("""\
1083
1.1.1: Joe Foo 2005-11-22 rev-merged
1087
def test_line_log_with_tags(self):
1088
wt = self._prepare_tree_with_merges(with_tags=True)
1089
logfile = self.make_utf8_encoded_stringio()
1090
formatter = log.LineLogFormatter(to_file=logfile)
1091
log.show_log(wt.branch, formatter)
1092
self.assertEqualDiff("""\
1093
3: Jane Foo 2005-11-22 {v1.0, v1.0rc1} rev-3
1094
2: Joe Foo 2005-11-22 [merge] {v0.2} rev-2
1095
1: Joe Foo 2005-11-22 rev-1
1099
class TestLineLogFormatterWithMergeRevisions(tests.TestCaseWithTransport):
1101
def test_line_merge_revs_log(self):
1102
"""Line log should show revno
1106
wt = self.make_branch_and_tree('.')
1108
self.build_tree(['a'])
1110
b.nick = 'test-line-log'
1111
wt.commit(message='add a',
1112
timestamp=1132711707,
1114
committer='Line-Log-Formatter Tester <test@line.log>')
1115
logfile = file('out.tmp', 'w+')
1116
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1117
log.show_log(b, formatter)
1120
log_contents = logfile.read()
1121
self.assertEqualDiff('1: Line-Log-Formatte... 2005-11-23 add a\n',
1124
def test_line_merge_revs_log_single_merge_revision(self):
1125
wt = self.make_branch_and_memory_tree('.')
1127
self.addCleanup(wt.unlock)
1129
wt.commit('rev-1', rev_id='rev-1',
1130
timestamp=1132586655, timezone=36000,
1131
committer='Joe Foo <joe@foo.com>')
1132
wt.commit('rev-merged', rev_id='rev-2a',
1133
timestamp=1132586700, timezone=36000,
1134
committer='Joe Foo <joe@foo.com>')
1135
wt.set_parent_ids(['rev-1', 'rev-2a'])
1136
wt.branch.set_last_revision_info(1, 'rev-1')
1137
wt.commit('rev-2', rev_id='rev-2b',
1138
timestamp=1132586800, timezone=36000,
1139
committer='Joe Foo <joe@foo.com>')
1140
logfile = self.make_utf8_encoded_stringio()
1141
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1142
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
1144
rev = revspec.in_history(wtb)
1145
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
1146
self.assertEqualDiff("""\
1147
1.1.1: Joe Foo 2005-11-22 rev-merged
1151
def test_line_merge_revs_log_with_merges(self):
1152
wt = self.make_branch_and_memory_tree('.')
1154
self.addCleanup(wt.unlock)
1156
wt.commit('rev-1', rev_id='rev-1',
1157
timestamp=1132586655, timezone=36000,
1158
committer='Joe Foo <joe@foo.com>')
1159
wt.commit('rev-merged', rev_id='rev-2a',
1160
timestamp=1132586700, timezone=36000,
1161
committer='Joe Foo <joe@foo.com>')
1162
wt.set_parent_ids(['rev-1', 'rev-2a'])
1163
wt.branch.set_last_revision_info(1, 'rev-1')
1164
wt.commit('rev-2', rev_id='rev-2b',
1165
timestamp=1132586800, timezone=36000,
1166
committer='Joe Foo <joe@foo.com>')
1167
logfile = self.make_utf8_encoded_stringio()
1168
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1169
log.show_log(wt.branch, formatter)
1170
self.assertEqualDiff("""\
1171
2: Joe Foo 2005-11-22 [merge] rev-2
1172
1.1.1: Joe Foo 2005-11-22 rev-merged
1173
1: Joe Foo 2005-11-22 rev-1
1177
class TestGetViewRevisions(tests.TestCaseWithTransport):
1179
def make_tree_with_commits(self):
1180
"""Create a tree with well-known revision ids"""
1181
wt = self.make_branch_and_tree('tree1')
1182
wt.commit('commit one', rev_id='1')
1183
wt.commit('commit two', rev_id='2')
1184
wt.commit('commit three', rev_id='3')
1185
mainline_revs = [None, '1', '2', '3']
1186
rev_nos = {'1': 1, '2': 2, '3': 3}
1187
return mainline_revs, rev_nos, wt
1189
def make_tree_with_merges(self):
1190
"""Create a tree with well-known revision ids and a merge"""
1191
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1192
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
1193
tree2.commit('four-a', rev_id='4a')
1194
wt.merge_from_branch(tree2.branch)
1195
wt.commit('four-b', rev_id='4b')
1196
mainline_revs.append('4b')
1199
return mainline_revs, rev_nos, wt
1201
def make_tree_with_many_merges(self):
1202
"""Create a tree with well-known revision ids"""
1203
wt = self.make_branch_and_tree('tree1')
1204
self.build_tree_contents([('tree1/f', '1\n')])
1205
wt.add(['f'], ['f-id'])
1206
wt.commit('commit one', rev_id='1')
1207
wt.commit('commit two', rev_id='2')
1209
tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
1210
self.build_tree_contents([('tree3/f', '1\n2\n3a\n')])
1211
tree3.commit('commit three a', rev_id='3a')
1213
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
1214
tree2.merge_from_branch(tree3.branch)
1215
tree2.commit('commit three b', rev_id='3b')
1217
wt.merge_from_branch(tree2.branch)
1218
wt.commit('commit three c', rev_id='3c')
1219
tree2.commit('four-a', rev_id='4a')
1221
wt.merge_from_branch(tree2.branch)
1222
wt.commit('four-b', rev_id='4b')
1224
mainline_revs = [None, '1', '2', '3c', '4b']
1225
rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
1226
full_rev_nos_for_reference = {
1229
'3a': '2.1.1', #first commit tree 3
1230
'3b': '2.2.1', # first commit tree 2
1231
'3c': '3', #merges 3b to main
1232
'4a': '2.2.2', # second commit tree 2
1233
'4b': '4', # merges 4a to main
1235
return mainline_revs, rev_nos, wt
1237
def test_get_view_revisions_forward(self):
1238
"""Test the get_view_revisions method"""
1239
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1241
self.addCleanup(wt.unlock)
1242
revisions = list(log.get_view_revisions(
1243
mainline_revs, rev_nos, wt.branch, 'forward'))
1244
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
1246
revisions2 = list(log.get_view_revisions(
1247
mainline_revs, rev_nos, wt.branch, 'forward',
1248
include_merges=False))
1249
self.assertEqual(revisions, revisions2)
1251
def test_get_view_revisions_reverse(self):
1252
"""Test the get_view_revisions with reverse"""
1253
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1255
self.addCleanup(wt.unlock)
1256
revisions = list(log.get_view_revisions(
1257
mainline_revs, rev_nos, wt.branch, 'reverse'))
1258
self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
1260
revisions2 = list(log.get_view_revisions(
1261
mainline_revs, rev_nos, wt.branch, 'reverse',
1262
include_merges=False))
1263
self.assertEqual(revisions, revisions2)
1265
def test_get_view_revisions_merge(self):
1266
"""Test get_view_revisions when there are merges"""
1267
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
1269
self.addCleanup(wt.unlock)
1270
revisions = list(log.get_view_revisions(
1271
mainline_revs, rev_nos, wt.branch, 'forward'))
1272
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
1273
('4b', '4', 0), ('4a', '3.1.1', 1)],
1275
revisions = list(log.get_view_revisions(
1276
mainline_revs, rev_nos, wt.branch, 'forward',
1277
include_merges=False))
1278
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
1282
def test_get_view_revisions_merge_reverse(self):
1283
"""Test get_view_revisions in reverse when there are merges"""
1284
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
1286
self.addCleanup(wt.unlock)
1287
revisions = list(log.get_view_revisions(
1288
mainline_revs, rev_nos, wt.branch, 'reverse'))
1289
self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
1290
('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
1292
revisions = list(log.get_view_revisions(
1293
mainline_revs, rev_nos, wt.branch, 'reverse',
1294
include_merges=False))
1295
self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
1299
def test_get_view_revisions_merge2(self):
1300
"""Test get_view_revisions when there are merges"""
1301
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
1303
self.addCleanup(wt.unlock)
1304
revisions = list(log.get_view_revisions(
1305
mainline_revs, rev_nos, wt.branch, 'forward'))
1306
expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
1307
('3a', '2.1.1', 1), ('3b', '2.2.1', 1), ('4b', '4', 0),
1309
self.assertEqual(expected, revisions)
1310
revisions = list(log.get_view_revisions(
1311
mainline_revs, rev_nos, wt.branch, 'forward',
1312
include_merges=False))
1313
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
1318
def test_file_id_for_range(self):
1319
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
1321
self.addCleanup(wt.unlock)
1323
def rev_from_rev_id(revid, branch):
1324
revspec = revisionspec.RevisionSpec.from_string('revid:%s' % revid)
1325
return revspec.in_history(branch)
1327
def view_revs(start_rev, end_rev, file_id, direction):
1328
revs = log.calculate_view_revisions(
1330
start_rev, # start_revision
1331
end_rev, # end_revision
1332
direction, # direction
1333
file_id, # specific_fileid
1334
True, # generate_merge_revisions
1338
rev_3a = rev_from_rev_id('3a', wt.branch)
1339
rev_4b = rev_from_rev_id('4b', wt.branch)
1340
self.assertEqual([('3c', '3', 0), ('3a', '2.1.1', 1)],
1341
view_revs(rev_3a, rev_4b, 'f-id', 'reverse'))
1342
# Note: 3c still appears before 3a here because of depth-based sorting
1343
self.assertEqual([('3c', '3', 0), ('3a', '2.1.1', 1)],
1344
view_revs(rev_3a, rev_4b, 'f-id', 'forward'))
1347
class TestGetRevisionsTouchingFileID(tests.TestCaseWithTransport):
1349
def create_tree_with_single_merge(self):
1350
"""Create a branch with a moderate layout.
1352
The revision graph looks like:
1360
In this graph, A introduced files f1 and f2 and f3.
1361
B modifies f1 and f3, and C modifies f2 and f3.
1362
D merges the changes from B and C and resolves the conflict for f3.
1364
# TODO: jam 20070218 This seems like it could really be done
1365
# with make_branch_and_memory_tree() if we could just
1366
# create the content of those files.
1367
# TODO: jam 20070218 Another alternative is that we would really
1368
# like to only create this tree 1 time for all tests that
1369
# use it. Since 'log' only uses the tree in a readonly
1370
# fashion, it seems a shame to regenerate an identical
1371
# tree for each test.
1372
tree = self.make_branch_and_tree('tree')
1374
self.addCleanup(tree.unlock)
1376
self.build_tree_contents([('tree/f1', 'A\n'),
1380
tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
1381
tree.commit('A', rev_id='A')
1383
self.build_tree_contents([('tree/f2', 'A\nC\n'),
1384
('tree/f3', 'A\nC\n'),
1386
tree.commit('C', rev_id='C')
1387
# Revert back to A to build the other history.
1388
tree.set_last_revision('A')
1389
tree.branch.set_last_revision_info(1, 'A')
1390
self.build_tree_contents([('tree/f1', 'A\nB\n'),
1392
('tree/f3', 'A\nB\n'),
1394
tree.commit('B', rev_id='B')
1395
tree.set_parent_ids(['B', 'C'])
1396
self.build_tree_contents([('tree/f1', 'A\nB\n'),
1397
('tree/f2', 'A\nC\n'),
1398
('tree/f3', 'A\nB\nC\n'),
1400
tree.commit('D', rev_id='D')
1402
# Switch to a read lock for this tree.
1403
# We still have an addCleanup(tree.unlock) pending
1408
def check_delta(self, delta, **kw):
1409
"""Check the filenames touched by a delta are as expected.
1411
Caller only have to pass in the list of files for each part, all
1412
unspecified parts are considered empty (and checked as such).
1414
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
1415
# By default we expect an empty list
1416
expected = kw.get(n, [])
1417
# strip out only the path components
1418
got = [x[0] for x in getattr(delta, n)]
1419
self.assertEqual(expected, got)
1421
def test_tree_with_single_merge(self):
1422
"""Make sure the tree layout is correct."""
1423
tree = self.create_tree_with_single_merge()
1424
rev_A_tree = tree.branch.repository.revision_tree('A')
1425
rev_B_tree = tree.branch.repository.revision_tree('B')
1426
rev_C_tree = tree.branch.repository.revision_tree('C')
1427
rev_D_tree = tree.branch.repository.revision_tree('D')
1429
self.check_delta(rev_B_tree.changes_from(rev_A_tree),
1430
modified=['f1', 'f3'])
1432
self.check_delta(rev_C_tree.changes_from(rev_A_tree),
1433
modified=['f2', 'f3'])
1435
self.check_delta(rev_D_tree.changes_from(rev_B_tree),
1436
modified=['f2', 'f3'])
1438
self.check_delta(rev_D_tree.changes_from(rev_C_tree),
1439
modified=['f1', 'f3'])
1441
def assertAllRevisionsForFileID(self, tree, file_id, revisions):
1442
"""Ensure _filter_revisions_touching_file_id returns the right values.
1444
Get the return value from _filter_revisions_touching_file_id and make
1445
sure they are correct.
1447
# The api for _filter_revisions_touching_file_id is a little crazy.
1448
# So we do the setup here.
1449
mainline = tree.branch.revision_history()
1450
mainline.insert(0, None)
1451
revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
1452
view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
1454
actual_revs = log._filter_revisions_touching_file_id(
1457
list(view_revs_iter))
1458
self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
1460
def test_file_id_f1(self):
1461
tree = self.create_tree_with_single_merge()
1462
# f1 should be marked as modified by revisions A and B
1463
self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
1465
def test_file_id_f2(self):
1466
tree = self.create_tree_with_single_merge()
1467
# f2 should be marked as modified by revisions A, C, and D
1468
# because D merged the changes from C.
1469
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1471
def test_file_id_f3(self):
1472
tree = self.create_tree_with_single_merge()
1473
# f3 should be marked as modified by revisions A, B, C, and D
1474
self.assertAllRevisionsForFileID(tree, 'f3-id', ['D', 'C', 'B', 'A'])
1476
def test_file_id_with_ghosts(self):
1477
# This is testing bug #209948, where having a ghost would cause
1478
# _filter_revisions_touching_file_id() to fail.
1479
tree = self.create_tree_with_single_merge()
1480
# We need to add a revision, so switch back to a write-locked tree
1481
# (still a single addCleanup(tree.unlock) pending).
1484
first_parent = tree.last_revision()
1485
tree.set_parent_ids([first_parent, 'ghost-revision-id'])
1486
self.build_tree_contents([('tree/f1', 'A\nB\nXX\n')])
1487
tree.commit('commit with a ghost', rev_id='XX')
1488
self.assertAllRevisionsForFileID(tree, 'f1-id', ['XX', 'B', 'A'])
1489
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1491
def test_unknown_file_id(self):
1492
tree = self.create_tree_with_single_merge()
1493
self.assertAllRevisionsForFileID(tree, 'unknown', [])
1495
def test_empty_branch_unknown_file_id(self):
1496
tree = self.make_branch_and_tree('tree')
1497
self.assertAllRevisionsForFileID(tree, 'unknown', [])
1500
class TestShowChangedRevisions(tests.TestCaseWithTransport):
1502
def test_show_changed_revisions_verbose(self):
1503
tree = self.make_branch_and_tree('tree_a')
1504
self.build_tree(['tree_a/foo'])
1506
tree.commit('bar', rev_id='bar-id')
1507
s = self.make_utf8_encoded_stringio()
1508
log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
1509
self.assertContainsRe(s.getvalue(), 'bar')
1510
self.assertNotContainsRe(s.getvalue(), 'foo')
1513
class TestLogFormatter(tests.TestCase):
1515
def test_short_committer(self):
1516
rev = revision.Revision('a-id')
1517
rev.committer = 'John Doe <jdoe@example.com>'
1518
lf = log.LogFormatter(None)
1519
self.assertEqual('John Doe', lf.short_committer(rev))
1520
rev.committer = 'John Smith <jsmith@example.com>'
1521
self.assertEqual('John Smith', lf.short_committer(rev))
1522
rev.committer = 'John Smith'
1523
self.assertEqual('John Smith', lf.short_committer(rev))
1524
rev.committer = 'jsmith@example.com'
1525
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1526
rev.committer = '<jsmith@example.com>'
1527
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1528
rev.committer = 'John Smith jsmith@example.com'
1529
self.assertEqual('John Smith', lf.short_committer(rev))
1531
def test_short_author(self):
1532
rev = revision.Revision('a-id')
1533
rev.committer = 'John Doe <jdoe@example.com>'
1534
lf = log.LogFormatter(None)
1535
self.assertEqual('John Doe', lf.short_author(rev))
1536
rev.properties['author'] = 'John Smith <jsmith@example.com>'
1537
self.assertEqual('John Smith', lf.short_author(rev))
1538
rev.properties['author'] = 'John Smith'
1539
self.assertEqual('John Smith', lf.short_author(rev))
1540
rev.properties['author'] = 'jsmith@example.com'
1541
self.assertEqual('jsmith@example.com', 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'] = 'John Smith jsmith@example.com'
1545
self.assertEqual('John Smith', lf.short_author(rev))
1546
del rev.properties['author']
1547
rev.properties['authors'] = ('John Smith <jsmith@example.com>\n'
1548
'Jane Rey <jrey@example.com>')
1549
self.assertEqual('John Smith', lf.short_author(rev))
1552
class TestReverseByDepth(tests.TestCase):
1553
"""Test reverse_by_depth behavior.
1555
This is used to present revisions in forward (oldest first) order in a nice
1558
The tests use lighter revision description to ease reading.
1561
def assertReversed(self, forward, backward):
1562
# Transform the descriptions to suit the API: tests use (revno, depth),
1563
# while the API expects (revid, revno, depth)
1564
def complete_revisions(l):
1565
"""Transform the description to suit the API.
1567
Tests use (revno, depth) whil the API expects (revid, revno, depth).
1568
Since the revid is arbitrary, we just duplicate revno
1570
return [ (r, r, d) for r, d in l]
1571
forward = complete_revisions(forward)
1572
backward= complete_revisions(backward)
1573
self.assertEqual(forward, log.reverse_by_depth(backward))
1576
def test_mainline_revisions(self):
1577
self.assertReversed([( '1', 0), ('2', 0)],
1578
[('2', 0), ('1', 0)])
1580
def test_merged_revisions(self):
1581
self.assertReversed([('1', 0), ('2', 0), ('2.2', 1), ('2.1', 1),],
1582
[('2', 0), ('2.1', 1), ('2.2', 1), ('1', 0),])
1583
def test_shifted_merged_revisions(self):
1584
"""Test irregular layout.
1586
Requesting revisions touching a file can produce "holes" in the depths.
1588
self.assertReversed([('1', 0), ('2', 0), ('1.1', 2), ('1.2', 2),],
1589
[('2', 0), ('1.2', 2), ('1.1', 2), ('1', 0),])
1591
def test_merged_without_child_revisions(self):
1592
"""Test irregular layout.
1594
Revision ranges can produce "holes" in the depths.
1596
# When a revision of higher depth doesn't follow one of lower depth, we
1597
# assume a lower depth one is virtually there
1598
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1599
[('4', 4), ('3', 3), ('2', 2), ('1', 2),])
1600
# So we get the same order after reversing below even if the original
1601
# revisions are not in the same order.
1602
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1603
[('3', 3), ('4', 4), ('2', 2), ('1', 2),])
1606
class TestHistoryChange(tests.TestCaseWithTransport):
1608
def setup_a_tree(self):
1609
tree = self.make_branch_and_tree('tree')
1611
self.addCleanup(tree.unlock)
1612
tree.commit('1a', rev_id='1a')
1613
tree.commit('2a', rev_id='2a')
1614
tree.commit('3a', rev_id='3a')
1617
def setup_ab_tree(self):
1618
tree = self.setup_a_tree()
1619
tree.set_last_revision('1a')
1620
tree.branch.set_last_revision_info(1, '1a')
1621
tree.commit('2b', rev_id='2b')
1622
tree.commit('3b', rev_id='3b')
1625
def setup_ac_tree(self):
1626
tree = self.setup_a_tree()
1627
tree.set_last_revision(revision.NULL_REVISION)
1628
tree.branch.set_last_revision_info(0, revision.NULL_REVISION)
1629
tree.commit('1c', rev_id='1c')
1630
tree.commit('2c', rev_id='2c')
1631
tree.commit('3c', rev_id='3c')
1634
def test_all_new(self):
1635
tree = self.setup_ab_tree()
1636
old, new = log.get_history_change('1a', '3a', tree.branch.repository)
1637
self.assertEqual([], old)
1638
self.assertEqual(['2a', '3a'], new)
1640
def test_all_old(self):
1641
tree = self.setup_ab_tree()
1642
old, new = log.get_history_change('3a', '1a', tree.branch.repository)
1643
self.assertEqual([], new)
1644
self.assertEqual(['2a', '3a'], old)
1646
def test_null_old(self):
1647
tree = self.setup_ab_tree()
1648
old, new = log.get_history_change(revision.NULL_REVISION,
1649
'3a', tree.branch.repository)
1650
self.assertEqual([], old)
1651
self.assertEqual(['1a', '2a', '3a'], new)
1653
def test_null_new(self):
1654
tree = self.setup_ab_tree()
1655
old, new = log.get_history_change('3a', revision.NULL_REVISION,
1656
tree.branch.repository)
1657
self.assertEqual([], new)
1658
self.assertEqual(['1a', '2a', '3a'], old)
1660
def test_diverged(self):
1661
tree = self.setup_ab_tree()
1662
old, new = log.get_history_change('3a', '3b', tree.branch.repository)
1663
self.assertEqual(old, ['2a', '3a'])
1664
self.assertEqual(new, ['2b', '3b'])
1666
def test_unrelated(self):
1667
tree = self.setup_ac_tree()
1668
old, new = log.get_history_change('3a', '3c', tree.branch.repository)
1669
self.assertEqual(old, ['1a', '2a', '3a'])
1670
self.assertEqual(new, ['1c', '2c', '3c'])
1672
def test_show_branch_change(self):
1673
tree = self.setup_ab_tree()
1675
log.show_branch_change(tree.branch, s, 3, '3a')
1676
self.assertContainsRe(s.getvalue(),
1677
'[*]{60}\nRemoved Revisions:\n(.|\n)*2a(.|\n)*3a(.|\n)*'
1678
'[*]{60}\n\nAdded Revisions:\n(.|\n)*2b(.|\n)*3b')
1680
def test_show_branch_change_no_change(self):
1681
tree = self.setup_ab_tree()
1683
log.show_branch_change(tree.branch, s, 3, '3b')
1684
self.assertEqual(s.getvalue(),
1685
'Nothing seems to have changed\n')
1687
def test_show_branch_change_no_old(self):
1688
tree = self.setup_ab_tree()
1690
log.show_branch_change(tree.branch, s, 2, '2b')
1691
self.assertContainsRe(s.getvalue(), 'Added Revisions:')
1692
self.assertNotContainsRe(s.getvalue(), 'Removed Revisions:')
1694
def test_show_branch_change_no_new(self):
1695
tree = self.setup_ab_tree()
1696
tree.branch.set_last_revision_info(2, '2b')
1698
log.show_branch_change(tree.branch, s, 3, '3b')
1699
self.assertContainsRe(s.getvalue(), 'Removed Revisions:')
1700
self.assertNotContainsRe(s.getvalue(), 'Added Revisions:')