13
14
# You should have received a copy of the GNU General Public License
14
15
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
19
"""Black-box tests for bzr log."""
20
from itertools import izip
30
from bzrlib.tests import (
34
from bzrlib.tests.matchers import ContainsNoVfsCalls
37
class TestLog(tests.TestCaseWithTransport, test_log.TestLogMixin):
39
def make_minimal_branch(self, path='.', format=None):
40
tree = self.make_branch_and_tree(path, format=format)
41
self.build_tree([path + '/hello.txt'])
43
tree.commit(message='message1')
46
def make_linear_branch(self, path='.', format=None):
47
tree = self.make_branch_and_tree(path, format=format)
49
[path + '/hello.txt', path + '/goodbye.txt', path + '/meep.txt'])
51
tree.commit(message='message1')
52
tree.add('goodbye.txt')
53
tree.commit(message='message2')
55
tree.commit(message='message3')
58
def make_merged_branch(self, path='.', format=None):
59
tree = self.make_linear_branch(path, format)
60
tree2 = tree.bzrdir.sprout('tree2',
61
revision_id=tree.branch.get_rev_id(1)).open_workingtree()
62
tree2.commit(message='tree2 message2')
63
tree2.commit(message='tree2 message3')
64
tree.merge_from_branch(tree2.branch)
65
tree.commit(message='merge')
69
class TestLogWithLogCatcher(TestLog):
72
super(TestLogWithLogCatcher, self).setUp()
73
# Capture log formatter creations
74
class MyLogFormatter(test_log.LogCatcher):
76
def __new__(klass, *args, **kwargs):
77
self.log_catcher = test_log.LogCatcher(*args, **kwargs)
78
# Always return our own log formatter
79
return self.log_catcher
80
# Break cycle with closure over self on cleanup by removing method
81
self.addCleanup(setattr, MyLogFormatter, "__new__", None)
84
# Always return our own log formatter class hijacking the
85
# default behavior (which requires setting up a config
88
self.overrideAttr(log.log_formatter_registry, 'get_default', getme)
90
def get_captured_revisions(self):
91
return self.log_catcher.revisions
93
def assertLogRevnos(self, args, expected_revnos, working_dir='.',
95
actual_out, actual_err = self.run_bzr(['log'] + args,
96
working_dir=working_dir)
97
self.assertEqual(out, actual_out)
98
self.assertEqual(err, actual_err)
99
self.assertEqual(expected_revnos,
100
[r.revno for r in self.get_captured_revisions()])
102
def assertLogRevnosAndDepths(self, args, expected_revnos_and_depths,
104
self.run_bzr(['log'] + args, working_dir=working_dir)
105
self.assertEqual(expected_revnos_and_depths,
106
[(r.revno, r.merge_depth)
107
for r in self.get_captured_revisions()])
110
class TestLogRevSpecs(TestLogWithLogCatcher):
112
def test_log_no_revspec(self):
113
self.make_linear_branch()
114
self.assertLogRevnos([], ['3', '2', '1'])
24
from bzrlib.tests.blackbox import ExternalBase
25
from bzrlib.tests import TestCaseInTempDir
28
class TestLog(ExternalBase):
32
self.build_tree(['hello.txt', 'goodbye.txt', 'meep.txt'])
33
self.runbzr("add hello.txt")
34
self.runbzr("commit -m message1 hello.txt")
35
self.runbzr("add goodbye.txt")
36
self.runbzr("commit -m message2 goodbye.txt")
37
self.runbzr("add meep.txt")
38
self.runbzr("commit -m message3 meep.txt")
39
self.full_log = self.runbzr("log")[0]
116
41
def test_log_null_end_revspec(self):
117
self.make_linear_branch()
118
self.assertLogRevnos(['-r1..'], ['3', '2', '1'])
43
self.assertTrue('revno: 1\n' in self.full_log)
44
self.assertTrue('revno: 2\n' in self.full_log)
45
self.assertTrue('revno: 3\n' in self.full_log)
46
self.assertTrue('message:\n message1\n' in self.full_log)
47
self.assertTrue('message:\n message2\n' in self.full_log)
48
self.assertTrue('message:\n message3\n' in self.full_log)
50
log = self.runbzr("log -r 1..")[0]
51
self.assertEquals(log, self.full_log)
120
53
def test_log_null_begin_revspec(self):
121
self.make_linear_branch()
122
self.assertLogRevnos(['-r..3'], ['3', '2', '1'])
55
log = self.runbzr("log -r ..3")[0]
56
self.assertEquals(self.full_log, log)
124
58
def test_log_null_both_revspecs(self):
125
self.make_linear_branch()
126
self.assertLogRevnos(['-r..'], ['3', '2', '1'])
60
log = self.runbzr("log -r ..")[0]
61
self.assertEquals(self.full_log, log)
128
63
def test_log_negative_begin_revspec_full_log(self):
129
self.make_linear_branch()
130
self.assertLogRevnos(['-r-3..'], ['3', '2', '1'])
65
log = self.runbzr("log -r -3..")[0]
66
self.assertEquals(self.full_log, log)
132
68
def test_log_negative_both_revspec_full_log(self):
133
self.make_linear_branch()
134
self.assertLogRevnos(['-r-3..-1'], ['3', '2', '1'])
70
log = self.runbzr("log -r -3..-1")[0]
71
self.assertEquals(self.full_log, log)
136
73
def test_log_negative_both_revspec_partial(self):
137
self.make_linear_branch()
138
self.assertLogRevnos(['-r-3..-2'], ['2', '1'])
75
log = self.runbzr("log -r -3..-2")[0]
76
self.assertTrue('revno: 1\n' in log)
77
self.assertTrue('revno: 2\n' in log)
78
self.assertTrue('revno: 3\n' not in log)
140
80
def test_log_negative_begin_revspec(self):
141
self.make_linear_branch()
142
self.assertLogRevnos(['-r-2..'], ['3', '2'])
144
def test_log_positive_revspecs(self):
145
self.make_linear_branch()
146
self.assertLogRevnos(['-r1..3'], ['3', '2', '1'])
148
def test_log_dotted_revspecs(self):
149
self.make_merged_branch()
150
self.assertLogRevnos(['-n0', '-r1..1.1.1'], ['1.1.1', '1'])
152
def test_log_limit(self):
153
tree = self.make_branch_and_tree('.')
154
# We want more commits than our batch size starts at
155
for pos in range(10):
156
tree.commit("%s" % pos)
157
self.assertLogRevnos(['--limit', '2'], ['10', '9'])
159
def test_log_limit_short(self):
160
self.make_linear_branch()
161
self.assertLogRevnos(['-l', '2'], ['3', '2'])
163
def test_log_change_revno(self):
164
self.make_linear_branch()
165
self.assertLogRevnos(['-c1'], ['1'])
167
def test_branch_revspec(self):
168
foo = self.make_branch_and_tree('foo')
169
bar = self.make_branch_and_tree('bar')
170
self.build_tree(['foo/foo.txt', 'bar/bar.txt'])
173
foo.commit(message='foo')
174
bar.commit(message='bar')
175
self.run_bzr('log -r branch:../bar', working_dir='foo')
176
self.assertEqual([bar.branch.get_rev_id(1)],
178
for r in self.get_captured_revisions()])
181
class TestLogExcludeCommonAncestry(TestLogWithLogCatcher):
183
def test_exclude_common_ancestry_simple_revnos(self):
184
self.make_linear_branch()
185
self.assertLogRevnos(['-r1..3', '--exclude-common-ancestry'],
189
class TestLogMergedLinearAncestry(TestLogWithLogCatcher):
192
super(TestLogMergedLinearAncestry, self).setUp()
193
# FIXME: Using a MemoryTree would be even better here (but until we
194
# stop calling run_bzr, there is no point) --vila 100118.
195
builder = branchbuilder.BranchBuilder(self.get_transport())
196
builder.start_series()
214
builder.build_snapshot('1', None, [
215
('add', ('', 'root-id', 'directory', ''))])
216
builder.build_snapshot('2', ['1'], [])
218
builder.build_snapshot('1.1.1', ['1'], [])
219
# merge branch into mainline
220
builder.build_snapshot('3', ['2', '1.1.1'], [])
221
# new commits in branch
222
builder.build_snapshot('1.1.2', ['1.1.1'], [])
223
builder.build_snapshot('1.1.3', ['1.1.2'], [])
224
# merge branch into mainline
225
builder.build_snapshot('4', ['3', '1.1.3'], [])
226
# merge mainline into branch
227
builder.build_snapshot('1.1.4', ['1.1.3', '4'], [])
228
# merge branch into mainline
229
builder.build_snapshot('5', ['4', '1.1.4'], [])
230
builder.build_snapshot('5.1.1', ['5'], [])
231
builder.build_snapshot('6', ['5', '5.1.1'], [])
232
builder.finish_series()
235
self.assertLogRevnos(['-n0', '-r1.1.1..1.1.4'],
236
['1.1.4', '4', '1.1.3', '1.1.2', '3', '1.1.1'])
237
def test_n0_forward(self):
238
self.assertLogRevnos(['-n0', '-r1.1.1..1.1.4', '--forward'],
239
['3', '1.1.1', '4', '1.1.2', '1.1.3', '1.1.4'])
242
# starting from 1.1.4 we follow the left-hand ancestry
243
self.assertLogRevnos(['-n1', '-r1.1.1..1.1.4'],
244
['1.1.4', '1.1.3', '1.1.2', '1.1.1'])
246
def test_n1_forward(self):
247
self.assertLogRevnos(['-n1', '-r1.1.1..1.1.4', '--forward'],
248
['1.1.1', '1.1.2', '1.1.3', '1.1.4'])
250
def test_fallback_when_end_rev_is_not_on_mainline(self):
251
self.assertLogRevnos(['-n1', '-r1.1.1..5.1.1'],
252
# We don't get 1.1.1 because we say -n1
253
['5.1.1', '5', '4', '3'])
256
class Test_GenerateAllRevisions(TestLogWithLogCatcher):
259
super(Test_GenerateAllRevisions, self).setUp()
260
builder = self.make_branch_with_many_merges()
261
b = builder.get_branch()
263
self.addCleanup(b.unlock)
266
def make_branch_with_many_merges(self, path='.', format=None):
267
builder = branchbuilder.BranchBuilder(self.get_transport())
268
builder.start_series()
269
# The graph below may look a bit complicated (and it may be but I've
270
# banged my head enough on it) but the bug requires at least dotted
271
# revnos *and* merged revisions below that.
285
builder.build_snapshot('1', None, [
286
('add', ('', 'root-id', 'directory', ''))])
287
builder.build_snapshot('2', ['1'], [])
288
builder.build_snapshot('1.1.1', ['1'], [])
289
builder.build_snapshot('2.1.1', ['2'], [])
290
builder.build_snapshot('3', ['2', '1.1.1'], [])
291
builder.build_snapshot('2.1.2', ['2.1.1'], [])
292
builder.build_snapshot('2.2.1', ['2.1.1'], [])
293
builder.build_snapshot('2.1.3', ['2.1.2', '2.2.1'], [])
294
builder.build_snapshot('4', ['3', '2.1.3'], [])
295
builder.build_snapshot('5', ['4', '2.1.2'], [])
296
builder.finish_series()
299
def test_not_an_ancestor(self):
300
self.assertRaises(errors.BzrCommandError,
301
log._generate_all_revisions,
302
self.branch, '1.1.1', '2.1.3', 'reverse',
303
delayed_graph_generation=True)
305
def test_wrong_order(self):
306
self.assertRaises(errors.BzrCommandError,
307
log._generate_all_revisions,
308
self.branch, '5', '2.1.3', 'reverse',
309
delayed_graph_generation=True)
311
def test_no_start_rev_id_with_end_rev_id_being_a_merge(self):
312
revs = log._generate_all_revisions(
313
self.branch, None, '2.1.3',
314
'reverse', delayed_graph_generation=True)
317
class TestLogRevSpecsWithPaths(TestLogWithLogCatcher):
319
def test_log_revno_n_path_wrong_namespace(self):
320
self.make_linear_branch('branch1')
321
self.make_linear_branch('branch2')
322
# There is no guarantee that a path exist between two arbitrary
324
self.run_bzr("log -r revno:2:branch1..revno:3:branch2", retcode=3)
326
def test_log_revno_n_path_correct_order(self):
327
self.make_linear_branch('branch2')
328
self.assertLogRevnos(['-rrevno:1:branch2..revno:3:branch2'],
331
def test_log_revno_n_path(self):
332
self.make_linear_branch('branch2')
333
self.assertLogRevnos(['-rrevno:1:branch2'],
335
rev_props = self.log_catcher.revisions[0].rev.properties
336
self.assertEqual('branch2', rev_props['branch-nick'])
339
class TestLogErrors(TestLog):
341
def test_log_zero_revspec(self):
342
self.make_minimal_branch()
343
self.run_bzr_error(['bzr: ERROR: Logging revision 0 is invalid.'],
346
def test_log_zero_begin_revspec(self):
347
self.make_linear_branch()
348
self.run_bzr_error(['bzr: ERROR: Logging revision 0 is invalid.'],
351
def test_log_zero_end_revspec(self):
352
self.make_linear_branch()
353
self.run_bzr_error(['bzr: ERROR: Logging revision 0 is invalid.'],
356
def test_log_nonexistent_revno(self):
357
self.make_minimal_branch()
358
self.run_bzr_error(["bzr: ERROR: Requested revision: '1234' "
359
"does not exist in branch:"],
362
def test_log_nonexistent_dotted_revno(self):
363
self.make_minimal_branch()
364
self.run_bzr_error(["bzr: ERROR: Requested revision: '123.123' "
365
"does not exist in branch:"],
366
['log', '-r123.123'])
368
def test_log_change_nonexistent_revno(self):
369
self.make_minimal_branch()
370
self.run_bzr_error(["bzr: ERROR: Requested revision: '1234' "
371
"does not exist in branch:"],
374
def test_log_change_nonexistent_dotted_revno(self):
375
self.make_minimal_branch()
376
self.run_bzr_error(["bzr: ERROR: Requested revision: '123.123' "
377
"does not exist in branch:"],
378
['log', '-c123.123'])
380
def test_log_change_single_revno_only(self):
381
self.make_minimal_branch()
382
self.run_bzr_error(['bzr: ERROR: Option --change does not'
383
' accept revision ranges'],
384
['log', '--change', '2..3'])
386
def test_log_change_incompatible_with_revision(self):
387
self.run_bzr_error(['bzr: ERROR: --revision and --change'
388
' are mutually exclusive'],
389
['log', '--change', '2', '--revision', '3'])
391
def test_log_nonexistent_file(self):
392
self.make_minimal_branch()
393
# files that don't exist in either the basis tree or working tree
394
# should give an error
395
out, err = self.run_bzr('log does-not-exist', retcode=3)
396
self.assertContainsRe(err,
397
'Path unknown at end or start of revision range: '
400
def test_log_reversed_revspecs(self):
401
self.make_linear_branch()
402
self.run_bzr_error(('bzr: ERROR: Start revision must be older than '
403
'the end revision.\n',),
406
def test_log_reversed_dotted_revspecs(self):
407
self.make_merged_branch()
408
self.run_bzr_error(('bzr: ERROR: Start revision not found in '
409
'history of end revision.\n',),
412
def test_log_bad_message_re(self):
413
"""Bad --message argument gives a sensible message
415
See https://bugs.launchpad.net/bzr/+bug/251352
417
self.make_minimal_branch()
418
out, err = self.run_bzr(['log', '-m', '*'], retcode=3)
419
self.assertContainsRe(err, "ERROR.*Invalid pattern.*nothing to repeat")
420
self.assertNotContainsRe(err, "Unprintable exception")
421
self.assertEqual(out, '')
423
def test_log_unsupported_timezone(self):
424
self.make_linear_branch()
425
self.run_bzr_error(['bzr: ERROR: Unsupported timezone format "foo", '
426
'options are "utc", "original", "local".'],
427
['log', '--timezone', 'foo'])
429
def test_log_exclude_ancestry_no_range(self):
430
self.make_linear_branch()
431
self.run_bzr_error(['bzr: ERROR: --exclude-common-ancestry'
432
' requires -r with two revisions'],
433
['log', '--exclude-common-ancestry'])
435
def test_log_exclude_ancestry_single_revision(self):
436
self.make_merged_branch()
437
self.run_bzr_error(['bzr: ERROR: --exclude-common-ancestry'
438
' requires two different revisions'],
439
['log', '--exclude-common-ancestry',
442
class TestLogTags(TestLog):
444
def test_log_with_tags(self):
445
tree = self.make_linear_branch(format='dirstate-tags')
447
branch.tags.set_tag('tag1', branch.get_rev_id(1))
448
branch.tags.set_tag('tag1.1', branch.get_rev_id(1))
449
branch.tags.set_tag('tag3', branch.last_revision())
451
log = self.run_bzr("log -r-1")[0]
452
self.assertTrue('tags: tag3' in log)
454
log = self.run_bzr("log -r1")[0]
455
# I guess that we can't know the order of tags in the output
456
# since dicts are unordered, need to check both possibilities
457
self.assertContainsRe(log, r'tags: (tag1, tag1\.1|tag1\.1, tag1)')
459
def test_merged_log_with_tags(self):
460
branch1_tree = self.make_linear_branch('branch1',
461
format='dirstate-tags')
462
branch1 = branch1_tree.branch
463
branch2_tree = branch1_tree.bzrdir.sprout('branch2').open_workingtree()
464
branch1_tree.commit(message='foobar', allow_pointless=True)
465
branch1.tags.set_tag('tag1', branch1.last_revision())
466
# tags don't propagate if we don't merge
467
self.run_bzr('merge ../branch1', working_dir='branch2')
468
branch2_tree.commit(message='merge branch 1')
469
log = self.run_bzr("log -n0 -r-1", working_dir='branch2')[0]
470
self.assertContainsRe(log, r' tags: tag1')
471
log = self.run_bzr("log -n0 -r3.1.1", working_dir='branch2')[0]
472
self.assertContainsRe(log, r'tags: tag1')
475
class TestLogSignatures(TestLog):
477
def test_log_with_signatures(self):
478
self.requireFeature(features.gpgme)
480
tree = self.make_linear_branch(format='dirstate-tags')
482
log = self.run_bzr("log --signatures")[0]
483
self.assertTrue('signature: no signature' in log)
485
def test_log_without_signatures(self):
486
self.requireFeature(features.gpgme)
488
tree = self.make_linear_branch(format='dirstate-tags')
490
log = self.run_bzr("log")[0]
491
self.assertFalse('signature: no signature' in log)
494
class TestLogVerbose(TestLog):
497
super(TestLogVerbose, self).setUp()
498
self.make_minimal_branch()
500
def assertUseShortDeltaFormat(self, cmd):
501
log = self.run_bzr(cmd)[0]
502
# Check that we use the short status format
503
self.assertContainsRe(log, '(?m)^\s*A hello.txt$')
504
self.assertNotContainsRe(log, '(?m)^\s*added:$')
506
def assertUseLongDeltaFormat(self, cmd):
507
log = self.run_bzr(cmd)[0]
508
# Check that we use the long status format
509
self.assertNotContainsRe(log, '(?m)^\s*A hello.txt$')
510
self.assertContainsRe(log, '(?m)^\s*added:$')
512
def test_log_short_verbose(self):
513
self.assertUseShortDeltaFormat(['log', '--short', '-v'])
515
def test_log_s_verbose(self):
516
self.assertUseShortDeltaFormat(['log', '-S', '-v'])
518
def test_log_short_verbose_verbose(self):
519
self.assertUseLongDeltaFormat(['log', '--short', '-vv'])
521
def test_log_long_verbose(self):
522
# Check that we use the long status format, ignoring the verbosity
524
self.assertUseLongDeltaFormat(['log', '--long', '-v'])
526
def test_log_long_verbose_verbose(self):
527
# Check that we use the long status format, ignoring the verbosity
529
self.assertUseLongDeltaFormat(['log', '--long', '-vv'])
532
class TestLogMerges(TestLogWithLogCatcher):
535
super(TestLogMerges, self).setUp()
536
self.make_branches_with_merges()
538
def make_branches_with_merges(self):
539
level0 = self.make_branch_and_tree('level0')
540
self.wt_commit(level0, 'in branch level0')
541
level1 = level0.bzrdir.sprout('level1').open_workingtree()
542
self.wt_commit(level1, 'in branch level1')
543
level2 = level1.bzrdir.sprout('level2').open_workingtree()
544
self.wt_commit(level2, 'in branch level2')
545
level1.merge_from_branch(level2.branch)
546
self.wt_commit(level1, 'merge branch level2')
547
level0.merge_from_branch(level1.branch)
548
self.wt_commit(level0, 'merge branch level1')
82
log = self.runbzr("log -r -2..")[0]
83
self.assertTrue('revno: 1\n' not in log)
84
self.assertTrue('revno: 2\n' in log)
85
self.assertTrue('revno: 3\n' in log)
87
def test_log_postive_revspecs(self):
89
log = self.runbzr("log -r 1..3")[0]
90
self.assertEquals(self.full_log, log)
93
class TestLogMerges(ExternalBase):
550
95
def test_merges_are_indented_by_level(self):
551
self.run_bzr(['log', '-n0'], working_dir='level0')
552
revnos_and_depth = [(r.revno, r.merge_depth)
553
for r in self.get_captured_revisions()]
554
self.assertEqual([('2', 0), ('1.1.2', 1), ('1.2.1', 2), ('1.1.1', 1),
556
[(r.revno, r.merge_depth)
557
for r in self.get_captured_revisions()])
559
def test_force_merge_revisions_off(self):
560
self.assertLogRevnos(['-n1'], ['2', '1'], working_dir='level0')
562
def test_force_merge_revisions_on(self):
563
self.assertLogRevnos(['-n0'], ['2', '1.1.2', '1.2.1', '1.1.1', '1'],
564
working_dir='level0')
566
def test_include_merges(self):
567
# Confirm --include-merges gives the same output as -n0
568
msg = ("The option '--include-merges' to 'bzr log' "
569
"has been deprecated in bzr 2.5. "
570
"Please use '--include-merged' instead.\n")
571
self.assertLogRevnos(['--include-merges'],
572
['2', '1.1.2', '1.2.1', '1.1.1', '1'],
573
working_dir='level0', err=msg)
574
self.assertLogRevnos(['--include-merges'],
575
['2', '1.1.2', '1.2.1', '1.1.1', '1'],
576
working_dir='level0', err=msg)
577
out_im, err_im = self.run_bzr('log --include-merges',
578
working_dir='level0')
579
out_n0, err_n0 = self.run_bzr('log -n0', working_dir='level0')
580
self.assertEqual(msg, err_im)
581
self.assertEqual('', err_n0)
582
self.assertEqual(out_im, out_n0)
584
def test_include_merged(self):
585
# Confirm --include-merged gives the same output as -n0
586
expected = ['2', '1.1.2', '1.2.1', '1.1.1', '1']
587
self.assertLogRevnos(['--include-merged'],
588
expected, working_dir='level0')
589
self.assertLogRevnos(['--include-merged'],
590
expected, working_dir='level0')
592
def test_force_merge_revisions_N(self):
593
self.assertLogRevnos(['-n2'],
594
['2', '1.1.2', '1.1.1', '1'],
595
working_dir='level0')
597
def test_merges_single_merge_rev(self):
598
self.assertLogRevnosAndDepths(['-n0', '-r1.1.2'],
599
[('1.1.2', 0), ('1.2.1', 1)],
600
working_dir='level0')
602
def test_merges_partial_range(self):
603
self.assertLogRevnosAndDepths(
604
['-n0', '-r1.1.1..1.1.2'],
605
[('1.1.2', 0), ('1.2.1', 1), ('1.1.1', 0)],
606
working_dir='level0')
608
def test_merges_partial_range_ignore_before_lower_bound(self):
609
"""Dont show revisions before the lower bound's merged revs"""
610
self.assertLogRevnosAndDepths(
611
['-n0', '-r1.1.2..2'],
612
[('2', 0), ('1.1.2', 1), ('1.2.1', 2)],
613
working_dir='level0')
615
def test_omit_merges_with_sidelines(self):
616
self.assertLogRevnos(['--omit-merges', '-n0'], ['1.2.1', '1.1.1', '1'],
617
working_dir='level0')
619
def test_omit_merges_without_sidelines(self):
620
self.assertLogRevnos(['--omit-merges', '-n1'], ['1'],
621
working_dir='level0')
624
class TestLogDiff(TestLogWithLogCatcher):
626
# FIXME: We need specific tests for each LogFormatter about how the diffs
627
# are displayed: --long indent them by depth, --short use a fixed
628
# indent and --line does't display them. -- vila 10019
631
super(TestLogDiff, self).setUp()
632
self.make_branch_with_diffs()
634
def make_branch_with_diffs(self):
635
level0 = self.make_branch_and_tree('level0')
636
self.build_tree(['level0/file1', 'level0/file2'])
639
self.wt_commit(level0, 'in branch level0')
641
level1 = level0.bzrdir.sprout('level1').open_workingtree()
642
self.build_tree_contents([('level1/file2', 'hello\n')])
643
self.wt_commit(level1, 'in branch level1')
644
level0.merge_from_branch(level1.branch)
645
self.wt_commit(level0, 'merge branch level1')
647
def _diff_file1_revno1(self):
648
return """=== added file 'file1'
649
--- file1\t1970-01-01 00:00:00 +0000
650
+++ file1\t2005-11-22 00:00:00 +0000
652
+contents of level0/file1
656
def _diff_file2_revno2(self):
657
return """=== 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_1_1(self):
667
return """=== modified file 'file2'
668
--- file2\t2005-11-22 00:00:00 +0000
669
+++ file2\t2005-11-22 00:00:01 +0000
671
-contents of level0/file2
676
def _diff_file2_revno1(self):
677
return """=== added file 'file2'
678
--- file2\t1970-01-01 00:00:00 +0000
679
+++ file2\t2005-11-22 00:00:00 +0000
681
+contents of level0/file2
685
def assertLogRevnosAndDiff(self, args, expected,
687
self.run_bzr(['log', '-p'] + args, working_dir=working_dir)
688
expected_revnos_and_depths = [
689
(revno, depth) for revno, depth, diff in expected]
690
# Check the revnos and depths first to make debugging easier
691
self.assertEqual(expected_revnos_and_depths,
692
[(r.revno, r.merge_depth)
693
for r in self.get_captured_revisions()])
694
# Now check the diffs, adding the revno in case of failure
695
fmt = 'In revno %s\n%s'
696
for expected_rev, actual_rev in izip(expected,
697
self.get_captured_revisions()):
698
revno, depth, expected_diff = expected_rev
699
actual_diff = actual_rev.diff
700
self.assertEqualDiff(fmt % (revno, expected_diff),
701
fmt % (revno, actual_diff))
703
def test_log_diff_with_merges(self):
704
self.assertLogRevnosAndDiff(
706
[('2', 0, self._diff_file2_revno2()),
707
('1.1.1', 1, self._diff_file2_revno1_1_1()),
708
('1', 0, self._diff_file1_revno1()
709
+ self._diff_file2_revno1())],
710
working_dir='level0')
713
def test_log_diff_file1(self):
714
self.assertLogRevnosAndDiff(['-n0', 'file1'],
715
[('1', 0, self._diff_file1_revno1())],
716
working_dir='level0')
718
def test_log_diff_file2(self):
719
self.assertLogRevnosAndDiff(['-n1', 'file2'],
720
[('2', 0, self._diff_file2_revno2()),
721
('1', 0, self._diff_file2_revno1())],
722
working_dir='level0')
725
class TestLogUnicodeDiff(TestLog):
727
def test_log_show_diff_non_ascii(self):
728
# Smoke test for bug #328007 UnicodeDecodeError on 'log -p'
729
message = u'Message with \xb5'
730
body = 'Body with \xb5\n'
731
wt = self.make_branch_and_tree('.')
732
self.build_tree_contents([('foo', body)])
734
wt.commit(message=message)
735
# check that command won't fail with unicode error
736
# don't care about exact output because we have other tests for this
737
out,err = self.run_bzr('log -p --long')
738
self.assertNotEqual('', out)
739
self.assertEqual('', err)
740
out,err = self.run_bzr('log -p --short')
741
self.assertNotEqual('', out)
742
self.assertEqual('', err)
743
out,err = self.run_bzr('log -p --line')
744
self.assertNotEqual('', out)
745
self.assertEqual('', err)
748
class TestLogEncodings(tests.TestCaseInTempDir):
96
self.build_tree(['parent/'])
97
self.run_bzr('init', 'parent')
98
self.run_bzr('commit', '-m', 'first post', '--unchanged', 'parent')
99
self.run_bzr('branch', 'parent', 'child')
100
self.run_bzr('commit', '-m', 'branch 1', '--unchanged', 'child')
101
self.run_bzr('branch', 'child', 'smallerchild')
102
self.run_bzr('commit', '-m', 'branch 2', '--unchanged', 'smallerchild')
104
self.run_bzr('merge', '../smallerchild')
105
self.run_bzr('commit', '-m', 'merge branch 2')
106
os.chdir('../parent')
107
self.run_bzr('merge', '../child')
108
self.run_bzr('commit', '-m', 'merge branch 1')
109
out,err = self.run_bzr('log')
110
# the log will look something like:
111
# self.assertEqual("""\
112
#------------------------------------------------------------
114
#committer: Robert Collins <foo@example.com>
116
#timestamp: Tue 2006-03-28 22:31:40 +1100
119
# ------------------------------------------------------------
120
# merged: foo@example.com-20060328113140-91f43cfb46dc2863
121
# committer: Robert Collins <foo@example.com>
123
# timestamp: Tue 2006-03-28 22:31:40 +1100
126
# ------------------------------------------------------------
127
# merged: foo@example.com-20060328113140-1ba24f850a0ef573
128
# committer: Robert Collins <foo@example.com>
129
# branch nick: smallerchild
130
# timestamp: Tue 2006-03-28 22:31:40 +1100
133
# ------------------------------------------------------------
134
# merged: foo@example.com-20060328113140-5749a4757a8ac792
135
# committer: Robert Collins <foo@example.com>
137
# timestamp: Tue 2006-03-28 22:31:40 +1100
140
#------------------------------------------------------------
142
#committer: Robert Collins <foo@example.com>
144
#timestamp: Tue 2006-03-28 22:31:39 +1100
148
# but we dont have a nice pattern matcher hooked up yet, so:
149
# we check for the indenting of the commit message:
150
self.assertTrue(' merge branch 1' in out)
151
self.assertTrue(' merge branch 2' in out)
152
self.assertTrue(' branch 2' in out)
153
self.assertTrue(' branch 1' in out)
154
self.assertTrue(' first post' in out)
155
self.assertEqual('', err)
158
class TestLogEncodings(TestCaseInTempDir):
751
161
_message = u'Message with \xb5'
837
251
test_in_cp866 = '\x92\xa5\xe1\xe2'
838
252
test_in_cp1251 = '\xd2\xe5\xf1\xf2'
839
253
# Make sure the log string is encoded in cp866
840
self.assertEqual(test_in_cp866, message[2:])
254
self.assertEquals(test_in_cp866, message[2:])
841
255
# Make sure the cp1251 string is not found anywhere
842
self.assertEqual(-1, stdout.find(test_in_cp1251))
845
class TestLogFile(TestLogWithLogCatcher):
847
def test_log_local_branch_file(self):
848
"""We should be able to log files in local treeless branches"""
849
tree = self.make_branch_and_tree('tree')
850
self.build_tree(['tree/file'])
852
tree.commit('revision 1')
853
tree.bzrdir.destroy_workingtree()
854
self.run_bzr('log tree/file')
856
def prepare_tree(self, complex=False):
857
# The complex configuration includes deletes and renames
858
tree = self.make_branch_and_tree('parent')
859
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
861
tree.commit('add file1')
863
tree.commit('add file2')
865
tree.commit('add file3')
866
child_tree = tree.bzrdir.sprout('child').open_workingtree()
867
self.build_tree_contents([('child/file2', 'hello')])
868
child_tree.commit(message='branch 1')
869
tree.merge_from_branch(child_tree.branch)
870
tree.commit(message='merge child branch')
873
tree.commit('remove file2')
874
tree.rename_one('file3', 'file4')
875
tree.commit('file3 is now called file4')
877
tree.commit('remove file1')
880
# FIXME: It would be good to parametrize the following tests against all
881
# formatters. But the revisions selection is not *currently* part of the
882
# LogFormatter contract, so using LogCatcher is sufficient -- vila 100118
883
def test_log_file1(self):
885
self.assertLogRevnos(['-n0', 'file1'], ['1'])
887
def test_log_file2(self):
890
self.assertLogRevnos(['-n0', 'file2'], ['4', '3.1.1', '2'])
891
# file2 in a merge revision
892
self.assertLogRevnos(['-n0', '-r3.1.1', 'file2'], ['3.1.1'])
893
# file2 in a mainline revision
894
self.assertLogRevnos(['-n0', '-r4', 'file2'], ['4', '3.1.1'])
895
# file2 since a revision
896
self.assertLogRevnos(['-n0', '-r3..', 'file2'], ['4', '3.1.1'])
897
# file2 up to a revision
898
self.assertLogRevnos(['-n0', '-r..3', 'file2'], ['2'])
900
def test_log_file3(self):
902
self.assertLogRevnos(['-n0', 'file3'], ['3'])
904
def test_log_file_historical_missing(self):
905
# Check logging a deleted file gives an error if the
906
# file isn't found at the end or start of the revision range
907
self.prepare_tree(complex=True)
908
err_msg = "Path unknown at end or start of revision range: file2"
909
err = self.run_bzr('log file2', retcode=3)[1]
910
self.assertContainsRe(err, err_msg)
912
def test_log_file_historical_end(self):
913
# Check logging a deleted file is ok if the file existed
914
# at the end the revision range
915
self.prepare_tree(complex=True)
916
self.assertLogRevnos(['-n0', '-r..4', 'file2'], ['4', '3.1.1', '2'])
918
def test_log_file_historical_start(self):
919
# Check logging a deleted file is ok if the file existed
920
# at the start of the revision range
921
self.prepare_tree(complex=True)
922
self.assertLogRevnos(['file1'], ['1'])
924
def test_log_file_renamed(self):
925
"""File matched against revision range, not current tree."""
926
self.prepare_tree(complex=True)
928
# Check logging a renamed file gives an error by default
929
err_msg = "Path unknown at end or start of revision range: file3"
930
err = self.run_bzr('log file3', retcode=3)[1]
931
self.assertContainsRe(err, err_msg)
933
# Check we can see a renamed file if we give the right end revision
934
self.assertLogRevnos(['-r..4', 'file3'], ['3'])
937
class TestLogMultiple(TestLogWithLogCatcher):
939
def prepare_tree(self):
940
tree = self.make_branch_and_tree('parent')
947
'parent/dir1/dir2/file3',
950
tree.commit('add file1')
952
tree.commit('add file2')
953
tree.add(['dir1', 'dir1/dir2', 'dir1/dir2/file3'])
954
tree.commit('add file3')
956
tree.commit('add file4')
957
tree.add('dir1/file5')
958
tree.commit('add file5')
959
child_tree = tree.bzrdir.sprout('child').open_workingtree()
960
self.build_tree_contents([('child/file2', 'hello')])
961
child_tree.commit(message='branch 1')
962
tree.merge_from_branch(child_tree.branch)
963
tree.commit(message='merge child branch')
966
def test_log_files(self):
967
"""The log for multiple file should only list revs for those files"""
969
self.assertLogRevnos(['file1', 'file2', 'dir1/dir2/file3'],
970
['6', '5.1.1', '3', '2', '1'])
972
def test_log_directory(self):
973
"""The log for a directory should show all nested files."""
975
self.assertLogRevnos(['dir1'], ['5', '3'])
977
def test_log_nested_directory(self):
978
"""The log for a directory should show all nested files."""
980
self.assertLogRevnos(['dir1/dir2'], ['3'])
982
def test_log_in_nested_directory(self):
983
"""The log for a directory should show all nested files."""
986
self.assertLogRevnos(['.'], ['5', '3'])
988
def test_log_files_and_directories(self):
989
"""Logging files and directories together should be fine."""
991
self.assertLogRevnos(['file4', 'dir1/dir2'], ['4', '3'])
993
def test_log_files_and_dirs_in_nested_directory(self):
994
"""The log for a directory should show all nested files."""
997
self.assertLogRevnos(['dir2', 'file5'], ['5', '3'])
1000
class MainlineGhostTests(TestLogWithLogCatcher):
1003
super(MainlineGhostTests, self).setUp()
1004
tree = self.make_branch_and_tree('')
1005
tree.set_parent_ids(["spooky"], allow_leftmost_as_ghost=True)
1007
tree.commit('msg1', rev_id='rev1')
1008
tree.commit('msg2', rev_id='rev2')
1010
def test_log_range(self):
1011
self.assertLogRevnos(["-r1..2"], ["2", "1"])
1013
def test_log_norange(self):
1014
self.assertLogRevnos([], ["2", "1"])
1016
def test_log_range_open_begin(self):
1017
self.knownFailure("log with ghosts fails. bug #726466")
1018
(stdout, stderr) = self.run_bzr(['log', '-r..2'], retcode=3)
1019
self.assertEqual(["2", "1"],
1020
[r.revno for r in self.get_captured_revisions()])
1021
self.assertEqual("bzr: ERROR: Further revision history missing.", stderr)
1023
def test_log_range_open_end(self):
1024
self.assertLogRevnos(["-r1.."], ["2", "1"])
1026
class TestLogMatch(TestLogWithLogCatcher):
1027
def prepare_tree(self):
1028
tree = self.make_branch_and_tree('')
1030
['/hello.txt', '/goodbye.txt'])
1031
tree.add('hello.txt')
1032
tree.commit(message='message1', committer='committer1', authors=['author1'])
1033
tree.add('goodbye.txt')
1034
tree.commit(message='message2', committer='committer2', authors=['author2'])
1036
def test_message(self):
1038
self.assertLogRevnos(["-m", "message1"], ["1"])
1039
self.assertLogRevnos(["-m", "message2"], ["2"])
1040
self.assertLogRevnos(["-m", "message"], ["2", "1"])
1041
self.assertLogRevnos(["-m", "message1", "-m", "message2"], ["2", "1"])
1042
self.assertLogRevnos(["--match-message", "message1"], ["1"])
1043
self.assertLogRevnos(["--match-message", "message2"], ["2"])
1044
self.assertLogRevnos(["--match-message", "message"], ["2", "1"])
1045
self.assertLogRevnos(["--match-message", "message1",
1046
"--match-message", "message2"], ["2", "1"])
1047
self.assertLogRevnos(["--message", "message1"], ["1"])
1048
self.assertLogRevnos(["--message", "message2"], ["2"])
1049
self.assertLogRevnos(["--message", "message"], ["2", "1"])
1050
self.assertLogRevnos(["--match-message", "message1",
1051
"--message", "message2"], ["2", "1"])
1052
self.assertLogRevnos(["--message", "message1",
1053
"--match-message", "message2"], ["2", "1"])
1055
def test_committer(self):
1057
self.assertLogRevnos(["-m", "committer1"], ["1"])
1058
self.assertLogRevnos(["-m", "committer2"], ["2"])
1059
self.assertLogRevnos(["-m", "committer"], ["2", "1"])
1060
self.assertLogRevnos(["-m", "committer1", "-m", "committer2"],
1062
self.assertLogRevnos(["--match-committer", "committer1"], ["1"])
1063
self.assertLogRevnos(["--match-committer", "committer2"], ["2"])
1064
self.assertLogRevnos(["--match-committer", "committer"], ["2", "1"])
1065
self.assertLogRevnos(["--match-committer", "committer1",
1066
"--match-committer", "committer2"], ["2", "1"])
1068
def test_author(self):
1070
self.assertLogRevnos(["-m", "author1"], ["1"])
1071
self.assertLogRevnos(["-m", "author2"], ["2"])
1072
self.assertLogRevnos(["-m", "author"], ["2", "1"])
1073
self.assertLogRevnos(["-m", "author1", "-m", "author2"],
1075
self.assertLogRevnos(["--match-author", "author1"], ["1"])
1076
self.assertLogRevnos(["--match-author", "author2"], ["2"])
1077
self.assertLogRevnos(["--match-author", "author"], ["2", "1"])
1078
self.assertLogRevnos(["--match-author", "author1",
1079
"--match-author", "author2"], ["2", "1"])
1082
class TestSmartServerLog(tests.TestCaseWithTransport):
1084
def test_standard_log(self):
1085
self.setup_smart_server_with_call_log()
1086
t = self.make_branch_and_tree('branch')
1087
self.build_tree_contents([('branch/foo', 'thecontents')])
1090
self.reset_smart_call_log()
1091
out, err = self.run_bzr(['log', self.get_url('branch')])
1092
# This figure represent the amount of work to perform this use case. It
1093
# is entirely ok to reduce this number if a test fails due to rpc_count
1094
# being too low. If rpc_count increases, more network roundtrips have
1095
# become necessary for this use case. Please do not adjust this number
1096
# upwards without agreement from bzr's network support maintainers.
1097
self.assertThat(self.hpss_calls, ContainsNoVfsCalls)
1098
self.assertLength(1, self.hpss_connections)
1099
self.assertLength(9, self.hpss_calls)
1101
def test_verbose_log(self):
1102
self.setup_smart_server_with_call_log()
1103
t = self.make_branch_and_tree('branch')
1104
self.build_tree_contents([('branch/foo', 'thecontents')])
1107
self.reset_smart_call_log()
1108
out, err = self.run_bzr(['log', '-v', self.get_url('branch')])
1109
# This figure represent the amount of work to perform this use case. It
1110
# is entirely ok to reduce this number if a test fails due to rpc_count
1111
# being too low. If rpc_count increases, more network roundtrips have
1112
# become necessary for this use case. Please do not adjust this number
1113
# upwards without agreement from bzr's network support maintainers.
1114
self.assertLength(10, self.hpss_calls)
1115
self.assertLength(1, self.hpss_connections)
1116
self.assertThat(self.hpss_calls, ContainsNoVfsCalls)
1118
def test_per_file(self):
1119
self.setup_smart_server_with_call_log()
1120
t = self.make_branch_and_tree('branch')
1121
self.build_tree_contents([('branch/foo', 'thecontents')])
1124
self.reset_smart_call_log()
1125
out, err = self.run_bzr(['log', '-v', self.get_url('branch') + "/foo"])
1126
# This figure represent the amount of work to perform this use case. It
1127
# is entirely ok to reduce this number if a test fails due to rpc_count
1128
# being too low. If rpc_count increases, more network roundtrips have
1129
# become necessary for this use case. Please do not adjust this number
1130
# upwards without agreement from bzr's network support maintainers.
1131
self.assertLength(14, self.hpss_calls)
1132
self.assertLength(1, self.hpss_connections)
1133
self.assertThat(self.hpss_calls, ContainsNoVfsCalls)
256
self.assertEquals(-1, stdout.find(test_in_cp1251))