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
31
from bzrlib.tests import (
37
class TestLog(tests.TestCaseWithTransport, test_log.TestLogMixin):
39
def make_minimal_branch(self, path='.', format=None):
40
tree = self.make_branch_and_tree(path, format=format)
41
self.build_tree([path + '/hello.txt'])
43
tree.commit(message='message1')
46
def make_linear_branch(self, path='.', format=None):
47
tree = self.make_branch_and_tree(path, format=format)
49
[path + '/hello.txt', path + '/goodbye.txt', path + '/meep.txt'])
51
tree.commit(message='message1')
52
tree.add('goodbye.txt')
53
tree.commit(message='message2')
55
tree.commit(message='message3')
58
def make_merged_branch(self, path='.', format=None):
59
tree = self.make_linear_branch(path, format)
60
tree2 = tree.bzrdir.sprout('tree2',
61
revision_id=tree.branch.get_rev_id(1)).open_workingtree()
62
tree2.commit(message='tree2 message2')
63
tree2.commit(message='tree2 message3')
64
tree.merge_from_branch(tree2.branch)
65
tree.commit(message='merge')
69
class TestLogWithLogCatcher(TestLog):
72
super(TestLogWithLogCatcher, self).setUp()
73
# Capture log formatter creations
74
class MyLogFormatter(test_log.LogCatcher):
76
def __new__(klass, *args, **kwargs):
77
self.log_catcher = test_log.LogCatcher(*args, **kwargs)
78
# Always return our own log formatter
79
return self.log_catcher
82
# Always return our own log formatter class hijacking the
83
# default behavior (which requires setting up a config
86
self.overrideAttr(log.log_formatter_registry, 'get_default', getme)
88
def get_captured_revisions(self):
89
return self.log_catcher.revisions
91
def assertLogRevnos(self, args, expected_revnos, working_dir='.'):
92
self.run_bzr(['log'] + args, working_dir=working_dir)
93
self.assertEqual(expected_revnos,
94
[r.revno for r in self.get_captured_revisions()])
96
def assertLogRevnosAndDepths(self, args, expected_revnos_and_depths,
98
self.run_bzr(['log'] + args, working_dir=working_dir)
99
self.assertEqual(expected_revnos_and_depths,
100
[(r.revno, r.merge_depth)
101
for r in self.get_captured_revisions()])
104
class TestLogRevSpecs(TestLogWithLogCatcher):
106
def test_log_no_revspec(self):
107
self.make_linear_branch()
108
self.assertLogRevnos([], ['3', '2', '1'])
23
from bzrlib.tests.blackbox import ExternalBase
26
class TestLog(ExternalBase):
30
self.build_tree(['hello.txt', 'goodbye.txt', 'meep.txt'])
31
self.runbzr("add hello.txt")
32
self.runbzr("commit -m message1 hello.txt")
33
self.runbzr("add goodbye.txt")
34
self.runbzr("commit -m message2 goodbye.txt")
35
self.runbzr("add meep.txt")
36
self.runbzr("commit -m message3 meep.txt")
37
self.full_log = self.runbzr("log")[0]
110
39
def test_log_null_end_revspec(self):
111
self.make_linear_branch()
112
self.assertLogRevnos(['-r1..'], ['3', '2', '1'])
41
self.assertTrue('revno: 1\n' in self.full_log)
42
self.assertTrue('revno: 2\n' in self.full_log)
43
self.assertTrue('revno: 3\n' in self.full_log)
44
self.assertTrue('message:\n message1\n' in self.full_log)
45
self.assertTrue('message:\n message2\n' in self.full_log)
46
self.assertTrue('message:\n message3\n' in self.full_log)
48
log = self.runbzr("log -r 1..")[0]
49
self.assertEquals(log, self.full_log)
114
51
def test_log_null_begin_revspec(self):
115
self.make_linear_branch()
116
self.assertLogRevnos(['-r..3'], ['3', '2', '1'])
53
log = self.runbzr("log -r ..3")[0]
54
self.assertEquals(self.full_log, log)
118
56
def test_log_null_both_revspecs(self):
119
self.make_linear_branch()
120
self.assertLogRevnos(['-r..'], ['3', '2', '1'])
58
log = self.runbzr("log -r ..")[0]
59
self.assertEquals(self.full_log, log)
122
61
def test_log_negative_begin_revspec_full_log(self):
123
self.make_linear_branch()
124
self.assertLogRevnos(['-r-3..'], ['3', '2', '1'])
63
log = self.runbzr("log -r -3..")[0]
64
self.assertEquals(self.full_log, log)
126
66
def test_log_negative_both_revspec_full_log(self):
127
self.make_linear_branch()
128
self.assertLogRevnos(['-r-3..-1'], ['3', '2', '1'])
68
log = self.runbzr("log -r -3..-1")[0]
69
self.assertEquals(self.full_log, log)
130
71
def test_log_negative_both_revspec_partial(self):
131
self.make_linear_branch()
132
self.assertLogRevnos(['-r-3..-2'], ['2', '1'])
73
log = self.runbzr("log -r -3..-2")[0]
74
self.assertTrue('revno: 1\n' in log)
75
self.assertTrue('revno: 2\n' in log)
76
self.assertTrue('revno: 3\n' not in log)
134
78
def test_log_negative_begin_revspec(self):
135
self.make_linear_branch()
136
self.assertLogRevnos(['-r-2..'], ['3', '2'])
138
def test_log_positive_revspecs(self):
139
self.make_linear_branch()
140
self.assertLogRevnos(['-r1..3'], ['3', '2', '1'])
142
def test_log_dotted_revspecs(self):
143
self.make_merged_branch()
144
self.assertLogRevnos(['-n0', '-r1..1.1.1'], ['1.1.1', '1'])
146
def test_log_limit(self):
147
tree = self.make_branch_and_tree('.')
148
# We want more commits than our batch size starts at
149
for pos in range(10):
150
tree.commit("%s" % pos)
151
self.assertLogRevnos(['--limit', '2'], ['10', '9'])
153
def test_log_limit_short(self):
154
self.make_linear_branch()
155
self.assertLogRevnos(['-l', '2'], ['3', '2'])
157
def test_log_change_revno(self):
158
self.make_linear_branch()
159
self.assertLogRevnos(['-c1'], ['1'])
162
class TestLogMergedLinearAncestry(TestLogWithLogCatcher):
165
super(TestLogMergedLinearAncestry, self).setUp()
166
# FIXME: Using a MemoryTree would be even better here (but until we
167
# stop calling run_bzr, there is no point) --vila 100118.
168
builder = branchbuilder.BranchBuilder(self.get_transport())
169
builder.start_series()
171
builder.build_snapshot('1', None, [
172
('add', ('', 'root-id', 'directory', ''))])
173
builder.build_snapshot('2', ['1'], [])
175
builder.build_snapshot('1.1.1', ['1'], [])
176
# merge branch into mainline
177
builder.build_snapshot('3', ['2', '1.1.1'], [])
178
# new commits in branch
179
builder.build_snapshot('1.1.2', ['1.1.1'], [])
180
builder.build_snapshot('1.1.3', ['1.1.2'], [])
181
# merge branch into mainline
182
builder.build_snapshot('4', ['3', '1.1.3'], [])
183
# merge mainline into branch
184
builder.build_snapshot('1.1.4', ['1.1.3', '4'], [])
185
# merge branch into mainline
186
builder.build_snapshot('5', ['4', '1.1.4'], [])
187
builder.finish_series()
190
self.assertLogRevnos(['-n0', '-r1.1.1..1.1.4'],
191
['1.1.4', '4', '1.1.3', '1.1.2', '3', '1.1.1'])
192
def test_n0_forward(self):
193
self.assertLogRevnos(['-n0', '-r1.1.1..1.1.4', '--forward'],
194
['3', '1.1.1', '4', '1.1.2', '1.1.3', '1.1.4'])
197
# starting from 1.1.4 we follow the left-hand ancestry
198
self.assertLogRevnos(['-n1', '-r1.1.1..1.1.4'],
199
['1.1.4', '1.1.3', '1.1.2', '1.1.1'])
201
def test_n1_forward(self):
202
self.assertLogRevnos(['-n1', '-r1.1.1..1.1.4', '--forward'],
203
['1.1.1', '1.1.2', '1.1.3', '1.1.4'])
206
class Test_GenerateAllRevisions(TestLogWithLogCatcher):
209
super(Test_GenerateAllRevisions, self).setUp()
210
builder = self.make_branch_with_many_merges()
211
b = builder.get_branch()
213
self.addCleanup(b.unlock)
216
def make_branch_with_many_merges(self, path='.', format=None):
217
builder = branchbuilder.BranchBuilder(self.get_transport())
218
builder.start_series()
219
# The graph below may look a bit complicated (and it may be but I've
220
# banged my head enough on it) but the bug requires at least dotted
221
# revnos *and* merged revisions below that.
222
builder.build_snapshot('1', None, [
223
('add', ('', 'root-id', 'directory', ''))])
224
builder.build_snapshot('2', ['1'], [])
225
builder.build_snapshot('1.1.1', ['1'], [])
226
builder.build_snapshot('2.1.1', ['2'], [])
227
builder.build_snapshot('3', ['2', '1.1.1'], [])
228
builder.build_snapshot('2.1.2', ['2.1.1'], [])
229
builder.build_snapshot('2.2.1', ['2.1.1'], [])
230
builder.build_snapshot('2.1.3', ['2.1.2', '2.2.1'], [])
231
builder.build_snapshot('4', ['3', '2.1.3'], [])
232
builder.build_snapshot('5', ['4', '2.1.2'], [])
233
builder.finish_series()
236
def test_not_an_ancestor(self):
237
self.assertRaises(errors.BzrCommandError,
238
log._generate_all_revisions,
239
self.branch, '1.1.1', '2.1.3', 'reverse',
240
delayed_graph_generation=True)
242
def test_wrong_order(self):
243
self.assertRaises(errors.BzrCommandError,
244
log._generate_all_revisions,
245
self.branch, '5', '2.1.3', 'reverse',
246
delayed_graph_generation=True)
248
def test_no_start_rev_id_with_end_rev_id_being_a_merge(self):
249
revs = log._generate_all_revisions(
250
self.branch, None, '2.1.3',
251
'reverse', delayed_graph_generation=True)
254
class TestLogRevSpecsWithPaths(TestLogWithLogCatcher):
256
def test_log_revno_n_path_wrong_namespace(self):
257
self.make_linear_branch('branch1')
258
self.make_linear_branch('branch2')
259
# There is no guarantee that a path exist between two arbitrary
261
self.run_bzr("log -r revno:2:branch1..revno:3:branch2", retcode=3)
263
def test_log_revno_n_path_correct_order(self):
264
self.make_linear_branch('branch2')
265
self.assertLogRevnos(['-rrevno:1:branch2..revno:3:branch2'],
268
def test_log_revno_n_path(self):
269
self.make_linear_branch('branch2')
270
self.assertLogRevnos(['-rrevno:1:branch2'],
272
rev_props = self.log_catcher.revisions[0].rev.properties
273
self.assertEqual('branch2', rev_props['branch-nick'])
276
class TestLogErrors(TestLog):
278
def test_log_zero_revspec(self):
279
self.make_minimal_branch()
280
self.run_bzr_error(['bzr: ERROR: Logging revision 0 is invalid.'],
283
def test_log_zero_begin_revspec(self):
284
self.make_linear_branch()
285
self.run_bzr_error(['bzr: ERROR: Logging revision 0 is invalid.'],
288
def test_log_zero_end_revspec(self):
289
self.make_linear_branch()
290
self.run_bzr_error(['bzr: ERROR: Logging revision 0 is invalid.'],
293
def test_log_nonexistent_revno(self):
294
self.make_minimal_branch()
295
self.run_bzr_error(["bzr: ERROR: Requested revision: '1234' "
296
"does not exist in branch:"],
299
def test_log_nonexistent_dotted_revno(self):
300
self.make_minimal_branch()
301
self.run_bzr_error(["bzr: ERROR: Requested revision: '123.123' "
302
"does not exist in branch:"],
303
['log', '-r123.123'])
305
def test_log_change_nonexistent_revno(self):
306
self.make_minimal_branch()
307
self.run_bzr_error(["bzr: ERROR: Requested revision: '1234' "
308
"does not exist in branch:"],
311
def test_log_change_nonexistent_dotted_revno(self):
312
self.make_minimal_branch()
313
self.run_bzr_error(["bzr: ERROR: Requested revision: '123.123' "
314
"does not exist in branch:"],
315
['log', '-c123.123'])
317
def test_log_change_single_revno_only(self):
318
self.make_minimal_branch()
319
self.run_bzr_error(['bzr: ERROR: Option --change does not'
320
' accept revision ranges'],
321
['log', '--change', '2..3'])
323
def test_log_change_incompatible_with_revision(self):
324
self.run_bzr_error(['bzr: ERROR: --revision and --change'
325
' are mutually exclusive'],
326
['log', '--change', '2', '--revision', '3'])
328
def test_log_nonexistent_file(self):
329
self.make_minimal_branch()
330
# files that don't exist in either the basis tree or working tree
331
# should give an error
332
out, err = self.run_bzr('log does-not-exist', retcode=3)
333
self.assertContainsRe(err,
334
'Path unknown at end or start of revision range: '
337
def test_log_reversed_revspecs(self):
338
self.make_linear_branch()
339
self.run_bzr_error(('bzr: ERROR: Start revision must be older than '
340
'the end revision.\n',),
343
def test_log_reversed_dotted_revspecs(self):
344
self.make_merged_branch()
345
self.run_bzr_error(('bzr: ERROR: Start revision not found in '
346
'left-hand history of end revision.\n',),
349
def test_log_bad_message_re(self):
350
"""Bad --message argument gives a sensible message
352
See https://bugs.launchpad.net/bzr/+bug/251352
354
self.make_minimal_branch()
355
out, err = self.run_bzr(['log', '-m', '*'], retcode=3)
356
self.assertEqual("bzr: ERROR: Invalid regular expression"
357
" in log message filter"
359
": nothing to repeat\n", err)
360
self.assertEqual('', out)
362
def test_log_unsupported_timezone(self):
363
self.make_linear_branch()
364
self.run_bzr_error(['bzr: ERROR: Unsupported timezone format "foo", '
365
'options are "utc", "original", "local".'],
366
['log', '--timezone', 'foo'])
368
def test_log_exclude_ancestry_no_range(self):
369
self.make_linear_branch()
370
self.run_bzr_error(['bzr: ERROR: --exclude-common-ancestry'
371
' requires -r with two revisions'],
372
['log', '--exclude-common-ancestry'])
374
def test_log_exclude_ancestry_single_revision(self):
375
self.make_merged_branch()
376
self.run_bzr_error(['bzr: ERROR: --exclude-common-ancestry'
377
' requires two different revisions'],
378
['log', '--exclude-common-ancestry',
381
class TestLogTags(TestLog):
383
def test_log_with_tags(self):
384
tree = self.make_linear_branch(format='dirstate-tags')
386
branch.tags.set_tag('tag1', branch.get_rev_id(1))
387
branch.tags.set_tag('tag1.1', branch.get_rev_id(1))
388
branch.tags.set_tag('tag3', branch.last_revision())
390
log = self.run_bzr("log -r-1")[0]
391
self.assertTrue('tags: tag3' in log)
393
log = self.run_bzr("log -r1")[0]
394
# I guess that we can't know the order of tags in the output
395
# since dicts are unordered, need to check both possibilities
396
self.assertContainsRe(log, r'tags: (tag1, tag1\.1|tag1\.1, tag1)')
398
def test_merged_log_with_tags(self):
399
branch1_tree = self.make_linear_branch('branch1',
400
format='dirstate-tags')
401
branch1 = branch1_tree.branch
402
branch2_tree = branch1_tree.bzrdir.sprout('branch2').open_workingtree()
403
branch1_tree.commit(message='foobar', allow_pointless=True)
404
branch1.tags.set_tag('tag1', branch1.last_revision())
405
# tags don't propagate if we don't merge
406
self.run_bzr('merge ../branch1', working_dir='branch2')
407
branch2_tree.commit(message='merge branch 1')
408
log = self.run_bzr("log -n0 -r-1", working_dir='branch2')[0]
409
self.assertContainsRe(log, r' tags: tag1')
410
log = self.run_bzr("log -n0 -r3.1.1", working_dir='branch2')[0]
411
self.assertContainsRe(log, r'tags: tag1')
414
class TestLogVerbose(TestLog):
417
super(TestLogVerbose, self).setUp()
418
self.make_minimal_branch()
420
def assertUseShortDeltaFormat(self, cmd):
421
log = self.run_bzr(cmd)[0]
422
# Check that we use the short status format
423
self.assertContainsRe(log, '(?m)^\s*A hello.txt$')
424
self.assertNotContainsRe(log, '(?m)^\s*added:$')
426
def assertUseLongDeltaFormat(self, cmd):
427
log = self.run_bzr(cmd)[0]
428
# Check that we use the long status format
429
self.assertNotContainsRe(log, '(?m)^\s*A hello.txt$')
430
self.assertContainsRe(log, '(?m)^\s*added:$')
432
def test_log_short_verbose(self):
433
self.assertUseShortDeltaFormat(['log', '--short', '-v'])
435
def test_log_short_verbose_verbose(self):
436
self.assertUseLongDeltaFormat(['log', '--short', '-vv'])
438
def test_log_long_verbose(self):
439
# Check that we use the long status format, ignoring the verbosity
441
self.assertUseLongDeltaFormat(['log', '--long', '-v'])
443
def test_log_long_verbose_verbose(self):
444
# Check that we use the long status format, ignoring the verbosity
446
self.assertUseLongDeltaFormat(['log', '--long', '-vv'])
449
class TestLogMerges(TestLogWithLogCatcher):
452
super(TestLogMerges, self).setUp()
453
self.make_branches_with_merges()
455
def make_branches_with_merges(self):
456
level0 = self.make_branch_and_tree('level0')
457
self.wt_commit(level0, 'in branch level0')
458
level1 = level0.bzrdir.sprout('level1').open_workingtree()
459
self.wt_commit(level1, 'in branch level1')
460
level2 = level1.bzrdir.sprout('level2').open_workingtree()
461
self.wt_commit(level2, 'in branch level2')
462
level1.merge_from_branch(level2.branch)
463
self.wt_commit(level1, 'merge branch level2')
464
level0.merge_from_branch(level1.branch)
465
self.wt_commit(level0, 'merge branch level1')
80
log = self.runbzr("log -r -2..")[0]
81
self.assertTrue('revno: 1\n' not in log)
82
self.assertTrue('revno: 2\n' in log)
83
self.assertTrue('revno: 3\n' in log)
85
def test_log_postive_revspecs(self):
87
log = self.runbzr("log -r 1..3")[0]
88
self.assertEquals(self.full_log, log)
91
class TestLogMerges(ExternalBase):
467
93
def test_merges_are_indented_by_level(self):
468
self.run_bzr(['log', '-n0'], working_dir='level0')
469
revnos_and_depth = [(r.revno, r.merge_depth)
470
for r in self.get_captured_revisions()]
471
self.assertEqual([('2', 0), ('1.1.2', 1), ('1.2.1', 2), ('1.1.1', 1),
473
[(r.revno, r.merge_depth)
474
for r in self.get_captured_revisions()])
476
def test_force_merge_revisions_off(self):
477
self.assertLogRevnos(['-n1'], ['2', '1'], working_dir='level0')
479
def test_force_merge_revisions_on(self):
480
self.assertLogRevnos(['-n0'], ['2', '1.1.2', '1.2.1', '1.1.1', '1'],
481
working_dir='level0')
483
def test_include_merges(self):
484
# Confirm --include-merges gives the same output as -n0
485
self.assertLogRevnos(['--include-merges'],
486
['2', '1.1.2', '1.2.1', '1.1.1', '1'],
487
working_dir='level0')
488
self.assertLogRevnos(['--include-merges'],
489
['2', '1.1.2', '1.2.1', '1.1.1', '1'],
490
working_dir='level0')
491
out_im, err_im = self.run_bzr('log --include-merges',
492
working_dir='level0')
493
out_n0, err_n0 = self.run_bzr('log -n0', working_dir='level0')
494
self.assertEqual('', err_im)
495
self.assertEqual('', err_n0)
496
self.assertEqual(out_im, out_n0)
498
def test_force_merge_revisions_N(self):
499
self.assertLogRevnos(['-n2'],
500
['2', '1.1.2', '1.1.1', '1'],
501
working_dir='level0')
503
def test_merges_single_merge_rev(self):
504
self.assertLogRevnosAndDepths(['-n0', '-r1.1.2'],
505
[('1.1.2', 0), ('1.2.1', 1)],
506
working_dir='level0')
508
def test_merges_partial_range(self):
509
self.assertLogRevnosAndDepths(
510
['-n0', '-r1.1.1..1.1.2'],
511
[('1.1.2', 0), ('1.2.1', 1), ('1.1.1', 0)],
512
working_dir='level0')
514
def test_merges_partial_range_ignore_before_lower_bound(self):
515
"""Dont show revisions before the lower bound's merged revs"""
516
self.assertLogRevnosAndDepths(
517
['-n0', '-r1.1.2..2'],
518
[('2', 0), ('1.1.2', 1), ('1.2.1', 2)],
519
working_dir='level0')
522
class TestLogDiff(TestLogWithLogCatcher):
524
# FIXME: We need specific tests for each LogFormatter about how the diffs
525
# are displayed: --long indent them by depth, --short use a fixed
526
# indent and --line does't display them. -- vila 10019
529
super(TestLogDiff, self).setUp()
530
self.make_branch_with_diffs()
532
def make_branch_with_diffs(self):
533
level0 = self.make_branch_and_tree('level0')
534
self.build_tree(['level0/file1', 'level0/file2'])
537
self.wt_commit(level0, 'in branch level0')
539
level1 = level0.bzrdir.sprout('level1').open_workingtree()
540
self.build_tree_contents([('level1/file2', 'hello\n')])
541
self.wt_commit(level1, 'in branch level1')
542
level0.merge_from_branch(level1.branch)
543
self.wt_commit(level0, 'merge branch level1')
545
def _diff_file1_revno1(self):
546
return """=== added file 'file1'
547
--- file1\t1970-01-01 00:00:00 +0000
548
+++ file1\t2005-11-22 00:00:00 +0000
550
+contents of level0/file1
554
def _diff_file2_revno2(self):
555
return """=== modified file 'file2'
556
--- file2\t2005-11-22 00:00:00 +0000
557
+++ file2\t2005-11-22 00:00:01 +0000
559
-contents of level0/file2
564
def _diff_file2_revno1_1_1(self):
565
return """=== modified file 'file2'
566
--- file2\t2005-11-22 00:00:00 +0000
567
+++ file2\t2005-11-22 00:00:01 +0000
569
-contents of level0/file2
574
def _diff_file2_revno1(self):
575
return """=== added file 'file2'
576
--- file2\t1970-01-01 00:00:00 +0000
577
+++ file2\t2005-11-22 00:00:00 +0000
579
+contents of level0/file2
583
def assertLogRevnosAndDiff(self, args, expected,
585
self.run_bzr(['log', '-p'] + args, working_dir=working_dir)
586
expected_revnos_and_depths = [
587
(revno, depth) for revno, depth, diff in expected]
588
# Check the revnos and depths first to make debugging easier
589
self.assertEqual(expected_revnos_and_depths,
590
[(r.revno, r.merge_depth)
591
for r in self.get_captured_revisions()])
592
# Now check the diffs, adding the revno in case of failure
593
fmt = 'In revno %s\n%s'
594
for expected_rev, actual_rev in izip(expected,
595
self.get_captured_revisions()):
596
revno, depth, expected_diff = expected_rev
597
actual_diff = actual_rev.diff
598
self.assertEqualDiff(fmt % (revno, expected_diff),
599
fmt % (revno, actual_diff))
601
def test_log_diff_with_merges(self):
602
self.assertLogRevnosAndDiff(
604
[('2', 0, self._diff_file2_revno2()),
605
('1.1.1', 1, self._diff_file2_revno1_1_1()),
606
('1', 0, self._diff_file1_revno1()
607
+ self._diff_file2_revno1())],
608
working_dir='level0')
611
def test_log_diff_file1(self):
612
self.assertLogRevnosAndDiff(['-n0', 'file1'],
613
[('1', 0, self._diff_file1_revno1())],
614
working_dir='level0')
616
def test_log_diff_file2(self):
617
self.assertLogRevnosAndDiff(['-n1', 'file2'],
618
[('2', 0, self._diff_file2_revno2()),
619
('1', 0, self._diff_file2_revno1())],
620
working_dir='level0')
623
class TestLogUnicodeDiff(TestLog):
625
def test_log_show_diff_non_ascii(self):
626
# Smoke test for bug #328007 UnicodeDecodeError on 'log -p'
627
message = u'Message with \xb5'
628
body = 'Body with \xb5\n'
629
wt = self.make_branch_and_tree('.')
630
self.build_tree_contents([('foo', body)])
632
wt.commit(message=message)
633
# check that command won't fail with unicode error
634
# don't care about exact output because we have other tests for this
635
out,err = self.run_bzr('log -p --long')
636
self.assertNotEqual('', out)
637
self.assertEqual('', err)
638
out,err = self.run_bzr('log -p --short')
639
self.assertNotEqual('', out)
640
self.assertEqual('', err)
641
out,err = self.run_bzr('log -p --line')
642
self.assertNotEqual('', out)
643
self.assertEqual('', err)
646
class TestLogEncodings(tests.TestCaseInTempDir):
649
_message = u'Message with \xb5'
651
# Encodings which can encode mu
656
'cp437', # Common windows encoding
657
'cp1251', # Russian windows encoding
658
'cp1258', # Common windows encoding
660
# Encodings which cannot encode mu
668
super(TestLogEncodings, self).setUp()
669
self.overrideAttr(osutils, '_cached_user_encoding')
671
def create_branch(self):
674
self.build_tree_contents([('a', 'some stuff\n')])
676
bzr(['commit', '-m', self._message])
678
def try_encoding(self, encoding, fail=False):
681
self.assertRaises(UnicodeEncodeError,
682
self._mu.encode, encoding)
683
encoded_msg = self._message.encode(encoding, 'replace')
685
encoded_msg = self._message.encode(encoding)
687
old_encoding = osutils._cached_user_encoding
688
# This test requires that 'run_bzr' uses the current
689
# bzrlib, because we override user_encoding, and expect
692
osutils._cached_user_encoding = 'ascii'
693
# We should be able to handle any encoding
694
out, err = bzr('log', encoding=encoding)
696
# Make sure we wrote mu as we expected it to exist
697
self.assertNotEqual(-1, out.find(encoded_msg))
698
out_unicode = out.decode(encoding)
699
self.assertNotEqual(-1, out_unicode.find(self._message))
701
self.assertNotEqual(-1, out.find('Message with ?'))
703
osutils._cached_user_encoding = old_encoding
705
def test_log_handles_encoding(self):
708
for encoding in self.good_encodings:
709
self.try_encoding(encoding)
711
def test_log_handles_bad_encoding(self):
714
for encoding in self.bad_encodings:
715
self.try_encoding(encoding, fail=True)
717
def test_stdout_encoding(self):
719
osutils._cached_user_encoding = "cp1251"
722
self.build_tree(['a'])
724
bzr(['commit', '-m', u'\u0422\u0435\u0441\u0442'])
725
stdout, stderr = self.run_bzr('log', encoding='cp866')
727
message = stdout.splitlines()[-1]
729
# explanation of the check:
730
# u'\u0422\u0435\u0441\u0442' is word 'Test' in russian
731
# in cp866 encoding this is string '\x92\xa5\xe1\xe2'
732
# in cp1251 encoding this is string '\xd2\xe5\xf1\xf2'
733
# This test should check that output of log command
734
# encoded to sys.stdout.encoding
735
test_in_cp866 = '\x92\xa5\xe1\xe2'
736
test_in_cp1251 = '\xd2\xe5\xf1\xf2'
737
# Make sure the log string is encoded in cp866
738
self.assertEquals(test_in_cp866, message[2:])
739
# Make sure the cp1251 string is not found anywhere
740
self.assertEquals(-1, stdout.find(test_in_cp1251))
743
class TestLogFile(TestLogWithLogCatcher):
745
def test_log_local_branch_file(self):
746
"""We should be able to log files in local treeless branches"""
747
tree = self.make_branch_and_tree('tree')
748
self.build_tree(['tree/file'])
750
tree.commit('revision 1')
751
tree.bzrdir.destroy_workingtree()
752
self.run_bzr('log tree/file')
754
def prepare_tree(self, complex=False):
755
# The complex configuration includes deletes and renames
756
tree = self.make_branch_and_tree('parent')
757
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
759
tree.commit('add file1')
761
tree.commit('add file2')
763
tree.commit('add file3')
764
child_tree = tree.bzrdir.sprout('child').open_workingtree()
765
self.build_tree_contents([('child/file2', 'hello')])
766
child_tree.commit(message='branch 1')
767
tree.merge_from_branch(child_tree.branch)
768
tree.commit(message='merge child branch')
771
tree.commit('remove file2')
772
tree.rename_one('file3', 'file4')
773
tree.commit('file3 is now called file4')
775
tree.commit('remove file1')
778
# FIXME: It would be good to parametrize the following tests against all
779
# formatters. But the revisions selection is not *currently* part of the
780
# LogFormatter contract, so using LogCatcher is sufficient -- vila 100118
781
def test_log_file1(self):
783
self.assertLogRevnos(['-n0', 'file1'], ['1'])
785
def test_log_file2(self):
788
self.assertLogRevnos(['-n0', 'file2'], ['4', '3.1.1', '2'])
789
# file2 in a merge revision
790
self.assertLogRevnos(['-n0', '-r3.1.1', 'file2'], ['3.1.1'])
791
# file2 in a mainline revision
792
self.assertLogRevnos(['-n0', '-r4', 'file2'], ['4', '3.1.1'])
793
# file2 since a revision
794
self.assertLogRevnos(['-n0', '-r3..', 'file2'], ['4', '3.1.1'])
795
# file2 up to a revision
796
self.assertLogRevnos(['-n0', '-r..3', 'file2'], ['2'])
798
def test_log_file3(self):
800
self.assertLogRevnos(['-n0', 'file3'], ['3'])
802
def test_log_file_historical_missing(self):
803
# Check logging a deleted file gives an error if the
804
# file isn't found at the end or start of the revision range
805
self.prepare_tree(complex=True)
806
err_msg = "Path unknown at end or start of revision range: file2"
807
err = self.run_bzr('log file2', retcode=3)[1]
808
self.assertContainsRe(err, err_msg)
810
def test_log_file_historical_end(self):
811
# Check logging a deleted file is ok if the file existed
812
# at the end the revision range
813
self.prepare_tree(complex=True)
814
self.assertLogRevnos(['-n0', '-r..4', 'file2'], ['4', '3.1.1', '2'])
816
def test_log_file_historical_start(self):
817
# Check logging a deleted file is ok if the file existed
818
# at the start of the revision range
819
self.prepare_tree(complex=True)
820
self.assertLogRevnos(['file1'], ['1'])
822
def test_log_file_renamed(self):
823
"""File matched against revision range, not current tree."""
824
self.prepare_tree(complex=True)
826
# Check logging a renamed file gives an error by default
827
err_msg = "Path unknown at end or start of revision range: file3"
828
err = self.run_bzr('log file3', retcode=3)[1]
829
self.assertContainsRe(err, err_msg)
831
# Check we can see a renamed file if we give the right end revision
832
self.assertLogRevnos(['-r..4', 'file3'], ['3'])
835
class TestLogMultiple(TestLogWithLogCatcher):
837
def prepare_tree(self):
838
tree = self.make_branch_and_tree('parent')
845
'parent/dir1/dir2/file3',
848
tree.commit('add file1')
850
tree.commit('add file2')
851
tree.add(['dir1', 'dir1/dir2', 'dir1/dir2/file3'])
852
tree.commit('add file3')
854
tree.commit('add file4')
855
tree.add('dir1/file5')
856
tree.commit('add file5')
857
child_tree = tree.bzrdir.sprout('child').open_workingtree()
858
self.build_tree_contents([('child/file2', 'hello')])
859
child_tree.commit(message='branch 1')
860
tree.merge_from_branch(child_tree.branch)
861
tree.commit(message='merge child branch')
864
def test_log_files(self):
865
"""The log for multiple file should only list revs for those files"""
867
self.assertLogRevnos(['file1', 'file2', 'dir1/dir2/file3'],
868
['6', '5.1.1', '3', '2', '1'])
870
def test_log_directory(self):
871
"""The log for a directory should show all nested files."""
873
self.assertLogRevnos(['dir1'], ['5', '3'])
875
def test_log_nested_directory(self):
876
"""The log for a directory should show all nested files."""
878
self.assertLogRevnos(['dir1/dir2'], ['3'])
880
def test_log_in_nested_directory(self):
881
"""The log for a directory should show all nested files."""
884
self.assertLogRevnos(['.'], ['5', '3'])
886
def test_log_files_and_directories(self):
887
"""Logging files and directories together should be fine."""
889
self.assertLogRevnos(['file4', 'dir1/dir2'], ['4', '3'])
891
def test_log_files_and_dirs_in_nested_directory(self):
892
"""The log for a directory should show all nested files."""
895
self.assertLogRevnos(['dir2', 'file5'], ['5', '3'])
94
self.build_tree(['parent/'])
95
self.run_bzr('init', 'parent')
96
self.run_bzr('commit', '-m', 'first post', '--unchanged', 'parent')
97
self.run_bzr('branch', 'parent', 'child')
98
self.run_bzr('commit', '-m', 'branch 1', '--unchanged', 'child')
99
self.run_bzr('branch', 'child', 'smallerchild')
100
self.run_bzr('commit', '-m', 'branch 2', '--unchanged', 'smallerchild')
102
self.run_bzr('merge', '../smallerchild')
103
self.run_bzr('commit', '-m', 'merge branch 2')
104
os.chdir('../parent')
105
self.run_bzr('merge', '../child')
106
self.run_bzr('commit', '-m', 'merge branch 1')
107
out,err = self.run_bzr('log')
108
# the log will look something like:
109
# self.assertEqual("""\
110
#------------------------------------------------------------
112
#committer: Robert Collins <foo@example.com>
114
#timestamp: Tue 2006-03-28 22:31:40 +1100
117
# ------------------------------------------------------------
118
# merged: foo@example.com-20060328113140-91f43cfb46dc2863
119
# committer: Robert Collins <foo@example.com>
121
# timestamp: Tue 2006-03-28 22:31:40 +1100
124
# ------------------------------------------------------------
125
# merged: foo@example.com-20060328113140-1ba24f850a0ef573
126
# committer: Robert Collins <foo@example.com>
127
# branch nick: smallerchild
128
# timestamp: Tue 2006-03-28 22:31:40 +1100
131
# ------------------------------------------------------------
132
# merged: foo@example.com-20060328113140-5749a4757a8ac792
133
# committer: Robert Collins <foo@example.com>
135
# timestamp: Tue 2006-03-28 22:31:40 +1100
138
#------------------------------------------------------------
140
#committer: Robert Collins <foo@example.com>
142
#timestamp: Tue 2006-03-28 22:31:39 +1100
146
# but we dont have a nice pattern matcher hooked up yet, so:
147
# we check for the indenting of the commit message:
148
self.assertTrue(' merge branch 1' in out)
149
self.assertTrue(' merge branch 2' in out)
150
self.assertTrue(' branch 2' in out)
151
self.assertTrue(' branch 1' in out)
152
self.assertTrue(' first post' in out)
153
self.assertEqual('', err)