1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
from cStringIO import StringIO
30
class TestCaseWithoutPropsHandler(tests.TestCaseWithTransport):
33
super(TestCaseWithoutPropsHandler, self).setUp()
34
# keep a reference to the "current" custom prop. handler registry
35
self.properties_handler_registry = log.properties_handler_registry
36
# clean up the registry in log
37
log.properties_handler_registry = registry.Registry()
40
super(TestCaseWithoutPropsHandler, self)._cleanup()
41
# restore the custom properties handler registry
42
log.properties_handler_registry = self.properties_handler_registry
45
class LogCatcher(log.LogFormatter):
46
"""Pull log messages into list rather than displaying them.
48
For ease of testing we save log messages here rather than actually
49
formatting them, so that we can precisely check the result without
50
being too dependent on the exact formatting.
52
We should also test the LogFormatter.
58
super(LogCatcher, self).__init__(to_file=None)
61
def log_revision(self, revision):
62
self.logs.append(revision)
65
class TestShowLog(tests.TestCaseWithTransport):
67
def checkDelta(self, delta, **kw):
68
"""Check the filenames touched by a delta are as expected.
70
Caller only have to pass in the list of files for each part, all
71
unspecified parts are considered empty (and checked as such).
73
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
74
# By default we expect an empty list
75
expected = kw.get(n, [])
76
# strip out only the path components
77
got = [x[0] for x in getattr(delta, n)]
78
self.assertEqual(expected, got)
80
def assertInvalidRevisonNumber(self, br, start, end):
82
self.assertRaises(errors.InvalidRevisionNumber,
84
start_revision=start, end_revision=end)
86
def test_cur_revno(self):
87
wt = self.make_branch_and_tree('.')
91
wt.commit('empty commit')
92
log.show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
94
# Since there is a single revision in the branch all the combinations
96
self.assertInvalidRevisonNumber(b, 2, 1)
97
self.assertInvalidRevisonNumber(b, 1, 2)
98
self.assertInvalidRevisonNumber(b, 0, 2)
99
self.assertInvalidRevisonNumber(b, 1, 0)
100
self.assertInvalidRevisonNumber(b, -1, 1)
101
self.assertInvalidRevisonNumber(b, 1, -1)
103
def test_empty_branch(self):
104
wt = self.make_branch_and_tree('.')
107
log.show_log(wt.branch, lf)
109
self.assertEqual([], lf.logs)
111
def test_empty_commit(self):
112
wt = self.make_branch_and_tree('.')
114
wt.commit('empty commit')
116
log.show_log(wt.branch, lf, verbose=True)
117
self.assertEqual(1, len(lf.logs))
118
self.assertEqual('1', lf.logs[0].revno)
119
self.assertEqual('empty commit', lf.logs[0].rev.message)
120
self.checkDelta(lf.logs[0].delta)
122
def test_simple_commit(self):
123
wt = self.make_branch_and_tree('.')
124
wt.commit('empty commit')
125
self.build_tree(['hello'])
127
wt.commit('add one file',
128
committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
129
u'<test@example.com>')
131
log.show_log(wt.branch, lf, verbose=True)
132
self.assertEqual(2, len(lf.logs))
133
# first one is most recent
134
log_entry = lf.logs[0]
135
self.assertEqual('2', log_entry.revno)
136
self.assertEqual('add one file', log_entry.rev.message)
137
self.checkDelta(log_entry.delta, added=['hello'])
139
def test_commit_message_with_control_chars(self):
140
wt = self.make_branch_and_tree('.')
141
msg = u"All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
142
msg = msg.replace(u'\r', u'\n')
145
log.show_log(wt.branch, lf, verbose=True)
146
committed_msg = lf.logs[0].rev.message
147
self.assertNotEqual(msg, committed_msg)
148
self.assertTrue(len(committed_msg) > len(msg))
150
def test_commit_message_without_control_chars(self):
151
wt = self.make_branch_and_tree('.')
152
# escaped. As ElementTree apparently does some kind of
153
# newline conversion, neither LF (\x0A) nor CR (\x0D) are
154
# included in the test commit message, even though they are
155
# valid XML 1.0 characters.
156
msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
159
log.show_log(wt.branch, lf, verbose=True)
160
committed_msg = lf.logs[0].rev.message
161
self.assertEqual(msg, committed_msg)
163
def test_deltas_in_merge_revisions(self):
164
"""Check deltas created for both mainline and merge revisions"""
165
wt = self.make_branch_and_tree('parent')
166
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
169
wt.commit(message='add file1 and file2')
170
self.run_bzr('branch parent child')
171
os.unlink('child/file1')
172
file('child/file2', 'wb').write('hello\n')
173
self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
176
self.run_bzr('merge ../child')
177
wt.commit('merge child branch')
181
lf.supports_merge_revisions = True
182
log.show_log(b, lf, verbose=True)
184
self.assertEqual(3, len(lf.logs))
186
logentry = lf.logs[0]
187
self.assertEqual('2', logentry.revno)
188
self.assertEqual('merge child branch', logentry.rev.message)
189
self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
191
logentry = lf.logs[1]
192
self.assertEqual('1.1.1', logentry.revno)
193
self.assertEqual('remove file1 and modify file2', logentry.rev.message)
194
self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
196
logentry = lf.logs[2]
197
self.assertEqual('1', logentry.revno)
198
self.assertEqual('add file1 and file2', logentry.rev.message)
199
self.checkDelta(logentry.delta, added=['file1', 'file2'])
201
def test_merges_nonsupporting_formatter(self):
202
"""Tests that show_log will raise if the formatter doesn't
203
support merge revisions."""
204
wt = self.make_branch_and_memory_tree('.')
206
self.addCleanup(wt.unlock)
208
wt.commit('rev-1', rev_id='rev-1',
209
timestamp=1132586655, timezone=36000,
210
committer='Joe Foo <joe@foo.com>')
211
wt.commit('rev-merged', rev_id='rev-2a',
212
timestamp=1132586700, timezone=36000,
213
committer='Joe Foo <joe@foo.com>')
214
wt.set_parent_ids(['rev-1', 'rev-2a'])
215
wt.branch.set_last_revision_info(1, 'rev-1')
216
wt.commit('rev-2', rev_id='rev-2b',
217
timestamp=1132586800, timezone=36000,
218
committer='Joe Foo <joe@foo.com>')
219
logfile = self.make_utf8_encoded_stringio()
220
formatter = log.ShortLogFormatter(to_file=logfile)
223
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
224
rev = revspec.in_history(wtb)
225
self.assertRaises(errors.BzrCommandError, log.show_log, wtb, lf,
226
start_revision=rev, end_revision=rev)
229
def make_commits_with_trailing_newlines(wt):
230
"""Helper method for LogFormatter tests"""
233
open('a', 'wb').write('hello moto\n')
235
wt.commit('simple log message', rev_id='a1',
236
timestamp=1132586655.459960938, timezone=-6*3600,
237
committer='Joe Foo <joe@foo.com>')
238
open('b', 'wb').write('goodbye\n')
240
wt.commit('multiline\nlog\nmessage\n', rev_id='a2',
241
timestamp=1132586842.411175966, timezone=-6*3600,
242
committer='Joe Foo <joe@foo.com>',
243
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_error_in_properties_handler(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_author_log'
747
wt.commit(message='add a',
748
timestamp=1132711707,
750
committer='Lorem Ipsum <test@example.com>',
751
author='John Doe <jdoe@example.com>',
752
revprops={'first_prop':'first_value'})
754
formatter = log.LongLogFormatter(to_file=sio)
756
def trivial_custom_prop_handler(revision):
757
raise StandardError("a test error")
759
log.properties_handler_registry.register(
760
'trivial_custom_prop_handler',
761
trivial_custom_prop_handler)
762
self.assertRaises(StandardError, log.show_log, b, formatter,)
764
log.properties_handler_registry.remove(
765
'trivial_custom_prop_handler')
767
def test_properties_handler_bad_argument(self):
768
wt = self.make_branch_and_tree('.')
770
self.build_tree(['a'])
772
b.nick = 'test_author_log'
773
wt.commit(message='add a',
774
timestamp=1132711707,
776
committer='Lorem Ipsum <test@example.com>',
777
author='John Doe <jdoe@example.com>',
778
revprops={'a_prop':'test_value'})
780
formatter = log.LongLogFormatter(to_file=sio)
782
def bad_argument_prop_handler(revision):
783
return {'custom_prop_name':revision.properties['a_prop']}
785
log.properties_handler_registry.register(
786
'bad_argument_prop_handler',
787
bad_argument_prop_handler)
789
self.assertRaises(AttributeError, formatter.show_properties,
792
revision = b.repository.get_revision(b.last_revision())
793
formatter.show_properties(revision, '')
794
self.assertEqualDiff('''custom_prop_name: test_value\n''',
797
log.properties_handler_registry.remove(
798
'bad_argument_prop_handler')
801
class TestLongLogFormatterWithoutMergeRevisions(TestCaseWithoutPropsHandler):
803
def test_long_verbose_log(self):
804
"""Verbose log includes changed files
808
wt = self.make_branch_and_tree('.')
810
self.build_tree(['a'])
812
# XXX: why does a longer nick show up?
813
b.nick = 'test_verbose_log'
814
wt.commit(message='add a',
815
timestamp=1132711707,
817
committer='Lorem Ipsum <test@example.com>')
818
logfile = file('out.tmp', 'w+')
819
formatter = log.LongLogFormatter(to_file=logfile, levels=1)
820
log.show_log(b, formatter, verbose=True)
823
log_contents = logfile.read()
824
self.assertEqualDiff('''\
825
------------------------------------------------------------
827
committer: Lorem Ipsum <test@example.com>
828
branch nick: test_verbose_log
829
timestamp: Wed 2005-11-23 12:08:27 +1000
837
def test_long_verbose_contain_deltas(self):
838
wt = self.make_branch_and_tree('parent')
839
self.build_tree(['parent/f1', 'parent/f2'])
841
wt.commit('first post')
842
self.run_bzr('branch parent child')
843
os.unlink('child/f1')
844
file('child/f2', 'wb').write('hello\n')
845
self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
848
self.run_bzr('merge ../child')
849
wt.commit('merge branch 1')
851
sio = self.make_utf8_encoded_stringio()
852
lf = log.LongLogFormatter(to_file=sio, levels=1)
853
log.show_log(b, lf, verbose=True)
854
the_log = normalize_log(sio.getvalue())
855
self.assertEqualDiff("""\
856
------------------------------------------------------------
858
committer: Lorem Ipsum <test@example.com>
867
------------------------------------------------------------
869
committer: Lorem Ipsum <test@example.com>
880
def test_long_trailing_newlines(self):
881
wt = self.make_branch_and_tree('.')
882
b = make_commits_with_trailing_newlines(wt)
883
sio = self.make_utf8_encoded_stringio()
884
lf = log.LongLogFormatter(to_file=sio, levels=1)
886
self.assertEqualDiff("""\
887
------------------------------------------------------------
889
committer: Joe Foo <joe@foo.com>
891
timestamp: Mon 2005-11-21 09:32:56 -0600
893
single line with trailing newline
894
------------------------------------------------------------
896
author: Joe Bar <joe@bar.com>
897
committer: Joe Foo <joe@foo.com>
899
timestamp: Mon 2005-11-21 09:27:22 -0600
904
------------------------------------------------------------
906
committer: Joe Foo <joe@foo.com>
908
timestamp: Mon 2005-11-21 09:24:15 -0600
914
def test_long_author_in_log(self):
915
"""Log includes the author name if it's set in
916
the revision properties
918
wt = self.make_branch_and_tree('.')
920
self.build_tree(['a'])
922
b.nick = 'test_author_log'
923
wt.commit(message='add a',
924
timestamp=1132711707,
926
committer='Lorem Ipsum <test@example.com>',
927
author='John Doe <jdoe@example.com>')
929
formatter = log.LongLogFormatter(to_file=sio, levels=1)
930
log.show_log(b, formatter)
931
self.assertEqualDiff('''\
932
------------------------------------------------------------
934
author: John Doe <jdoe@example.com>
935
committer: Lorem Ipsum <test@example.com>
936
branch nick: test_author_log
937
timestamp: Wed 2005-11-23 12:08:27 +1000
943
def test_long_properties_in_log(self):
944
"""Log includes the custom properties returned by the registered
947
wt = self.make_branch_and_tree('.')
949
self.build_tree(['a'])
951
b.nick = 'test_properties_in_log'
952
wt.commit(message='add a',
953
timestamp=1132711707,
955
committer='Lorem Ipsum <test@example.com>',
956
author='John Doe <jdoe@example.com>')
958
formatter = log.LongLogFormatter(to_file=sio, levels=1)
960
def trivial_custom_prop_handler(revision):
961
return {'test_prop':'test_value'}
963
log.properties_handler_registry.register(
964
'trivial_custom_prop_handler',
965
trivial_custom_prop_handler)
966
log.show_log(b, formatter)
968
log.properties_handler_registry.remove(
969
'trivial_custom_prop_handler')
970
self.assertEqualDiff('''\
971
------------------------------------------------------------
973
test_prop: test_value
974
author: John Doe <jdoe@example.com>
975
committer: Lorem Ipsum <test@example.com>
976
branch nick: test_properties_in_log
977
timestamp: Wed 2005-11-23 12:08:27 +1000
984
class TestLineLogFormatter(tests.TestCaseWithTransport):
986
def test_line_log(self):
987
"""Line log should show revno
991
wt = self.make_branch_and_tree('.')
993
self.build_tree(['a'])
995
b.nick = 'test-line-log'
996
wt.commit(message='add a',
997
timestamp=1132711707,
999
committer='Line-Log-Formatter Tester <test@line.log>')
1000
logfile = file('out.tmp', 'w+')
1001
formatter = log.LineLogFormatter(to_file=logfile)
1002
log.show_log(b, formatter)
1005
log_contents = logfile.read()
1006
self.assertEqualDiff('1: Line-Log-Formatte... 2005-11-23 add a\n',
1009
def test_trailing_newlines(self):
1010
wt = self.make_branch_and_tree('.')
1011
b = make_commits_with_trailing_newlines(wt)
1012
sio = self.make_utf8_encoded_stringio()
1013
lf = log.LineLogFormatter(to_file=sio)
1015
self.assertEqualDiff("""\
1016
3: Joe Foo 2005-11-21 single line with trailing newline
1017
2: Joe Bar 2005-11-21 multiline
1018
1: Joe Foo 2005-11-21 simple log message
1022
def _prepare_tree_with_merges(self, with_tags=False):
1023
wt = self.make_branch_and_memory_tree('.')
1025
self.addCleanup(wt.unlock)
1027
wt.commit('rev-1', rev_id='rev-1',
1028
timestamp=1132586655, timezone=36000,
1029
committer='Joe Foo <joe@foo.com>')
1030
wt.commit('rev-merged', rev_id='rev-2a',
1031
timestamp=1132586700, timezone=36000,
1032
committer='Joe Foo <joe@foo.com>')
1033
wt.set_parent_ids(['rev-1', 'rev-2a'])
1034
wt.branch.set_last_revision_info(1, 'rev-1')
1035
wt.commit('rev-2', rev_id='rev-2b',
1036
timestamp=1132586800, timezone=36000,
1037
committer='Joe Foo <joe@foo.com>')
1040
branch.tags.set_tag('v0.2', 'rev-2b')
1041
wt.commit('rev-3', rev_id='rev-3',
1042
timestamp=1132586900, timezone=36000,
1043
committer='Jane Foo <jane@foo.com>')
1044
branch.tags.set_tag('v1.0rc1', 'rev-3')
1045
branch.tags.set_tag('v1.0', 'rev-3')
1048
def test_line_log_single_merge_revision(self):
1049
wt = self._prepare_tree_with_merges()
1050
logfile = self.make_utf8_encoded_stringio()
1051
formatter = log.LineLogFormatter(to_file=logfile)
1052
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
1054
rev = revspec.in_history(wtb)
1055
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
1056
self.assertEqualDiff("""\
1057
1.1.1: Joe Foo 2005-11-22 rev-merged
1061
def test_line_log_with_tags(self):
1062
wt = self._prepare_tree_with_merges(with_tags=True)
1063
logfile = self.make_utf8_encoded_stringio()
1064
formatter = log.LineLogFormatter(to_file=logfile)
1065
log.show_log(wt.branch, formatter)
1066
self.assertEqualDiff("""\
1067
3: Jane Foo 2005-11-22 {v1.0, v1.0rc1} rev-3
1068
2: Joe Foo 2005-11-22 {v0.2} rev-2
1069
1: Joe Foo 2005-11-22 rev-1
1073
class TestLineLogFormatterWithMergeRevisions(tests.TestCaseWithTransport):
1075
def test_line_merge_revs_log(self):
1076
"""Line log should show revno
1080
wt = self.make_branch_and_tree('.')
1082
self.build_tree(['a'])
1084
b.nick = 'test-line-log'
1085
wt.commit(message='add a',
1086
timestamp=1132711707,
1088
committer='Line-Log-Formatter Tester <test@line.log>')
1089
logfile = file('out.tmp', 'w+')
1090
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1091
log.show_log(b, formatter)
1094
log_contents = logfile.read()
1095
self.assertEqualDiff('1: Line-Log-Formatte... 2005-11-23 add a\n',
1098
def test_line_merge_revs_log_single_merge_revision(self):
1099
wt = self.make_branch_and_memory_tree('.')
1101
self.addCleanup(wt.unlock)
1103
wt.commit('rev-1', rev_id='rev-1',
1104
timestamp=1132586655, timezone=36000,
1105
committer='Joe Foo <joe@foo.com>')
1106
wt.commit('rev-merged', rev_id='rev-2a',
1107
timestamp=1132586700, timezone=36000,
1108
committer='Joe Foo <joe@foo.com>')
1109
wt.set_parent_ids(['rev-1', 'rev-2a'])
1110
wt.branch.set_last_revision_info(1, 'rev-1')
1111
wt.commit('rev-2', rev_id='rev-2b',
1112
timestamp=1132586800, timezone=36000,
1113
committer='Joe Foo <joe@foo.com>')
1114
logfile = self.make_utf8_encoded_stringio()
1115
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1116
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
1118
rev = revspec.in_history(wtb)
1119
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
1120
self.assertEqualDiff("""\
1121
1.1.1: Joe Foo 2005-11-22 rev-merged
1125
def test_line_merge_revs_log_with_merges(self):
1126
wt = self.make_branch_and_memory_tree('.')
1128
self.addCleanup(wt.unlock)
1130
wt.commit('rev-1', rev_id='rev-1',
1131
timestamp=1132586655, timezone=36000,
1132
committer='Joe Foo <joe@foo.com>')
1133
wt.commit('rev-merged', rev_id='rev-2a',
1134
timestamp=1132586700, timezone=36000,
1135
committer='Joe Foo <joe@foo.com>')
1136
wt.set_parent_ids(['rev-1', 'rev-2a'])
1137
wt.branch.set_last_revision_info(1, 'rev-1')
1138
wt.commit('rev-2', rev_id='rev-2b',
1139
timestamp=1132586800, timezone=36000,
1140
committer='Joe Foo <joe@foo.com>')
1141
logfile = self.make_utf8_encoded_stringio()
1142
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1143
log.show_log(wt.branch, formatter)
1144
self.assertEqualDiff("""\
1145
2: Joe Foo 2005-11-22 rev-2
1146
1.1.1: Joe Foo 2005-11-22 rev-merged
1147
1: Joe Foo 2005-11-22 rev-1
1151
class TestGetViewRevisions(tests.TestCaseWithTransport):
1153
def make_tree_with_commits(self):
1154
"""Create a tree with well-known revision ids"""
1155
wt = self.make_branch_and_tree('tree1')
1156
wt.commit('commit one', rev_id='1')
1157
wt.commit('commit two', rev_id='2')
1158
wt.commit('commit three', rev_id='3')
1159
mainline_revs = [None, '1', '2', '3']
1160
rev_nos = {'1': 1, '2': 2, '3': 3}
1161
return mainline_revs, rev_nos, wt
1163
def make_tree_with_merges(self):
1164
"""Create a tree with well-known revision ids and a merge"""
1165
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1166
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
1167
tree2.commit('four-a', rev_id='4a')
1168
wt.merge_from_branch(tree2.branch)
1169
wt.commit('four-b', rev_id='4b')
1170
mainline_revs.append('4b')
1173
return mainline_revs, rev_nos, wt
1175
def make_tree_with_many_merges(self):
1176
"""Create a tree with well-known revision ids"""
1177
wt = self.make_branch_and_tree('tree1')
1178
self.build_tree_contents([('tree1/f', '1\n')])
1179
wt.add(['f'], ['f-id'])
1180
wt.commit('commit one', rev_id='1')
1181
wt.commit('commit two', rev_id='2')
1183
tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
1184
self.build_tree_contents([('tree3/f', '1\n2\n3a\n')])
1185
tree3.commit('commit three a', rev_id='3a')
1187
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
1188
tree2.merge_from_branch(tree3.branch)
1189
tree2.commit('commit three b', rev_id='3b')
1191
wt.merge_from_branch(tree2.branch)
1192
wt.commit('commit three c', rev_id='3c')
1193
tree2.commit('four-a', rev_id='4a')
1195
wt.merge_from_branch(tree2.branch)
1196
wt.commit('four-b', rev_id='4b')
1198
mainline_revs = [None, '1', '2', '3c', '4b']
1199
rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
1200
full_rev_nos_for_reference = {
1203
'3a': '2.1.1', #first commit tree 3
1204
'3b': '2.2.1', # first commit tree 2
1205
'3c': '3', #merges 3b to main
1206
'4a': '2.2.2', # second commit tree 2
1207
'4b': '4', # merges 4a to main
1209
return mainline_revs, rev_nos, wt
1211
def test_get_view_revisions_forward(self):
1212
"""Test the get_view_revisions method"""
1213
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1215
self.addCleanup(wt.unlock)
1216
revisions = list(log.get_view_revisions(
1217
mainline_revs, rev_nos, wt.branch, 'forward'))
1218
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
1220
revisions2 = list(log.get_view_revisions(
1221
mainline_revs, rev_nos, wt.branch, 'forward',
1222
include_merges=False))
1223
self.assertEqual(revisions, revisions2)
1225
def test_get_view_revisions_reverse(self):
1226
"""Test the get_view_revisions with reverse"""
1227
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1229
self.addCleanup(wt.unlock)
1230
revisions = list(log.get_view_revisions(
1231
mainline_revs, rev_nos, wt.branch, 'reverse'))
1232
self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
1234
revisions2 = list(log.get_view_revisions(
1235
mainline_revs, rev_nos, wt.branch, 'reverse',
1236
include_merges=False))
1237
self.assertEqual(revisions, revisions2)
1239
def test_get_view_revisions_merge(self):
1240
"""Test get_view_revisions when there are merges"""
1241
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
1243
self.addCleanup(wt.unlock)
1244
revisions = list(log.get_view_revisions(
1245
mainline_revs, rev_nos, wt.branch, 'forward'))
1246
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
1247
('4b', '4', 0), ('4a', '3.1.1', 1)],
1249
revisions = list(log.get_view_revisions(
1250
mainline_revs, rev_nos, wt.branch, 'forward',
1251
include_merges=False))
1252
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
1256
def test_get_view_revisions_merge_reverse(self):
1257
"""Test get_view_revisions in reverse when there are merges"""
1258
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
1260
self.addCleanup(wt.unlock)
1261
revisions = list(log.get_view_revisions(
1262
mainline_revs, rev_nos, wt.branch, 'reverse'))
1263
self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
1264
('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
1266
revisions = list(log.get_view_revisions(
1267
mainline_revs, rev_nos, wt.branch, 'reverse',
1268
include_merges=False))
1269
self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
1273
def test_get_view_revisions_merge2(self):
1274
"""Test get_view_revisions when there are merges"""
1275
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
1277
self.addCleanup(wt.unlock)
1278
revisions = list(log.get_view_revisions(
1279
mainline_revs, rev_nos, wt.branch, 'forward'))
1280
expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
1281
('3a', '2.1.1', 1), ('3b', '2.2.1', 1), ('4b', '4', 0),
1283
self.assertEqual(expected, revisions)
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), ('3c', '3', 0),
1292
def test_file_id_for_range(self):
1293
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
1295
self.addCleanup(wt.unlock)
1297
def rev_from_rev_id(revid, branch):
1298
revspec = revisionspec.RevisionSpec.from_string('revid:%s' % revid)
1299
return revspec.in_history(branch)
1301
def view_revs(start_rev, end_rev, file_id, direction):
1302
revs = log.calculate_view_revisions(
1304
start_rev, # start_revision
1305
end_rev, # end_revision
1306
direction, # direction
1307
file_id, # specific_fileid
1308
True, # generate_merge_revisions
1309
True, # allow_single_merge_revision
1313
rev_3a = rev_from_rev_id('3a', wt.branch)
1314
rev_4b = rev_from_rev_id('4b', wt.branch)
1315
self.assertEqual([('3c', '3', 0), ('3a', '2.1.1', 1)],
1316
view_revs(rev_3a, rev_4b, 'f-id', 'reverse'))
1317
# Note that the depth is 0 for 3a because depths are normalized, but
1318
# there is still a bug somewhere... most probably in
1319
# _filter_revision_range and/or get_view_revisions still around a bad
1320
# use of reverse_by_depth
1321
self.assertEqual([('3a', '2.1.1', 0)],
1322
view_revs(rev_3a, rev_4b, 'f-id', 'forward'))
1325
class TestGetRevisionsTouchingFileID(tests.TestCaseWithTransport):
1327
def create_tree_with_single_merge(self):
1328
"""Create a branch with a moderate layout.
1330
The revision graph looks like:
1338
In this graph, A introduced files f1 and f2 and f3.
1339
B modifies f1 and f3, and C modifies f2 and f3.
1340
D merges the changes from B and C and resolves the conflict for f3.
1342
# TODO: jam 20070218 This seems like it could really be done
1343
# with make_branch_and_memory_tree() if we could just
1344
# create the content of those files.
1345
# TODO: jam 20070218 Another alternative is that we would really
1346
# like to only create this tree 1 time for all tests that
1347
# use it. Since 'log' only uses the tree in a readonly
1348
# fashion, it seems a shame to regenerate an identical
1349
# tree for each test.
1350
tree = self.make_branch_and_tree('tree')
1352
self.addCleanup(tree.unlock)
1354
self.build_tree_contents([('tree/f1', 'A\n'),
1358
tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
1359
tree.commit('A', rev_id='A')
1361
self.build_tree_contents([('tree/f2', 'A\nC\n'),
1362
('tree/f3', 'A\nC\n'),
1364
tree.commit('C', rev_id='C')
1365
# Revert back to A to build the other history.
1366
tree.set_last_revision('A')
1367
tree.branch.set_last_revision_info(1, 'A')
1368
self.build_tree_contents([('tree/f1', 'A\nB\n'),
1370
('tree/f3', 'A\nB\n'),
1372
tree.commit('B', rev_id='B')
1373
tree.set_parent_ids(['B', 'C'])
1374
self.build_tree_contents([('tree/f1', 'A\nB\n'),
1375
('tree/f2', 'A\nC\n'),
1376
('tree/f3', 'A\nB\nC\n'),
1378
tree.commit('D', rev_id='D')
1380
# Switch to a read lock for this tree.
1381
# We still have an addCleanup(tree.unlock) pending
1386
def check_delta(self, delta, **kw):
1387
"""Check the filenames touched by a delta are as expected.
1389
Caller only have to pass in the list of files for each part, all
1390
unspecified parts are considered empty (and checked as such).
1392
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
1393
# By default we expect an empty list
1394
expected = kw.get(n, [])
1395
# strip out only the path components
1396
got = [x[0] for x in getattr(delta, n)]
1397
self.assertEqual(expected, got)
1399
def test_tree_with_single_merge(self):
1400
"""Make sure the tree layout is correct."""
1401
tree = self.create_tree_with_single_merge()
1402
rev_A_tree = tree.branch.repository.revision_tree('A')
1403
rev_B_tree = tree.branch.repository.revision_tree('B')
1404
rev_C_tree = tree.branch.repository.revision_tree('C')
1405
rev_D_tree = tree.branch.repository.revision_tree('D')
1407
self.check_delta(rev_B_tree.changes_from(rev_A_tree),
1408
modified=['f1', 'f3'])
1410
self.check_delta(rev_C_tree.changes_from(rev_A_tree),
1411
modified=['f2', 'f3'])
1413
self.check_delta(rev_D_tree.changes_from(rev_B_tree),
1414
modified=['f2', 'f3'])
1416
self.check_delta(rev_D_tree.changes_from(rev_C_tree),
1417
modified=['f1', 'f3'])
1419
def assertAllRevisionsForFileID(self, tree, file_id, revisions):
1420
"""Ensure _filter_revisions_touching_file_id returns the right values.
1422
Get the return value from _filter_revisions_touching_file_id and make
1423
sure they are correct.
1425
# The api for _filter_revisions_touching_file_id is a little crazy.
1426
# So we do the setup here.
1427
mainline = tree.branch.revision_history()
1428
mainline.insert(0, None)
1429
revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
1430
view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
1432
actual_revs = log._filter_revisions_touching_file_id(
1435
list(view_revs_iter))
1436
self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
1438
def test_file_id_f1(self):
1439
tree = self.create_tree_with_single_merge()
1440
# f1 should be marked as modified by revisions A and B
1441
self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
1443
def test_file_id_f2(self):
1444
tree = self.create_tree_with_single_merge()
1445
# f2 should be marked as modified by revisions A, C, and D
1446
# because D merged the changes from C.
1447
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1449
def test_file_id_f3(self):
1450
tree = self.create_tree_with_single_merge()
1451
# f3 should be marked as modified by revisions A, B, C, and D
1452
self.assertAllRevisionsForFileID(tree, 'f3-id', ['D', 'C', 'B', 'A'])
1454
def test_file_id_with_ghosts(self):
1455
# This is testing bug #209948, where having a ghost would cause
1456
# _filter_revisions_touching_file_id() to fail.
1457
tree = self.create_tree_with_single_merge()
1458
# We need to add a revision, so switch back to a write-locked tree
1459
# (still a single addCleanup(tree.unlock) pending).
1462
first_parent = tree.last_revision()
1463
tree.set_parent_ids([first_parent, 'ghost-revision-id'])
1464
self.build_tree_contents([('tree/f1', 'A\nB\nXX\n')])
1465
tree.commit('commit with a ghost', rev_id='XX')
1466
self.assertAllRevisionsForFileID(tree, 'f1-id', ['XX', 'B', 'A'])
1467
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1470
class TestShowChangedRevisions(tests.TestCaseWithTransport):
1472
def test_show_changed_revisions_verbose(self):
1473
tree = self.make_branch_and_tree('tree_a')
1474
self.build_tree(['tree_a/foo'])
1476
tree.commit('bar', rev_id='bar-id')
1477
s = self.make_utf8_encoded_stringio()
1478
log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
1479
self.assertContainsRe(s.getvalue(), 'bar')
1480
self.assertNotContainsRe(s.getvalue(), 'foo')
1483
class TestLogFormatter(tests.TestCase):
1485
def test_short_committer(self):
1486
rev = revision.Revision('a-id')
1487
rev.committer = 'John Doe <jdoe@example.com>'
1488
lf = log.LogFormatter(None)
1489
self.assertEqual('John Doe', lf.short_committer(rev))
1490
rev.committer = 'John Smith <jsmith@example.com>'
1491
self.assertEqual('John Smith', lf.short_committer(rev))
1492
rev.committer = 'John Smith'
1493
self.assertEqual('John Smith', lf.short_committer(rev))
1494
rev.committer = 'jsmith@example.com'
1495
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1496
rev.committer = '<jsmith@example.com>'
1497
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1498
rev.committer = 'John Smith jsmith@example.com'
1499
self.assertEqual('John Smith', lf.short_committer(rev))
1501
def test_short_author(self):
1502
rev = revision.Revision('a-id')
1503
rev.committer = 'John Doe <jdoe@example.com>'
1504
lf = log.LogFormatter(None)
1505
self.assertEqual('John Doe', lf.short_author(rev))
1506
rev.properties['author'] = 'John Smith <jsmith@example.com>'
1507
self.assertEqual('John Smith', lf.short_author(rev))
1508
rev.properties['author'] = 'John Smith'
1509
self.assertEqual('John Smith', lf.short_author(rev))
1510
rev.properties['author'] = 'jsmith@example.com'
1511
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1512
rev.properties['author'] = '<jsmith@example.com>'
1513
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1514
rev.properties['author'] = 'John Smith jsmith@example.com'
1515
self.assertEqual('John Smith', lf.short_author(rev))
1518
class TestReverseByDepth(tests.TestCase):
1519
"""Test reverse_by_depth behavior.
1521
This is used to present revisions in forward (oldest first) order in a nice
1524
The tests use lighter revision description to ease reading.
1527
def assertReversed(self, forward, backward):
1528
# Transform the descriptions to suit the API: tests use (revno, depth),
1529
# while the API expects (revid, revno, depth)
1530
def complete_revisions(l):
1531
"""Transform the description to suit the API.
1533
Tests use (revno, depth) whil the API expects (revid, revno, depth).
1534
Since the revid is arbitrary, we just duplicate revno
1536
return [ (r, r, d) for r, d in l]
1537
forward = complete_revisions(forward)
1538
backward= complete_revisions(backward)
1539
self.assertEqual(forward, log.reverse_by_depth(backward))
1542
def test_mainline_revisions(self):
1543
self.assertReversed([( '1', 0), ('2', 0)],
1544
[('2', 0), ('1', 0)])
1546
def test_merged_revisions(self):
1547
self.assertReversed([('1', 0), ('2', 0), ('2.2', 1), ('2.1', 1),],
1548
[('2', 0), ('2.1', 1), ('2.2', 1), ('1', 0),])
1549
def test_shifted_merged_revisions(self):
1550
"""Test irregular layout.
1552
Requesting revisions touching a file can produce "holes" in the depths.
1554
self.assertReversed([('1', 0), ('2', 0), ('1.1', 2), ('1.2', 2),],
1555
[('2', 0), ('1.2', 2), ('1.1', 2), ('1', 0),])
1557
def test_merged_without_child_revisions(self):
1558
"""Test irregular layout.
1560
Revision ranges can produce "holes" in the depths.
1562
# When a revision of higher depth doesn't follow one of lower depth, we
1563
# assume a lower depth one is virtually there
1564
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1565
[('4', 4), ('3', 3), ('2', 2), ('1', 2),])
1566
# So we get the same order after reversing below even if the original
1567
# revisions are not in the same order.
1568
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1569
[('3', 3), ('4', 4), ('2', 2), ('1', 2),])
1572
class TestHistoryChange(tests.TestCaseWithTransport):
1574
def setup_a_tree(self):
1575
tree = self.make_branch_and_tree('tree')
1577
self.addCleanup(tree.unlock)
1578
tree.commit('1a', rev_id='1a')
1579
tree.commit('2a', rev_id='2a')
1580
tree.commit('3a', rev_id='3a')
1583
def setup_ab_tree(self):
1584
tree = self.setup_a_tree()
1585
tree.set_last_revision('1a')
1586
tree.branch.set_last_revision_info(1, '1a')
1587
tree.commit('2b', rev_id='2b')
1588
tree.commit('3b', rev_id='3b')
1591
def setup_ac_tree(self):
1592
tree = self.setup_a_tree()
1593
tree.set_last_revision(revision.NULL_REVISION)
1594
tree.branch.set_last_revision_info(0, revision.NULL_REVISION)
1595
tree.commit('1c', rev_id='1c')
1596
tree.commit('2c', rev_id='2c')
1597
tree.commit('3c', rev_id='3c')
1600
def test_all_new(self):
1601
tree = self.setup_ab_tree()
1602
old, new = log.get_history_change('1a', '3a', tree.branch.repository)
1603
self.assertEqual([], old)
1604
self.assertEqual(['2a', '3a'], new)
1606
def test_all_old(self):
1607
tree = self.setup_ab_tree()
1608
old, new = log.get_history_change('3a', '1a', tree.branch.repository)
1609
self.assertEqual([], new)
1610
self.assertEqual(['2a', '3a'], old)
1612
def test_null_old(self):
1613
tree = self.setup_ab_tree()
1614
old, new = log.get_history_change(revision.NULL_REVISION,
1615
'3a', tree.branch.repository)
1616
self.assertEqual([], old)
1617
self.assertEqual(['1a', '2a', '3a'], new)
1619
def test_null_new(self):
1620
tree = self.setup_ab_tree()
1621
old, new = log.get_history_change('3a', revision.NULL_REVISION,
1622
tree.branch.repository)
1623
self.assertEqual([], new)
1624
self.assertEqual(['1a', '2a', '3a'], old)
1626
def test_diverged(self):
1627
tree = self.setup_ab_tree()
1628
old, new = log.get_history_change('3a', '3b', tree.branch.repository)
1629
self.assertEqual(old, ['2a', '3a'])
1630
self.assertEqual(new, ['2b', '3b'])
1632
def test_unrelated(self):
1633
tree = self.setup_ac_tree()
1634
old, new = log.get_history_change('3a', '3c', tree.branch.repository)
1635
self.assertEqual(old, ['1a', '2a', '3a'])
1636
self.assertEqual(new, ['1c', '2c', '3c'])
1638
def test_show_branch_change(self):
1639
tree = self.setup_ab_tree()
1641
log.show_branch_change(tree.branch, s, 3, '3a')
1642
self.assertContainsRe(s.getvalue(),
1643
'[*]{60}\nRemoved Revisions:\n(.|\n)*2a(.|\n)*3a(.|\n)*'
1644
'[*]{60}\n\nAdded Revisions:\n(.|\n)*2b(.|\n)*3b')
1646
def test_show_branch_change_no_change(self):
1647
tree = self.setup_ab_tree()
1649
log.show_branch_change(tree.branch, s, 3, '3b')
1650
self.assertEqual(s.getvalue(),
1651
'Nothing seems to have changed\n')
1653
def test_show_branch_change_no_old(self):
1654
tree = self.setup_ab_tree()
1656
log.show_branch_change(tree.branch, s, 2, '2b')
1657
self.assertContainsRe(s.getvalue(), 'Added Revisions:')
1658
self.assertNotContainsRe(s.getvalue(), 'Removed Revisions:')
1660
def test_show_branch_change_no_new(self):
1661
tree = self.setup_ab_tree()
1662
tree.branch.set_last_revision_info(2, '2b')
1664
log.show_branch_change(tree.branch, s, 3, '3b')
1665
self.assertContainsRe(s.getvalue(), 'Removed Revisions:')
1666
self.assertNotContainsRe(s.getvalue(), 'Added Revisions:')