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: 3c still appears before 3a here because of depth-based sorting
1318
self.assertEqual([('3c', '3', 0), ('3a', '2.1.1', 1)],
1319
view_revs(rev_3a, rev_4b, 'f-id', 'forward'))
1322
class TestGetRevisionsTouchingFileID(tests.TestCaseWithTransport):
1324
def create_tree_with_single_merge(self):
1325
"""Create a branch with a moderate layout.
1327
The revision graph looks like:
1335
In this graph, A introduced files f1 and f2 and f3.
1336
B modifies f1 and f3, and C modifies f2 and f3.
1337
D merges the changes from B and C and resolves the conflict for f3.
1339
# TODO: jam 20070218 This seems like it could really be done
1340
# with make_branch_and_memory_tree() if we could just
1341
# create the content of those files.
1342
# TODO: jam 20070218 Another alternative is that we would really
1343
# like to only create this tree 1 time for all tests that
1344
# use it. Since 'log' only uses the tree in a readonly
1345
# fashion, it seems a shame to regenerate an identical
1346
# tree for each test.
1347
tree = self.make_branch_and_tree('tree')
1349
self.addCleanup(tree.unlock)
1351
self.build_tree_contents([('tree/f1', 'A\n'),
1355
tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
1356
tree.commit('A', rev_id='A')
1358
self.build_tree_contents([('tree/f2', 'A\nC\n'),
1359
('tree/f3', 'A\nC\n'),
1361
tree.commit('C', rev_id='C')
1362
# Revert back to A to build the other history.
1363
tree.set_last_revision('A')
1364
tree.branch.set_last_revision_info(1, 'A')
1365
self.build_tree_contents([('tree/f1', 'A\nB\n'),
1367
('tree/f3', 'A\nB\n'),
1369
tree.commit('B', rev_id='B')
1370
tree.set_parent_ids(['B', 'C'])
1371
self.build_tree_contents([('tree/f1', 'A\nB\n'),
1372
('tree/f2', 'A\nC\n'),
1373
('tree/f3', 'A\nB\nC\n'),
1375
tree.commit('D', rev_id='D')
1377
# Switch to a read lock for this tree.
1378
# We still have an addCleanup(tree.unlock) pending
1383
def check_delta(self, delta, **kw):
1384
"""Check the filenames touched by a delta are as expected.
1386
Caller only have to pass in the list of files for each part, all
1387
unspecified parts are considered empty (and checked as such).
1389
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
1390
# By default we expect an empty list
1391
expected = kw.get(n, [])
1392
# strip out only the path components
1393
got = [x[0] for x in getattr(delta, n)]
1394
self.assertEqual(expected, got)
1396
def test_tree_with_single_merge(self):
1397
"""Make sure the tree layout is correct."""
1398
tree = self.create_tree_with_single_merge()
1399
rev_A_tree = tree.branch.repository.revision_tree('A')
1400
rev_B_tree = tree.branch.repository.revision_tree('B')
1401
rev_C_tree = tree.branch.repository.revision_tree('C')
1402
rev_D_tree = tree.branch.repository.revision_tree('D')
1404
self.check_delta(rev_B_tree.changes_from(rev_A_tree),
1405
modified=['f1', 'f3'])
1407
self.check_delta(rev_C_tree.changes_from(rev_A_tree),
1408
modified=['f2', 'f3'])
1410
self.check_delta(rev_D_tree.changes_from(rev_B_tree),
1411
modified=['f2', 'f3'])
1413
self.check_delta(rev_D_tree.changes_from(rev_C_tree),
1414
modified=['f1', 'f3'])
1416
def assertAllRevisionsForFileID(self, tree, file_id, revisions):
1417
"""Ensure _filter_revisions_touching_file_id returns the right values.
1419
Get the return value from _filter_revisions_touching_file_id and make
1420
sure they are correct.
1422
# The api for _filter_revisions_touching_file_id is a little crazy.
1423
# So we do the setup here.
1424
mainline = tree.branch.revision_history()
1425
mainline.insert(0, None)
1426
revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
1427
view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
1429
actual_revs = log._filter_revisions_touching_file_id(
1432
list(view_revs_iter))
1433
self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
1435
def test_file_id_f1(self):
1436
tree = self.create_tree_with_single_merge()
1437
# f1 should be marked as modified by revisions A and B
1438
self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
1440
def test_file_id_f2(self):
1441
tree = self.create_tree_with_single_merge()
1442
# f2 should be marked as modified by revisions A, C, and D
1443
# because D merged the changes from C.
1444
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1446
def test_file_id_f3(self):
1447
tree = self.create_tree_with_single_merge()
1448
# f3 should be marked as modified by revisions A, B, C, and D
1449
self.assertAllRevisionsForFileID(tree, 'f3-id', ['D', 'C', 'B', 'A'])
1451
def test_file_id_with_ghosts(self):
1452
# This is testing bug #209948, where having a ghost would cause
1453
# _filter_revisions_touching_file_id() to fail.
1454
tree = self.create_tree_with_single_merge()
1455
# We need to add a revision, so switch back to a write-locked tree
1456
# (still a single addCleanup(tree.unlock) pending).
1459
first_parent = tree.last_revision()
1460
tree.set_parent_ids([first_parent, 'ghost-revision-id'])
1461
self.build_tree_contents([('tree/f1', 'A\nB\nXX\n')])
1462
tree.commit('commit with a ghost', rev_id='XX')
1463
self.assertAllRevisionsForFileID(tree, 'f1-id', ['XX', 'B', 'A'])
1464
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1467
class TestShowChangedRevisions(tests.TestCaseWithTransport):
1469
def test_show_changed_revisions_verbose(self):
1470
tree = self.make_branch_and_tree('tree_a')
1471
self.build_tree(['tree_a/foo'])
1473
tree.commit('bar', rev_id='bar-id')
1474
s = self.make_utf8_encoded_stringio()
1475
log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
1476
self.assertContainsRe(s.getvalue(), 'bar')
1477
self.assertNotContainsRe(s.getvalue(), 'foo')
1480
class TestLogFormatter(tests.TestCase):
1482
def test_short_committer(self):
1483
rev = revision.Revision('a-id')
1484
rev.committer = 'John Doe <jdoe@example.com>'
1485
lf = log.LogFormatter(None)
1486
self.assertEqual('John Doe', lf.short_committer(rev))
1487
rev.committer = 'John Smith <jsmith@example.com>'
1488
self.assertEqual('John Smith', lf.short_committer(rev))
1489
rev.committer = 'John Smith'
1490
self.assertEqual('John Smith', lf.short_committer(rev))
1491
rev.committer = 'jsmith@example.com'
1492
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1493
rev.committer = '<jsmith@example.com>'
1494
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1495
rev.committer = 'John Smith jsmith@example.com'
1496
self.assertEqual('John Smith', lf.short_committer(rev))
1498
def test_short_author(self):
1499
rev = revision.Revision('a-id')
1500
rev.committer = 'John Doe <jdoe@example.com>'
1501
lf = log.LogFormatter(None)
1502
self.assertEqual('John Doe', lf.short_author(rev))
1503
rev.properties['author'] = 'John Smith <jsmith@example.com>'
1504
self.assertEqual('John Smith', lf.short_author(rev))
1505
rev.properties['author'] = 'John Smith'
1506
self.assertEqual('John Smith', lf.short_author(rev))
1507
rev.properties['author'] = 'jsmith@example.com'
1508
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1509
rev.properties['author'] = '<jsmith@example.com>'
1510
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1511
rev.properties['author'] = 'John Smith jsmith@example.com'
1512
self.assertEqual('John Smith', lf.short_author(rev))
1515
class TestReverseByDepth(tests.TestCase):
1516
"""Test reverse_by_depth behavior.
1518
This is used to present revisions in forward (oldest first) order in a nice
1521
The tests use lighter revision description to ease reading.
1524
def assertReversed(self, forward, backward):
1525
# Transform the descriptions to suit the API: tests use (revno, depth),
1526
# while the API expects (revid, revno, depth)
1527
def complete_revisions(l):
1528
"""Transform the description to suit the API.
1530
Tests use (revno, depth) whil the API expects (revid, revno, depth).
1531
Since the revid is arbitrary, we just duplicate revno
1533
return [ (r, r, d) for r, d in l]
1534
forward = complete_revisions(forward)
1535
backward= complete_revisions(backward)
1536
self.assertEqual(forward, log.reverse_by_depth(backward))
1539
def test_mainline_revisions(self):
1540
self.assertReversed([( '1', 0), ('2', 0)],
1541
[('2', 0), ('1', 0)])
1543
def test_merged_revisions(self):
1544
self.assertReversed([('1', 0), ('2', 0), ('2.2', 1), ('2.1', 1),],
1545
[('2', 0), ('2.1', 1), ('2.2', 1), ('1', 0),])
1546
def test_shifted_merged_revisions(self):
1547
"""Test irregular layout.
1549
Requesting revisions touching a file can produce "holes" in the depths.
1551
self.assertReversed([('1', 0), ('2', 0), ('1.1', 2), ('1.2', 2),],
1552
[('2', 0), ('1.2', 2), ('1.1', 2), ('1', 0),])
1554
def test_merged_without_child_revisions(self):
1555
"""Test irregular layout.
1557
Revision ranges can produce "holes" in the depths.
1559
# When a revision of higher depth doesn't follow one of lower depth, we
1560
# assume a lower depth one is virtually there
1561
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1562
[('4', 4), ('3', 3), ('2', 2), ('1', 2),])
1563
# So we get the same order after reversing below even if the original
1564
# revisions are not in the same order.
1565
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1566
[('3', 3), ('4', 4), ('2', 2), ('1', 2),])
1569
class TestHistoryChange(tests.TestCaseWithTransport):
1571
def setup_a_tree(self):
1572
tree = self.make_branch_and_tree('tree')
1574
self.addCleanup(tree.unlock)
1575
tree.commit('1a', rev_id='1a')
1576
tree.commit('2a', rev_id='2a')
1577
tree.commit('3a', rev_id='3a')
1580
def setup_ab_tree(self):
1581
tree = self.setup_a_tree()
1582
tree.set_last_revision('1a')
1583
tree.branch.set_last_revision_info(1, '1a')
1584
tree.commit('2b', rev_id='2b')
1585
tree.commit('3b', rev_id='3b')
1588
def setup_ac_tree(self):
1589
tree = self.setup_a_tree()
1590
tree.set_last_revision(revision.NULL_REVISION)
1591
tree.branch.set_last_revision_info(0, revision.NULL_REVISION)
1592
tree.commit('1c', rev_id='1c')
1593
tree.commit('2c', rev_id='2c')
1594
tree.commit('3c', rev_id='3c')
1597
def test_all_new(self):
1598
tree = self.setup_ab_tree()
1599
old, new = log.get_history_change('1a', '3a', tree.branch.repository)
1600
self.assertEqual([], old)
1601
self.assertEqual(['2a', '3a'], new)
1603
def test_all_old(self):
1604
tree = self.setup_ab_tree()
1605
old, new = log.get_history_change('3a', '1a', tree.branch.repository)
1606
self.assertEqual([], new)
1607
self.assertEqual(['2a', '3a'], old)
1609
def test_null_old(self):
1610
tree = self.setup_ab_tree()
1611
old, new = log.get_history_change(revision.NULL_REVISION,
1612
'3a', tree.branch.repository)
1613
self.assertEqual([], old)
1614
self.assertEqual(['1a', '2a', '3a'], new)
1616
def test_null_new(self):
1617
tree = self.setup_ab_tree()
1618
old, new = log.get_history_change('3a', revision.NULL_REVISION,
1619
tree.branch.repository)
1620
self.assertEqual([], new)
1621
self.assertEqual(['1a', '2a', '3a'], old)
1623
def test_diverged(self):
1624
tree = self.setup_ab_tree()
1625
old, new = log.get_history_change('3a', '3b', tree.branch.repository)
1626
self.assertEqual(old, ['2a', '3a'])
1627
self.assertEqual(new, ['2b', '3b'])
1629
def test_unrelated(self):
1630
tree = self.setup_ac_tree()
1631
old, new = log.get_history_change('3a', '3c', tree.branch.repository)
1632
self.assertEqual(old, ['1a', '2a', '3a'])
1633
self.assertEqual(new, ['1c', '2c', '3c'])
1635
def test_show_branch_change(self):
1636
tree = self.setup_ab_tree()
1638
log.show_branch_change(tree.branch, s, 3, '3a')
1639
self.assertContainsRe(s.getvalue(),
1640
'[*]{60}\nRemoved Revisions:\n(.|\n)*2a(.|\n)*3a(.|\n)*'
1641
'[*]{60}\n\nAdded Revisions:\n(.|\n)*2b(.|\n)*3b')
1643
def test_show_branch_change_no_change(self):
1644
tree = self.setup_ab_tree()
1646
log.show_branch_change(tree.branch, s, 3, '3b')
1647
self.assertEqual(s.getvalue(),
1648
'Nothing seems to have changed\n')
1650
def test_show_branch_change_no_old(self):
1651
tree = self.setup_ab_tree()
1653
log.show_branch_change(tree.branch, s, 2, '2b')
1654
self.assertContainsRe(s.getvalue(), 'Added Revisions:')
1655
self.assertNotContainsRe(s.getvalue(), 'Removed Revisions:')
1657
def test_show_branch_change_no_new(self):
1658
tree = self.setup_ab_tree()
1659
tree.branch.set_last_revision_info(2, '2b')
1661
log.show_branch_change(tree.branch, s, 3, '3b')
1662
self.assertContainsRe(s.getvalue(), 'Removed Revisions:')
1663
self.assertNotContainsRe(s.getvalue(), 'Added Revisions:')