1
# Copyright (C) 2006-2010 Canonical Ltd
1
# Copyright (C) 2005 by Canonical Ltd
2
# -*- coding: utf-8 -*-
3
4
# This program is free software; you can redistribute it and/or modify
4
5
# it under the terms of the GNU General Public License as published by
5
6
# the Free Software Foundation; either version 2 of the License, or
6
7
# (at your option) any later version.
8
9
# This program is distributed in the hope that it will be useful,
9
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
12
# GNU General Public License for more details.
13
14
# You should have received a copy of the GNU General Public License
14
15
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
19
"""Black-box tests for bzr log."""
20
from itertools import izip
30
from bzrlib.tests import (
35
class TestLog(tests.TestCaseWithTransport, test_log.TestLogMixin):
37
def make_minimal_branch(self, path='.', format=None):
38
tree = self.make_branch_and_tree(path, format=format)
39
self.build_tree([path + '/hello.txt'])
41
tree.commit(message='message1')
44
def make_linear_branch(self, path='.', format=None):
45
tree = self.make_branch_and_tree(path, format=format)
47
[path + '/hello.txt', path + '/goodbye.txt', path + '/meep.txt'])
49
tree.commit(message='message1')
50
tree.add('goodbye.txt')
51
tree.commit(message='message2')
53
tree.commit(message='message3')
56
def make_merged_branch(self, path='.', format=None):
57
tree = self.make_linear_branch(path, format)
58
tree2 = tree.bzrdir.sprout('tree2',
59
revision_id=tree.branch.get_rev_id(1)).open_workingtree()
60
tree2.commit(message='tree2 message2')
61
tree2.commit(message='tree2 message3')
62
tree.merge_from_branch(tree2.branch)
63
tree.commit(message='merge')
67
class TestLogWithLogCatcher(TestLog):
70
super(TestLogWithLogCatcher, self).setUp()
71
# Capture log formatter creations
72
class MyLogFormatter(test_log.LogCatcher):
74
def __new__(klass, *args, **kwargs):
75
self.log_catcher = test_log.LogCatcher(*args, **kwargs)
76
# Always return our own log formatter
77
return self.log_catcher
80
# Always return our own log formatter class hijacking the
81
# default behavior (which requires setting up a config
84
self.overrideAttr(log.log_formatter_registry, 'get_default', getme)
86
def get_captured_revisions(self):
87
return self.log_catcher.revisions
89
def assertLogRevnos(self, args, expected_revnos, working_dir='.'):
90
self.run_bzr(['log'] + args, working_dir=working_dir)
91
self.assertEqual(expected_revnos,
92
[r.revno for r in self.get_captured_revisions()])
94
def assertLogRevnosAndDepths(self, args, expected_revnos_and_depths,
96
self.run_bzr(['log'] + args, working_dir=working_dir)
97
self.assertEqual(expected_revnos_and_depths,
98
[(r.revno, r.merge_depth)
99
for r in self.get_captured_revisions()])
102
class TestLogRevSpecs(TestLogWithLogCatcher):
104
def test_log_no_revspec(self):
105
self.make_linear_branch()
106
self.assertLogRevnos([], ['3', '2', '1'])
24
from bzrlib.tests.blackbox import ExternalBase
25
from bzrlib.tests import TestCaseInTempDir
28
class TestLog(ExternalBase):
32
self.build_tree(['hello.txt', 'goodbye.txt', 'meep.txt'])
33
self.runbzr("add hello.txt")
34
self.runbzr("commit -m message1 hello.txt")
35
self.runbzr("add goodbye.txt")
36
self.runbzr("commit -m message2 goodbye.txt")
37
self.runbzr("add meep.txt")
38
self.runbzr("commit -m message3 meep.txt")
39
self.full_log = self.runbzr("log")[0]
108
41
def test_log_null_end_revspec(self):
109
self.make_linear_branch()
110
self.assertLogRevnos(['-r1..'], ['3', '2', '1'])
43
self.assertTrue('revno: 1\n' in self.full_log)
44
self.assertTrue('revno: 2\n' in self.full_log)
45
self.assertTrue('revno: 3\n' in self.full_log)
46
self.assertTrue('message:\n message1\n' in self.full_log)
47
self.assertTrue('message:\n message2\n' in self.full_log)
48
self.assertTrue('message:\n message3\n' in self.full_log)
50
log = self.runbzr("log -r 1..")[0]
51
self.assertEquals(log, self.full_log)
112
53
def test_log_null_begin_revspec(self):
113
self.make_linear_branch()
114
self.assertLogRevnos(['-r..3'], ['3', '2', '1'])
55
log = self.runbzr("log -r ..3")[0]
56
self.assertEquals(self.full_log, log)
116
58
def test_log_null_both_revspecs(self):
117
self.make_linear_branch()
118
self.assertLogRevnos(['-r..'], ['3', '2', '1'])
60
log = self.runbzr("log -r ..")[0]
61
self.assertEquals(self.full_log, log)
120
63
def test_log_negative_begin_revspec_full_log(self):
121
self.make_linear_branch()
122
self.assertLogRevnos(['-r-3..'], ['3', '2', '1'])
65
log = self.runbzr("log -r -3..")[0]
66
self.assertEquals(self.full_log, log)
124
68
def test_log_negative_both_revspec_full_log(self):
125
self.make_linear_branch()
126
self.assertLogRevnos(['-r-3..-1'], ['3', '2', '1'])
70
log = self.runbzr("log -r -3..-1")[0]
71
self.assertEquals(self.full_log, log)
128
73
def test_log_negative_both_revspec_partial(self):
129
self.make_linear_branch()
130
self.assertLogRevnos(['-r-3..-2'], ['2', '1'])
75
log = self.runbzr("log -r -3..-2")[0]
76
self.assertTrue('revno: 1\n' in log)
77
self.assertTrue('revno: 2\n' in log)
78
self.assertTrue('revno: 3\n' not in log)
132
80
def test_log_negative_begin_revspec(self):
133
self.make_linear_branch()
134
self.assertLogRevnos(['-r-2..'], ['3', '2'])
136
def test_log_positive_revspecs(self):
137
self.make_linear_branch()
138
self.assertLogRevnos(['-r1..3'], ['3', '2', '1'])
140
def test_log_dotted_revspecs(self):
141
self.make_merged_branch()
142
self.assertLogRevnos(['-n0', '-r1..1.1.1'], ['1.1.1', '1'])
144
def test_log_limit(self):
145
tree = self.make_branch_and_tree('.')
146
# We want more commits than our batch size starts at
147
for pos in range(10):
148
tree.commit("%s" % pos)
149
self.assertLogRevnos(['--limit', '2'], ['10', '9'])
151
def test_log_limit_short(self):
152
self.make_linear_branch()
153
self.assertLogRevnos(['-l', '2'], ['3', '2'])
155
def test_log_change_revno(self):
156
self.make_linear_branch()
157
self.assertLogRevnos(['-c1'], ['1'])
159
def test_branch_revspec(self):
160
foo = self.make_branch_and_tree('foo')
161
bar = self.make_branch_and_tree('bar')
162
self.build_tree(['foo/foo.txt', 'bar/bar.txt'])
165
foo.commit(message='foo')
166
bar.commit(message='bar')
167
self.run_bzr('log -r branch:../bar', working_dir='foo')
168
self.assertEqual([bar.branch.get_rev_id(1)],
170
for r in self.get_captured_revisions()])
173
class TestLogExcludeCommonAncestry(TestLogWithLogCatcher):
175
def test_exclude_common_ancestry_simple_revnos(self):
176
self.make_linear_branch()
177
self.assertLogRevnos(['-r1..3', '--exclude-common-ancestry'],
181
class TestLogMergedLinearAncestry(TestLogWithLogCatcher):
184
super(TestLogMergedLinearAncestry, self).setUp()
185
# FIXME: Using a MemoryTree would be even better here (but until we
186
# stop calling run_bzr, there is no point) --vila 100118.
187
builder = branchbuilder.BranchBuilder(self.get_transport())
188
builder.start_series()
202
builder.build_snapshot('1', None, [
203
('add', ('', 'root-id', 'directory', ''))])
204
builder.build_snapshot('2', ['1'], [])
206
builder.build_snapshot('1.1.1', ['1'], [])
207
# merge branch into mainline
208
builder.build_snapshot('3', ['2', '1.1.1'], [])
209
# new commits in branch
210
builder.build_snapshot('1.1.2', ['1.1.1'], [])
211
builder.build_snapshot('1.1.3', ['1.1.2'], [])
212
# merge branch into mainline
213
builder.build_snapshot('4', ['3', '1.1.3'], [])
214
# merge mainline into branch
215
builder.build_snapshot('1.1.4', ['1.1.3', '4'], [])
216
# merge branch into mainline
217
builder.build_snapshot('5', ['4', '1.1.4'], [])
218
builder.finish_series()
221
self.assertLogRevnos(['-n0', '-r1.1.1..1.1.4'],
222
['1.1.4', '4', '1.1.3', '1.1.2', '3', '1.1.1'])
223
def test_n0_forward(self):
224
self.assertLogRevnos(['-n0', '-r1.1.1..1.1.4', '--forward'],
225
['3', '1.1.1', '4', '1.1.2', '1.1.3', '1.1.4'])
228
# starting from 1.1.4 we follow the left-hand ancestry
229
self.assertLogRevnos(['-n1', '-r1.1.1..1.1.4'],
230
['1.1.4', '1.1.3', '1.1.2', '1.1.1'])
232
def test_n1_forward(self):
233
self.assertLogRevnos(['-n1', '-r1.1.1..1.1.4', '--forward'],
234
['1.1.1', '1.1.2', '1.1.3', '1.1.4'])
237
class Test_GenerateAllRevisions(TestLogWithLogCatcher):
240
super(Test_GenerateAllRevisions, self).setUp()
241
builder = self.make_branch_with_many_merges()
242
b = builder.get_branch()
244
self.addCleanup(b.unlock)
247
def make_branch_with_many_merges(self, path='.', format=None):
248
builder = branchbuilder.BranchBuilder(self.get_transport())
249
builder.start_series()
250
# The graph below may look a bit complicated (and it may be but I've
251
# banged my head enough on it) but the bug requires at least dotted
252
# revnos *and* merged revisions below that.
253
builder.build_snapshot('1', None, [
254
('add', ('', 'root-id', 'directory', ''))])
255
builder.build_snapshot('2', ['1'], [])
256
builder.build_snapshot('1.1.1', ['1'], [])
257
builder.build_snapshot('2.1.1', ['2'], [])
258
builder.build_snapshot('3', ['2', '1.1.1'], [])
259
builder.build_snapshot('2.1.2', ['2.1.1'], [])
260
builder.build_snapshot('2.2.1', ['2.1.1'], [])
261
builder.build_snapshot('2.1.3', ['2.1.2', '2.2.1'], [])
262
builder.build_snapshot('4', ['3', '2.1.3'], [])
263
builder.build_snapshot('5', ['4', '2.1.2'], [])
264
builder.finish_series()
267
def test_not_an_ancestor(self):
268
self.assertRaises(errors.BzrCommandError,
269
log._generate_all_revisions,
270
self.branch, '1.1.1', '2.1.3', 'reverse',
271
delayed_graph_generation=True)
273
def test_wrong_order(self):
274
self.assertRaises(errors.BzrCommandError,
275
log._generate_all_revisions,
276
self.branch, '5', '2.1.3', 'reverse',
277
delayed_graph_generation=True)
279
def test_no_start_rev_id_with_end_rev_id_being_a_merge(self):
280
revs = log._generate_all_revisions(
281
self.branch, None, '2.1.3',
282
'reverse', delayed_graph_generation=True)
285
class TestLogRevSpecsWithPaths(TestLogWithLogCatcher):
287
def test_log_revno_n_path_wrong_namespace(self):
288
self.make_linear_branch('branch1')
289
self.make_linear_branch('branch2')
290
# There is no guarantee that a path exist between two arbitrary
292
self.run_bzr("log -r revno:2:branch1..revno:3:branch2", retcode=3)
294
def test_log_revno_n_path_correct_order(self):
295
self.make_linear_branch('branch2')
296
self.assertLogRevnos(['-rrevno:1:branch2..revno:3:branch2'],
299
def test_log_revno_n_path(self):
300
self.make_linear_branch('branch2')
301
self.assertLogRevnos(['-rrevno:1:branch2'],
303
rev_props = self.log_catcher.revisions[0].rev.properties
304
self.assertEqual('branch2', rev_props['branch-nick'])
307
class TestLogErrors(TestLog):
309
def test_log_zero_revspec(self):
310
self.make_minimal_branch()
311
self.run_bzr_error(['bzr: ERROR: Logging revision 0 is invalid.'],
314
def test_log_zero_begin_revspec(self):
315
self.make_linear_branch()
316
self.run_bzr_error(['bzr: ERROR: Logging revision 0 is invalid.'],
319
def test_log_zero_end_revspec(self):
320
self.make_linear_branch()
321
self.run_bzr_error(['bzr: ERROR: Logging revision 0 is invalid.'],
324
def test_log_nonexistent_revno(self):
325
self.make_minimal_branch()
326
self.run_bzr_error(["bzr: ERROR: Requested revision: '1234' "
327
"does not exist in branch:"],
330
def test_log_nonexistent_dotted_revno(self):
331
self.make_minimal_branch()
332
self.run_bzr_error(["bzr: ERROR: Requested revision: '123.123' "
333
"does not exist in branch:"],
334
['log', '-r123.123'])
336
def test_log_change_nonexistent_revno(self):
337
self.make_minimal_branch()
338
self.run_bzr_error(["bzr: ERROR: Requested revision: '1234' "
339
"does not exist in branch:"],
342
def test_log_change_nonexistent_dotted_revno(self):
343
self.make_minimal_branch()
344
self.run_bzr_error(["bzr: ERROR: Requested revision: '123.123' "
345
"does not exist in branch:"],
346
['log', '-c123.123'])
348
def test_log_change_single_revno_only(self):
349
self.make_minimal_branch()
350
self.run_bzr_error(['bzr: ERROR: Option --change does not'
351
' accept revision ranges'],
352
['log', '--change', '2..3'])
354
def test_log_change_incompatible_with_revision(self):
355
self.run_bzr_error(['bzr: ERROR: --revision and --change'
356
' are mutually exclusive'],
357
['log', '--change', '2', '--revision', '3'])
359
def test_log_nonexistent_file(self):
360
self.make_minimal_branch()
361
# files that don't exist in either the basis tree or working tree
362
# should give an error
363
out, err = self.run_bzr('log does-not-exist', retcode=3)
364
self.assertContainsRe(err,
365
'Path unknown at end or start of revision range: '
368
def test_log_reversed_revspecs(self):
369
self.make_linear_branch()
370
self.run_bzr_error(('bzr: ERROR: Start revision must be older than '
371
'the end revision.\n',),
374
def test_log_reversed_dotted_revspecs(self):
375
self.make_merged_branch()
376
self.run_bzr_error(('bzr: ERROR: Start revision not found in '
377
'left-hand history of end revision.\n',),
380
def test_log_bad_message_re(self):
381
"""Bad --message argument gives a sensible message
383
See https://bugs.launchpad.net/bzr/+bug/251352
385
self.make_minimal_branch()
386
out, err = self.run_bzr(['log', '-m', '*'], retcode=3)
387
self.assertContainsRe(err, "ERROR.*Invalid pattern.*nothing to repeat")
388
self.assertNotContainsRe(err, "Unprintable exception")
389
self.assertEqual(out, '')
391
def test_log_unsupported_timezone(self):
392
self.make_linear_branch()
393
self.run_bzr_error(['bzr: ERROR: Unsupported timezone format "foo", '
394
'options are "utc", "original", "local".'],
395
['log', '--timezone', 'foo'])
397
def test_log_exclude_ancestry_no_range(self):
398
self.make_linear_branch()
399
self.run_bzr_error(['bzr: ERROR: --exclude-common-ancestry'
400
' requires -r with two revisions'],
401
['log', '--exclude-common-ancestry'])
403
def test_log_exclude_ancestry_single_revision(self):
404
self.make_merged_branch()
405
self.run_bzr_error(['bzr: ERROR: --exclude-common-ancestry'
406
' requires two different revisions'],
407
['log', '--exclude-common-ancestry',
410
class TestLogTags(TestLog):
412
def test_log_with_tags(self):
413
tree = self.make_linear_branch(format='dirstate-tags')
415
branch.tags.set_tag('tag1', branch.get_rev_id(1))
416
branch.tags.set_tag('tag1.1', branch.get_rev_id(1))
417
branch.tags.set_tag('tag3', branch.last_revision())
419
log = self.run_bzr("log -r-1")[0]
420
self.assertTrue('tags: tag3' in log)
422
log = self.run_bzr("log -r1")[0]
423
# I guess that we can't know the order of tags in the output
424
# since dicts are unordered, need to check both possibilities
425
self.assertContainsRe(log, r'tags: (tag1, tag1\.1|tag1\.1, tag1)')
427
def test_merged_log_with_tags(self):
428
branch1_tree = self.make_linear_branch('branch1',
429
format='dirstate-tags')
430
branch1 = branch1_tree.branch
431
branch2_tree = branch1_tree.bzrdir.sprout('branch2').open_workingtree()
432
branch1_tree.commit(message='foobar', allow_pointless=True)
433
branch1.tags.set_tag('tag1', branch1.last_revision())
434
# tags don't propagate if we don't merge
435
self.run_bzr('merge ../branch1', working_dir='branch2')
436
branch2_tree.commit(message='merge branch 1')
437
log = self.run_bzr("log -n0 -r-1", working_dir='branch2')[0]
438
self.assertContainsRe(log, r' tags: tag1')
439
log = self.run_bzr("log -n0 -r3.1.1", working_dir='branch2')[0]
440
self.assertContainsRe(log, r'tags: tag1')
443
class TestLogVerbose(TestLog):
446
super(TestLogVerbose, self).setUp()
447
self.make_minimal_branch()
449
def assertUseShortDeltaFormat(self, cmd):
450
log = self.run_bzr(cmd)[0]
451
# Check that we use the short status format
452
self.assertContainsRe(log, '(?m)^\s*A hello.txt$')
453
self.assertNotContainsRe(log, '(?m)^\s*added:$')
455
def assertUseLongDeltaFormat(self, cmd):
456
log = self.run_bzr(cmd)[0]
457
# Check that we use the long status format
458
self.assertNotContainsRe(log, '(?m)^\s*A hello.txt$')
459
self.assertContainsRe(log, '(?m)^\s*added:$')
461
def test_log_short_verbose(self):
462
self.assertUseShortDeltaFormat(['log', '--short', '-v'])
464
def test_log_short_verbose_verbose(self):
465
self.assertUseLongDeltaFormat(['log', '--short', '-vv'])
467
def test_log_long_verbose(self):
468
# Check that we use the long status format, ignoring the verbosity
470
self.assertUseLongDeltaFormat(['log', '--long', '-v'])
472
def test_log_long_verbose_verbose(self):
473
# Check that we use the long status format, ignoring the verbosity
475
self.assertUseLongDeltaFormat(['log', '--long', '-vv'])
478
class TestLogMerges(TestLogWithLogCatcher):
481
super(TestLogMerges, self).setUp()
482
self.make_branches_with_merges()
484
def make_branches_with_merges(self):
485
level0 = self.make_branch_and_tree('level0')
486
self.wt_commit(level0, 'in branch level0')
487
level1 = level0.bzrdir.sprout('level1').open_workingtree()
488
self.wt_commit(level1, 'in branch level1')
489
level2 = level1.bzrdir.sprout('level2').open_workingtree()
490
self.wt_commit(level2, 'in branch level2')
491
level1.merge_from_branch(level2.branch)
492
self.wt_commit(level1, 'merge branch level2')
493
level0.merge_from_branch(level1.branch)
494
self.wt_commit(level0, 'merge branch level1')
82
log = self.runbzr("log -r -2..")[0]
83
self.assertTrue('revno: 1\n' not in log)
84
self.assertTrue('revno: 2\n' in log)
85
self.assertTrue('revno: 3\n' in log)
87
def test_log_postive_revspecs(self):
89
log = self.runbzr("log -r 1..3")[0]
90
self.assertEquals(self.full_log, log)
93
class TestLogMerges(ExternalBase):
496
95
def test_merges_are_indented_by_level(self):
497
self.run_bzr(['log', '-n0'], working_dir='level0')
498
revnos_and_depth = [(r.revno, r.merge_depth)
499
for r in self.get_captured_revisions()]
500
self.assertEqual([('2', 0), ('1.1.2', 1), ('1.2.1', 2), ('1.1.1', 1),
502
[(r.revno, r.merge_depth)
503
for r in self.get_captured_revisions()])
505
def test_force_merge_revisions_off(self):
506
self.assertLogRevnos(['-n1'], ['2', '1'], working_dir='level0')
508
def test_force_merge_revisions_on(self):
509
self.assertLogRevnos(['-n0'], ['2', '1.1.2', '1.2.1', '1.1.1', '1'],
510
working_dir='level0')
512
def test_include_merges(self):
513
# Confirm --include-merges gives the same output as -n0
514
self.assertLogRevnos(['--include-merges'],
515
['2', '1.1.2', '1.2.1', '1.1.1', '1'],
516
working_dir='level0')
517
self.assertLogRevnos(['--include-merges'],
518
['2', '1.1.2', '1.2.1', '1.1.1', '1'],
519
working_dir='level0')
520
out_im, err_im = self.run_bzr('log --include-merges',
521
working_dir='level0')
522
out_n0, err_n0 = self.run_bzr('log -n0', working_dir='level0')
523
self.assertEqual('', err_im)
524
self.assertEqual('', err_n0)
525
self.assertEqual(out_im, out_n0)
527
def test_force_merge_revisions_N(self):
528
self.assertLogRevnos(['-n2'],
529
['2', '1.1.2', '1.1.1', '1'],
530
working_dir='level0')
532
def test_merges_single_merge_rev(self):
533
self.assertLogRevnosAndDepths(['-n0', '-r1.1.2'],
534
[('1.1.2', 0), ('1.2.1', 1)],
535
working_dir='level0')
537
def test_merges_partial_range(self):
538
self.assertLogRevnosAndDepths(
539
['-n0', '-r1.1.1..1.1.2'],
540
[('1.1.2', 0), ('1.2.1', 1), ('1.1.1', 0)],
541
working_dir='level0')
543
def test_merges_partial_range_ignore_before_lower_bound(self):
544
"""Dont show revisions before the lower bound's merged revs"""
545
self.assertLogRevnosAndDepths(
546
['-n0', '-r1.1.2..2'],
547
[('2', 0), ('1.1.2', 1), ('1.2.1', 2)],
548
working_dir='level0')
551
class TestLogDiff(TestLogWithLogCatcher):
553
# FIXME: We need specific tests for each LogFormatter about how the diffs
554
# are displayed: --long indent them by depth, --short use a fixed
555
# indent and --line does't display them. -- vila 10019
558
super(TestLogDiff, self).setUp()
559
self.make_branch_with_diffs()
561
def make_branch_with_diffs(self):
562
level0 = self.make_branch_and_tree('level0')
563
self.build_tree(['level0/file1', 'level0/file2'])
566
self.wt_commit(level0, 'in branch level0')
568
level1 = level0.bzrdir.sprout('level1').open_workingtree()
569
self.build_tree_contents([('level1/file2', 'hello\n')])
570
self.wt_commit(level1, 'in branch level1')
571
level0.merge_from_branch(level1.branch)
572
self.wt_commit(level0, 'merge branch level1')
574
def _diff_file1_revno1(self):
575
return """=== added file 'file1'
576
--- file1\t1970-01-01 00:00:00 +0000
577
+++ file1\t2005-11-22 00:00:00 +0000
579
+contents of level0/file1
583
def _diff_file2_revno2(self):
584
return """=== modified file 'file2'
585
--- file2\t2005-11-22 00:00:00 +0000
586
+++ file2\t2005-11-22 00:00:01 +0000
588
-contents of level0/file2
593
def _diff_file2_revno1_1_1(self):
594
return """=== modified file 'file2'
595
--- file2\t2005-11-22 00:00:00 +0000
596
+++ file2\t2005-11-22 00:00:01 +0000
598
-contents of level0/file2
603
def _diff_file2_revno1(self):
604
return """=== added file 'file2'
605
--- file2\t1970-01-01 00:00:00 +0000
606
+++ file2\t2005-11-22 00:00:00 +0000
608
+contents of level0/file2
612
def assertLogRevnosAndDiff(self, args, expected,
614
self.run_bzr(['log', '-p'] + args, working_dir=working_dir)
615
expected_revnos_and_depths = [
616
(revno, depth) for revno, depth, diff in expected]
617
# Check the revnos and depths first to make debugging easier
618
self.assertEqual(expected_revnos_and_depths,
619
[(r.revno, r.merge_depth)
620
for r in self.get_captured_revisions()])
621
# Now check the diffs, adding the revno in case of failure
622
fmt = 'In revno %s\n%s'
623
for expected_rev, actual_rev in izip(expected,
624
self.get_captured_revisions()):
625
revno, depth, expected_diff = expected_rev
626
actual_diff = actual_rev.diff
627
self.assertEqualDiff(fmt % (revno, expected_diff),
628
fmt % (revno, actual_diff))
630
def test_log_diff_with_merges(self):
631
self.assertLogRevnosAndDiff(
633
[('2', 0, self._diff_file2_revno2()),
634
('1.1.1', 1, self._diff_file2_revno1_1_1()),
635
('1', 0, self._diff_file1_revno1()
636
+ self._diff_file2_revno1())],
637
working_dir='level0')
640
def test_log_diff_file1(self):
641
self.assertLogRevnosAndDiff(['-n0', 'file1'],
642
[('1', 0, self._diff_file1_revno1())],
643
working_dir='level0')
645
def test_log_diff_file2(self):
646
self.assertLogRevnosAndDiff(['-n1', 'file2'],
647
[('2', 0, self._diff_file2_revno2()),
648
('1', 0, self._diff_file2_revno1())],
649
working_dir='level0')
652
class TestLogUnicodeDiff(TestLog):
654
def test_log_show_diff_non_ascii(self):
655
# Smoke test for bug #328007 UnicodeDecodeError on 'log -p'
656
message = u'Message with \xb5'
657
body = 'Body with \xb5\n'
658
wt = self.make_branch_and_tree('.')
659
self.build_tree_contents([('foo', body)])
661
wt.commit(message=message)
662
# check that command won't fail with unicode error
663
# don't care about exact output because we have other tests for this
664
out,err = self.run_bzr('log -p --long')
665
self.assertNotEqual('', out)
666
self.assertEqual('', err)
667
out,err = self.run_bzr('log -p --short')
668
self.assertNotEqual('', out)
669
self.assertEqual('', err)
670
out,err = self.run_bzr('log -p --line')
671
self.assertNotEqual('', out)
672
self.assertEqual('', err)
675
class TestLogEncodings(tests.TestCaseInTempDir):
96
self.build_tree(['parent/'])
97
self.run_bzr('init', 'parent')
98
self.run_bzr('commit', '-m', 'first post', '--unchanged', 'parent')
99
self.run_bzr('branch', 'parent', 'child')
100
self.run_bzr('commit', '-m', 'branch 1', '--unchanged', 'child')
101
self.run_bzr('branch', 'child', 'smallerchild')
102
self.run_bzr('commit', '-m', 'branch 2', '--unchanged', 'smallerchild')
104
self.run_bzr('merge', '../smallerchild')
105
self.run_bzr('commit', '-m', 'merge branch 2')
106
os.chdir('../parent')
107
self.run_bzr('merge', '../child')
108
self.run_bzr('commit', '-m', 'merge branch 1')
109
out,err = self.run_bzr('log')
110
# the log will look something like:
111
# self.assertEqual("""\
112
#------------------------------------------------------------
114
#committer: Robert Collins <foo@example.com>
116
#timestamp: Tue 2006-03-28 22:31:40 +1100
119
# ------------------------------------------------------------
120
# merged: foo@example.com-20060328113140-91f43cfb46dc2863
121
# committer: Robert Collins <foo@example.com>
123
# timestamp: Tue 2006-03-28 22:31:40 +1100
126
# ------------------------------------------------------------
127
# merged: foo@example.com-20060328113140-1ba24f850a0ef573
128
# committer: Robert Collins <foo@example.com>
129
# branch nick: smallerchild
130
# timestamp: Tue 2006-03-28 22:31:40 +1100
133
# ------------------------------------------------------------
134
# merged: foo@example.com-20060328113140-5749a4757a8ac792
135
# committer: Robert Collins <foo@example.com>
137
# timestamp: Tue 2006-03-28 22:31:40 +1100
140
#------------------------------------------------------------
142
#committer: Robert Collins <foo@example.com>
144
#timestamp: Tue 2006-03-28 22:31:39 +1100
148
# but we dont have a nice pattern matcher hooked up yet, so:
149
# we check for the indenting of the commit message:
150
self.assertTrue(' merge branch 1' in out)
151
self.assertTrue(' merge branch 2' in out)
152
self.assertTrue(' branch 2' in out)
153
self.assertTrue(' branch 1' in out)
154
self.assertTrue(' first post' in out)
155
self.assertEqual('', err)
158
class TestLogEncodings(TestCaseInTempDir):
678
161
_message = u'Message with \xb5'
768
255
# Make sure the cp1251 string is not found anywhere
769
256
self.assertEquals(-1, stdout.find(test_in_cp1251))
772
class TestLogFile(TestLogWithLogCatcher):
774
def test_log_local_branch_file(self):
775
"""We should be able to log files in local treeless branches"""
776
tree = self.make_branch_and_tree('tree')
777
self.build_tree(['tree/file'])
779
tree.commit('revision 1')
780
tree.bzrdir.destroy_workingtree()
781
self.run_bzr('log tree/file')
783
def prepare_tree(self, complex=False):
784
# The complex configuration includes deletes and renames
785
tree = self.make_branch_and_tree('parent')
786
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
788
tree.commit('add file1')
790
tree.commit('add file2')
792
tree.commit('add file3')
793
child_tree = tree.bzrdir.sprout('child').open_workingtree()
794
self.build_tree_contents([('child/file2', 'hello')])
795
child_tree.commit(message='branch 1')
796
tree.merge_from_branch(child_tree.branch)
797
tree.commit(message='merge child branch')
800
tree.commit('remove file2')
801
tree.rename_one('file3', 'file4')
802
tree.commit('file3 is now called file4')
804
tree.commit('remove file1')
807
# FIXME: It would be good to parametrize the following tests against all
808
# formatters. But the revisions selection is not *currently* part of the
809
# LogFormatter contract, so using LogCatcher is sufficient -- vila 100118
810
def test_log_file1(self):
812
self.assertLogRevnos(['-n0', 'file1'], ['1'])
814
def test_log_file2(self):
817
self.assertLogRevnos(['-n0', 'file2'], ['4', '3.1.1', '2'])
818
# file2 in a merge revision
819
self.assertLogRevnos(['-n0', '-r3.1.1', 'file2'], ['3.1.1'])
820
# file2 in a mainline revision
821
self.assertLogRevnos(['-n0', '-r4', 'file2'], ['4', '3.1.1'])
822
# file2 since a revision
823
self.assertLogRevnos(['-n0', '-r3..', 'file2'], ['4', '3.1.1'])
824
# file2 up to a revision
825
self.assertLogRevnos(['-n0', '-r..3', 'file2'], ['2'])
827
def test_log_file3(self):
829
self.assertLogRevnos(['-n0', 'file3'], ['3'])
831
def test_log_file_historical_missing(self):
832
# Check logging a deleted file gives an error if the
833
# file isn't found at the end or start of the revision range
834
self.prepare_tree(complex=True)
835
err_msg = "Path unknown at end or start of revision range: file2"
836
err = self.run_bzr('log file2', retcode=3)[1]
837
self.assertContainsRe(err, err_msg)
839
def test_log_file_historical_end(self):
840
# Check logging a deleted file is ok if the file existed
841
# at the end the revision range
842
self.prepare_tree(complex=True)
843
self.assertLogRevnos(['-n0', '-r..4', 'file2'], ['4', '3.1.1', '2'])
845
def test_log_file_historical_start(self):
846
# Check logging a deleted file is ok if the file existed
847
# at the start of the revision range
848
self.prepare_tree(complex=True)
849
self.assertLogRevnos(['file1'], ['1'])
851
def test_log_file_renamed(self):
852
"""File matched against revision range, not current tree."""
853
self.prepare_tree(complex=True)
855
# Check logging a renamed file gives an error by default
856
err_msg = "Path unknown at end or start of revision range: file3"
857
err = self.run_bzr('log file3', retcode=3)[1]
858
self.assertContainsRe(err, err_msg)
860
# Check we can see a renamed file if we give the right end revision
861
self.assertLogRevnos(['-r..4', 'file3'], ['3'])
864
class TestLogMultiple(TestLogWithLogCatcher):
866
def prepare_tree(self):
867
tree = self.make_branch_and_tree('parent')
874
'parent/dir1/dir2/file3',
877
tree.commit('add file1')
879
tree.commit('add file2')
880
tree.add(['dir1', 'dir1/dir2', 'dir1/dir2/file3'])
881
tree.commit('add file3')
883
tree.commit('add file4')
884
tree.add('dir1/file5')
885
tree.commit('add file5')
886
child_tree = tree.bzrdir.sprout('child').open_workingtree()
887
self.build_tree_contents([('child/file2', 'hello')])
888
child_tree.commit(message='branch 1')
889
tree.merge_from_branch(child_tree.branch)
890
tree.commit(message='merge child branch')
893
def test_log_files(self):
894
"""The log for multiple file should only list revs for those files"""
896
self.assertLogRevnos(['file1', 'file2', 'dir1/dir2/file3'],
897
['6', '5.1.1', '3', '2', '1'])
899
def test_log_directory(self):
900
"""The log for a directory should show all nested files."""
902
self.assertLogRevnos(['dir1'], ['5', '3'])
904
def test_log_nested_directory(self):
905
"""The log for a directory should show all nested files."""
907
self.assertLogRevnos(['dir1/dir2'], ['3'])
909
def test_log_in_nested_directory(self):
910
"""The log for a directory should show all nested files."""
913
self.assertLogRevnos(['.'], ['5', '3'])
915
def test_log_files_and_directories(self):
916
"""Logging files and directories together should be fine."""
918
self.assertLogRevnos(['file4', 'dir1/dir2'], ['4', '3'])
920
def test_log_files_and_dirs_in_nested_directory(self):
921
"""The log for a directory should show all nested files."""
924
self.assertLogRevnos(['dir2', 'file5'], ['5', '3'])
927
class MainlineGhostTests(TestLogWithLogCatcher):
930
super(MainlineGhostTests, self).setUp()
931
tree = self.make_branch_and_tree('')
932
tree.set_parent_ids(["spooky"], allow_leftmost_as_ghost=True)
934
tree.commit('msg1', rev_id='rev1')
935
tree.commit('msg2', rev_id='rev2')
937
def test_log_range(self):
938
self.assertLogRevnos(["-r1..2"], ["2", "1"])
940
def test_log_norange(self):
941
self.assertLogRevnos([], ["2", "1"])
943
def test_log_range_open_begin(self):
944
raise tests.KnownFailure("log with ghosts fails. bug #726466")
945
(stdout, stderr) = self.run_bzr(['log', '-r..2'], retcode=3)
946
self.assertEqual(["2", "1"],
947
[r.revno for r in self.get_captured_revisions()])
948
self.assertEquals("bzr: ERROR: Further revision history missing.", stderr)
950
def test_log_range_open_end(self):
951
self.assertLogRevnos(["-r1.."], ["2", "1"])