1
# Copyright (C) 2005 by Canonical Ltd
2
# -*- coding: utf-8 -*-
1
# Copyright (C) 2006-2010 Canonical Ltd
4
3
# This program is free software; you can redistribute it and/or modify
5
4
# it under the terms of the GNU General Public License as published by
6
5
# the Free Software Foundation; either version 2 of the License, or
7
6
# (at your option) any later version.
9
8
# This program is distributed in the hope that it will be useful,
10
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
11
# GNU General Public License for more details.
14
13
# You should have received a copy of the GNU General Public License
15
14
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
18
"""Black-box tests for bzr log."""
20
from itertools import izip
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]
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
78
# Break cycle with closure over self on cleanup by removing method
79
self.addCleanup(setattr, MyLogFormatter, "__new__", None)
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'])
39
110
def test_log_null_end_revspec(self):
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)
111
self.make_linear_branch()
112
self.assertLogRevnos(['-r1..'], ['3', '2', '1'])
51
114
def test_log_null_begin_revspec(self):
53
log = self.runbzr("log -r ..3")[0]
54
self.assertEquals(self.full_log, log)
115
self.make_linear_branch()
116
self.assertLogRevnos(['-r..3'], ['3', '2', '1'])
56
118
def test_log_null_both_revspecs(self):
58
log = self.runbzr("log -r ..")[0]
59
self.assertEquals(self.full_log, log)
119
self.make_linear_branch()
120
self.assertLogRevnos(['-r..'], ['3', '2', '1'])
61
122
def test_log_negative_begin_revspec_full_log(self):
63
log = self.runbzr("log -r -3..")[0]
64
self.assertEquals(self.full_log, log)
123
self.make_linear_branch()
124
self.assertLogRevnos(['-r-3..'], ['3', '2', '1'])
66
126
def test_log_negative_both_revspec_full_log(self):
68
log = self.runbzr("log -r -3..-1")[0]
69
self.assertEquals(self.full_log, log)
127
self.make_linear_branch()
128
self.assertLogRevnos(['-r-3..-1'], ['3', '2', '1'])
71
130
def test_log_negative_both_revspec_partial(self):
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)
131
self.make_linear_branch()
132
self.assertLogRevnos(['-r-3..-2'], ['2', '1'])
78
134
def test_log_negative_begin_revspec(self):
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):
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'])
161
def test_branch_revspec(self):
162
foo = self.make_branch_and_tree('foo')
163
bar = self.make_branch_and_tree('bar')
164
self.build_tree(['foo/foo.txt', 'bar/bar.txt'])
167
foo.commit(message='foo')
168
bar.commit(message='bar')
169
self.run_bzr('log -r branch:../bar', working_dir='foo')
170
self.assertEqual([bar.branch.get_rev_id(1)],
172
for r in self.get_captured_revisions()])
175
class TestLogExcludeCommonAncestry(TestLogWithLogCatcher):
177
def test_exclude_common_ancestry_simple_revnos(self):
178
self.make_linear_branch()
179
self.assertLogRevnos(['-r1..3', '--exclude-common-ancestry'],
183
class TestLogMergedLinearAncestry(TestLogWithLogCatcher):
186
super(TestLogMergedLinearAncestry, self).setUp()
187
# FIXME: Using a MemoryTree would be even better here (but until we
188
# stop calling run_bzr, there is no point) --vila 100118.
189
builder = branchbuilder.BranchBuilder(self.get_transport())
190
builder.start_series()
204
builder.build_snapshot('1', None, [
205
('add', ('', 'root-id', 'directory', ''))])
206
builder.build_snapshot('2', ['1'], [])
208
builder.build_snapshot('1.1.1', ['1'], [])
209
# merge branch into mainline
210
builder.build_snapshot('3', ['2', '1.1.1'], [])
211
# new commits in branch
212
builder.build_snapshot('1.1.2', ['1.1.1'], [])
213
builder.build_snapshot('1.1.3', ['1.1.2'], [])
214
# merge branch into mainline
215
builder.build_snapshot('4', ['3', '1.1.3'], [])
216
# merge mainline into branch
217
builder.build_snapshot('1.1.4', ['1.1.3', '4'], [])
218
# merge branch into mainline
219
builder.build_snapshot('5', ['4', '1.1.4'], [])
220
builder.finish_series()
223
self.assertLogRevnos(['-n0', '-r1.1.1..1.1.4'],
224
['1.1.4', '4', '1.1.3', '1.1.2', '3', '1.1.1'])
225
def test_n0_forward(self):
226
self.assertLogRevnos(['-n0', '-r1.1.1..1.1.4', '--forward'],
227
['3', '1.1.1', '4', '1.1.2', '1.1.3', '1.1.4'])
230
# starting from 1.1.4 we follow the left-hand ancestry
231
self.assertLogRevnos(['-n1', '-r1.1.1..1.1.4'],
232
['1.1.4', '1.1.3', '1.1.2', '1.1.1'])
234
def test_n1_forward(self):
235
self.assertLogRevnos(['-n1', '-r1.1.1..1.1.4', '--forward'],
236
['1.1.1', '1.1.2', '1.1.3', '1.1.4'])
239
class Test_GenerateAllRevisions(TestLogWithLogCatcher):
242
super(Test_GenerateAllRevisions, self).setUp()
243
builder = self.make_branch_with_many_merges()
244
b = builder.get_branch()
246
self.addCleanup(b.unlock)
249
def make_branch_with_many_merges(self, path='.', format=None):
250
builder = branchbuilder.BranchBuilder(self.get_transport())
251
builder.start_series()
252
# The graph below may look a bit complicated (and it may be but I've
253
# banged my head enough on it) but the bug requires at least dotted
254
# revnos *and* merged revisions below that.
255
builder.build_snapshot('1', None, [
256
('add', ('', 'root-id', 'directory', ''))])
257
builder.build_snapshot('2', ['1'], [])
258
builder.build_snapshot('1.1.1', ['1'], [])
259
builder.build_snapshot('2.1.1', ['2'], [])
260
builder.build_snapshot('3', ['2', '1.1.1'], [])
261
builder.build_snapshot('2.1.2', ['2.1.1'], [])
262
builder.build_snapshot('2.2.1', ['2.1.1'], [])
263
builder.build_snapshot('2.1.3', ['2.1.2', '2.2.1'], [])
264
builder.build_snapshot('4', ['3', '2.1.3'], [])
265
builder.build_snapshot('5', ['4', '2.1.2'], [])
266
builder.finish_series()
269
def test_not_an_ancestor(self):
270
self.assertRaises(errors.BzrCommandError,
271
log._generate_all_revisions,
272
self.branch, '1.1.1', '2.1.3', 'reverse',
273
delayed_graph_generation=True)
275
def test_wrong_order(self):
276
self.assertRaises(errors.BzrCommandError,
277
log._generate_all_revisions,
278
self.branch, '5', '2.1.3', 'reverse',
279
delayed_graph_generation=True)
281
def test_no_start_rev_id_with_end_rev_id_being_a_merge(self):
282
revs = log._generate_all_revisions(
283
self.branch, None, '2.1.3',
284
'reverse', delayed_graph_generation=True)
287
class TestLogRevSpecsWithPaths(TestLogWithLogCatcher):
289
def test_log_revno_n_path_wrong_namespace(self):
290
self.make_linear_branch('branch1')
291
self.make_linear_branch('branch2')
292
# There is no guarantee that a path exist between two arbitrary
294
self.run_bzr("log -r revno:2:branch1..revno:3:branch2", retcode=3)
296
def test_log_revno_n_path_correct_order(self):
297
self.make_linear_branch('branch2')
298
self.assertLogRevnos(['-rrevno:1:branch2..revno:3:branch2'],
301
def test_log_revno_n_path(self):
302
self.make_linear_branch('branch2')
303
self.assertLogRevnos(['-rrevno:1:branch2'],
305
rev_props = self.log_catcher.revisions[0].rev.properties
306
self.assertEqual('branch2', rev_props['branch-nick'])
309
class TestLogErrors(TestLog):
311
def test_log_zero_revspec(self):
312
self.make_minimal_branch()
313
self.run_bzr_error(['bzr: ERROR: Logging revision 0 is invalid.'],
316
def test_log_zero_begin_revspec(self):
317
self.make_linear_branch()
318
self.run_bzr_error(['bzr: ERROR: Logging revision 0 is invalid.'],
321
def test_log_zero_end_revspec(self):
322
self.make_linear_branch()
323
self.run_bzr_error(['bzr: ERROR: Logging revision 0 is invalid.'],
326
def test_log_nonexistent_revno(self):
327
self.make_minimal_branch()
328
self.run_bzr_error(["bzr: ERROR: Requested revision: '1234' "
329
"does not exist in branch:"],
332
def test_log_nonexistent_dotted_revno(self):
333
self.make_minimal_branch()
334
self.run_bzr_error(["bzr: ERROR: Requested revision: '123.123' "
335
"does not exist in branch:"],
336
['log', '-r123.123'])
338
def test_log_change_nonexistent_revno(self):
339
self.make_minimal_branch()
340
self.run_bzr_error(["bzr: ERROR: Requested revision: '1234' "
341
"does not exist in branch:"],
344
def test_log_change_nonexistent_dotted_revno(self):
345
self.make_minimal_branch()
346
self.run_bzr_error(["bzr: ERROR: Requested revision: '123.123' "
347
"does not exist in branch:"],
348
['log', '-c123.123'])
350
def test_log_change_single_revno_only(self):
351
self.make_minimal_branch()
352
self.run_bzr_error(['bzr: ERROR: Option --change does not'
353
' accept revision ranges'],
354
['log', '--change', '2..3'])
356
def test_log_change_incompatible_with_revision(self):
357
self.run_bzr_error(['bzr: ERROR: --revision and --change'
358
' are mutually exclusive'],
359
['log', '--change', '2', '--revision', '3'])
361
def test_log_nonexistent_file(self):
362
self.make_minimal_branch()
363
# files that don't exist in either the basis tree or working tree
364
# should give an error
365
out, err = self.run_bzr('log does-not-exist', retcode=3)
366
self.assertContainsRe(err,
367
'Path unknown at end or start of revision range: '
370
def test_log_reversed_revspecs(self):
371
self.make_linear_branch()
372
self.run_bzr_error(('bzr: ERROR: Start revision must be older than '
373
'the end revision.\n',),
376
def test_log_reversed_dotted_revspecs(self):
377
self.make_merged_branch()
378
self.run_bzr_error(('bzr: ERROR: Start revision not found in '
379
'left-hand history of end revision.\n',),
382
def test_log_bad_message_re(self):
383
"""Bad --message argument gives a sensible message
385
See https://bugs.launchpad.net/bzr/+bug/251352
387
self.make_minimal_branch()
388
out, err = self.run_bzr(['log', '-m', '*'], retcode=3)
389
self.assertContainsRe(err, "ERROR.*Invalid pattern.*nothing to repeat")
390
self.assertNotContainsRe(err, "Unprintable exception")
391
self.assertEqual(out, '')
393
def test_log_unsupported_timezone(self):
394
self.make_linear_branch()
395
self.run_bzr_error(['bzr: ERROR: Unsupported timezone format "foo", '
396
'options are "utc", "original", "local".'],
397
['log', '--timezone', 'foo'])
399
def test_log_exclude_ancestry_no_range(self):
400
self.make_linear_branch()
401
self.run_bzr_error(['bzr: ERROR: --exclude-common-ancestry'
402
' requires -r with two revisions'],
403
['log', '--exclude-common-ancestry'])
405
def test_log_exclude_ancestry_single_revision(self):
406
self.make_merged_branch()
407
self.run_bzr_error(['bzr: ERROR: --exclude-common-ancestry'
408
' requires two different revisions'],
409
['log', '--exclude-common-ancestry',
412
class TestLogTags(TestLog):
414
def test_log_with_tags(self):
415
tree = self.make_linear_branch(format='dirstate-tags')
417
branch.tags.set_tag('tag1', branch.get_rev_id(1))
418
branch.tags.set_tag('tag1.1', branch.get_rev_id(1))
419
branch.tags.set_tag('tag3', branch.last_revision())
421
log = self.run_bzr("log -r-1")[0]
422
self.assertTrue('tags: tag3' in log)
424
log = self.run_bzr("log -r1")[0]
425
# I guess that we can't know the order of tags in the output
426
# since dicts are unordered, need to check both possibilities
427
self.assertContainsRe(log, r'tags: (tag1, tag1\.1|tag1\.1, tag1)')
429
def test_merged_log_with_tags(self):
430
branch1_tree = self.make_linear_branch('branch1',
431
format='dirstate-tags')
432
branch1 = branch1_tree.branch
433
branch2_tree = branch1_tree.bzrdir.sprout('branch2').open_workingtree()
434
branch1_tree.commit(message='foobar', allow_pointless=True)
435
branch1.tags.set_tag('tag1', branch1.last_revision())
436
# tags don't propagate if we don't merge
437
self.run_bzr('merge ../branch1', working_dir='branch2')
438
branch2_tree.commit(message='merge branch 1')
439
log = self.run_bzr("log -n0 -r-1", working_dir='branch2')[0]
440
self.assertContainsRe(log, r' tags: tag1')
441
log = self.run_bzr("log -n0 -r3.1.1", working_dir='branch2')[0]
442
self.assertContainsRe(log, r'tags: tag1')
445
class TestLogVerbose(TestLog):
448
super(TestLogVerbose, self).setUp()
449
self.make_minimal_branch()
451
def assertUseShortDeltaFormat(self, cmd):
452
log = self.run_bzr(cmd)[0]
453
# Check that we use the short status format
454
self.assertContainsRe(log, '(?m)^\s*A hello.txt$')
455
self.assertNotContainsRe(log, '(?m)^\s*added:$')
457
def assertUseLongDeltaFormat(self, cmd):
458
log = self.run_bzr(cmd)[0]
459
# Check that we use the long status format
460
self.assertNotContainsRe(log, '(?m)^\s*A hello.txt$')
461
self.assertContainsRe(log, '(?m)^\s*added:$')
463
def test_log_short_verbose(self):
464
self.assertUseShortDeltaFormat(['log', '--short', '-v'])
466
def test_log_short_verbose_verbose(self):
467
self.assertUseLongDeltaFormat(['log', '--short', '-vv'])
469
def test_log_long_verbose(self):
470
# Check that we use the long status format, ignoring the verbosity
472
self.assertUseLongDeltaFormat(['log', '--long', '-v'])
474
def test_log_long_verbose_verbose(self):
475
# Check that we use the long status format, ignoring the verbosity
477
self.assertUseLongDeltaFormat(['log', '--long', '-vv'])
480
class TestLogMerges(TestLogWithLogCatcher):
483
super(TestLogMerges, self).setUp()
484
self.make_branches_with_merges()
486
def make_branches_with_merges(self):
487
level0 = self.make_branch_and_tree('level0')
488
self.wt_commit(level0, 'in branch level0')
489
level1 = level0.bzrdir.sprout('level1').open_workingtree()
490
self.wt_commit(level1, 'in branch level1')
491
level2 = level1.bzrdir.sprout('level2').open_workingtree()
492
self.wt_commit(level2, 'in branch level2')
493
level1.merge_from_branch(level2.branch)
494
self.wt_commit(level1, 'merge branch level2')
495
level0.merge_from_branch(level1.branch)
496
self.wt_commit(level0, 'merge branch level1')
93
498
def test_merges_are_indented_by_level(self):
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)
499
self.run_bzr(['log', '-n0'], working_dir='level0')
500
revnos_and_depth = [(r.revno, r.merge_depth)
501
for r in self.get_captured_revisions()]
502
self.assertEqual([('2', 0), ('1.1.2', 1), ('1.2.1', 2), ('1.1.1', 1),
504
[(r.revno, r.merge_depth)
505
for r in self.get_captured_revisions()])
507
def test_force_merge_revisions_off(self):
508
self.assertLogRevnos(['-n1'], ['2', '1'], working_dir='level0')
510
def test_force_merge_revisions_on(self):
511
self.assertLogRevnos(['-n0'], ['2', '1.1.2', '1.2.1', '1.1.1', '1'],
512
working_dir='level0')
514
def test_include_merges(self):
515
# Confirm --include-merges gives the same output as -n0
516
self.assertLogRevnos(['--include-merges'],
517
['2', '1.1.2', '1.2.1', '1.1.1', '1'],
518
working_dir='level0')
519
self.assertLogRevnos(['--include-merges'],
520
['2', '1.1.2', '1.2.1', '1.1.1', '1'],
521
working_dir='level0')
522
out_im, err_im = self.run_bzr('log --include-merges',
523
working_dir='level0')
524
out_n0, err_n0 = self.run_bzr('log -n0', working_dir='level0')
525
self.assertEqual('', err_im)
526
self.assertEqual('', err_n0)
527
self.assertEqual(out_im, out_n0)
529
def test_force_merge_revisions_N(self):
530
self.assertLogRevnos(['-n2'],
531
['2', '1.1.2', '1.1.1', '1'],
532
working_dir='level0')
534
def test_merges_single_merge_rev(self):
535
self.assertLogRevnosAndDepths(['-n0', '-r1.1.2'],
536
[('1.1.2', 0), ('1.2.1', 1)],
537
working_dir='level0')
539
def test_merges_partial_range(self):
540
self.assertLogRevnosAndDepths(
541
['-n0', '-r1.1.1..1.1.2'],
542
[('1.1.2', 0), ('1.2.1', 1), ('1.1.1', 0)],
543
working_dir='level0')
545
def test_merges_partial_range_ignore_before_lower_bound(self):
546
"""Dont show revisions before the lower bound's merged revs"""
547
self.assertLogRevnosAndDepths(
548
['-n0', '-r1.1.2..2'],
549
[('2', 0), ('1.1.2', 1), ('1.2.1', 2)],
550
working_dir='level0')
553
class TestLogDiff(TestLogWithLogCatcher):
555
# FIXME: We need specific tests for each LogFormatter about how the diffs
556
# are displayed: --long indent them by depth, --short use a fixed
557
# indent and --line does't display them. -- vila 10019
560
super(TestLogDiff, self).setUp()
561
self.make_branch_with_diffs()
563
def make_branch_with_diffs(self):
564
level0 = self.make_branch_and_tree('level0')
565
self.build_tree(['level0/file1', 'level0/file2'])
568
self.wt_commit(level0, 'in branch level0')
570
level1 = level0.bzrdir.sprout('level1').open_workingtree()
571
self.build_tree_contents([('level1/file2', 'hello\n')])
572
self.wt_commit(level1, 'in branch level1')
573
level0.merge_from_branch(level1.branch)
574
self.wt_commit(level0, 'merge branch level1')
576
def _diff_file1_revno1(self):
577
return """=== added file 'file1'
578
--- file1\t1970-01-01 00:00:00 +0000
579
+++ file1\t2005-11-22 00:00:00 +0000
581
+contents of level0/file1
585
def _diff_file2_revno2(self):
586
return """=== modified file 'file2'
587
--- file2\t2005-11-22 00:00:00 +0000
588
+++ file2\t2005-11-22 00:00:01 +0000
590
-contents of level0/file2
595
def _diff_file2_revno1_1_1(self):
596
return """=== modified file 'file2'
597
--- file2\t2005-11-22 00:00:00 +0000
598
+++ file2\t2005-11-22 00:00:01 +0000
600
-contents of level0/file2
605
def _diff_file2_revno1(self):
606
return """=== added file 'file2'
607
--- file2\t1970-01-01 00:00:00 +0000
608
+++ file2\t2005-11-22 00:00:00 +0000
610
+contents of level0/file2
614
def assertLogRevnosAndDiff(self, args, expected,
616
self.run_bzr(['log', '-p'] + args, working_dir=working_dir)
617
expected_revnos_and_depths = [
618
(revno, depth) for revno, depth, diff in expected]
619
# Check the revnos and depths first to make debugging easier
620
self.assertEqual(expected_revnos_and_depths,
621
[(r.revno, r.merge_depth)
622
for r in self.get_captured_revisions()])
623
# Now check the diffs, adding the revno in case of failure
624
fmt = 'In revno %s\n%s'
625
for expected_rev, actual_rev in izip(expected,
626
self.get_captured_revisions()):
627
revno, depth, expected_diff = expected_rev
628
actual_diff = actual_rev.diff
629
self.assertEqualDiff(fmt % (revno, expected_diff),
630
fmt % (revno, actual_diff))
632
def test_log_diff_with_merges(self):
633
self.assertLogRevnosAndDiff(
635
[('2', 0, self._diff_file2_revno2()),
636
('1.1.1', 1, self._diff_file2_revno1_1_1()),
637
('1', 0, self._diff_file1_revno1()
638
+ self._diff_file2_revno1())],
639
working_dir='level0')
642
def test_log_diff_file1(self):
643
self.assertLogRevnosAndDiff(['-n0', 'file1'],
644
[('1', 0, self._diff_file1_revno1())],
645
working_dir='level0')
647
def test_log_diff_file2(self):
648
self.assertLogRevnosAndDiff(['-n1', 'file2'],
649
[('2', 0, self._diff_file2_revno2()),
650
('1', 0, self._diff_file2_revno1())],
651
working_dir='level0')
654
class TestLogUnicodeDiff(TestLog):
656
def test_log_show_diff_non_ascii(self):
657
# Smoke test for bug #328007 UnicodeDecodeError on 'log -p'
658
message = u'Message with \xb5'
659
body = 'Body with \xb5\n'
660
wt = self.make_branch_and_tree('.')
661
self.build_tree_contents([('foo', body)])
663
wt.commit(message=message)
664
# check that command won't fail with unicode error
665
# don't care about exact output because we have other tests for this
666
out,err = self.run_bzr('log -p --long')
667
self.assertNotEqual('', out)
668
self.assertEqual('', err)
669
out,err = self.run_bzr('log -p --short')
670
self.assertNotEqual('', out)
671
self.assertEqual('', err)
672
out,err = self.run_bzr('log -p --line')
673
self.assertNotEqual('', out)
674
self.assertEqual('', err)
677
class TestLogEncodings(tests.TestCaseInTempDir):
680
_message = u'Message with \xb5'
682
# Encodings which can encode mu
687
'cp437', # Common windows encoding
688
'cp1251', # Russian windows encoding
689
'cp1258', # Common windows encoding
691
# Encodings which cannot encode mu
699
super(TestLogEncodings, self).setUp()
700
self.overrideAttr(osutils, '_cached_user_encoding')
702
def create_branch(self):
705
self.build_tree_contents([('a', 'some stuff\n')])
707
bzr(['commit', '-m', self._message])
709
def try_encoding(self, encoding, fail=False):
712
self.assertRaises(UnicodeEncodeError,
713
self._mu.encode, encoding)
714
encoded_msg = self._message.encode(encoding, 'replace')
716
encoded_msg = self._message.encode(encoding)
718
old_encoding = osutils._cached_user_encoding
719
# This test requires that 'run_bzr' uses the current
720
# bzrlib, because we override user_encoding, and expect
723
osutils._cached_user_encoding = 'ascii'
724
# We should be able to handle any encoding
725
out, err = bzr('log', encoding=encoding)
727
# Make sure we wrote mu as we expected it to exist
728
self.assertNotEqual(-1, out.find(encoded_msg))
729
out_unicode = out.decode(encoding)
730
self.assertNotEqual(-1, out_unicode.find(self._message))
732
self.assertNotEqual(-1, out.find('Message with ?'))
734
osutils._cached_user_encoding = old_encoding
736
def test_log_handles_encoding(self):
739
for encoding in self.good_encodings:
740
self.try_encoding(encoding)
742
def test_log_handles_bad_encoding(self):
745
for encoding in self.bad_encodings:
746
self.try_encoding(encoding, fail=True)
748
def test_stdout_encoding(self):
750
osutils._cached_user_encoding = "cp1251"
753
self.build_tree(['a'])
755
bzr(['commit', '-m', u'\u0422\u0435\u0441\u0442'])
756
stdout, stderr = self.run_bzr('log', encoding='cp866')
758
message = stdout.splitlines()[-1]
760
# explanation of the check:
761
# u'\u0422\u0435\u0441\u0442' is word 'Test' in russian
762
# in cp866 encoding this is string '\x92\xa5\xe1\xe2'
763
# in cp1251 encoding this is string '\xd2\xe5\xf1\xf2'
764
# This test should check that output of log command
765
# encoded to sys.stdout.encoding
766
test_in_cp866 = '\x92\xa5\xe1\xe2'
767
test_in_cp1251 = '\xd2\xe5\xf1\xf2'
768
# Make sure the log string is encoded in cp866
769
self.assertEquals(test_in_cp866, message[2:])
770
# Make sure the cp1251 string is not found anywhere
771
self.assertEquals(-1, stdout.find(test_in_cp1251))
774
class TestLogFile(TestLogWithLogCatcher):
776
def test_log_local_branch_file(self):
777
"""We should be able to log files in local treeless branches"""
778
tree = self.make_branch_and_tree('tree')
779
self.build_tree(['tree/file'])
781
tree.commit('revision 1')
782
tree.bzrdir.destroy_workingtree()
783
self.run_bzr('log tree/file')
785
def prepare_tree(self, complex=False):
786
# The complex configuration includes deletes and renames
787
tree = self.make_branch_and_tree('parent')
788
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
790
tree.commit('add file1')
792
tree.commit('add file2')
794
tree.commit('add file3')
795
child_tree = tree.bzrdir.sprout('child').open_workingtree()
796
self.build_tree_contents([('child/file2', 'hello')])
797
child_tree.commit(message='branch 1')
798
tree.merge_from_branch(child_tree.branch)
799
tree.commit(message='merge child branch')
802
tree.commit('remove file2')
803
tree.rename_one('file3', 'file4')
804
tree.commit('file3 is now called file4')
806
tree.commit('remove file1')
809
# FIXME: It would be good to parametrize the following tests against all
810
# formatters. But the revisions selection is not *currently* part of the
811
# LogFormatter contract, so using LogCatcher is sufficient -- vila 100118
812
def test_log_file1(self):
814
self.assertLogRevnos(['-n0', 'file1'], ['1'])
816
def test_log_file2(self):
819
self.assertLogRevnos(['-n0', 'file2'], ['4', '3.1.1', '2'])
820
# file2 in a merge revision
821
self.assertLogRevnos(['-n0', '-r3.1.1', 'file2'], ['3.1.1'])
822
# file2 in a mainline revision
823
self.assertLogRevnos(['-n0', '-r4', 'file2'], ['4', '3.1.1'])
824
# file2 since a revision
825
self.assertLogRevnos(['-n0', '-r3..', 'file2'], ['4', '3.1.1'])
826
# file2 up to a revision
827
self.assertLogRevnos(['-n0', '-r..3', 'file2'], ['2'])
829
def test_log_file3(self):
831
self.assertLogRevnos(['-n0', 'file3'], ['3'])
833
def test_log_file_historical_missing(self):
834
# Check logging a deleted file gives an error if the
835
# file isn't found at the end or start of the revision range
836
self.prepare_tree(complex=True)
837
err_msg = "Path unknown at end or start of revision range: file2"
838
err = self.run_bzr('log file2', retcode=3)[1]
839
self.assertContainsRe(err, err_msg)
841
def test_log_file_historical_end(self):
842
# Check logging a deleted file is ok if the file existed
843
# at the end the revision range
844
self.prepare_tree(complex=True)
845
self.assertLogRevnos(['-n0', '-r..4', 'file2'], ['4', '3.1.1', '2'])
847
def test_log_file_historical_start(self):
848
# Check logging a deleted file is ok if the file existed
849
# at the start of the revision range
850
self.prepare_tree(complex=True)
851
self.assertLogRevnos(['file1'], ['1'])
853
def test_log_file_renamed(self):
854
"""File matched against revision range, not current tree."""
855
self.prepare_tree(complex=True)
857
# Check logging a renamed file gives an error by default
858
err_msg = "Path unknown at end or start of revision range: file3"
859
err = self.run_bzr('log file3', retcode=3)[1]
860
self.assertContainsRe(err, err_msg)
862
# Check we can see a renamed file if we give the right end revision
863
self.assertLogRevnos(['-r..4', 'file3'], ['3'])
866
class TestLogMultiple(TestLogWithLogCatcher):
868
def prepare_tree(self):
869
tree = self.make_branch_and_tree('parent')
876
'parent/dir1/dir2/file3',
879
tree.commit('add file1')
881
tree.commit('add file2')
882
tree.add(['dir1', 'dir1/dir2', 'dir1/dir2/file3'])
883
tree.commit('add file3')
885
tree.commit('add file4')
886
tree.add('dir1/file5')
887
tree.commit('add file5')
888
child_tree = tree.bzrdir.sprout('child').open_workingtree()
889
self.build_tree_contents([('child/file2', 'hello')])
890
child_tree.commit(message='branch 1')
891
tree.merge_from_branch(child_tree.branch)
892
tree.commit(message='merge child branch')
895
def test_log_files(self):
896
"""The log for multiple file should only list revs for those files"""
898
self.assertLogRevnos(['file1', 'file2', 'dir1/dir2/file3'],
899
['6', '5.1.1', '3', '2', '1'])
901
def test_log_directory(self):
902
"""The log for a directory should show all nested files."""
904
self.assertLogRevnos(['dir1'], ['5', '3'])
906
def test_log_nested_directory(self):
907
"""The log for a directory should show all nested files."""
909
self.assertLogRevnos(['dir1/dir2'], ['3'])
911
def test_log_in_nested_directory(self):
912
"""The log for a directory should show all nested files."""
915
self.assertLogRevnos(['.'], ['5', '3'])
917
def test_log_files_and_directories(self):
918
"""Logging files and directories together should be fine."""
920
self.assertLogRevnos(['file4', 'dir1/dir2'], ['4', '3'])
922
def test_log_files_and_dirs_in_nested_directory(self):
923
"""The log for a directory should show all nested files."""
926
self.assertLogRevnos(['dir2', 'file5'], ['5', '3'])
929
class MainlineGhostTests(TestLogWithLogCatcher):
932
super(MainlineGhostTests, self).setUp()
933
tree = self.make_branch_and_tree('')
934
tree.set_parent_ids(["spooky"], allow_leftmost_as_ghost=True)
936
tree.commit('msg1', rev_id='rev1')
937
tree.commit('msg2', rev_id='rev2')
939
def test_log_range(self):
940
self.assertLogRevnos(["-r1..2"], ["2", "1"])
942
def test_log_norange(self):
943
self.assertLogRevnos([], ["2", "1"])
945
def test_log_range_open_begin(self):
946
raise tests.KnownFailure("log with ghosts fails. bug #726466")
947
(stdout, stderr) = self.run_bzr(['log', '-r..2'], retcode=3)
948
self.assertEqual(["2", "1"],
949
[r.revno for r in self.get_captured_revisions()])
950
self.assertEquals("bzr: ERROR: Further revision history missing.", stderr)
952
def test_log_range_open_end(self):
953
self.assertLogRevnos(["-r1.."], ["2", "1"])