1
# Copyright (C) 2006-2012, 2016 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
18
"""Black-box tests for brz log."""
20
from __future__ import absolute_import
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
"""Black-box tests for bzr log."""
32
from breezy.sixish import PY3
33
from breezy.tests import (
37
from breezy.tests.matchers import ContainsNoVfsCalls
40
class TestLog(tests.TestCaseWithTransport, test_log.TestLogMixin):
42
def make_minimal_branch(self, path='.', format=None):
43
tree = self.make_branch_and_tree(path, format=format)
44
self.build_tree([path + '/hello.txt'])
46
tree.commit(message='message1')
49
def make_linear_branch(self, path='.', format=None):
50
tree = self.make_branch_and_tree(path, format=format)
52
[path + '/hello.txt', path + '/goodbye.txt', path + '/meep.txt'])
54
tree.commit(message='message1')
55
tree.add('goodbye.txt')
56
tree.commit(message='message2')
58
tree.commit(message='message3')
61
def make_merged_branch(self, path='.', format=None):
62
tree = self.make_linear_branch(path, format)
63
tree2 = tree.controldir.sprout('tree2',
64
revision_id=tree.branch.get_rev_id(1)).open_workingtree()
65
tree2.commit(message='tree2 message2')
66
tree2.commit(message='tree2 message3')
67
tree.merge_from_branch(tree2.branch)
68
tree.commit(message='merge')
72
class TestLogWithLogCatcher(TestLog):
75
super(TestLogWithLogCatcher, self).setUp()
76
# Capture log formatter creations
78
class MyLogFormatter(test_log.LogCatcher):
80
def __new__(klass, *args, **kwargs):
81
self.log_catcher = test_log.LogCatcher(*args, **kwargs)
82
# Always return our own log formatter
83
return self.log_catcher
84
# Break cycle with closure over self on cleanup by removing method
85
self.addCleanup(setattr, MyLogFormatter, "__new__", None)
88
# Always return our own log formatter class hijacking the
89
# default behavior (which requires setting up a config
92
self.overrideAttr(log.log_formatter_registry, 'get_default', getme)
94
def get_captured_revisions(self):
95
return self.log_catcher.revisions
97
def assertLogRevnos(self, args, expected_revnos, working_dir='.',
99
actual_out, actual_err = self.run_bzr(['log'] + args,
100
working_dir=working_dir)
101
self.assertEqual(out, actual_out)
102
self.assertEqual(err, actual_err)
103
self.assertEqual(expected_revnos,
104
[r.revno for r in self.get_captured_revisions()])
106
def assertLogRevnosAndDepths(self, args, expected_revnos_and_depths,
108
self.run_bzr(['log'] + args, working_dir=working_dir)
109
self.assertEqual(expected_revnos_and_depths,
110
[(r.revno, r.merge_depth)
111
for r in self.get_captured_revisions()])
114
class TestLogRevSpecs(TestLogWithLogCatcher):
116
def test_log_no_revspec(self):
117
self.make_linear_branch()
118
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]
120
39
def test_log_null_end_revspec(self):
121
self.make_linear_branch()
122
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)
124
51
def test_log_null_begin_revspec(self):
125
self.make_linear_branch()
126
self.assertLogRevnos(['-r..3'], ['3', '2', '1'])
53
log = self.runbzr("log -r ..3")[0]
54
self.assertEquals(self.full_log, log)
128
56
def test_log_null_both_revspecs(self):
129
self.make_linear_branch()
130
self.assertLogRevnos(['-r..'], ['3', '2', '1'])
58
log = self.runbzr("log -r ..")[0]
59
self.assertEquals(self.full_log, log)
132
61
def test_log_negative_begin_revspec_full_log(self):
133
self.make_linear_branch()
134
self.assertLogRevnos(['-r-3..'], ['3', '2', '1'])
63
log = self.runbzr("log -r -3..")[0]
64
self.assertEquals(self.full_log, log)
136
66
def test_log_negative_both_revspec_full_log(self):
137
self.make_linear_branch()
138
self.assertLogRevnos(['-r-3..-1'], ['3', '2', '1'])
68
log = self.runbzr("log -r -3..-1")[0]
69
self.assertEquals(self.full_log, log)
140
71
def test_log_negative_both_revspec_partial(self):
141
self.make_linear_branch()
142
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)
144
78
def test_log_negative_begin_revspec(self):
145
self.make_linear_branch()
146
self.assertLogRevnos(['-r-2..'], ['3', '2'])
148
def test_log_positive_revspecs(self):
149
self.make_linear_branch()
150
self.assertLogRevnos(['-r1..3'], ['3', '2', '1'])
152
def test_log_dotted_revspecs(self):
153
self.make_merged_branch()
154
self.assertLogRevnos(['-n0', '-r1..1.1.1'], ['1.1.1', '1'])
156
def test_log_limit(self):
157
tree = self.make_branch_and_tree('.')
158
# We want more commits than our batch size starts at
159
for pos in range(10):
160
tree.commit("%s" % pos)
161
self.assertLogRevnos(['--limit', '2'], ['10', '9'])
163
def test_log_limit_short(self):
164
self.make_linear_branch()
165
self.assertLogRevnos(['-l', '2'], ['3', '2'])
167
def test_log_change_revno(self):
168
self.make_linear_branch()
169
self.assertLogRevnos(['-c1'], ['1'])
171
def test_branch_revspec(self):
172
foo = self.make_branch_and_tree('foo')
173
bar = self.make_branch_and_tree('bar')
174
self.build_tree(['foo/foo.txt', 'bar/bar.txt'])
177
foo.commit(message='foo')
178
bar.commit(message='bar')
179
self.run_bzr('log -r branch:../bar', working_dir='foo')
180
self.assertEqual([bar.branch.get_rev_id(1)],
182
for r in self.get_captured_revisions()])
185
class TestLogExcludeCommonAncestry(TestLogWithLogCatcher):
187
def test_exclude_common_ancestry_simple_revnos(self):
188
self.make_linear_branch()
189
self.assertLogRevnos(['-r1..3', '--exclude-common-ancestry'],
193
class TestLogMergedLinearAncestry(TestLogWithLogCatcher):
196
super(TestLogMergedLinearAncestry, self).setUp()
197
# FIXME: Using a MemoryTree would be even better here (but until we
198
# stop calling run_bzr, there is no point) --vila 100118.
199
builder = branchbuilder.BranchBuilder(self.get_transport())
200
builder.start_series()
218
builder.build_snapshot(None, [
219
('add', ('', b'root-id', 'directory', ''))],
221
builder.build_snapshot([b'1'], [], revision_id=b'2')
223
builder.build_snapshot([b'1'], [], revision_id=b'1.1.1')
224
# merge branch into mainline
225
builder.build_snapshot([b'2', b'1.1.1'], [], revision_id=b'3')
226
# new commits in branch
227
builder.build_snapshot([b'1.1.1'], [], revision_id=b'1.1.2')
228
builder.build_snapshot([b'1.1.2'], [], revision_id=b'1.1.3')
229
# merge branch into mainline
230
builder.build_snapshot([b'3', b'1.1.3'], [], revision_id=b'4')
231
# merge mainline into branch
232
builder.build_snapshot([b'1.1.3', b'4'], [], revision_id=b'1.1.4')
233
# merge branch into mainline
234
builder.build_snapshot([b'4', b'1.1.4'], [], revision_id=b'5')
235
builder.build_snapshot([b'5'], [], revision_id=b'5.1.1')
236
builder.build_snapshot([b'5', b'5.1.1'], [], revision_id=b'6')
237
builder.finish_series()
240
self.assertLogRevnos(['-n0', '-r1.1.1..1.1.4'],
241
['1.1.4', '4', '1.1.3', '1.1.2', '3', '1.1.1'])
243
def test_n0_forward(self):
244
self.assertLogRevnos(['-n0', '-r1.1.1..1.1.4', '--forward'],
245
['3', '1.1.1', '4', '1.1.2', '1.1.3', '1.1.4'])
248
# starting from 1.1.4 we follow the left-hand ancestry
249
self.assertLogRevnos(['-n1', '-r1.1.1..1.1.4'],
250
['1.1.4', '1.1.3', '1.1.2', '1.1.1'])
252
def test_n1_forward(self):
253
self.assertLogRevnos(['-n1', '-r1.1.1..1.1.4', '--forward'],
254
['1.1.1', '1.1.2', '1.1.3', '1.1.4'])
256
def test_fallback_when_end_rev_is_not_on_mainline(self):
257
self.assertLogRevnos(['-n1', '-r1.1.1..5.1.1'],
258
# We don't get 1.1.1 because we say -n1
259
['5.1.1', '5', '4', '3'])
262
class Test_GenerateAllRevisions(TestLogWithLogCatcher):
265
super(Test_GenerateAllRevisions, self).setUp()
266
builder = self.make_branch_with_many_merges()
267
b = builder.get_branch()
269
self.addCleanup(b.unlock)
272
def make_branch_with_many_merges(self, path='.', format=None):
273
builder = branchbuilder.BranchBuilder(self.get_transport())
274
builder.start_series()
275
# The graph below may look a bit complicated (and it may be but I've
276
# banged my head enough on it) but the bug requires at least dotted
277
# revnos *and* merged revisions below that.
291
builder.build_snapshot(None, [
292
('add', ('', b'root-id', 'directory', ''))], revision_id=b'1')
293
builder.build_snapshot([b'1'], [], revision_id=b'2')
294
builder.build_snapshot([b'1'], [], revision_id=b'1.1.1')
295
builder.build_snapshot([b'2'], [], revision_id=b'2.1.1')
296
builder.build_snapshot([b'2', b'1.1.1'], [], revision_id=b'3')
297
builder.build_snapshot([b'2.1.1'], [], revision_id=b'2.1.2')
298
builder.build_snapshot([b'2.1.1'], [], revision_id=b'2.2.1')
299
builder.build_snapshot([b'2.1.2', b'2.2.1'], [], revision_id=b'2.1.3')
300
builder.build_snapshot([b'3', b'2.1.3'], [], revision_id=b'4')
301
builder.build_snapshot([b'4', b'2.1.2'], [], revision_id=b'5')
302
builder.finish_series()
305
def test_not_an_ancestor(self):
306
self.assertRaises(errors.BzrCommandError,
307
log._generate_all_revisions,
308
self.branch, '1.1.1', '2.1.3', 'reverse',
309
delayed_graph_generation=True)
311
def test_wrong_order(self):
312
self.assertRaises(errors.BzrCommandError,
313
log._generate_all_revisions,
314
self.branch, '5', '2.1.3', 'reverse',
315
delayed_graph_generation=True)
317
def test_no_start_rev_id_with_end_rev_id_being_a_merge(self):
318
revs = log._generate_all_revisions(
319
self.branch, None, '2.1.3',
320
'reverse', delayed_graph_generation=True)
323
class TestLogRevSpecsWithPaths(TestLogWithLogCatcher):
325
def test_log_revno_n_path_wrong_namespace(self):
326
self.make_linear_branch('branch1')
327
self.make_linear_branch('branch2')
328
# There is no guarantee that a path exist between two arbitrary
330
self.run_bzr("log -r revno:2:branch1..revno:3:branch2", retcode=3)
332
def test_log_revno_n_path_correct_order(self):
333
self.make_linear_branch('branch2')
334
self.assertLogRevnos(['-rrevno:1:branch2..revno:3:branch2'],
337
def test_log_revno_n_path(self):
338
self.make_linear_branch('branch2')
339
self.assertLogRevnos(['-rrevno:1:branch2'],
341
rev_props = self.log_catcher.revisions[0].rev.properties
342
self.assertEqual('branch2', rev_props[u'branch-nick'])
345
class TestLogErrors(TestLog):
347
def test_log_zero_revspec(self):
348
self.make_minimal_branch()
349
self.run_bzr_error(['brz: ERROR: Logging revision 0 is invalid.'],
352
def test_log_zero_begin_revspec(self):
353
self.make_linear_branch()
354
self.run_bzr_error(['brz: ERROR: Logging revision 0 is invalid.'],
357
def test_log_zero_end_revspec(self):
358
self.make_linear_branch()
359
self.run_bzr_error(['brz: ERROR: Logging revision 0 is invalid.'],
362
def test_log_nonexistent_revno(self):
363
self.make_minimal_branch()
364
self.run_bzr_error(["brz: ERROR: Requested revision: '1234' "
365
"does not exist in branch:"],
368
def test_log_nonexistent_dotted_revno(self):
369
self.make_minimal_branch()
370
self.run_bzr_error(["brz: ERROR: Requested revision: '123.123' "
371
"does not exist in branch:"],
372
['log', '-r123.123'])
374
def test_log_change_nonexistent_revno(self):
375
self.make_minimal_branch()
376
self.run_bzr_error(["brz: ERROR: Requested revision: '1234' "
377
"does not exist in branch:"],
380
def test_log_change_nonexistent_dotted_revno(self):
381
self.make_minimal_branch()
382
self.run_bzr_error(["brz: ERROR: Requested revision: '123.123' "
383
"does not exist in branch:"],
384
['log', '-c123.123'])
386
def test_log_change_single_revno_only(self):
387
self.make_minimal_branch()
388
self.run_bzr_error(['brz: ERROR: Option --change does not'
389
' accept revision ranges'],
390
['log', '--change', '2..3'])
392
def test_log_change_incompatible_with_revision(self):
393
self.run_bzr_error(['brz: ERROR: --revision and --change'
394
' are mutually exclusive'],
395
['log', '--change', '2', '--revision', '3'])
397
def test_log_nonexistent_file(self):
398
self.make_minimal_branch()
399
# files that don't exist in either the basis tree or working tree
400
# should give an error
401
out, err = self.run_bzr('log does-not-exist', retcode=3)
402
self.assertContainsRe(err,
403
'Path unknown at end or start of revision range: '
406
def test_log_reversed_revspecs(self):
407
self.make_linear_branch()
408
self.run_bzr_error(('brz: ERROR: Start revision must be older than '
409
'the end revision.\n',),
412
def test_log_reversed_dotted_revspecs(self):
413
self.make_merged_branch()
414
self.run_bzr_error(('brz: ERROR: Start revision not found in '
415
'history of end revision.\n',),
418
def test_log_bad_message_re(self):
419
"""Bad --message argument gives a sensible message
421
See https://bugs.launchpad.net/bzr/+bug/251352
423
self.make_minimal_branch()
424
out, err = self.run_bzr(['log', '-m', '*'], retcode=3)
425
self.assertContainsRe(err, "ERROR.*Invalid pattern.*nothing to repeat")
426
self.assertNotContainsRe(err, "Unprintable exception")
427
self.assertEqual(out, '')
429
def test_log_unsupported_timezone(self):
430
self.make_linear_branch()
431
self.run_bzr_error(['brz: ERROR: Unsupported timezone format "foo", '
432
'options are "utc", "original", "local".'],
433
['log', '--timezone', 'foo'])
435
def test_log_exclude_ancestry_no_range(self):
436
self.make_linear_branch()
437
self.run_bzr_error(['brz: ERROR: --exclude-common-ancestry'
438
' requires -r with two revisions'],
439
['log', '--exclude-common-ancestry'])
441
def test_log_exclude_ancestry_single_revision(self):
442
self.make_merged_branch()
443
self.run_bzr_error(['brz: ERROR: --exclude-common-ancestry'
444
' requires two different revisions'],
445
['log', '--exclude-common-ancestry',
449
class TestLogTags(TestLog):
451
def test_log_with_tags(self):
452
tree = self.make_linear_branch(format='dirstate-tags')
454
branch.tags.set_tag('tag1', branch.get_rev_id(1))
455
branch.tags.set_tag('tag1.1', branch.get_rev_id(1))
456
branch.tags.set_tag('tag3', branch.last_revision())
458
log = self.run_bzr("log -r-1")[0]
459
self.assertTrue('tags: tag3' in log)
461
log = self.run_bzr("log -r1")[0]
462
# I guess that we can't know the order of tags in the output
463
# since dicts are unordered, need to check both possibilities
464
self.assertContainsRe(log, r'tags: (tag1, tag1\.1|tag1\.1, tag1)')
466
def test_merged_log_with_tags(self):
467
branch1_tree = self.make_linear_branch('branch1',
468
format='dirstate-tags')
469
branch1 = branch1_tree.branch
470
branch2_tree = branch1_tree.controldir.sprout(
471
'branch2').open_workingtree()
472
branch1_tree.commit(message='foobar', allow_pointless=True)
473
branch1.tags.set_tag('tag1', branch1.last_revision())
474
# tags don't propagate if we don't merge
475
self.run_bzr('merge ../branch1', working_dir='branch2')
476
branch2_tree.commit(message='merge branch 1')
477
log = self.run_bzr("log -n0 -r-1", working_dir='branch2')[0]
478
self.assertContainsRe(log, r' tags: tag1')
479
log = self.run_bzr("log -n0 -r3.1.1", working_dir='branch2')[0]
480
self.assertContainsRe(log, r'tags: tag1')
483
class TestLogSignatures(TestLog):
485
def test_log_with_signatures(self):
486
self.requireFeature(features.gpg)
488
tree = self.make_linear_branch(format='dirstate-tags')
490
log = self.run_bzr("log --signatures")[0]
491
self.assertTrue('signature: no signature' in log)
493
def test_log_without_signatures(self):
494
self.requireFeature(features.gpg)
496
tree = self.make_linear_branch(format='dirstate-tags')
498
log = self.run_bzr("log")[0]
499
self.assertFalse('signature: no signature' in log)
502
class TestLogVerbose(TestLog):
505
super(TestLogVerbose, self).setUp()
506
self.make_minimal_branch()
508
def assertUseShortDeltaFormat(self, cmd):
509
log = self.run_bzr(cmd)[0]
510
# Check that we use the short status format
511
self.assertContainsRe(log, '(?m)^\\s*A hello.txt$')
512
self.assertNotContainsRe(log, '(?m)^\\s*added:$')
514
def assertUseLongDeltaFormat(self, cmd):
515
log = self.run_bzr(cmd)[0]
516
# Check that we use the long status format
517
self.assertNotContainsRe(log, '(?m)^\\s*A hello.txt$')
518
self.assertContainsRe(log, '(?m)^\\s*added:$')
520
def test_log_short_verbose(self):
521
self.assertUseShortDeltaFormat(['log', '--short', '-v'])
523
def test_log_s_verbose(self):
524
self.assertUseShortDeltaFormat(['log', '-S', '-v'])
526
def test_log_short_verbose_verbose(self):
527
self.assertUseLongDeltaFormat(['log', '--short', '-vv'])
529
def test_log_long_verbose(self):
530
# Check that we use the long status format, ignoring the verbosity
532
self.assertUseLongDeltaFormat(['log', '--long', '-v'])
534
def test_log_long_verbose_verbose(self):
535
# Check that we use the long status format, ignoring the verbosity
537
self.assertUseLongDeltaFormat(['log', '--long', '-vv'])
540
class TestLogMerges(TestLogWithLogCatcher):
543
super(TestLogMerges, self).setUp()
544
self.make_branches_with_merges()
546
def make_branches_with_merges(self):
547
level0 = self.make_branch_and_tree('level0')
548
self.wt_commit(level0, 'in branch level0')
549
level1 = level0.controldir.sprout('level1').open_workingtree()
550
self.wt_commit(level1, 'in branch level1')
551
level2 = level1.controldir.sprout('level2').open_workingtree()
552
self.wt_commit(level2, 'in branch level2')
553
level1.merge_from_branch(level2.branch)
554
self.wt_commit(level1, 'merge branch level2')
555
level0.merge_from_branch(level1.branch)
556
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):
558
93
def test_merges_are_indented_by_level(self):
559
self.run_bzr(['log', '-n0'], working_dir='level0')
560
revnos_and_depth = [(r.revno, r.merge_depth)
561
for r in self.get_captured_revisions()]
562
self.assertEqual([('2', 0), ('1.1.2', 1), ('1.2.1', 2), ('1.1.1', 1),
564
[(r.revno, r.merge_depth)
565
for r in self.get_captured_revisions()])
567
def test_force_merge_revisions_off(self):
568
self.assertLogRevnos(['-n1'], ['2', '1'], working_dir='level0')
570
def test_force_merge_revisions_on(self):
571
self.assertLogRevnos(['-n0'], ['2', '1.1.2', '1.2.1', '1.1.1', '1'],
572
working_dir='level0')
574
def test_include_merged(self):
575
# Confirm --include-merged gives the same output as -n0
576
expected = ['2', '1.1.2', '1.2.1', '1.1.1', '1']
577
self.assertLogRevnos(['--include-merged'],
578
expected, working_dir='level0')
579
self.assertLogRevnos(['--include-merged'],
580
expected, working_dir='level0')
582
def test_force_merge_revisions_N(self):
583
self.assertLogRevnos(['-n2'],
584
['2', '1.1.2', '1.1.1', '1'],
585
working_dir='level0')
587
def test_merges_single_merge_rev(self):
588
self.assertLogRevnosAndDepths(['-n0', '-r1.1.2'],
589
[('1.1.2', 0), ('1.2.1', 1)],
590
working_dir='level0')
592
def test_merges_partial_range(self):
593
self.assertLogRevnosAndDepths(
594
['-n0', '-r1.1.1..1.1.2'],
595
[('1.1.2', 0), ('1.2.1', 1), ('1.1.1', 0)],
596
working_dir='level0')
598
def test_merges_partial_range_ignore_before_lower_bound(self):
599
"""Dont show revisions before the lower bound's merged revs"""
600
self.assertLogRevnosAndDepths(
601
['-n0', '-r1.1.2..2'],
602
[('2', 0), ('1.1.2', 1), ('1.2.1', 2)],
603
working_dir='level0')
605
def test_omit_merges_with_sidelines(self):
606
self.assertLogRevnos(['--omit-merges', '-n0'], ['1.2.1', '1.1.1', '1'],
607
working_dir='level0')
609
def test_omit_merges_without_sidelines(self):
610
self.assertLogRevnos(['--omit-merges', '-n1'], ['1'],
611
working_dir='level0')
614
class TestLogDiff(TestLogWithLogCatcher):
616
# FIXME: We need specific tests for each LogFormatter about how the diffs
617
# are displayed: --long indent them by depth, --short use a fixed
618
# indent and --line does't display them. -- vila 10019
621
super(TestLogDiff, self).setUp()
622
self.make_branch_with_diffs()
624
def make_branch_with_diffs(self):
625
level0 = self.make_branch_and_tree('level0')
626
self.build_tree(['level0/file1', 'level0/file2'])
629
self.wt_commit(level0, 'in branch level0')
631
level1 = level0.controldir.sprout('level1').open_workingtree()
632
self.build_tree_contents([('level1/file2', b'hello\n')])
633
self.wt_commit(level1, 'in branch level1')
634
level0.merge_from_branch(level1.branch)
635
self.wt_commit(level0, 'merge branch level1')
637
def _diff_file1_revno1(self):
638
return b"""=== added file 'file1'
639
--- file1\t1970-01-01 00:00:00 +0000
640
+++ file1\t2005-11-22 00:00:00 +0000
642
+contents of level0/file1
646
def _diff_file2_revno2(self):
647
return b"""=== modified file 'file2'
648
--- file2\t2005-11-22 00:00:00 +0000
649
+++ file2\t2005-11-22 00:00:01 +0000
651
-contents of level0/file2
656
def _diff_file2_revno1_1_1(self):
657
return b"""=== modified file 'file2'
658
--- file2\t2005-11-22 00:00:00 +0000
659
+++ file2\t2005-11-22 00:00:01 +0000
661
-contents of level0/file2
666
def _diff_file2_revno1(self):
667
return b"""=== added file 'file2'
668
--- file2\t1970-01-01 00:00:00 +0000
669
+++ file2\t2005-11-22 00:00:00 +0000
671
+contents of level0/file2
675
def assertLogRevnosAndDiff(self, args, expected,
677
self.run_bzr(['log', '-p'] + args, working_dir=working_dir)
678
expected_revnos_and_depths = [
679
(revno, depth) for revno, depth, diff in expected]
680
# Check the revnos and depths first to make debugging easier
681
self.assertEqual(expected_revnos_and_depths,
682
[(r.revno, r.merge_depth)
683
for r in self.get_captured_revisions()])
684
# Now check the diffs, adding the revno in case of failure
685
fmt = 'In revno %s\n%s'
686
for expected_rev, actual_rev in zip(expected,
687
self.get_captured_revisions()):
688
revno, depth, expected_diff = expected_rev
689
actual_diff = actual_rev.diff
690
self.assertEqualDiff(fmt % (revno, expected_diff),
691
fmt % (revno, actual_diff))
693
def test_log_diff_with_merges(self):
694
self.assertLogRevnosAndDiff(
696
[('2', 0, self._diff_file2_revno2()),
697
('1.1.1', 1, self._diff_file2_revno1_1_1()),
698
('1', 0, self._diff_file1_revno1() +
699
self._diff_file2_revno1())],
700
working_dir='level0')
702
def test_log_diff_file1(self):
703
self.assertLogRevnosAndDiff(['-n0', 'file1'],
704
[('1', 0, self._diff_file1_revno1())],
705
working_dir='level0')
707
def test_log_diff_file2(self):
708
self.assertLogRevnosAndDiff(['-n1', 'file2'],
709
[('2', 0, self._diff_file2_revno2()),
710
('1', 0, self._diff_file2_revno1())],
711
working_dir='level0')
714
class TestLogUnicodeDiff(TestLog):
716
def test_log_show_diff_non_ascii(self):
717
# Smoke test for bug #328007 UnicodeDecodeError on 'log -p'
718
message = u'Message with \xb5'
719
body = b'Body with \xb5\n'
720
wt = self.make_branch_and_tree('.')
721
self.build_tree_contents([('foo', body)])
723
wt.commit(message=message)
724
# check that command won't fail with unicode error
725
# don't care about exact output because we have other tests for this
726
out, err = self.run_bzr('log -p --long')
727
self.assertNotEqual('', out)
728
self.assertEqual('', err)
729
out, err = self.run_bzr('log -p --short')
730
self.assertNotEqual('', out)
731
self.assertEqual('', err)
732
out, err = self.run_bzr('log -p --line')
733
self.assertNotEqual('', out)
734
self.assertEqual('', err)
737
class TestLogEncodings(tests.TestCaseInTempDir):
740
_message = u'Message with \xb5'
742
# Encodings which can encode mu
747
'cp437', # Common windows encoding
748
'cp1251', # Russian windows encoding
749
'cp1258', # Common windows encoding
751
# Encodings which cannot encode mu
759
super(TestLogEncodings, self).setUp()
760
self.overrideAttr(osutils, '_cached_user_encoding')
762
def create_branch(self):
765
self.build_tree_contents([('a', b'some stuff\n')])
767
brz(['commit', '-m', self._message])
769
def try_encoding(self, encoding, fail=False):
772
self.assertRaises(UnicodeEncodeError,
773
self._mu.encode, encoding)
774
encoded_msg = self._message.encode(encoding, 'replace')
776
encoded_msg = self._message.encode(encoding)
778
old_encoding = osutils._cached_user_encoding
779
# This test requires that 'run_bzr' uses the current
780
# breezy, because we override user_encoding, and expect
783
osutils._cached_user_encoding = 'ascii'
784
# We should be able to handle any encoding
785
out, err = brz('log', encoding=encoding)
787
# Make sure we wrote mu as we expected it to exist
789
self.assertNotEqual(-1, out.find(encoded_msg))
790
out_unicode = out.decode(encoding)
793
self.assertNotEqual(-1, out_unicode.find(self._message))
795
self.assertNotEqual(-1, out.find('Message with ?'))
797
osutils._cached_user_encoding = old_encoding
799
def test_log_handles_encoding(self):
802
for encoding in self.good_encodings:
803
self.try_encoding(encoding)
805
def test_log_handles_bad_encoding(self):
808
for encoding in self.bad_encodings:
809
self.try_encoding(encoding, fail=True)
811
def test_stdout_encoding(self):
813
osutils._cached_user_encoding = "cp1251"
816
self.build_tree(['a'])
818
brz(['commit', '-m', u'\u0422\u0435\u0441\u0442'])
819
stdout, stderr = self.run_bzr_raw('log', encoding='cp866')
821
message = stdout.splitlines()[-1]
823
# explanation of the check:
824
# u'\u0422\u0435\u0441\u0442' is word 'Test' in russian
825
# in cp866 encoding this is string '\x92\xa5\xe1\xe2'
826
# in cp1251 encoding this is string '\xd2\xe5\xf1\xf2'
827
# This test should check that output of log command
828
# encoded to sys.stdout.encoding
829
test_in_cp866 = b'\x92\xa5\xe1\xe2'
830
test_in_cp1251 = b'\xd2\xe5\xf1\xf2'
831
# Make sure the log string is encoded in cp866
832
self.assertEqual(test_in_cp866, message[2:])
833
# Make sure the cp1251 string is not found anywhere
834
self.assertEqual(-1, stdout.find(test_in_cp1251))
837
class TestLogFile(TestLogWithLogCatcher):
839
def test_log_local_branch_file(self):
840
"""We should be able to log files in local treeless branches"""
841
tree = self.make_branch_and_tree('tree')
842
self.build_tree(['tree/file'])
844
tree.commit('revision 1')
845
tree.controldir.destroy_workingtree()
846
self.run_bzr('log tree/file')
848
def prepare_tree(self, complex=False):
849
# The complex configuration includes deletes and renames
850
tree = self.make_branch_and_tree('parent')
851
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
853
tree.commit('add file1')
855
tree.commit('add file2')
857
tree.commit('add file3')
858
child_tree = tree.controldir.sprout('child').open_workingtree()
859
self.build_tree_contents([('child/file2', b'hello')])
860
child_tree.commit(message='branch 1')
861
tree.merge_from_branch(child_tree.branch)
862
tree.commit(message='merge child branch')
865
tree.commit('remove file2')
866
tree.rename_one('file3', 'file4')
867
tree.commit('file3 is now called file4')
869
tree.commit('remove file1')
872
# FIXME: It would be good to parametrize the following tests against all
873
# formatters. But the revisions selection is not *currently* part of the
874
# LogFormatter contract, so using LogCatcher is sufficient -- vila 100118
875
def test_log_file1(self):
877
self.assertLogRevnos(['-n0', 'file1'], ['1'])
879
def test_log_file2(self):
882
self.assertLogRevnos(['-n0', 'file2'], ['4', '3.1.1', '2'])
883
# file2 in a merge revision
884
self.assertLogRevnos(['-n0', '-r3.1.1', 'file2'], ['3.1.1'])
885
# file2 in a mainline revision
886
self.assertLogRevnos(['-n0', '-r4', 'file2'], ['4', '3.1.1'])
887
# file2 since a revision
888
self.assertLogRevnos(['-n0', '-r3..', 'file2'], ['4', '3.1.1'])
889
# file2 up to a revision
890
self.assertLogRevnos(['-n0', '-r..3', 'file2'], ['2'])
892
def test_log_file3(self):
894
self.assertLogRevnos(['-n0', 'file3'], ['3'])
896
def test_log_file_historical_missing(self):
897
# Check logging a deleted file gives an error if the
898
# file isn't found at the end or start of the revision range
899
self.prepare_tree(complex=True)
900
err_msg = "Path unknown at end or start of revision range: file2"
901
err = self.run_bzr('log file2', retcode=3)[1]
902
self.assertContainsRe(err, err_msg)
904
def test_log_file_historical_end(self):
905
# Check logging a deleted file is ok if the file existed
906
# at the end the revision range
907
self.prepare_tree(complex=True)
908
self.assertLogRevnos(['-n0', '-r..4', 'file2'], ['4', '3.1.1', '2'])
910
def test_log_file_historical_start(self):
911
# Check logging a deleted file is ok if the file existed
912
# at the start of the revision range
913
self.prepare_tree(complex=True)
914
self.assertLogRevnos(['file1'], ['1'])
916
def test_log_file_renamed(self):
917
"""File matched against revision range, not current tree."""
918
self.prepare_tree(complex=True)
920
# Check logging a renamed file gives an error by default
921
err_msg = "Path unknown at end or start of revision range: file3"
922
err = self.run_bzr('log file3', retcode=3)[1]
923
self.assertContainsRe(err, err_msg)
925
# Check we can see a renamed file if we give the right end revision
926
self.assertLogRevnos(['-r..4', 'file3'], ['3'])
929
class TestLogMultiple(TestLogWithLogCatcher):
931
def prepare_tree(self):
932
tree = self.make_branch_and_tree('parent')
939
'parent/dir1/dir2/file3',
942
tree.commit('add file1')
944
tree.commit('add file2')
945
tree.add(['dir1', 'dir1/dir2', 'dir1/dir2/file3'])
946
tree.commit('add file3')
948
tree.commit('add file4')
949
tree.add('dir1/file5')
950
tree.commit('add file5')
951
child_tree = tree.controldir.sprout('child').open_workingtree()
952
self.build_tree_contents([('child/file2', b'hello')])
953
child_tree.commit(message='branch 1')
954
tree.merge_from_branch(child_tree.branch)
955
tree.commit(message='merge child branch')
958
def test_log_files(self):
959
"""The log for multiple file should only list revs for those files"""
961
self.assertLogRevnos(['file1', 'file2', 'dir1/dir2/file3'],
962
['6', '5.1.1', '3', '2', '1'])
964
def test_log_directory(self):
965
"""The log for a directory should show all nested files."""
967
self.assertLogRevnos(['dir1'], ['5', '3'])
969
def test_log_nested_directory(self):
970
"""The log for a directory should show all nested files."""
972
self.assertLogRevnos(['dir1/dir2'], ['3'])
974
def test_log_in_nested_directory(self):
975
"""The log for a directory should show all nested files."""
978
self.assertLogRevnos(['.'], ['5', '3'])
980
def test_log_files_and_directories(self):
981
"""Logging files and directories together should be fine."""
983
self.assertLogRevnos(['file4', 'dir1/dir2'], ['4', '3'])
985
def test_log_files_and_dirs_in_nested_directory(self):
986
"""The log for a directory should show all nested files."""
989
self.assertLogRevnos(['dir2', 'file5'], ['5', '3'])
992
class MainlineGhostTests(TestLogWithLogCatcher):
995
super(MainlineGhostTests, self).setUp()
996
tree = self.make_branch_and_tree('')
997
tree.set_parent_ids([b"spooky"], allow_leftmost_as_ghost=True)
999
tree.commit('msg1', rev_id=b'rev1')
1000
tree.commit('msg2', rev_id=b'rev2')
1002
def test_log_range(self):
1003
self.assertLogRevnos(["-r1..2"], ["2", "1"])
1005
def test_log_norange(self):
1006
self.assertLogRevnos([], ["2", "1"])
1008
def test_log_range_open_begin(self):
1009
(stdout, stderr) = self.run_bzr(['log', '-r..2'], retcode=3)
1010
self.assertEqual(["2", "1"],
1011
[r.revno for r in self.get_captured_revisions()])
1012
self.assertEqual("brz: ERROR: Further revision history missing.\n",
1015
def test_log_range_open_end(self):
1016
self.assertLogRevnos(["-r1.."], ["2", "1"])
1019
class TestLogMatch(TestLogWithLogCatcher):
1021
def prepare_tree(self):
1022
tree = self.make_branch_and_tree('')
1024
['/hello.txt', '/goodbye.txt'])
1025
tree.add('hello.txt')
1026
tree.commit(message='message1', committer='committer1',
1027
authors=['author1'])
1028
tree.add('goodbye.txt')
1029
tree.commit(message='message2', committer='committer2',
1030
authors=['author2'])
1032
def test_message(self):
1034
self.assertLogRevnos(["-m", "message1"], ["1"])
1035
self.assertLogRevnos(["-m", "message2"], ["2"])
1036
self.assertLogRevnos(["-m", "message"], ["2", "1"])
1037
self.assertLogRevnos(["-m", "message1", "-m", "message2"], ["2", "1"])
1038
self.assertLogRevnos(["--match-message", "message1"], ["1"])
1039
self.assertLogRevnos(["--match-message", "message2"], ["2"])
1040
self.assertLogRevnos(["--match-message", "message"], ["2", "1"])
1041
self.assertLogRevnos(["--match-message", "message1",
1042
"--match-message", "message2"], ["2", "1"])
1043
self.assertLogRevnos(["--message", "message1"], ["1"])
1044
self.assertLogRevnos(["--message", "message2"], ["2"])
1045
self.assertLogRevnos(["--message", "message"], ["2", "1"])
1046
self.assertLogRevnos(["--match-message", "message1",
1047
"--message", "message2"], ["2", "1"])
1048
self.assertLogRevnos(["--message", "message1",
1049
"--match-message", "message2"], ["2", "1"])
1051
def test_committer(self):
1053
self.assertLogRevnos(["-m", "committer1"], ["1"])
1054
self.assertLogRevnos(["-m", "committer2"], ["2"])
1055
self.assertLogRevnos(["-m", "committer"], ["2", "1"])
1056
self.assertLogRevnos(["-m", "committer1", "-m", "committer2"],
1058
self.assertLogRevnos(["--match-committer", "committer1"], ["1"])
1059
self.assertLogRevnos(["--match-committer", "committer2"], ["2"])
1060
self.assertLogRevnos(["--match-committer", "committer"], ["2", "1"])
1061
self.assertLogRevnos(["--match-committer", "committer1",
1062
"--match-committer", "committer2"], ["2", "1"])
1064
def test_author(self):
1066
self.assertLogRevnos(["-m", "author1"], ["1"])
1067
self.assertLogRevnos(["-m", "author2"], ["2"])
1068
self.assertLogRevnos(["-m", "author"], ["2", "1"])
1069
self.assertLogRevnos(["-m", "author1", "-m", "author2"],
1071
self.assertLogRevnos(["--match-author", "author1"], ["1"])
1072
self.assertLogRevnos(["--match-author", "author2"], ["2"])
1073
self.assertLogRevnos(["--match-author", "author"], ["2", "1"])
1074
self.assertLogRevnos(["--match-author", "author1",
1075
"--match-author", "author2"], ["2", "1"])
1078
class TestSmartServerLog(tests.TestCaseWithTransport):
1080
def test_standard_log(self):
1081
self.setup_smart_server_with_call_log()
1082
t = self.make_branch_and_tree('branch')
1083
self.build_tree_contents([('branch/foo', b'thecontents')])
1086
self.reset_smart_call_log()
1087
out, err = self.run_bzr(['log', self.get_url('branch')])
1088
# This figure represent the amount of work to perform this use case. It
1089
# is entirely ok to reduce this number if a test fails due to rpc_count
1090
# being too low. If rpc_count increases, more network roundtrips have
1091
# become necessary for this use case. Please do not adjust this number
1092
# upwards without agreement from bzr's network support maintainers.
1093
self.assertThat(self.hpss_calls, ContainsNoVfsCalls)
1094
self.assertLength(1, self.hpss_connections)
1095
self.assertLength(9, self.hpss_calls)
1097
def test_verbose_log(self):
1098
self.setup_smart_server_with_call_log()
1099
t = self.make_branch_and_tree('branch')
1100
self.build_tree_contents([('branch/foo', b'thecontents')])
1103
self.reset_smart_call_log()
1104
out, err = self.run_bzr(['log', '-v', self.get_url('branch')])
1105
# This figure represent the amount of work to perform this use case. It
1106
# is entirely ok to reduce this number if a test fails due to rpc_count
1107
# being too low. If rpc_count increases, more network roundtrips have
1108
# become necessary for this use case. Please do not adjust this number
1109
# upwards without agreement from bzr's network support maintainers.
1110
self.assertLength(10, self.hpss_calls)
1111
self.assertLength(1, self.hpss_connections)
1112
self.assertThat(self.hpss_calls, ContainsNoVfsCalls)
1114
def test_per_file(self):
1115
self.setup_smart_server_with_call_log()
1116
t = self.make_branch_and_tree('branch')
1117
self.build_tree_contents([('branch/foo', b'thecontents')])
1120
self.reset_smart_call_log()
1121
out, err = self.run_bzr(['log', '-v', self.get_url('branch') + "/foo"])
1122
# This figure represent the amount of work to perform this use case. It
1123
# is entirely ok to reduce this number if a test fails due to rpc_count
1124
# being too low. If rpc_count increases, more network roundtrips have
1125
# become necessary for this use case. Please do not adjust this number
1126
# upwards without agreement from bzr's network support maintainers.
1127
self.assertLength(14, self.hpss_calls)
1128
self.assertLength(1, self.hpss_connections)
1129
self.assertThat(self.hpss_calls, ContainsNoVfsCalls)
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)