79
91
wt.commit('empty commit')
80
show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
81
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
82
start_revision=2, end_revision=1)
83
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
84
start_revision=1, end_revision=2)
85
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
86
start_revision=0, end_revision=2)
87
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
88
start_revision=1, end_revision=0)
89
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
90
start_revision=-1, end_revision=1)
91
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
92
start_revision=1, end_revision=-1)
94
def test_simple_log(self):
95
eq = self.assertEquals
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):
97
104
wt = self.make_branch_and_tree('.')
100
106
lf = LogCatcher()
107
log.show_log(wt.branch, lf)
109
self.assertEqual([], lf.logs)
111
def test_empty_commit(self):
112
wt = self.make_branch_and_tree('.')
105
114
wt.commit('empty commit')
106
115
lf = LogCatcher()
107
show_log(b, lf, verbose=True)
109
eq(lf.logs[0].revno, '1')
110
eq(lf.logs[0].rev.message, 'empty commit')
112
self.log('log delta: %r' % d)
116
log.show_log(wt.branch, lf, verbose=True)
117
self.assertEqual(1, len(lf.logs))
118
self.assertEqual('1', lf.logs[0].revno)
119
self.assertEqual('empty commit', lf.logs[0].rev.message)
120
self.checkDelta(lf.logs[0].delta)
122
def test_simple_commit(self):
123
wt = self.make_branch_and_tree('.')
124
wt.commit('empty commit')
115
125
self.build_tree(['hello'])
117
wt.commit('add one file')
120
# log using regular thing
121
show_log(b, LongLogFormatter(lf))
123
for l in lf.readlines():
126
# get log as data structure
127
wt.commit('add one file',
128
committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
129
u'<test@example.com>')
127
130
lf = LogCatcher()
128
show_log(b, lf, verbose=True)
130
self.log('log entries:')
131
for logentry in lf.logs:
132
self.log('%4s %s' % (logentry.revno, logentry.rev.message))
131
log.show_log(wt.branch, lf, verbose=True)
132
self.assertEqual(2, len(lf.logs))
134
133
# first one is most recent
135
logentry = lf.logs[0]
136
eq(logentry.revno, '2')
137
eq(logentry.rev.message, 'add one file')
139
self.log('log 2 delta: %r' % d)
140
# self.checkDelta(d, added=['hello'])
142
# commit a log message with control characters
143
msg = "All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
144
self.log("original commit message: %r", msg)
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')
146
144
lf = LogCatcher()
147
show_log(b, lf, verbose=True)
145
log.show_log(wt.branch, lf, verbose=True)
148
146
committed_msg = lf.logs[0].rev.message
149
self.log("escaped commit message: %r", committed_msg)
150
self.assert_(msg != committed_msg)
151
self.assert_(len(committed_msg) > len(msg))
147
self.assertNotEqual(msg, committed_msg)
148
self.assertTrue(len(committed_msg) > len(msg))
153
# Check that log message with only XML-valid characters isn't
150
def test_commit_message_without_control_chars(self):
151
wt = self.make_branch_and_tree('.')
154
152
# escaped. As ElementTree apparently does some kind of
155
153
# newline conversion, neither LF (\x0A) nor CR (\x0D) are
156
154
# included in the test commit message, even though they are
157
155
# valid XML 1.0 characters.
158
156
msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
159
self.log("original commit message: %r", msg)
161
158
lf = LogCatcher()
162
show_log(b, lf, verbose=True)
159
log.show_log(wt.branch, lf, verbose=True)
163
160
committed_msg = lf.logs[0].rev.message
164
self.log("escaped commit message: %r", committed_msg)
165
self.assert_(msg == committed_msg)
161
self.assertEqual(msg, committed_msg)
163
def test_deltas_in_merge_revisions(self):
164
"""Check deltas created for both mainline and merge revisions"""
165
wt = self.make_branch_and_tree('parent')
166
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
169
wt.commit(message='add file1 and file2')
170
self.run_bzr('branch parent child')
171
os.unlink('child/file1')
172
file('child/file2', 'wb').write('hello\n')
173
self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
176
self.run_bzr('merge ../child')
177
wt.commit('merge child branch')
181
lf.supports_merge_revisions = True
182
log.show_log(b, lf, verbose=True)
184
self.assertEqual(3, len(lf.logs))
186
logentry = lf.logs[0]
187
self.assertEqual('2', logentry.revno)
188
self.assertEqual('merge child branch', logentry.rev.message)
189
self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
191
logentry = lf.logs[1]
192
self.assertEqual('1.1.1', logentry.revno)
193
self.assertEqual('remove file1 and modify file2', logentry.rev.message)
194
self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
196
logentry = lf.logs[2]
197
self.assertEqual('1', logentry.revno)
198
self.assertEqual('add file1 and file2', logentry.rev.message)
199
self.checkDelta(logentry.delta, added=['file1', 'file2'])
202
def make_commits_with_trailing_newlines(wt):
203
"""Helper method for LogFormatter tests"""
206
open('a', 'wb').write('hello moto\n')
208
wt.commit('simple log message', rev_id='a1',
209
timestamp=1132586655.459960938, timezone=-6*3600,
210
committer='Joe Foo <joe@foo.com>')
211
open('b', 'wb').write('goodbye\n')
213
wt.commit('multiline\nlog\nmessage\n', rev_id='a2',
214
timestamp=1132586842.411175966, timezone=-6*3600,
215
committer='Joe Foo <joe@foo.com>',
216
authors=['Joe Bar <joe@bar.com>'])
218
open('c', 'wb').write('just another manic monday\n')
220
wt.commit('single line with trailing newline\n', rev_id='a3',
221
timestamp=1132587176.835228920, timezone=-6*3600,
222
committer = 'Joe Foo <joe@foo.com>')
226
def normalize_log(log):
227
"""Replaces the variable lines of logs with fixed lines"""
228
author = 'author: Dolor Sit <test@example.com>'
229
committer = 'committer: Lorem Ipsum <test@example.com>'
230
lines = log.splitlines(True)
231
for idx,line in enumerate(lines):
232
stripped_line = line.lstrip()
233
indent = ' ' * (len(line) - len(stripped_line))
234
if stripped_line.startswith('author:'):
235
lines[idx] = indent + author + '\n'
236
elif stripped_line.startswith('committer:'):
237
lines[idx] = indent + committer + '\n'
238
elif stripped_line.startswith('timestamp:'):
239
lines[idx] = indent + 'timestamp: Just now\n'
240
return ''.join(lines)
243
class TestShortLogFormatter(tests.TestCaseWithTransport):
167
245
def test_trailing_newlines(self):
168
246
wt = self.make_branch_and_tree('.')
171
open('a', 'wb').write('hello moto\n')
173
wt.commit('simple log message', rev_id='a1'
174
, timestamp=1132586655.459960938, timezone=-6*3600
175
, committer='Joe Foo <joe@foo.com>')
176
open('b', 'wb').write('goodbye\n')
178
wt.commit('multiline\nlog\nmessage\n', rev_id='a2'
179
, timestamp=1132586842.411175966, timezone=-6*3600
180
, committer='Joe Foo <joe@foo.com>')
182
open('c', 'wb').write('just another manic monday\n')
184
wt.commit('single line with trailing newline\n', rev_id='a3'
185
, timestamp=1132587176.835228920, timezone=-6*3600
186
, committer = 'Joe Foo <joe@foo.com>')
189
lf = ShortLogFormatter(to_file=sio)
191
self.assertEquals(sio.getvalue(), """\
247
b = make_commits_with_trailing_newlines(wt)
248
sio = self.make_utf8_encoded_stringio()
249
lf = log.ShortLogFormatter(to_file=sio)
251
self.assertEqualDiff("""\
192
252
3 Joe Foo\t2005-11-21
193
253
single line with trailing newline
195
2 Joe Foo\t2005-11-21
255
2 Joe Bar\t2005-11-21
200
260
1 Joe Foo\t2005-11-21
201
261
simple log message
206
lf = LongLogFormatter(to_file=sio)
208
self.assertEquals(sio.getvalue(), """\
209
------------------------------------------------------------
211
committer: Joe Foo <joe@foo.com>
213
timestamp: Mon 2005-11-21 09:32:56 -0600
215
single line with trailing newline
216
------------------------------------------------------------
218
committer: Joe Foo <joe@foo.com>
220
timestamp: Mon 2005-11-21 09:27:22 -0600
225
------------------------------------------------------------
227
committer: Joe Foo <joe@foo.com>
229
timestamp: Mon 2005-11-21 09:24:15 -0600
266
def _prepare_tree_with_merges(self, with_tags=False):
267
wt = self.make_branch_and_memory_tree('.')
269
self.addCleanup(wt.unlock)
271
wt.commit('rev-1', rev_id='rev-1',
272
timestamp=1132586655, timezone=36000,
273
committer='Joe Foo <joe@foo.com>')
274
wt.commit('rev-merged', rev_id='rev-2a',
275
timestamp=1132586700, timezone=36000,
276
committer='Joe Foo <joe@foo.com>')
277
wt.set_parent_ids(['rev-1', 'rev-2a'])
278
wt.branch.set_last_revision_info(1, 'rev-1')
279
wt.commit('rev-2', rev_id='rev-2b',
280
timestamp=1132586800, timezone=36000,
281
committer='Joe Foo <joe@foo.com>')
284
branch.tags.set_tag('v0.2', 'rev-2b')
285
wt.commit('rev-3', rev_id='rev-3',
286
timestamp=1132586900, timezone=36000,
287
committer='Jane Foo <jane@foo.com>')
288
branch.tags.set_tag('v1.0rc1', 'rev-3')
289
branch.tags.set_tag('v1.0', 'rev-3')
292
def test_short_log_with_merges(self):
293
wt = self._prepare_tree_with_merges()
294
logfile = self.make_utf8_encoded_stringio()
295
formatter = log.ShortLogFormatter(to_file=logfile)
296
log.show_log(wt.branch, formatter)
297
self.assertEqualDiff("""\
298
2 Joe Foo\t2005-11-22 [merge]
301
1 Joe Foo\t2005-11-22
304
Use --levels 0 (or -n0) to see merged revisions.
308
def test_short_log_with_merges_and_range(self):
309
wt = self.make_branch_and_memory_tree('.')
311
self.addCleanup(wt.unlock)
313
wt.commit('rev-1', rev_id='rev-1',
314
timestamp=1132586655, timezone=36000,
315
committer='Joe Foo <joe@foo.com>')
316
wt.commit('rev-merged', rev_id='rev-2a',
317
timestamp=1132586700, timezone=36000,
318
committer='Joe Foo <joe@foo.com>')
319
wt.branch.set_last_revision_info(1, 'rev-1')
320
wt.set_parent_ids(['rev-1', 'rev-2a'])
321
wt.commit('rev-2b', rev_id='rev-2b',
322
timestamp=1132586800, timezone=36000,
323
committer='Joe Foo <joe@foo.com>')
324
wt.commit('rev-3a', rev_id='rev-3a',
325
timestamp=1132586800, timezone=36000,
326
committer='Joe Foo <joe@foo.com>')
327
wt.branch.set_last_revision_info(2, 'rev-2b')
328
wt.set_parent_ids(['rev-2b', 'rev-3a'])
329
wt.commit('rev-3b', rev_id='rev-3b',
330
timestamp=1132586800, timezone=36000,
331
committer='Joe Foo <joe@foo.com>')
332
logfile = self.make_utf8_encoded_stringio()
333
formatter = log.ShortLogFormatter(to_file=logfile)
334
log.show_log(wt.branch, formatter,
335
start_revision=2, end_revision=3)
336
self.assertEqualDiff("""\
337
3 Joe Foo\t2005-11-22 [merge]
340
2 Joe Foo\t2005-11-22 [merge]
343
Use --levels 0 (or -n0) to see merged revisions.
347
def test_short_log_with_tags(self):
348
wt = self._prepare_tree_with_merges(with_tags=True)
349
logfile = self.make_utf8_encoded_stringio()
350
formatter = log.ShortLogFormatter(to_file=logfile)
351
log.show_log(wt.branch, formatter)
352
self.assertEqualDiff("""\
353
3 Jane Foo\t2005-11-22 {v1.0, v1.0rc1}
356
2 Joe Foo\t2005-11-22 {v0.2} [merge]
359
1 Joe Foo\t2005-11-22
362
Use --levels 0 (or -n0) to see merged revisions.
366
def test_short_log_single_merge_revision(self):
367
wt = self.make_branch_and_memory_tree('.')
369
self.addCleanup(wt.unlock)
371
wt.commit('rev-1', rev_id='rev-1',
372
timestamp=1132586655, timezone=36000,
373
committer='Joe Foo <joe@foo.com>')
374
wt.commit('rev-merged', rev_id='rev-2a',
375
timestamp=1132586700, timezone=36000,
376
committer='Joe Foo <joe@foo.com>')
377
wt.set_parent_ids(['rev-1', 'rev-2a'])
378
wt.branch.set_last_revision_info(1, 'rev-1')
379
wt.commit('rev-2', rev_id='rev-2b',
380
timestamp=1132586800, timezone=36000,
381
committer='Joe Foo <joe@foo.com>')
382
logfile = self.make_utf8_encoded_stringio()
383
formatter = log.ShortLogFormatter(to_file=logfile)
384
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
386
rev = revspec.in_history(wtb)
387
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
388
self.assertEqualDiff("""\
389
1.1.1 Joe Foo\t2005-11-22
396
class TestShortLogFormatterWithMergeRevisions(tests.TestCaseWithTransport):
398
def test_short_merge_revs_log_with_merges(self):
399
wt = self.make_branch_and_memory_tree('.')
401
self.addCleanup(wt.unlock)
403
wt.commit('rev-1', rev_id='rev-1',
404
timestamp=1132586655, timezone=36000,
405
committer='Joe Foo <joe@foo.com>')
406
wt.commit('rev-merged', rev_id='rev-2a',
407
timestamp=1132586700, timezone=36000,
408
committer='Joe Foo <joe@foo.com>')
409
wt.set_parent_ids(['rev-1', 'rev-2a'])
410
wt.branch.set_last_revision_info(1, 'rev-1')
411
wt.commit('rev-2', rev_id='rev-2b',
412
timestamp=1132586800, timezone=36000,
413
committer='Joe Foo <joe@foo.com>')
414
logfile = self.make_utf8_encoded_stringio()
415
formatter = log.ShortLogFormatter(to_file=logfile, levels=0)
416
log.show_log(wt.branch, formatter)
417
# Note that the 1.1.1 indenting is in fact correct given that
418
# the revision numbers are right justified within 5 characters
419
# for mainline revnos and 9 characters for dotted revnos.
420
self.assertEqualDiff("""\
421
2 Joe Foo\t2005-11-22 [merge]
424
1.1.1 Joe Foo\t2005-11-22
427
1 Joe Foo\t2005-11-22
433
def test_short_merge_revs_log_single_merge_revision(self):
434
wt = self.make_branch_and_memory_tree('.')
436
self.addCleanup(wt.unlock)
438
wt.commit('rev-1', rev_id='rev-1',
439
timestamp=1132586655, timezone=36000,
440
committer='Joe Foo <joe@foo.com>')
441
wt.commit('rev-merged', rev_id='rev-2a',
442
timestamp=1132586700, timezone=36000,
443
committer='Joe Foo <joe@foo.com>')
444
wt.set_parent_ids(['rev-1', 'rev-2a'])
445
wt.branch.set_last_revision_info(1, 'rev-1')
446
wt.commit('rev-2', rev_id='rev-2b',
447
timestamp=1132586800, timezone=36000,
448
committer='Joe Foo <joe@foo.com>')
449
logfile = self.make_utf8_encoded_stringio()
450
formatter = log.ShortLogFormatter(to_file=logfile, levels=0)
451
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
453
rev = revspec.in_history(wtb)
454
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
455
self.assertEqualDiff("""\
456
1.1.1 Joe Foo\t2005-11-22
463
class TestLongLogFormatter(TestCaseWithoutPropsHandler):
234
465
def test_verbose_log(self):
235
466
"""Verbose log includes changed files
239
wt = self.make_branch_and_tree('.')
241
self.build_tree(['a'])
243
# XXX: why does a longer nick show up?
244
b.nick = 'test_verbose_log'
245
wt.commit(message='add a',
246
timestamp=1132711707,
248
committer='Lorem Ipsum <test@example.com>')
249
logfile = file('out.tmp', 'w+')
250
formatter = LongLogFormatter(to_file=logfile)
251
show_log(b, formatter, verbose=True)
254
log_contents = logfile.read()
255
self.assertEqualDiff(log_contents, '''\
256
------------------------------------------------------------
258
committer: Lorem Ipsum <test@example.com>
259
branch nick: test_verbose_log
260
timestamp: Wed 2005-11-23 12:08:27 +1000
470
wt = self.make_branch_and_tree('.')
472
self.build_tree(['a'])
474
# XXX: why does a longer nick show up?
475
b.nick = 'test_verbose_log'
476
wt.commit(message='add a',
477
timestamp=1132711707,
479
committer='Lorem Ipsum <test@example.com>')
480
logfile = file('out.tmp', 'w+')
481
formatter = log.LongLogFormatter(to_file=logfile)
482
log.show_log(b, formatter, verbose=True)
485
log_contents = logfile.read()
486
self.assertEqualDiff('''\
487
------------------------------------------------------------
489
committer: Lorem Ipsum <test@example.com>
490
branch nick: test_verbose_log
491
timestamp: Wed 2005-11-23 12:08:27 +1000
499
def test_merges_are_indented_by_level(self):
500
wt = self.make_branch_and_tree('parent')
501
wt.commit('first post')
502
self.run_bzr('branch parent child')
503
self.run_bzr(['commit', '-m', 'branch 1', '--unchanged', 'child'])
504
self.run_bzr('branch child smallerchild')
505
self.run_bzr(['commit', '-m', 'branch 2', '--unchanged',
508
self.run_bzr('merge ../smallerchild')
509
self.run_bzr(['commit', '-m', 'merge branch 2'])
510
os.chdir('../parent')
511
self.run_bzr('merge ../child')
512
wt.commit('merge branch 1')
514
sio = self.make_utf8_encoded_stringio()
515
lf = log.LongLogFormatter(to_file=sio, levels=0)
516
log.show_log(b, lf, verbose=True)
517
the_log = normalize_log(sio.getvalue())
518
self.assertEqualDiff("""\
519
------------------------------------------------------------
521
committer: Lorem Ipsum <test@example.com>
526
------------------------------------------------------------
528
committer: Lorem Ipsum <test@example.com>
533
------------------------------------------------------------
535
committer: Lorem Ipsum <test@example.com>
536
branch nick: smallerchild
540
------------------------------------------------------------
542
committer: Lorem Ipsum <test@example.com>
547
------------------------------------------------------------
549
committer: Lorem Ipsum <test@example.com>
557
def test_verbose_merge_revisions_contain_deltas(self):
558
wt = self.make_branch_and_tree('parent')
559
self.build_tree(['parent/f1', 'parent/f2'])
561
wt.commit('first post')
562
self.run_bzr('branch parent child')
563
os.unlink('child/f1')
564
file('child/f2', 'wb').write('hello\n')
565
self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
568
self.run_bzr('merge ../child')
569
wt.commit('merge branch 1')
571
sio = self.make_utf8_encoded_stringio()
572
lf = log.LongLogFormatter(to_file=sio, levels=0)
573
log.show_log(b, lf, verbose=True)
574
the_log = normalize_log(sio.getvalue())
575
self.assertEqualDiff("""\
576
------------------------------------------------------------
578
committer: Lorem Ipsum <test@example.com>
587
------------------------------------------------------------
589
committer: Lorem Ipsum <test@example.com>
593
removed f1 and modified f2
598
------------------------------------------------------------
600
committer: Lorem Ipsum <test@example.com>
611
def test_trailing_newlines(self):
612
wt = self.make_branch_and_tree('.')
613
b = make_commits_with_trailing_newlines(wt)
614
sio = self.make_utf8_encoded_stringio()
615
lf = log.LongLogFormatter(to_file=sio)
617
self.assertEqualDiff("""\
618
------------------------------------------------------------
620
committer: Joe Foo <joe@foo.com>
622
timestamp: Mon 2005-11-21 09:32:56 -0600
624
single line with trailing newline
625
------------------------------------------------------------
627
author: Joe Bar <joe@bar.com>
628
committer: Joe Foo <joe@foo.com>
630
timestamp: Mon 2005-11-21 09:27:22 -0600
635
------------------------------------------------------------
637
committer: Joe Foo <joe@foo.com>
639
timestamp: Mon 2005-11-21 09:24:15 -0600
645
def test_author_in_log(self):
646
"""Log includes the author name if it's set in
647
the revision properties
649
wt = self.make_branch_and_tree('.')
651
self.build_tree(['a'])
653
b.nick = 'test_author_log'
654
wt.commit(message='add a',
655
timestamp=1132711707,
657
committer='Lorem Ipsum <test@example.com>',
658
authors=['John Doe <jdoe@example.com>',
659
'Jane Rey <jrey@example.com>'])
661
formatter = log.LongLogFormatter(to_file=sio)
662
log.show_log(b, formatter)
663
self.assertEqualDiff('''\
664
------------------------------------------------------------
666
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
667
committer: Lorem Ipsum <test@example.com>
668
branch nick: test_author_log
669
timestamp: Wed 2005-11-23 12:08:27 +1000
675
def test_properties_in_log(self):
676
"""Log includes the custom properties returned by the registered
679
wt = self.make_branch_and_tree('.')
681
self.build_tree(['a'])
683
b.nick = 'test_properties_in_log'
684
wt.commit(message='add a',
685
timestamp=1132711707,
687
committer='Lorem Ipsum <test@example.com>',
688
authors=['John Doe <jdoe@example.com>'])
690
formatter = log.LongLogFormatter(to_file=sio)
692
def trivial_custom_prop_handler(revision):
693
return {'test_prop':'test_value'}
695
log.properties_handler_registry.register(
696
'trivial_custom_prop_handler',
697
trivial_custom_prop_handler)
698
log.show_log(b, formatter)
700
log.properties_handler_registry.remove(
701
'trivial_custom_prop_handler')
702
self.assertEqualDiff('''\
703
------------------------------------------------------------
705
test_prop: test_value
706
author: John Doe <jdoe@example.com>
707
committer: Lorem Ipsum <test@example.com>
708
branch nick: test_properties_in_log
709
timestamp: Wed 2005-11-23 12:08:27 +1000
715
def test_properties_in_short_log(self):
716
"""Log includes the custom properties returned by the registered
719
wt = self.make_branch_and_tree('.')
721
self.build_tree(['a'])
723
b.nick = 'test_properties_in_short_log'
724
wt.commit(message='add a',
725
timestamp=1132711707,
727
committer='Lorem Ipsum <test@example.com>',
728
authors=['John Doe <jdoe@example.com>'])
730
formatter = log.ShortLogFormatter(to_file=sio)
732
def trivial_custom_prop_handler(revision):
733
return {'test_prop':'test_value'}
735
log.properties_handler_registry.register(
736
'trivial_custom_prop_handler',
737
trivial_custom_prop_handler)
738
log.show_log(b, formatter)
740
log.properties_handler_registry.remove(
741
'trivial_custom_prop_handler')
742
self.assertEqualDiff('''\
743
1 John Doe\t2005-11-23
744
test_prop: test_value
750
def test_error_in_properties_handler(self):
751
"""Log includes the custom properties returned by the registered
754
wt = self.make_branch_and_tree('.')
756
self.build_tree(['a'])
758
b.nick = 'test_author_log'
759
wt.commit(message='add a',
760
timestamp=1132711707,
762
committer='Lorem Ipsum <test@example.com>',
763
authors=['John Doe <jdoe@example.com>'],
764
revprops={'first_prop':'first_value'})
766
formatter = log.LongLogFormatter(to_file=sio)
768
def trivial_custom_prop_handler(revision):
769
raise StandardError("a test error")
771
log.properties_handler_registry.register(
772
'trivial_custom_prop_handler',
773
trivial_custom_prop_handler)
774
self.assertRaises(StandardError, log.show_log, b, formatter,)
776
log.properties_handler_registry.remove(
777
'trivial_custom_prop_handler')
779
def test_properties_handler_bad_argument(self):
780
wt = self.make_branch_and_tree('.')
782
self.build_tree(['a'])
784
b.nick = 'test_author_log'
785
wt.commit(message='add a',
786
timestamp=1132711707,
788
committer='Lorem Ipsum <test@example.com>',
789
authors=['John Doe <jdoe@example.com>'],
790
revprops={'a_prop':'test_value'})
792
formatter = log.LongLogFormatter(to_file=sio)
794
def bad_argument_prop_handler(revision):
795
return {'custom_prop_name':revision.properties['a_prop']}
797
log.properties_handler_registry.register(
798
'bad_argument_prop_handler',
799
bad_argument_prop_handler)
801
self.assertRaises(AttributeError, formatter.show_properties,
804
revision = b.repository.get_revision(b.last_revision())
805
formatter.show_properties(revision, '')
806
self.assertEqualDiff('''custom_prop_name: test_value\n''',
809
log.properties_handler_registry.remove(
810
'bad_argument_prop_handler')
813
class TestLongLogFormatterWithoutMergeRevisions(TestCaseWithoutPropsHandler):
815
def test_long_verbose_log(self):
816
"""Verbose log includes changed files
820
wt = self.make_branch_and_tree('.')
822
self.build_tree(['a'])
824
# XXX: why does a longer nick show up?
825
b.nick = 'test_verbose_log'
826
wt.commit(message='add a',
827
timestamp=1132711707,
829
committer='Lorem Ipsum <test@example.com>')
830
logfile = file('out.tmp', 'w+')
831
formatter = log.LongLogFormatter(to_file=logfile, levels=1)
832
log.show_log(b, formatter, verbose=True)
835
log_contents = logfile.read()
836
self.assertEqualDiff('''\
837
------------------------------------------------------------
839
committer: Lorem Ipsum <test@example.com>
840
branch nick: test_verbose_log
841
timestamp: Wed 2005-11-23 12:08:27 +1000
849
def test_long_verbose_contain_deltas(self):
850
wt = self.make_branch_and_tree('parent')
851
self.build_tree(['parent/f1', 'parent/f2'])
853
wt.commit('first post')
854
self.run_bzr('branch parent child')
855
os.unlink('child/f1')
856
file('child/f2', 'wb').write('hello\n')
857
self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
860
self.run_bzr('merge ../child')
861
wt.commit('merge branch 1')
863
sio = self.make_utf8_encoded_stringio()
864
lf = log.LongLogFormatter(to_file=sio, levels=1)
865
log.show_log(b, lf, verbose=True)
866
the_log = normalize_log(sio.getvalue())
867
self.assertEqualDiff("""\
868
------------------------------------------------------------
870
committer: Lorem Ipsum <test@example.com>
879
------------------------------------------------------------
881
committer: Lorem Ipsum <test@example.com>
889
------------------------------------------------------------
890
Use --levels 0 (or -n0) to see merged revisions.
894
def test_long_trailing_newlines(self):
895
wt = self.make_branch_and_tree('.')
896
b = make_commits_with_trailing_newlines(wt)
897
sio = self.make_utf8_encoded_stringio()
898
lf = log.LongLogFormatter(to_file=sio, levels=1)
900
self.assertEqualDiff("""\
901
------------------------------------------------------------
903
committer: Joe Foo <joe@foo.com>
905
timestamp: Mon 2005-11-21 09:32:56 -0600
907
single line with trailing newline
908
------------------------------------------------------------
910
author: Joe Bar <joe@bar.com>
911
committer: Joe Foo <joe@foo.com>
913
timestamp: Mon 2005-11-21 09:27:22 -0600
918
------------------------------------------------------------
920
committer: Joe Foo <joe@foo.com>
922
timestamp: Mon 2005-11-21 09:24:15 -0600
928
def test_long_author_in_log(self):
929
"""Log includes the author name if it's set in
930
the revision properties
932
wt = self.make_branch_and_tree('.')
934
self.build_tree(['a'])
936
b.nick = 'test_author_log'
937
wt.commit(message='add a',
938
timestamp=1132711707,
940
committer='Lorem Ipsum <test@example.com>',
941
authors=['John Doe <jdoe@example.com>'])
943
formatter = log.LongLogFormatter(to_file=sio, levels=1)
944
log.show_log(b, formatter)
945
self.assertEqualDiff('''\
946
------------------------------------------------------------
948
author: John Doe <jdoe@example.com>
949
committer: Lorem Ipsum <test@example.com>
950
branch nick: test_author_log
951
timestamp: Wed 2005-11-23 12:08:27 +1000
957
def test_long_properties_in_log(self):
958
"""Log includes the custom properties returned by the registered
961
wt = self.make_branch_and_tree('.')
963
self.build_tree(['a'])
965
b.nick = 'test_properties_in_log'
966
wt.commit(message='add a',
967
timestamp=1132711707,
969
committer='Lorem Ipsum <test@example.com>',
970
authors=['John Doe <jdoe@example.com>'])
972
formatter = log.LongLogFormatter(to_file=sio, levels=1)
974
def trivial_custom_prop_handler(revision):
975
return {'test_prop':'test_value'}
977
log.properties_handler_registry.register(
978
'trivial_custom_prop_handler',
979
trivial_custom_prop_handler)
980
log.show_log(b, formatter)
982
log.properties_handler_registry.remove(
983
'trivial_custom_prop_handler')
984
self.assertEqualDiff('''\
985
------------------------------------------------------------
987
test_prop: test_value
988
author: John Doe <jdoe@example.com>
989
committer: Lorem Ipsum <test@example.com>
990
branch nick: test_properties_in_log
991
timestamp: Wed 2005-11-23 12:08:27 +1000
998
class TestLineLogFormatter(tests.TestCaseWithTransport):
267
1000
def test_line_log(self):
268
1001
"""Line log should show revno
272
wt = self.make_branch_and_tree('.')
274
self.build_tree(['a'])
276
b.nick = 'test-line-log'
277
wt.commit(message='add a',
278
timestamp=1132711707,
280
committer='Line-Log-Formatter Tester <test@line.log>')
281
logfile = file('out.tmp', 'w+')
282
formatter = LineLogFormatter(to_file=logfile)
283
show_log(b, formatter)
286
log_contents = logfile.read()
287
self.assertEqualDiff(log_contents, '1: Line-Log-Formatte... 2005-11-23 add a\n')
1005
wt = self.make_branch_and_tree('.')
1007
self.build_tree(['a'])
1009
b.nick = 'test-line-log'
1010
wt.commit(message='add a',
1011
timestamp=1132711707,
1013
committer='Line-Log-Formatter Tester <test@line.log>')
1014
logfile = file('out.tmp', 'w+')
1015
formatter = log.LineLogFormatter(to_file=logfile)
1016
log.show_log(b, formatter)
1019
log_contents = logfile.read()
1020
self.assertEqualDiff('1: Line-Log-Formatte... 2005-11-23 add a\n',
1023
def test_trailing_newlines(self):
1024
wt = self.make_branch_and_tree('.')
1025
b = make_commits_with_trailing_newlines(wt)
1026
sio = self.make_utf8_encoded_stringio()
1027
lf = log.LineLogFormatter(to_file=sio)
1029
self.assertEqualDiff("""\
1030
3: Joe Foo 2005-11-21 single line with trailing newline
1031
2: Joe Bar 2005-11-21 multiline
1032
1: Joe Foo 2005-11-21 simple log message
1036
def _prepare_tree_with_merges(self, with_tags=False):
1037
wt = self.make_branch_and_memory_tree('.')
1039
self.addCleanup(wt.unlock)
1041
wt.commit('rev-1', rev_id='rev-1',
1042
timestamp=1132586655, timezone=36000,
1043
committer='Joe Foo <joe@foo.com>')
1044
wt.commit('rev-merged', rev_id='rev-2a',
1045
timestamp=1132586700, timezone=36000,
1046
committer='Joe Foo <joe@foo.com>')
1047
wt.set_parent_ids(['rev-1', 'rev-2a'])
1048
wt.branch.set_last_revision_info(1, 'rev-1')
1049
wt.commit('rev-2', rev_id='rev-2b',
1050
timestamp=1132586800, timezone=36000,
1051
committer='Joe Foo <joe@foo.com>')
1054
branch.tags.set_tag('v0.2', 'rev-2b')
1055
wt.commit('rev-3', rev_id='rev-3',
1056
timestamp=1132586900, timezone=36000,
1057
committer='Jane Foo <jane@foo.com>')
1058
branch.tags.set_tag('v1.0rc1', 'rev-3')
1059
branch.tags.set_tag('v1.0', 'rev-3')
1062
def test_line_log_single_merge_revision(self):
1063
wt = self._prepare_tree_with_merges()
1064
logfile = self.make_utf8_encoded_stringio()
1065
formatter = log.LineLogFormatter(to_file=logfile)
1066
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
1068
rev = revspec.in_history(wtb)
1069
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
1070
self.assertEqualDiff("""\
1071
1.1.1: Joe Foo 2005-11-22 rev-merged
1075
def test_line_log_with_tags(self):
1076
wt = self._prepare_tree_with_merges(with_tags=True)
1077
logfile = self.make_utf8_encoded_stringio()
1078
formatter = log.LineLogFormatter(to_file=logfile)
1079
log.show_log(wt.branch, formatter)
1080
self.assertEqualDiff("""\
1081
3: Jane Foo 2005-11-22 {v1.0, v1.0rc1} rev-3
1082
2: Joe Foo 2005-11-22 [merge] {v0.2} rev-2
1083
1: Joe Foo 2005-11-22 rev-1
1087
class TestLineLogFormatterWithMergeRevisions(tests.TestCaseWithTransport):
1089
def test_line_merge_revs_log(self):
1090
"""Line log should show revno
1094
wt = self.make_branch_and_tree('.')
1096
self.build_tree(['a'])
1098
b.nick = 'test-line-log'
1099
wt.commit(message='add a',
1100
timestamp=1132711707,
1102
committer='Line-Log-Formatter Tester <test@line.log>')
1103
logfile = file('out.tmp', 'w+')
1104
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1105
log.show_log(b, formatter)
1108
log_contents = logfile.read()
1109
self.assertEqualDiff('1: Line-Log-Formatte... 2005-11-23 add a\n',
1112
def test_line_merge_revs_log_single_merge_revision(self):
1113
wt = self.make_branch_and_memory_tree('.')
1115
self.addCleanup(wt.unlock)
1117
wt.commit('rev-1', rev_id='rev-1',
1118
timestamp=1132586655, timezone=36000,
1119
committer='Joe Foo <joe@foo.com>')
1120
wt.commit('rev-merged', rev_id='rev-2a',
1121
timestamp=1132586700, timezone=36000,
1122
committer='Joe Foo <joe@foo.com>')
1123
wt.set_parent_ids(['rev-1', 'rev-2a'])
1124
wt.branch.set_last_revision_info(1, 'rev-1')
1125
wt.commit('rev-2', rev_id='rev-2b',
1126
timestamp=1132586800, timezone=36000,
1127
committer='Joe Foo <joe@foo.com>')
1128
logfile = self.make_utf8_encoded_stringio()
1129
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1130
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
1132
rev = revspec.in_history(wtb)
1133
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
1134
self.assertEqualDiff("""\
1135
1.1.1: Joe Foo 2005-11-22 rev-merged
1139
def test_line_merge_revs_log_with_merges(self):
1140
wt = self.make_branch_and_memory_tree('.')
1142
self.addCleanup(wt.unlock)
1144
wt.commit('rev-1', rev_id='rev-1',
1145
timestamp=1132586655, timezone=36000,
1146
committer='Joe Foo <joe@foo.com>')
1147
wt.commit('rev-merged', rev_id='rev-2a',
1148
timestamp=1132586700, timezone=36000,
1149
committer='Joe Foo <joe@foo.com>')
1150
wt.set_parent_ids(['rev-1', 'rev-2a'])
1151
wt.branch.set_last_revision_info(1, 'rev-1')
1152
wt.commit('rev-2', rev_id='rev-2b',
1153
timestamp=1132586800, timezone=36000,
1154
committer='Joe Foo <joe@foo.com>')
1155
logfile = self.make_utf8_encoded_stringio()
1156
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1157
log.show_log(wt.branch, formatter)
1158
self.assertEqualDiff("""\
1159
2: Joe Foo 2005-11-22 [merge] rev-2
1160
1.1.1: Joe Foo 2005-11-22 rev-merged
1161
1: Joe Foo 2005-11-22 rev-1
1165
class TestGetViewRevisions(tests.TestCaseWithTransport):
289
1167
def make_tree_with_commits(self):
290
1168
"""Create a tree with well-known revision ids"""
339
1225
def test_get_view_revisions_forward(self):
340
1226
"""Test the get_view_revisions method"""
341
1227
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
342
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
1229
self.addCleanup(wt.unlock)
1230
revisions = list(log.get_view_revisions(
1231
mainline_revs, rev_nos, wt.branch, 'forward'))
344
1232
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
346
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
347
'forward', include_merges=False))
1234
revisions2 = list(log.get_view_revisions(
1235
mainline_revs, rev_nos, wt.branch, 'forward',
1236
include_merges=False))
348
1237
self.assertEqual(revisions, revisions2)
350
1239
def test_get_view_revisions_reverse(self):
351
1240
"""Test the get_view_revisions with reverse"""
352
1241
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
353
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
1243
self.addCleanup(wt.unlock)
1244
revisions = list(log.get_view_revisions(
1245
mainline_revs, rev_nos, wt.branch, 'reverse'))
355
1246
self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
357
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
358
'reverse', include_merges=False))
1248
revisions2 = list(log.get_view_revisions(
1249
mainline_revs, rev_nos, wt.branch, 'reverse',
1250
include_merges=False))
359
1251
self.assertEqual(revisions, revisions2)
361
1253
def test_get_view_revisions_merge(self):
362
1254
"""Test get_view_revisions when there are merges"""
363
1255
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
364
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
366
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
367
('4b', '4', 0), ('4a', '3.1.1', 1)],
369
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
370
'forward', include_merges=False))
371
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
1257
self.addCleanup(wt.unlock)
1258
revisions = list(log.get_view_revisions(
1259
mainline_revs, rev_nos, wt.branch, 'forward'))
1260
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
1261
('4b', '4', 0), ('4a', '3.1.1', 1)],
1263
revisions = list(log.get_view_revisions(
1264
mainline_revs, rev_nos, wt.branch, 'forward',
1265
include_merges=False))
1266
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
375
1270
def test_get_view_revisions_merge_reverse(self):
376
1271
"""Test get_view_revisions in reverse when there are merges"""
377
1272
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
378
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
1274
self.addCleanup(wt.unlock)
1275
revisions = list(log.get_view_revisions(
1276
mainline_revs, rev_nos, wt.branch, 'reverse'))
380
1277
self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
381
('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
383
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
384
'reverse', include_merges=False))
1278
('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
1280
revisions = list(log.get_view_revisions(
1281
mainline_revs, rev_nos, wt.branch, 'reverse',
1282
include_merges=False))
385
1283
self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
389
1287
def test_get_view_revisions_merge2(self):
390
1288
"""Test get_view_revisions when there are merges"""
391
1289
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
392
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
1291
self.addCleanup(wt.unlock)
1292
revisions = list(log.get_view_revisions(
1293
mainline_revs, rev_nos, wt.branch, 'forward'))
394
1294
expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
395
('3a', '2.2.1', 1), ('3b', '2.1.1', 1), ('4b', '4', 0),
1295
('3a', '2.1.1', 1), ('3b', '2.2.1', 1), ('4b', '4', 0),
397
1297
self.assertEqual(expected, revisions)
398
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
399
'forward', include_merges=False))
1298
revisions = list(log.get_view_revisions(
1299
mainline_revs, rev_nos, wt.branch, 'forward',
1300
include_merges=False))
400
1301
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
1306
def test_file_id_for_range(self):
1307
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
1309
self.addCleanup(wt.unlock)
1311
def rev_from_rev_id(revid, branch):
1312
revspec = revisionspec.RevisionSpec.from_string('revid:%s' % revid)
1313
return revspec.in_history(branch)
1315
def view_revs(start_rev, end_rev, file_id, direction):
1316
revs = log.calculate_view_revisions(
1318
start_rev, # start_revision
1319
end_rev, # end_revision
1320
direction, # direction
1321
file_id, # specific_fileid
1322
True, # generate_merge_revisions
1326
rev_3a = rev_from_rev_id('3a', wt.branch)
1327
rev_4b = rev_from_rev_id('4b', wt.branch)
1328
self.assertEqual([('3c', '3', 0), ('3a', '2.1.1', 1)],
1329
view_revs(rev_3a, rev_4b, 'f-id', 'reverse'))
1330
# Note: 3c still appears before 3a here because of depth-based sorting
1331
self.assertEqual([('3c', '3', 0), ('3a', '2.1.1', 1)],
1332
view_revs(rev_3a, rev_4b, 'f-id', 'forward'))
1335
class TestGetRevisionsTouchingFileID(tests.TestCaseWithTransport):
1337
def create_tree_with_single_merge(self):
1338
"""Create a branch with a moderate layout.
1340
The revision graph looks like:
1348
In this graph, A introduced files f1 and f2 and f3.
1349
B modifies f1 and f3, and C modifies f2 and f3.
1350
D merges the changes from B and C and resolves the conflict for f3.
1352
# TODO: jam 20070218 This seems like it could really be done
1353
# with make_branch_and_memory_tree() if we could just
1354
# create the content of those files.
1355
# TODO: jam 20070218 Another alternative is that we would really
1356
# like to only create this tree 1 time for all tests that
1357
# use it. Since 'log' only uses the tree in a readonly
1358
# fashion, it seems a shame to regenerate an identical
1359
# tree for each test.
1360
tree = self.make_branch_and_tree('tree')
1362
self.addCleanup(tree.unlock)
1364
self.build_tree_contents([('tree/f1', 'A\n'),
1368
tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
1369
tree.commit('A', rev_id='A')
1371
self.build_tree_contents([('tree/f2', 'A\nC\n'),
1372
('tree/f3', 'A\nC\n'),
1374
tree.commit('C', rev_id='C')
1375
# Revert back to A to build the other history.
1376
tree.set_last_revision('A')
1377
tree.branch.set_last_revision_info(1, 'A')
1378
self.build_tree_contents([('tree/f1', 'A\nB\n'),
1380
('tree/f3', 'A\nB\n'),
1382
tree.commit('B', rev_id='B')
1383
tree.set_parent_ids(['B', 'C'])
1384
self.build_tree_contents([('tree/f1', 'A\nB\n'),
1385
('tree/f2', 'A\nC\n'),
1386
('tree/f3', 'A\nB\nC\n'),
1388
tree.commit('D', rev_id='D')
1390
# Switch to a read lock for this tree.
1391
# We still have an addCleanup(tree.unlock) pending
1396
def check_delta(self, delta, **kw):
1397
"""Check the filenames touched by a delta are as expected.
1399
Caller only have to pass in the list of files for each part, all
1400
unspecified parts are considered empty (and checked as such).
1402
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
1403
# By default we expect an empty list
1404
expected = kw.get(n, [])
1405
# strip out only the path components
1406
got = [x[0] for x in getattr(delta, n)]
1407
self.assertEqual(expected, got)
1409
def test_tree_with_single_merge(self):
1410
"""Make sure the tree layout is correct."""
1411
tree = self.create_tree_with_single_merge()
1412
rev_A_tree = tree.branch.repository.revision_tree('A')
1413
rev_B_tree = tree.branch.repository.revision_tree('B')
1414
rev_C_tree = tree.branch.repository.revision_tree('C')
1415
rev_D_tree = tree.branch.repository.revision_tree('D')
1417
self.check_delta(rev_B_tree.changes_from(rev_A_tree),
1418
modified=['f1', 'f3'])
1420
self.check_delta(rev_C_tree.changes_from(rev_A_tree),
1421
modified=['f2', 'f3'])
1423
self.check_delta(rev_D_tree.changes_from(rev_B_tree),
1424
modified=['f2', 'f3'])
1426
self.check_delta(rev_D_tree.changes_from(rev_C_tree),
1427
modified=['f1', 'f3'])
1429
def assertAllRevisionsForFileID(self, tree, file_id, revisions):
1430
"""Ensure _filter_revisions_touching_file_id returns the right values.
1432
Get the return value from _filter_revisions_touching_file_id and make
1433
sure they are correct.
1435
# The api for _filter_revisions_touching_file_id is a little crazy.
1436
# So we do the setup here.
1437
mainline = tree.branch.revision_history()
1438
mainline.insert(0, None)
1439
revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
1440
view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
1442
actual_revs = log._filter_revisions_touching_file_id(
1445
list(view_revs_iter))
1446
self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
1448
def test_file_id_f1(self):
1449
tree = self.create_tree_with_single_merge()
1450
# f1 should be marked as modified by revisions A and B
1451
self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
1453
def test_file_id_f2(self):
1454
tree = self.create_tree_with_single_merge()
1455
# f2 should be marked as modified by revisions A, C, and D
1456
# because D merged the changes from C.
1457
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1459
def test_file_id_f3(self):
1460
tree = self.create_tree_with_single_merge()
1461
# f3 should be marked as modified by revisions A, B, C, and D
1462
self.assertAllRevisionsForFileID(tree, 'f3-id', ['D', 'C', 'B', 'A'])
1464
def test_file_id_with_ghosts(self):
1465
# This is testing bug #209948, where having a ghost would cause
1466
# _filter_revisions_touching_file_id() to fail.
1467
tree = self.create_tree_with_single_merge()
1468
# We need to add a revision, so switch back to a write-locked tree
1469
# (still a single addCleanup(tree.unlock) pending).
1472
first_parent = tree.last_revision()
1473
tree.set_parent_ids([first_parent, 'ghost-revision-id'])
1474
self.build_tree_contents([('tree/f1', 'A\nB\nXX\n')])
1475
tree.commit('commit with a ghost', rev_id='XX')
1476
self.assertAllRevisionsForFileID(tree, 'f1-id', ['XX', 'B', 'A'])
1477
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1479
def test_unknown_file_id(self):
1480
tree = self.create_tree_with_single_merge()
1481
self.assertAllRevisionsForFileID(tree, 'unknown', [])
1483
def test_empty_branch_unknown_file_id(self):
1484
tree = self.make_branch_and_tree('tree')
1485
self.assertAllRevisionsForFileID(tree, 'unknown', [])
1488
class TestShowChangedRevisions(tests.TestCaseWithTransport):
1490
def test_show_changed_revisions_verbose(self):
1491
tree = self.make_branch_and_tree('tree_a')
1492
self.build_tree(['tree_a/foo'])
1494
tree.commit('bar', rev_id='bar-id')
1495
s = self.make_utf8_encoded_stringio()
1496
log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
1497
self.assertContainsRe(s.getvalue(), 'bar')
1498
self.assertNotContainsRe(s.getvalue(), 'foo')
1501
class TestLogFormatter(tests.TestCase):
1503
def test_short_committer(self):
1504
rev = revision.Revision('a-id')
1505
rev.committer = 'John Doe <jdoe@example.com>'
1506
lf = log.LogFormatter(None)
1507
self.assertEqual('John Doe', lf.short_committer(rev))
1508
rev.committer = 'John Smith <jsmith@example.com>'
1509
self.assertEqual('John Smith', lf.short_committer(rev))
1510
rev.committer = 'John Smith'
1511
self.assertEqual('John Smith', lf.short_committer(rev))
1512
rev.committer = 'jsmith@example.com'
1513
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1514
rev.committer = '<jsmith@example.com>'
1515
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1516
rev.committer = 'John Smith jsmith@example.com'
1517
self.assertEqual('John Smith', lf.short_committer(rev))
1519
def test_short_author(self):
1520
rev = revision.Revision('a-id')
1521
rev.committer = 'John Doe <jdoe@example.com>'
1522
lf = log.LogFormatter(None)
1523
self.assertEqual('John Doe', lf.short_author(rev))
1524
rev.properties['author'] = 'John Smith <jsmith@example.com>'
1525
self.assertEqual('John Smith', lf.short_author(rev))
1526
rev.properties['author'] = 'John Smith'
1527
self.assertEqual('John Smith', lf.short_author(rev))
1528
rev.properties['author'] = 'jsmith@example.com'
1529
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1530
rev.properties['author'] = '<jsmith@example.com>'
1531
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1532
rev.properties['author'] = 'John Smith jsmith@example.com'
1533
self.assertEqual('John Smith', lf.short_author(rev))
1534
del rev.properties['author']
1535
rev.properties['authors'] = ('John Smith <jsmith@example.com>\n'
1536
'Jane Rey <jrey@example.com>')
1537
self.assertEqual('John Smith', lf.short_author(rev))
1540
class TestReverseByDepth(tests.TestCase):
1541
"""Test reverse_by_depth behavior.
1543
This is used to present revisions in forward (oldest first) order in a nice
1546
The tests use lighter revision description to ease reading.
1549
def assertReversed(self, forward, backward):
1550
# Transform the descriptions to suit the API: tests use (revno, depth),
1551
# while the API expects (revid, revno, depth)
1552
def complete_revisions(l):
1553
"""Transform the description to suit the API.
1555
Tests use (revno, depth) whil the API expects (revid, revno, depth).
1556
Since the revid is arbitrary, we just duplicate revno
1558
return [ (r, r, d) for r, d in l]
1559
forward = complete_revisions(forward)
1560
backward= complete_revisions(backward)
1561
self.assertEqual(forward, log.reverse_by_depth(backward))
1564
def test_mainline_revisions(self):
1565
self.assertReversed([( '1', 0), ('2', 0)],
1566
[('2', 0), ('1', 0)])
1568
def test_merged_revisions(self):
1569
self.assertReversed([('1', 0), ('2', 0), ('2.2', 1), ('2.1', 1),],
1570
[('2', 0), ('2.1', 1), ('2.2', 1), ('1', 0),])
1571
def test_shifted_merged_revisions(self):
1572
"""Test irregular layout.
1574
Requesting revisions touching a file can produce "holes" in the depths.
1576
self.assertReversed([('1', 0), ('2', 0), ('1.1', 2), ('1.2', 2),],
1577
[('2', 0), ('1.2', 2), ('1.1', 2), ('1', 0),])
1579
def test_merged_without_child_revisions(self):
1580
"""Test irregular layout.
1582
Revision ranges can produce "holes" in the depths.
1584
# When a revision of higher depth doesn't follow one of lower depth, we
1585
# assume a lower depth one is virtually there
1586
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1587
[('4', 4), ('3', 3), ('2', 2), ('1', 2),])
1588
# So we get the same order after reversing below even if the original
1589
# revisions are not in the same order.
1590
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1591
[('3', 3), ('4', 4), ('2', 2), ('1', 2),])
1594
class TestHistoryChange(tests.TestCaseWithTransport):
1596
def setup_a_tree(self):
1597
tree = self.make_branch_and_tree('tree')
1599
self.addCleanup(tree.unlock)
1600
tree.commit('1a', rev_id='1a')
1601
tree.commit('2a', rev_id='2a')
1602
tree.commit('3a', rev_id='3a')
1605
def setup_ab_tree(self):
1606
tree = self.setup_a_tree()
1607
tree.set_last_revision('1a')
1608
tree.branch.set_last_revision_info(1, '1a')
1609
tree.commit('2b', rev_id='2b')
1610
tree.commit('3b', rev_id='3b')
1613
def setup_ac_tree(self):
1614
tree = self.setup_a_tree()
1615
tree.set_last_revision(revision.NULL_REVISION)
1616
tree.branch.set_last_revision_info(0, revision.NULL_REVISION)
1617
tree.commit('1c', rev_id='1c')
1618
tree.commit('2c', rev_id='2c')
1619
tree.commit('3c', rev_id='3c')
1622
def test_all_new(self):
1623
tree = self.setup_ab_tree()
1624
old, new = log.get_history_change('1a', '3a', tree.branch.repository)
1625
self.assertEqual([], old)
1626
self.assertEqual(['2a', '3a'], new)
1628
def test_all_old(self):
1629
tree = self.setup_ab_tree()
1630
old, new = log.get_history_change('3a', '1a', tree.branch.repository)
1631
self.assertEqual([], new)
1632
self.assertEqual(['2a', '3a'], old)
1634
def test_null_old(self):
1635
tree = self.setup_ab_tree()
1636
old, new = log.get_history_change(revision.NULL_REVISION,
1637
'3a', tree.branch.repository)
1638
self.assertEqual([], old)
1639
self.assertEqual(['1a', '2a', '3a'], new)
1641
def test_null_new(self):
1642
tree = self.setup_ab_tree()
1643
old, new = log.get_history_change('3a', revision.NULL_REVISION,
1644
tree.branch.repository)
1645
self.assertEqual([], new)
1646
self.assertEqual(['1a', '2a', '3a'], old)
1648
def test_diverged(self):
1649
tree = self.setup_ab_tree()
1650
old, new = log.get_history_change('3a', '3b', tree.branch.repository)
1651
self.assertEqual(old, ['2a', '3a'])
1652
self.assertEqual(new, ['2b', '3b'])
1654
def test_unrelated(self):
1655
tree = self.setup_ac_tree()
1656
old, new = log.get_history_change('3a', '3c', tree.branch.repository)
1657
self.assertEqual(old, ['1a', '2a', '3a'])
1658
self.assertEqual(new, ['1c', '2c', '3c'])
1660
def test_show_branch_change(self):
1661
tree = self.setup_ab_tree()
1663
log.show_branch_change(tree.branch, s, 3, '3a')
1664
self.assertContainsRe(s.getvalue(),
1665
'[*]{60}\nRemoved Revisions:\n(.|\n)*2a(.|\n)*3a(.|\n)*'
1666
'[*]{60}\n\nAdded Revisions:\n(.|\n)*2b(.|\n)*3b')
1668
def test_show_branch_change_no_change(self):
1669
tree = self.setup_ab_tree()
1671
log.show_branch_change(tree.branch, s, 3, '3b')
1672
self.assertEqual(s.getvalue(),
1673
'Nothing seems to have changed\n')
1675
def test_show_branch_change_no_old(self):
1676
tree = self.setup_ab_tree()
1678
log.show_branch_change(tree.branch, s, 2, '2b')
1679
self.assertContainsRe(s.getvalue(), 'Added Revisions:')
1680
self.assertNotContainsRe(s.getvalue(), 'Removed Revisions:')
1682
def test_show_branch_change_no_new(self):
1683
tree = self.setup_ab_tree()
1684
tree.branch.set_last_revision_info(2, '2b')
1686
log.show_branch_change(tree.branch, s, 3, '3b')
1687
self.assertContainsRe(s.getvalue(), 'Removed Revisions:')
1688
self.assertNotContainsRe(s.getvalue(), 'Added Revisions:')