52
37
We should also test the LogFormatter.
57
39
def __init__(self):
58
40
super(LogCatcher, self).__init__(to_file=None)
61
def log_revision(self, revision):
62
self.logs.append(revision)
65
class TestShowLog(tests.TestCaseWithTransport):
44
def show(self, revno, rev, delta):
52
class SimpleLogTest(TestCaseInTempDir):
67
54
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).
55
"""Check the filenames touched by a delta are as expected."""
73
56
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
74
# By default we expect an empty list
75
57
expected = kw.get(n, [])
59
# tests are written with unix paths; fix them up for windows
61
expected = [x.replace('/', os.sep) for x in expected]
76
63
# strip out only the path components
77
64
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)
65
self.assertEquals(expected, got)
86
67
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)
68
b = Branch('.', init=True)
71
b.commit('empty commit')
72
show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
73
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
74
start_revision=2, end_revision=1)
75
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
76
start_revision=1, end_revision=2)
77
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
78
start_revision=0, end_revision=2)
79
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
80
start_revision=1, end_revision=0)
81
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
82
start_revision=-1, end_revision=1)
83
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
84
start_revision=1, end_revision=-1)
86
def test_simple_log(self):
87
eq = self.assertEquals
89
b = Branch('.', init=True)
109
self.assertEqual([], lf.logs)
111
def test_empty_commit(self):
112
wt = self.make_branch_and_tree('.')
114
wt.commit('empty commit')
97
b.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')
99
show_log(b, lf, verbose=True)
101
eq(lf.logs[0].revno, 1)
102
eq(lf.logs[0].rev.message, 'empty commit')
104
self.log('log delta: %r' % d)
125
108
self.build_tree(['hello'])
127
wt.commit('add one file',
128
committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
129
u'<test@example.com>')
110
b.commit('add one file')
113
# log using regular thing
114
show_log(b, LongLogFormatter(lf))
116
for l in lf.readlines():
119
# get log as data structure
130
120
lf = LogCatcher()
131
log.show_log(wt.branch, lf, verbose=True)
132
self.assertEqual(2, len(lf.logs))
121
show_log(b, lf, verbose=True)
123
self.log('log entries:')
124
for logentry in lf.logs:
125
self.log('%4d %s' % (logentry.revno, logentry.rev.message))
133
127
# 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
128
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'])
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'])
1502
def test_unknown_file_id(self):
1503
tree = self.create_tree_with_single_merge()
1504
self.assertAllRevisionsForFileID(tree, 'unknown', [])
1506
def test_empty_branch_unknown_file_id(self):
1507
tree = self.make_branch_and_tree('tree')
1508
self.assertAllRevisionsForFileID(tree, 'unknown', [])
1511
class TestShowChangedRevisions(tests.TestCaseWithTransport):
1513
def test_show_changed_revisions_verbose(self):
1514
tree = self.make_branch_and_tree('tree_a')
1515
self.build_tree(['tree_a/foo'])
1517
tree.commit('bar', rev_id='bar-id')
1518
s = self.make_utf8_encoded_stringio()
1519
log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
1520
self.assertContainsRe(s.getvalue(), 'bar')
1521
self.assertNotContainsRe(s.getvalue(), 'foo')
1524
class TestLogFormatter(tests.TestCase):
1526
def test_short_committer(self):
1527
rev = revision.Revision('a-id')
1528
rev.committer = 'John Doe <jdoe@example.com>'
1529
lf = log.LogFormatter(None)
1530
self.assertEqual('John Doe', lf.short_committer(rev))
1531
rev.committer = 'John Smith <jsmith@example.com>'
1532
self.assertEqual('John Smith', lf.short_committer(rev))
1533
rev.committer = 'John Smith'
1534
self.assertEqual('John Smith', lf.short_committer(rev))
1535
rev.committer = 'jsmith@example.com'
1536
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1537
rev.committer = '<jsmith@example.com>'
1538
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1539
rev.committer = 'John Smith jsmith@example.com'
1540
self.assertEqual('John Smith', lf.short_committer(rev))
1542
def test_short_author(self):
1543
rev = revision.Revision('a-id')
1544
rev.committer = 'John Doe <jdoe@example.com>'
1545
lf = log.LogFormatter(None)
1546
self.assertEqual('John Doe', lf.short_author(rev))
1547
rev.properties['author'] = 'John Smith <jsmith@example.com>'
1548
self.assertEqual('John Smith', lf.short_author(rev))
1549
rev.properties['author'] = 'John Smith'
1550
self.assertEqual('John Smith', lf.short_author(rev))
1551
rev.properties['author'] = 'jsmith@example.com'
1552
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1553
rev.properties['author'] = '<jsmith@example.com>'
1554
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1555
rev.properties['author'] = 'John Smith jsmith@example.com'
1556
self.assertEqual('John Smith', lf.short_author(rev))
1557
del rev.properties['author']
1558
rev.properties['authors'] = ('John Smith <jsmith@example.com>\n'
1559
'Jane Rey <jrey@example.com>')
1560
self.assertEqual('John Smith', lf.short_author(rev))
1563
class TestReverseByDepth(tests.TestCase):
1564
"""Test reverse_by_depth behavior.
1566
This is used to present revisions in forward (oldest first) order in a nice
1569
The tests use lighter revision description to ease reading.
1572
def assertReversed(self, forward, backward):
1573
# Transform the descriptions to suit the API: tests use (revno, depth),
1574
# while the API expects (revid, revno, depth)
1575
def complete_revisions(l):
1576
"""Transform the description to suit the API.
1578
Tests use (revno, depth) whil the API expects (revid, revno, depth).
1579
Since the revid is arbitrary, we just duplicate revno
1581
return [ (r, r, d) for r, d in l]
1582
forward = complete_revisions(forward)
1583
backward= complete_revisions(backward)
1584
self.assertEqual(forward, log.reverse_by_depth(backward))
1587
def test_mainline_revisions(self):
1588
self.assertReversed([( '1', 0), ('2', 0)],
1589
[('2', 0), ('1', 0)])
1591
def test_merged_revisions(self):
1592
self.assertReversed([('1', 0), ('2', 0), ('2.2', 1), ('2.1', 1),],
1593
[('2', 0), ('2.1', 1), ('2.2', 1), ('1', 0),])
1594
def test_shifted_merged_revisions(self):
1595
"""Test irregular layout.
1597
Requesting revisions touching a file can produce "holes" in the depths.
1599
self.assertReversed([('1', 0), ('2', 0), ('1.1', 2), ('1.2', 2),],
1600
[('2', 0), ('1.2', 2), ('1.1', 2), ('1', 0),])
1602
def test_merged_without_child_revisions(self):
1603
"""Test irregular layout.
1605
Revision ranges can produce "holes" in the depths.
1607
# When a revision of higher depth doesn't follow one of lower depth, we
1608
# assume a lower depth one is virtually there
1609
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1610
[('4', 4), ('3', 3), ('2', 2), ('1', 2),])
1611
# So we get the same order after reversing below even if the original
1612
# revisions are not in the same order.
1613
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1614
[('3', 3), ('4', 4), ('2', 2), ('1', 2),])
1617
class TestHistoryChange(tests.TestCaseWithTransport):
1619
def setup_a_tree(self):
1620
tree = self.make_branch_and_tree('tree')
1622
self.addCleanup(tree.unlock)
1623
tree.commit('1a', rev_id='1a')
1624
tree.commit('2a', rev_id='2a')
1625
tree.commit('3a', rev_id='3a')
1628
def setup_ab_tree(self):
1629
tree = self.setup_a_tree()
1630
tree.set_last_revision('1a')
1631
tree.branch.set_last_revision_info(1, '1a')
1632
tree.commit('2b', rev_id='2b')
1633
tree.commit('3b', rev_id='3b')
1636
def setup_ac_tree(self):
1637
tree = self.setup_a_tree()
1638
tree.set_last_revision(revision.NULL_REVISION)
1639
tree.branch.set_last_revision_info(0, revision.NULL_REVISION)
1640
tree.commit('1c', rev_id='1c')
1641
tree.commit('2c', rev_id='2c')
1642
tree.commit('3c', rev_id='3c')
1645
def test_all_new(self):
1646
tree = self.setup_ab_tree()
1647
old, new = log.get_history_change('1a', '3a', tree.branch.repository)
1648
self.assertEqual([], old)
1649
self.assertEqual(['2a', '3a'], new)
1651
def test_all_old(self):
1652
tree = self.setup_ab_tree()
1653
old, new = log.get_history_change('3a', '1a', tree.branch.repository)
1654
self.assertEqual([], new)
1655
self.assertEqual(['2a', '3a'], old)
1657
def test_null_old(self):
1658
tree = self.setup_ab_tree()
1659
old, new = log.get_history_change(revision.NULL_REVISION,
1660
'3a', tree.branch.repository)
1661
self.assertEqual([], old)
1662
self.assertEqual(['1a', '2a', '3a'], new)
1664
def test_null_new(self):
1665
tree = self.setup_ab_tree()
1666
old, new = log.get_history_change('3a', revision.NULL_REVISION,
1667
tree.branch.repository)
1668
self.assertEqual([], new)
1669
self.assertEqual(['1a', '2a', '3a'], old)
1671
def test_diverged(self):
1672
tree = self.setup_ab_tree()
1673
old, new = log.get_history_change('3a', '3b', tree.branch.repository)
1674
self.assertEqual(old, ['2a', '3a'])
1675
self.assertEqual(new, ['2b', '3b'])
1677
def test_unrelated(self):
1678
tree = self.setup_ac_tree()
1679
old, new = log.get_history_change('3a', '3c', tree.branch.repository)
1680
self.assertEqual(old, ['1a', '2a', '3a'])
1681
self.assertEqual(new, ['1c', '2c', '3c'])
1683
def test_show_branch_change(self):
1684
tree = self.setup_ab_tree()
1686
log.show_branch_change(tree.branch, s, 3, '3a')
1687
self.assertContainsRe(s.getvalue(),
1688
'[*]{60}\nRemoved Revisions:\n(.|\n)*2a(.|\n)*3a(.|\n)*'
1689
'[*]{60}\n\nAdded Revisions:\n(.|\n)*2b(.|\n)*3b')
1691
def test_show_branch_change_no_change(self):
1692
tree = self.setup_ab_tree()
1694
log.show_branch_change(tree.branch, s, 3, '3b')
1695
self.assertEqual(s.getvalue(),
1696
'Nothing seems to have changed\n')
1698
def test_show_branch_change_no_old(self):
1699
tree = self.setup_ab_tree()
1701
log.show_branch_change(tree.branch, s, 2, '2b')
1702
self.assertContainsRe(s.getvalue(), 'Added Revisions:')
1703
self.assertNotContainsRe(s.getvalue(), 'Removed Revisions:')
1705
def test_show_branch_change_no_new(self):
1706
tree = self.setup_ab_tree()
1707
tree.branch.set_last_revision_info(2, '2b')
1709
log.show_branch_change(tree.branch, s, 3, '3b')
1710
self.assertContainsRe(s.getvalue(), 'Removed Revisions:')
1711
self.assertNotContainsRe(s.getvalue(), 'Added Revisions:')
129
eq(logentry.revno, 2)
130
eq(logentry.rev.message, 'add one file')
132
self.log('log 2 delta: %r' % d)
133
# self.checkDelta(d, added=['hello'])