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)
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)
65
self.assertEquals(expected, got)
67
def test_cur_revno(self):
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_cur_revno(self):
87
b = Branch.initialize('.')
90
b.commit('empty commit')
91
show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
92
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
93
start_revision=2, end_revision=1)
94
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
95
start_revision=1, end_revision=2)
96
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
97
start_revision=0, end_revision=2)
98
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
99
start_revision=1, end_revision=0)
100
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
101
start_revision=-1, end_revision=1)
102
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
103
start_revision=1, end_revision=-1)
105
def test_simple_log(self):
106
eq = self.assertEquals
108
b = Branch.initialize('.')
109
self.assertEqual([], lf.logs)
111
def test_empty_commit(self):
112
wt = self.make_branch_and_tree('.')
114
wt.commit('empty commit')
116
b.commit('empty commit')
115
117
lf = LogCatcher()
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')
118
show_log(b, lf, verbose=True)
120
eq(lf.logs[0].revno, 1)
121
eq(lf.logs[0].rev.message, 'empty commit')
123
self.log('log delta: %r' % d)
125
127
self.build_tree(['hello'])
127
wt.commit('add one file',
128
committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
129
u'<test@example.com>')
129
b.commit('add one file')
132
# log using regular thing
133
show_log(b, LongLogFormatter(lf))
135
for l in lf.readlines():
138
# get log as data structure
130
139
lf = LogCatcher()
131
log.show_log(wt.branch, lf, verbose=True)
132
self.assertEqual(2, len(lf.logs))
140
show_log(b, lf, verbose=True)
142
self.log('log entries:')
143
for logentry in lf.logs:
144
self.log('%4d %s' % (logentry.revno, logentry.rev.message))
133
146
# 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
147
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)
148
eq(logentry.revno, 2)
149
eq(logentry.rev.message, 'add one file')
151
self.log('log 2 delta: %r' % d)
152
# self.checkDelta(d, added=['hello'])
154
# commit a log message with control characters
155
msg = "All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
222
157
lf = LogCatcher()
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
author='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
author='John Doe <jdoe@example.com>')
684
formatter = log.LongLogFormatter(to_file=sio)
685
log.show_log(b, formatter)
686
self.assertEqualDiff('''\
687
------------------------------------------------------------
689
author: John Doe <jdoe@example.com>
690
committer: Lorem Ipsum <test@example.com>
691
branch nick: test_author_log
692
timestamp: Wed 2005-11-23 12:08:27 +1000
698
def test_properties_in_log(self):
699
"""Log includes the custom properties returned by the registered
702
wt = self.make_branch_and_tree('.')
704
self.build_tree(['a'])
706
b.nick = 'test_properties_in_log'
707
wt.commit(message='add a',
708
timestamp=1132711707,
710
committer='Lorem Ipsum <test@example.com>',
711
author='John Doe <jdoe@example.com>')
713
formatter = log.LongLogFormatter(to_file=sio)
715
def trivial_custom_prop_handler(revision):
716
return {'test_prop':'test_value'}
718
log.properties_handler_registry.register(
719
'trivial_custom_prop_handler',
720
trivial_custom_prop_handler)
721
log.show_log(b, formatter)
723
log.properties_handler_registry.remove(
724
'trivial_custom_prop_handler')
725
self.assertEqualDiff('''\
726
------------------------------------------------------------
728
test_prop: test_value
729
author: John Doe <jdoe@example.com>
730
committer: Lorem Ipsum <test@example.com>
731
branch nick: test_properties_in_log
732
timestamp: Wed 2005-11-23 12:08:27 +1000
738
def test_properties_in_short_log(self):
739
"""Log includes the custom properties returned by the registered
742
wt = self.make_branch_and_tree('.')
744
self.build_tree(['a'])
746
b.nick = 'test_properties_in_short_log'
747
wt.commit(message='add a',
748
timestamp=1132711707,
750
committer='Lorem Ipsum <test@example.com>',
751
author='John Doe <jdoe@example.com>')
753
formatter = log.ShortLogFormatter(to_file=sio)
755
def trivial_custom_prop_handler(revision):
756
return {'test_prop':'test_value'}
758
log.properties_handler_registry.register(
759
'trivial_custom_prop_handler',
760
trivial_custom_prop_handler)
761
log.show_log(b, formatter)
763
log.properties_handler_registry.remove(
764
'trivial_custom_prop_handler')
765
self.assertEqualDiff('''\
766
1 John Doe\t2005-11-23
767
test_prop: test_value
773
def test_error_in_properties_handler(self):
774
"""Log includes the custom properties returned by the registered
777
wt = self.make_branch_and_tree('.')
779
self.build_tree(['a'])
781
b.nick = 'test_author_log'
782
wt.commit(message='add a',
783
timestamp=1132711707,
785
committer='Lorem Ipsum <test@example.com>',
786
author='John Doe <jdoe@example.com>',
787
revprops={'first_prop':'first_value'})
789
formatter = log.LongLogFormatter(to_file=sio)
791
def trivial_custom_prop_handler(revision):
792
raise StandardError("a test error")
794
log.properties_handler_registry.register(
795
'trivial_custom_prop_handler',
796
trivial_custom_prop_handler)
797
self.assertRaises(StandardError, log.show_log, b, formatter,)
799
log.properties_handler_registry.remove(
800
'trivial_custom_prop_handler')
802
def test_properties_handler_bad_argument(self):
803
wt = self.make_branch_and_tree('.')
805
self.build_tree(['a'])
807
b.nick = 'test_author_log'
808
wt.commit(message='add a',
809
timestamp=1132711707,
811
committer='Lorem Ipsum <test@example.com>',
812
author='John Doe <jdoe@example.com>',
813
revprops={'a_prop':'test_value'})
815
formatter = log.LongLogFormatter(to_file=sio)
817
def bad_argument_prop_handler(revision):
818
return {'custom_prop_name':revision.properties['a_prop']}
820
log.properties_handler_registry.register(
821
'bad_argument_prop_handler',
822
bad_argument_prop_handler)
824
self.assertRaises(AttributeError, formatter.show_properties,
827
revision = b.repository.get_revision(b.last_revision())
828
formatter.show_properties(revision, '')
829
self.assertEqualDiff('''custom_prop_name: test_value\n''',
832
log.properties_handler_registry.remove(
833
'bad_argument_prop_handler')
836
class TestLongLogFormatterWithoutMergeRevisions(TestCaseWithoutPropsHandler):
838
def test_long_verbose_log(self):
839
"""Verbose log includes changed files
843
wt = self.make_branch_and_tree('.')
845
self.build_tree(['a'])
847
# XXX: why does a longer nick show up?
848
b.nick = 'test_verbose_log'
849
wt.commit(message='add a',
850
timestamp=1132711707,
852
committer='Lorem Ipsum <test@example.com>')
853
logfile = file('out.tmp', 'w+')
854
formatter = log.LongLogFormatter(to_file=logfile, levels=1)
855
log.show_log(b, formatter, verbose=True)
858
log_contents = logfile.read()
859
self.assertEqualDiff('''\
860
------------------------------------------------------------
862
committer: Lorem Ipsum <test@example.com>
863
branch nick: test_verbose_log
864
timestamp: Wed 2005-11-23 12:08:27 +1000
872
def test_long_verbose_contain_deltas(self):
873
wt = self.make_branch_and_tree('parent')
874
self.build_tree(['parent/f1', 'parent/f2'])
876
wt.commit('first post')
877
self.run_bzr('branch parent child')
878
os.unlink('child/f1')
879
file('child/f2', 'wb').write('hello\n')
880
self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
883
self.run_bzr('merge ../child')
884
wt.commit('merge branch 1')
886
sio = self.make_utf8_encoded_stringio()
887
lf = log.LongLogFormatter(to_file=sio, levels=1)
888
log.show_log(b, lf, verbose=True)
889
the_log = normalize_log(sio.getvalue())
890
self.assertEqualDiff("""\
891
------------------------------------------------------------
893
committer: Lorem Ipsum <test@example.com>
902
------------------------------------------------------------
904
committer: Lorem Ipsum <test@example.com>
915
def test_long_trailing_newlines(self):
916
wt = self.make_branch_and_tree('.')
917
b = make_commits_with_trailing_newlines(wt)
918
sio = self.make_utf8_encoded_stringio()
919
lf = log.LongLogFormatter(to_file=sio, levels=1)
921
self.assertEqualDiff("""\
922
------------------------------------------------------------
924
committer: Joe Foo <joe@foo.com>
926
timestamp: Mon 2005-11-21 09:32:56 -0600
928
single line with trailing newline
929
------------------------------------------------------------
931
author: Joe Bar <joe@bar.com>
932
committer: Joe Foo <joe@foo.com>
934
timestamp: Mon 2005-11-21 09:27:22 -0600
939
------------------------------------------------------------
941
committer: Joe Foo <joe@foo.com>
943
timestamp: Mon 2005-11-21 09:24:15 -0600
949
def test_long_author_in_log(self):
950
"""Log includes the author name if it's set in
951
the revision properties
953
wt = self.make_branch_and_tree('.')
955
self.build_tree(['a'])
957
b.nick = 'test_author_log'
958
wt.commit(message='add a',
959
timestamp=1132711707,
961
committer='Lorem Ipsum <test@example.com>',
962
author='John Doe <jdoe@example.com>')
964
formatter = log.LongLogFormatter(to_file=sio, levels=1)
965
log.show_log(b, formatter)
966
self.assertEqualDiff('''\
967
------------------------------------------------------------
969
author: John Doe <jdoe@example.com>
970
committer: Lorem Ipsum <test@example.com>
971
branch nick: test_author_log
972
timestamp: Wed 2005-11-23 12:08:27 +1000
978
def test_long_properties_in_log(self):
979
"""Log includes the custom properties returned by the registered
982
wt = self.make_branch_and_tree('.')
984
self.build_tree(['a'])
986
b.nick = 'test_properties_in_log'
987
wt.commit(message='add a',
988
timestamp=1132711707,
990
committer='Lorem Ipsum <test@example.com>',
991
author='John Doe <jdoe@example.com>')
993
formatter = log.LongLogFormatter(to_file=sio, levels=1)
995
def trivial_custom_prop_handler(revision):
996
return {'test_prop':'test_value'}
998
log.properties_handler_registry.register(
999
'trivial_custom_prop_handler',
1000
trivial_custom_prop_handler)
1001
log.show_log(b, formatter)
1003
log.properties_handler_registry.remove(
1004
'trivial_custom_prop_handler')
1005
self.assertEqualDiff('''\
1006
------------------------------------------------------------
1008
test_prop: test_value
1009
author: John Doe <jdoe@example.com>
1010
committer: Lorem Ipsum <test@example.com>
1011
branch nick: test_properties_in_log
1012
timestamp: Wed 2005-11-23 12:08:27 +1000
1019
class TestLineLogFormatter(tests.TestCaseWithTransport):
1021
def test_line_log(self):
1022
"""Line log should show revno
1026
wt = self.make_branch_and_tree('.')
1028
self.build_tree(['a'])
1030
b.nick = 'test-line-log'
1031
wt.commit(message='add a',
1032
timestamp=1132711707,
1034
committer='Line-Log-Formatter Tester <test@line.log>')
1035
logfile = file('out.tmp', 'w+')
1036
formatter = log.LineLogFormatter(to_file=logfile)
1037
log.show_log(b, formatter)
1040
log_contents = logfile.read()
1041
self.assertEqualDiff('1: Line-Log-Formatte... 2005-11-23 add a\n',
1044
def test_trailing_newlines(self):
1045
wt = self.make_branch_and_tree('.')
1046
b = make_commits_with_trailing_newlines(wt)
1047
sio = self.make_utf8_encoded_stringio()
1048
lf = log.LineLogFormatter(to_file=sio)
1050
self.assertEqualDiff("""\
1051
3: Joe Foo 2005-11-21 single line with trailing newline
1052
2: Joe Bar 2005-11-21 multiline
1053
1: Joe Foo 2005-11-21 simple log message
1057
def _prepare_tree_with_merges(self, with_tags=False):
1058
wt = self.make_branch_and_memory_tree('.')
1060
self.addCleanup(wt.unlock)
1062
wt.commit('rev-1', rev_id='rev-1',
1063
timestamp=1132586655, timezone=36000,
1064
committer='Joe Foo <joe@foo.com>')
1065
wt.commit('rev-merged', rev_id='rev-2a',
1066
timestamp=1132586700, timezone=36000,
1067
committer='Joe Foo <joe@foo.com>')
1068
wt.set_parent_ids(['rev-1', 'rev-2a'])
1069
wt.branch.set_last_revision_info(1, 'rev-1')
1070
wt.commit('rev-2', rev_id='rev-2b',
1071
timestamp=1132586800, timezone=36000,
1072
committer='Joe Foo <joe@foo.com>')
1075
branch.tags.set_tag('v0.2', 'rev-2b')
1076
wt.commit('rev-3', rev_id='rev-3',
1077
timestamp=1132586900, timezone=36000,
1078
committer='Jane Foo <jane@foo.com>')
1079
branch.tags.set_tag('v1.0rc1', 'rev-3')
1080
branch.tags.set_tag('v1.0', 'rev-3')
1083
def test_line_log_single_merge_revision(self):
1084
wt = self._prepare_tree_with_merges()
1085
logfile = self.make_utf8_encoded_stringio()
1086
formatter = log.LineLogFormatter(to_file=logfile)
1087
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
1089
rev = revspec.in_history(wtb)
1090
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
1091
self.assertEqualDiff("""\
1092
1.1.1: Joe Foo 2005-11-22 rev-merged
1096
def test_line_log_with_tags(self):
1097
wt = self._prepare_tree_with_merges(with_tags=True)
1098
logfile = self.make_utf8_encoded_stringio()
1099
formatter = log.LineLogFormatter(to_file=logfile)
1100
log.show_log(wt.branch, formatter)
1101
self.assertEqualDiff("""\
1102
3: Jane Foo 2005-11-22 {v1.0, v1.0rc1} rev-3
1103
2: Joe Foo 2005-11-22 [merge] {v0.2} rev-2
1104
1: Joe Foo 2005-11-22 rev-1
1108
class TestLineLogFormatterWithMergeRevisions(tests.TestCaseWithTransport):
1110
def test_line_merge_revs_log(self):
1111
"""Line log should show revno
1115
wt = self.make_branch_and_tree('.')
1117
self.build_tree(['a'])
1119
b.nick = 'test-line-log'
1120
wt.commit(message='add a',
1121
timestamp=1132711707,
1123
committer='Line-Log-Formatter Tester <test@line.log>')
1124
logfile = file('out.tmp', 'w+')
1125
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1126
log.show_log(b, formatter)
1129
log_contents = logfile.read()
1130
self.assertEqualDiff('1: Line-Log-Formatte... 2005-11-23 add a\n',
1133
def test_line_merge_revs_log_single_merge_revision(self):
1134
wt = self.make_branch_and_memory_tree('.')
1136
self.addCleanup(wt.unlock)
1138
wt.commit('rev-1', rev_id='rev-1',
1139
timestamp=1132586655, timezone=36000,
1140
committer='Joe Foo <joe@foo.com>')
1141
wt.commit('rev-merged', rev_id='rev-2a',
1142
timestamp=1132586700, timezone=36000,
1143
committer='Joe Foo <joe@foo.com>')
1144
wt.set_parent_ids(['rev-1', 'rev-2a'])
1145
wt.branch.set_last_revision_info(1, 'rev-1')
1146
wt.commit('rev-2', rev_id='rev-2b',
1147
timestamp=1132586800, timezone=36000,
1148
committer='Joe Foo <joe@foo.com>')
1149
logfile = self.make_utf8_encoded_stringio()
1150
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1151
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
1153
rev = revspec.in_history(wtb)
1154
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
1155
self.assertEqualDiff("""\
1156
1.1.1: Joe Foo 2005-11-22 rev-merged
1160
def test_line_merge_revs_log_with_merges(self):
1161
wt = self.make_branch_and_memory_tree('.')
1163
self.addCleanup(wt.unlock)
1165
wt.commit('rev-1', rev_id='rev-1',
1166
timestamp=1132586655, timezone=36000,
1167
committer='Joe Foo <joe@foo.com>')
1168
wt.commit('rev-merged', rev_id='rev-2a',
1169
timestamp=1132586700, timezone=36000,
1170
committer='Joe Foo <joe@foo.com>')
1171
wt.set_parent_ids(['rev-1', 'rev-2a'])
1172
wt.branch.set_last_revision_info(1, 'rev-1')
1173
wt.commit('rev-2', rev_id='rev-2b',
1174
timestamp=1132586800, timezone=36000,
1175
committer='Joe Foo <joe@foo.com>')
1176
logfile = self.make_utf8_encoded_stringio()
1177
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1178
log.show_log(wt.branch, formatter)
1179
self.assertEqualDiff("""\
1180
2: Joe Foo 2005-11-22 [merge] rev-2
1181
1.1.1: Joe Foo 2005-11-22 rev-merged
1182
1: Joe Foo 2005-11-22 rev-1
1186
class TestGetViewRevisions(tests.TestCaseWithTransport):
1188
def make_tree_with_commits(self):
1189
"""Create a tree with well-known revision ids"""
1190
wt = self.make_branch_and_tree('tree1')
1191
wt.commit('commit one', rev_id='1')
1192
wt.commit('commit two', rev_id='2')
1193
wt.commit('commit three', rev_id='3')
1194
mainline_revs = [None, '1', '2', '3']
1195
rev_nos = {'1': 1, '2': 2, '3': 3}
1196
return mainline_revs, rev_nos, wt
1198
def make_tree_with_merges(self):
1199
"""Create a tree with well-known revision ids and a merge"""
1200
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1201
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
1202
tree2.commit('four-a', rev_id='4a')
1203
wt.merge_from_branch(tree2.branch)
1204
wt.commit('four-b', rev_id='4b')
1205
mainline_revs.append('4b')
1208
return mainline_revs, rev_nos, wt
1210
def make_tree_with_many_merges(self):
1211
"""Create a tree with well-known revision ids"""
1212
wt = self.make_branch_and_tree('tree1')
1213
self.build_tree_contents([('tree1/f', '1\n')])
1214
wt.add(['f'], ['f-id'])
1215
wt.commit('commit one', rev_id='1')
1216
wt.commit('commit two', rev_id='2')
1218
tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
1219
self.build_tree_contents([('tree3/f', '1\n2\n3a\n')])
1220
tree3.commit('commit three a', rev_id='3a')
1222
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
1223
tree2.merge_from_branch(tree3.branch)
1224
tree2.commit('commit three b', rev_id='3b')
1226
wt.merge_from_branch(tree2.branch)
1227
wt.commit('commit three c', rev_id='3c')
1228
tree2.commit('four-a', rev_id='4a')
1230
wt.merge_from_branch(tree2.branch)
1231
wt.commit('four-b', rev_id='4b')
1233
mainline_revs = [None, '1', '2', '3c', '4b']
1234
rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
1235
full_rev_nos_for_reference = {
1238
'3a': '2.1.1', #first commit tree 3
1239
'3b': '2.2.1', # first commit tree 2
1240
'3c': '3', #merges 3b to main
1241
'4a': '2.2.2', # second commit tree 2
1242
'4b': '4', # merges 4a to main
1244
return mainline_revs, rev_nos, wt
1246
def test_get_view_revisions_forward(self):
1247
"""Test the get_view_revisions method"""
1248
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1250
self.addCleanup(wt.unlock)
1251
revisions = list(log.get_view_revisions(
1252
mainline_revs, rev_nos, wt.branch, 'forward'))
1253
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
1255
revisions2 = list(log.get_view_revisions(
1256
mainline_revs, rev_nos, wt.branch, 'forward',
1257
include_merges=False))
1258
self.assertEqual(revisions, revisions2)
1260
def test_get_view_revisions_reverse(self):
1261
"""Test the get_view_revisions with reverse"""
1262
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1264
self.addCleanup(wt.unlock)
1265
revisions = list(log.get_view_revisions(
1266
mainline_revs, rev_nos, wt.branch, 'reverse'))
1267
self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
1269
revisions2 = list(log.get_view_revisions(
1270
mainline_revs, rev_nos, wt.branch, 'reverse',
1271
include_merges=False))
1272
self.assertEqual(revisions, revisions2)
1274
def test_get_view_revisions_merge(self):
1275
"""Test get_view_revisions when there are merges"""
1276
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
1278
self.addCleanup(wt.unlock)
1279
revisions = list(log.get_view_revisions(
1280
mainline_revs, rev_nos, wt.branch, 'forward'))
1281
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
1282
('4b', '4', 0), ('4a', '3.1.1', 1)],
1284
revisions = list(log.get_view_revisions(
1285
mainline_revs, rev_nos, wt.branch, 'forward',
1286
include_merges=False))
1287
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
1291
def test_get_view_revisions_merge_reverse(self):
1292
"""Test get_view_revisions in reverse when there are merges"""
1293
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
1295
self.addCleanup(wt.unlock)
1296
revisions = list(log.get_view_revisions(
1297
mainline_revs, rev_nos, wt.branch, 'reverse'))
1298
self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
1299
('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
1301
revisions = list(log.get_view_revisions(
1302
mainline_revs, rev_nos, wt.branch, 'reverse',
1303
include_merges=False))
1304
self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
1308
def test_get_view_revisions_merge2(self):
1309
"""Test get_view_revisions when there are merges"""
1310
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
1312
self.addCleanup(wt.unlock)
1313
revisions = list(log.get_view_revisions(
1314
mainline_revs, rev_nos, wt.branch, 'forward'))
1315
expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
1316
('3a', '2.1.1', 1), ('3b', '2.2.1', 1), ('4b', '4', 0),
1318
self.assertEqual(expected, revisions)
1319
revisions = list(log.get_view_revisions(
1320
mainline_revs, rev_nos, wt.branch, 'forward',
1321
include_merges=False))
1322
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
1327
def test_file_id_for_range(self):
1328
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
1330
self.addCleanup(wt.unlock)
1332
def rev_from_rev_id(revid, branch):
1333
revspec = revisionspec.RevisionSpec.from_string('revid:%s' % revid)
1334
return revspec.in_history(branch)
1336
def view_revs(start_rev, end_rev, file_id, direction):
1337
revs = log.calculate_view_revisions(
1339
start_rev, # start_revision
1340
end_rev, # end_revision
1341
direction, # direction
1342
file_id, # specific_fileid
1343
True, # generate_merge_revisions
1344
True, # allow_single_merge_revision
1348
rev_3a = rev_from_rev_id('3a', wt.branch)
1349
rev_4b = rev_from_rev_id('4b', wt.branch)
1350
self.assertEqual([('3c', '3', 0), ('3a', '2.1.1', 1)],
1351
view_revs(rev_3a, rev_4b, 'f-id', 'reverse'))
1352
# Note: 3c still appears before 3a here because of depth-based sorting
1353
self.assertEqual([('3c', '3', 0), ('3a', '2.1.1', 1)],
1354
view_revs(rev_3a, rev_4b, 'f-id', 'forward'))
1357
class TestGetRevisionsTouchingFileID(tests.TestCaseWithTransport):
1359
def create_tree_with_single_merge(self):
1360
"""Create a branch with a moderate layout.
1362
The revision graph looks like:
1370
In this graph, A introduced files f1 and f2 and f3.
1371
B modifies f1 and f3, and C modifies f2 and f3.
1372
D merges the changes from B and C and resolves the conflict for f3.
1374
# TODO: jam 20070218 This seems like it could really be done
1375
# with make_branch_and_memory_tree() if we could just
1376
# create the content of those files.
1377
# TODO: jam 20070218 Another alternative is that we would really
1378
# like to only create this tree 1 time for all tests that
1379
# use it. Since 'log' only uses the tree in a readonly
1380
# fashion, it seems a shame to regenerate an identical
1381
# tree for each test.
1382
tree = self.make_branch_and_tree('tree')
1384
self.addCleanup(tree.unlock)
1386
self.build_tree_contents([('tree/f1', 'A\n'),
1390
tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
1391
tree.commit('A', rev_id='A')
1393
self.build_tree_contents([('tree/f2', 'A\nC\n'),
1394
('tree/f3', 'A\nC\n'),
1396
tree.commit('C', rev_id='C')
1397
# Revert back to A to build the other history.
1398
tree.set_last_revision('A')
1399
tree.branch.set_last_revision_info(1, 'A')
1400
self.build_tree_contents([('tree/f1', 'A\nB\n'),
1402
('tree/f3', 'A\nB\n'),
1404
tree.commit('B', rev_id='B')
1405
tree.set_parent_ids(['B', 'C'])
1406
self.build_tree_contents([('tree/f1', 'A\nB\n'),
1407
('tree/f2', 'A\nC\n'),
1408
('tree/f3', 'A\nB\nC\n'),
1410
tree.commit('D', rev_id='D')
1412
# Switch to a read lock for this tree.
1413
# We still have an addCleanup(tree.unlock) pending
1418
def check_delta(self, delta, **kw):
1419
"""Check the filenames touched by a delta are as expected.
1421
Caller only have to pass in the list of files for each part, all
1422
unspecified parts are considered empty (and checked as such).
1424
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
1425
# By default we expect an empty list
1426
expected = kw.get(n, [])
1427
# strip out only the path components
1428
got = [x[0] for x in getattr(delta, n)]
1429
self.assertEqual(expected, got)
1431
def test_tree_with_single_merge(self):
1432
"""Make sure the tree layout is correct."""
1433
tree = self.create_tree_with_single_merge()
1434
rev_A_tree = tree.branch.repository.revision_tree('A')
1435
rev_B_tree = tree.branch.repository.revision_tree('B')
1436
rev_C_tree = tree.branch.repository.revision_tree('C')
1437
rev_D_tree = tree.branch.repository.revision_tree('D')
1439
self.check_delta(rev_B_tree.changes_from(rev_A_tree),
1440
modified=['f1', 'f3'])
1442
self.check_delta(rev_C_tree.changes_from(rev_A_tree),
1443
modified=['f2', 'f3'])
1445
self.check_delta(rev_D_tree.changes_from(rev_B_tree),
1446
modified=['f2', 'f3'])
1448
self.check_delta(rev_D_tree.changes_from(rev_C_tree),
1449
modified=['f1', 'f3'])
1451
def assertAllRevisionsForFileID(self, tree, file_id, revisions):
1452
"""Ensure _filter_revisions_touching_file_id returns the right values.
1454
Get the return value from _filter_revisions_touching_file_id and make
1455
sure they are correct.
1457
# The api for _filter_revisions_touching_file_id is a little crazy.
1458
# So we do the setup here.
1459
mainline = tree.branch.revision_history()
1460
mainline.insert(0, None)
1461
revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
1462
view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
1464
actual_revs = log._filter_revisions_touching_file_id(
1467
list(view_revs_iter))
1468
self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
1470
def test_file_id_f1(self):
1471
tree = self.create_tree_with_single_merge()
1472
# f1 should be marked as modified by revisions A and B
1473
self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
1475
def test_file_id_f2(self):
1476
tree = self.create_tree_with_single_merge()
1477
# f2 should be marked as modified by revisions A, C, and D
1478
# because D merged the changes from C.
1479
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1481
def test_file_id_f3(self):
1482
tree = self.create_tree_with_single_merge()
1483
# f3 should be marked as modified by revisions A, B, C, and D
1484
self.assertAllRevisionsForFileID(tree, 'f3-id', ['D', 'C', 'B', 'A'])
1486
def test_file_id_with_ghosts(self):
1487
# This is testing bug #209948, where having a ghost would cause
1488
# _filter_revisions_touching_file_id() to fail.
1489
tree = self.create_tree_with_single_merge()
1490
# We need to add a revision, so switch back to a write-locked tree
1491
# (still a single addCleanup(tree.unlock) pending).
1494
first_parent = tree.last_revision()
1495
tree.set_parent_ids([first_parent, 'ghost-revision-id'])
1496
self.build_tree_contents([('tree/f1', 'A\nB\nXX\n')])
1497
tree.commit('commit with a ghost', rev_id='XX')
1498
self.assertAllRevisionsForFileID(tree, 'f1-id', ['XX', 'B', 'A'])
1499
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1502
class TestShowChangedRevisions(tests.TestCaseWithTransport):
1504
def test_show_changed_revisions_verbose(self):
1505
tree = self.make_branch_and_tree('tree_a')
1506
self.build_tree(['tree_a/foo'])
1508
tree.commit('bar', rev_id='bar-id')
1509
s = self.make_utf8_encoded_stringio()
1510
log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
1511
self.assertContainsRe(s.getvalue(), 'bar')
1512
self.assertNotContainsRe(s.getvalue(), 'foo')
1515
class TestLogFormatter(tests.TestCase):
1517
def test_short_committer(self):
1518
rev = revision.Revision('a-id')
1519
rev.committer = 'John Doe <jdoe@example.com>'
1520
lf = log.LogFormatter(None)
1521
self.assertEqual('John Doe', lf.short_committer(rev))
1522
rev.committer = 'John Smith <jsmith@example.com>'
1523
self.assertEqual('John Smith', lf.short_committer(rev))
1524
rev.committer = 'John Smith'
1525
self.assertEqual('John Smith', lf.short_committer(rev))
1526
rev.committer = 'jsmith@example.com'
1527
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1528
rev.committer = '<jsmith@example.com>'
1529
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1530
rev.committer = 'John Smith jsmith@example.com'
1531
self.assertEqual('John Smith', lf.short_committer(rev))
1533
def test_short_author(self):
1534
rev = revision.Revision('a-id')
1535
rev.committer = 'John Doe <jdoe@example.com>'
1536
lf = log.LogFormatter(None)
1537
self.assertEqual('John Doe', lf.short_author(rev))
1538
rev.properties['author'] = 'John Smith <jsmith@example.com>'
1539
self.assertEqual('John Smith', lf.short_author(rev))
1540
rev.properties['author'] = 'John Smith'
1541
self.assertEqual('John Smith', lf.short_author(rev))
1542
rev.properties['author'] = 'jsmith@example.com'
1543
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1544
rev.properties['author'] = '<jsmith@example.com>'
1545
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1546
rev.properties['author'] = 'John Smith jsmith@example.com'
1547
self.assertEqual('John Smith', lf.short_author(rev))
1550
class TestReverseByDepth(tests.TestCase):
1551
"""Test reverse_by_depth behavior.
1553
This is used to present revisions in forward (oldest first) order in a nice
1556
The tests use lighter revision description to ease reading.
1559
def assertReversed(self, forward, backward):
1560
# Transform the descriptions to suit the API: tests use (revno, depth),
1561
# while the API expects (revid, revno, depth)
1562
def complete_revisions(l):
1563
"""Transform the description to suit the API.
1565
Tests use (revno, depth) whil the API expects (revid, revno, depth).
1566
Since the revid is arbitrary, we just duplicate revno
1568
return [ (r, r, d) for r, d in l]
1569
forward = complete_revisions(forward)
1570
backward= complete_revisions(backward)
1571
self.assertEqual(forward, log.reverse_by_depth(backward))
1574
def test_mainline_revisions(self):
1575
self.assertReversed([( '1', 0), ('2', 0)],
1576
[('2', 0), ('1', 0)])
1578
def test_merged_revisions(self):
1579
self.assertReversed([('1', 0), ('2', 0), ('2.2', 1), ('2.1', 1),],
1580
[('2', 0), ('2.1', 1), ('2.2', 1), ('1', 0),])
1581
def test_shifted_merged_revisions(self):
1582
"""Test irregular layout.
1584
Requesting revisions touching a file can produce "holes" in the depths.
1586
self.assertReversed([('1', 0), ('2', 0), ('1.1', 2), ('1.2', 2),],
1587
[('2', 0), ('1.2', 2), ('1.1', 2), ('1', 0),])
1589
def test_merged_without_child_revisions(self):
1590
"""Test irregular layout.
1592
Revision ranges can produce "holes" in the depths.
1594
# When a revision of higher depth doesn't follow one of lower depth, we
1595
# assume a lower depth one is virtually there
1596
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1597
[('4', 4), ('3', 3), ('2', 2), ('1', 2),])
1598
# So we get the same order after reversing below even if the original
1599
# revisions are not in the same order.
1600
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1601
[('3', 3), ('4', 4), ('2', 2), ('1', 2),])
1604
class TestHistoryChange(tests.TestCaseWithTransport):
1606
def setup_a_tree(self):
1607
tree = self.make_branch_and_tree('tree')
1609
self.addCleanup(tree.unlock)
1610
tree.commit('1a', rev_id='1a')
1611
tree.commit('2a', rev_id='2a')
1612
tree.commit('3a', rev_id='3a')
1615
def setup_ab_tree(self):
1616
tree = self.setup_a_tree()
1617
tree.set_last_revision('1a')
1618
tree.branch.set_last_revision_info(1, '1a')
1619
tree.commit('2b', rev_id='2b')
1620
tree.commit('3b', rev_id='3b')
1623
def setup_ac_tree(self):
1624
tree = self.setup_a_tree()
1625
tree.set_last_revision(revision.NULL_REVISION)
1626
tree.branch.set_last_revision_info(0, revision.NULL_REVISION)
1627
tree.commit('1c', rev_id='1c')
1628
tree.commit('2c', rev_id='2c')
1629
tree.commit('3c', rev_id='3c')
1632
def test_all_new(self):
1633
tree = self.setup_ab_tree()
1634
old, new = log.get_history_change('1a', '3a', tree.branch.repository)
1635
self.assertEqual([], old)
1636
self.assertEqual(['2a', '3a'], new)
1638
def test_all_old(self):
1639
tree = self.setup_ab_tree()
1640
old, new = log.get_history_change('3a', '1a', tree.branch.repository)
1641
self.assertEqual([], new)
1642
self.assertEqual(['2a', '3a'], old)
1644
def test_null_old(self):
1645
tree = self.setup_ab_tree()
1646
old, new = log.get_history_change(revision.NULL_REVISION,
1647
'3a', tree.branch.repository)
1648
self.assertEqual([], old)
1649
self.assertEqual(['1a', '2a', '3a'], new)
1651
def test_null_new(self):
1652
tree = self.setup_ab_tree()
1653
old, new = log.get_history_change('3a', revision.NULL_REVISION,
1654
tree.branch.repository)
1655
self.assertEqual([], new)
1656
self.assertEqual(['1a', '2a', '3a'], old)
1658
def test_diverged(self):
1659
tree = self.setup_ab_tree()
1660
old, new = log.get_history_change('3a', '3b', tree.branch.repository)
1661
self.assertEqual(old, ['2a', '3a'])
1662
self.assertEqual(new, ['2b', '3b'])
1664
def test_unrelated(self):
1665
tree = self.setup_ac_tree()
1666
old, new = log.get_history_change('3a', '3c', tree.branch.repository)
1667
self.assertEqual(old, ['1a', '2a', '3a'])
1668
self.assertEqual(new, ['1c', '2c', '3c'])
1670
def test_show_branch_change(self):
1671
tree = self.setup_ab_tree()
1673
log.show_branch_change(tree.branch, s, 3, '3a')
1674
self.assertContainsRe(s.getvalue(),
1675
'[*]{60}\nRemoved Revisions:\n(.|\n)*2a(.|\n)*3a(.|\n)*'
1676
'[*]{60}\n\nAdded Revisions:\n(.|\n)*2b(.|\n)*3b')
1678
def test_show_branch_change_no_change(self):
1679
tree = self.setup_ab_tree()
1681
log.show_branch_change(tree.branch, s, 3, '3b')
1682
self.assertEqual(s.getvalue(),
1683
'Nothing seems to have changed\n')
1685
def test_show_branch_change_no_old(self):
1686
tree = self.setup_ab_tree()
1688
log.show_branch_change(tree.branch, s, 2, '2b')
1689
self.assertContainsRe(s.getvalue(), 'Added Revisions:')
1690
self.assertNotContainsRe(s.getvalue(), 'Removed Revisions:')
1692
def test_show_branch_change_no_new(self):
1693
tree = self.setup_ab_tree()
1694
tree.branch.set_last_revision_info(2, '2b')
1696
log.show_branch_change(tree.branch, s, 3, '3b')
1697
self.assertContainsRe(s.getvalue(), 'Removed Revisions:')
1698
self.assertNotContainsRe(s.getvalue(), 'Added Revisions:')
158
show_log(b, lf, verbose=True)
159
committed_msg = lf.logs[0].rev.message
160
self.log("escaped commit message: %r", committed_msg)
161
self.assert_(msg != committed_msg)
162
self.assert_(len(committed_msg) > len(msg))