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 commit_options(self):
57
"""Use some mostly fixed values for commits to simplify tests.
59
Tests can use this function to get some commit attributes. The time
60
stamp is incremented at each commit.
62
self.timestamp += 1 # 1 second between each commit
63
return dict(committer='Lorem Ipsum <joe@foo.com>',
64
timezone=self.timezone,
65
timestamp=self.timestamp,
68
def check_log(self, expected, args, working_dir='level0'):
69
out, err = self.run_bzr(['log', '--timezone', 'utc'] + args,
70
working_dir=working_dir)
71
self.assertEqual('', err)
72
self.assertEqualDiff(expected, test_log.normalize_log(out))
75
class TestLogRevSpecs(TestLog):
77
def test_log_null_end_revspec(self):
78
self.make_linear_branch()
79
log = self.run_bzr(['log'])[0]
80
self.assertTrue('revno: 1\n' in log)
81
self.assertTrue('revno: 2\n' in log)
82
self.assertTrue('revno: 3\n' in log)
83
self.assertTrue('message:\n message1\n' in log)
84
self.assertTrue('message:\n message2\n' in log)
85
self.assertTrue('message:\n message3\n' in log)
87
full_log = self.run_bzr(['log'])[0]
88
log = self.run_bzr("log -r 1..")[0]
89
self.assertEqualDiff(log, full_log)
91
def test_log_null_begin_revspec(self):
92
self.make_linear_branch()
93
full_log = self.run_bzr(['log'])[0]
94
log = self.run_bzr("log -r ..3")[0]
95
self.assertEqualDiff(full_log, log)
97
def test_log_null_both_revspecs(self):
98
self.make_linear_branch()
99
full_log = self.run_bzr(['log'])[0]
100
log = self.run_bzr("log -r ..")[0]
101
self.assertEqualDiff(full_log, log)
103
def test_log_zero_revspec(self):
104
self.make_minimal_branch()
105
self.run_bzr_error(['bzr: ERROR: Logging revision 0 is invalid.'],
108
def test_log_zero_begin_revspec(self):
109
self.make_linear_branch()
110
self.run_bzr_error(['bzr: ERROR: Logging revision 0 is invalid.'],
113
def test_log_zero_end_revspec(self):
114
self.make_linear_branch()
115
self.run_bzr_error(['bzr: ERROR: Logging revision 0 is invalid.'],
118
def test_log_negative_begin_revspec_full_log(self):
119
self.make_linear_branch()
120
full_log = self.run_bzr(['log'])[0]
121
log = self.run_bzr("log -r -3..")[0]
122
self.assertEqualDiff(full_log, log)
124
def test_log_negative_both_revspec_full_log(self):
125
self.make_linear_branch()
126
full_log = self.run_bzr(['log'])[0]
127
log = self.run_bzr("log -r -3..-1")[0]
128
self.assertEqualDiff(full_log, log)
130
def test_log_negative_both_revspec_partial(self):
131
self.make_linear_branch()
132
log = self.run_bzr("log -r -3..-2")[0]
133
self.assertTrue('revno: 1\n' in log)
134
self.assertTrue('revno: 2\n' in log)
135
self.assertTrue('revno: 3\n' not in log)
137
def test_log_negative_begin_revspec(self):
138
self.make_linear_branch()
139
log = self.run_bzr("log -r -2..")[0]
140
self.assertTrue('revno: 1\n' not in log)
141
self.assertTrue('revno: 2\n' in log)
142
self.assertTrue('revno: 3\n' in log)
144
def test_log_positive_revspecs(self):
145
self.make_linear_branch()
146
full_log = self.run_bzr(['log'])[0]
147
log = self.run_bzr("log -r 1..3")[0]
148
self.assertEqualDiff(full_log, log)
150
def test_log_reversed_revspecs(self):
151
self.make_linear_branch()
152
self.run_bzr_error(('bzr: ERROR: Start revision must be older than '
153
'the end revision.\n',),
156
def test_log_revno_n_path(self):
157
self.make_linear_branch('branch1')
158
self.make_linear_branch('branch2')
160
self.run_bzr("log -r revno:2:branch1..revno:3:branch2", retcode=3)[0]
162
log = self.run_bzr("log -r revno:1:branch2..revno:3:branch2")[0]
163
full_log = self.run_bzr(['log'], working_dir='branch2')[0]
164
self.assertEqualDiff(full_log, log)
165
log = self.run_bzr("log -r revno:1:branch2")[0]
166
self.assertTrue('revno: 1\n' in log)
167
self.assertTrue('revno: 2\n' not in log)
168
self.assertTrue('branch nick: branch2\n' in log)
169
self.assertTrue('branch nick: branch1\n' not in log)
171
def test_log_nonexistent_revno(self):
172
self.make_minimal_branch()
173
(out, err) = self.run_bzr_error(
174
["bzr: ERROR: Requested revision: '1234' "
175
"does not exist in branch:"],
178
def test_log_nonexistent_dotted_revno(self):
179
self.make_minimal_branch()
180
(out, err) = self.run_bzr_error(
181
["bzr: ERROR: Requested revision: '123.123' "
182
"does not exist in branch:"],
183
['log', '-r123.123'])
185
def test_log_change_revno(self):
186
self.make_linear_branch()
187
expected_log = self.run_bzr("log -r 1")[0]
188
log = self.run_bzr("log -c 1")[0]
189
self.assertEqualDiff(expected_log, log)
191
def test_log_change_nonexistent_revno(self):
192
self.make_minimal_branch()
193
(out, err) = self.run_bzr_error(
194
["bzr: ERROR: Requested revision: '1234' "
195
"does not exist in branch:"],
198
def test_log_change_nonexistent_dotted_revno(self):
199
self.make_minimal_branch()
200
(out, err) = self.run_bzr_error(
201
["bzr: ERROR: Requested revision: '123.123' "
202
"does not exist in branch:"],
203
['log', '-c123.123'])
205
def test_log_change_single_revno_only(self):
206
self.make_minimal_branch()
207
self.run_bzr_error(['bzr: ERROR: Option --change does not'
208
' accept revision ranges'],
209
['log', '--change', '2..3'])
211
def test_log_change_incompatible_with_revision(self):
212
self.run_bzr_error(['bzr: ERROR: --revision and --change'
213
' are mutually exclusive'],
214
['log', '--change', '2', '--revision', '3'])
216
def test_log_nonexistent_file(self):
217
self.make_minimal_branch()
218
# files that don't exist in either the basis tree or working tree
219
# should give an error
220
out, err = self.run_bzr('log does-not-exist', retcode=3)
221
self.assertContainsRe(err,
222
'Path unknown at end or start of revision range: '
225
def test_log_with_tags(self):
226
tree = self.make_linear_branch(format='dirstate-tags')
228
branch.tags.set_tag('tag1', branch.get_rev_id(1))
229
branch.tags.set_tag('tag1.1', branch.get_rev_id(1))
230
branch.tags.set_tag('tag3', branch.last_revision())
232
log = self.run_bzr("log -r-1")[0]
233
self.assertTrue('tags: tag3' in log)
235
log = self.run_bzr("log -r1")[0]
236
# I guess that we can't know the order of tags in the output
237
# since dicts are unordered, need to check both possibilities
238
self.assertContainsRe(log, r'tags: (tag1, tag1\.1|tag1\.1, tag1)')
240
def test_merged_log_with_tags(self):
241
branch1_tree = self.make_linear_branch('branch1',
242
format='dirstate-tags')
243
branch1 = branch1_tree.branch
244
branch2_tree = branch1_tree.bzrdir.sprout('branch2').open_workingtree()
245
branch1_tree.commit(message='foobar', allow_pointless=True)
246
branch1.tags.set_tag('tag1', branch1.last_revision())
247
# tags don't propagate if we don't merge
248
self.run_bzr('merge ../branch1', working_dir='branch2')
249
branch2_tree.commit(message='merge branch 1')
250
log = self.run_bzr("log -n0 -r-1", working_dir='branch2')[0]
251
self.assertContainsRe(log, r' tags: tag1')
252
log = self.run_bzr("log -n0 -r3.1.1", working_dir='branch2')[0]
253
self.assertContainsRe(log, r'tags: tag1')
255
def test_log_limit(self):
256
tree = self.make_branch_and_tree('.')
257
# We want more commits than our batch size starts at
258
for pos in range(10):
259
tree.commit("%s" % pos)
260
log = self.run_bzr("log --limit 2")[0]
261
self.assertNotContainsRe(log, r'revno: 1\n')
262
self.assertNotContainsRe(log, r'revno: 2\n')
263
self.assertNotContainsRe(log, r'revno: 3\n')
264
self.assertNotContainsRe(log, r'revno: 4\n')
265
self.assertNotContainsRe(log, r'revno: 5\n')
266
self.assertNotContainsRe(log, r'revno: 6\n')
267
self.assertNotContainsRe(log, r'revno: 7\n')
268
self.assertNotContainsRe(log, r'revno: 8\n')
269
self.assertContainsRe(log, r'revno: 9\n')
270
self.assertContainsRe(log, r'revno: 10\n')
272
def test_log_limit_short(self):
273
self.make_linear_branch()
274
log = self.run_bzr("log -l 2")[0]
275
self.assertNotContainsRe(log, r'revno: 1\n')
276
self.assertContainsRe(log, r'revno: 2\n')
277
self.assertContainsRe(log, r'revno: 3\n')
279
def test_log_bad_message_re(self):
280
"""Bad --message argument gives a sensible message
282
See https://bugs.launchpad.net/bzr/+bug/251352
284
self.make_minimal_branch()
285
out, err = self.run_bzr(['log', '-m', '*'], retcode=3)
286
self.assertEqual("bzr: ERROR: Invalid regular expression"
287
" in log message filter"
289
": nothing to repeat\n", err)
290
self.assertEqual('', out)
293
class TestLogTimeZone(TestLog):
295
def test_log_unsupported_timezone(self):
296
self.make_linear_branch()
297
self.run_bzr_error(['bzr: ERROR: Unsupported timezone format "foo", '
298
'options are "utc", "original", "local".'],
299
['log', '--timezone', 'foo'])
302
class TestLogVerbose(TestLog):
305
super(TestLogVerbose, self).setUp()
306
self.make_minimal_branch()
308
def assertUseShortDeltaFormat(self, cmd):
309
log = self.run_bzr(cmd)[0]
310
# Check that we use the short status format
311
self.assertContainsRe(log, '(?m)^\s*A hello.txt$')
312
self.assertNotContainsRe(log, '(?m)^\s*added:$')
314
def assertUseLongDeltaFormat(self, cmd):
315
log = self.run_bzr(cmd)[0]
316
# Check that we use the long status format
317
self.assertNotContainsRe(log, '(?m)^\s*A hello.txt$')
318
self.assertContainsRe(log, '(?m)^\s*added:$')
320
def test_log_short_verbose(self):
321
self.assertUseShortDeltaFormat(['log', '--short', '-v'])
323
def test_log_short_verbose_verbose(self):
324
self.assertUseLongDeltaFormat(['log', '--short', '-vv'])
326
def test_log_long_verbose(self):
327
# Check that we use the long status format, ignoring the verbosity
329
self.assertUseLongDeltaFormat(['log', '--long', '-v'])
331
def test_log_long_verbose_verbose(self):
332
# Check that we use the long status format, ignoring the verbosity
334
self.assertUseLongDeltaFormat(['log', '--long', '-vv'])
337
class TestLogMerges(TestLog):
340
super(TestLogMerges, self).setUp()
341
self.make_branches_with_merges()
343
def make_branches_with_merges(self):
344
level0 = self.make_branch_and_tree('level0')
345
level0.commit(message='in branch level0', **self.commit_options())
347
level1 = level0.bzrdir.sprout('level1').open_workingtree()
348
level1.commit(message='in branch level1', **self.commit_options())
350
level2 = level1.bzrdir.sprout('level2').open_workingtree()
351
level2.commit(message='in branch level2', **self.commit_options())
353
level1.merge_from_branch(level2.branch)
354
level1.commit(message='merge branch level2', **self.commit_options())
356
level0.merge_from_branch(level1.branch)
357
level0.commit(message='merge branch level1', **self.commit_options())
359
def test_merges_are_indented_by_level(self):
361
------------------------------------------------------------
363
committer: Lorem Ipsum <test@example.com>
368
------------------------------------------------------------
370
committer: Lorem Ipsum <test@example.com>
375
------------------------------------------------------------
377
committer: Lorem Ipsum <test@example.com>
382
------------------------------------------------------------
384
committer: Lorem Ipsum <test@example.com>
389
------------------------------------------------------------
391
committer: Lorem Ipsum <test@example.com>
397
self.check_log(expected, ['-n0'])
399
def test_force_merge_revisions_off(self):
401
------------------------------------------------------------
403
committer: Lorem Ipsum <test@example.com>
408
------------------------------------------------------------
410
committer: Lorem Ipsum <test@example.com>
416
self.check_log(expected, ['--long', '-n1'])
418
def test_force_merge_revisions_on(self):
420
2 Lorem Ipsum\t2005-11-22 [merge]
423
1.1.2 Lorem Ipsum\t2005-11-22 [merge]
426
1.2.1 Lorem Ipsum\t2005-11-22
429
1.1.1 Lorem Ipsum\t2005-11-22
432
1 Lorem Ipsum\t2005-11-22
436
self.check_log(expected, ['--short', '-n0'])
438
def test_include_merges(self):
439
# Confirm --include-merges gives the same output as -n0
440
out_im, err_im = self.run_bzr('log --include-merges',
441
working_dir='level0')
442
out_n0, err_n0 = self.run_bzr('log -n0', working_dir='level0')
443
self.assertEqual('', err_im)
444
self.assertEqual('', err_n0)
445
self.assertEqual(out_im, out_n0)
447
def test_force_merge_revisions_N(self):
449
2 Lorem Ipsum\t2005-11-22 [merge]
452
1.1.2 Lorem Ipsum\t2005-11-22 [merge]
455
1.1.1 Lorem Ipsum\t2005-11-22
458
1 Lorem Ipsum\t2005-11-22
462
self.check_log(expected, ['--short', '-n2'])
464
def test_merges_single_merge_rev(self):
466
------------------------------------------------------------
468
committer: Lorem Ipsum <test@example.com>
473
------------------------------------------------------------
475
committer: Lorem Ipsum <test@example.com>
481
self.check_log(expected, ['-n0', '-r1.1.2'])
483
def test_merges_partial_range(self):
485
------------------------------------------------------------
487
committer: Lorem Ipsum <test@example.com>
492
------------------------------------------------------------
494
committer: Lorem Ipsum <test@example.com>
499
------------------------------------------------------------
501
committer: Lorem Ipsum <test@example.com>
507
self.check_log(expected, ['-n0', '-r1.1.1..1.1.2'])
510
class TestLogDiff(TestLog):
513
super(TestLogDiff, self).setUp()
514
self.make_branch_with_diffs()
516
def make_branch_with_diffs(self):
517
level0 = self.make_branch_and_tree('level0')
518
self.build_tree(['level0/file1', 'level0/file2'])
521
level0.commit(message='in branch level0', **self.commit_options())
523
level1 = level0.bzrdir.sprout('level1').open_workingtree()
524
self.build_tree_contents([('level1/file2', 'hello\n')])
525
level1.commit(message='in branch level1', **self.commit_options())
526
level0.merge_from_branch(level1.branch)
527
level0.commit(message='merge branch level1', **self.commit_options())
529
def test_log_show_diff_long_with_merges(self):
530
out,err = self.run_bzr('log -p -n0')
531
self.assertEqual('', err)
532
log = test_log.normalize_log(out)
534
------------------------------------------------------------
536
committer: Lorem Ipsum <test@example.com>
542
=== modified file 'file2'
543
--- file2\t2005-11-22 00:00:01 +0000
544
+++ file2\t2005-11-22 00:00:02 +0000
546
-contents of level0/file2
548
------------------------------------------------------------
550
committer: Lorem Ipsum <test@example.com>
556
=== modified file 'file2'
557
--- file2\t2005-11-22 00:00:01 +0000
558
+++ file2\t2005-11-22 00:00:02 +0000
560
-contents of level0/file2
562
------------------------------------------------------------
564
committer: Lorem Ipsum <test@example.com>
570
=== added file 'file1'
571
--- file1\t1970-01-01 00:00:00 +0000
572
+++ file1\t2005-11-22 00:00:01 +0000
574
+contents of level0/file1
576
=== added file 'file2'
577
--- file2\t1970-01-01 00:00:00 +0000
578
+++ file2\t2005-11-22 00:00:01 +0000
580
+contents of level0/file2
582
self.check_log(expected, ['-p', '-n0'])
584
def test_log_show_diff_short(self):
586
2 Lorem Ipsum\t2005-11-22 [merge]
588
=== modified file 'file2'
589
--- file2\t2005-11-22 00:00:01 +0000
590
+++ file2\t2005-11-22 00:00:02 +0000
592
-contents of level0/file2
595
1 Lorem Ipsum\t2005-11-22
597
=== added file 'file1'
598
--- file1\t1970-01-01 00:00:00 +0000
599
+++ file1\t2005-11-22 00:00:01 +0000
601
+contents of level0/file1
602
\x20\x20\x20\x20\x20\x20
603
=== added file 'file2'
604
--- file2\t1970-01-01 00:00:00 +0000
605
+++ file2\t2005-11-22 00:00:01 +0000
607
+contents of level0/file2
609
Use --include-merges or -n0 to see merged revisions.
611
self.check_log(expected, ['-p', '--short'])
613
def test_log_show_diff_line(self):
614
# Not supported by this formatter so expect plain output
616
2: Lorem Ipsum 2005-11-22 [merge] merge branch level1
617
1: Lorem Ipsum 2005-11-22 in branch level0
619
self.check_log(expected, ['-p', '--line'])
621
def test_log_show_diff_file1(self):
622
"""Only the diffs for the given file are to be shown"""
624
1 Lorem Ipsum\t2005-11-22
626
=== added file 'file1'
627
--- file1\t1970-01-01 00:00:00 +0000
628
+++ file1\t2005-11-22 00:00:01 +0000
630
+contents of level0/file1
633
self.check_log(expected, ['-p', '--short', 'file1'])
635
def test_log_show_diff_file2(self):
636
"""Only the diffs for the given file are to be shown"""
638
2 Lorem Ipsum\t2005-11-22 [merge]
640
=== modified file 'file2'
641
--- file2\t2005-11-22 00:00:01 +0000
642
+++ file2\t2005-11-22 00:00:02 +0000
644
-contents of level0/file2
647
1 Lorem Ipsum\t2005-11-22
649
=== added file 'file2'
650
--- file2\t1970-01-01 00:00:00 +0000
651
+++ file2\t2005-11-22 00:00:01 +0000
653
+contents of level0/file2
655
Use --include-merges or -n0 to see merged revisions.
657
self.check_log(expected, ['-p', '--short', 'file2'])
660
class TestLogUnicodeDiff(TestLog):
662
def test_log_show_diff_non_ascii(self):
663
# Smoke test for bug #328007 UnicodeDecodeError on 'log -p'
664
message = u'Message with \xb5'
665
body = 'Body with \xb5\n'
666
wt = self.make_branch_and_tree('.')
667
self.build_tree_contents([('foo', body)])
669
wt.commit(message=message)
670
# check that command won't fail with unicode error
671
# don't care about exact output because we have other tests for this
672
out,err = self.run_bzr('log -p --long')
673
self.assertNotEqual('', out)
674
self.assertEqual('', err)
675
out,err = self.run_bzr('log -p --short')
676
self.assertNotEqual('', out)
677
self.assertEqual('', err)
678
out,err = self.run_bzr('log -p --line')
679
self.assertNotEqual('', out)
680
self.assertEqual('', err)
683
class TestLogEncodings(tests.TestCaseInTempDir):
686
_message = u'Message with \xb5'
688
# Encodings which can encode mu
693
'cp437', # Common windows encoding
694
'cp1251', # Russian windows encoding
695
'cp1258', # Common windows encoding
697
# Encodings which cannot encode mu
705
super(TestLogEncodings, self).setUp()
706
self.user_encoding = osutils._cached_user_encoding
708
osutils._cached_user_encoding = self.user_encoding
709
self.addCleanup(restore)
711
def create_branch(self):
714
open('a', 'wb').write('some stuff\n')
716
bzr(['commit', '-m', self._message])
718
def try_encoding(self, encoding, fail=False):
721
self.assertRaises(UnicodeEncodeError,
722
self._mu.encode, encoding)
723
encoded_msg = self._message.encode(encoding, 'replace')
725
encoded_msg = self._message.encode(encoding)
727
old_encoding = osutils._cached_user_encoding
728
# This test requires that 'run_bzr' uses the current
729
# bzrlib, because we override user_encoding, and expect
732
osutils._cached_user_encoding = 'ascii'
733
# We should be able to handle any encoding
734
out, err = bzr('log', encoding=encoding)
736
# Make sure we wrote mu as we expected it to exist
737
self.assertNotEqual(-1, out.find(encoded_msg))
738
out_unicode = out.decode(encoding)
739
self.assertNotEqual(-1, out_unicode.find(self._message))
741
self.assertNotEqual(-1, out.find('Message with ?'))
743
osutils._cached_user_encoding = old_encoding
745
def test_log_handles_encoding(self):
748
for encoding in self.good_encodings:
749
self.try_encoding(encoding)
751
def test_log_handles_bad_encoding(self):
754
for encoding in self.bad_encodings:
755
self.try_encoding(encoding, fail=True)
757
def test_stdout_encoding(self):
759
osutils._cached_user_encoding = "cp1251"
762
self.build_tree(['a'])
764
bzr(['commit', '-m', u'\u0422\u0435\u0441\u0442'])
765
stdout, stderr = self.run_bzr('log', encoding='cp866')
767
message = stdout.splitlines()[-1]
769
# explanation of the check:
770
# u'\u0422\u0435\u0441\u0442' is word 'Test' in russian
771
# in cp866 encoding this is string '\x92\xa5\xe1\xe2'
772
# in cp1251 encoding this is string '\xd2\xe5\xf1\xf2'
773
# This test should check that output of log command
774
# encoded to sys.stdout.encoding
775
test_in_cp866 = '\x92\xa5\xe1\xe2'
776
test_in_cp1251 = '\xd2\xe5\xf1\xf2'
777
# Make sure the log string is encoded in cp866
778
self.assertEquals(test_in_cp866, message[2:])
779
# Make sure the cp1251 string is not found anywhere
780
self.assertEquals(-1, stdout.find(test_in_cp1251))
783
class TestLogFile(tests.TestCaseWithTransport):
785
def test_log_local_branch_file(self):
786
"""We should be able to log files in local treeless branches"""
787
tree = self.make_branch_and_tree('tree')
788
self.build_tree(['tree/file'])
790
tree.commit('revision 1')
791
tree.bzrdir.destroy_workingtree()
792
self.run_bzr('log tree/file')
794
def prepare_tree(self, complex=False):
795
# The complex configuration includes deletes and renames
796
tree = self.make_branch_and_tree('parent')
797
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
799
tree.commit('add file1')
801
tree.commit('add file2')
803
tree.commit('add file3')
804
child_tree = tree.bzrdir.sprout('child').open_workingtree()
805
self.build_tree_contents([('child/file2', 'hello')])
806
child_tree.commit(message='branch 1')
807
tree.merge_from_branch(child_tree.branch)
808
tree.commit(message='merge child branch')
811
tree.commit('remove file2')
812
tree.rename_one('file3', 'file4')
813
tree.commit('file3 is now called file4')
815
tree.commit('remove file1')
818
def test_log_file(self):
819
"""The log for a particular file should only list revs for that file"""
821
log = self.run_bzr('log -n0 file1')[0]
822
self.assertContainsRe(log, 'revno: 1\n')
823
self.assertNotContainsRe(log, 'revno: 2\n')
824
self.assertNotContainsRe(log, 'revno: 3\n')
825
self.assertNotContainsRe(log, 'revno: 3.1.1\n')
826
self.assertNotContainsRe(log, 'revno: 4 ')
827
log = self.run_bzr('log -n0 file2')[0]
828
self.assertNotContainsRe(log, 'revno: 1\n')
829
self.assertContainsRe(log, 'revno: 2\n')
830
self.assertNotContainsRe(log, 'revno: 3\n')
831
self.assertContainsRe(log, 'revno: 3.1.1\n')
832
self.assertContainsRe(log, 'revno: 4 ')
833
log = self.run_bzr('log -n0 file3')[0]
834
self.assertNotContainsRe(log, 'revno: 1\n')
835
self.assertNotContainsRe(log, 'revno: 2\n')
836
self.assertContainsRe(log, 'revno: 3\n')
837
self.assertNotContainsRe(log, 'revno: 3.1.1\n')
838
self.assertNotContainsRe(log, 'revno: 4 ')
839
log = self.run_bzr('log -n0 -r3.1.1 file2')[0]
840
self.assertNotContainsRe(log, 'revno: 1\n')
841
self.assertNotContainsRe(log, 'revno: 2\n')
842
self.assertNotContainsRe(log, 'revno: 3\n')
843
self.assertContainsRe(log, 'revno: 3.1.1\n')
844
self.assertNotContainsRe(log, 'revno: 4 ')
845
log = self.run_bzr('log -n0 -r4 file2')[0]
846
self.assertNotContainsRe(log, 'revno: 1\n')
847
self.assertNotContainsRe(log, 'revno: 2\n')
848
self.assertNotContainsRe(log, 'revno: 3\n')
849
self.assertContainsRe(log, 'revno: 3.1.1\n')
850
self.assertContainsRe(log, 'revno: 4 ')
851
log = self.run_bzr('log -n0 -r3.. file2')[0]
852
self.assertNotContainsRe(log, 'revno: 1\n')
853
self.assertNotContainsRe(log, 'revno: 2\n')
854
self.assertNotContainsRe(log, 'revno: 3\n')
855
self.assertContainsRe(log, 'revno: 3.1.1\n')
856
self.assertContainsRe(log, 'revno: 4 ')
857
log = self.run_bzr('log -n0 -r..3 file2')[0]
858
self.assertNotContainsRe(log, 'revno: 1\n')
859
self.assertContainsRe(log, 'revno: 2\n')
860
self.assertNotContainsRe(log, 'revno: 3\n')
861
self.assertNotContainsRe(log, 'revno: 3.1.1\n')
862
self.assertNotContainsRe(log, 'revno: 4 ')
864
def test_log_file_historical_missing(self):
865
# Check logging a deleted file gives an error if the
866
# file isn't found at the end or start of the revision range
867
self.prepare_tree(complex=True)
868
err_msg = "Path unknown at end or start of revision range: file2"
869
err = self.run_bzr('log file2', retcode=3)[1]
870
self.assertContainsRe(err, err_msg)
872
def test_log_file_historical_end(self):
873
# Check logging a deleted file is ok if the file existed
874
# at the end the revision range
875
self.prepare_tree(complex=True)
876
log, err = self.run_bzr('log -n0 -r..4 file2')
877
self.assertEquals('', err)
878
self.assertNotContainsRe(log, 'revno: 1\n')
879
self.assertContainsRe(log, 'revno: 2\n')
880
self.assertNotContainsRe(log, 'revno: 3\n')
881
self.assertContainsRe(log, 'revno: 3.1.1\n')
882
self.assertContainsRe(log, 'revno: 4 ')
884
def test_log_file_historical_start(self):
885
# Check logging a deleted file is ok if the file existed
886
# at the start of the revision range
887
self.prepare_tree(complex=True)
888
log, err = self.run_bzr('log file1')
889
self.assertEquals('', err)
890
self.assertContainsRe(log, 'revno: 1\n')
891
self.assertNotContainsRe(log, 'revno: 2\n')
892
self.assertNotContainsRe(log, 'revno: 3\n')
893
self.assertNotContainsRe(log, 'revno: 3.1.1\n')
894
self.assertNotContainsRe(log, 'revno: 4 ')
896
def test_log_file_renamed(self):
897
"""File matched against revision range, not current tree."""
898
self.prepare_tree(complex=True)
900
# Check logging a renamed file gives an error by default
901
err_msg = "Path unknown at end or start of revision range: file3"
902
err = self.run_bzr('log file3', retcode=3)[1]
903
self.assertContainsRe(err, err_msg)
905
# Check we can see a renamed file if we give the right end revision
906
log, err = self.run_bzr('log -r..4 file3')
907
self.assertEquals('', err)
908
self.assertNotContainsRe(log, 'revno: 1\n')
909
self.assertNotContainsRe(log, 'revno: 2\n')
910
self.assertContainsRe(log, 'revno: 3\n')
911
self.assertNotContainsRe(log, 'revno: 3.1.1\n')
912
self.assertNotContainsRe(log, 'revno: 4 ')
914
def test_line_log_file(self):
915
"""The line log for a file should only list relevant mainline revs"""
916
# Note: this also implicitly covers the short logging case.
917
# We test using --line in preference to --short because matching
918
# revnos in the output of --line is more reliable.
921
# full history of file1
922
log = self.run_bzr('log --line file1')[0]
923
self.assertContainsRe(log, '^1:', re.MULTILINE)
924
self.assertNotContainsRe(log, '^2:', re.MULTILINE)
925
self.assertNotContainsRe(log, '^3:', re.MULTILINE)
926
self.assertNotContainsRe(log, '^3.1.1:', re.MULTILINE)
927
self.assertNotContainsRe(log, '^4:', re.MULTILINE)
929
# full history of file2
930
log = self.run_bzr('log --line file2')[0]
931
self.assertNotContainsRe(log, '^1:', re.MULTILINE)
932
self.assertContainsRe(log, '^2:', re.MULTILINE)
933
self.assertNotContainsRe(log, '^3:', re.MULTILINE)
934
self.assertNotContainsRe(log, '^3.1.1:', re.MULTILINE)
935
self.assertContainsRe(log, '^4:', re.MULTILINE)
937
# full history of file3
938
log = self.run_bzr('log --line file3')[0]
939
self.assertNotContainsRe(log, '^1:', re.MULTILINE)
940
self.assertNotContainsRe(log, '^2:', re.MULTILINE)
941
self.assertContainsRe(log, '^3:', re.MULTILINE)
942
self.assertNotContainsRe(log, '^3.1.1:', re.MULTILINE)
943
self.assertNotContainsRe(log, '^4:', re.MULTILINE)
945
# file in a merge revision
946
log = self.run_bzr('log --line -r3.1.1 file2')[0]
947
self.assertNotContainsRe(log, '^1:', re.MULTILINE)
948
self.assertNotContainsRe(log, '^2:', re.MULTILINE)
949
self.assertNotContainsRe(log, '^3:', re.MULTILINE)
950
self.assertContainsRe(log, '^3.1.1:', re.MULTILINE)
951
self.assertNotContainsRe(log, '^4:', re.MULTILINE)
953
# file in a mainline revision
954
log = self.run_bzr('log --line -r4 file2')[0]
955
self.assertNotContainsRe(log, '^1:', re.MULTILINE)
956
self.assertNotContainsRe(log, '^2:', re.MULTILINE)
957
self.assertNotContainsRe(log, '^3:', re.MULTILINE)
958
self.assertNotContainsRe(log, '^3.1.1:', re.MULTILINE)
959
self.assertContainsRe(log, '^4:', re.MULTILINE)
961
# file since a revision
962
log = self.run_bzr('log --line -r3.. file2')[0]
963
self.assertNotContainsRe(log, '^1:', re.MULTILINE)
964
self.assertNotContainsRe(log, '^2:', re.MULTILINE)
965
self.assertNotContainsRe(log, '^3:', re.MULTILINE)
966
self.assertNotContainsRe(log, '^3.1.1:', re.MULTILINE)
967
self.assertContainsRe(log, '^4:', re.MULTILINE)
969
# file up to a revision
970
log = self.run_bzr('log --line -r..3 file2')[0]
971
self.assertNotContainsRe(log, '^1:', re.MULTILINE)
972
self.assertContainsRe(log, '^2:', re.MULTILINE)
973
self.assertNotContainsRe(log, '^3:', re.MULTILINE)
974
self.assertNotContainsRe(log, '^3.1.1:', re.MULTILINE)
975
self.assertNotContainsRe(log, '^4:', re.MULTILINE)
978
class TestLogMultiple(tests.TestCaseWithTransport):
980
def prepare_tree(self):
981
tree = self.make_branch_and_tree('parent')
988
'parent/dir1/dir2/file3',
991
tree.commit('add file1')
993
tree.commit('add file2')
994
tree.add(['dir1', 'dir1/dir2', 'dir1/dir2/file3'])
995
tree.commit('add file3')
997
tree.commit('add file4')
998
tree.add('dir1/file5')
999
tree.commit('add file5')
1000
child_tree = tree.bzrdir.sprout('child').open_workingtree()
1001
self.build_tree_contents([('child/file2', 'hello')])
1002
child_tree.commit(message='branch 1')
1003
tree.merge_from_branch(child_tree.branch)
1004
tree.commit(message='merge child branch')
1007
def assertRevnos(self, paths_str, expected_revnos):
1008
# confirm the revision numbers in log --line output are those expected
1009
out, err = self.run_bzr('log --line -n0 %s' % (paths_str,))
1010
self.assertEqual('', err)
1011
revnos = [s.split(':', 1)[0].lstrip() for s in out.splitlines()]
1012
self.assertEqual(expected_revnos, revnos)
1014
def test_log_files(self):
1015
"""The log for multiple file should only list revs for those files"""
1017
self.assertRevnos('file1 file2 dir1/dir2/file3',
1018
['6', '5.1.1', '3', '2', '1'])
1020
def test_log_directory(self):
1021
"""The log for a directory should show all nested files."""
1023
self.assertRevnos('dir1', ['5', '3'])
1025
def test_log_nested_directory(self):
1026
"""The log for a directory should show all nested files."""
1028
self.assertRevnos('dir1/dir2', ['3'])
1030
def test_log_in_nested_directory(self):
1031
"""The log for a directory should show all nested files."""
1034
self.assertRevnos('.', ['5', '3'])
1036
def test_log_files_and_directories(self):
1037
"""Logging files and directories together should be fine."""
1039
self.assertRevnos('file4 dir1/dir2', ['4', '3'])
1041
def test_log_files_and_dirs_in_nested_directory(self):
1042
"""The log for a directory should show all nested files."""
1045
self.assertRevnos('dir2 file5', ['5', '3'])