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
file('child/file2', 'wb').write('hello\n')
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
def normalize_log(log):
223
"""Replaces the variable lines of logs with fixed lines"""
224
author = 'author: Dolor Sit <test@example.com>'
225
committer = 'committer: Lorem Ipsum <test@example.com>'
226
lines = log.splitlines(True)
227
for idx,line in enumerate(lines):
228
stripped_line = line.lstrip()
229
indent = ' ' * (len(line) - len(stripped_line))
230
if stripped_line.startswith('author:'):
231
lines[idx] = indent + author + '\n'
232
elif stripped_line.startswith('committer:'):
233
lines[idx] = indent + committer + '\n'
234
elif stripped_line.startswith('timestamp:'):
235
lines[idx] = indent + 'timestamp: Just now\n'
236
return ''.join(lines)
239
class TestShortLogFormatter(TestCaseWithTransport):
241
def test_trailing_newlines(self):
242
wt = self.make_branch_and_tree('.')
243
b = make_commits_with_trailing_newlines(wt)
244
sio = self.make_utf8_encoded_stringio()
245
lf = ShortLogFormatter(to_file=sio)
247
self.assertEqualDiff(sio.getvalue(), """\
248
3 Joe Foo\t2005-11-21
249
single line with trailing newline
251
2 Joe Bar\t2005-11-21
256
1 Joe Foo\t2005-11-21
262
class TestLongLogFormatter(TestCaseWithTransport):
264
def test_verbose_log(self):
265
"""Verbose log includes changed files
269
wt = self.make_branch_and_tree('.')
271
self.build_tree(['a'])
273
# XXX: why does a longer nick show up?
274
b.nick = 'test_verbose_log'
275
wt.commit(message='add a',
276
timestamp=1132711707,
278
committer='Lorem Ipsum <test@example.com>')
279
logfile = file('out.tmp', 'w+')
280
formatter = LongLogFormatter(to_file=logfile)
281
show_log(b, formatter, verbose=True)
284
log_contents = logfile.read()
285
self.assertEqualDiff(log_contents, '''\
286
------------------------------------------------------------
288
committer: Lorem Ipsum <test@example.com>
289
branch nick: test_verbose_log
290
timestamp: Wed 2005-11-23 12:08:27 +1000
297
def test_merges_are_indented_by_level(self):
298
wt = self.make_branch_and_tree('parent')
299
wt.commit('first post')
300
self.run_bzr('branch parent child')
301
self.run_bzr(['commit', '-m', 'branch 1', '--unchanged', 'child'])
302
self.run_bzr('branch child smallerchild')
303
self.run_bzr(['commit', '-m', 'branch 2', '--unchanged',
306
self.run_bzr('merge ../smallerchild')
307
self.run_bzr(['commit', '-m', 'merge branch 2'])
308
os.chdir('../parent')
309
self.run_bzr('merge ../child')
310
wt.commit('merge branch 1')
312
sio = self.make_utf8_encoded_stringio()
313
lf = LongLogFormatter(to_file=sio)
314
show_log(b, lf, verbose=True)
315
log = normalize_log(sio.getvalue())
316
self.assertEqualDiff(log, """\
317
------------------------------------------------------------
319
committer: Lorem Ipsum <test@example.com>
324
------------------------------------------------------------
326
committer: Lorem Ipsum <test@example.com>
331
------------------------------------------------------------
333
committer: Lorem Ipsum <test@example.com>
334
branch nick: smallerchild
338
------------------------------------------------------------
340
committer: Lorem Ipsum <test@example.com>
345
------------------------------------------------------------
347
committer: Lorem Ipsum <test@example.com>
354
def test_verbose_merge_revisions_contain_deltas(self):
355
wt = self.make_branch_and_tree('parent')
356
self.build_tree(['parent/f1', 'parent/f2'])
358
wt.commit('first post')
359
self.run_bzr('branch parent child')
360
os.unlink('child/f1')
361
file('child/f2', 'wb').write('hello\n')
362
self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
365
self.run_bzr('merge ../child')
366
wt.commit('merge branch 1')
368
sio = self.make_utf8_encoded_stringio()
369
lf = LongLogFormatter(to_file=sio)
370
show_log(b, lf, verbose=True)
371
log = normalize_log(sio.getvalue())
372
self.assertEqualDiff(log, """\
373
------------------------------------------------------------
375
committer: Lorem Ipsum <test@example.com>
384
------------------------------------------------------------
386
committer: Lorem Ipsum <test@example.com>
390
removed f1 and modified f2
395
------------------------------------------------------------
397
committer: Lorem Ipsum <test@example.com>
407
def test_trailing_newlines(self):
408
wt = self.make_branch_and_tree('.')
409
b = make_commits_with_trailing_newlines(wt)
410
sio = self.make_utf8_encoded_stringio()
411
lf = LongLogFormatter(to_file=sio)
413
self.assertEqualDiff(sio.getvalue(), """\
414
------------------------------------------------------------
416
committer: Joe Foo <joe@foo.com>
418
timestamp: Mon 2005-11-21 09:32:56 -0600
420
single line with trailing newline
421
------------------------------------------------------------
423
author: Joe Bar <joe@bar.com>
424
committer: Joe Foo <joe@foo.com>
426
timestamp: Mon 2005-11-21 09:27:22 -0600
431
------------------------------------------------------------
433
committer: Joe Foo <joe@foo.com>
435
timestamp: Mon 2005-11-21 09:24:15 -0600
440
def test_author_in_log(self):
441
"""Log includes the author name if it's set in
442
the revision properties
444
wt = self.make_branch_and_tree('.')
446
self.build_tree(['a'])
448
b.nick = 'test_author_log'
449
wt.commit(message='add a',
450
timestamp=1132711707,
452
committer='Lorem Ipsum <test@example.com>',
453
author='John Doe <jdoe@example.com>')
455
formatter = LongLogFormatter(to_file=sio)
456
show_log(b, formatter)
457
self.assertEqualDiff(sio.getvalue(), '''\
458
------------------------------------------------------------
460
author: John Doe <jdoe@example.com>
461
committer: Lorem Ipsum <test@example.com>
462
branch nick: test_author_log
463
timestamp: Wed 2005-11-23 12:08:27 +1000
470
class TestLineLogFormatter(TestCaseWithTransport):
472
def test_line_log(self):
473
"""Line log should show revno
477
wt = self.make_branch_and_tree('.')
479
self.build_tree(['a'])
481
b.nick = 'test-line-log'
482
wt.commit(message='add a',
483
timestamp=1132711707,
485
committer='Line-Log-Formatter Tester <test@line.log>')
486
logfile = file('out.tmp', 'w+')
487
formatter = LineLogFormatter(to_file=logfile)
488
show_log(b, formatter)
491
log_contents = logfile.read()
492
self.assertEqualDiff(log_contents,
493
'1: Line-Log-Formatte... 2005-11-23 add a\n')
495
def test_short_log_with_merges(self):
496
wt = self.make_branch_and_memory_tree('.')
500
wt.commit('rev-1', rev_id='rev-1',
501
timestamp=1132586655, timezone=36000,
502
committer='Joe Foo <joe@foo.com>')
503
wt.commit('rev-merged', rev_id='rev-2a',
504
timestamp=1132586700, timezone=36000,
505
committer='Joe Foo <joe@foo.com>')
506
wt.set_parent_ids(['rev-1', 'rev-2a'])
507
wt.branch.set_last_revision_info(1, 'rev-1')
508
wt.commit('rev-2', rev_id='rev-2b',
509
timestamp=1132586800, timezone=36000,
510
committer='Joe Foo <joe@foo.com>')
511
logfile = self.make_utf8_encoded_stringio()
512
formatter = ShortLogFormatter(to_file=logfile)
513
show_log(wt.branch, formatter)
515
self.assertEqualDiff(logfile.getvalue(), """\
516
2 Joe Foo\t2005-11-22 [merge]
519
1 Joe Foo\t2005-11-22
526
def test_trailing_newlines(self):
527
wt = self.make_branch_and_tree('.')
528
b = make_commits_with_trailing_newlines(wt)
529
sio = self.make_utf8_encoded_stringio()
530
lf = LineLogFormatter(to_file=sio)
532
self.assertEqualDiff(sio.getvalue(), """\
533
3: Joe Foo 2005-11-21 single line with trailing newline
534
2: Joe Bar 2005-11-21 multiline
535
1: Joe Foo 2005-11-21 simple log message
539
class TestGetViewRevisions(TestCaseWithTransport):
541
def make_tree_with_commits(self):
542
"""Create a tree with well-known revision ids"""
543
wt = self.make_branch_and_tree('tree1')
544
wt.commit('commit one', rev_id='1')
545
wt.commit('commit two', rev_id='2')
546
wt.commit('commit three', rev_id='3')
547
mainline_revs = [None, '1', '2', '3']
548
rev_nos = {'1': 1, '2': 2, '3': 3}
549
return mainline_revs, rev_nos, wt
551
def make_tree_with_merges(self):
552
"""Create a tree with well-known revision ids and a merge"""
553
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
554
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
555
tree2.commit('four-a', rev_id='4a')
556
wt.merge_from_branch(tree2.branch)
557
wt.commit('four-b', rev_id='4b')
558
mainline_revs.append('4b')
561
return mainline_revs, rev_nos, wt
563
def make_tree_with_many_merges(self):
564
"""Create a tree with well-known revision ids"""
565
wt = self.make_branch_and_tree('tree1')
566
wt.commit('commit one', rev_id='1')
567
wt.commit('commit two', rev_id='2')
568
tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
569
tree3.commit('commit three a', rev_id='3a')
570
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
571
tree2.merge_from_branch(tree3.branch)
572
tree2.commit('commit three b', rev_id='3b')
573
wt.merge_from_branch(tree2.branch)
574
wt.commit('commit three c', rev_id='3c')
575
tree2.commit('four-a', rev_id='4a')
576
wt.merge_from_branch(tree2.branch)
577
wt.commit('four-b', rev_id='4b')
578
mainline_revs = [None, '1', '2', '3c', '4b']
579
rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
580
full_rev_nos_for_reference = {
583
'3a': '2.2.1', #first commit tree 3
584
'3b': '2.1.1', # first commit tree 2
585
'3c': '3', #merges 3b to main
586
'4a': '2.1.2', # second commit tree 2
587
'4b': '4', # merges 4a to main
589
return mainline_revs, rev_nos, wt
591
def test_get_view_revisions_forward(self):
592
"""Test the get_view_revisions method"""
593
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
594
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
596
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
598
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
599
'forward', include_merges=False))
600
self.assertEqual(revisions, revisions2)
602
def test_get_view_revisions_reverse(self):
603
"""Test the get_view_revisions with reverse"""
604
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
605
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
607
self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
609
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
610
'reverse', include_merges=False))
611
self.assertEqual(revisions, revisions2)
613
def test_get_view_revisions_merge(self):
614
"""Test get_view_revisions when there are merges"""
615
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
616
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
618
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
619
('4b', '4', 0), ('4a', '3.1.1', 1)],
621
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
622
'forward', include_merges=False))
623
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
627
def test_get_view_revisions_merge_reverse(self):
628
"""Test get_view_revisions in reverse when there are merges"""
629
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
630
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
632
self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
633
('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
635
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
636
'reverse', include_merges=False))
637
self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
641
def test_get_view_revisions_merge2(self):
642
"""Test get_view_revisions when there are merges"""
643
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
644
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
646
expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
647
('3a', '2.2.1', 1), ('3b', '2.1.1', 1), ('4b', '4', 0),
649
self.assertEqual(expected, revisions)
650
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
651
'forward', include_merges=False))
652
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
657
class TestGetRevisionsTouchingFileID(TestCaseWithTransport):
659
def create_tree_with_single_merge(self):
660
"""Create a branch with a moderate layout.
662
The revision graph looks like:
670
In this graph, A introduced files f1 and f2 and f3.
671
B modifies f1 and f3, and C modifies f2 and f3.
672
D merges the changes from B and C and resolves the conflict for f3.
674
# TODO: jam 20070218 This seems like it could really be done
675
# with make_branch_and_memory_tree() if we could just
676
# create the content of those files.
677
# TODO: jam 20070218 Another alternative is that we would really
678
# like to only create this tree 1 time for all tests that
679
# use it. Since 'log' only uses the tree in a readonly
680
# fashion, it seems a shame to regenerate an identical
681
# tree for each test.
682
tree = self.make_branch_and_tree('tree')
684
self.addCleanup(tree.unlock)
686
self.build_tree_contents([('tree/f1', 'A\n'),
690
tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
691
tree.commit('A', rev_id='A')
693
self.build_tree_contents([('tree/f2', 'A\nC\n'),
694
('tree/f3', 'A\nC\n'),
696
tree.commit('C', rev_id='C')
697
# Revert back to A to build the other history.
698
tree.set_last_revision('A')
699
tree.branch.set_last_revision_info(1, 'A')
700
self.build_tree_contents([('tree/f1', 'A\nB\n'),
702
('tree/f3', 'A\nB\n'),
704
tree.commit('B', rev_id='B')
705
tree.set_parent_ids(['B', 'C'])
706
self.build_tree_contents([('tree/f1', 'A\nB\n'),
707
('tree/f2', 'A\nC\n'),
708
('tree/f3', 'A\nB\nC\n'),
710
tree.commit('D', rev_id='D')
712
# Switch to a read lock for this tree.
713
# We still have addCleanup(unlock)
718
def test_tree_with_single_merge(self):
719
"""Make sure the tree layout is correct."""
720
tree = self.create_tree_with_single_merge()
721
rev_A_tree = tree.branch.repository.revision_tree('A')
722
rev_B_tree = tree.branch.repository.revision_tree('B')
724
f1_changed = (u'f1', 'f1-id', 'file', True, False)
725
f2_changed = (u'f2', 'f2-id', 'file', True, False)
726
f3_changed = (u'f3', 'f3-id', 'file', True, False)
728
delta = rev_B_tree.changes_from(rev_A_tree)
729
self.assertEqual([f1_changed, f3_changed], delta.modified)
730
self.assertEqual([], delta.renamed)
731
self.assertEqual([], delta.added)
732
self.assertEqual([], delta.removed)
734
rev_C_tree = tree.branch.repository.revision_tree('C')
735
delta = rev_C_tree.changes_from(rev_A_tree)
736
self.assertEqual([f2_changed, f3_changed], delta.modified)
737
self.assertEqual([], delta.renamed)
738
self.assertEqual([], delta.added)
739
self.assertEqual([], delta.removed)
741
rev_D_tree = tree.branch.repository.revision_tree('D')
742
delta = rev_D_tree.changes_from(rev_B_tree)
743
self.assertEqual([f2_changed, f3_changed], delta.modified)
744
self.assertEqual([], delta.renamed)
745
self.assertEqual([], delta.added)
746
self.assertEqual([], delta.removed)
748
delta = rev_D_tree.changes_from(rev_C_tree)
749
self.assertEqual([f1_changed, f3_changed], delta.modified)
750
self.assertEqual([], delta.renamed)
751
self.assertEqual([], delta.added)
752
self.assertEqual([], delta.removed)
754
def assertAllRevisionsForFileID(self, tree, file_id, revisions):
755
"""Make sure _filter_revisions_touching_file_id returns the right values.
757
Get the return value from _filter_revisions_touching_file_id and make
758
sure they are correct.
760
# The api for _get_revisions_touching_file_id is a little crazy,
761
# So we do the setup here.
762
mainline = tree.branch.revision_history()
763
mainline.insert(0, None)
764
revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
765
view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
767
actual_revs = log._filter_revisions_touching_file_id(
771
list(view_revs_iter))
772
self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
774
def test_file_id_f1(self):
775
tree = self.create_tree_with_single_merge()
776
# f1 should be marked as modified by revisions A and B
777
self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
779
def test_file_id_f2(self):
780
tree = self.create_tree_with_single_merge()
781
# f2 should be marked as modified by revisions A, C, and D
782
# because D merged the changes from C.
783
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
785
def test_file_id_f3(self):
786
tree = self.create_tree_with_single_merge()
787
# f3 should be marked as modified by revisions A, B, C, and D
788
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
791
class TestShowChangedRevisions(TestCaseWithTransport):
793
def test_show_changed_revisions_verbose(self):
794
tree = self.make_branch_and_tree('tree_a')
795
self.build_tree(['tree_a/foo'])
797
tree.commit('bar', rev_id='bar-id')
798
s = self.make_utf8_encoded_stringio()
799
log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
800
self.assertContainsRe(s.getvalue(), 'bar')
801
self.assertNotContainsRe(s.getvalue(), 'foo')
804
class TestLogFormatter(TestCase):
806
def test_short_committer(self):
807
rev = Revision('a-id')
808
rev.committer = 'John Doe <jdoe@example.com>'
809
lf = LogFormatter(None)
810
self.assertEqual('John Doe', lf.short_committer(rev))
812
def test_short_author(self):
813
rev = Revision('a-id')
814
rev.committer = 'John Doe <jdoe@example.com>'
815
lf = LogFormatter(None)
816
self.assertEqual('John Doe', lf.short_author(rev))
817
rev.properties['author'] = 'John Smith <jsmith@example.com>'
818
self.assertEqual('John Smith', lf.short_author(rev))