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
24
from bzrlib.tests.blackbox import ExternalBase
25
from bzrlib.tests import TestCaseInTempDir
28
class TestLog(ExternalBase):
32
self.build_tree(['hello.txt', 'goodbye.txt', 'meep.txt'])
33
self.runbzr("add hello.txt")
34
self.runbzr("commit -m message1 hello.txt")
35
self.runbzr("add goodbye.txt")
36
self.runbzr("commit -m message2 goodbye.txt")
37
self.runbzr("add meep.txt")
38
self.runbzr("commit -m message3 meep.txt")
39
self.full_log = self.runbzr("log")[0]
30
from bzrlib.tests import (
36
class TestLog(tests.TestCaseWithTransport, test_log.TestLogMixin):
38
def make_minimal_branch(self, path='.', format=None):
39
tree = self.make_branch_and_tree(path, format=format)
40
self.build_tree([path + '/hello.txt'])
42
tree.commit(message='message1')
45
def make_linear_branch(self, path='.', format=None):
46
tree = self.make_branch_and_tree(path, format=format)
48
[path + '/hello.txt', path + '/goodbye.txt', path + '/meep.txt'])
50
tree.commit(message='message1')
51
tree.add('goodbye.txt')
52
tree.commit(message='message2')
54
tree.commit(message='message3')
57
def make_merged_branch(self, path='.', format=None):
58
tree = self.make_linear_branch(path, format)
59
tree2 = tree.bzrdir.sprout('tree2',
60
revision_id=tree.branch.get_rev_id(1)).open_workingtree()
61
tree2.commit(message='tree2 message2')
62
tree2.commit(message='tree2 message3')
63
tree.merge_from_branch(tree2.branch)
64
tree.commit(message='merge')
68
class TestLogWithLogCatcher(TestLog):
71
super(TestLogWithLogCatcher, self).setUp()
72
# Capture log formatter creations
73
class MyLogFormatter(test_log.LogCatcher):
75
def __new__(klass, *args, **kwargs):
76
self.log_catcher = test_log.LogCatcher(*args, **kwargs)
77
# Always return our own log formatter
78
return self.log_catcher
79
# Break cycle with closure over self on cleanup by removing method
80
self.addCleanup(setattr, MyLogFormatter, "__new__", None)
83
# Always return our own log formatter class hijacking the
84
# default behavior (which requires setting up a config
87
self.overrideAttr(log.log_formatter_registry, 'get_default', getme)
89
def get_captured_revisions(self):
90
return self.log_catcher.revisions
92
def assertLogRevnos(self, args, expected_revnos, working_dir='.',
94
actual_out, actual_err = self.run_bzr(['log'] + args,
95
working_dir=working_dir)
96
self.assertEqual(out, actual_out)
97
self.assertEqual(err, actual_err)
98
self.assertEqual(expected_revnos,
99
[r.revno for r in self.get_captured_revisions()])
101
def assertLogRevnosAndDepths(self, args, expected_revnos_and_depths,
103
self.run_bzr(['log'] + args, working_dir=working_dir)
104
self.assertEqual(expected_revnos_and_depths,
105
[(r.revno, r.merge_depth)
106
for r in self.get_captured_revisions()])
109
class TestLogRevSpecs(TestLogWithLogCatcher):
111
def test_log_no_revspec(self):
112
self.make_linear_branch()
113
self.assertLogRevnos([], ['3', '2', '1'])
41
115
def test_log_null_end_revspec(self):
43
self.assertTrue('revno: 1\n' in self.full_log)
44
self.assertTrue('revno: 2\n' in self.full_log)
45
self.assertTrue('revno: 3\n' in self.full_log)
46
self.assertTrue('message:\n message1\n' in self.full_log)
47
self.assertTrue('message:\n message2\n' in self.full_log)
48
self.assertTrue('message:\n message3\n' in self.full_log)
50
log = self.runbzr("log -r 1..")[0]
51
self.assertEquals(log, self.full_log)
116
self.make_linear_branch()
117
self.assertLogRevnos(['-r1..'], ['3', '2', '1'])
53
119
def test_log_null_begin_revspec(self):
55
log = self.runbzr("log -r ..3")[0]
56
self.assertEquals(self.full_log, log)
120
self.make_linear_branch()
121
self.assertLogRevnos(['-r..3'], ['3', '2', '1'])
58
123
def test_log_null_both_revspecs(self):
60
log = self.runbzr("log -r ..")[0]
61
self.assertEquals(self.full_log, log)
124
self.make_linear_branch()
125
self.assertLogRevnos(['-r..'], ['3', '2', '1'])
63
127
def test_log_negative_begin_revspec_full_log(self):
65
log = self.runbzr("log -r -3..")[0]
66
self.assertEquals(self.full_log, log)
128
self.make_linear_branch()
129
self.assertLogRevnos(['-r-3..'], ['3', '2', '1'])
68
131
def test_log_negative_both_revspec_full_log(self):
70
log = self.runbzr("log -r -3..-1")[0]
71
self.assertEquals(self.full_log, log)
132
self.make_linear_branch()
133
self.assertLogRevnos(['-r-3..-1'], ['3', '2', '1'])
73
135
def test_log_negative_both_revspec_partial(self):
75
log = self.runbzr("log -r -3..-2")[0]
76
self.assertTrue('revno: 1\n' in log)
77
self.assertTrue('revno: 2\n' in log)
78
self.assertTrue('revno: 3\n' not in log)
136
self.make_linear_branch()
137
self.assertLogRevnos(['-r-3..-2'], ['2', '1'])
80
139
def test_log_negative_begin_revspec(self):
82
log = self.runbzr("log -r -2..")[0]
83
self.assertTrue('revno: 1\n' not in log)
84
self.assertTrue('revno: 2\n' in log)
85
self.assertTrue('revno: 3\n' in log)
87
def test_log_postive_revspecs(self):
89
log = self.runbzr("log -r 1..3")[0]
90
self.assertEquals(self.full_log, log)
93
class TestLogMerges(ExternalBase):
140
self.make_linear_branch()
141
self.assertLogRevnos(['-r-2..'], ['3', '2'])
143
def test_log_positive_revspecs(self):
144
self.make_linear_branch()
145
self.assertLogRevnos(['-r1..3'], ['3', '2', '1'])
147
def test_log_dotted_revspecs(self):
148
self.make_merged_branch()
149
self.assertLogRevnos(['-n0', '-r1..1.1.1'], ['1.1.1', '1'])
151
def test_log_limit(self):
152
tree = self.make_branch_and_tree('.')
153
# We want more commits than our batch size starts at
154
for pos in range(10):
155
tree.commit("%s" % pos)
156
self.assertLogRevnos(['--limit', '2'], ['10', '9'])
158
def test_log_limit_short(self):
159
self.make_linear_branch()
160
self.assertLogRevnos(['-l', '2'], ['3', '2'])
162
def test_log_change_revno(self):
163
self.make_linear_branch()
164
self.assertLogRevnos(['-c1'], ['1'])
166
def test_branch_revspec(self):
167
foo = self.make_branch_and_tree('foo')
168
bar = self.make_branch_and_tree('bar')
169
self.build_tree(['foo/foo.txt', 'bar/bar.txt'])
172
foo.commit(message='foo')
173
bar.commit(message='bar')
174
self.run_bzr('log -r branch:../bar', working_dir='foo')
175
self.assertEqual([bar.branch.get_rev_id(1)],
177
for r in self.get_captured_revisions()])
180
class TestLogExcludeCommonAncestry(TestLogWithLogCatcher):
182
def test_exclude_common_ancestry_simple_revnos(self):
183
self.make_linear_branch()
184
self.assertLogRevnos(['-r1..3', '--exclude-common-ancestry'],
188
class TestLogMergedLinearAncestry(TestLogWithLogCatcher):
191
super(TestLogMergedLinearAncestry, self).setUp()
192
# FIXME: Using a MemoryTree would be even better here (but until we
193
# stop calling run_bzr, there is no point) --vila 100118.
194
builder = branchbuilder.BranchBuilder(self.get_transport())
195
builder.start_series()
209
builder.build_snapshot('1', None, [
210
('add', ('', 'root-id', 'directory', ''))])
211
builder.build_snapshot('2', ['1'], [])
213
builder.build_snapshot('1.1.1', ['1'], [])
214
# merge branch into mainline
215
builder.build_snapshot('3', ['2', '1.1.1'], [])
216
# new commits in branch
217
builder.build_snapshot('1.1.2', ['1.1.1'], [])
218
builder.build_snapshot('1.1.3', ['1.1.2'], [])
219
# merge branch into mainline
220
builder.build_snapshot('4', ['3', '1.1.3'], [])
221
# merge mainline into branch
222
builder.build_snapshot('1.1.4', ['1.1.3', '4'], [])
223
# merge branch into mainline
224
builder.build_snapshot('5', ['4', '1.1.4'], [])
225
builder.finish_series()
228
self.assertLogRevnos(['-n0', '-r1.1.1..1.1.4'],
229
['1.1.4', '4', '1.1.3', '1.1.2', '3', '1.1.1'])
230
def test_n0_forward(self):
231
self.assertLogRevnos(['-n0', '-r1.1.1..1.1.4', '--forward'],
232
['3', '1.1.1', '4', '1.1.2', '1.1.3', '1.1.4'])
235
# starting from 1.1.4 we follow the left-hand ancestry
236
self.assertLogRevnos(['-n1', '-r1.1.1..1.1.4'],
237
['1.1.4', '1.1.3', '1.1.2', '1.1.1'])
239
def test_n1_forward(self):
240
self.assertLogRevnos(['-n1', '-r1.1.1..1.1.4', '--forward'],
241
['1.1.1', '1.1.2', '1.1.3', '1.1.4'])
244
class Test_GenerateAllRevisions(TestLogWithLogCatcher):
247
super(Test_GenerateAllRevisions, self).setUp()
248
builder = self.make_branch_with_many_merges()
249
b = builder.get_branch()
251
self.addCleanup(b.unlock)
254
def make_branch_with_many_merges(self, path='.', format=None):
255
builder = branchbuilder.BranchBuilder(self.get_transport())
256
builder.start_series()
257
# The graph below may look a bit complicated (and it may be but I've
258
# banged my head enough on it) but the bug requires at least dotted
259
# revnos *and* merged revisions below that.
260
builder.build_snapshot('1', None, [
261
('add', ('', 'root-id', 'directory', ''))])
262
builder.build_snapshot('2', ['1'], [])
263
builder.build_snapshot('1.1.1', ['1'], [])
264
builder.build_snapshot('2.1.1', ['2'], [])
265
builder.build_snapshot('3', ['2', '1.1.1'], [])
266
builder.build_snapshot('2.1.2', ['2.1.1'], [])
267
builder.build_snapshot('2.2.1', ['2.1.1'], [])
268
builder.build_snapshot('2.1.3', ['2.1.2', '2.2.1'], [])
269
builder.build_snapshot('4', ['3', '2.1.3'], [])
270
builder.build_snapshot('5', ['4', '2.1.2'], [])
271
builder.finish_series()
274
def test_not_an_ancestor(self):
275
self.assertRaises(errors.BzrCommandError,
276
log._generate_all_revisions,
277
self.branch, '1.1.1', '2.1.3', 'reverse',
278
delayed_graph_generation=True)
280
def test_wrong_order(self):
281
self.assertRaises(errors.BzrCommandError,
282
log._generate_all_revisions,
283
self.branch, '5', '2.1.3', 'reverse',
284
delayed_graph_generation=True)
286
def test_no_start_rev_id_with_end_rev_id_being_a_merge(self):
287
revs = log._generate_all_revisions(
288
self.branch, None, '2.1.3',
289
'reverse', delayed_graph_generation=True)
292
class TestLogRevSpecsWithPaths(TestLogWithLogCatcher):
294
def test_log_revno_n_path_wrong_namespace(self):
295
self.make_linear_branch('branch1')
296
self.make_linear_branch('branch2')
297
# There is no guarantee that a path exist between two arbitrary
299
self.run_bzr("log -r revno:2:branch1..revno:3:branch2", retcode=3)
301
def test_log_revno_n_path_correct_order(self):
302
self.make_linear_branch('branch2')
303
self.assertLogRevnos(['-rrevno:1:branch2..revno:3:branch2'],
306
def test_log_revno_n_path(self):
307
self.make_linear_branch('branch2')
308
self.assertLogRevnos(['-rrevno:1:branch2'],
310
rev_props = self.log_catcher.revisions[0].rev.properties
311
self.assertEqual('branch2', rev_props['branch-nick'])
314
class TestLogErrors(TestLog):
316
def test_log_zero_revspec(self):
317
self.make_minimal_branch()
318
self.run_bzr_error(['bzr: ERROR: Logging revision 0 is invalid.'],
321
def test_log_zero_begin_revspec(self):
322
self.make_linear_branch()
323
self.run_bzr_error(['bzr: ERROR: Logging revision 0 is invalid.'],
326
def test_log_zero_end_revspec(self):
327
self.make_linear_branch()
328
self.run_bzr_error(['bzr: ERROR: Logging revision 0 is invalid.'],
331
def test_log_nonexistent_revno(self):
332
self.make_minimal_branch()
333
self.run_bzr_error(["bzr: ERROR: Requested revision: '1234' "
334
"does not exist in branch:"],
337
def test_log_nonexistent_dotted_revno(self):
338
self.make_minimal_branch()
339
self.run_bzr_error(["bzr: ERROR: Requested revision: '123.123' "
340
"does not exist in branch:"],
341
['log', '-r123.123'])
343
def test_log_change_nonexistent_revno(self):
344
self.make_minimal_branch()
345
self.run_bzr_error(["bzr: ERROR: Requested revision: '1234' "
346
"does not exist in branch:"],
349
def test_log_change_nonexistent_dotted_revno(self):
350
self.make_minimal_branch()
351
self.run_bzr_error(["bzr: ERROR: Requested revision: '123.123' "
352
"does not exist in branch:"],
353
['log', '-c123.123'])
355
def test_log_change_single_revno_only(self):
356
self.make_minimal_branch()
357
self.run_bzr_error(['bzr: ERROR: Option --change does not'
358
' accept revision ranges'],
359
['log', '--change', '2..3'])
361
def test_log_change_incompatible_with_revision(self):
362
self.run_bzr_error(['bzr: ERROR: --revision and --change'
363
' are mutually exclusive'],
364
['log', '--change', '2', '--revision', '3'])
366
def test_log_nonexistent_file(self):
367
self.make_minimal_branch()
368
# files that don't exist in either the basis tree or working tree
369
# should give an error
370
out, err = self.run_bzr('log does-not-exist', retcode=3)
371
self.assertContainsRe(err,
372
'Path unknown at end or start of revision range: '
375
def test_log_reversed_revspecs(self):
376
self.make_linear_branch()
377
self.run_bzr_error(('bzr: ERROR: Start revision must be older than '
378
'the end revision.\n',),
381
def test_log_reversed_dotted_revspecs(self):
382
self.make_merged_branch()
383
self.run_bzr_error(('bzr: ERROR: Start revision not found in '
384
'left-hand history of end revision.\n',),
387
def test_log_bad_message_re(self):
388
"""Bad --message argument gives a sensible message
390
See https://bugs.launchpad.net/bzr/+bug/251352
392
self.make_minimal_branch()
393
out, err = self.run_bzr(['log', '-m', '*'], retcode=3)
394
self.assertContainsRe(err, "ERROR.*Invalid pattern.*nothing to repeat")
395
self.assertNotContainsRe(err, "Unprintable exception")
396
self.assertEqual(out, '')
398
def test_log_unsupported_timezone(self):
399
self.make_linear_branch()
400
self.run_bzr_error(['bzr: ERROR: Unsupported timezone format "foo", '
401
'options are "utc", "original", "local".'],
402
['log', '--timezone', 'foo'])
404
def test_log_exclude_ancestry_no_range(self):
405
self.make_linear_branch()
406
self.run_bzr_error(['bzr: ERROR: --exclude-common-ancestry'
407
' requires -r with two revisions'],
408
['log', '--exclude-common-ancestry'])
410
def test_log_exclude_ancestry_single_revision(self):
411
self.make_merged_branch()
412
self.run_bzr_error(['bzr: ERROR: --exclude-common-ancestry'
413
' requires two different revisions'],
414
['log', '--exclude-common-ancestry',
417
class TestLogTags(TestLog):
419
def test_log_with_tags(self):
420
tree = self.make_linear_branch(format='dirstate-tags')
422
branch.tags.set_tag('tag1', branch.get_rev_id(1))
423
branch.tags.set_tag('tag1.1', branch.get_rev_id(1))
424
branch.tags.set_tag('tag3', branch.last_revision())
426
log = self.run_bzr("log -r-1")[0]
427
self.assertTrue('tags: tag3' in log)
429
log = self.run_bzr("log -r1")[0]
430
# I guess that we can't know the order of tags in the output
431
# since dicts are unordered, need to check both possibilities
432
self.assertContainsRe(log, r'tags: (tag1, tag1\.1|tag1\.1, tag1)')
434
def test_merged_log_with_tags(self):
435
branch1_tree = self.make_linear_branch('branch1',
436
format='dirstate-tags')
437
branch1 = branch1_tree.branch
438
branch2_tree = branch1_tree.bzrdir.sprout('branch2').open_workingtree()
439
branch1_tree.commit(message='foobar', allow_pointless=True)
440
branch1.tags.set_tag('tag1', branch1.last_revision())
441
# tags don't propagate if we don't merge
442
self.run_bzr('merge ../branch1', working_dir='branch2')
443
branch2_tree.commit(message='merge branch 1')
444
log = self.run_bzr("log -n0 -r-1", working_dir='branch2')[0]
445
self.assertContainsRe(log, r' tags: tag1')
446
log = self.run_bzr("log -n0 -r3.1.1", working_dir='branch2')[0]
447
self.assertContainsRe(log, r'tags: tag1')
450
class TestLogSignatures(TestLog):
452
def test_log_with_signatures(self):
453
self.requireFeature(features.gpgme)
455
tree = self.make_linear_branch(format='dirstate-tags')
457
log = self.run_bzr("log --signatures")[0]
458
self.assertTrue('signature: no signature' in log)
460
def test_log_without_signatures(self):
461
self.requireFeature(features.gpgme)
463
tree = self.make_linear_branch(format='dirstate-tags')
465
log = self.run_bzr("log")[0]
466
self.assertFalse('signature: no signature' in log)
469
class TestLogVerbose(TestLog):
472
super(TestLogVerbose, self).setUp()
473
self.make_minimal_branch()
475
def assertUseShortDeltaFormat(self, cmd):
476
log = self.run_bzr(cmd)[0]
477
# Check that we use the short status format
478
self.assertContainsRe(log, '(?m)^\s*A hello.txt$')
479
self.assertNotContainsRe(log, '(?m)^\s*added:$')
481
def assertUseLongDeltaFormat(self, cmd):
482
log = self.run_bzr(cmd)[0]
483
# Check that we use the long status format
484
self.assertNotContainsRe(log, '(?m)^\s*A hello.txt$')
485
self.assertContainsRe(log, '(?m)^\s*added:$')
487
def test_log_short_verbose(self):
488
self.assertUseShortDeltaFormat(['log', '--short', '-v'])
490
def test_log_s_verbose(self):
491
self.assertUseShortDeltaFormat(['log', '-S', '-v'])
493
def test_log_short_verbose_verbose(self):
494
self.assertUseLongDeltaFormat(['log', '--short', '-vv'])
496
def test_log_long_verbose(self):
497
# Check that we use the long status format, ignoring the verbosity
499
self.assertUseLongDeltaFormat(['log', '--long', '-v'])
501
def test_log_long_verbose_verbose(self):
502
# Check that we use the long status format, ignoring the verbosity
504
self.assertUseLongDeltaFormat(['log', '--long', '-vv'])
507
class TestLogMerges(TestLogWithLogCatcher):
510
super(TestLogMerges, self).setUp()
511
self.make_branches_with_merges()
513
def make_branches_with_merges(self):
514
level0 = self.make_branch_and_tree('level0')
515
self.wt_commit(level0, 'in branch level0')
516
level1 = level0.bzrdir.sprout('level1').open_workingtree()
517
self.wt_commit(level1, 'in branch level1')
518
level2 = level1.bzrdir.sprout('level2').open_workingtree()
519
self.wt_commit(level2, 'in branch level2')
520
level1.merge_from_branch(level2.branch)
521
self.wt_commit(level1, 'merge branch level2')
522
level0.merge_from_branch(level1.branch)
523
self.wt_commit(level0, 'merge branch level1')
95
525
def test_merges_are_indented_by_level(self):
96
self.build_tree(['parent/'])
97
self.run_bzr('init', 'parent')
98
self.run_bzr('commit', '-m', 'first post', '--unchanged', 'parent')
99
self.run_bzr('branch', 'parent', 'child')
100
self.run_bzr('commit', '-m', 'branch 1', '--unchanged', 'child')
101
self.run_bzr('branch', 'child', 'smallerchild')
102
self.run_bzr('commit', '-m', 'branch 2', '--unchanged', 'smallerchild')
104
self.run_bzr('merge', '../smallerchild')
105
self.run_bzr('commit', '-m', 'merge branch 2')
106
os.chdir('../parent')
107
self.run_bzr('merge', '../child')
108
self.run_bzr('commit', '-m', 'merge branch 1')
109
out,err = self.run_bzr('log')
110
# the log will look something like:
111
# self.assertEqual("""\
112
#------------------------------------------------------------
114
#committer: Robert Collins <foo@example.com>
116
#timestamp: Tue 2006-03-28 22:31:40 +1100
119
# ------------------------------------------------------------
120
# merged: foo@example.com-20060328113140-91f43cfb46dc2863
121
# committer: Robert Collins <foo@example.com>
123
# timestamp: Tue 2006-03-28 22:31:40 +1100
126
# ------------------------------------------------------------
127
# merged: foo@example.com-20060328113140-1ba24f850a0ef573
128
# committer: Robert Collins <foo@example.com>
129
# branch nick: smallerchild
130
# timestamp: Tue 2006-03-28 22:31:40 +1100
133
# ------------------------------------------------------------
134
# merged: foo@example.com-20060328113140-5749a4757a8ac792
135
# committer: Robert Collins <foo@example.com>
137
# timestamp: Tue 2006-03-28 22:31:40 +1100
140
#------------------------------------------------------------
142
#committer: Robert Collins <foo@example.com>
144
#timestamp: Tue 2006-03-28 22:31:39 +1100
148
# but we dont have a nice pattern matcher hooked up yet, so:
149
# we check for the indenting of the commit message:
150
self.assertTrue(' merge branch 1' in out)
151
self.assertTrue(' merge branch 2' in out)
152
self.assertTrue(' branch 2' in out)
153
self.assertTrue(' branch 1' in out)
154
self.assertTrue(' first post' in out)
155
self.assertEqual('', err)
158
class TestLogEncodings(TestCaseInTempDir):
526
self.run_bzr(['log', '-n0'], working_dir='level0')
527
revnos_and_depth = [(r.revno, r.merge_depth)
528
for r in self.get_captured_revisions()]
529
self.assertEqual([('2', 0), ('1.1.2', 1), ('1.2.1', 2), ('1.1.1', 1),
531
[(r.revno, r.merge_depth)
532
for r in self.get_captured_revisions()])
534
def test_force_merge_revisions_off(self):
535
self.assertLogRevnos(['-n1'], ['2', '1'], working_dir='level0')
537
def test_force_merge_revisions_on(self):
538
self.assertLogRevnos(['-n0'], ['2', '1.1.2', '1.2.1', '1.1.1', '1'],
539
working_dir='level0')
541
def test_include_merges(self):
542
# Confirm --include-merges gives the same output as -n0
543
msg = ("The option '--include-merges' to 'bzr log' "
544
"has been deprecated in bzr 2.5. "
545
"Please use '--include-merged' instead.\n")
546
self.assertLogRevnos(['--include-merges'],
547
['2', '1.1.2', '1.2.1', '1.1.1', '1'],
548
working_dir='level0', err=msg)
549
self.assertLogRevnos(['--include-merges'],
550
['2', '1.1.2', '1.2.1', '1.1.1', '1'],
551
working_dir='level0', err=msg)
552
out_im, err_im = self.run_bzr('log --include-merges',
553
working_dir='level0')
554
out_n0, err_n0 = self.run_bzr('log -n0', working_dir='level0')
555
self.assertEqual(msg, err_im)
556
self.assertEqual('', err_n0)
557
self.assertEqual(out_im, out_n0)
559
def test_include_merged(self):
560
# Confirm --include-merged gives the same output as -n0
561
expected = ['2', '1.1.2', '1.2.1', '1.1.1', '1']
562
self.assertLogRevnos(['--include-merged'],
563
expected, working_dir='level0')
564
self.assertLogRevnos(['--include-merged'],
565
expected, working_dir='level0')
567
def test_force_merge_revisions_N(self):
568
self.assertLogRevnos(['-n2'],
569
['2', '1.1.2', '1.1.1', '1'],
570
working_dir='level0')
572
def test_merges_single_merge_rev(self):
573
self.assertLogRevnosAndDepths(['-n0', '-r1.1.2'],
574
[('1.1.2', 0), ('1.2.1', 1)],
575
working_dir='level0')
577
def test_merges_partial_range(self):
578
self.assertLogRevnosAndDepths(
579
['-n0', '-r1.1.1..1.1.2'],
580
[('1.1.2', 0), ('1.2.1', 1), ('1.1.1', 0)],
581
working_dir='level0')
583
def test_merges_partial_range_ignore_before_lower_bound(self):
584
"""Dont show revisions before the lower bound's merged revs"""
585
self.assertLogRevnosAndDepths(
586
['-n0', '-r1.1.2..2'],
587
[('2', 0), ('1.1.2', 1), ('1.2.1', 2)],
588
working_dir='level0')
590
def test_omit_merges_with_sidelines(self):
591
self.assertLogRevnos(['--omit-merges', '-n0'], ['1.2.1', '1.1.1', '1'],
592
working_dir='level0')
594
def test_omit_merges_without_sidelines(self):
595
self.assertLogRevnos(['--omit-merges', '-n1'], ['1'],
596
working_dir='level0')
599
class TestLogDiff(TestLogWithLogCatcher):
601
# FIXME: We need specific tests for each LogFormatter about how the diffs
602
# are displayed: --long indent them by depth, --short use a fixed
603
# indent and --line does't display them. -- vila 10019
606
super(TestLogDiff, self).setUp()
607
self.make_branch_with_diffs()
609
def make_branch_with_diffs(self):
610
level0 = self.make_branch_and_tree('level0')
611
self.build_tree(['level0/file1', 'level0/file2'])
614
self.wt_commit(level0, 'in branch level0')
616
level1 = level0.bzrdir.sprout('level1').open_workingtree()
617
self.build_tree_contents([('level1/file2', 'hello\n')])
618
self.wt_commit(level1, 'in branch level1')
619
level0.merge_from_branch(level1.branch)
620
self.wt_commit(level0, 'merge branch level1')
622
def _diff_file1_revno1(self):
623
return """=== added file 'file1'
624
--- file1\t1970-01-01 00:00:00 +0000
625
+++ file1\t2005-11-22 00:00:00 +0000
627
+contents of level0/file1
631
def _diff_file2_revno2(self):
632
return """=== modified file 'file2'
633
--- file2\t2005-11-22 00:00:00 +0000
634
+++ file2\t2005-11-22 00:00:01 +0000
636
-contents of level0/file2
641
def _diff_file2_revno1_1_1(self):
642
return """=== modified file 'file2'
643
--- file2\t2005-11-22 00:00:00 +0000
644
+++ file2\t2005-11-22 00:00:01 +0000
646
-contents of level0/file2
651
def _diff_file2_revno1(self):
652
return """=== added file 'file2'
653
--- file2\t1970-01-01 00:00:00 +0000
654
+++ file2\t2005-11-22 00:00:00 +0000
656
+contents of level0/file2
660
def assertLogRevnosAndDiff(self, args, expected,
662
self.run_bzr(['log', '-p'] + args, working_dir=working_dir)
663
expected_revnos_and_depths = [
664
(revno, depth) for revno, depth, diff in expected]
665
# Check the revnos and depths first to make debugging easier
666
self.assertEqual(expected_revnos_and_depths,
667
[(r.revno, r.merge_depth)
668
for r in self.get_captured_revisions()])
669
# Now check the diffs, adding the revno in case of failure
670
fmt = 'In revno %s\n%s'
671
for expected_rev, actual_rev in izip(expected,
672
self.get_captured_revisions()):
673
revno, depth, expected_diff = expected_rev
674
actual_diff = actual_rev.diff
675
self.assertEqualDiff(fmt % (revno, expected_diff),
676
fmt % (revno, actual_diff))
678
def test_log_diff_with_merges(self):
679
self.assertLogRevnosAndDiff(
681
[('2', 0, self._diff_file2_revno2()),
682
('1.1.1', 1, self._diff_file2_revno1_1_1()),
683
('1', 0, self._diff_file1_revno1()
684
+ self._diff_file2_revno1())],
685
working_dir='level0')
688
def test_log_diff_file1(self):
689
self.assertLogRevnosAndDiff(['-n0', 'file1'],
690
[('1', 0, self._diff_file1_revno1())],
691
working_dir='level0')
693
def test_log_diff_file2(self):
694
self.assertLogRevnosAndDiff(['-n1', 'file2'],
695
[('2', 0, self._diff_file2_revno2()),
696
('1', 0, self._diff_file2_revno1())],
697
working_dir='level0')
700
class TestLogUnicodeDiff(TestLog):
702
def test_log_show_diff_non_ascii(self):
703
# Smoke test for bug #328007 UnicodeDecodeError on 'log -p'
704
message = u'Message with \xb5'
705
body = 'Body with \xb5\n'
706
wt = self.make_branch_and_tree('.')
707
self.build_tree_contents([('foo', body)])
709
wt.commit(message=message)
710
# check that command won't fail with unicode error
711
# don't care about exact output because we have other tests for this
712
out,err = self.run_bzr('log -p --long')
713
self.assertNotEqual('', out)
714
self.assertEqual('', err)
715
out,err = self.run_bzr('log -p --short')
716
self.assertNotEqual('', out)
717
self.assertEqual('', err)
718
out,err = self.run_bzr('log -p --line')
719
self.assertNotEqual('', out)
720
self.assertEqual('', err)
723
class TestLogEncodings(tests.TestCaseInTempDir):
161
726
_message = u'Message with \xb5'
255
816
# Make sure the cp1251 string is not found anywhere
256
817
self.assertEquals(-1, stdout.find(test_in_cp1251))
820
class TestLogFile(TestLogWithLogCatcher):
822
def test_log_local_branch_file(self):
823
"""We should be able to log files in local treeless branches"""
824
tree = self.make_branch_and_tree('tree')
825
self.build_tree(['tree/file'])
827
tree.commit('revision 1')
828
tree.bzrdir.destroy_workingtree()
829
self.run_bzr('log tree/file')
831
def prepare_tree(self, complex=False):
832
# The complex configuration includes deletes and renames
833
tree = self.make_branch_and_tree('parent')
834
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
836
tree.commit('add file1')
838
tree.commit('add file2')
840
tree.commit('add file3')
841
child_tree = tree.bzrdir.sprout('child').open_workingtree()
842
self.build_tree_contents([('child/file2', 'hello')])
843
child_tree.commit(message='branch 1')
844
tree.merge_from_branch(child_tree.branch)
845
tree.commit(message='merge child branch')
848
tree.commit('remove file2')
849
tree.rename_one('file3', 'file4')
850
tree.commit('file3 is now called file4')
852
tree.commit('remove file1')
855
# FIXME: It would be good to parametrize the following tests against all
856
# formatters. But the revisions selection is not *currently* part of the
857
# LogFormatter contract, so using LogCatcher is sufficient -- vila 100118
858
def test_log_file1(self):
860
self.assertLogRevnos(['-n0', 'file1'], ['1'])
862
def test_log_file2(self):
865
self.assertLogRevnos(['-n0', 'file2'], ['4', '3.1.1', '2'])
866
# file2 in a merge revision
867
self.assertLogRevnos(['-n0', '-r3.1.1', 'file2'], ['3.1.1'])
868
# file2 in a mainline revision
869
self.assertLogRevnos(['-n0', '-r4', 'file2'], ['4', '3.1.1'])
870
# file2 since a revision
871
self.assertLogRevnos(['-n0', '-r3..', 'file2'], ['4', '3.1.1'])
872
# file2 up to a revision
873
self.assertLogRevnos(['-n0', '-r..3', 'file2'], ['2'])
875
def test_log_file3(self):
877
self.assertLogRevnos(['-n0', 'file3'], ['3'])
879
def test_log_file_historical_missing(self):
880
# Check logging a deleted file gives an error if the
881
# file isn't found at the end or start of the revision range
882
self.prepare_tree(complex=True)
883
err_msg = "Path unknown at end or start of revision range: file2"
884
err = self.run_bzr('log file2', retcode=3)[1]
885
self.assertContainsRe(err, err_msg)
887
def test_log_file_historical_end(self):
888
# Check logging a deleted file is ok if the file existed
889
# at the end the revision range
890
self.prepare_tree(complex=True)
891
self.assertLogRevnos(['-n0', '-r..4', 'file2'], ['4', '3.1.1', '2'])
893
def test_log_file_historical_start(self):
894
# Check logging a deleted file is ok if the file existed
895
# at the start of the revision range
896
self.prepare_tree(complex=True)
897
self.assertLogRevnos(['file1'], ['1'])
899
def test_log_file_renamed(self):
900
"""File matched against revision range, not current tree."""
901
self.prepare_tree(complex=True)
903
# Check logging a renamed file gives an error by default
904
err_msg = "Path unknown at end or start of revision range: file3"
905
err = self.run_bzr('log file3', retcode=3)[1]
906
self.assertContainsRe(err, err_msg)
908
# Check we can see a renamed file if we give the right end revision
909
self.assertLogRevnos(['-r..4', 'file3'], ['3'])
912
class TestLogMultiple(TestLogWithLogCatcher):
914
def prepare_tree(self):
915
tree = self.make_branch_and_tree('parent')
922
'parent/dir1/dir2/file3',
925
tree.commit('add file1')
927
tree.commit('add file2')
928
tree.add(['dir1', 'dir1/dir2', 'dir1/dir2/file3'])
929
tree.commit('add file3')
931
tree.commit('add file4')
932
tree.add('dir1/file5')
933
tree.commit('add file5')
934
child_tree = tree.bzrdir.sprout('child').open_workingtree()
935
self.build_tree_contents([('child/file2', 'hello')])
936
child_tree.commit(message='branch 1')
937
tree.merge_from_branch(child_tree.branch)
938
tree.commit(message='merge child branch')
941
def test_log_files(self):
942
"""The log for multiple file should only list revs for those files"""
944
self.assertLogRevnos(['file1', 'file2', 'dir1/dir2/file3'],
945
['6', '5.1.1', '3', '2', '1'])
947
def test_log_directory(self):
948
"""The log for a directory should show all nested files."""
950
self.assertLogRevnos(['dir1'], ['5', '3'])
952
def test_log_nested_directory(self):
953
"""The log for a directory should show all nested files."""
955
self.assertLogRevnos(['dir1/dir2'], ['3'])
957
def test_log_in_nested_directory(self):
958
"""The log for a directory should show all nested files."""
961
self.assertLogRevnos(['.'], ['5', '3'])
963
def test_log_files_and_directories(self):
964
"""Logging files and directories together should be fine."""
966
self.assertLogRevnos(['file4', 'dir1/dir2'], ['4', '3'])
968
def test_log_files_and_dirs_in_nested_directory(self):
969
"""The log for a directory should show all nested files."""
972
self.assertLogRevnos(['dir2', 'file5'], ['5', '3'])
975
class MainlineGhostTests(TestLogWithLogCatcher):
978
super(MainlineGhostTests, self).setUp()
979
tree = self.make_branch_and_tree('')
980
tree.set_parent_ids(["spooky"], allow_leftmost_as_ghost=True)
982
tree.commit('msg1', rev_id='rev1')
983
tree.commit('msg2', rev_id='rev2')
985
def test_log_range(self):
986
self.assertLogRevnos(["-r1..2"], ["2", "1"])
988
def test_log_norange(self):
989
self.assertLogRevnos([], ["2", "1"])
991
def test_log_range_open_begin(self):
992
self.knownFailure("log with ghosts fails. bug #726466")
993
(stdout, stderr) = self.run_bzr(['log', '-r..2'], retcode=3)
994
self.assertEqual(["2", "1"],
995
[r.revno for r in self.get_captured_revisions()])
996
self.assertEquals("bzr: ERROR: Further revision history missing.", stderr)
998
def test_log_range_open_end(self):
999
self.assertLogRevnos(["-r1.."], ["2", "1"])
1001
class TestLogMatch(TestLogWithLogCatcher):
1002
def prepare_tree(self):
1003
tree = self.make_branch_and_tree('')
1005
['/hello.txt', '/goodbye.txt'])
1006
tree.add('hello.txt')
1007
tree.commit(message='message1', committer='committer1', authors=['author1'])
1008
tree.add('goodbye.txt')
1009
tree.commit(message='message2', committer='committer2', authors=['author2'])
1011
def test_message(self):
1013
self.assertLogRevnos(["-m", "message1"], ["1"])
1014
self.assertLogRevnos(["-m", "message2"], ["2"])
1015
self.assertLogRevnos(["-m", "message"], ["2", "1"])
1016
self.assertLogRevnos(["-m", "message1", "-m", "message2"], ["2", "1"])
1017
self.assertLogRevnos(["--match-message", "message1"], ["1"])
1018
self.assertLogRevnos(["--match-message", "message2"], ["2"])
1019
self.assertLogRevnos(["--match-message", "message"], ["2", "1"])
1020
self.assertLogRevnos(["--match-message", "message1",
1021
"--match-message", "message2"], ["2", "1"])
1022
self.assertLogRevnos(["--message", "message1"], ["1"])
1023
self.assertLogRevnos(["--message", "message2"], ["2"])
1024
self.assertLogRevnos(["--message", "message"], ["2", "1"])
1025
self.assertLogRevnos(["--match-message", "message1",
1026
"--message", "message2"], ["2", "1"])
1027
self.assertLogRevnos(["--message", "message1",
1028
"--match-message", "message2"], ["2", "1"])
1030
def test_committer(self):
1032
self.assertLogRevnos(["-m", "committer1"], ["1"])
1033
self.assertLogRevnos(["-m", "committer2"], ["2"])
1034
self.assertLogRevnos(["-m", "committer"], ["2", "1"])
1035
self.assertLogRevnos(["-m", "committer1", "-m", "committer2"],
1037
self.assertLogRevnos(["--match-committer", "committer1"], ["1"])
1038
self.assertLogRevnos(["--match-committer", "committer2"], ["2"])
1039
self.assertLogRevnos(["--match-committer", "committer"], ["2", "1"])
1040
self.assertLogRevnos(["--match-committer", "committer1",
1041
"--match-committer", "committer2"], ["2", "1"])
1043
def test_author(self):
1045
self.assertLogRevnos(["-m", "author1"], ["1"])
1046
self.assertLogRevnos(["-m", "author2"], ["2"])
1047
self.assertLogRevnos(["-m", "author"], ["2", "1"])
1048
self.assertLogRevnos(["-m", "author1", "-m", "author2"],
1050
self.assertLogRevnos(["--match-author", "author1"], ["1"])
1051
self.assertLogRevnos(["--match-author", "author2"], ["2"])
1052
self.assertLogRevnos(["--match-author", "author"], ["2", "1"])
1053
self.assertLogRevnos(["--match-author", "author1",
1054
"--match-author", "author2"], ["2", "1"])