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 TestCaseWithTransport
22
from bzrlib.log import (show_log,
29
from bzrlib.branch import Branch
30
from bzrlib.errors import InvalidRevisionNumber
33
class LogCatcher(LogFormatter):
34
"""Pull log messages into list rather than displaying them.
36
For ease of testing we save log messages here rather than actually
37
formatting them, so that we can precisely check the result without
38
being too dependent on the exact formatting.
40
We should also test the LogFormatter.
46
super(LogCatcher, self).__init__(to_file=None)
49
def log_revision(self, revision):
50
self.logs.append(revision)
53
class TestShowLog(TestCaseWithTransport):
55
def checkDelta(self, delta, **kw):
56
"""Check the filenames touched by a delta are as expected."""
57
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
58
expected = kw.get(n, [])
59
# strip out only the path components
60
got = [x[0] for x in getattr(delta, n)]
61
self.assertEquals(expected, got)
63
def test_cur_revno(self):
64
wt = self.make_branch_and_tree('.')
68
wt.commit('empty commit')
69
show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
70
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
71
start_revision=2, end_revision=1)
72
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
73
start_revision=1, end_revision=2)
74
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
75
start_revision=0, end_revision=2)
76
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
77
start_revision=1, end_revision=0)
78
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
79
start_revision=-1, end_revision=1)
80
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
81
start_revision=1, end_revision=-1)
83
def test_simple_log(self):
84
eq = self.assertEquals
86
wt = self.make_branch_and_tree('.')
94
wt.commit('empty commit')
96
show_log(b, lf, verbose=True)
98
eq(lf.logs[0].revno, '1')
99
eq(lf.logs[0].rev.message, 'empty commit')
101
self.log('log delta: %r' % d)
104
self.build_tree(['hello'])
106
wt.commit('add one file')
109
# log using regular thing
110
show_log(b, LongLogFormatter(lf))
112
for l in lf.readlines():
115
# get log as data structure
117
show_log(b, lf, verbose=True)
119
self.log('log entries:')
120
for logentry in lf.logs:
121
self.log('%4s %s' % (logentry.revno, logentry.rev.message))
123
# first one is most recent
124
logentry = lf.logs[0]
125
eq(logentry.revno, '2')
126
eq(logentry.rev.message, 'add one file')
128
self.log('log 2 delta: %r' % d)
129
self.checkDelta(d, added=['hello'])
131
# commit a log message with control characters
132
msg = "All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
133
self.log("original commit message: %r", msg)
136
show_log(b, lf, verbose=True)
137
committed_msg = lf.logs[0].rev.message
138
self.log("escaped commit message: %r", committed_msg)
139
self.assert_(msg != committed_msg)
140
self.assert_(len(committed_msg) > len(msg))
142
# Check that log message with only XML-valid characters isn't
143
# escaped. As ElementTree apparently does some kind of
144
# newline conversion, neither LF (\x0A) nor CR (\x0D) are
145
# included in the test commit message, even though they are
146
# valid XML 1.0 characters.
147
msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
148
self.log("original commit message: %r", msg)
151
show_log(b, lf, verbose=True)
152
committed_msg = lf.logs[0].rev.message
153
self.log("escaped commit message: %r", committed_msg)
154
self.assert_(msg == committed_msg)
156
def test_deltas_in_merge_revisions(self):
157
"""Check deltas created for both mainline and merge revisions"""
158
eq = self.assertEquals
159
wt = self.make_branch_and_tree('parent')
160
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
163
wt.commit(message='add file1 and file2')
164
self.run_bzr('branch parent child')
165
os.unlink('child/file1')
166
print >> file('child/file2', 'wb'), 'hello'
167
self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
170
self.run_bzr('merge ../child')
171
wt.commit('merge child branch')
175
lf.supports_merge_revisions = True
176
show_log(b, lf, verbose=True)
178
logentry = lf.logs[0]
179
eq(logentry.revno, '2')
180
eq(logentry.rev.message, 'merge child branch')
182
self.checkDelta(d, removed=['file1'], modified=['file2'])
183
logentry = lf.logs[1]
184
eq(logentry.revno, '1.1.1')
185
eq(logentry.rev.message, 'remove file1 and modify file2')
187
self.checkDelta(d, removed=['file1'], modified=['file2'])
188
logentry = lf.logs[2]
189
eq(logentry.revno, '1')
190
eq(logentry.rev.message, 'add file1 and file2')
192
self.checkDelta(d, added=['file1', 'file2'])
195
def make_commits_with_trailing_newlines(wt):
196
"""Helper method for LogFormatter tests"""
199
open('a', 'wb').write('hello moto\n')
201
wt.commit('simple log message', rev_id='a1'
202
, timestamp=1132586655.459960938, timezone=-6*3600
203
, committer='Joe Foo <joe@foo.com>')
204
open('b', 'wb').write('goodbye\n')
206
wt.commit('multiline\nlog\nmessage\n', rev_id='a2'
207
, timestamp=1132586842.411175966, timezone=-6*3600
208
, committer='Joe Foo <joe@foo.com>')
210
open('c', 'wb').write('just another manic monday\n')
212
wt.commit('single line with trailing newline\n', rev_id='a3'
213
, timestamp=1132587176.835228920, timezone=-6*3600
214
, committer = 'Joe Foo <joe@foo.com>')
218
class TestShortLogFormatter(TestCaseWithTransport):
220
def test_trailing_newlines(self):
221
wt = self.make_branch_and_tree('.')
222
b = make_commits_with_trailing_newlines(wt)
224
lf = ShortLogFormatter(to_file=sio)
226
self.assertEquals(sio.getvalue(), """\
227
3 Joe Foo\t2005-11-21
228
single line with trailing newline
230
2 Joe Foo\t2005-11-21
235
1 Joe Foo\t2005-11-21
241
class TestLongLogFormatter(TestCaseWithTransport):
243
def normalize_log(self,log):
244
"""Replaces the variable lines of logs with fixed lines"""
245
committer = 'committer: Lorem Ipsum <test@example.com>'
246
lines = log.splitlines(True)
247
for idx,line in enumerate(lines):
248
stripped_line = line.lstrip()
249
indent = ' ' * (len(line) - len(stripped_line))
250
if stripped_line.startswith('committer:'):
251
lines[idx] = indent + committer + '\n'
252
if stripped_line.startswith('timestamp:'):
253
lines[idx] = indent + 'timestamp: Just now\n'
254
return ''.join(lines)
256
def test_verbose_log(self):
257
"""Verbose log includes changed files
261
wt = self.make_branch_and_tree('.')
263
self.build_tree(['a'])
265
# XXX: why does a longer nick show up?
266
b.nick = 'test_verbose_log'
267
wt.commit(message='add a',
268
timestamp=1132711707,
270
committer='Lorem Ipsum <test@example.com>')
271
logfile = file('out.tmp', 'w+')
272
formatter = LongLogFormatter(to_file=logfile)
273
show_log(b, formatter, verbose=True)
276
log_contents = logfile.read()
277
self.assertEqualDiff(log_contents, '''\
278
------------------------------------------------------------
280
committer: Lorem Ipsum <test@example.com>
281
branch nick: test_verbose_log
282
timestamp: Wed 2005-11-23 12:08:27 +1000
289
def test_merges_are_indented_by_level(self):
290
wt = self.make_branch_and_tree('parent')
291
wt.commit('first post')
292
self.run_bzr('branch parent child')
293
self.run_bzr(['commit', '-m', 'branch 1', '--unchanged', 'child'])
294
self.run_bzr('branch child smallerchild')
295
self.run_bzr(['commit', '-m', 'branch 2', '--unchanged',
298
self.run_bzr('merge ../smallerchild')
299
self.run_bzr(['commit', '-m', 'merge branch 2'])
300
os.chdir('../parent')
301
self.run_bzr('merge ../child')
302
wt.commit('merge branch 1')
305
lf = LongLogFormatter(to_file=sio)
306
show_log(b, lf, verbose=True)
307
log = self.normalize_log(sio.getvalue())
308
self.assertEqualDiff("""\
309
------------------------------------------------------------
311
committer: Lorem Ipsum <test@example.com>
316
------------------------------------------------------------
318
committer: Lorem Ipsum <test@example.com>
323
------------------------------------------------------------
325
committer: Lorem Ipsum <test@example.com>
326
branch nick: smallerchild
330
------------------------------------------------------------
332
committer: Lorem Ipsum <test@example.com>
337
------------------------------------------------------------
339
committer: Lorem Ipsum <test@example.com>
346
def test_verbose_merge_revisions_contain_deltas(self):
347
wt = self.make_branch_and_tree('parent')
348
self.build_tree(['parent/f1', 'parent/f2'])
350
wt.commit('first post')
351
self.run_bzr('branch parent child')
352
os.unlink('child/f1')
353
print >> file('child/f2', 'wb'), 'hello'
354
self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
357
self.run_bzr('merge ../child')
358
wt.commit('merge branch 1')
361
lf = LongLogFormatter(to_file=sio)
362
show_log(b, lf, verbose=True)
363
log = self.normalize_log(sio.getvalue())
364
self.assertEqualDiff("""\
365
------------------------------------------------------------
367
committer: Lorem Ipsum <test@example.com>
376
------------------------------------------------------------
378
committer: Lorem Ipsum <test@example.com>
382
removed f1 and modified f2
387
------------------------------------------------------------
389
committer: Lorem Ipsum <test@example.com>
399
def test_trailing_newlines(self):
400
wt = self.make_branch_and_tree('.')
401
b = make_commits_with_trailing_newlines(wt)
403
lf = LongLogFormatter(to_file=sio)
405
self.assertEqualDiff(sio.getvalue(), """\
406
------------------------------------------------------------
408
committer: Joe Foo <joe@foo.com>
410
timestamp: Mon 2005-11-21 09:32:56 -0600
412
single line with trailing newline
413
------------------------------------------------------------
415
committer: Joe Foo <joe@foo.com>
417
timestamp: Mon 2005-11-21 09:27:22 -0600
422
------------------------------------------------------------
424
committer: Joe Foo <joe@foo.com>
426
timestamp: Mon 2005-11-21 09:24:15 -0600
431
def test_author_in_log(self):
432
"""Log includes the author name if it's set in
433
the revision properties
435
wt = self.make_branch_and_tree('.')
437
self.build_tree(['a'])
439
b.nick = 'test_author_log'
440
wt.commit(message='add a',
441
timestamp=1132711707,
443
committer='Lorem Ipsum <test@example.com>',
444
author='John Doe <jdoe@example.com>')
446
formatter = LongLogFormatter(to_file=sio)
447
show_log(b, formatter)
448
self.assertEqualDiff(sio.getvalue(), '''\
449
------------------------------------------------------------
451
committer: Lorem Ipsum <test@example.com>
452
author: John Doe <jdoe@example.com>
453
branch nick: test_author_log
454
timestamp: Wed 2005-11-23 12:08:27 +1000
461
class TestLineLogFormatter(TestCaseWithTransport):
463
def test_line_log(self):
464
"""Line log should show revno
468
wt = self.make_branch_and_tree('.')
470
self.build_tree(['a'])
472
b.nick = 'test-line-log'
473
wt.commit(message='add a',
474
timestamp=1132711707,
476
committer='Line-Log-Formatter Tester <test@line.log>')
477
logfile = file('out.tmp', 'w+')
478
formatter = LineLogFormatter(to_file=logfile)
479
show_log(b, formatter)
482
log_contents = logfile.read()
483
self.assertEqualDiff(log_contents, '1: Line-Log-Formatte... 2005-11-23 add a\n')
485
def test_short_log_with_merges(self):
486
wt = self.make_branch_and_memory_tree('.')
490
wt.commit('rev-1', rev_id='rev-1',
491
timestamp=1132586655, timezone=36000,
492
committer='Joe Foo <joe@foo.com>')
493
wt.commit('rev-merged', rev_id='rev-2a',
494
timestamp=1132586700, timezone=36000,
495
committer='Joe Foo <joe@foo.com>')
496
wt.set_parent_ids(['rev-1', 'rev-2a'])
497
wt.branch.set_last_revision_info(1, 'rev-1')
498
wt.commit('rev-2', rev_id='rev-2b',
499
timestamp=1132586800, timezone=36000,
500
committer='Joe Foo <joe@foo.com>')
502
formatter = ShortLogFormatter(to_file=logfile)
503
show_log(wt.branch, formatter)
505
self.assertEqualDiff("""\
506
2 Joe Foo\t2005-11-22 [merge]
509
1 Joe Foo\t2005-11-22
512
""", logfile.getvalue())
516
def test_trailing_newlines(self):
517
wt = self.make_branch_and_tree('.')
518
b = make_commits_with_trailing_newlines(wt)
520
lf = LineLogFormatter(to_file=sio)
522
self.assertEqualDiff(sio.getvalue(), """\
523
3: Joe Foo 2005-11-21 single line with trailing newline
524
2: Joe Foo 2005-11-21 multiline
525
1: Joe Foo 2005-11-21 simple log message
529
class TestGetViewRevisions(TestCaseWithTransport):
531
def make_tree_with_commits(self):
532
"""Create a tree with well-known revision ids"""
533
wt = self.make_branch_and_tree('tree1')
534
wt.commit('commit one', rev_id='1')
535
wt.commit('commit two', rev_id='2')
536
wt.commit('commit three', rev_id='3')
537
mainline_revs = [None, '1', '2', '3']
538
rev_nos = {'1': 1, '2': 2, '3': 3}
539
return mainline_revs, rev_nos, wt
541
def make_tree_with_merges(self):
542
"""Create a tree with well-known revision ids and a merge"""
543
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
544
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
545
tree2.commit('four-a', rev_id='4a')
546
wt.merge_from_branch(tree2.branch)
547
wt.commit('four-b', rev_id='4b')
548
mainline_revs.append('4b')
551
return mainline_revs, rev_nos, wt
553
def make_tree_with_many_merges(self):
554
"""Create a tree with well-known revision ids"""
555
wt = self.make_branch_and_tree('tree1')
556
wt.commit('commit one', rev_id='1')
557
wt.commit('commit two', rev_id='2')
558
tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
559
tree3.commit('commit three a', rev_id='3a')
560
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
561
tree2.merge_from_branch(tree3.branch)
562
tree2.commit('commit three b', rev_id='3b')
563
wt.merge_from_branch(tree2.branch)
564
wt.commit('commit three c', rev_id='3c')
565
tree2.commit('four-a', rev_id='4a')
566
wt.merge_from_branch(tree2.branch)
567
wt.commit('four-b', rev_id='4b')
568
mainline_revs = [None, '1', '2', '3c', '4b']
569
rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
570
full_rev_nos_for_reference = {
573
'3a': '2.2.1', #first commit tree 3
574
'3b': '2.1.1', # first commit tree 2
575
'3c': '3', #merges 3b to main
576
'4a': '2.1.2', # second commit tree 2
577
'4b': '4', # merges 4a to main
579
return mainline_revs, rev_nos, wt
581
def test_get_view_revisions_forward(self):
582
"""Test the get_view_revisions method"""
583
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
584
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
586
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
588
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
589
'forward', include_merges=False))
590
self.assertEqual(revisions, revisions2)
592
def test_get_view_revisions_reverse(self):
593
"""Test the get_view_revisions with reverse"""
594
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
595
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
597
self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
599
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
600
'reverse', include_merges=False))
601
self.assertEqual(revisions, revisions2)
603
def test_get_view_revisions_merge(self):
604
"""Test get_view_revisions when there are merges"""
605
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
606
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
608
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
609
('4b', '4', 0), ('4a', '3.1.1', 1)],
611
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
612
'forward', include_merges=False))
613
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
617
def test_get_view_revisions_merge_reverse(self):
618
"""Test get_view_revisions in reverse when there are merges"""
619
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
620
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
622
self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
623
('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
625
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
626
'reverse', include_merges=False))
627
self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
631
def test_get_view_revisions_merge2(self):
632
"""Test get_view_revisions when there are merges"""
633
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
634
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
636
expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
637
('3a', '2.2.1', 1), ('3b', '2.1.1', 1), ('4b', '4', 0),
639
self.assertEqual(expected, revisions)
640
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
641
'forward', include_merges=False))
642
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
647
class TestGetRevisionsTouchingFileID(TestCaseWithTransport):
649
def create_tree_with_single_merge(self):
650
"""Create a branch with a moderate layout.
652
The revision graph looks like:
660
In this graph, A introduced files f1 and f2 and f3.
661
B modifies f1 and f3, and C modifies f2 and f3.
662
D merges the changes from B and C and resolves the conflict for f3.
664
# TODO: jam 20070218 This seems like it could really be done
665
# with make_branch_and_memory_tree() if we could just
666
# create the content of those files.
667
# TODO: jam 20070218 Another alternative is that we would really
668
# like to only create this tree 1 time for all tests that
669
# use it. Since 'log' only uses the tree in a readonly
670
# fashion, it seems a shame to regenerate an identical
671
# tree for each test.
672
tree = self.make_branch_and_tree('tree')
674
self.addCleanup(tree.unlock)
676
self.build_tree_contents([('tree/f1', 'A\n'),
680
tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
681
tree.commit('A', rev_id='A')
683
self.build_tree_contents([('tree/f2', 'A\nC\n'),
684
('tree/f3', 'A\nC\n'),
686
tree.commit('C', rev_id='C')
687
# Revert back to A to build the other history.
688
tree.set_last_revision('A')
689
tree.branch.set_last_revision_info(1, 'A')
690
self.build_tree_contents([('tree/f1', 'A\nB\n'),
692
('tree/f3', 'A\nB\n'),
694
tree.commit('B', rev_id='B')
695
tree.set_parent_ids(['B', 'C'])
696
self.build_tree_contents([('tree/f1', 'A\nB\n'),
697
('tree/f2', 'A\nC\n'),
698
('tree/f3', 'A\nB\nC\n'),
700
tree.commit('D', rev_id='D')
702
# Switch to a read lock for this tree.
703
# We still have addCleanup(unlock)
708
def test_tree_with_single_merge(self):
709
"""Make sure the tree layout is correct."""
710
tree = self.create_tree_with_single_merge()
711
rev_A_tree = tree.branch.repository.revision_tree('A')
712
rev_B_tree = tree.branch.repository.revision_tree('B')
714
f1_changed = (u'f1', 'f1-id', 'file', True, False)
715
f2_changed = (u'f2', 'f2-id', 'file', True, False)
716
f3_changed = (u'f3', 'f3-id', 'file', True, False)
718
delta = rev_B_tree.changes_from(rev_A_tree)
719
self.assertEqual([f1_changed, f3_changed], delta.modified)
720
self.assertEqual([], delta.renamed)
721
self.assertEqual([], delta.added)
722
self.assertEqual([], delta.removed)
724
rev_C_tree = tree.branch.repository.revision_tree('C')
725
delta = rev_C_tree.changes_from(rev_A_tree)
726
self.assertEqual([f2_changed, f3_changed], delta.modified)
727
self.assertEqual([], delta.renamed)
728
self.assertEqual([], delta.added)
729
self.assertEqual([], delta.removed)
731
rev_D_tree = tree.branch.repository.revision_tree('D')
732
delta = rev_D_tree.changes_from(rev_B_tree)
733
self.assertEqual([f2_changed, f3_changed], delta.modified)
734
self.assertEqual([], delta.renamed)
735
self.assertEqual([], delta.added)
736
self.assertEqual([], delta.removed)
738
delta = rev_D_tree.changes_from(rev_C_tree)
739
self.assertEqual([f1_changed, f3_changed], delta.modified)
740
self.assertEqual([], delta.renamed)
741
self.assertEqual([], delta.added)
742
self.assertEqual([], delta.removed)
744
def assertAllRevisionsForFileID(self, tree, file_id, revisions):
745
"""Make sure _filter_revisions_touching_file_id returns the right values.
747
Get the return value from _filter_revisions_touching_file_id and make
748
sure they are correct.
750
# The api for _get_revisions_touching_file_id is a little crazy,
751
# So we do the setup here.
752
mainline = tree.branch.revision_history()
753
mainline.insert(0, None)
754
revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
755
view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
757
actual_revs = log._filter_revisions_touching_file_id(
761
list(view_revs_iter))
762
self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
764
def test_file_id_f1(self):
765
tree = self.create_tree_with_single_merge()
766
# f1 should be marked as modified by revisions A and B
767
self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
769
def test_file_id_f2(self):
770
tree = self.create_tree_with_single_merge()
771
# f2 should be marked as modified by revisions A, C, and D
772
# because D merged the changes from C.
773
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
775
def test_file_id_f3(self):
776
tree = self.create_tree_with_single_merge()
777
# f3 should be marked as modified by revisions A, B, C, and D
778
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
781
class TestShowChangedRevisions(TestCaseWithTransport):
783
def test_show_changed_revisions_verbose(self):
784
tree = self.make_branch_and_tree('tree_a')
785
self.build_tree(['tree_a/foo'])
787
tree.commit('bar', rev_id='bar-id')
789
log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
790
self.assertContainsRe(s.getvalue(), 'bar')
791
self.assertNotContainsRe(s.getvalue(), 'foo')