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'])
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
authors=['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, levels=0)
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, levels=0)
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
authors=['John Doe <jdoe@example.com>',
683
'Jane Rey <jrey@example.com>'])
685
formatter = log.LongLogFormatter(to_file=sio)
686
log.show_log(b, formatter)
687
self.assertEqualDiff('''\
688
------------------------------------------------------------
690
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
691
committer: Lorem Ipsum <test@example.com>
692
branch nick: test_author_log
693
timestamp: Wed 2005-11-23 12:08:27 +1000
699
def test_properties_in_log(self):
700
"""Log includes the custom properties returned by the registered
703
wt = self.make_branch_and_tree('.')
705
self.build_tree(['a'])
707
b.nick = 'test_properties_in_log'
708
wt.commit(message='add a',
709
timestamp=1132711707,
711
committer='Lorem Ipsum <test@example.com>',
712
authors=['John Doe <jdoe@example.com>'])
714
formatter = log.LongLogFormatter(to_file=sio)
716
def trivial_custom_prop_handler(revision):
717
return {'test_prop':'test_value'}
719
log.properties_handler_registry.register(
720
'trivial_custom_prop_handler',
721
trivial_custom_prop_handler)
722
log.show_log(b, formatter)
724
log.properties_handler_registry.remove(
725
'trivial_custom_prop_handler')
726
self.assertEqualDiff('''\
727
------------------------------------------------------------
729
test_prop: test_value
730
author: John Doe <jdoe@example.com>
731
committer: Lorem Ipsum <test@example.com>
732
branch nick: test_properties_in_log
733
timestamp: Wed 2005-11-23 12:08:27 +1000
739
def test_properties_in_short_log(self):
740
"""Log includes the custom properties returned by the registered
743
wt = self.make_branch_and_tree('.')
745
self.build_tree(['a'])
747
b.nick = 'test_properties_in_short_log'
748
wt.commit(message='add a',
749
timestamp=1132711707,
751
committer='Lorem Ipsum <test@example.com>',
752
authors=['John Doe <jdoe@example.com>'])
754
formatter = log.ShortLogFormatter(to_file=sio)
756
def trivial_custom_prop_handler(revision):
757
return {'test_prop':'test_value'}
759
log.properties_handler_registry.register(
760
'trivial_custom_prop_handler',
761
trivial_custom_prop_handler)
762
log.show_log(b, formatter)
764
log.properties_handler_registry.remove(
765
'trivial_custom_prop_handler')
766
self.assertEqualDiff('''\
767
1 John Doe\t2005-11-23
768
test_prop: test_value
774
def test_error_in_properties_handler(self):
775
"""Log includes the custom properties returned by the registered
778
wt = self.make_branch_and_tree('.')
780
self.build_tree(['a'])
782
b.nick = 'test_author_log'
783
wt.commit(message='add a',
784
timestamp=1132711707,
786
committer='Lorem Ipsum <test@example.com>',
787
authors=['John Doe <jdoe@example.com>'],
788
revprops={'first_prop':'first_value'})
790
formatter = log.LongLogFormatter(to_file=sio)
792
def trivial_custom_prop_handler(revision):
793
raise StandardError("a test error")
795
log.properties_handler_registry.register(
796
'trivial_custom_prop_handler',
797
trivial_custom_prop_handler)
798
self.assertRaises(StandardError, log.show_log, b, formatter,)
800
log.properties_handler_registry.remove(
801
'trivial_custom_prop_handler')
803
def test_properties_handler_bad_argument(self):
804
wt = self.make_branch_and_tree('.')
806
self.build_tree(['a'])
808
b.nick = 'test_author_log'
809
wt.commit(message='add a',
810
timestamp=1132711707,
812
committer='Lorem Ipsum <test@example.com>',
813
authors=['John Doe <jdoe@example.com>'],
814
revprops={'a_prop':'test_value'})
816
formatter = log.LongLogFormatter(to_file=sio)
818
def bad_argument_prop_handler(revision):
819
return {'custom_prop_name':revision.properties['a_prop']}
821
log.properties_handler_registry.register(
822
'bad_argument_prop_handler',
823
bad_argument_prop_handler)
825
self.assertRaises(AttributeError, formatter.show_properties,
828
revision = b.repository.get_revision(b.last_revision())
829
formatter.show_properties(revision, '')
830
self.assertEqualDiff('''custom_prop_name: test_value\n''',
833
log.properties_handler_registry.remove(
834
'bad_argument_prop_handler')
837
class TestLongLogFormatterWithoutMergeRevisions(TestCaseWithoutPropsHandler):
839
def test_long_verbose_log(self):
840
"""Verbose log includes changed files
844
wt = self.make_branch_and_tree('.')
846
self.build_tree(['a'])
848
# XXX: why does a longer nick show up?
849
b.nick = 'test_verbose_log'
850
wt.commit(message='add a',
851
timestamp=1132711707,
853
committer='Lorem Ipsum <test@example.com>')
854
logfile = file('out.tmp', 'w+')
855
formatter = log.LongLogFormatter(to_file=logfile, levels=1)
856
log.show_log(b, formatter, verbose=True)
859
log_contents = logfile.read()
860
self.assertEqualDiff('''\
861
------------------------------------------------------------
863
committer: Lorem Ipsum <test@example.com>
864
branch nick: test_verbose_log
865
timestamp: Wed 2005-11-23 12:08:27 +1000
873
def test_long_verbose_contain_deltas(self):
874
wt = self.make_branch_and_tree('parent')
875
self.build_tree(['parent/f1', 'parent/f2'])
877
wt.commit('first post')
878
self.run_bzr('branch parent child')
879
os.unlink('child/f1')
880
file('child/f2', 'wb').write('hello\n')
881
self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
884
self.run_bzr('merge ../child')
885
wt.commit('merge branch 1')
887
sio = self.make_utf8_encoded_stringio()
888
lf = log.LongLogFormatter(to_file=sio, levels=1)
889
log.show_log(b, lf, verbose=True)
890
the_log = normalize_log(sio.getvalue())
891
self.assertEqualDiff("""\
892
------------------------------------------------------------
894
committer: Lorem Ipsum <test@example.com>
903
------------------------------------------------------------
905
committer: Lorem Ipsum <test@example.com>
913
------------------------------------------------------------
914
Use --levels 0 (or -n0) to see merged revisions.
918
def test_long_trailing_newlines(self):
919
wt = self.make_branch_and_tree('.')
920
b = make_commits_with_trailing_newlines(wt)
921
sio = self.make_utf8_encoded_stringio()
922
lf = log.LongLogFormatter(to_file=sio, levels=1)
924
self.assertEqualDiff("""\
925
------------------------------------------------------------
927
committer: Joe Foo <joe@foo.com>
929
timestamp: Mon 2005-11-21 09:32:56 -0600
931
single line with trailing newline
932
------------------------------------------------------------
934
author: Joe Bar <joe@bar.com>
935
committer: Joe Foo <joe@foo.com>
937
timestamp: Mon 2005-11-21 09:27:22 -0600
942
------------------------------------------------------------
944
committer: Joe Foo <joe@foo.com>
946
timestamp: Mon 2005-11-21 09:24:15 -0600
952
def test_long_author_in_log(self):
953
"""Log includes the author name if it's set in
954
the revision properties
956
wt = self.make_branch_and_tree('.')
958
self.build_tree(['a'])
960
b.nick = 'test_author_log'
961
wt.commit(message='add a',
962
timestamp=1132711707,
964
committer='Lorem Ipsum <test@example.com>',
965
authors=['John Doe <jdoe@example.com>'])
967
formatter = log.LongLogFormatter(to_file=sio, levels=1)
968
log.show_log(b, formatter)
969
self.assertEqualDiff('''\
970
------------------------------------------------------------
972
author: John Doe <jdoe@example.com>
973
committer: Lorem Ipsum <test@example.com>
974
branch nick: test_author_log
975
timestamp: Wed 2005-11-23 12:08:27 +1000
981
def test_long_properties_in_log(self):
982
"""Log includes the custom properties returned by the registered
985
wt = self.make_branch_and_tree('.')
987
self.build_tree(['a'])
989
b.nick = 'test_properties_in_log'
990
wt.commit(message='add a',
991
timestamp=1132711707,
993
committer='Lorem Ipsum <test@example.com>',
994
authors=['John Doe <jdoe@example.com>'])
996
formatter = log.LongLogFormatter(to_file=sio, levels=1)
998
def trivial_custom_prop_handler(revision):
999
return {'test_prop':'test_value'}
1001
log.properties_handler_registry.register(
1002
'trivial_custom_prop_handler',
1003
trivial_custom_prop_handler)
1004
log.show_log(b, formatter)
1006
log.properties_handler_registry.remove(
1007
'trivial_custom_prop_handler')
1008
self.assertEqualDiff('''\
1009
------------------------------------------------------------
1011
test_prop: test_value
1012
author: John Doe <jdoe@example.com>
1013
committer: Lorem Ipsum <test@example.com>
1014
branch nick: test_properties_in_log
1015
timestamp: Wed 2005-11-23 12:08:27 +1000
1022
class TestLineLogFormatter(tests.TestCaseWithTransport):
1024
def test_line_log(self):
1025
"""Line log should show revno
1029
wt = self.make_branch_and_tree('.')
1031
self.build_tree(['a'])
1033
b.nick = 'test-line-log'
1034
wt.commit(message='add a',
1035
timestamp=1132711707,
1037
committer='Line-Log-Formatter Tester <test@line.log>')
1038
logfile = file('out.tmp', 'w+')
1039
formatter = log.LineLogFormatter(to_file=logfile)
1040
log.show_log(b, formatter)
1043
log_contents = logfile.read()
1044
self.assertEqualDiff('1: Line-Log-Formatte... 2005-11-23 add a\n',
1047
def test_trailing_newlines(self):
1048
wt = self.make_branch_and_tree('.')
1049
b = make_commits_with_trailing_newlines(wt)
1050
sio = self.make_utf8_encoded_stringio()
1051
lf = log.LineLogFormatter(to_file=sio)
1053
self.assertEqualDiff("""\
1054
3: Joe Foo 2005-11-21 single line with trailing newline
1055
2: Joe Bar 2005-11-21 multiline
1056
1: Joe Foo 2005-11-21 simple log message
1060
def _prepare_tree_with_merges(self, with_tags=False):
1061
wt = self.make_branch_and_memory_tree('.')
1063
self.addCleanup(wt.unlock)
1065
wt.commit('rev-1', rev_id='rev-1',
1066
timestamp=1132586655, timezone=36000,
1067
committer='Joe Foo <joe@foo.com>')
1068
wt.commit('rev-merged', rev_id='rev-2a',
1069
timestamp=1132586700, timezone=36000,
1070
committer='Joe Foo <joe@foo.com>')
1071
wt.set_parent_ids(['rev-1', 'rev-2a'])
1072
wt.branch.set_last_revision_info(1, 'rev-1')
1073
wt.commit('rev-2', rev_id='rev-2b',
1074
timestamp=1132586800, timezone=36000,
1075
committer='Joe Foo <joe@foo.com>')
1078
branch.tags.set_tag('v0.2', 'rev-2b')
1079
wt.commit('rev-3', rev_id='rev-3',
1080
timestamp=1132586900, timezone=36000,
1081
committer='Jane Foo <jane@foo.com>')
1082
branch.tags.set_tag('v1.0rc1', 'rev-3')
1083
branch.tags.set_tag('v1.0', 'rev-3')
1086
def test_line_log_single_merge_revision(self):
1087
wt = self._prepare_tree_with_merges()
1088
logfile = self.make_utf8_encoded_stringio()
1089
formatter = log.LineLogFormatter(to_file=logfile)
1090
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
1092
rev = revspec.in_history(wtb)
1093
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
1094
self.assertEqualDiff("""\
1095
1.1.1: Joe Foo 2005-11-22 rev-merged
1099
def test_line_log_with_tags(self):
1100
wt = self._prepare_tree_with_merges(with_tags=True)
1101
logfile = self.make_utf8_encoded_stringio()
1102
formatter = log.LineLogFormatter(to_file=logfile)
1103
log.show_log(wt.branch, formatter)
1104
self.assertEqualDiff("""\
1105
3: Jane Foo 2005-11-22 {v1.0, v1.0rc1} rev-3
1106
2: Joe Foo 2005-11-22 [merge] {v0.2} rev-2
1107
1: Joe Foo 2005-11-22 rev-1
1111
class TestLineLogFormatterWithMergeRevisions(tests.TestCaseWithTransport):
1113
def test_line_merge_revs_log(self):
1114
"""Line log should show revno
1118
wt = self.make_branch_and_tree('.')
1120
self.build_tree(['a'])
1122
b.nick = 'test-line-log'
1123
wt.commit(message='add a',
1124
timestamp=1132711707,
1126
committer='Line-Log-Formatter Tester <test@line.log>')
1127
logfile = file('out.tmp', 'w+')
1128
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1129
log.show_log(b, formatter)
1132
log_contents = logfile.read()
1133
self.assertEqualDiff('1: Line-Log-Formatte... 2005-11-23 add a\n',
1136
def test_line_merge_revs_log_single_merge_revision(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
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
1156
rev = revspec.in_history(wtb)
1157
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
1158
self.assertEqualDiff("""\
1159
1.1.1: Joe Foo 2005-11-22 rev-merged
1163
def test_line_merge_revs_log_with_merges(self):
1164
wt = self.make_branch_and_memory_tree('.')
1166
self.addCleanup(wt.unlock)
1168
wt.commit('rev-1', rev_id='rev-1',
1169
timestamp=1132586655, timezone=36000,
1170
committer='Joe Foo <joe@foo.com>')
1171
wt.commit('rev-merged', rev_id='rev-2a',
1172
timestamp=1132586700, timezone=36000,
1173
committer='Joe Foo <joe@foo.com>')
1174
wt.set_parent_ids(['rev-1', 'rev-2a'])
1175
wt.branch.set_last_revision_info(1, 'rev-1')
1176
wt.commit('rev-2', rev_id='rev-2b',
1177
timestamp=1132586800, timezone=36000,
1178
committer='Joe Foo <joe@foo.com>')
1179
logfile = self.make_utf8_encoded_stringio()
1180
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1181
log.show_log(wt.branch, formatter)
1182
self.assertEqualDiff("""\
1183
2: Joe Foo 2005-11-22 [merge] rev-2
1184
1.1.1: Joe Foo 2005-11-22 rev-merged
1185
1: Joe Foo 2005-11-22 rev-1
1189
class TestGetViewRevisions(tests.TestCaseWithTransport):
1191
def make_tree_with_commits(self):
1192
"""Create a tree with well-known revision ids"""
1193
wt = self.make_branch_and_tree('tree1')
1194
wt.commit('commit one', rev_id='1')
1195
wt.commit('commit two', rev_id='2')
1196
wt.commit('commit three', rev_id='3')
1197
mainline_revs = [None, '1', '2', '3']
1198
rev_nos = {'1': 1, '2': 2, '3': 3}
1199
return mainline_revs, rev_nos, wt
1201
def make_tree_with_merges(self):
1202
"""Create a tree with well-known revision ids and a merge"""
1203
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1204
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
1205
tree2.commit('four-a', rev_id='4a')
1206
wt.merge_from_branch(tree2.branch)
1207
wt.commit('four-b', rev_id='4b')
1208
mainline_revs.append('4b')
1211
return mainline_revs, rev_nos, wt
1213
def make_tree_with_many_merges(self):
1214
"""Create a tree with well-known revision ids"""
1215
wt = self.make_branch_and_tree('tree1')
1216
self.build_tree_contents([('tree1/f', '1\n')])
1217
wt.add(['f'], ['f-id'])
1218
wt.commit('commit one', rev_id='1')
1219
wt.commit('commit two', rev_id='2')
1221
tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
1222
self.build_tree_contents([('tree3/f', '1\n2\n3a\n')])
1223
tree3.commit('commit three a', rev_id='3a')
1225
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
1226
tree2.merge_from_branch(tree3.branch)
1227
tree2.commit('commit three b', rev_id='3b')
1229
wt.merge_from_branch(tree2.branch)
1230
wt.commit('commit three c', rev_id='3c')
1231
tree2.commit('four-a', rev_id='4a')
1233
wt.merge_from_branch(tree2.branch)
1234
wt.commit('four-b', rev_id='4b')
1236
mainline_revs = [None, '1', '2', '3c', '4b']
1237
rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
1238
full_rev_nos_for_reference = {
1241
'3a': '2.1.1', #first commit tree 3
1242
'3b': '2.2.1', # first commit tree 2
1243
'3c': '3', #merges 3b to main
1244
'4a': '2.2.2', # second commit tree 2
1245
'4b': '4', # merges 4a to main
1247
return mainline_revs, rev_nos, wt
1249
def test_get_view_revisions_forward(self):
1250
"""Test the get_view_revisions method"""
1251
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1253
self.addCleanup(wt.unlock)
1254
revisions = list(log.get_view_revisions(
1255
mainline_revs, rev_nos, wt.branch, 'forward'))
1256
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
1258
revisions2 = list(log.get_view_revisions(
1259
mainline_revs, rev_nos, wt.branch, 'forward',
1260
include_merges=False))
1261
self.assertEqual(revisions, revisions2)
1263
def test_get_view_revisions_reverse(self):
1264
"""Test the get_view_revisions with reverse"""
1265
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1267
self.addCleanup(wt.unlock)
1268
revisions = list(log.get_view_revisions(
1269
mainline_revs, rev_nos, wt.branch, 'reverse'))
1270
self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
1272
revisions2 = list(log.get_view_revisions(
1273
mainline_revs, rev_nos, wt.branch, 'reverse',
1274
include_merges=False))
1275
self.assertEqual(revisions, revisions2)
1277
def test_get_view_revisions_merge(self):
1278
"""Test get_view_revisions when there are merges"""
1279
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
1281
self.addCleanup(wt.unlock)
1282
revisions = list(log.get_view_revisions(
1283
mainline_revs, rev_nos, wt.branch, 'forward'))
1284
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
1285
('4b', '4', 0), ('4a', '3.1.1', 1)],
1287
revisions = list(log.get_view_revisions(
1288
mainline_revs, rev_nos, wt.branch, 'forward',
1289
include_merges=False))
1290
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
1294
def test_get_view_revisions_merge_reverse(self):
1295
"""Test get_view_revisions in reverse when there are merges"""
1296
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
1298
self.addCleanup(wt.unlock)
1299
revisions = list(log.get_view_revisions(
1300
mainline_revs, rev_nos, wt.branch, 'reverse'))
1301
self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
1302
('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
1304
revisions = list(log.get_view_revisions(
1305
mainline_revs, rev_nos, wt.branch, 'reverse',
1306
include_merges=False))
1307
self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
1311
def test_get_view_revisions_merge2(self):
1312
"""Test get_view_revisions when there are merges"""
1313
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
1315
self.addCleanup(wt.unlock)
1316
revisions = list(log.get_view_revisions(
1317
mainline_revs, rev_nos, wt.branch, 'forward'))
1318
expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
1319
('3a', '2.1.1', 1), ('3b', '2.2.1', 1), ('4b', '4', 0),
1321
self.assertEqual(expected, revisions)
1322
revisions = list(log.get_view_revisions(
1323
mainline_revs, rev_nos, wt.branch, 'forward',
1324
include_merges=False))
1325
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
1330
def test_file_id_for_range(self):
1331
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
1333
self.addCleanup(wt.unlock)
1335
def rev_from_rev_id(revid, branch):
1336
revspec = revisionspec.RevisionSpec.from_string('revid:%s' % revid)
1337
return revspec.in_history(branch)
1339
def view_revs(start_rev, end_rev, file_id, direction):
1340
revs = log.calculate_view_revisions(
1342
start_rev, # start_revision
1343
end_rev, # end_revision
1344
direction, # direction
1345
file_id, # specific_fileid
1346
True, # generate_merge_revisions
1347
True, # allow_single_merge_revision
1351
rev_3a = rev_from_rev_id('3a', wt.branch)
1352
rev_4b = rev_from_rev_id('4b', wt.branch)
1353
self.assertEqual([('3c', '3', 0), ('3a', '2.1.1', 1)],
1354
view_revs(rev_3a, rev_4b, 'f-id', 'reverse'))
1355
# Note: 3c still appears before 3a here because of depth-based sorting
1356
self.assertEqual([('3c', '3', 0), ('3a', '2.1.1', 1)],
1357
view_revs(rev_3a, rev_4b, 'f-id', 'forward'))
1360
class TestGetRevisionsTouchingFileID(tests.TestCaseWithTransport):
1362
def create_tree_with_single_merge(self):
1363
"""Create a branch with a moderate layout.
1365
The revision graph looks like:
1373
In this graph, A introduced files f1 and f2 and f3.
1374
B modifies f1 and f3, and C modifies f2 and f3.
1375
D merges the changes from B and C and resolves the conflict for f3.
1377
# TODO: jam 20070218 This seems like it could really be done
1378
# with make_branch_and_memory_tree() if we could just
1379
# create the content of those files.
1380
# TODO: jam 20070218 Another alternative is that we would really
1381
# like to only create this tree 1 time for all tests that
1382
# use it. Since 'log' only uses the tree in a readonly
1383
# fashion, it seems a shame to regenerate an identical
1384
# tree for each test.
1385
tree = self.make_branch_and_tree('tree')
1387
self.addCleanup(tree.unlock)
1389
self.build_tree_contents([('tree/f1', 'A\n'),
1393
tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
1394
tree.commit('A', rev_id='A')
1396
self.build_tree_contents([('tree/f2', 'A\nC\n'),
1397
('tree/f3', 'A\nC\n'),
1399
tree.commit('C', rev_id='C')
1400
# Revert back to A to build the other history.
1401
tree.set_last_revision('A')
1402
tree.branch.set_last_revision_info(1, 'A')
1403
self.build_tree_contents([('tree/f1', 'A\nB\n'),
1405
('tree/f3', 'A\nB\n'),
1407
tree.commit('B', rev_id='B')
1408
tree.set_parent_ids(['B', 'C'])
1409
self.build_tree_contents([('tree/f1', 'A\nB\n'),
1410
('tree/f2', 'A\nC\n'),
1411
('tree/f3', 'A\nB\nC\n'),
1413
tree.commit('D', rev_id='D')
1415
# Switch to a read lock for this tree.
1416
# We still have an addCleanup(tree.unlock) pending
1421
def check_delta(self, delta, **kw):
1422
"""Check the filenames touched by a delta are as expected.
1424
Caller only have to pass in the list of files for each part, all
1425
unspecified parts are considered empty (and checked as such).
1427
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
1428
# By default we expect an empty list
1429
expected = kw.get(n, [])
1430
# strip out only the path components
1431
got = [x[0] for x in getattr(delta, n)]
1432
self.assertEqual(expected, got)
1434
def test_tree_with_single_merge(self):
1435
"""Make sure the tree layout is correct."""
1436
tree = self.create_tree_with_single_merge()
1437
rev_A_tree = tree.branch.repository.revision_tree('A')
1438
rev_B_tree = tree.branch.repository.revision_tree('B')
1439
rev_C_tree = tree.branch.repository.revision_tree('C')
1440
rev_D_tree = tree.branch.repository.revision_tree('D')
1442
self.check_delta(rev_B_tree.changes_from(rev_A_tree),
1443
modified=['f1', 'f3'])
1445
self.check_delta(rev_C_tree.changes_from(rev_A_tree),
1446
modified=['f2', 'f3'])
1448
self.check_delta(rev_D_tree.changes_from(rev_B_tree),
1449
modified=['f2', 'f3'])
1451
self.check_delta(rev_D_tree.changes_from(rev_C_tree),
1452
modified=['f1', 'f3'])
1454
def assertAllRevisionsForFileID(self, tree, file_id, revisions):
1455
"""Ensure _filter_revisions_touching_file_id returns the right values.
1457
Get the return value from _filter_revisions_touching_file_id and make
1458
sure they are correct.
1460
# The api for _filter_revisions_touching_file_id is a little crazy.
1461
# So we do the setup here.
1462
mainline = tree.branch.revision_history()
1463
mainline.insert(0, None)
1464
revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
1465
view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
1467
actual_revs = log._filter_revisions_touching_file_id(
1470
list(view_revs_iter))
1471
self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
1473
def test_file_id_f1(self):
1474
tree = self.create_tree_with_single_merge()
1475
# f1 should be marked as modified by revisions A and B
1476
self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
1478
def test_file_id_f2(self):
1479
tree = self.create_tree_with_single_merge()
1480
# f2 should be marked as modified by revisions A, C, and D
1481
# because D merged the changes from C.
1482
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1484
def test_file_id_f3(self):
1485
tree = self.create_tree_with_single_merge()
1486
# f3 should be marked as modified by revisions A, B, C, and D
1487
self.assertAllRevisionsForFileID(tree, 'f3-id', ['D', 'C', 'B', 'A'])
1489
def test_file_id_with_ghosts(self):
1490
# This is testing bug #209948, where having a ghost would cause
1491
# _filter_revisions_touching_file_id() to fail.
1492
tree = self.create_tree_with_single_merge()
1493
# We need to add a revision, so switch back to a write-locked tree
1494
# (still a single addCleanup(tree.unlock) pending).
1497
first_parent = tree.last_revision()
1498
tree.set_parent_ids([first_parent, 'ghost-revision-id'])
1499
self.build_tree_contents([('tree/f1', 'A\nB\nXX\n')])
1500
tree.commit('commit with a ghost', rev_id='XX')
1501
self.assertAllRevisionsForFileID(tree, 'f1-id', ['XX', 'B', 'A'])
1502
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1504
def test_unknown_file_id(self):
1505
tree = self.create_tree_with_single_merge()
1506
self.assertAllRevisionsForFileID(tree, 'unknown', [])
1508
def test_empty_branch_unknown_file_id(self):
1509
tree = self.make_branch_and_tree('tree')
1510
self.assertAllRevisionsForFileID(tree, 'unknown', [])
1513
class TestShowChangedRevisions(tests.TestCaseWithTransport):
1515
def test_show_changed_revisions_verbose(self):
1516
tree = self.make_branch_and_tree('tree_a')
1517
self.build_tree(['tree_a/foo'])
1519
tree.commit('bar', rev_id='bar-id')
1520
s = self.make_utf8_encoded_stringio()
1521
log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
1522
self.assertContainsRe(s.getvalue(), 'bar')
1523
self.assertNotContainsRe(s.getvalue(), 'foo')
1526
class TestLogFormatter(tests.TestCase):
1528
def test_short_committer(self):
1529
rev = revision.Revision('a-id')
1530
rev.committer = 'John Doe <jdoe@example.com>'
1531
lf = log.LogFormatter(None)
1532
self.assertEqual('John Doe', lf.short_committer(rev))
1533
rev.committer = 'John Smith <jsmith@example.com>'
1534
self.assertEqual('John Smith', lf.short_committer(rev))
1535
rev.committer = 'John Smith'
1536
self.assertEqual('John Smith', lf.short_committer(rev))
1537
rev.committer = 'jsmith@example.com'
1538
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1539
rev.committer = '<jsmith@example.com>'
1540
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1541
rev.committer = 'John Smith jsmith@example.com'
1542
self.assertEqual('John Smith', lf.short_committer(rev))
1544
def test_short_author(self):
1545
rev = revision.Revision('a-id')
1546
rev.committer = 'John Doe <jdoe@example.com>'
1547
lf = log.LogFormatter(None)
1548
self.assertEqual('John Doe', lf.short_author(rev))
1549
rev.properties['author'] = 'John Smith <jsmith@example.com>'
1550
self.assertEqual('John Smith', lf.short_author(rev))
1551
rev.properties['author'] = 'John Smith'
1552
self.assertEqual('John Smith', lf.short_author(rev))
1553
rev.properties['author'] = 'jsmith@example.com'
1554
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1555
rev.properties['author'] = '<jsmith@example.com>'
1556
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1557
rev.properties['author'] = 'John Smith jsmith@example.com'
1558
self.assertEqual('John Smith', lf.short_author(rev))
1559
del rev.properties['author']
1560
rev.properties['authors'] = ('John Smith <jsmith@example.com>\n'
1561
'Jane Rey <jrey@example.com>')
1562
self.assertEqual('John Smith', lf.short_author(rev))
1565
class TestReverseByDepth(tests.TestCase):
1566
"""Test reverse_by_depth behavior.
1568
This is used to present revisions in forward (oldest first) order in a nice
1571
The tests use lighter revision description to ease reading.
1574
def assertReversed(self, forward, backward):
1575
# Transform the descriptions to suit the API: tests use (revno, depth),
1576
# while the API expects (revid, revno, depth)
1577
def complete_revisions(l):
1578
"""Transform the description to suit the API.
1580
Tests use (revno, depth) whil the API expects (revid, revno, depth).
1581
Since the revid is arbitrary, we just duplicate revno
1583
return [ (r, r, d) for r, d in l]
1584
forward = complete_revisions(forward)
1585
backward= complete_revisions(backward)
1586
self.assertEqual(forward, log.reverse_by_depth(backward))
1589
def test_mainline_revisions(self):
1590
self.assertReversed([( '1', 0), ('2', 0)],
1591
[('2', 0), ('1', 0)])
1593
def test_merged_revisions(self):
1594
self.assertReversed([('1', 0), ('2', 0), ('2.2', 1), ('2.1', 1),],
1595
[('2', 0), ('2.1', 1), ('2.2', 1), ('1', 0),])
1596
def test_shifted_merged_revisions(self):
1597
"""Test irregular layout.
1599
Requesting revisions touching a file can produce "holes" in the depths.
1601
self.assertReversed([('1', 0), ('2', 0), ('1.1', 2), ('1.2', 2),],
1602
[('2', 0), ('1.2', 2), ('1.1', 2), ('1', 0),])
1604
def test_merged_without_child_revisions(self):
1605
"""Test irregular layout.
1607
Revision ranges can produce "holes" in the depths.
1609
# When a revision of higher depth doesn't follow one of lower depth, we
1610
# assume a lower depth one is virtually there
1611
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1612
[('4', 4), ('3', 3), ('2', 2), ('1', 2),])
1613
# So we get the same order after reversing below even if the original
1614
# revisions are not in the same order.
1615
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1616
[('3', 3), ('4', 4), ('2', 2), ('1', 2),])
1619
class TestHistoryChange(tests.TestCaseWithTransport):
1621
def setup_a_tree(self):
1622
tree = self.make_branch_and_tree('tree')
1624
self.addCleanup(tree.unlock)
1625
tree.commit('1a', rev_id='1a')
1626
tree.commit('2a', rev_id='2a')
1627
tree.commit('3a', rev_id='3a')
1630
def setup_ab_tree(self):
1631
tree = self.setup_a_tree()
1632
tree.set_last_revision('1a')
1633
tree.branch.set_last_revision_info(1, '1a')
1634
tree.commit('2b', rev_id='2b')
1635
tree.commit('3b', rev_id='3b')
1638
def setup_ac_tree(self):
1639
tree = self.setup_a_tree()
1640
tree.set_last_revision(revision.NULL_REVISION)
1641
tree.branch.set_last_revision_info(0, revision.NULL_REVISION)
1642
tree.commit('1c', rev_id='1c')
1643
tree.commit('2c', rev_id='2c')
1644
tree.commit('3c', rev_id='3c')
1647
def test_all_new(self):
1648
tree = self.setup_ab_tree()
1649
old, new = log.get_history_change('1a', '3a', tree.branch.repository)
1650
self.assertEqual([], old)
1651
self.assertEqual(['2a', '3a'], new)
1653
def test_all_old(self):
1654
tree = self.setup_ab_tree()
1655
old, new = log.get_history_change('3a', '1a', tree.branch.repository)
1656
self.assertEqual([], new)
1657
self.assertEqual(['2a', '3a'], old)
1659
def test_null_old(self):
1660
tree = self.setup_ab_tree()
1661
old, new = log.get_history_change(revision.NULL_REVISION,
1662
'3a', tree.branch.repository)
1663
self.assertEqual([], old)
1664
self.assertEqual(['1a', '2a', '3a'], new)
1666
def test_null_new(self):
1667
tree = self.setup_ab_tree()
1668
old, new = log.get_history_change('3a', revision.NULL_REVISION,
1669
tree.branch.repository)
1670
self.assertEqual([], new)
1671
self.assertEqual(['1a', '2a', '3a'], old)
1673
def test_diverged(self):
1674
tree = self.setup_ab_tree()
1675
old, new = log.get_history_change('3a', '3b', tree.branch.repository)
1676
self.assertEqual(old, ['2a', '3a'])
1677
self.assertEqual(new, ['2b', '3b'])
1679
def test_unrelated(self):
1680
tree = self.setup_ac_tree()
1681
old, new = log.get_history_change('3a', '3c', tree.branch.repository)
1682
self.assertEqual(old, ['1a', '2a', '3a'])
1683
self.assertEqual(new, ['1c', '2c', '3c'])
1685
def test_show_branch_change(self):
1686
tree = self.setup_ab_tree()
1688
log.show_branch_change(tree.branch, s, 3, '3a')
1689
self.assertContainsRe(s.getvalue(),
1690
'[*]{60}\nRemoved Revisions:\n(.|\n)*2a(.|\n)*3a(.|\n)*'
1691
'[*]{60}\n\nAdded Revisions:\n(.|\n)*2b(.|\n)*3b')
1693
def test_show_branch_change_no_change(self):
1694
tree = self.setup_ab_tree()
1696
log.show_branch_change(tree.branch, s, 3, '3b')
1697
self.assertEqual(s.getvalue(),
1698
'Nothing seems to have changed\n')
1700
def test_show_branch_change_no_old(self):
1701
tree = self.setup_ab_tree()
1703
log.show_branch_change(tree.branch, s, 2, '2b')
1704
self.assertContainsRe(s.getvalue(), 'Added Revisions:')
1705
self.assertNotContainsRe(s.getvalue(), 'Removed Revisions:')
1707
def test_show_branch_change_no_new(self):
1708
tree = self.setup_ab_tree()
1709
tree.branch.set_last_revision_info(2, '2b')
1711
log.show_branch_change(tree.branch, s, 3, '3b')
1712
self.assertContainsRe(s.getvalue(), 'Removed Revisions:')
1713
self.assertNotContainsRe(s.getvalue(), 'Added Revisions:')