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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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'])
202
def make_commits_with_trailing_newlines(wt):
203
"""Helper method for LogFormatter tests"""
206
open('a', 'wb').write('hello moto\n')
208
wt.commit('simple log message', rev_id='a1',
209
timestamp=1132586655.459960938, timezone=-6*3600,
210
committer='Joe Foo <joe@foo.com>')
211
open('b', 'wb').write('goodbye\n')
213
wt.commit('multiline\nlog\nmessage\n', rev_id='a2',
214
timestamp=1132586842.411175966, timezone=-6*3600,
215
committer='Joe Foo <joe@foo.com>',
216
authors=['Joe Bar <joe@bar.com>'])
218
open('c', 'wb').write('just another manic monday\n')
220
wt.commit('single line with trailing newline\n', rev_id='a3',
221
timestamp=1132587176.835228920, timezone=-6*3600,
222
committer = 'Joe Foo <joe@foo.com>')
226
def normalize_log(log):
227
"""Replaces the variable lines of logs with fixed lines"""
228
author = 'author: Dolor Sit <test@example.com>'
229
committer = 'committer: Lorem Ipsum <test@example.com>'
230
lines = log.splitlines(True)
231
for idx,line in enumerate(lines):
232
stripped_line = line.lstrip()
233
indent = ' ' * (len(line) - len(stripped_line))
234
if stripped_line.startswith('author:'):
235
lines[idx] = indent + author + '\n'
236
elif stripped_line.startswith('committer:'):
237
lines[idx] = indent + committer + '\n'
238
elif stripped_line.startswith('timestamp:'):
239
lines[idx] = indent + 'timestamp: Just now\n'
240
return ''.join(lines)
243
class TestShortLogFormatter(tests.TestCaseWithTransport):
245
def test_trailing_newlines(self):
246
wt = self.make_branch_and_tree('.')
247
b = make_commits_with_trailing_newlines(wt)
248
sio = self.make_utf8_encoded_stringio()
249
lf = log.ShortLogFormatter(to_file=sio)
251
self.assertEqualDiff("""\
252
3 Joe Foo\t2005-11-21
253
single line with trailing newline
255
2 Joe Bar\t2005-11-21
260
1 Joe Foo\t2005-11-21
266
def _prepare_tree_with_merges(self, with_tags=False):
267
wt = self.make_branch_and_memory_tree('.')
269
self.addCleanup(wt.unlock)
271
wt.commit('rev-1', rev_id='rev-1',
272
timestamp=1132586655, timezone=36000,
273
committer='Joe Foo <joe@foo.com>')
274
wt.commit('rev-merged', rev_id='rev-2a',
275
timestamp=1132586700, timezone=36000,
276
committer='Joe Foo <joe@foo.com>')
277
wt.set_parent_ids(['rev-1', 'rev-2a'])
278
wt.branch.set_last_revision_info(1, 'rev-1')
279
wt.commit('rev-2', rev_id='rev-2b',
280
timestamp=1132586800, timezone=36000,
281
committer='Joe Foo <joe@foo.com>')
284
branch.tags.set_tag('v0.2', 'rev-2b')
285
wt.commit('rev-3', rev_id='rev-3',
286
timestamp=1132586900, timezone=36000,
287
committer='Jane Foo <jane@foo.com>')
288
branch.tags.set_tag('v1.0rc1', 'rev-3')
289
branch.tags.set_tag('v1.0', 'rev-3')
292
def test_short_log_with_merges(self):
293
wt = self._prepare_tree_with_merges()
294
logfile = self.make_utf8_encoded_stringio()
295
formatter = log.ShortLogFormatter(to_file=logfile)
296
log.show_log(wt.branch, formatter)
297
self.assertEqualDiff("""\
298
2 Joe Foo\t2005-11-22 [merge]
301
1 Joe Foo\t2005-11-22
307
def test_short_log_with_merges_and_range(self):
308
wt = self.make_branch_and_memory_tree('.')
310
self.addCleanup(wt.unlock)
312
wt.commit('rev-1', rev_id='rev-1',
313
timestamp=1132586655, timezone=36000,
314
committer='Joe Foo <joe@foo.com>')
315
wt.commit('rev-merged', rev_id='rev-2a',
316
timestamp=1132586700, timezone=36000,
317
committer='Joe Foo <joe@foo.com>')
318
wt.branch.set_last_revision_info(1, 'rev-1')
319
wt.set_parent_ids(['rev-1', 'rev-2a'])
320
wt.commit('rev-2b', rev_id='rev-2b',
321
timestamp=1132586800, timezone=36000,
322
committer='Joe Foo <joe@foo.com>')
323
wt.commit('rev-3a', rev_id='rev-3a',
324
timestamp=1132586800, timezone=36000,
325
committer='Joe Foo <joe@foo.com>')
326
wt.branch.set_last_revision_info(2, 'rev-2b')
327
wt.set_parent_ids(['rev-2b', 'rev-3a'])
328
wt.commit('rev-3b', rev_id='rev-3b',
329
timestamp=1132586800, timezone=36000,
330
committer='Joe Foo <joe@foo.com>')
331
logfile = self.make_utf8_encoded_stringio()
332
formatter = log.ShortLogFormatter(to_file=logfile)
333
log.show_log(wt.branch, formatter,
334
start_revision=2, end_revision=3)
335
self.assertEqualDiff("""\
336
3 Joe Foo\t2005-11-22 [merge]
339
2 Joe Foo\t2005-11-22 [merge]
345
def test_short_log_with_tags(self):
346
wt = self._prepare_tree_with_merges(with_tags=True)
347
logfile = self.make_utf8_encoded_stringio()
348
formatter = log.ShortLogFormatter(to_file=logfile)
349
log.show_log(wt.branch, formatter)
350
self.assertEqualDiff("""\
351
3 Jane Foo\t2005-11-22 {v1.0, v1.0rc1}
354
2 Joe Foo\t2005-11-22 {v0.2} [merge]
357
1 Joe Foo\t2005-11-22
363
def test_short_log_single_merge_revision(self):
364
wt = self.make_branch_and_memory_tree('.')
366
self.addCleanup(wt.unlock)
368
wt.commit('rev-1', rev_id='rev-1',
369
timestamp=1132586655, timezone=36000,
370
committer='Joe Foo <joe@foo.com>')
371
wt.commit('rev-merged', rev_id='rev-2a',
372
timestamp=1132586700, timezone=36000,
373
committer='Joe Foo <joe@foo.com>')
374
wt.set_parent_ids(['rev-1', 'rev-2a'])
375
wt.branch.set_last_revision_info(1, 'rev-1')
376
wt.commit('rev-2', rev_id='rev-2b',
377
timestamp=1132586800, timezone=36000,
378
committer='Joe Foo <joe@foo.com>')
379
logfile = self.make_utf8_encoded_stringio()
380
formatter = log.ShortLogFormatter(to_file=logfile)
381
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
383
rev = revspec.in_history(wtb)
384
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
385
self.assertEqualDiff("""\
386
1.1.1 Joe Foo\t2005-11-22
393
class TestShortLogFormatterWithMergeRevisions(tests.TestCaseWithTransport):
395
def test_short_merge_revs_log_with_merges(self):
396
wt = self.make_branch_and_memory_tree('.')
398
self.addCleanup(wt.unlock)
400
wt.commit('rev-1', rev_id='rev-1',
401
timestamp=1132586655, timezone=36000,
402
committer='Joe Foo <joe@foo.com>')
403
wt.commit('rev-merged', rev_id='rev-2a',
404
timestamp=1132586700, timezone=36000,
405
committer='Joe Foo <joe@foo.com>')
406
wt.set_parent_ids(['rev-1', 'rev-2a'])
407
wt.branch.set_last_revision_info(1, 'rev-1')
408
wt.commit('rev-2', rev_id='rev-2b',
409
timestamp=1132586800, timezone=36000,
410
committer='Joe Foo <joe@foo.com>')
411
logfile = self.make_utf8_encoded_stringio()
412
formatter = log.ShortLogFormatter(to_file=logfile, levels=0)
413
log.show_log(wt.branch, formatter)
414
# Note that the 1.1.1 indenting is in fact correct given that
415
# the revision numbers are right justified within 5 characters
416
# for mainline revnos and 9 characters for dotted revnos.
417
self.assertEqualDiff("""\
418
2 Joe Foo\t2005-11-22 [merge]
421
1.1.1 Joe Foo\t2005-11-22
424
1 Joe Foo\t2005-11-22
430
def test_short_merge_revs_log_single_merge_revision(self):
431
wt = self.make_branch_and_memory_tree('.')
433
self.addCleanup(wt.unlock)
435
wt.commit('rev-1', rev_id='rev-1',
436
timestamp=1132586655, timezone=36000,
437
committer='Joe Foo <joe@foo.com>')
438
wt.commit('rev-merged', rev_id='rev-2a',
439
timestamp=1132586700, timezone=36000,
440
committer='Joe Foo <joe@foo.com>')
441
wt.set_parent_ids(['rev-1', 'rev-2a'])
442
wt.branch.set_last_revision_info(1, 'rev-1')
443
wt.commit('rev-2', rev_id='rev-2b',
444
timestamp=1132586800, timezone=36000,
445
committer='Joe Foo <joe@foo.com>')
446
logfile = self.make_utf8_encoded_stringio()
447
formatter = log.ShortLogFormatter(to_file=logfile, levels=0)
448
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
450
rev = revspec.in_history(wtb)
451
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
452
self.assertEqualDiff("""\
453
1.1.1 Joe Foo\t2005-11-22
460
class TestLongLogFormatter(TestCaseWithoutPropsHandler):
462
def test_verbose_log(self):
463
"""Verbose log includes changed files
467
wt = self.make_branch_and_tree('.')
469
self.build_tree(['a'])
471
# XXX: why does a longer nick show up?
472
b.nick = 'test_verbose_log'
473
wt.commit(message='add a',
474
timestamp=1132711707,
476
committer='Lorem Ipsum <test@example.com>')
477
logfile = file('out.tmp', 'w+')
478
formatter = log.LongLogFormatter(to_file=logfile)
479
log.show_log(b, formatter, verbose=True)
482
log_contents = logfile.read()
483
self.assertEqualDiff('''\
484
------------------------------------------------------------
486
committer: Lorem Ipsum <test@example.com>
487
branch nick: test_verbose_log
488
timestamp: Wed 2005-11-23 12:08:27 +1000
496
def test_merges_are_indented_by_level(self):
497
wt = self.make_branch_and_tree('parent')
498
wt.commit('first post')
499
self.run_bzr('branch parent child')
500
self.run_bzr(['commit', '-m', 'branch 1', '--unchanged', 'child'])
501
self.run_bzr('branch child smallerchild')
502
self.run_bzr(['commit', '-m', 'branch 2', '--unchanged',
505
self.run_bzr('merge ../smallerchild')
506
self.run_bzr(['commit', '-m', 'merge branch 2'])
507
os.chdir('../parent')
508
self.run_bzr('merge ../child')
509
wt.commit('merge branch 1')
511
sio = self.make_utf8_encoded_stringio()
512
lf = log.LongLogFormatter(to_file=sio, levels=0)
513
log.show_log(b, lf, verbose=True)
514
the_log = normalize_log(sio.getvalue())
515
self.assertEqualDiff("""\
516
------------------------------------------------------------
518
committer: Lorem Ipsum <test@example.com>
523
------------------------------------------------------------
525
committer: Lorem Ipsum <test@example.com>
530
------------------------------------------------------------
532
committer: Lorem Ipsum <test@example.com>
533
branch nick: smallerchild
537
------------------------------------------------------------
539
committer: Lorem Ipsum <test@example.com>
544
------------------------------------------------------------
546
committer: Lorem Ipsum <test@example.com>
554
def test_verbose_merge_revisions_contain_deltas(self):
555
wt = self.make_branch_and_tree('parent')
556
self.build_tree(['parent/f1', 'parent/f2'])
558
wt.commit('first post')
559
self.run_bzr('branch parent child')
560
os.unlink('child/f1')
561
file('child/f2', 'wb').write('hello\n')
562
self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
565
self.run_bzr('merge ../child')
566
wt.commit('merge branch 1')
568
sio = self.make_utf8_encoded_stringio()
569
lf = log.LongLogFormatter(to_file=sio, levels=0)
570
log.show_log(b, lf, verbose=True)
571
the_log = normalize_log(sio.getvalue())
572
self.assertEqualDiff("""\
573
------------------------------------------------------------
575
committer: Lorem Ipsum <test@example.com>
584
------------------------------------------------------------
586
committer: Lorem Ipsum <test@example.com>
590
removed f1 and modified f2
595
------------------------------------------------------------
597
committer: Lorem Ipsum <test@example.com>
608
def test_trailing_newlines(self):
609
wt = self.make_branch_and_tree('.')
610
b = make_commits_with_trailing_newlines(wt)
611
sio = self.make_utf8_encoded_stringio()
612
lf = log.LongLogFormatter(to_file=sio)
614
self.assertEqualDiff("""\
615
------------------------------------------------------------
617
committer: Joe Foo <joe@foo.com>
619
timestamp: Mon 2005-11-21 09:32:56 -0600
621
single line with trailing newline
622
------------------------------------------------------------
624
author: Joe Bar <joe@bar.com>
625
committer: Joe Foo <joe@foo.com>
627
timestamp: Mon 2005-11-21 09:27:22 -0600
632
------------------------------------------------------------
634
committer: Joe Foo <joe@foo.com>
636
timestamp: Mon 2005-11-21 09:24:15 -0600
642
def test_author_in_log(self):
643
"""Log includes the author name if it's set in
644
the revision properties
646
wt = self.make_branch_and_tree('.')
648
self.build_tree(['a'])
650
b.nick = 'test_author_log'
651
wt.commit(message='add a',
652
timestamp=1132711707,
654
committer='Lorem Ipsum <test@example.com>',
655
authors=['John Doe <jdoe@example.com>',
656
'Jane Rey <jrey@example.com>'])
658
formatter = log.LongLogFormatter(to_file=sio)
659
log.show_log(b, formatter)
660
self.assertEqualDiff('''\
661
------------------------------------------------------------
663
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
664
committer: Lorem Ipsum <test@example.com>
665
branch nick: test_author_log
666
timestamp: Wed 2005-11-23 12:08:27 +1000
672
def test_properties_in_log(self):
673
"""Log includes the custom properties returned by the registered
676
wt = self.make_branch_and_tree('.')
678
self.build_tree(['a'])
680
b.nick = 'test_properties_in_log'
681
wt.commit(message='add a',
682
timestamp=1132711707,
684
committer='Lorem Ipsum <test@example.com>',
685
authors=['John Doe <jdoe@example.com>'])
687
formatter = log.LongLogFormatter(to_file=sio)
689
def trivial_custom_prop_handler(revision):
690
return {'test_prop':'test_value'}
692
log.properties_handler_registry.register(
693
'trivial_custom_prop_handler',
694
trivial_custom_prop_handler)
695
log.show_log(b, formatter)
697
log.properties_handler_registry.remove(
698
'trivial_custom_prop_handler')
699
self.assertEqualDiff('''\
700
------------------------------------------------------------
702
test_prop: test_value
703
author: John Doe <jdoe@example.com>
704
committer: Lorem Ipsum <test@example.com>
705
branch nick: test_properties_in_log
706
timestamp: Wed 2005-11-23 12:08:27 +1000
712
def test_properties_in_short_log(self):
713
"""Log includes the custom properties returned by the registered
716
wt = self.make_branch_and_tree('.')
718
self.build_tree(['a'])
720
b.nick = 'test_properties_in_short_log'
721
wt.commit(message='add a',
722
timestamp=1132711707,
724
committer='Lorem Ipsum <test@example.com>',
725
authors=['John Doe <jdoe@example.com>'])
727
formatter = log.ShortLogFormatter(to_file=sio)
729
def trivial_custom_prop_handler(revision):
730
return {'test_prop':'test_value'}
732
log.properties_handler_registry.register(
733
'trivial_custom_prop_handler',
734
trivial_custom_prop_handler)
735
log.show_log(b, formatter)
737
log.properties_handler_registry.remove(
738
'trivial_custom_prop_handler')
739
self.assertEqualDiff('''\
740
1 John Doe\t2005-11-23
741
test_prop: test_value
747
def test_error_in_properties_handler(self):
748
"""Log includes the custom properties returned by the registered
751
wt = self.make_branch_and_tree('.')
753
self.build_tree(['a'])
755
b.nick = 'test_author_log'
756
wt.commit(message='add a',
757
timestamp=1132711707,
759
committer='Lorem Ipsum <test@example.com>',
760
authors=['John Doe <jdoe@example.com>'],
761
revprops={'first_prop':'first_value'})
763
formatter = log.LongLogFormatter(to_file=sio)
765
def trivial_custom_prop_handler(revision):
766
raise StandardError("a test error")
768
log.properties_handler_registry.register(
769
'trivial_custom_prop_handler',
770
trivial_custom_prop_handler)
771
self.assertRaises(StandardError, log.show_log, b, formatter,)
773
log.properties_handler_registry.remove(
774
'trivial_custom_prop_handler')
776
def test_properties_handler_bad_argument(self):
777
wt = self.make_branch_and_tree('.')
779
self.build_tree(['a'])
781
b.nick = 'test_author_log'
782
wt.commit(message='add a',
783
timestamp=1132711707,
785
committer='Lorem Ipsum <test@example.com>',
786
authors=['John Doe <jdoe@example.com>'],
787
revprops={'a_prop':'test_value'})
789
formatter = log.LongLogFormatter(to_file=sio)
791
def bad_argument_prop_handler(revision):
792
return {'custom_prop_name':revision.properties['a_prop']}
794
log.properties_handler_registry.register(
795
'bad_argument_prop_handler',
796
bad_argument_prop_handler)
798
self.assertRaises(AttributeError, formatter.show_properties,
801
revision = b.repository.get_revision(b.last_revision())
802
formatter.show_properties(revision, '')
803
self.assertEqualDiff('''custom_prop_name: test_value\n''',
806
log.properties_handler_registry.remove(
807
'bad_argument_prop_handler')
810
class TestLongLogFormatterWithoutMergeRevisions(TestCaseWithoutPropsHandler):
812
def test_long_verbose_log(self):
813
"""Verbose log includes changed files
817
wt = self.make_branch_and_tree('.')
819
self.build_tree(['a'])
821
# XXX: why does a longer nick show up?
822
b.nick = 'test_verbose_log'
823
wt.commit(message='add a',
824
timestamp=1132711707,
826
committer='Lorem Ipsum <test@example.com>')
827
logfile = file('out.tmp', 'w+')
828
formatter = log.LongLogFormatter(to_file=logfile, levels=1)
829
log.show_log(b, formatter, verbose=True)
832
log_contents = logfile.read()
833
self.assertEqualDiff('''\
834
------------------------------------------------------------
836
committer: Lorem Ipsum <test@example.com>
837
branch nick: test_verbose_log
838
timestamp: Wed 2005-11-23 12:08:27 +1000
846
def test_long_verbose_contain_deltas(self):
847
wt = self.make_branch_and_tree('parent')
848
self.build_tree(['parent/f1', 'parent/f2'])
850
wt.commit('first post')
851
self.run_bzr('branch parent child')
852
os.unlink('child/f1')
853
file('child/f2', 'wb').write('hello\n')
854
self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
857
self.run_bzr('merge ../child')
858
wt.commit('merge branch 1')
860
sio = self.make_utf8_encoded_stringio()
861
lf = log.LongLogFormatter(to_file=sio, levels=1)
862
log.show_log(b, lf, verbose=True)
863
the_log = normalize_log(sio.getvalue())
864
self.assertEqualDiff("""\
865
------------------------------------------------------------
867
committer: Lorem Ipsum <test@example.com>
876
------------------------------------------------------------
878
committer: Lorem Ipsum <test@example.com>
886
------------------------------------------------------------
887
Use --levels 0 (or -n0) to see merged revisions.
891
def test_long_trailing_newlines(self):
892
wt = self.make_branch_and_tree('.')
893
b = make_commits_with_trailing_newlines(wt)
894
sio = self.make_utf8_encoded_stringio()
895
lf = log.LongLogFormatter(to_file=sio, levels=1)
897
self.assertEqualDiff("""\
898
------------------------------------------------------------
900
committer: Joe Foo <joe@foo.com>
902
timestamp: Mon 2005-11-21 09:32:56 -0600
904
single line with trailing newline
905
------------------------------------------------------------
907
author: Joe Bar <joe@bar.com>
908
committer: Joe Foo <joe@foo.com>
910
timestamp: Mon 2005-11-21 09:27:22 -0600
915
------------------------------------------------------------
917
committer: Joe Foo <joe@foo.com>
919
timestamp: Mon 2005-11-21 09:24:15 -0600
925
def test_long_author_in_log(self):
926
"""Log includes the author name if it's set in
927
the revision properties
929
wt = self.make_branch_and_tree('.')
931
self.build_tree(['a'])
933
b.nick = 'test_author_log'
934
wt.commit(message='add a',
935
timestamp=1132711707,
937
committer='Lorem Ipsum <test@example.com>',
938
authors=['John Doe <jdoe@example.com>'])
940
formatter = log.LongLogFormatter(to_file=sio, levels=1)
941
log.show_log(b, formatter)
942
self.assertEqualDiff('''\
943
------------------------------------------------------------
945
author: John Doe <jdoe@example.com>
946
committer: Lorem Ipsum <test@example.com>
947
branch nick: test_author_log
948
timestamp: Wed 2005-11-23 12:08:27 +1000
954
def test_long_properties_in_log(self):
955
"""Log includes the custom properties returned by the registered
958
wt = self.make_branch_and_tree('.')
960
self.build_tree(['a'])
962
b.nick = 'test_properties_in_log'
963
wt.commit(message='add a',
964
timestamp=1132711707,
966
committer='Lorem Ipsum <test@example.com>',
967
authors=['John Doe <jdoe@example.com>'])
969
formatter = log.LongLogFormatter(to_file=sio, levels=1)
971
def trivial_custom_prop_handler(revision):
972
return {'test_prop':'test_value'}
974
log.properties_handler_registry.register(
975
'trivial_custom_prop_handler',
976
trivial_custom_prop_handler)
977
log.show_log(b, formatter)
979
log.properties_handler_registry.remove(
980
'trivial_custom_prop_handler')
981
self.assertEqualDiff('''\
982
------------------------------------------------------------
984
test_prop: test_value
985
author: John Doe <jdoe@example.com>
986
committer: Lorem Ipsum <test@example.com>
987
branch nick: test_properties_in_log
988
timestamp: Wed 2005-11-23 12:08:27 +1000
995
class TestLineLogFormatter(tests.TestCaseWithTransport):
997
def test_line_log(self):
998
"""Line log should show revno
1002
wt = self.make_branch_and_tree('.')
1004
self.build_tree(['a'])
1006
b.nick = 'test-line-log'
1007
wt.commit(message='add a',
1008
timestamp=1132711707,
1010
committer='Line-Log-Formatter Tester <test@line.log>')
1011
logfile = file('out.tmp', 'w+')
1012
formatter = log.LineLogFormatter(to_file=logfile)
1013
log.show_log(b, formatter)
1016
log_contents = logfile.read()
1017
self.assertEqualDiff('1: Line-Log-Formatte... 2005-11-23 add a\n',
1020
def test_trailing_newlines(self):
1021
wt = self.make_branch_and_tree('.')
1022
b = make_commits_with_trailing_newlines(wt)
1023
sio = self.make_utf8_encoded_stringio()
1024
lf = log.LineLogFormatter(to_file=sio)
1026
self.assertEqualDiff("""\
1027
3: Joe Foo 2005-11-21 single line with trailing newline
1028
2: Joe Bar 2005-11-21 multiline
1029
1: Joe Foo 2005-11-21 simple log message
1033
def _prepare_tree_with_merges(self, with_tags=False):
1034
wt = self.make_branch_and_memory_tree('.')
1036
self.addCleanup(wt.unlock)
1038
wt.commit('rev-1', rev_id='rev-1',
1039
timestamp=1132586655, timezone=36000,
1040
committer='Joe Foo <joe@foo.com>')
1041
wt.commit('rev-merged', rev_id='rev-2a',
1042
timestamp=1132586700, timezone=36000,
1043
committer='Joe Foo <joe@foo.com>')
1044
wt.set_parent_ids(['rev-1', 'rev-2a'])
1045
wt.branch.set_last_revision_info(1, 'rev-1')
1046
wt.commit('rev-2', rev_id='rev-2b',
1047
timestamp=1132586800, timezone=36000,
1048
committer='Joe Foo <joe@foo.com>')
1051
branch.tags.set_tag('v0.2', 'rev-2b')
1052
wt.commit('rev-3', rev_id='rev-3',
1053
timestamp=1132586900, timezone=36000,
1054
committer='Jane Foo <jane@foo.com>')
1055
branch.tags.set_tag('v1.0rc1', 'rev-3')
1056
branch.tags.set_tag('v1.0', 'rev-3')
1059
def test_line_log_single_merge_revision(self):
1060
wt = self._prepare_tree_with_merges()
1061
logfile = self.make_utf8_encoded_stringio()
1062
formatter = log.LineLogFormatter(to_file=logfile)
1063
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
1065
rev = revspec.in_history(wtb)
1066
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
1067
self.assertEqualDiff("""\
1068
1.1.1: Joe Foo 2005-11-22 rev-merged
1072
def test_line_log_with_tags(self):
1073
wt = self._prepare_tree_with_merges(with_tags=True)
1074
logfile = self.make_utf8_encoded_stringio()
1075
formatter = log.LineLogFormatter(to_file=logfile)
1076
log.show_log(wt.branch, formatter)
1077
self.assertEqualDiff("""\
1078
3: Jane Foo 2005-11-22 {v1.0, v1.0rc1} rev-3
1079
2: Joe Foo 2005-11-22 [merge] {v0.2} rev-2
1080
1: Joe Foo 2005-11-22 rev-1
1084
class TestLineLogFormatterWithMergeRevisions(tests.TestCaseWithTransport):
1086
def test_line_merge_revs_log(self):
1087
"""Line log should show revno
1091
wt = self.make_branch_and_tree('.')
1093
self.build_tree(['a'])
1095
b.nick = 'test-line-log'
1096
wt.commit(message='add a',
1097
timestamp=1132711707,
1099
committer='Line-Log-Formatter Tester <test@line.log>')
1100
logfile = file('out.tmp', 'w+')
1101
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1102
log.show_log(b, formatter)
1105
log_contents = logfile.read()
1106
self.assertEqualDiff('1: Line-Log-Formatte... 2005-11-23 add a\n',
1109
def test_line_merge_revs_log_single_merge_revision(self):
1110
wt = self.make_branch_and_memory_tree('.')
1112
self.addCleanup(wt.unlock)
1114
wt.commit('rev-1', rev_id='rev-1',
1115
timestamp=1132586655, timezone=36000,
1116
committer='Joe Foo <joe@foo.com>')
1117
wt.commit('rev-merged', rev_id='rev-2a',
1118
timestamp=1132586700, timezone=36000,
1119
committer='Joe Foo <joe@foo.com>')
1120
wt.set_parent_ids(['rev-1', 'rev-2a'])
1121
wt.branch.set_last_revision_info(1, 'rev-1')
1122
wt.commit('rev-2', rev_id='rev-2b',
1123
timestamp=1132586800, timezone=36000,
1124
committer='Joe Foo <joe@foo.com>')
1125
logfile = self.make_utf8_encoded_stringio()
1126
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1127
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
1129
rev = revspec.in_history(wtb)
1130
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
1131
self.assertEqualDiff("""\
1132
1.1.1: Joe Foo 2005-11-22 rev-merged
1136
def test_line_merge_revs_log_with_merges(self):
1137
wt = self.make_branch_and_memory_tree('.')
1139
self.addCleanup(wt.unlock)
1141
wt.commit('rev-1', rev_id='rev-1',
1142
timestamp=1132586655, timezone=36000,
1143
committer='Joe Foo <joe@foo.com>')
1144
wt.commit('rev-merged', rev_id='rev-2a',
1145
timestamp=1132586700, timezone=36000,
1146
committer='Joe Foo <joe@foo.com>')
1147
wt.set_parent_ids(['rev-1', 'rev-2a'])
1148
wt.branch.set_last_revision_info(1, 'rev-1')
1149
wt.commit('rev-2', rev_id='rev-2b',
1150
timestamp=1132586800, timezone=36000,
1151
committer='Joe Foo <joe@foo.com>')
1152
logfile = self.make_utf8_encoded_stringio()
1153
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1154
log.show_log(wt.branch, formatter)
1155
self.assertEqualDiff("""\
1156
2: Joe Foo 2005-11-22 [merge] rev-2
1157
1.1.1: Joe Foo 2005-11-22 rev-merged
1158
1: Joe Foo 2005-11-22 rev-1
1162
class TestGetViewRevisions(tests.TestCaseWithTransport):
1164
def make_tree_with_commits(self):
1165
"""Create a tree with well-known revision ids"""
1166
wt = self.make_branch_and_tree('tree1')
1167
wt.commit('commit one', rev_id='1')
1168
wt.commit('commit two', rev_id='2')
1169
wt.commit('commit three', rev_id='3')
1170
mainline_revs = [None, '1', '2', '3']
1171
rev_nos = {'1': 1, '2': 2, '3': 3}
1172
return mainline_revs, rev_nos, wt
1174
def make_tree_with_merges(self):
1175
"""Create a tree with well-known revision ids and a merge"""
1176
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1177
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
1178
tree2.commit('four-a', rev_id='4a')
1179
wt.merge_from_branch(tree2.branch)
1180
wt.commit('four-b', rev_id='4b')
1181
mainline_revs.append('4b')
1184
return mainline_revs, rev_nos, wt
1186
def make_tree_with_many_merges(self):
1187
"""Create a tree with well-known revision ids"""
1188
wt = self.make_branch_and_tree('tree1')
1189
self.build_tree_contents([('tree1/f', '1\n')])
1190
wt.add(['f'], ['f-id'])
1191
wt.commit('commit one', rev_id='1')
1192
wt.commit('commit two', rev_id='2')
1194
tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
1195
self.build_tree_contents([('tree3/f', '1\n2\n3a\n')])
1196
tree3.commit('commit three a', rev_id='3a')
1198
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
1199
tree2.merge_from_branch(tree3.branch)
1200
tree2.commit('commit three b', rev_id='3b')
1202
wt.merge_from_branch(tree2.branch)
1203
wt.commit('commit three c', rev_id='3c')
1204
tree2.commit('four-a', rev_id='4a')
1206
wt.merge_from_branch(tree2.branch)
1207
wt.commit('four-b', rev_id='4b')
1209
mainline_revs = [None, '1', '2', '3c', '4b']
1210
rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
1211
full_rev_nos_for_reference = {
1214
'3a': '2.1.1', #first commit tree 3
1215
'3b': '2.2.1', # first commit tree 2
1216
'3c': '3', #merges 3b to main
1217
'4a': '2.2.2', # second commit tree 2
1218
'4b': '4', # merges 4a to main
1220
return mainline_revs, rev_nos, wt
1222
def test_get_view_revisions_forward(self):
1223
"""Test the get_view_revisions method"""
1224
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1226
self.addCleanup(wt.unlock)
1227
revisions = list(log.get_view_revisions(
1228
mainline_revs, rev_nos, wt.branch, 'forward'))
1229
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
1231
revisions2 = list(log.get_view_revisions(
1232
mainline_revs, rev_nos, wt.branch, 'forward',
1233
include_merges=False))
1234
self.assertEqual(revisions, revisions2)
1236
def test_get_view_revisions_reverse(self):
1237
"""Test the get_view_revisions with reverse"""
1238
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1240
self.addCleanup(wt.unlock)
1241
revisions = list(log.get_view_revisions(
1242
mainline_revs, rev_nos, wt.branch, 'reverse'))
1243
self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
1245
revisions2 = list(log.get_view_revisions(
1246
mainline_revs, rev_nos, wt.branch, 'reverse',
1247
include_merges=False))
1248
self.assertEqual(revisions, revisions2)
1250
def test_get_view_revisions_merge(self):
1251
"""Test get_view_revisions when there are merges"""
1252
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
1254
self.addCleanup(wt.unlock)
1255
revisions = list(log.get_view_revisions(
1256
mainline_revs, rev_nos, wt.branch, 'forward'))
1257
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
1258
('4b', '4', 0), ('4a', '3.1.1', 1)],
1260
revisions = list(log.get_view_revisions(
1261
mainline_revs, rev_nos, wt.branch, 'forward',
1262
include_merges=False))
1263
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
1267
def test_get_view_revisions_merge_reverse(self):
1268
"""Test get_view_revisions in reverse when there are merges"""
1269
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
1271
self.addCleanup(wt.unlock)
1272
revisions = list(log.get_view_revisions(
1273
mainline_revs, rev_nos, wt.branch, 'reverse'))
1274
self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
1275
('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
1277
revisions = list(log.get_view_revisions(
1278
mainline_revs, rev_nos, wt.branch, 'reverse',
1279
include_merges=False))
1280
self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
1284
def test_get_view_revisions_merge2(self):
1285
"""Test get_view_revisions when there are merges"""
1286
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
1288
self.addCleanup(wt.unlock)
1289
revisions = list(log.get_view_revisions(
1290
mainline_revs, rev_nos, wt.branch, 'forward'))
1291
expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
1292
('3a', '2.1.1', 1), ('3b', '2.2.1', 1), ('4b', '4', 0),
1294
self.assertEqual(expected, revisions)
1295
revisions = list(log.get_view_revisions(
1296
mainline_revs, rev_nos, wt.branch, 'forward',
1297
include_merges=False))
1298
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
1303
def test_file_id_for_range(self):
1304
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
1306
self.addCleanup(wt.unlock)
1308
def rev_from_rev_id(revid, branch):
1309
revspec = revisionspec.RevisionSpec.from_string('revid:%s' % revid)
1310
return revspec.in_history(branch)
1312
def view_revs(start_rev, end_rev, file_id, direction):
1313
revs = log.calculate_view_revisions(
1315
start_rev, # start_revision
1316
end_rev, # end_revision
1317
direction, # direction
1318
file_id, # specific_fileid
1319
True, # generate_merge_revisions
1323
rev_3a = rev_from_rev_id('3a', wt.branch)
1324
rev_4b = rev_from_rev_id('4b', wt.branch)
1325
self.assertEqual([('3c', '3', 0), ('3a', '2.1.1', 1)],
1326
view_revs(rev_3a, rev_4b, 'f-id', 'reverse'))
1327
# Note: 3c still appears before 3a here because of depth-based sorting
1328
self.assertEqual([('3c', '3', 0), ('3a', '2.1.1', 1)],
1329
view_revs(rev_3a, rev_4b, 'f-id', 'forward'))
1332
class TestGetRevisionsTouchingFileID(tests.TestCaseWithTransport):
1334
def create_tree_with_single_merge(self):
1335
"""Create a branch with a moderate layout.
1337
The revision graph looks like:
1345
In this graph, A introduced files f1 and f2 and f3.
1346
B modifies f1 and f3, and C modifies f2 and f3.
1347
D merges the changes from B and C and resolves the conflict for f3.
1349
# TODO: jam 20070218 This seems like it could really be done
1350
# with make_branch_and_memory_tree() if we could just
1351
# create the content of those files.
1352
# TODO: jam 20070218 Another alternative is that we would really
1353
# like to only create this tree 1 time for all tests that
1354
# use it. Since 'log' only uses the tree in a readonly
1355
# fashion, it seems a shame to regenerate an identical
1356
# tree for each test.
1357
tree = self.make_branch_and_tree('tree')
1359
self.addCleanup(tree.unlock)
1361
self.build_tree_contents([('tree/f1', 'A\n'),
1365
tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
1366
tree.commit('A', rev_id='A')
1368
self.build_tree_contents([('tree/f2', 'A\nC\n'),
1369
('tree/f3', 'A\nC\n'),
1371
tree.commit('C', rev_id='C')
1372
# Revert back to A to build the other history.
1373
tree.set_last_revision('A')
1374
tree.branch.set_last_revision_info(1, 'A')
1375
self.build_tree_contents([('tree/f1', 'A\nB\n'),
1377
('tree/f3', 'A\nB\n'),
1379
tree.commit('B', rev_id='B')
1380
tree.set_parent_ids(['B', 'C'])
1381
self.build_tree_contents([('tree/f1', 'A\nB\n'),
1382
('tree/f2', 'A\nC\n'),
1383
('tree/f3', 'A\nB\nC\n'),
1385
tree.commit('D', rev_id='D')
1387
# Switch to a read lock for this tree.
1388
# We still have an addCleanup(tree.unlock) pending
1393
def check_delta(self, delta, **kw):
1394
"""Check the filenames touched by a delta are as expected.
1396
Caller only have to pass in the list of files for each part, all
1397
unspecified parts are considered empty (and checked as such).
1399
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
1400
# By default we expect an empty list
1401
expected = kw.get(n, [])
1402
# strip out only the path components
1403
got = [x[0] for x in getattr(delta, n)]
1404
self.assertEqual(expected, got)
1406
def test_tree_with_single_merge(self):
1407
"""Make sure the tree layout is correct."""
1408
tree = self.create_tree_with_single_merge()
1409
rev_A_tree = tree.branch.repository.revision_tree('A')
1410
rev_B_tree = tree.branch.repository.revision_tree('B')
1411
rev_C_tree = tree.branch.repository.revision_tree('C')
1412
rev_D_tree = tree.branch.repository.revision_tree('D')
1414
self.check_delta(rev_B_tree.changes_from(rev_A_tree),
1415
modified=['f1', 'f3'])
1417
self.check_delta(rev_C_tree.changes_from(rev_A_tree),
1418
modified=['f2', 'f3'])
1420
self.check_delta(rev_D_tree.changes_from(rev_B_tree),
1421
modified=['f2', 'f3'])
1423
self.check_delta(rev_D_tree.changes_from(rev_C_tree),
1424
modified=['f1', 'f3'])
1426
def assertAllRevisionsForFileID(self, tree, file_id, revisions):
1427
"""Ensure _filter_revisions_touching_file_id returns the right values.
1429
Get the return value from _filter_revisions_touching_file_id and make
1430
sure they are correct.
1432
# The api for _filter_revisions_touching_file_id is a little crazy.
1433
# So we do the setup here.
1434
mainline = tree.branch.revision_history()
1435
mainline.insert(0, None)
1436
revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
1437
view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
1439
actual_revs = log._filter_revisions_touching_file_id(
1442
list(view_revs_iter))
1443
self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
1445
def test_file_id_f1(self):
1446
tree = self.create_tree_with_single_merge()
1447
# f1 should be marked as modified by revisions A and B
1448
self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
1450
def test_file_id_f2(self):
1451
tree = self.create_tree_with_single_merge()
1452
# f2 should be marked as modified by revisions A, C, and D
1453
# because D merged the changes from C.
1454
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1456
def test_file_id_f3(self):
1457
tree = self.create_tree_with_single_merge()
1458
# f3 should be marked as modified by revisions A, B, C, and D
1459
self.assertAllRevisionsForFileID(tree, 'f3-id', ['D', 'C', 'B', 'A'])
1461
def test_file_id_with_ghosts(self):
1462
# This is testing bug #209948, where having a ghost would cause
1463
# _filter_revisions_touching_file_id() to fail.
1464
tree = self.create_tree_with_single_merge()
1465
# We need to add a revision, so switch back to a write-locked tree
1466
# (still a single addCleanup(tree.unlock) pending).
1469
first_parent = tree.last_revision()
1470
tree.set_parent_ids([first_parent, 'ghost-revision-id'])
1471
self.build_tree_contents([('tree/f1', 'A\nB\nXX\n')])
1472
tree.commit('commit with a ghost', rev_id='XX')
1473
self.assertAllRevisionsForFileID(tree, 'f1-id', ['XX', 'B', 'A'])
1474
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1476
def test_unknown_file_id(self):
1477
tree = self.create_tree_with_single_merge()
1478
self.assertAllRevisionsForFileID(tree, 'unknown', [])
1480
def test_empty_branch_unknown_file_id(self):
1481
tree = self.make_branch_and_tree('tree')
1482
self.assertAllRevisionsForFileID(tree, 'unknown', [])
1485
class TestShowChangedRevisions(tests.TestCaseWithTransport):
1487
def test_show_changed_revisions_verbose(self):
1488
tree = self.make_branch_and_tree('tree_a')
1489
self.build_tree(['tree_a/foo'])
1491
tree.commit('bar', rev_id='bar-id')
1492
s = self.make_utf8_encoded_stringio()
1493
log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
1494
self.assertContainsRe(s.getvalue(), 'bar')
1495
self.assertNotContainsRe(s.getvalue(), 'foo')
1498
class TestLogFormatter(tests.TestCase):
1500
def test_short_committer(self):
1501
rev = revision.Revision('a-id')
1502
rev.committer = 'John Doe <jdoe@example.com>'
1503
lf = log.LogFormatter(None)
1504
self.assertEqual('John Doe', lf.short_committer(rev))
1505
rev.committer = 'John Smith <jsmith@example.com>'
1506
self.assertEqual('John Smith', lf.short_committer(rev))
1507
rev.committer = 'John Smith'
1508
self.assertEqual('John Smith', lf.short_committer(rev))
1509
rev.committer = 'jsmith@example.com'
1510
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1511
rev.committer = '<jsmith@example.com>'
1512
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1513
rev.committer = 'John Smith jsmith@example.com'
1514
self.assertEqual('John Smith', lf.short_committer(rev))
1516
def test_short_author(self):
1517
rev = revision.Revision('a-id')
1518
rev.committer = 'John Doe <jdoe@example.com>'
1519
lf = log.LogFormatter(None)
1520
self.assertEqual('John Doe', lf.short_author(rev))
1521
rev.properties['author'] = 'John Smith <jsmith@example.com>'
1522
self.assertEqual('John Smith', lf.short_author(rev))
1523
rev.properties['author'] = 'John Smith'
1524
self.assertEqual('John Smith', lf.short_author(rev))
1525
rev.properties['author'] = 'jsmith@example.com'
1526
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1527
rev.properties['author'] = '<jsmith@example.com>'
1528
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1529
rev.properties['author'] = 'John Smith jsmith@example.com'
1530
self.assertEqual('John Smith', lf.short_author(rev))
1531
del rev.properties['author']
1532
rev.properties['authors'] = ('John Smith <jsmith@example.com>\n'
1533
'Jane Rey <jrey@example.com>')
1534
self.assertEqual('John Smith', lf.short_author(rev))
1537
class TestReverseByDepth(tests.TestCase):
1538
"""Test reverse_by_depth behavior.
1540
This is used to present revisions in forward (oldest first) order in a nice
1543
The tests use lighter revision description to ease reading.
1546
def assertReversed(self, forward, backward):
1547
# Transform the descriptions to suit the API: tests use (revno, depth),
1548
# while the API expects (revid, revno, depth)
1549
def complete_revisions(l):
1550
"""Transform the description to suit the API.
1552
Tests use (revno, depth) whil the API expects (revid, revno, depth).
1553
Since the revid is arbitrary, we just duplicate revno
1555
return [ (r, r, d) for r, d in l]
1556
forward = complete_revisions(forward)
1557
backward= complete_revisions(backward)
1558
self.assertEqual(forward, log.reverse_by_depth(backward))
1561
def test_mainline_revisions(self):
1562
self.assertReversed([( '1', 0), ('2', 0)],
1563
[('2', 0), ('1', 0)])
1565
def test_merged_revisions(self):
1566
self.assertReversed([('1', 0), ('2', 0), ('2.2', 1), ('2.1', 1),],
1567
[('2', 0), ('2.1', 1), ('2.2', 1), ('1', 0),])
1568
def test_shifted_merged_revisions(self):
1569
"""Test irregular layout.
1571
Requesting revisions touching a file can produce "holes" in the depths.
1573
self.assertReversed([('1', 0), ('2', 0), ('1.1', 2), ('1.2', 2),],
1574
[('2', 0), ('1.2', 2), ('1.1', 2), ('1', 0),])
1576
def test_merged_without_child_revisions(self):
1577
"""Test irregular layout.
1579
Revision ranges can produce "holes" in the depths.
1581
# When a revision of higher depth doesn't follow one of lower depth, we
1582
# assume a lower depth one is virtually there
1583
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1584
[('4', 4), ('3', 3), ('2', 2), ('1', 2),])
1585
# So we get the same order after reversing below even if the original
1586
# revisions are not in the same order.
1587
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1588
[('3', 3), ('4', 4), ('2', 2), ('1', 2),])
1591
class TestHistoryChange(tests.TestCaseWithTransport):
1593
def setup_a_tree(self):
1594
tree = self.make_branch_and_tree('tree')
1596
self.addCleanup(tree.unlock)
1597
tree.commit('1a', rev_id='1a')
1598
tree.commit('2a', rev_id='2a')
1599
tree.commit('3a', rev_id='3a')
1602
def setup_ab_tree(self):
1603
tree = self.setup_a_tree()
1604
tree.set_last_revision('1a')
1605
tree.branch.set_last_revision_info(1, '1a')
1606
tree.commit('2b', rev_id='2b')
1607
tree.commit('3b', rev_id='3b')
1610
def setup_ac_tree(self):
1611
tree = self.setup_a_tree()
1612
tree.set_last_revision(revision.NULL_REVISION)
1613
tree.branch.set_last_revision_info(0, revision.NULL_REVISION)
1614
tree.commit('1c', rev_id='1c')
1615
tree.commit('2c', rev_id='2c')
1616
tree.commit('3c', rev_id='3c')
1619
def test_all_new(self):
1620
tree = self.setup_ab_tree()
1621
old, new = log.get_history_change('1a', '3a', tree.branch.repository)
1622
self.assertEqual([], old)
1623
self.assertEqual(['2a', '3a'], new)
1625
def test_all_old(self):
1626
tree = self.setup_ab_tree()
1627
old, new = log.get_history_change('3a', '1a', tree.branch.repository)
1628
self.assertEqual([], new)
1629
self.assertEqual(['2a', '3a'], old)
1631
def test_null_old(self):
1632
tree = self.setup_ab_tree()
1633
old, new = log.get_history_change(revision.NULL_REVISION,
1634
'3a', tree.branch.repository)
1635
self.assertEqual([], old)
1636
self.assertEqual(['1a', '2a', '3a'], new)
1638
def test_null_new(self):
1639
tree = self.setup_ab_tree()
1640
old, new = log.get_history_change('3a', revision.NULL_REVISION,
1641
tree.branch.repository)
1642
self.assertEqual([], new)
1643
self.assertEqual(['1a', '2a', '3a'], old)
1645
def test_diverged(self):
1646
tree = self.setup_ab_tree()
1647
old, new = log.get_history_change('3a', '3b', tree.branch.repository)
1648
self.assertEqual(old, ['2a', '3a'])
1649
self.assertEqual(new, ['2b', '3b'])
1651
def test_unrelated(self):
1652
tree = self.setup_ac_tree()
1653
old, new = log.get_history_change('3a', '3c', tree.branch.repository)
1654
self.assertEqual(old, ['1a', '2a', '3a'])
1655
self.assertEqual(new, ['1c', '2c', '3c'])
1657
def test_show_branch_change(self):
1658
tree = self.setup_ab_tree()
1660
log.show_branch_change(tree.branch, s, 3, '3a')
1661
self.assertContainsRe(s.getvalue(),
1662
'[*]{60}\nRemoved Revisions:\n(.|\n)*2a(.|\n)*3a(.|\n)*'
1663
'[*]{60}\n\nAdded Revisions:\n(.|\n)*2b(.|\n)*3b')
1665
def test_show_branch_change_no_change(self):
1666
tree = self.setup_ab_tree()
1668
log.show_branch_change(tree.branch, s, 3, '3b')
1669
self.assertEqual(s.getvalue(),
1670
'Nothing seems to have changed\n')
1672
def test_show_branch_change_no_old(self):
1673
tree = self.setup_ab_tree()
1675
log.show_branch_change(tree.branch, s, 2, '2b')
1676
self.assertContainsRe(s.getvalue(), 'Added Revisions:')
1677
self.assertNotContainsRe(s.getvalue(), 'Removed Revisions:')
1679
def test_show_branch_change_no_new(self):
1680
tree = self.setup_ab_tree()
1681
tree.branch.set_last_revision_info(2, '2b')
1683
log.show_branch_change(tree.branch, s, 3, '3b')
1684
self.assertContainsRe(s.getvalue(), 'Removed Revisions:')
1685
self.assertNotContainsRe(s.getvalue(), 'Added Revisions:')