35
52
We should also test the LogFormatter.
37
57
def __init__(self):
38
58
super(LogCatcher, self).__init__(to_file=None)
42
def show(self, revno, rev, delta):
50
class SimpleLogTest(BzrTestBase):
61
def log_revision(self, revision):
62
self.logs.append(revision)
65
class TestShowLog(tests.TestCaseWithTransport):
51
67
def checkDelta(self, delta, **kw):
52
"""Check the filenames touched by a delta are as expected."""
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).
53
73
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
74
# By default we expect an empty list
54
75
expected = kw.get(n, [])
56
# tests are written with unix paths; fix them up for windows
58
expected = [x.replace('/', os.sep) for x in expected]
60
76
# strip out only the path components
61
77
got = [x[0] for x in getattr(delta, n)]
62
self.assertEquals(expected, got)
66
eq = self.assertEquals
69
b = Branch('.', init=True)
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)
77
b.commit('empty commit')
109
self.assertEqual([], lf.logs)
111
def test_empty_commit(self):
112
wt = self.make_branch_and_tree('.')
114
wt.commit('empty commit')
79
show_log(b, lf, verbose=True)
81
eq(lf.logs[0].revno, 1)
82
eq(lf.logs[0].rev.message, 'empty commit')
84
self.log('log delta: %r' % d)
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')
88
125
self.build_tree(['hello'])
90
b.commit('add one file')
91
# log using regular thing
92
show_log(b, LongLogFormatter(self.TEST_LOG))
94
# get log as data structure
127
wt.commit('add one file',
128
committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
129
u'<test@example.com>')
96
show_log(b, lf, verbose=True)
98
self.log('log entries:')
99
for logentry in lf.logs:
100
self.log('%4d %s' % (logentry.revno, logentry.rev.message))
131
log.show_log(wt.branch, lf, verbose=True)
132
self.assertEqual(2, len(lf.logs))
102
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))
103
186
logentry = lf.logs[0]
104
eq(logentry.revno, 2)
105
eq(logentry.rev.message, 'add one file')
107
self.log('log 2 delta: %r' % d)
108
# self.checkDelta(d, added=['hello'])
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'])
201
def test_merges_nonsupporting_formatter(self):
202
"""Tests that show_log will raise if the formatter doesn't
203
support merge revisions."""
204
wt = self.make_branch_and_memory_tree('.')
206
self.addCleanup(wt.unlock)
208
wt.commit('rev-1', rev_id='rev-1',
209
timestamp=1132586655, timezone=36000,
210
committer='Joe Foo <joe@foo.com>')
211
wt.commit('rev-merged', rev_id='rev-2a',
212
timestamp=1132586700, timezone=36000,
213
committer='Joe Foo <joe@foo.com>')
214
wt.set_parent_ids(['rev-1', 'rev-2a'])
215
wt.branch.set_last_revision_info(1, 'rev-1')
216
wt.commit('rev-2', rev_id='rev-2b',
217
timestamp=1132586800, timezone=36000,
218
committer='Joe Foo <joe@foo.com>')
219
logfile = self.make_utf8_encoded_stringio()
220
formatter = log.ShortLogFormatter(to_file=logfile)
223
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
224
rev = revspec.in_history(wtb)
225
self.assertRaises(errors.BzrCommandError, log.show_log, wtb, lf,
226
start_revision=rev, end_revision=rev)
229
def make_commits_with_trailing_newlines(wt):
230
"""Helper method for LogFormatter tests"""
233
open('a', 'wb').write('hello moto\n')
235
wt.commit('simple log message', rev_id='a1',
236
timestamp=1132586655.459960938, timezone=-6*3600,
237
committer='Joe Foo <joe@foo.com>')
238
open('b', 'wb').write('goodbye\n')
240
wt.commit('multiline\nlog\nmessage\n', rev_id='a2',
241
timestamp=1132586842.411175966, timezone=-6*3600,
242
committer='Joe Foo <joe@foo.com>',
243
authors=['Joe Bar <joe@bar.com>'])
245
open('c', 'wb').write('just another manic monday\n')
247
wt.commit('single line with trailing newline\n', rev_id='a3',
248
timestamp=1132587176.835228920, timezone=-6*3600,
249
committer = 'Joe Foo <joe@foo.com>')
253
def normalize_log(log):
254
"""Replaces the variable lines of logs with fixed lines"""
255
author = 'author: Dolor Sit <test@example.com>'
256
committer = 'committer: Lorem Ipsum <test@example.com>'
257
lines = log.splitlines(True)
258
for idx,line in enumerate(lines):
259
stripped_line = line.lstrip()
260
indent = ' ' * (len(line) - len(stripped_line))
261
if stripped_line.startswith('author:'):
262
lines[idx] = indent + author + '\n'
263
elif stripped_line.startswith('committer:'):
264
lines[idx] = indent + committer + '\n'
265
elif stripped_line.startswith('timestamp:'):
266
lines[idx] = indent + 'timestamp: Just now\n'
267
return ''.join(lines)
270
class TestShortLogFormatter(tests.TestCaseWithTransport):
272
def test_trailing_newlines(self):
273
wt = self.make_branch_and_tree('.')
274
b = make_commits_with_trailing_newlines(wt)
275
sio = self.make_utf8_encoded_stringio()
276
lf = log.ShortLogFormatter(to_file=sio)
278
self.assertEqualDiff("""\
279
3 Joe Foo\t2005-11-21
280
single line with trailing newline
282
2 Joe Bar\t2005-11-21
287
1 Joe Foo\t2005-11-21
293
def _prepare_tree_with_merges(self, with_tags=False):
294
wt = self.make_branch_and_memory_tree('.')
296
self.addCleanup(wt.unlock)
298
wt.commit('rev-1', rev_id='rev-1',
299
timestamp=1132586655, timezone=36000,
300
committer='Joe Foo <joe@foo.com>')
301
wt.commit('rev-merged', rev_id='rev-2a',
302
timestamp=1132586700, timezone=36000,
303
committer='Joe Foo <joe@foo.com>')
304
wt.set_parent_ids(['rev-1', 'rev-2a'])
305
wt.branch.set_last_revision_info(1, 'rev-1')
306
wt.commit('rev-2', rev_id='rev-2b',
307
timestamp=1132586800, timezone=36000,
308
committer='Joe Foo <joe@foo.com>')
311
branch.tags.set_tag('v0.2', 'rev-2b')
312
wt.commit('rev-3', rev_id='rev-3',
313
timestamp=1132586900, timezone=36000,
314
committer='Jane Foo <jane@foo.com>')
315
branch.tags.set_tag('v1.0rc1', 'rev-3')
316
branch.tags.set_tag('v1.0', 'rev-3')
319
def test_short_log_with_merges(self):
320
wt = self._prepare_tree_with_merges()
321
logfile = self.make_utf8_encoded_stringio()
322
formatter = log.ShortLogFormatter(to_file=logfile)
323
log.show_log(wt.branch, formatter)
324
self.assertEqualDiff("""\
325
2 Joe Foo\t2005-11-22 [merge]
328
1 Joe Foo\t2005-11-22
334
def test_short_log_with_merges_and_range(self):
335
wt = self.make_branch_and_memory_tree('.')
337
self.addCleanup(wt.unlock)
339
wt.commit('rev-1', rev_id='rev-1',
340
timestamp=1132586655, timezone=36000,
341
committer='Joe Foo <joe@foo.com>')
342
wt.commit('rev-merged', rev_id='rev-2a',
343
timestamp=1132586700, timezone=36000,
344
committer='Joe Foo <joe@foo.com>')
345
wt.branch.set_last_revision_info(1, 'rev-1')
346
wt.set_parent_ids(['rev-1', 'rev-2a'])
347
wt.commit('rev-2b', rev_id='rev-2b',
348
timestamp=1132586800, timezone=36000,
349
committer='Joe Foo <joe@foo.com>')
350
wt.commit('rev-3a', rev_id='rev-3a',
351
timestamp=1132586800, timezone=36000,
352
committer='Joe Foo <joe@foo.com>')
353
wt.branch.set_last_revision_info(2, 'rev-2b')
354
wt.set_parent_ids(['rev-2b', 'rev-3a'])
355
wt.commit('rev-3b', rev_id='rev-3b',
356
timestamp=1132586800, timezone=36000,
357
committer='Joe Foo <joe@foo.com>')
358
logfile = self.make_utf8_encoded_stringio()
359
formatter = log.ShortLogFormatter(to_file=logfile)
360
log.show_log(wt.branch, formatter,
361
start_revision=2, end_revision=3)
362
self.assertEqualDiff("""\
363
3 Joe Foo\t2005-11-22 [merge]
366
2 Joe Foo\t2005-11-22 [merge]
372
def test_short_log_with_tags(self):
373
wt = self._prepare_tree_with_merges(with_tags=True)
374
logfile = self.make_utf8_encoded_stringio()
375
formatter = log.ShortLogFormatter(to_file=logfile)
376
log.show_log(wt.branch, formatter)
377
self.assertEqualDiff("""\
378
3 Jane Foo\t2005-11-22 {v1.0, v1.0rc1}
381
2 Joe Foo\t2005-11-22 {v0.2} [merge]
384
1 Joe Foo\t2005-11-22
390
def test_short_log_single_merge_revision(self):
391
wt = self.make_branch_and_memory_tree('.')
393
self.addCleanup(wt.unlock)
395
wt.commit('rev-1', rev_id='rev-1',
396
timestamp=1132586655, timezone=36000,
397
committer='Joe Foo <joe@foo.com>')
398
wt.commit('rev-merged', rev_id='rev-2a',
399
timestamp=1132586700, timezone=36000,
400
committer='Joe Foo <joe@foo.com>')
401
wt.set_parent_ids(['rev-1', 'rev-2a'])
402
wt.branch.set_last_revision_info(1, 'rev-1')
403
wt.commit('rev-2', rev_id='rev-2b',
404
timestamp=1132586800, timezone=36000,
405
committer='Joe Foo <joe@foo.com>')
406
logfile = self.make_utf8_encoded_stringio()
407
formatter = log.ShortLogFormatter(to_file=logfile)
408
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
410
rev = revspec.in_history(wtb)
411
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
412
self.assertEqualDiff("""\
413
1.1.1 Joe Foo\t2005-11-22
420
class TestShortLogFormatterWithMergeRevisions(tests.TestCaseWithTransport):
422
def test_short_merge_revs_log_with_merges(self):
423
wt = self.make_branch_and_memory_tree('.')
425
self.addCleanup(wt.unlock)
427
wt.commit('rev-1', rev_id='rev-1',
428
timestamp=1132586655, timezone=36000,
429
committer='Joe Foo <joe@foo.com>')
430
wt.commit('rev-merged', rev_id='rev-2a',
431
timestamp=1132586700, timezone=36000,
432
committer='Joe Foo <joe@foo.com>')
433
wt.set_parent_ids(['rev-1', 'rev-2a'])
434
wt.branch.set_last_revision_info(1, 'rev-1')
435
wt.commit('rev-2', rev_id='rev-2b',
436
timestamp=1132586800, timezone=36000,
437
committer='Joe Foo <joe@foo.com>')
438
logfile = self.make_utf8_encoded_stringio()
439
formatter = log.ShortLogFormatter(to_file=logfile, levels=0)
440
log.show_log(wt.branch, formatter)
441
# Note that the 1.1.1 indenting is in fact correct given that
442
# the revision numbers are right justified within 5 characters
443
# for mainline revnos and 9 characters for dotted revnos.
444
self.assertEqualDiff("""\
445
2 Joe Foo\t2005-11-22 [merge]
448
1.1.1 Joe Foo\t2005-11-22
451
1 Joe Foo\t2005-11-22
457
def test_short_merge_revs_log_single_merge_revision(self):
458
wt = self.make_branch_and_memory_tree('.')
460
self.addCleanup(wt.unlock)
462
wt.commit('rev-1', rev_id='rev-1',
463
timestamp=1132586655, timezone=36000,
464
committer='Joe Foo <joe@foo.com>')
465
wt.commit('rev-merged', rev_id='rev-2a',
466
timestamp=1132586700, timezone=36000,
467
committer='Joe Foo <joe@foo.com>')
468
wt.set_parent_ids(['rev-1', 'rev-2a'])
469
wt.branch.set_last_revision_info(1, 'rev-1')
470
wt.commit('rev-2', rev_id='rev-2b',
471
timestamp=1132586800, timezone=36000,
472
committer='Joe Foo <joe@foo.com>')
473
logfile = self.make_utf8_encoded_stringio()
474
formatter = log.ShortLogFormatter(to_file=logfile, levels=0)
475
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
477
rev = revspec.in_history(wtb)
478
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
479
self.assertEqualDiff("""\
480
1.1.1 Joe Foo\t2005-11-22
487
class TestLongLogFormatter(TestCaseWithoutPropsHandler):
489
def test_verbose_log(self):
490
"""Verbose log includes changed files
494
wt = self.make_branch_and_tree('.')
496
self.build_tree(['a'])
498
# XXX: why does a longer nick show up?
499
b.nick = 'test_verbose_log'
500
wt.commit(message='add a',
501
timestamp=1132711707,
503
committer='Lorem Ipsum <test@example.com>')
504
logfile = file('out.tmp', 'w+')
505
formatter = log.LongLogFormatter(to_file=logfile)
506
log.show_log(b, formatter, verbose=True)
509
log_contents = logfile.read()
510
self.assertEqualDiff('''\
511
------------------------------------------------------------
513
committer: Lorem Ipsum <test@example.com>
514
branch nick: test_verbose_log
515
timestamp: Wed 2005-11-23 12:08:27 +1000
523
def test_merges_are_indented_by_level(self):
524
wt = self.make_branch_and_tree('parent')
525
wt.commit('first post')
526
self.run_bzr('branch parent child')
527
self.run_bzr(['commit', '-m', 'branch 1', '--unchanged', 'child'])
528
self.run_bzr('branch child smallerchild')
529
self.run_bzr(['commit', '-m', 'branch 2', '--unchanged',
532
self.run_bzr('merge ../smallerchild')
533
self.run_bzr(['commit', '-m', 'merge branch 2'])
534
os.chdir('../parent')
535
self.run_bzr('merge ../child')
536
wt.commit('merge branch 1')
538
sio = self.make_utf8_encoded_stringio()
539
lf = log.LongLogFormatter(to_file=sio)
540
log.show_log(b, lf, verbose=True)
541
the_log = normalize_log(sio.getvalue())
542
self.assertEqualDiff("""\
543
------------------------------------------------------------
545
committer: Lorem Ipsum <test@example.com>
550
------------------------------------------------------------
552
committer: Lorem Ipsum <test@example.com>
557
------------------------------------------------------------
559
committer: Lorem Ipsum <test@example.com>
560
branch nick: smallerchild
564
------------------------------------------------------------
566
committer: Lorem Ipsum <test@example.com>
571
------------------------------------------------------------
573
committer: Lorem Ipsum <test@example.com>
581
def test_verbose_merge_revisions_contain_deltas(self):
582
wt = self.make_branch_and_tree('parent')
583
self.build_tree(['parent/f1', 'parent/f2'])
585
wt.commit('first post')
586
self.run_bzr('branch parent child')
587
os.unlink('child/f1')
588
file('child/f2', 'wb').write('hello\n')
589
self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
592
self.run_bzr('merge ../child')
593
wt.commit('merge branch 1')
595
sio = self.make_utf8_encoded_stringio()
596
lf = log.LongLogFormatter(to_file=sio)
597
log.show_log(b, lf, verbose=True)
598
the_log = normalize_log(sio.getvalue())
599
self.assertEqualDiff("""\
600
------------------------------------------------------------
602
committer: Lorem Ipsum <test@example.com>
611
------------------------------------------------------------
613
committer: Lorem Ipsum <test@example.com>
617
removed f1 and modified f2
622
------------------------------------------------------------
624
committer: Lorem Ipsum <test@example.com>
635
def test_trailing_newlines(self):
636
wt = self.make_branch_and_tree('.')
637
b = make_commits_with_trailing_newlines(wt)
638
sio = self.make_utf8_encoded_stringio()
639
lf = log.LongLogFormatter(to_file=sio)
641
self.assertEqualDiff("""\
642
------------------------------------------------------------
644
committer: Joe Foo <joe@foo.com>
646
timestamp: Mon 2005-11-21 09:32:56 -0600
648
single line with trailing newline
649
------------------------------------------------------------
651
author: Joe Bar <joe@bar.com>
652
committer: Joe Foo <joe@foo.com>
654
timestamp: Mon 2005-11-21 09:27:22 -0600
659
------------------------------------------------------------
661
committer: Joe Foo <joe@foo.com>
663
timestamp: Mon 2005-11-21 09:24:15 -0600
669
def test_author_in_log(self):
670
"""Log includes the author name if it's set in
671
the revision properties
673
wt = self.make_branch_and_tree('.')
675
self.build_tree(['a'])
677
b.nick = 'test_author_log'
678
wt.commit(message='add a',
679
timestamp=1132711707,
681
committer='Lorem Ipsum <test@example.com>',
682
authors=['John Doe <jdoe@example.com>',
683
'Jane Rey <jrey@example.com>'])
685
formatter = log.LongLogFormatter(to_file=sio)
686
log.show_log(b, formatter)
687
self.assertEqualDiff('''\
688
------------------------------------------------------------
690
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
691
committer: Lorem Ipsum <test@example.com>
692
branch nick: test_author_log
693
timestamp: Wed 2005-11-23 12:08:27 +1000
699
def test_properties_in_log(self):
700
"""Log includes the custom properties returned by the registered
703
wt = self.make_branch_and_tree('.')
705
self.build_tree(['a'])
707
b.nick = 'test_properties_in_log'
708
wt.commit(message='add a',
709
timestamp=1132711707,
711
committer='Lorem Ipsum <test@example.com>',
712
authors=['John Doe <jdoe@example.com>'])
714
formatter = log.LongLogFormatter(to_file=sio)
716
def trivial_custom_prop_handler(revision):
717
return {'test_prop':'test_value'}
719
log.properties_handler_registry.register(
720
'trivial_custom_prop_handler',
721
trivial_custom_prop_handler)
722
log.show_log(b, formatter)
724
log.properties_handler_registry.remove(
725
'trivial_custom_prop_handler')
726
self.assertEqualDiff('''\
727
------------------------------------------------------------
729
test_prop: test_value
730
author: John Doe <jdoe@example.com>
731
committer: Lorem Ipsum <test@example.com>
732
branch nick: test_properties_in_log
733
timestamp: Wed 2005-11-23 12:08:27 +1000
739
def test_properties_in_short_log(self):
740
"""Log includes the custom properties returned by the registered
743
wt = self.make_branch_and_tree('.')
745
self.build_tree(['a'])
747
b.nick = 'test_properties_in_short_log'
748
wt.commit(message='add a',
749
timestamp=1132711707,
751
committer='Lorem Ipsum <test@example.com>',
752
authors=['John Doe <jdoe@example.com>'])
754
formatter = log.ShortLogFormatter(to_file=sio)
756
def trivial_custom_prop_handler(revision):
757
return {'test_prop':'test_value'}
759
log.properties_handler_registry.register(
760
'trivial_custom_prop_handler',
761
trivial_custom_prop_handler)
762
log.show_log(b, formatter)
764
log.properties_handler_registry.remove(
765
'trivial_custom_prop_handler')
766
self.assertEqualDiff('''\
767
1 John Doe\t2005-11-23
768
test_prop: test_value
774
def test_error_in_properties_handler(self):
775
"""Log includes the custom properties returned by the registered
778
wt = self.make_branch_and_tree('.')
780
self.build_tree(['a'])
782
b.nick = 'test_author_log'
783
wt.commit(message='add a',
784
timestamp=1132711707,
786
committer='Lorem Ipsum <test@example.com>',
787
authors=['John Doe <jdoe@example.com>'],
788
revprops={'first_prop':'first_value'})
790
formatter = log.LongLogFormatter(to_file=sio)
792
def trivial_custom_prop_handler(revision):
793
raise StandardError("a test error")
795
log.properties_handler_registry.register(
796
'trivial_custom_prop_handler',
797
trivial_custom_prop_handler)
798
self.assertRaises(StandardError, log.show_log, b, formatter,)
800
log.properties_handler_registry.remove(
801
'trivial_custom_prop_handler')
803
def test_properties_handler_bad_argument(self):
804
wt = self.make_branch_and_tree('.')
806
self.build_tree(['a'])
808
b.nick = 'test_author_log'
809
wt.commit(message='add a',
810
timestamp=1132711707,
812
committer='Lorem Ipsum <test@example.com>',
813
authors=['John Doe <jdoe@example.com>'],
814
revprops={'a_prop':'test_value'})
816
formatter = log.LongLogFormatter(to_file=sio)
818
def bad_argument_prop_handler(revision):
819
return {'custom_prop_name':revision.properties['a_prop']}
821
log.properties_handler_registry.register(
822
'bad_argument_prop_handler',
823
bad_argument_prop_handler)
825
self.assertRaises(AttributeError, formatter.show_properties,
828
revision = b.repository.get_revision(b.last_revision())
829
formatter.show_properties(revision, '')
830
self.assertEqualDiff('''custom_prop_name: test_value\n''',
833
log.properties_handler_registry.remove(
834
'bad_argument_prop_handler')
837
class TestLongLogFormatterWithoutMergeRevisions(TestCaseWithoutPropsHandler):
839
def test_long_verbose_log(self):
840
"""Verbose log includes changed files
844
wt = self.make_branch_and_tree('.')
846
self.build_tree(['a'])
848
# XXX: why does a longer nick show up?
849
b.nick = 'test_verbose_log'
850
wt.commit(message='add a',
851
timestamp=1132711707,
853
committer='Lorem Ipsum <test@example.com>')
854
logfile = file('out.tmp', 'w+')
855
formatter = log.LongLogFormatter(to_file=logfile, levels=1)
856
log.show_log(b, formatter, verbose=True)
859
log_contents = logfile.read()
860
self.assertEqualDiff('''\
861
------------------------------------------------------------
863
committer: Lorem Ipsum <test@example.com>
864
branch nick: test_verbose_log
865
timestamp: Wed 2005-11-23 12:08:27 +1000
873
def test_long_verbose_contain_deltas(self):
874
wt = self.make_branch_and_tree('parent')
875
self.build_tree(['parent/f1', 'parent/f2'])
877
wt.commit('first post')
878
self.run_bzr('branch parent child')
879
os.unlink('child/f1')
880
file('child/f2', 'wb').write('hello\n')
881
self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
884
self.run_bzr('merge ../child')
885
wt.commit('merge branch 1')
887
sio = self.make_utf8_encoded_stringio()
888
lf = log.LongLogFormatter(to_file=sio, levels=1)
889
log.show_log(b, lf, verbose=True)
890
the_log = normalize_log(sio.getvalue())
891
self.assertEqualDiff("""\
892
------------------------------------------------------------
894
committer: Lorem Ipsum <test@example.com>
903
------------------------------------------------------------
905
committer: Lorem Ipsum <test@example.com>
916
def test_long_trailing_newlines(self):
917
wt = self.make_branch_and_tree('.')
918
b = make_commits_with_trailing_newlines(wt)
919
sio = self.make_utf8_encoded_stringio()
920
lf = log.LongLogFormatter(to_file=sio, levels=1)
922
self.assertEqualDiff("""\
923
------------------------------------------------------------
925
committer: Joe Foo <joe@foo.com>
927
timestamp: Mon 2005-11-21 09:32:56 -0600
929
single line with trailing newline
930
------------------------------------------------------------
932
author: Joe Bar <joe@bar.com>
933
committer: Joe Foo <joe@foo.com>
935
timestamp: Mon 2005-11-21 09:27:22 -0600
940
------------------------------------------------------------
942
committer: Joe Foo <joe@foo.com>
944
timestamp: Mon 2005-11-21 09:24:15 -0600
950
def test_long_author_in_log(self):
951
"""Log includes the author name if it's set in
952
the revision properties
954
wt = self.make_branch_and_tree('.')
956
self.build_tree(['a'])
958
b.nick = 'test_author_log'
959
wt.commit(message='add a',
960
timestamp=1132711707,
962
committer='Lorem Ipsum <test@example.com>',
963
authors=['John Doe <jdoe@example.com>'])
965
formatter = log.LongLogFormatter(to_file=sio, levels=1)
966
log.show_log(b, formatter)
967
self.assertEqualDiff('''\
968
------------------------------------------------------------
970
author: John Doe <jdoe@example.com>
971
committer: Lorem Ipsum <test@example.com>
972
branch nick: test_author_log
973
timestamp: Wed 2005-11-23 12:08:27 +1000
979
def test_long_properties_in_log(self):
980
"""Log includes the custom properties returned by the registered
983
wt = self.make_branch_and_tree('.')
985
self.build_tree(['a'])
987
b.nick = 'test_properties_in_log'
988
wt.commit(message='add a',
989
timestamp=1132711707,
991
committer='Lorem Ipsum <test@example.com>',
992
authors=['John Doe <jdoe@example.com>'])
994
formatter = log.LongLogFormatter(to_file=sio, levels=1)
996
def trivial_custom_prop_handler(revision):
997
return {'test_prop':'test_value'}
999
log.properties_handler_registry.register(
1000
'trivial_custom_prop_handler',
1001
trivial_custom_prop_handler)
1002
log.show_log(b, formatter)
1004
log.properties_handler_registry.remove(
1005
'trivial_custom_prop_handler')
1006
self.assertEqualDiff('''\
1007
------------------------------------------------------------
1009
test_prop: test_value
1010
author: John Doe <jdoe@example.com>
1011
committer: Lorem Ipsum <test@example.com>
1012
branch nick: test_properties_in_log
1013
timestamp: Wed 2005-11-23 12:08:27 +1000
1020
class TestLineLogFormatter(tests.TestCaseWithTransport):
1022
def test_line_log(self):
1023
"""Line log should show revno
1027
wt = self.make_branch_and_tree('.')
1029
self.build_tree(['a'])
1031
b.nick = 'test-line-log'
1032
wt.commit(message='add a',
1033
timestamp=1132711707,
1035
committer='Line-Log-Formatter Tester <test@line.log>')
1036
logfile = file('out.tmp', 'w+')
1037
formatter = log.LineLogFormatter(to_file=logfile)
1038
log.show_log(b, formatter)
1041
log_contents = logfile.read()
1042
self.assertEqualDiff('1: Line-Log-Formatte... 2005-11-23 add a\n',
1045
def test_trailing_newlines(self):
1046
wt = self.make_branch_and_tree('.')
1047
b = make_commits_with_trailing_newlines(wt)
1048
sio = self.make_utf8_encoded_stringio()
1049
lf = log.LineLogFormatter(to_file=sio)
1051
self.assertEqualDiff("""\
1052
3: Joe Foo 2005-11-21 single line with trailing newline
1053
2: Joe Bar 2005-11-21 multiline
1054
1: Joe Foo 2005-11-21 simple log message
1058
def _prepare_tree_with_merges(self, with_tags=False):
1059
wt = self.make_branch_and_memory_tree('.')
1061
self.addCleanup(wt.unlock)
1063
wt.commit('rev-1', rev_id='rev-1',
1064
timestamp=1132586655, timezone=36000,
1065
committer='Joe Foo <joe@foo.com>')
1066
wt.commit('rev-merged', rev_id='rev-2a',
1067
timestamp=1132586700, timezone=36000,
1068
committer='Joe Foo <joe@foo.com>')
1069
wt.set_parent_ids(['rev-1', 'rev-2a'])
1070
wt.branch.set_last_revision_info(1, 'rev-1')
1071
wt.commit('rev-2', rev_id='rev-2b',
1072
timestamp=1132586800, timezone=36000,
1073
committer='Joe Foo <joe@foo.com>')
1076
branch.tags.set_tag('v0.2', 'rev-2b')
1077
wt.commit('rev-3', rev_id='rev-3',
1078
timestamp=1132586900, timezone=36000,
1079
committer='Jane Foo <jane@foo.com>')
1080
branch.tags.set_tag('v1.0rc1', 'rev-3')
1081
branch.tags.set_tag('v1.0', 'rev-3')
1084
def test_line_log_single_merge_revision(self):
1085
wt = self._prepare_tree_with_merges()
1086
logfile = self.make_utf8_encoded_stringio()
1087
formatter = log.LineLogFormatter(to_file=logfile)
1088
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
1090
rev = revspec.in_history(wtb)
1091
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
1092
self.assertEqualDiff("""\
1093
1.1.1: Joe Foo 2005-11-22 rev-merged
1097
def test_line_log_with_tags(self):
1098
wt = self._prepare_tree_with_merges(with_tags=True)
1099
logfile = self.make_utf8_encoded_stringio()
1100
formatter = log.LineLogFormatter(to_file=logfile)
1101
log.show_log(wt.branch, formatter)
1102
self.assertEqualDiff("""\
1103
3: Jane Foo 2005-11-22 {v1.0, v1.0rc1} rev-3
1104
2: Joe Foo 2005-11-22 [merge] {v0.2} rev-2
1105
1: Joe Foo 2005-11-22 rev-1
1109
class TestLineLogFormatterWithMergeRevisions(tests.TestCaseWithTransport):
1111
def test_line_merge_revs_log(self):
1112
"""Line log should show revno
1116
wt = self.make_branch_and_tree('.')
1118
self.build_tree(['a'])
1120
b.nick = 'test-line-log'
1121
wt.commit(message='add a',
1122
timestamp=1132711707,
1124
committer='Line-Log-Formatter Tester <test@line.log>')
1125
logfile = file('out.tmp', 'w+')
1126
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1127
log.show_log(b, formatter)
1130
log_contents = logfile.read()
1131
self.assertEqualDiff('1: Line-Log-Formatte... 2005-11-23 add a\n',
1134
def test_line_merge_revs_log_single_merge_revision(self):
1135
wt = self.make_branch_and_memory_tree('.')
1137
self.addCleanup(wt.unlock)
1139
wt.commit('rev-1', rev_id='rev-1',
1140
timestamp=1132586655, timezone=36000,
1141
committer='Joe Foo <joe@foo.com>')
1142
wt.commit('rev-merged', rev_id='rev-2a',
1143
timestamp=1132586700, timezone=36000,
1144
committer='Joe Foo <joe@foo.com>')
1145
wt.set_parent_ids(['rev-1', 'rev-2a'])
1146
wt.branch.set_last_revision_info(1, 'rev-1')
1147
wt.commit('rev-2', rev_id='rev-2b',
1148
timestamp=1132586800, timezone=36000,
1149
committer='Joe Foo <joe@foo.com>')
1150
logfile = self.make_utf8_encoded_stringio()
1151
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1152
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
1154
rev = revspec.in_history(wtb)
1155
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
1156
self.assertEqualDiff("""\
1157
1.1.1: Joe Foo 2005-11-22 rev-merged
1161
def test_line_merge_revs_log_with_merges(self):
1162
wt = self.make_branch_and_memory_tree('.')
1164
self.addCleanup(wt.unlock)
1166
wt.commit('rev-1', rev_id='rev-1',
1167
timestamp=1132586655, timezone=36000,
1168
committer='Joe Foo <joe@foo.com>')
1169
wt.commit('rev-merged', rev_id='rev-2a',
1170
timestamp=1132586700, timezone=36000,
1171
committer='Joe Foo <joe@foo.com>')
1172
wt.set_parent_ids(['rev-1', 'rev-2a'])
1173
wt.branch.set_last_revision_info(1, 'rev-1')
1174
wt.commit('rev-2', rev_id='rev-2b',
1175
timestamp=1132586800, timezone=36000,
1176
committer='Joe Foo <joe@foo.com>')
1177
logfile = self.make_utf8_encoded_stringio()
1178
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1179
log.show_log(wt.branch, formatter)
1180
self.assertEqualDiff("""\
1181
2: Joe Foo 2005-11-22 [merge] rev-2
1182
1.1.1: Joe Foo 2005-11-22 rev-merged
1183
1: Joe Foo 2005-11-22 rev-1
1187
class TestGetViewRevisions(tests.TestCaseWithTransport):
1189
def make_tree_with_commits(self):
1190
"""Create a tree with well-known revision ids"""
1191
wt = self.make_branch_and_tree('tree1')
1192
wt.commit('commit one', rev_id='1')
1193
wt.commit('commit two', rev_id='2')
1194
wt.commit('commit three', rev_id='3')
1195
mainline_revs = [None, '1', '2', '3']
1196
rev_nos = {'1': 1, '2': 2, '3': 3}
1197
return mainline_revs, rev_nos, wt
1199
def make_tree_with_merges(self):
1200
"""Create a tree with well-known revision ids and a merge"""
1201
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1202
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
1203
tree2.commit('four-a', rev_id='4a')
1204
wt.merge_from_branch(tree2.branch)
1205
wt.commit('four-b', rev_id='4b')
1206
mainline_revs.append('4b')
1209
return mainline_revs, rev_nos, wt
1211
def make_tree_with_many_merges(self):
1212
"""Create a tree with well-known revision ids"""
1213
wt = self.make_branch_and_tree('tree1')
1214
self.build_tree_contents([('tree1/f', '1\n')])
1215
wt.add(['f'], ['f-id'])
1216
wt.commit('commit one', rev_id='1')
1217
wt.commit('commit two', rev_id='2')
1219
tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
1220
self.build_tree_contents([('tree3/f', '1\n2\n3a\n')])
1221
tree3.commit('commit three a', rev_id='3a')
1223
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
1224
tree2.merge_from_branch(tree3.branch)
1225
tree2.commit('commit three b', rev_id='3b')
1227
wt.merge_from_branch(tree2.branch)
1228
wt.commit('commit three c', rev_id='3c')
1229
tree2.commit('four-a', rev_id='4a')
1231
wt.merge_from_branch(tree2.branch)
1232
wt.commit('four-b', rev_id='4b')
1234
mainline_revs = [None, '1', '2', '3c', '4b']
1235
rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
1236
full_rev_nos_for_reference = {
1239
'3a': '2.1.1', #first commit tree 3
1240
'3b': '2.2.1', # first commit tree 2
1241
'3c': '3', #merges 3b to main
1242
'4a': '2.2.2', # second commit tree 2
1243
'4b': '4', # merges 4a to main
1245
return mainline_revs, rev_nos, wt
1247
def test_get_view_revisions_forward(self):
1248
"""Test the get_view_revisions method"""
1249
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1251
self.addCleanup(wt.unlock)
1252
revisions = list(log.get_view_revisions(
1253
mainline_revs, rev_nos, wt.branch, 'forward'))
1254
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
1256
revisions2 = list(log.get_view_revisions(
1257
mainline_revs, rev_nos, wt.branch, 'forward',
1258
include_merges=False))
1259
self.assertEqual(revisions, revisions2)
1261
def test_get_view_revisions_reverse(self):
1262
"""Test the get_view_revisions with reverse"""
1263
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1265
self.addCleanup(wt.unlock)
1266
revisions = list(log.get_view_revisions(
1267
mainline_revs, rev_nos, wt.branch, 'reverse'))
1268
self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
1270
revisions2 = list(log.get_view_revisions(
1271
mainline_revs, rev_nos, wt.branch, 'reverse',
1272
include_merges=False))
1273
self.assertEqual(revisions, revisions2)
1275
def test_get_view_revisions_merge(self):
1276
"""Test get_view_revisions when there are merges"""
1277
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
1279
self.addCleanup(wt.unlock)
1280
revisions = list(log.get_view_revisions(
1281
mainline_revs, rev_nos, wt.branch, 'forward'))
1282
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
1283
('4b', '4', 0), ('4a', '3.1.1', 1)],
1285
revisions = list(log.get_view_revisions(
1286
mainline_revs, rev_nos, wt.branch, 'forward',
1287
include_merges=False))
1288
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
1292
def test_get_view_revisions_merge_reverse(self):
1293
"""Test get_view_revisions in reverse when there are merges"""
1294
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
1296
self.addCleanup(wt.unlock)
1297
revisions = list(log.get_view_revisions(
1298
mainline_revs, rev_nos, wt.branch, 'reverse'))
1299
self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
1300
('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
1302
revisions = list(log.get_view_revisions(
1303
mainline_revs, rev_nos, wt.branch, 'reverse',
1304
include_merges=False))
1305
self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
1309
def test_get_view_revisions_merge2(self):
1310
"""Test get_view_revisions when there are merges"""
1311
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
1313
self.addCleanup(wt.unlock)
1314
revisions = list(log.get_view_revisions(
1315
mainline_revs, rev_nos, wt.branch, 'forward'))
1316
expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
1317
('3a', '2.1.1', 1), ('3b', '2.2.1', 1), ('4b', '4', 0),
1319
self.assertEqual(expected, revisions)
1320
revisions = list(log.get_view_revisions(
1321
mainline_revs, rev_nos, wt.branch, 'forward',
1322
include_merges=False))
1323
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
1328
def test_file_id_for_range(self):
1329
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
1331
self.addCleanup(wt.unlock)
1333
def rev_from_rev_id(revid, branch):
1334
revspec = revisionspec.RevisionSpec.from_string('revid:%s' % revid)
1335
return revspec.in_history(branch)
1337
def view_revs(start_rev, end_rev, file_id, direction):
1338
revs = log.calculate_view_revisions(
1340
start_rev, # start_revision
1341
end_rev, # end_revision
1342
direction, # direction
1343
file_id, # specific_fileid
1344
True, # generate_merge_revisions
1345
True, # allow_single_merge_revision
1349
rev_3a = rev_from_rev_id('3a', wt.branch)
1350
rev_4b = rev_from_rev_id('4b', wt.branch)
1351
self.assertEqual([('3c', '3', 0), ('3a', '2.1.1', 1)],
1352
view_revs(rev_3a, rev_4b, 'f-id', 'reverse'))
1353
# Note: 3c still appears before 3a here because of depth-based sorting
1354
self.assertEqual([('3c', '3', 0), ('3a', '2.1.1', 1)],
1355
view_revs(rev_3a, rev_4b, 'f-id', 'forward'))
1358
class TestGetRevisionsTouchingFileID(tests.TestCaseWithTransport):
1360
def create_tree_with_single_merge(self):
1361
"""Create a branch with a moderate layout.
1363
The revision graph looks like:
1371
In this graph, A introduced files f1 and f2 and f3.
1372
B modifies f1 and f3, and C modifies f2 and f3.
1373
D merges the changes from B and C and resolves the conflict for f3.
1375
# TODO: jam 20070218 This seems like it could really be done
1376
# with make_branch_and_memory_tree() if we could just
1377
# create the content of those files.
1378
# TODO: jam 20070218 Another alternative is that we would really
1379
# like to only create this tree 1 time for all tests that
1380
# use it. Since 'log' only uses the tree in a readonly
1381
# fashion, it seems a shame to regenerate an identical
1382
# tree for each test.
1383
tree = self.make_branch_and_tree('tree')
1385
self.addCleanup(tree.unlock)
1387
self.build_tree_contents([('tree/f1', 'A\n'),
1391
tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
1392
tree.commit('A', rev_id='A')
1394
self.build_tree_contents([('tree/f2', 'A\nC\n'),
1395
('tree/f3', 'A\nC\n'),
1397
tree.commit('C', rev_id='C')
1398
# Revert back to A to build the other history.
1399
tree.set_last_revision('A')
1400
tree.branch.set_last_revision_info(1, 'A')
1401
self.build_tree_contents([('tree/f1', 'A\nB\n'),
1403
('tree/f3', 'A\nB\n'),
1405
tree.commit('B', rev_id='B')
1406
tree.set_parent_ids(['B', 'C'])
1407
self.build_tree_contents([('tree/f1', 'A\nB\n'),
1408
('tree/f2', 'A\nC\n'),
1409
('tree/f3', 'A\nB\nC\n'),
1411
tree.commit('D', rev_id='D')
1413
# Switch to a read lock for this tree.
1414
# We still have an addCleanup(tree.unlock) pending
1419
def check_delta(self, delta, **kw):
1420
"""Check the filenames touched by a delta are as expected.
1422
Caller only have to pass in the list of files for each part, all
1423
unspecified parts are considered empty (and checked as such).
1425
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
1426
# By default we expect an empty list
1427
expected = kw.get(n, [])
1428
# strip out only the path components
1429
got = [x[0] for x in getattr(delta, n)]
1430
self.assertEqual(expected, got)
1432
def test_tree_with_single_merge(self):
1433
"""Make sure the tree layout is correct."""
1434
tree = self.create_tree_with_single_merge()
1435
rev_A_tree = tree.branch.repository.revision_tree('A')
1436
rev_B_tree = tree.branch.repository.revision_tree('B')
1437
rev_C_tree = tree.branch.repository.revision_tree('C')
1438
rev_D_tree = tree.branch.repository.revision_tree('D')
1440
self.check_delta(rev_B_tree.changes_from(rev_A_tree),
1441
modified=['f1', 'f3'])
1443
self.check_delta(rev_C_tree.changes_from(rev_A_tree),
1444
modified=['f2', 'f3'])
1446
self.check_delta(rev_D_tree.changes_from(rev_B_tree),
1447
modified=['f2', 'f3'])
1449
self.check_delta(rev_D_tree.changes_from(rev_C_tree),
1450
modified=['f1', 'f3'])
1452
def assertAllRevisionsForFileID(self, tree, file_id, revisions):
1453
"""Ensure _filter_revisions_touching_file_id returns the right values.
1455
Get the return value from _filter_revisions_touching_file_id and make
1456
sure they are correct.
1458
# The api for _filter_revisions_touching_file_id is a little crazy.
1459
# So we do the setup here.
1460
mainline = tree.branch.revision_history()
1461
mainline.insert(0, None)
1462
revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
1463
view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
1465
actual_revs = log._filter_revisions_touching_file_id(
1468
list(view_revs_iter))
1469
self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
1471
def test_file_id_f1(self):
1472
tree = self.create_tree_with_single_merge()
1473
# f1 should be marked as modified by revisions A and B
1474
self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
1476
def test_file_id_f2(self):
1477
tree = self.create_tree_with_single_merge()
1478
# f2 should be marked as modified by revisions A, C, and D
1479
# because D merged the changes from C.
1480
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1482
def test_file_id_f3(self):
1483
tree = self.create_tree_with_single_merge()
1484
# f3 should be marked as modified by revisions A, B, C, and D
1485
self.assertAllRevisionsForFileID(tree, 'f3-id', ['D', 'C', 'B', 'A'])
1487
def test_file_id_with_ghosts(self):
1488
# This is testing bug #209948, where having a ghost would cause
1489
# _filter_revisions_touching_file_id() to fail.
1490
tree = self.create_tree_with_single_merge()
1491
# We need to add a revision, so switch back to a write-locked tree
1492
# (still a single addCleanup(tree.unlock) pending).
1495
first_parent = tree.last_revision()
1496
tree.set_parent_ids([first_parent, 'ghost-revision-id'])
1497
self.build_tree_contents([('tree/f1', 'A\nB\nXX\n')])
1498
tree.commit('commit with a ghost', rev_id='XX')
1499
self.assertAllRevisionsForFileID(tree, 'f1-id', ['XX', 'B', 'A'])
1500
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1503
class TestShowChangedRevisions(tests.TestCaseWithTransport):
1505
def test_show_changed_revisions_verbose(self):
1506
tree = self.make_branch_and_tree('tree_a')
1507
self.build_tree(['tree_a/foo'])
1509
tree.commit('bar', rev_id='bar-id')
1510
s = self.make_utf8_encoded_stringio()
1511
log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
1512
self.assertContainsRe(s.getvalue(), 'bar')
1513
self.assertNotContainsRe(s.getvalue(), 'foo')
1516
class TestLogFormatter(tests.TestCase):
1518
def test_short_committer(self):
1519
rev = revision.Revision('a-id')
1520
rev.committer = 'John Doe <jdoe@example.com>'
1521
lf = log.LogFormatter(None)
1522
self.assertEqual('John Doe', lf.short_committer(rev))
1523
rev.committer = 'John Smith <jsmith@example.com>'
1524
self.assertEqual('John Smith', lf.short_committer(rev))
1525
rev.committer = 'John Smith'
1526
self.assertEqual('John Smith', lf.short_committer(rev))
1527
rev.committer = 'jsmith@example.com'
1528
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1529
rev.committer = '<jsmith@example.com>'
1530
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1531
rev.committer = 'John Smith jsmith@example.com'
1532
self.assertEqual('John Smith', lf.short_committer(rev))
1534
def test_short_author(self):
1535
rev = revision.Revision('a-id')
1536
rev.committer = 'John Doe <jdoe@example.com>'
1537
lf = log.LogFormatter(None)
1538
self.assertEqual('John Doe', lf.short_author(rev))
1539
rev.properties['author'] = 'John Smith <jsmith@example.com>'
1540
self.assertEqual('John Smith', lf.short_author(rev))
1541
rev.properties['author'] = 'John Smith'
1542
self.assertEqual('John Smith', lf.short_author(rev))
1543
rev.properties['author'] = 'jsmith@example.com'
1544
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1545
rev.properties['author'] = '<jsmith@example.com>'
1546
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1547
rev.properties['author'] = 'John Smith jsmith@example.com'
1548
self.assertEqual('John Smith', lf.short_author(rev))
1549
del rev.properties['author']
1550
rev.properties['authors'] = ('John Smith <jsmith@example.com>\n'
1551
'Jane Rey <jrey@example.com>')
1552
self.assertEqual('John Smith', lf.short_author(rev))
1555
class TestReverseByDepth(tests.TestCase):
1556
"""Test reverse_by_depth behavior.
1558
This is used to present revisions in forward (oldest first) order in a nice
1561
The tests use lighter revision description to ease reading.
1564
def assertReversed(self, forward, backward):
1565
# Transform the descriptions to suit the API: tests use (revno, depth),
1566
# while the API expects (revid, revno, depth)
1567
def complete_revisions(l):
1568
"""Transform the description to suit the API.
1570
Tests use (revno, depth) whil the API expects (revid, revno, depth).
1571
Since the revid is arbitrary, we just duplicate revno
1573
return [ (r, r, d) for r, d in l]
1574
forward = complete_revisions(forward)
1575
backward= complete_revisions(backward)
1576
self.assertEqual(forward, log.reverse_by_depth(backward))
1579
def test_mainline_revisions(self):
1580
self.assertReversed([( '1', 0), ('2', 0)],
1581
[('2', 0), ('1', 0)])
1583
def test_merged_revisions(self):
1584
self.assertReversed([('1', 0), ('2', 0), ('2.2', 1), ('2.1', 1),],
1585
[('2', 0), ('2.1', 1), ('2.2', 1), ('1', 0),])
1586
def test_shifted_merged_revisions(self):
1587
"""Test irregular layout.
1589
Requesting revisions touching a file can produce "holes" in the depths.
1591
self.assertReversed([('1', 0), ('2', 0), ('1.1', 2), ('1.2', 2),],
1592
[('2', 0), ('1.2', 2), ('1.1', 2), ('1', 0),])
1594
def test_merged_without_child_revisions(self):
1595
"""Test irregular layout.
1597
Revision ranges can produce "holes" in the depths.
1599
# When a revision of higher depth doesn't follow one of lower depth, we
1600
# assume a lower depth one is virtually there
1601
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1602
[('4', 4), ('3', 3), ('2', 2), ('1', 2),])
1603
# So we get the same order after reversing below even if the original
1604
# revisions are not in the same order.
1605
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1606
[('3', 3), ('4', 4), ('2', 2), ('1', 2),])
1609
class TestHistoryChange(tests.TestCaseWithTransport):
1611
def setup_a_tree(self):
1612
tree = self.make_branch_and_tree('tree')
1614
self.addCleanup(tree.unlock)
1615
tree.commit('1a', rev_id='1a')
1616
tree.commit('2a', rev_id='2a')
1617
tree.commit('3a', rev_id='3a')
1620
def setup_ab_tree(self):
1621
tree = self.setup_a_tree()
1622
tree.set_last_revision('1a')
1623
tree.branch.set_last_revision_info(1, '1a')
1624
tree.commit('2b', rev_id='2b')
1625
tree.commit('3b', rev_id='3b')
1628
def setup_ac_tree(self):
1629
tree = self.setup_a_tree()
1630
tree.set_last_revision(revision.NULL_REVISION)
1631
tree.branch.set_last_revision_info(0, revision.NULL_REVISION)
1632
tree.commit('1c', rev_id='1c')
1633
tree.commit('2c', rev_id='2c')
1634
tree.commit('3c', rev_id='3c')
1637
def test_all_new(self):
1638
tree = self.setup_ab_tree()
1639
old, new = log.get_history_change('1a', '3a', tree.branch.repository)
1640
self.assertEqual([], old)
1641
self.assertEqual(['2a', '3a'], new)
1643
def test_all_old(self):
1644
tree = self.setup_ab_tree()
1645
old, new = log.get_history_change('3a', '1a', tree.branch.repository)
1646
self.assertEqual([], new)
1647
self.assertEqual(['2a', '3a'], old)
1649
def test_null_old(self):
1650
tree = self.setup_ab_tree()
1651
old, new = log.get_history_change(revision.NULL_REVISION,
1652
'3a', tree.branch.repository)
1653
self.assertEqual([], old)
1654
self.assertEqual(['1a', '2a', '3a'], new)
1656
def test_null_new(self):
1657
tree = self.setup_ab_tree()
1658
old, new = log.get_history_change('3a', revision.NULL_REVISION,
1659
tree.branch.repository)
1660
self.assertEqual([], new)
1661
self.assertEqual(['1a', '2a', '3a'], old)
1663
def test_diverged(self):
1664
tree = self.setup_ab_tree()
1665
old, new = log.get_history_change('3a', '3b', tree.branch.repository)
1666
self.assertEqual(old, ['2a', '3a'])
1667
self.assertEqual(new, ['2b', '3b'])
1669
def test_unrelated(self):
1670
tree = self.setup_ac_tree()
1671
old, new = log.get_history_change('3a', '3c', tree.branch.repository)
1672
self.assertEqual(old, ['1a', '2a', '3a'])
1673
self.assertEqual(new, ['1c', '2c', '3c'])
1675
def test_show_branch_change(self):
1676
tree = self.setup_ab_tree()
1678
log.show_branch_change(tree.branch, s, 3, '3a')
1679
self.assertContainsRe(s.getvalue(),
1680
'[*]{60}\nRemoved Revisions:\n(.|\n)*2a(.|\n)*3a(.|\n)*'
1681
'[*]{60}\n\nAdded Revisions:\n(.|\n)*2b(.|\n)*3b')
1683
def test_show_branch_change_no_change(self):
1684
tree = self.setup_ab_tree()
1686
log.show_branch_change(tree.branch, s, 3, '3b')
1687
self.assertEqual(s.getvalue(),
1688
'Nothing seems to have changed\n')
1690
def test_show_branch_change_no_old(self):
1691
tree = self.setup_ab_tree()
1693
log.show_branch_change(tree.branch, s, 2, '2b')
1694
self.assertContainsRe(s.getvalue(), 'Added Revisions:')
1695
self.assertNotContainsRe(s.getvalue(), 'Removed Revisions:')
1697
def test_show_branch_change_no_new(self):
1698
tree = self.setup_ab_tree()
1699
tree.branch.set_last_revision_info(2, '2b')
1701
log.show_branch_change(tree.branch, s, 3, '3b')
1702
self.assertContainsRe(s.getvalue(), 'Removed Revisions:')
1703
self.assertNotContainsRe(s.getvalue(), 'Added Revisions:')