1
# Copyright (C) 2005, 2006, 2007, 2009 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# 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 bzr log."""
27
from bzrlib.tests import test_log
30
class TestLog(tests.TestCaseWithTransport):
33
super(TestLog, self).setUp()
34
self.timezone = 0 # UTC
35
self.timestamp = 1132617600 # Mon 2005-11-22 00:00:00 +0000
37
def make_minimal_branch(self, path='.', format=None):
38
tree = self.make_branch_and_tree(path, format=format)
39
self.build_tree([path + '/hello.txt'])
41
tree.commit(message='message1')
44
def make_linear_branch(self, path='.', format=None):
45
tree = self.make_branch_and_tree(path, format=format)
47
[path + '/hello.txt', path + '/goodbye.txt', path + '/meep.txt'])
49
tree.commit(message='message1')
50
tree.add('goodbye.txt')
51
tree.commit(message='message2')
53
tree.commit(message='message3')
56
def make_merged_branch(self, path='.', format=None):
57
tree = self.make_linear_branch(path, format)
58
tree2 = tree.bzrdir.sprout('tree2',
59
revision_id=tree.branch.get_rev_id(1)).open_workingtree()
60
tree2.commit(message='tree2 message2')
61
tree2.commit(message='tree2 message3')
62
tree.merge_from_branch(tree2.branch)
63
tree.commit(message='merge')
66
def assertRevnos(self, log, must_have=(), must_not_have=()):
67
"""Check if revnos are in or not in the log output"""
68
for revno in must_have:
69
self.assertTrue(('revno: %s\n' % revno) in log,
70
'Does not contain expected revno %s' % revno)
71
for revno in must_not_have:
72
self.assertFalse(('revno: %s\n' % revno) in log,
73
'Contains unexpected revno %s' % revno)
75
def commit_options(self):
76
"""Use some mostly fixed values for commits to simplify tests.
78
Tests can use this function to get some commit attributes. The time
79
stamp is incremented at each commit.
81
self.timestamp += 1 # 1 second between each commit
82
return dict(committer='Lorem Ipsum <joe@foo.com>',
83
timezone=self.timezone,
84
timestamp=self.timestamp,
87
def check_log(self, expected, args, working_dir='level0'):
88
out, err = self.run_bzr(['log', '--timezone', 'utc'] + args,
89
working_dir=working_dir)
90
self.assertEqual('', err)
91
self.assertEqualDiff(expected, test_log.normalize_log(out))
94
class TestLogRevSpecs(TestLog):
96
def test_log_null_end_revspec(self):
97
self.make_linear_branch()
98
log = self.run_bzr(['log'])[0]
99
self.assertTrue('revno: 1\n' in log)
100
self.assertTrue('revno: 2\n' in log)
101
self.assertTrue('revno: 3\n' in log)
102
self.assertTrue('message:\n message1\n' in log)
103
self.assertTrue('message:\n message2\n' in log)
104
self.assertTrue('message:\n message3\n' in log)
106
full_log = self.run_bzr(['log'])[0]
107
log = self.run_bzr("log -r 1..")[0]
108
self.assertEqualDiff(log, full_log)
110
def test_log_null_begin_revspec(self):
111
self.make_linear_branch()
112
full_log = self.run_bzr(['log'])[0]
113
log = self.run_bzr("log -r ..3")[0]
114
self.assertEqualDiff(full_log, log)
116
def test_log_null_both_revspecs(self):
117
self.make_linear_branch()
118
full_log = self.run_bzr(['log'])[0]
119
log = self.run_bzr("log -r ..")[0]
120
self.assertEqualDiff(full_log, log)
122
def test_log_zero_revspec(self):
123
self.make_minimal_branch()
124
self.run_bzr_error(['bzr: ERROR: Logging revision 0 is invalid.'],
127
def test_log_zero_begin_revspec(self):
128
self.make_linear_branch()
129
self.run_bzr_error(['bzr: ERROR: Logging revision 0 is invalid.'],
132
def test_log_zero_end_revspec(self):
133
self.make_linear_branch()
134
self.run_bzr_error(['bzr: ERROR: Logging revision 0 is invalid.'],
137
def test_log_negative_begin_revspec_full_log(self):
138
self.make_linear_branch()
139
full_log = self.run_bzr(['log'])[0]
140
log = self.run_bzr("log -r -3..")[0]
141
self.assertEqualDiff(full_log, log)
143
def test_log_negative_both_revspec_full_log(self):
144
self.make_linear_branch()
145
full_log = self.run_bzr(['log'])[0]
146
log = self.run_bzr("log -r -3..-1")[0]
147
self.assertEqualDiff(full_log, log)
149
def test_log_negative_both_revspec_partial(self):
150
self.make_linear_branch()
151
log = self.run_bzr("log -r -3..-2")[0]
152
self.assertTrue('revno: 1\n' in log)
153
self.assertTrue('revno: 2\n' in log)
154
self.assertTrue('revno: 3\n' not in log)
156
def test_log_negative_begin_revspec(self):
157
self.make_linear_branch()
158
log = self.run_bzr("log -r -2..")[0]
159
self.assertTrue('revno: 1\n' not in log)
160
self.assertTrue('revno: 2\n' in log)
161
self.assertTrue('revno: 3\n' in log)
163
def test_log_positive_revspecs(self):
164
self.make_linear_branch()
165
full_log = self.run_bzr(['log'])[0]
166
log = self.run_bzr("log -r 1..3")[0]
167
self.assertEqualDiff(full_log, log)
169
def test_log_dotted_revspecs(self):
170
self.make_merged_branch()
171
log = self.run_bzr("log -n0 -r 1..1.1.1")[0]
172
self.assertRevnos(log, (1, '1.1.1'), (2, 3, '1.1.2', 4))
174
def test_log_reversed_revspecs(self):
175
self.make_linear_branch()
176
self.run_bzr_error(('bzr: ERROR: Start revision must be older than '
177
'the end revision.\n',),
180
def test_log_reversed_dotted_revspecs(self):
181
self.make_merged_branch()
182
self.run_bzr_error(('bzr: ERROR: Start revision not found in '
183
'left-hand history of end revision.\n',),
186
def test_log_revno_n_path(self):
187
self.make_linear_branch('branch1')
188
self.make_linear_branch('branch2')
190
self.run_bzr("log -r revno:2:branch1..revno:3:branch2", retcode=3)[0]
192
log = self.run_bzr("log -r revno:1:branch2..revno:3:branch2")[0]
193
full_log = self.run_bzr(['log'], working_dir='branch2')[0]
194
self.assertEqualDiff(full_log, log)
195
log = self.run_bzr("log -r revno:1:branch2")[0]
196
self.assertTrue('revno: 1\n' in log)
197
self.assertTrue('revno: 2\n' not in log)
198
self.assertTrue('branch nick: branch2\n' in log)
199
self.assertTrue('branch nick: branch1\n' not in log)
201
def test_log_nonexistent_revno(self):
202
self.make_minimal_branch()
203
(out, err) = self.run_bzr_error(
204
["bzr: ERROR: Requested revision: '1234' "
205
"does not exist in branch:"],
208
def test_log_nonexistent_dotted_revno(self):
209
self.make_minimal_branch()
210
(out, err) = self.run_bzr_error(
211
["bzr: ERROR: Requested revision: '123.123' "
212
"does not exist in branch:"],
213
['log', '-r123.123'])
215
def test_log_change_revno(self):
216
self.make_linear_branch()
217
expected_log = self.run_bzr("log -r 1")[0]
218
log = self.run_bzr("log -c 1")[0]
219
self.assertEqualDiff(expected_log, log)
221
def test_log_change_nonexistent_revno(self):
222
self.make_minimal_branch()
223
(out, err) = self.run_bzr_error(
224
["bzr: ERROR: Requested revision: '1234' "
225
"does not exist in branch:"],
228
def test_log_change_nonexistent_dotted_revno(self):
229
self.make_minimal_branch()
230
(out, err) = self.run_bzr_error(
231
["bzr: ERROR: Requested revision: '123.123' "
232
"does not exist in branch:"],
233
['log', '-c123.123'])
235
def test_log_change_single_revno_only(self):
236
self.make_minimal_branch()
237
self.run_bzr_error(['bzr: ERROR: Option --change does not'
238
' accept revision ranges'],
239
['log', '--change', '2..3'])
241
def test_log_change_incompatible_with_revision(self):
242
self.run_bzr_error(['bzr: ERROR: --revision and --change'
243
' are mutually exclusive'],
244
['log', '--change', '2', '--revision', '3'])
246
def test_log_nonexistent_file(self):
247
self.make_minimal_branch()
248
# files that don't exist in either the basis tree or working tree
249
# should give an error
250
out, err = self.run_bzr('log does-not-exist', retcode=3)
251
self.assertContainsRe(err,
252
'Path unknown at end or start of revision range: '
255
def test_log_with_tags(self):
256
tree = self.make_linear_branch(format='dirstate-tags')
258
branch.tags.set_tag('tag1', branch.get_rev_id(1))
259
branch.tags.set_tag('tag1.1', branch.get_rev_id(1))
260
branch.tags.set_tag('tag3', branch.last_revision())
262
log = self.run_bzr("log -r-1")[0]
263
self.assertTrue('tags: tag3' in log)
265
log = self.run_bzr("log -r1")[0]
266
# I guess that we can't know the order of tags in the output
267
# since dicts are unordered, need to check both possibilities
268
self.assertContainsRe(log, r'tags: (tag1, tag1\.1|tag1\.1, tag1)')
270
def test_merged_log_with_tags(self):
271
branch1_tree = self.make_linear_branch('branch1',
272
format='dirstate-tags')
273
branch1 = branch1_tree.branch
274
branch2_tree = branch1_tree.bzrdir.sprout('branch2').open_workingtree()
275
branch1_tree.commit(message='foobar', allow_pointless=True)
276
branch1.tags.set_tag('tag1', branch1.last_revision())
277
# tags don't propagate if we don't merge
278
self.run_bzr('merge ../branch1', working_dir='branch2')
279
branch2_tree.commit(message='merge branch 1')
280
log = self.run_bzr("log -n0 -r-1", working_dir='branch2')[0]
281
self.assertContainsRe(log, r' tags: tag1')
282
log = self.run_bzr("log -n0 -r3.1.1", working_dir='branch2')[0]
283
self.assertContainsRe(log, r'tags: tag1')
285
def test_log_limit(self):
286
tree = self.make_branch_and_tree('.')
287
# We want more commits than our batch size starts at
288
for pos in range(10):
289
tree.commit("%s" % pos)
290
log = self.run_bzr("log --limit 2")[0]
291
self.assertNotContainsRe(log, r'revno: 1\n')
292
self.assertNotContainsRe(log, r'revno: 2\n')
293
self.assertNotContainsRe(log, r'revno: 3\n')
294
self.assertNotContainsRe(log, r'revno: 4\n')
295
self.assertNotContainsRe(log, r'revno: 5\n')
296
self.assertNotContainsRe(log, r'revno: 6\n')
297
self.assertNotContainsRe(log, r'revno: 7\n')
298
self.assertNotContainsRe(log, r'revno: 8\n')
299
self.assertContainsRe(log, r'revno: 9\n')
300
self.assertContainsRe(log, r'revno: 10\n')
302
def test_log_limit_short(self):
303
self.make_linear_branch()
304
log = self.run_bzr("log -l 2")[0]
305
self.assertNotContainsRe(log, r'revno: 1\n')
306
self.assertContainsRe(log, r'revno: 2\n')
307
self.assertContainsRe(log, r'revno: 3\n')
309
def test_log_bad_message_re(self):
310
"""Bad --message argument gives a sensible message
312
See https://bugs.launchpad.net/bzr/+bug/251352
314
self.make_minimal_branch()
315
out, err = self.run_bzr(['log', '-m', '*'], retcode=3)
316
self.assertEqual("bzr: ERROR: Invalid regular expression"
317
" in log message filter"
319
": nothing to repeat\n", err)
320
self.assertEqual('', out)
323
class TestLogTimeZone(TestLog):
325
def test_log_unsupported_timezone(self):
326
self.make_linear_branch()
327
self.run_bzr_error(['bzr: ERROR: Unsupported timezone format "foo", '
328
'options are "utc", "original", "local".'],
329
['log', '--timezone', 'foo'])
332
class TestLogVerbose(TestLog):
335
super(TestLogVerbose, self).setUp()
336
self.make_minimal_branch()
338
def assertUseShortDeltaFormat(self, cmd):
339
log = self.run_bzr(cmd)[0]
340
# Check that we use the short status format
341
self.assertContainsRe(log, '(?m)^\s*A hello.txt$')
342
self.assertNotContainsRe(log, '(?m)^\s*added:$')
344
def assertUseLongDeltaFormat(self, cmd):
345
log = self.run_bzr(cmd)[0]
346
# Check that we use the long status format
347
self.assertNotContainsRe(log, '(?m)^\s*A hello.txt$')
348
self.assertContainsRe(log, '(?m)^\s*added:$')
350
def test_log_short_verbose(self):
351
self.assertUseShortDeltaFormat(['log', '--short', '-v'])
353
def test_log_short_verbose_verbose(self):
354
self.assertUseLongDeltaFormat(['log', '--short', '-vv'])
356
def test_log_long_verbose(self):
357
# Check that we use the long status format, ignoring the verbosity
359
self.assertUseLongDeltaFormat(['log', '--long', '-v'])
361
def test_log_long_verbose_verbose(self):
362
# Check that we use the long status format, ignoring the verbosity
364
self.assertUseLongDeltaFormat(['log', '--long', '-vv'])
367
class TestLogMerges(TestLog):
370
super(TestLogMerges, self).setUp()
371
self.make_branches_with_merges()
373
def make_branches_with_merges(self):
374
level0 = self.make_branch_and_tree('level0')
375
level0.commit(message='in branch level0', **self.commit_options())
377
level1 = level0.bzrdir.sprout('level1').open_workingtree()
378
level1.commit(message='in branch level1', **self.commit_options())
380
level2 = level1.bzrdir.sprout('level2').open_workingtree()
381
level2.commit(message='in branch level2', **self.commit_options())
383
level1.merge_from_branch(level2.branch)
384
level1.commit(message='merge branch level2', **self.commit_options())
386
level0.merge_from_branch(level1.branch)
387
level0.commit(message='merge branch level1', **self.commit_options())
389
def test_merges_are_indented_by_level(self):
391
------------------------------------------------------------
393
committer: Lorem Ipsum <test@example.com>
398
------------------------------------------------------------
400
committer: Lorem Ipsum <test@example.com>
405
------------------------------------------------------------
407
committer: Lorem Ipsum <test@example.com>
412
------------------------------------------------------------
414
committer: Lorem Ipsum <test@example.com>
419
------------------------------------------------------------
421
committer: Lorem Ipsum <test@example.com>
427
self.check_log(expected, ['-n0'])
429
def test_force_merge_revisions_off(self):
431
------------------------------------------------------------
433
committer: Lorem Ipsum <test@example.com>
438
------------------------------------------------------------
440
committer: Lorem Ipsum <test@example.com>
446
self.check_log(expected, ['--long', '-n1'])
448
def test_force_merge_revisions_on(self):
450
2 Lorem Ipsum\t2005-11-22 [merge]
453
1.1.2 Lorem Ipsum\t2005-11-22 [merge]
456
1.2.1 Lorem Ipsum\t2005-11-22
459
1.1.1 Lorem Ipsum\t2005-11-22
462
1 Lorem Ipsum\t2005-11-22
466
self.check_log(expected, ['--short', '-n0'])
468
def test_include_merges(self):
469
# Confirm --include-merges gives the same output as -n0
470
out_im, err_im = self.run_bzr('log --include-merges',
471
working_dir='level0')
472
out_n0, err_n0 = self.run_bzr('log -n0', working_dir='level0')
473
self.assertEqual('', err_im)
474
self.assertEqual('', err_n0)
475
self.assertEqual(out_im, out_n0)
477
def test_force_merge_revisions_N(self):
479
2 Lorem Ipsum\t2005-11-22 [merge]
482
1.1.2 Lorem Ipsum\t2005-11-22 [merge]
485
1.1.1 Lorem Ipsum\t2005-11-22
488
1 Lorem Ipsum\t2005-11-22
492
self.check_log(expected, ['--short', '-n2'])
494
def test_merges_single_merge_rev(self):
496
------------------------------------------------------------
498
committer: Lorem Ipsum <test@example.com>
503
------------------------------------------------------------
505
committer: Lorem Ipsum <test@example.com>
511
self.check_log(expected, ['-n0', '-r1.1.2'])
513
def test_merges_partial_range(self):
515
------------------------------------------------------------
517
committer: Lorem Ipsum <test@example.com>
522
------------------------------------------------------------
524
committer: Lorem Ipsum <test@example.com>
529
------------------------------------------------------------
531
committer: Lorem Ipsum <test@example.com>
537
self.check_log(expected, ['-n0', '-r1.1.1..1.1.2'])
539
def test_merges_partial_range_ignore_before_lower_bound(self):
540
"""Dont show revisions before the lower bound's merged revs"""
542
2 Lorem Ipsum\t2005-11-22 [merge]
545
1.1.2 Lorem Ipsum\t2005-11-22 [merge]
548
1.2.1 Lorem Ipsum\t2005-11-22
552
self.check_log(expected, ['--short', '-n0', '-r1.1.2..2'])
555
class TestLogDiff(TestLog):
558
super(TestLogDiff, self).setUp()
559
self.make_branch_with_diffs()
561
def make_branch_with_diffs(self):
562
level0 = self.make_branch_and_tree('level0')
563
self.build_tree(['level0/file1', 'level0/file2'])
566
level0.commit(message='in branch level0', **self.commit_options())
568
level1 = level0.bzrdir.sprout('level1').open_workingtree()
569
self.build_tree_contents([('level1/file2', 'hello\n')])
570
level1.commit(message='in branch level1', **self.commit_options())
571
level0.merge_from_branch(level1.branch)
572
level0.commit(message='merge branch level1', **self.commit_options())
574
def test_log_show_diff_long_with_merges(self):
575
out,err = self.run_bzr('log -p -n0')
576
self.assertEqual('', err)
577
log = test_log.normalize_log(out)
579
------------------------------------------------------------
581
committer: Lorem Ipsum <test@example.com>
587
=== modified file 'file2'
588
--- file2\t2005-11-22 00:00:01 +0000
589
+++ file2\t2005-11-22 00:00:02 +0000
591
-contents of level0/file2
593
------------------------------------------------------------
595
committer: Lorem Ipsum <test@example.com>
601
=== modified file 'file2'
602
--- file2\t2005-11-22 00:00:01 +0000
603
+++ file2\t2005-11-22 00:00:02 +0000
605
-contents of level0/file2
607
------------------------------------------------------------
609
committer: Lorem Ipsum <test@example.com>
615
=== added file 'file1'
616
--- file1\t1970-01-01 00:00:00 +0000
617
+++ file1\t2005-11-22 00:00:01 +0000
619
+contents of level0/file1
621
=== added file 'file2'
622
--- file2\t1970-01-01 00:00:00 +0000
623
+++ file2\t2005-11-22 00:00:01 +0000
625
+contents of level0/file2
627
self.check_log(expected, ['-p', '-n0'])
629
def test_log_show_diff_short(self):
631
2 Lorem Ipsum\t2005-11-22 [merge]
633
=== modified file 'file2'
634
--- file2\t2005-11-22 00:00:01 +0000
635
+++ file2\t2005-11-22 00:00:02 +0000
637
-contents of level0/file2
640
1 Lorem Ipsum\t2005-11-22
642
=== added file 'file1'
643
--- file1\t1970-01-01 00:00:00 +0000
644
+++ file1\t2005-11-22 00:00:01 +0000
646
+contents of level0/file1
647
\x20\x20\x20\x20\x20\x20
648
=== added file 'file2'
649
--- file2\t1970-01-01 00:00:00 +0000
650
+++ file2\t2005-11-22 00:00:01 +0000
652
+contents of level0/file2
654
Use --include-merges or -n0 to see merged revisions.
656
self.check_log(expected, ['-p', '--short'])
658
def test_log_show_diff_line(self):
659
# Not supported by this formatter so expect plain output
661
2: Lorem Ipsum 2005-11-22 [merge] merge branch level1
662
1: Lorem Ipsum 2005-11-22 in branch level0
664
self.check_log(expected, ['-p', '--line'])
666
def test_log_show_diff_file1(self):
667
"""Only the diffs for the given file are to be shown"""
669
1 Lorem Ipsum\t2005-11-22
671
=== added file 'file1'
672
--- file1\t1970-01-01 00:00:00 +0000
673
+++ file1\t2005-11-22 00:00:01 +0000
675
+contents of level0/file1
678
self.check_log(expected, ['-p', '--short', 'file1'])
680
def test_log_show_diff_file2(self):
681
"""Only the diffs for the given file are to be shown"""
683
2 Lorem Ipsum\t2005-11-22 [merge]
685
=== modified file 'file2'
686
--- file2\t2005-11-22 00:00:01 +0000
687
+++ file2\t2005-11-22 00:00:02 +0000
689
-contents of level0/file2
692
1 Lorem Ipsum\t2005-11-22
694
=== added file 'file2'
695
--- file2\t1970-01-01 00:00:00 +0000
696
+++ file2\t2005-11-22 00:00:01 +0000
698
+contents of level0/file2
700
Use --include-merges or -n0 to see merged revisions.
702
self.check_log(expected, ['-p', '--short', 'file2'])
705
class TestLogUnicodeDiff(TestLog):
707
def test_log_show_diff_non_ascii(self):
708
# Smoke test for bug #328007 UnicodeDecodeError on 'log -p'
709
message = u'Message with \xb5'
710
body = 'Body with \xb5\n'
711
wt = self.make_branch_and_tree('.')
712
self.build_tree_contents([('foo', body)])
714
wt.commit(message=message)
715
# check that command won't fail with unicode error
716
# don't care about exact output because we have other tests for this
717
out,err = self.run_bzr('log -p --long')
718
self.assertNotEqual('', out)
719
self.assertEqual('', err)
720
out,err = self.run_bzr('log -p --short')
721
self.assertNotEqual('', out)
722
self.assertEqual('', err)
723
out,err = self.run_bzr('log -p --line')
724
self.assertNotEqual('', out)
725
self.assertEqual('', err)
728
class TestLogEncodings(tests.TestCaseInTempDir):
731
_message = u'Message with \xb5'
733
# Encodings which can encode mu
738
'cp437', # Common windows encoding
739
'cp1251', # Russian windows encoding
740
'cp1258', # Common windows encoding
742
# Encodings which cannot encode mu
750
super(TestLogEncodings, self).setUp()
751
self.user_encoding = osutils._cached_user_encoding
753
osutils._cached_user_encoding = self.user_encoding
754
self.addCleanup(restore)
756
def create_branch(self):
759
open('a', 'wb').write('some stuff\n')
761
bzr(['commit', '-m', self._message])
763
def try_encoding(self, encoding, fail=False):
766
self.assertRaises(UnicodeEncodeError,
767
self._mu.encode, encoding)
768
encoded_msg = self._message.encode(encoding, 'replace')
770
encoded_msg = self._message.encode(encoding)
772
old_encoding = osutils._cached_user_encoding
773
# This test requires that 'run_bzr' uses the current
774
# bzrlib, because we override user_encoding, and expect
777
osutils._cached_user_encoding = 'ascii'
778
# We should be able to handle any encoding
779
out, err = bzr('log', encoding=encoding)
781
# Make sure we wrote mu as we expected it to exist
782
self.assertNotEqual(-1, out.find(encoded_msg))
783
out_unicode = out.decode(encoding)
784
self.assertNotEqual(-1, out_unicode.find(self._message))
786
self.assertNotEqual(-1, out.find('Message with ?'))
788
osutils._cached_user_encoding = old_encoding
790
def test_log_handles_encoding(self):
793
for encoding in self.good_encodings:
794
self.try_encoding(encoding)
796
def test_log_handles_bad_encoding(self):
799
for encoding in self.bad_encodings:
800
self.try_encoding(encoding, fail=True)
802
def test_stdout_encoding(self):
804
osutils._cached_user_encoding = "cp1251"
807
self.build_tree(['a'])
809
bzr(['commit', '-m', u'\u0422\u0435\u0441\u0442'])
810
stdout, stderr = self.run_bzr('log', encoding='cp866')
812
message = stdout.splitlines()[-1]
814
# explanation of the check:
815
# u'\u0422\u0435\u0441\u0442' is word 'Test' in russian
816
# in cp866 encoding this is string '\x92\xa5\xe1\xe2'
817
# in cp1251 encoding this is string '\xd2\xe5\xf1\xf2'
818
# This test should check that output of log command
819
# encoded to sys.stdout.encoding
820
test_in_cp866 = '\x92\xa5\xe1\xe2'
821
test_in_cp1251 = '\xd2\xe5\xf1\xf2'
822
# Make sure the log string is encoded in cp866
823
self.assertEquals(test_in_cp866, message[2:])
824
# Make sure the cp1251 string is not found anywhere
825
self.assertEquals(-1, stdout.find(test_in_cp1251))
828
class TestLogFile(tests.TestCaseWithTransport):
830
def test_log_local_branch_file(self):
831
"""We should be able to log files in local treeless branches"""
832
tree = self.make_branch_and_tree('tree')
833
self.build_tree(['tree/file'])
835
tree.commit('revision 1')
836
tree.bzrdir.destroy_workingtree()
837
self.run_bzr('log tree/file')
839
def prepare_tree(self, complex=False):
840
# The complex configuration includes deletes and renames
841
tree = self.make_branch_and_tree('parent')
842
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
844
tree.commit('add file1')
846
tree.commit('add file2')
848
tree.commit('add file3')
849
child_tree = tree.bzrdir.sprout('child').open_workingtree()
850
self.build_tree_contents([('child/file2', 'hello')])
851
child_tree.commit(message='branch 1')
852
tree.merge_from_branch(child_tree.branch)
853
tree.commit(message='merge child branch')
856
tree.commit('remove file2')
857
tree.rename_one('file3', 'file4')
858
tree.commit('file3 is now called file4')
860
tree.commit('remove file1')
863
def test_log_file(self):
864
"""The log for a particular file should only list revs for that file"""
866
log = self.run_bzr('log -n0 file1')[0]
867
self.assertContainsRe(log, 'revno: 1\n')
868
self.assertNotContainsRe(log, 'revno: 2\n')
869
self.assertNotContainsRe(log, 'revno: 3\n')
870
self.assertNotContainsRe(log, 'revno: 3.1.1\n')
871
self.assertNotContainsRe(log, 'revno: 4 ')
872
log = self.run_bzr('log -n0 file2')[0]
873
self.assertNotContainsRe(log, 'revno: 1\n')
874
self.assertContainsRe(log, 'revno: 2\n')
875
self.assertNotContainsRe(log, 'revno: 3\n')
876
self.assertContainsRe(log, 'revno: 3.1.1\n')
877
self.assertContainsRe(log, 'revno: 4 ')
878
log = self.run_bzr('log -n0 file3')[0]
879
self.assertNotContainsRe(log, 'revno: 1\n')
880
self.assertNotContainsRe(log, 'revno: 2\n')
881
self.assertContainsRe(log, 'revno: 3\n')
882
self.assertNotContainsRe(log, 'revno: 3.1.1\n')
883
self.assertNotContainsRe(log, 'revno: 4 ')
884
log = self.run_bzr('log -n0 -r3.1.1 file2')[0]
885
self.assertNotContainsRe(log, 'revno: 1\n')
886
self.assertNotContainsRe(log, 'revno: 2\n')
887
self.assertNotContainsRe(log, 'revno: 3\n')
888
self.assertContainsRe(log, 'revno: 3.1.1\n')
889
self.assertNotContainsRe(log, 'revno: 4 ')
890
log = self.run_bzr('log -n0 -r4 file2')[0]
891
self.assertNotContainsRe(log, 'revno: 1\n')
892
self.assertNotContainsRe(log, 'revno: 2\n')
893
self.assertNotContainsRe(log, 'revno: 3\n')
894
self.assertContainsRe(log, 'revno: 3.1.1\n')
895
self.assertContainsRe(log, 'revno: 4 ')
896
log = self.run_bzr('log -n0 -r3.. file2')[0]
897
self.assertNotContainsRe(log, 'revno: 1\n')
898
self.assertNotContainsRe(log, 'revno: 2\n')
899
self.assertNotContainsRe(log, 'revno: 3\n')
900
self.assertContainsRe(log, 'revno: 3.1.1\n')
901
self.assertContainsRe(log, 'revno: 4 ')
902
log = self.run_bzr('log -n0 -r..3 file2')[0]
903
self.assertNotContainsRe(log, 'revno: 1\n')
904
self.assertContainsRe(log, 'revno: 2\n')
905
self.assertNotContainsRe(log, 'revno: 3\n')
906
self.assertNotContainsRe(log, 'revno: 3.1.1\n')
907
self.assertNotContainsRe(log, 'revno: 4 ')
909
def test_log_file_historical_missing(self):
910
# Check logging a deleted file gives an error if the
911
# file isn't found at the end or start of the revision range
912
self.prepare_tree(complex=True)
913
err_msg = "Path unknown at end or start of revision range: file2"
914
err = self.run_bzr('log file2', retcode=3)[1]
915
self.assertContainsRe(err, err_msg)
917
def test_log_file_historical_end(self):
918
# Check logging a deleted file is ok if the file existed
919
# at the end the revision range
920
self.prepare_tree(complex=True)
921
log, err = self.run_bzr('log -n0 -r..4 file2')
922
self.assertEquals('', err)
923
self.assertNotContainsRe(log, 'revno: 1\n')
924
self.assertContainsRe(log, 'revno: 2\n')
925
self.assertNotContainsRe(log, 'revno: 3\n')
926
self.assertContainsRe(log, 'revno: 3.1.1\n')
927
self.assertContainsRe(log, 'revno: 4 ')
929
def test_log_file_historical_start(self):
930
# Check logging a deleted file is ok if the file existed
931
# at the start of the revision range
932
self.prepare_tree(complex=True)
933
log, err = self.run_bzr('log file1')
934
self.assertEquals('', err)
935
self.assertContainsRe(log, 'revno: 1\n')
936
self.assertNotContainsRe(log, 'revno: 2\n')
937
self.assertNotContainsRe(log, 'revno: 3\n')
938
self.assertNotContainsRe(log, 'revno: 3.1.1\n')
939
self.assertNotContainsRe(log, 'revno: 4 ')
941
def test_log_file_renamed(self):
942
"""File matched against revision range, not current tree."""
943
self.prepare_tree(complex=True)
945
# Check logging a renamed file gives an error by default
946
err_msg = "Path unknown at end or start of revision range: file3"
947
err = self.run_bzr('log file3', retcode=3)[1]
948
self.assertContainsRe(err, err_msg)
950
# Check we can see a renamed file if we give the right end revision
951
log, err = self.run_bzr('log -r..4 file3')
952
self.assertEquals('', err)
953
self.assertNotContainsRe(log, 'revno: 1\n')
954
self.assertNotContainsRe(log, 'revno: 2\n')
955
self.assertContainsRe(log, 'revno: 3\n')
956
self.assertNotContainsRe(log, 'revno: 3.1.1\n')
957
self.assertNotContainsRe(log, 'revno: 4 ')
959
def test_line_log_file(self):
960
"""The line log for a file should only list relevant mainline revs"""
961
# Note: this also implicitly covers the short logging case.
962
# We test using --line in preference to --short because matching
963
# revnos in the output of --line is more reliable.
966
# full history of file1
967
log = self.run_bzr('log --line file1')[0]
968
self.assertContainsRe(log, '^1:', re.MULTILINE)
969
self.assertNotContainsRe(log, '^2:', re.MULTILINE)
970
self.assertNotContainsRe(log, '^3:', re.MULTILINE)
971
self.assertNotContainsRe(log, '^3.1.1:', re.MULTILINE)
972
self.assertNotContainsRe(log, '^4:', re.MULTILINE)
974
# full history of file2
975
log = self.run_bzr('log --line file2')[0]
976
self.assertNotContainsRe(log, '^1:', re.MULTILINE)
977
self.assertContainsRe(log, '^2:', re.MULTILINE)
978
self.assertNotContainsRe(log, '^3:', re.MULTILINE)
979
self.assertNotContainsRe(log, '^3.1.1:', re.MULTILINE)
980
self.assertContainsRe(log, '^4:', re.MULTILINE)
982
# full history of file3
983
log = self.run_bzr('log --line file3')[0]
984
self.assertNotContainsRe(log, '^1:', re.MULTILINE)
985
self.assertNotContainsRe(log, '^2:', re.MULTILINE)
986
self.assertContainsRe(log, '^3:', re.MULTILINE)
987
self.assertNotContainsRe(log, '^3.1.1:', re.MULTILINE)
988
self.assertNotContainsRe(log, '^4:', re.MULTILINE)
990
# file in a merge revision
991
log = self.run_bzr('log --line -r3.1.1 file2')[0]
992
self.assertNotContainsRe(log, '^1:', re.MULTILINE)
993
self.assertNotContainsRe(log, '^2:', re.MULTILINE)
994
self.assertNotContainsRe(log, '^3:', re.MULTILINE)
995
self.assertContainsRe(log, '^3.1.1:', re.MULTILINE)
996
self.assertNotContainsRe(log, '^4:', re.MULTILINE)
998
# file in a mainline revision
999
log = self.run_bzr('log --line -r4 file2')[0]
1000
self.assertNotContainsRe(log, '^1:', re.MULTILINE)
1001
self.assertNotContainsRe(log, '^2:', re.MULTILINE)
1002
self.assertNotContainsRe(log, '^3:', re.MULTILINE)
1003
self.assertNotContainsRe(log, '^3.1.1:', re.MULTILINE)
1004
self.assertContainsRe(log, '^4:', re.MULTILINE)
1006
# file since a revision
1007
log = self.run_bzr('log --line -r3.. file2')[0]
1008
self.assertNotContainsRe(log, '^1:', re.MULTILINE)
1009
self.assertNotContainsRe(log, '^2:', re.MULTILINE)
1010
self.assertNotContainsRe(log, '^3:', re.MULTILINE)
1011
self.assertNotContainsRe(log, '^3.1.1:', re.MULTILINE)
1012
self.assertContainsRe(log, '^4:', re.MULTILINE)
1014
# file up to a revision
1015
log = self.run_bzr('log --line -r..3 file2')[0]
1016
self.assertNotContainsRe(log, '^1:', re.MULTILINE)
1017
self.assertContainsRe(log, '^2:', re.MULTILINE)
1018
self.assertNotContainsRe(log, '^3:', re.MULTILINE)
1019
self.assertNotContainsRe(log, '^3.1.1:', re.MULTILINE)
1020
self.assertNotContainsRe(log, '^4:', re.MULTILINE)
1023
class TestLogMultiple(tests.TestCaseWithTransport):
1025
def prepare_tree(self):
1026
tree = self.make_branch_and_tree('parent')
1031
'parent/dir1/file5',
1032
'parent/dir1/dir2/',
1033
'parent/dir1/dir2/file3',
1036
tree.commit('add file1')
1038
tree.commit('add file2')
1039
tree.add(['dir1', 'dir1/dir2', 'dir1/dir2/file3'])
1040
tree.commit('add file3')
1042
tree.commit('add file4')
1043
tree.add('dir1/file5')
1044
tree.commit('add file5')
1045
child_tree = tree.bzrdir.sprout('child').open_workingtree()
1046
self.build_tree_contents([('child/file2', 'hello')])
1047
child_tree.commit(message='branch 1')
1048
tree.merge_from_branch(child_tree.branch)
1049
tree.commit(message='merge child branch')
1052
def assertRevnos(self, paths_str, expected_revnos):
1053
# confirm the revision numbers in log --line output are those expected
1054
out, err = self.run_bzr('log --line -n0 %s' % (paths_str,))
1055
self.assertEqual('', err)
1056
revnos = [s.split(':', 1)[0].lstrip() for s in out.splitlines()]
1057
self.assertEqual(expected_revnos, revnos)
1059
def test_log_files(self):
1060
"""The log for multiple file should only list revs for those files"""
1062
self.assertRevnos('file1 file2 dir1/dir2/file3',
1063
['6', '5.1.1', '3', '2', '1'])
1065
def test_log_directory(self):
1066
"""The log for a directory should show all nested files."""
1068
self.assertRevnos('dir1', ['5', '3'])
1070
def test_log_nested_directory(self):
1071
"""The log for a directory should show all nested files."""
1073
self.assertRevnos('dir1/dir2', ['3'])
1075
def test_log_in_nested_directory(self):
1076
"""The log for a directory should show all nested files."""
1079
self.assertRevnos('.', ['5', '3'])
1081
def test_log_files_and_directories(self):
1082
"""Logging files and directories together should be fine."""
1084
self.assertRevnos('file4 dir1/dir2', ['4', '3'])
1086
def test_log_files_and_dirs_in_nested_directory(self):
1087
"""The log for a directory should show all nested files."""
1090
self.assertRevnos('dir2 file5', ['5', '3'])