1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
from cStringIO import StringIO
20
from bzrlib import log
21
from bzrlib.tests import TestCase, TestCaseWithTransport
22
from bzrlib.log import (show_log,
29
from bzrlib.branch import Branch
30
from bzrlib.errors import InvalidRevisionNumber
31
from bzrlib.revision import Revision
34
class LogCatcher(LogFormatter):
35
"""Pull log messages into list rather than displaying them.
37
For ease of testing we save log messages here rather than actually
38
formatting them, so that we can precisely check the result without
39
being too dependent on the exact formatting.
41
We should also test the LogFormatter.
47
super(LogCatcher, self).__init__(to_file=None)
50
def log_revision(self, revision):
51
self.logs.append(revision)
54
class TestShowLog(TestCaseWithTransport):
56
def checkDelta(self, delta, **kw):
57
"""Check the filenames touched by a delta are as expected."""
58
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
59
expected = kw.get(n, [])
60
# strip out only the path components
61
got = [x[0] for x in getattr(delta, n)]
62
self.assertEquals(expected, got)
64
def test_cur_revno(self):
65
wt = self.make_branch_and_tree('.')
69
wt.commit('empty commit')
70
show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
71
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
72
start_revision=2, end_revision=1)
73
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
74
start_revision=1, end_revision=2)
75
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
76
start_revision=0, end_revision=2)
77
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
78
start_revision=1, end_revision=0)
79
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
80
start_revision=-1, end_revision=1)
81
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
82
start_revision=1, end_revision=-1)
84
def test_simple_log(self):
85
eq = self.assertEquals
87
wt = self.make_branch_and_tree('.')
95
wt.commit('empty commit')
97
show_log(b, lf, verbose=True)
99
eq(lf.logs[0].revno, '1')
100
eq(lf.logs[0].rev.message, 'empty commit')
102
self.log('log delta: %r' % d)
105
self.build_tree(['hello'])
107
wt.commit('add one file',
108
committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
109
u'<test@example.com>')
111
lf = self.make_utf8_encoded_stringio()
112
# log using regular thing
113
show_log(b, LongLogFormatter(lf))
115
for l in lf.readlines():
118
# get log as data structure
120
show_log(b, lf, verbose=True)
122
self.log('log entries:')
123
for logentry in lf.logs:
124
self.log('%4s %s' % (logentry.revno, logentry.rev.message))
126
# first one is most recent
127
logentry = lf.logs[0]
128
eq(logentry.revno, '2')
129
eq(logentry.rev.message, 'add one file')
131
self.log('log 2 delta: %r' % d)
132
self.checkDelta(d, added=['hello'])
134
# commit a log message with control characters
135
msg = "All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
136
self.log("original commit message: %r", msg)
139
show_log(b, lf, verbose=True)
140
committed_msg = lf.logs[0].rev.message
141
self.log("escaped commit message: %r", committed_msg)
142
self.assert_(msg != committed_msg)
143
self.assert_(len(committed_msg) > len(msg))
145
# Check that log message with only XML-valid characters isn't
146
# escaped. As ElementTree apparently does some kind of
147
# newline conversion, neither LF (\x0A) nor CR (\x0D) are
148
# included in the test commit message, even though they are
149
# valid XML 1.0 characters.
150
msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
151
self.log("original commit message: %r", msg)
154
show_log(b, lf, verbose=True)
155
committed_msg = lf.logs[0].rev.message
156
self.log("escaped commit message: %r", committed_msg)
157
self.assert_(msg == committed_msg)
159
def test_deltas_in_merge_revisions(self):
160
"""Check deltas created for both mainline and merge revisions"""
161
eq = self.assertEquals
162
wt = self.make_branch_and_tree('parent')
163
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
166
wt.commit(message='add file1 and file2')
167
self.run_bzr('branch parent child')
168
os.unlink('child/file1')
169
print >> file('child/file2', 'wb'), 'hello'
170
self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
173
self.run_bzr('merge ../child')
174
wt.commit('merge child branch')
178
lf.supports_merge_revisions = True
179
show_log(b, lf, verbose=True)
181
logentry = lf.logs[0]
182
eq(logentry.revno, '2')
183
eq(logentry.rev.message, 'merge child branch')
185
self.checkDelta(d, removed=['file1'], modified=['file2'])
186
logentry = lf.logs[1]
187
eq(logentry.revno, '1.1.1')
188
eq(logentry.rev.message, 'remove file1 and modify file2')
190
self.checkDelta(d, removed=['file1'], modified=['file2'])
191
logentry = lf.logs[2]
192
eq(logentry.revno, '1')
193
eq(logentry.rev.message, 'add file1 and file2')
195
self.checkDelta(d, added=['file1', 'file2'])
198
def make_commits_with_trailing_newlines(wt):
199
"""Helper method for LogFormatter tests"""
202
open('a', 'wb').write('hello moto\n')
204
wt.commit('simple log message', rev_id='a1',
205
timestamp=1132586655.459960938, timezone=-6*3600,
206
committer='Joe Foo <joe@foo.com>')
207
open('b', 'wb').write('goodbye\n')
209
wt.commit('multiline\nlog\nmessage\n', rev_id='a2',
210
timestamp=1132586842.411175966, timezone=-6*3600,
211
committer='Joe Foo <joe@foo.com>',
212
author='Joe Bar <joe@bar.com>')
214
open('c', 'wb').write('just another manic monday\n')
216
wt.commit('single line with trailing newline\n', rev_id='a3',
217
timestamp=1132587176.835228920, timezone=-6*3600,
218
committer = 'Joe Foo <joe@foo.com>')
222
class TestShortLogFormatter(TestCaseWithTransport):
224
def test_trailing_newlines(self):
225
wt = self.make_branch_and_tree('.')
226
b = make_commits_with_trailing_newlines(wt)
227
sio = self.make_utf8_encoded_stringio()
228
lf = ShortLogFormatter(to_file=sio)
230
self.assertEquals(sio.getvalue(), """\
231
3 Joe Foo\t2005-11-21
232
single line with trailing newline
234
2 Joe Bar\t2005-11-21
239
1 Joe Foo\t2005-11-21
245
class TestLongLogFormatter(TestCaseWithTransport):
247
def normalize_log(self,log):
248
"""Replaces the variable lines of logs with fixed lines"""
249
author = 'author: Dolor Sit <test@example.com>'
250
committer = 'committer: Lorem Ipsum <test@example.com>'
251
lines = log.splitlines(True)
252
for idx,line in enumerate(lines):
253
stripped_line = line.lstrip()
254
indent = ' ' * (len(line) - len(stripped_line))
255
if stripped_line.startswith('author:'):
256
lines[idx] = indent + author + '\n'
257
elif stripped_line.startswith('committer:'):
258
lines[idx] = indent + committer + '\n'
259
elif stripped_line.startswith('timestamp:'):
260
lines[idx] = indent + 'timestamp: Just now\n'
261
return ''.join(lines)
263
def test_verbose_log(self):
264
"""Verbose log includes changed files
268
wt = self.make_branch_and_tree('.')
270
self.build_tree(['a'])
272
# XXX: why does a longer nick show up?
273
b.nick = 'test_verbose_log'
274
wt.commit(message='add a',
275
timestamp=1132711707,
277
committer='Lorem Ipsum <test@example.com>')
278
logfile = file('out.tmp', 'w+')
279
formatter = LongLogFormatter(to_file=logfile)
280
show_log(b, formatter, verbose=True)
283
log_contents = logfile.read()
284
self.assertEqualDiff(log_contents, '''\
285
------------------------------------------------------------
287
committer: Lorem Ipsum <test@example.com>
288
branch nick: test_verbose_log
289
timestamp: Wed 2005-11-23 12:08:27 +1000
296
def test_merges_are_indented_by_level(self):
297
wt = self.make_branch_and_tree('parent')
298
wt.commit('first post')
299
self.run_bzr('branch parent child')
300
self.run_bzr(['commit', '-m', 'branch 1', '--unchanged', 'child'])
301
self.run_bzr('branch child smallerchild')
302
self.run_bzr(['commit', '-m', 'branch 2', '--unchanged',
305
self.run_bzr('merge ../smallerchild')
306
self.run_bzr(['commit', '-m', 'merge branch 2'])
307
os.chdir('../parent')
308
self.run_bzr('merge ../child')
309
wt.commit('merge branch 1')
311
sio = self.make_utf8_encoded_stringio()
312
lf = LongLogFormatter(to_file=sio)
313
show_log(b, lf, verbose=True)
314
log = self.normalize_log(sio.getvalue())
315
self.assertEqualDiff("""\
316
------------------------------------------------------------
318
committer: Lorem Ipsum <test@example.com>
323
------------------------------------------------------------
325
committer: Lorem Ipsum <test@example.com>
330
------------------------------------------------------------
332
committer: Lorem Ipsum <test@example.com>
333
branch nick: smallerchild
337
------------------------------------------------------------
339
committer: Lorem Ipsum <test@example.com>
344
------------------------------------------------------------
346
committer: Lorem Ipsum <test@example.com>
353
def test_verbose_merge_revisions_contain_deltas(self):
354
wt = self.make_branch_and_tree('parent')
355
self.build_tree(['parent/f1', 'parent/f2'])
357
wt.commit('first post')
358
self.run_bzr('branch parent child')
359
os.unlink('child/f1')
360
print >> file('child/f2', 'wb'), 'hello'
361
self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
364
self.run_bzr('merge ../child')
365
wt.commit('merge branch 1')
367
sio = self.make_utf8_encoded_stringio()
368
lf = LongLogFormatter(to_file=sio)
369
show_log(b, lf, verbose=True)
370
log = self.normalize_log(sio.getvalue())
371
self.assertEqualDiff("""\
372
------------------------------------------------------------
374
committer: Lorem Ipsum <test@example.com>
383
------------------------------------------------------------
385
committer: Lorem Ipsum <test@example.com>
389
removed f1 and modified f2
394
------------------------------------------------------------
396
committer: Lorem Ipsum <test@example.com>
406
def test_trailing_newlines(self):
407
wt = self.make_branch_and_tree('.')
408
b = make_commits_with_trailing_newlines(wt)
409
sio = self.make_utf8_encoded_stringio()
410
lf = LongLogFormatter(to_file=sio)
412
self.assertEqualDiff(sio.getvalue(), """\
413
------------------------------------------------------------
415
committer: Joe Foo <joe@foo.com>
417
timestamp: Mon 2005-11-21 09:32:56 -0600
419
single line with trailing newline
420
------------------------------------------------------------
422
author: Joe Bar <joe@bar.com>
423
committer: Joe Foo <joe@foo.com>
425
timestamp: Mon 2005-11-21 09:27:22 -0600
430
------------------------------------------------------------
432
committer: Joe Foo <joe@foo.com>
434
timestamp: Mon 2005-11-21 09:24:15 -0600
439
def test_author_in_log(self):
440
"""Log includes the author name if it's set in
441
the revision properties
443
wt = self.make_branch_and_tree('.')
445
self.build_tree(['a'])
447
b.nick = 'test_author_log'
448
wt.commit(message='add a',
449
timestamp=1132711707,
451
committer='Lorem Ipsum <test@example.com>',
452
author='John Doe <jdoe@example.com>')
454
formatter = LongLogFormatter(to_file=sio)
455
show_log(b, formatter)
456
self.assertEqualDiff(sio.getvalue(), '''\
457
------------------------------------------------------------
459
author: John Doe <jdoe@example.com>
460
committer: Lorem Ipsum <test@example.com>
461
branch nick: test_author_log
462
timestamp: Wed 2005-11-23 12:08:27 +1000
469
class TestLineLogFormatter(TestCaseWithTransport):
471
def test_line_log(self):
472
"""Line log should show revno
476
wt = self.make_branch_and_tree('.')
478
self.build_tree(['a'])
480
b.nick = 'test-line-log'
481
wt.commit(message='add a',
482
timestamp=1132711707,
484
committer='Line-Log-Formatter Tester <test@line.log>')
485
logfile = file('out.tmp', 'w+')
486
formatter = LineLogFormatter(to_file=logfile)
487
show_log(b, formatter)
490
log_contents = logfile.read()
491
self.assertEqualDiff(log_contents, '1: Line-Log-Formatte... 2005-11-23 add a\n')
493
def test_short_log_with_merges(self):
494
wt = self.make_branch_and_memory_tree('.')
498
wt.commit('rev-1', rev_id='rev-1',
499
timestamp=1132586655, timezone=36000,
500
committer='Joe Foo <joe@foo.com>')
501
wt.commit('rev-merged', rev_id='rev-2a',
502
timestamp=1132586700, timezone=36000,
503
committer='Joe Foo <joe@foo.com>')
504
wt.set_parent_ids(['rev-1', 'rev-2a'])
505
wt.branch.set_last_revision_info(1, 'rev-1')
506
wt.commit('rev-2', rev_id='rev-2b',
507
timestamp=1132586800, timezone=36000,
508
committer='Joe Foo <joe@foo.com>')
509
logfile = self.make_utf8_encoded_stringio()
510
formatter = ShortLogFormatter(to_file=logfile)
511
show_log(wt.branch, formatter)
513
self.assertEqualDiff("""\
514
2 Joe Foo\t2005-11-22 [merge]
517
1 Joe Foo\t2005-11-22
520
""", logfile.getvalue())
524
def test_trailing_newlines(self):
525
wt = self.make_branch_and_tree('.')
526
b = make_commits_with_trailing_newlines(wt)
527
sio = self.make_utf8_encoded_stringio()
528
lf = LineLogFormatter(to_file=sio)
530
self.assertEqualDiff(sio.getvalue(), """\
531
3: Joe Foo 2005-11-21 single line with trailing newline
532
2: Joe Bar 2005-11-21 multiline
533
1: Joe Foo 2005-11-21 simple log message
537
class TestGetViewRevisions(TestCaseWithTransport):
539
def make_tree_with_commits(self):
540
"""Create a tree with well-known revision ids"""
541
wt = self.make_branch_and_tree('tree1')
542
wt.commit('commit one', rev_id='1')
543
wt.commit('commit two', rev_id='2')
544
wt.commit('commit three', rev_id='3')
545
mainline_revs = [None, '1', '2', '3']
546
rev_nos = {'1': 1, '2': 2, '3': 3}
547
return mainline_revs, rev_nos, wt
549
def make_tree_with_merges(self):
550
"""Create a tree with well-known revision ids and a merge"""
551
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
552
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
553
tree2.commit('four-a', rev_id='4a')
554
wt.merge_from_branch(tree2.branch)
555
wt.commit('four-b', rev_id='4b')
556
mainline_revs.append('4b')
559
return mainline_revs, rev_nos, wt
561
def make_tree_with_many_merges(self):
562
"""Create a tree with well-known revision ids"""
563
wt = self.make_branch_and_tree('tree1')
564
wt.commit('commit one', rev_id='1')
565
wt.commit('commit two', rev_id='2')
566
tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
567
tree3.commit('commit three a', rev_id='3a')
568
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
569
tree2.merge_from_branch(tree3.branch)
570
tree2.commit('commit three b', rev_id='3b')
571
wt.merge_from_branch(tree2.branch)
572
wt.commit('commit three c', rev_id='3c')
573
tree2.commit('four-a', rev_id='4a')
574
wt.merge_from_branch(tree2.branch)
575
wt.commit('four-b', rev_id='4b')
576
mainline_revs = [None, '1', '2', '3c', '4b']
577
rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
578
full_rev_nos_for_reference = {
581
'3a': '2.2.1', #first commit tree 3
582
'3b': '2.1.1', # first commit tree 2
583
'3c': '3', #merges 3b to main
584
'4a': '2.1.2', # second commit tree 2
585
'4b': '4', # merges 4a to main
587
return mainline_revs, rev_nos, wt
589
def test_get_view_revisions_forward(self):
590
"""Test the get_view_revisions method"""
591
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
592
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
594
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
596
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
597
'forward', include_merges=False))
598
self.assertEqual(revisions, revisions2)
600
def test_get_view_revisions_reverse(self):
601
"""Test the get_view_revisions with reverse"""
602
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
603
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
605
self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
607
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
608
'reverse', include_merges=False))
609
self.assertEqual(revisions, revisions2)
611
def test_get_view_revisions_merge(self):
612
"""Test get_view_revisions when there are merges"""
613
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
614
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
616
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
617
('4b', '4', 0), ('4a', '3.1.1', 1)],
619
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
620
'forward', include_merges=False))
621
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
625
def test_get_view_revisions_merge_reverse(self):
626
"""Test get_view_revisions in reverse when there are merges"""
627
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
628
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
630
self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
631
('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
633
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
634
'reverse', include_merges=False))
635
self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
639
def test_get_view_revisions_merge2(self):
640
"""Test get_view_revisions when there are merges"""
641
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
642
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
644
expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
645
('3a', '2.2.1', 1), ('3b', '2.1.1', 1), ('4b', '4', 0),
647
self.assertEqual(expected, revisions)
648
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
649
'forward', include_merges=False))
650
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
655
class TestGetRevisionsTouchingFileID(TestCaseWithTransport):
657
def create_tree_with_single_merge(self):
658
"""Create a branch with a moderate layout.
660
The revision graph looks like:
668
In this graph, A introduced files f1 and f2 and f3.
669
B modifies f1 and f3, and C modifies f2 and f3.
670
D merges the changes from B and C and resolves the conflict for f3.
672
# TODO: jam 20070218 This seems like it could really be done
673
# with make_branch_and_memory_tree() if we could just
674
# create the content of those files.
675
# TODO: jam 20070218 Another alternative is that we would really
676
# like to only create this tree 1 time for all tests that
677
# use it. Since 'log' only uses the tree in a readonly
678
# fashion, it seems a shame to regenerate an identical
679
# tree for each test.
680
tree = self.make_branch_and_tree('tree')
682
self.addCleanup(tree.unlock)
684
self.build_tree_contents([('tree/f1', 'A\n'),
688
tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
689
tree.commit('A', rev_id='A')
691
self.build_tree_contents([('tree/f2', 'A\nC\n'),
692
('tree/f3', 'A\nC\n'),
694
tree.commit('C', rev_id='C')
695
# Revert back to A to build the other history.
696
tree.set_last_revision('A')
697
tree.branch.set_last_revision_info(1, 'A')
698
self.build_tree_contents([('tree/f1', 'A\nB\n'),
700
('tree/f3', 'A\nB\n'),
702
tree.commit('B', rev_id='B')
703
tree.set_parent_ids(['B', 'C'])
704
self.build_tree_contents([('tree/f1', 'A\nB\n'),
705
('tree/f2', 'A\nC\n'),
706
('tree/f3', 'A\nB\nC\n'),
708
tree.commit('D', rev_id='D')
710
# Switch to a read lock for this tree.
711
# We still have addCleanup(unlock)
716
def test_tree_with_single_merge(self):
717
"""Make sure the tree layout is correct."""
718
tree = self.create_tree_with_single_merge()
719
rev_A_tree = tree.branch.repository.revision_tree('A')
720
rev_B_tree = tree.branch.repository.revision_tree('B')
722
f1_changed = (u'f1', 'f1-id', 'file', True, False)
723
f2_changed = (u'f2', 'f2-id', 'file', True, False)
724
f3_changed = (u'f3', 'f3-id', 'file', True, False)
726
delta = rev_B_tree.changes_from(rev_A_tree)
727
self.assertEqual([f1_changed, f3_changed], delta.modified)
728
self.assertEqual([], delta.renamed)
729
self.assertEqual([], delta.added)
730
self.assertEqual([], delta.removed)
732
rev_C_tree = tree.branch.repository.revision_tree('C')
733
delta = rev_C_tree.changes_from(rev_A_tree)
734
self.assertEqual([f2_changed, f3_changed], delta.modified)
735
self.assertEqual([], delta.renamed)
736
self.assertEqual([], delta.added)
737
self.assertEqual([], delta.removed)
739
rev_D_tree = tree.branch.repository.revision_tree('D')
740
delta = rev_D_tree.changes_from(rev_B_tree)
741
self.assertEqual([f2_changed, f3_changed], delta.modified)
742
self.assertEqual([], delta.renamed)
743
self.assertEqual([], delta.added)
744
self.assertEqual([], delta.removed)
746
delta = rev_D_tree.changes_from(rev_C_tree)
747
self.assertEqual([f1_changed, f3_changed], delta.modified)
748
self.assertEqual([], delta.renamed)
749
self.assertEqual([], delta.added)
750
self.assertEqual([], delta.removed)
752
def assertAllRevisionsForFileID(self, tree, file_id, revisions):
753
"""Make sure _filter_revisions_touching_file_id returns the right values.
755
Get the return value from _filter_revisions_touching_file_id and make
756
sure they are correct.
758
# The api for _get_revisions_touching_file_id is a little crazy,
759
# So we do the setup here.
760
mainline = tree.branch.revision_history()
761
mainline.insert(0, None)
762
revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
763
view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
765
actual_revs = log._filter_revisions_touching_file_id(
769
list(view_revs_iter))
770
self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
772
def test_file_id_f1(self):
773
tree = self.create_tree_with_single_merge()
774
# f1 should be marked as modified by revisions A and B
775
self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
777
def test_file_id_f2(self):
778
tree = self.create_tree_with_single_merge()
779
# f2 should be marked as modified by revisions A, C, and D
780
# because D merged the changes from C.
781
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
783
def test_file_id_f3(self):
784
tree = self.create_tree_with_single_merge()
785
# f3 should be marked as modified by revisions A, B, C, and D
786
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
789
class TestShowChangedRevisions(TestCaseWithTransport):
791
def test_show_changed_revisions_verbose(self):
792
tree = self.make_branch_and_tree('tree_a')
793
self.build_tree(['tree_a/foo'])
795
tree.commit('bar', rev_id='bar-id')
796
s = self.make_utf8_encoded_stringio()
797
log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
798
self.assertContainsRe(s.getvalue(), 'bar')
799
self.assertNotContainsRe(s.getvalue(), 'foo')
802
class TestLogFormatter(TestCase):
804
def test_short_committer(self):
805
rev = Revision('a-id')
806
rev.committer = 'John Doe <jdoe@example.com>'
807
lf = LogFormatter(None)
808
self.assertEqual('John Doe', lf.short_committer(rev))
810
def test_short_author(self):
811
rev = Revision('a-id')
812
rev.committer = 'John Doe <jdoe@example.com>'
813
lf = LogFormatter(None)
814
self.assertEqual('John Doe', lf.short_author(rev))
815
rev.properties['author'] = 'John Smith <jsmith@example.com>'
816
self.assertEqual('John Smith', lf.short_author(rev))