/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/blackbox/test_log.py

  • Committer: Robert Collins
  • Date: 2010-05-11 08:44:59 UTC
  • mfrom: (5221 +trunk)
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100511084459-pb0uinna9zs3wu59
Merge trunk - resolve conflicts.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 by Canonical Ltd
2
 
# -*- coding: utf-8 -*-
 
1
# Copyright (C) 2006-2010 Canonical Ltd
3
2
#
4
3
# This program is free software; you can redistribute it and/or modify
5
4
# it under the terms of the GNU General Public License as published by
13
12
#
14
13
# You should have received a copy of the GNU General Public License
15
14
# along with this program; if not, write to the Free Software
16
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
16
 
18
17
 
19
18
"""Black-box tests for bzr log."""
20
19
 
 
20
from itertools import izip
21
21
import os
22
 
 
23
 
import bzrlib
24
 
from bzrlib.tests.blackbox import ExternalBase
25
 
from bzrlib.tests import TestCaseInTempDir
26
 
 
27
 
 
28
 
class TestLog(ExternalBase):
29
 
 
30
 
    def _prepare(self):
31
 
        self.runbzr("init")
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]
 
22
import re
 
23
 
 
24
from bzrlib import (
 
25
    branchbuilder,
 
26
    errors,
 
27
    log,
 
28
    osutils,
 
29
    tests,
 
30
    )
 
31
from bzrlib.tests import (
 
32
    script,
 
33
    test_log,
 
34
    )
 
35
 
 
36
 
 
37
class TestLog(tests.TestCaseWithTransport, test_log.TestLogMixin):
 
38
 
 
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'])
 
42
        tree.add('hello.txt')
 
43
        tree.commit(message='message1')
 
44
        return tree
 
45
 
 
46
    def make_linear_branch(self, path='.', format=None):
 
47
        tree = self.make_branch_and_tree(path, format=format)
 
48
        self.build_tree(
 
49
            [path + '/hello.txt', path + '/goodbye.txt', path + '/meep.txt'])
 
50
        tree.add('hello.txt')
 
51
        tree.commit(message='message1')
 
52
        tree.add('goodbye.txt')
 
53
        tree.commit(message='message2')
 
54
        tree.add('meep.txt')
 
55
        tree.commit(message='message3')
 
56
        return tree
 
57
 
 
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')
 
66
        return tree
 
67
 
 
68
 
 
69
class TestLogWithLogCatcher(TestLog):
 
70
 
 
71
    def setUp(self):
 
72
        super(TestLogWithLogCatcher, self).setUp()
 
73
        # Capture log formatter creations
 
74
        class MyLogFormatter(test_log.LogCatcher):
 
75
 
 
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
 
 
81
        def getme(branch):
 
82
                # Always return our own log formatter class hijacking the
 
83
                # default behavior (which requires setting up a config
 
84
                # variable)
 
85
            return MyLogFormatter
 
86
        self.overrideAttr(log.log_formatter_registry, 'get_default', getme)
 
87
 
 
88
    def get_captured_revisions(self):
 
89
        return self.log_catcher.revisions
 
90
 
 
91
    def assertLogRevnos(self, args, expected_revnos, working_dir='.'):
 
92
        self.run_bzr(['log'] + args, working_dir=working_dir)
 
93
        self.assertEqual(expected_revnos,
 
94
                         [r.revno for r in self.get_captured_revisions()])
 
95
 
 
96
    def assertLogRevnosAndDepths(self, args, expected_revnos_and_depths,
 
97
                                working_dir='.'):
 
98
        self.run_bzr(['log'] + args, working_dir=working_dir)
 
99
        self.assertEqual(expected_revnos_and_depths,
 
100
                         [(r.revno, r.merge_depth)
 
101
                           for r in self.get_captured_revisions()])
 
102
 
 
103
 
 
104
class TestLogRevSpecs(TestLogWithLogCatcher):
 
105
 
 
106
    def test_log_no_revspec(self):
 
107
        self.make_linear_branch()
 
108
        self.assertLogRevnos([], ['3', '2', '1'])
40
109
 
41
110
    def test_log_null_end_revspec(self):
42
 
        self._prepare()
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)
49
 
 
50
 
        log = self.runbzr("log -r 1..")[0]
51
 
        self.assertEquals(log, self.full_log)
 
111
        self.make_linear_branch()
 
112
        self.assertLogRevnos(['-r1..'], ['3', '2', '1'])
52
113
 
53
114
    def test_log_null_begin_revspec(self):
54
 
        self._prepare()
55
 
        log = self.runbzr("log -r ..3")[0]
56
 
        self.assertEquals(self.full_log, log)
 
115
        self.make_linear_branch()
 
116
        self.assertLogRevnos(['-r..3'], ['3', '2', '1'])
57
117
 
58
118
    def test_log_null_both_revspecs(self):
59
 
        self._prepare()
60
 
        log = self.runbzr("log -r ..")[0]
61
 
        self.assertEquals(self.full_log, log)
 
119
        self.make_linear_branch()
 
120
        self.assertLogRevnos(['-r..'], ['3', '2', '1'])
62
121
 
63
122
    def test_log_negative_begin_revspec_full_log(self):
64
 
        self._prepare()
65
 
        log = self.runbzr("log -r -3..")[0]
66
 
        self.assertEquals(self.full_log, log)
 
123
        self.make_linear_branch()
 
124
        self.assertLogRevnos(['-r-3..'], ['3', '2', '1'])
67
125
 
68
126
    def test_log_negative_both_revspec_full_log(self):
69
 
        self._prepare()
70
 
        log = self.runbzr("log -r -3..-1")[0]
71
 
        self.assertEquals(self.full_log, log)
 
127
        self.make_linear_branch()
 
128
        self.assertLogRevnos(['-r-3..-1'], ['3', '2', '1'])
72
129
 
73
130
    def test_log_negative_both_revspec_partial(self):
74
 
        self._prepare()
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)
 
131
        self.make_linear_branch()
 
132
        self.assertLogRevnos(['-r-3..-2'], ['2', '1'])
79
133
 
80
134
    def test_log_negative_begin_revspec(self):
81
 
        self._prepare()
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)
86
 
 
87
 
    def test_log_postive_revspecs(self):
88
 
        self._prepare()
89
 
        log = self.runbzr("log -r 1..3")[0]
90
 
        self.assertEquals(self.full_log, log)
 
135
        self.make_linear_branch()
 
136
        self.assertLogRevnos(['-r-2..'], ['3', '2'])
 
137
 
 
138
    def test_log_positive_revspecs(self):
 
139
        self.make_linear_branch()
 
140
        self.assertLogRevnos(['-r1..3'], ['3', '2', '1'])
 
141
 
 
142
    def test_log_dotted_revspecs(self):
 
143
        self.make_merged_branch()
 
144
        self.assertLogRevnos(['-n0', '-r1..1.1.1'], ['1.1.1', '1'])
 
145
 
 
146
    def test_log_limit(self):
 
147
        tree = self.make_branch_and_tree('.')
 
148
        # We want more commits than our batch size starts at
 
149
        for pos in range(10):
 
150
            tree.commit("%s" % pos)
 
151
        self.assertLogRevnos(['--limit', '2'], ['10', '9'])
 
152
 
 
153
    def test_log_limit_short(self):
 
154
        self.make_linear_branch()
 
155
        self.assertLogRevnos(['-l', '2'], ['3', '2'])
 
156
 
 
157
    def test_log_change_revno(self):
 
158
        self.make_linear_branch()
 
159
        self.assertLogRevnos(['-c1'], ['1'])
 
160
 
 
161
 
 
162
class TestLogMergedLinearAncestry(TestLogWithLogCatcher):
 
163
 
 
164
    def setUp(self):
 
165
        super(TestLogMergedLinearAncestry, self).setUp()
 
166
        # FIXME: Using a MemoryTree would be even better here (but until we
 
167
        # stop calling run_bzr, there is no point) --vila 100118.
 
168
        builder = branchbuilder.BranchBuilder(self.get_transport())
 
169
        builder.start_series()
 
170
        # mainline
 
171
        builder.build_snapshot('1', None, [
 
172
            ('add', ('', 'root-id', 'directory', ''))])
 
173
        builder.build_snapshot('2', ['1'], [])
 
174
        # branch
 
175
        builder.build_snapshot('1.1.1', ['1'], [])
 
176
        # merge branch into mainline
 
177
        builder.build_snapshot('3', ['2', '1.1.1'], [])
 
178
        # new commits in branch
 
179
        builder.build_snapshot('1.1.2', ['1.1.1'], [])
 
180
        builder.build_snapshot('1.1.3', ['1.1.2'], [])
 
181
        # merge branch into mainline
 
182
        builder.build_snapshot('4', ['3', '1.1.3'], [])
 
183
        # merge mainline into branch
 
184
        builder.build_snapshot('1.1.4', ['1.1.3', '4'], [])
 
185
        # merge branch into mainline
 
186
        builder.build_snapshot('5', ['4', '1.1.4'], [])
 
187
        builder.finish_series()
 
188
 
 
189
    def test_n0(self):
 
190
        self.assertLogRevnos(['-n0', '-r1.1.1..1.1.4'],
 
191
                             ['1.1.4', '4', '1.1.3', '1.1.2', '3', '1.1.1'])
 
192
    def test_n0_forward(self):
 
193
        self.assertLogRevnos(['-n0', '-r1.1.1..1.1.4', '--forward'],
 
194
                             ['3', '1.1.1', '4', '1.1.2', '1.1.3', '1.1.4'])
 
195
 
 
196
    def test_n1(self):
 
197
        # starting from 1.1.4 we follow the left-hand ancestry
 
198
        self.assertLogRevnos(['-n1', '-r1.1.1..1.1.4'],
 
199
                             ['1.1.4', '1.1.3', '1.1.2', '1.1.1'])
 
200
 
 
201
    def test_n1_forward(self):
 
202
        self.assertLogRevnos(['-n1', '-r1.1.1..1.1.4', '--forward'],
 
203
                             ['1.1.1', '1.1.2', '1.1.3', '1.1.4'])
 
204
 
 
205
 
 
206
class Test_GenerateAllRevisions(TestLogWithLogCatcher):
 
207
 
 
208
    def setUp(self):
 
209
        super(Test_GenerateAllRevisions, self).setUp()
 
210
        builder = self.make_branch_with_many_merges()
 
211
        b = builder.get_branch()
 
212
        b.lock_read()
 
213
        self.addCleanup(b.unlock)
 
214
        self.branch = b
 
215
 
 
216
    def make_branch_with_many_merges(self, path='.', format=None):
 
217
        builder = branchbuilder.BranchBuilder(self.get_transport())
 
218
        builder.start_series()
 
219
        # The graph below may look a bit complicated (and it may be but I've
 
220
        # banged my head enough on it) but the bug requires at least dotted
 
221
        # revnos *and* merged revisions below that.
 
222
        builder.build_snapshot('1', None, [
 
223
            ('add', ('', 'root-id', 'directory', ''))])
 
224
        builder.build_snapshot('2', ['1'], [])
 
225
        builder.build_snapshot('1.1.1', ['1'], [])
 
226
        builder.build_snapshot('2.1.1', ['2'], [])
 
227
        builder.build_snapshot('3', ['2', '1.1.1'], [])
 
228
        builder.build_snapshot('2.1.2', ['2.1.1'], [])
 
229
        builder.build_snapshot('2.2.1', ['2.1.1'], [])
 
230
        builder.build_snapshot('2.1.3', ['2.1.2', '2.2.1'], [])
 
231
        builder.build_snapshot('4', ['3', '2.1.3'], [])
 
232
        builder.build_snapshot('5', ['4', '2.1.2'], [])
 
233
        builder.finish_series()
 
234
        return builder
 
235
 
 
236
    def test_not_an_ancestor(self):
 
237
        self.assertRaises(errors.BzrCommandError,
 
238
                          log._generate_all_revisions,
 
239
                          self.branch, '1.1.1', '2.1.3', 'reverse',
 
240
                          delayed_graph_generation=True)
 
241
 
 
242
    def test_wrong_order(self):
 
243
        self.assertRaises(errors.BzrCommandError,
 
244
                          log._generate_all_revisions,
 
245
                          self.branch, '5', '2.1.3', 'reverse',
 
246
                          delayed_graph_generation=True)
 
247
 
 
248
    def test_no_start_rev_id_with_end_rev_id_being_a_merge(self):
 
249
        revs = log._generate_all_revisions(
 
250
            self.branch, None, '2.1.3',
 
251
            'reverse', delayed_graph_generation=True)
 
252
 
 
253
 
 
254
class TestLogRevSpecsWithPaths(TestLogWithLogCatcher):
 
255
 
 
256
    def test_log_revno_n_path_wrong_namespace(self):
 
257
        self.make_linear_branch('branch1')
 
258
        self.make_linear_branch('branch2')
 
259
        # There is no guarantee that a path exist between two arbitrary
 
260
        # revisions.
 
261
        self.run_bzr("log -r revno:2:branch1..revno:3:branch2", retcode=3)
 
262
 
 
263
    def test_log_revno_n_path_correct_order(self):
 
264
        self.make_linear_branch('branch2')
 
265
        self.assertLogRevnos(['-rrevno:1:branch2..revno:3:branch2'],
 
266
                             ['3', '2','1'])
91
267
 
92
268
    def test_log_revno_n_path(self):
93
 
        os.mkdir('branch1')
94
 
        os.chdir('branch1')
95
 
        self._prepare()
96
 
        os.chdir('..')
97
 
        os.mkdir('branch2')
98
 
        os.chdir('branch2')
99
 
        self._prepare()
100
 
        os.chdir('..')
101
 
        log = self.runbzr("log -r revno:2:branch1..revno:3:branch2",
102
 
                          retcode=3)[0]
103
 
        log = self.runbzr("log -r revno:1:branch2..revno:3:branch2")[0]
104
 
        self.assertEquals(self.full_log, log)
105
 
        log = self.runbzr("log -r revno:1:branch2")[0]
106
 
        self.assertTrue('revno: 1\n' in log)
107
 
        self.assertTrue('revno: 2\n' not in log)
108
 
        self.assertTrue('branch nick: branch2\n' in log)
109
 
        self.assertTrue('branch nick: branch1\n' not in log)
 
269
        self.make_linear_branch('branch2')
 
270
        self.assertLogRevnos(['-rrevno:1:branch2'],
 
271
                             ['1'])
 
272
        rev_props = self.log_catcher.revisions[0].rev.properties
 
273
        self.assertEqual('branch2', rev_props['branch-nick'])
 
274
 
 
275
 
 
276
class TestLogErrors(TestLog):
 
277
 
 
278
    def test_log_zero_revspec(self):
 
279
        self.make_minimal_branch()
 
280
        self.run_bzr_error(['bzr: ERROR: Logging revision 0 is invalid.'],
 
281
                           ['log', '-r0'])
 
282
 
 
283
    def test_log_zero_begin_revspec(self):
 
284
        self.make_linear_branch()
 
285
        self.run_bzr_error(['bzr: ERROR: Logging revision 0 is invalid.'],
 
286
                           ['log', '-r0..2'])
 
287
 
 
288
    def test_log_zero_end_revspec(self):
 
289
        self.make_linear_branch()
 
290
        self.run_bzr_error(['bzr: ERROR: Logging revision 0 is invalid.'],
 
291
                           ['log', '-r-2..0'])
 
292
 
 
293
    def test_log_nonexistent_revno(self):
 
294
        self.make_minimal_branch()
 
295
        self.run_bzr_error(["bzr: ERROR: Requested revision: '1234' "
 
296
                            "does not exist in branch:"],
 
297
                           ['log', '-r1234'])
 
298
 
 
299
    def test_log_nonexistent_dotted_revno(self):
 
300
        self.make_minimal_branch()
 
301
        self.run_bzr_error(["bzr: ERROR: Requested revision: '123.123' "
 
302
                            "does not exist in branch:"],
 
303
                           ['log',  '-r123.123'])
 
304
 
 
305
    def test_log_change_nonexistent_revno(self):
 
306
        self.make_minimal_branch()
 
307
        self.run_bzr_error(["bzr: ERROR: Requested revision: '1234' "
 
308
                            "does not exist in branch:"],
 
309
                           ['log',  '-c1234'])
 
310
 
 
311
    def test_log_change_nonexistent_dotted_revno(self):
 
312
        self.make_minimal_branch()
 
313
        self.run_bzr_error(["bzr: ERROR: Requested revision: '123.123' "
 
314
                            "does not exist in branch:"],
 
315
                           ['log', '-c123.123'])
 
316
 
 
317
    def test_log_change_single_revno_only(self):
 
318
        self.make_minimal_branch()
 
319
        self.run_bzr_error(['bzr: ERROR: Option --change does not'
 
320
                           ' accept revision ranges'],
 
321
                           ['log', '--change', '2..3'])
 
322
 
 
323
    def test_log_change_incompatible_with_revision(self):
 
324
        self.run_bzr_error(['bzr: ERROR: --revision and --change'
 
325
                           ' are mutually exclusive'],
 
326
                           ['log', '--change', '2', '--revision', '3'])
 
327
 
 
328
    def test_log_nonexistent_file(self):
 
329
        self.make_minimal_branch()
 
330
        # files that don't exist in either the basis tree or working tree
 
331
        # should give an error
 
332
        out, err = self.run_bzr('log does-not-exist', retcode=3)
 
333
        self.assertContainsRe(err,
 
334
                              'Path unknown at end or start of revision range: '
 
335
                              'does-not-exist')
 
336
 
 
337
    def test_log_reversed_revspecs(self):
 
338
        self.make_linear_branch()
 
339
        self.run_bzr_error(('bzr: ERROR: Start revision must be older than '
 
340
                            'the end revision.\n',),
 
341
                           ['log', '-r3..1'])
 
342
 
 
343
    def test_log_reversed_dotted_revspecs(self):
 
344
        self.make_merged_branch()
 
345
        self.run_bzr_error(('bzr: ERROR: Start revision not found in '
 
346
                            'left-hand history of end revision.\n',),
 
347
                           "log -r 1.1.1..1")
 
348
 
 
349
    def test_log_bad_message_re(self):
 
350
        """Bad --message argument gives a sensible message
110
351
        
111
 
 
112
 
class TestLogMerges(ExternalBase):
 
352
        See https://bugs.launchpad.net/bzr/+bug/251352
 
353
        """
 
354
        self.make_minimal_branch()
 
355
        out, err = self.run_bzr(['log', '-m', '*'], retcode=3)
 
356
        self.assertEqual("bzr: ERROR: Invalid regular expression"
 
357
            " in log message filter"
 
358
            ": '*'"
 
359
            ": nothing to repeat\n", err)
 
360
        self.assertEqual('', out)
 
361
 
 
362
    def test_log_unsupported_timezone(self):
 
363
        self.make_linear_branch()
 
364
        self.run_bzr_error(['bzr: ERROR: Unsupported timezone format "foo", '
 
365
                            'options are "utc", "original", "local".'],
 
366
                           ['log', '--timezone', 'foo'])
 
367
 
 
368
    def test_log_exclude_ancestry_no_range(self):
 
369
        self.make_linear_branch()
 
370
        self.run_bzr_error(['bzr: ERROR: --exclude-common-ancestry'
 
371
                            ' requires -r with two revisions'],
 
372
                           ['log', '--exclude-common-ancestry'])
 
373
 
 
374
    def test_log_exclude_ancestry_single_revision(self):
 
375
        self.make_merged_branch()
 
376
        self.run_bzr_error(['bzr: ERROR: --exclude-common-ancestry'
 
377
                            ' requires two different revisions'],
 
378
                           ['log', '--exclude-common-ancestry',
 
379
                            '-r1.1.1..1.1.1'])
 
380
 
 
381
class TestLogTags(TestLog):
 
382
 
 
383
    def test_log_with_tags(self):
 
384
        tree = self.make_linear_branch(format='dirstate-tags')
 
385
        branch = tree.branch
 
386
        branch.tags.set_tag('tag1', branch.get_rev_id(1))
 
387
        branch.tags.set_tag('tag1.1', branch.get_rev_id(1))
 
388
        branch.tags.set_tag('tag3', branch.last_revision())
 
389
 
 
390
        log = self.run_bzr("log -r-1")[0]
 
391
        self.assertTrue('tags: tag3' in log)
 
392
 
 
393
        log = self.run_bzr("log -r1")[0]
 
394
        # I guess that we can't know the order of tags in the output
 
395
        # since dicts are unordered, need to check both possibilities
 
396
        self.assertContainsRe(log, r'tags: (tag1, tag1\.1|tag1\.1, tag1)')
 
397
 
 
398
    def test_merged_log_with_tags(self):
 
399
        branch1_tree = self.make_linear_branch('branch1',
 
400
                                               format='dirstate-tags')
 
401
        branch1 = branch1_tree.branch
 
402
        branch2_tree = branch1_tree.bzrdir.sprout('branch2').open_workingtree()
 
403
        branch1_tree.commit(message='foobar', allow_pointless=True)
 
404
        branch1.tags.set_tag('tag1', branch1.last_revision())
 
405
        # tags don't propagate if we don't merge
 
406
        self.run_bzr('merge ../branch1', working_dir='branch2')
 
407
        branch2_tree.commit(message='merge branch 1')
 
408
        log = self.run_bzr("log -n0 -r-1", working_dir='branch2')[0]
 
409
        self.assertContainsRe(log, r'    tags: tag1')
 
410
        log = self.run_bzr("log -n0 -r3.1.1", working_dir='branch2')[0]
 
411
        self.assertContainsRe(log, r'tags: tag1')
 
412
 
 
413
 
 
414
class TestLogVerbose(TestLog):
 
415
 
 
416
    def setUp(self):
 
417
        super(TestLogVerbose, self).setUp()
 
418
        self.make_minimal_branch()
 
419
 
 
420
    def assertUseShortDeltaFormat(self, cmd):
 
421
        log = self.run_bzr(cmd)[0]
 
422
        # Check that we use the short status format
 
423
        self.assertContainsRe(log, '(?m)^\s*A  hello.txt$')
 
424
        self.assertNotContainsRe(log, '(?m)^\s*added:$')
 
425
 
 
426
    def assertUseLongDeltaFormat(self, cmd):
 
427
        log = self.run_bzr(cmd)[0]
 
428
        # Check that we use the long status format
 
429
        self.assertNotContainsRe(log, '(?m)^\s*A  hello.txt$')
 
430
        self.assertContainsRe(log, '(?m)^\s*added:$')
 
431
 
 
432
    def test_log_short_verbose(self):
 
433
        self.assertUseShortDeltaFormat(['log', '--short', '-v'])
 
434
 
 
435
    def test_log_short_verbose_verbose(self):
 
436
        self.assertUseLongDeltaFormat(['log', '--short', '-vv'])
 
437
 
 
438
    def test_log_long_verbose(self):
 
439
        # Check that we use the long status format, ignoring the verbosity
 
440
        # level
 
441
        self.assertUseLongDeltaFormat(['log', '--long', '-v'])
 
442
 
 
443
    def test_log_long_verbose_verbose(self):
 
444
        # Check that we use the long status format, ignoring the verbosity
 
445
        # level
 
446
        self.assertUseLongDeltaFormat(['log', '--long', '-vv'])
 
447
 
 
448
 
 
449
class TestLogMerges(TestLogWithLogCatcher):
 
450
 
 
451
    def setUp(self):
 
452
        super(TestLogMerges, self).setUp()
 
453
        self.make_branches_with_merges()
 
454
 
 
455
    def make_branches_with_merges(self):
 
456
        level0 = self.make_branch_and_tree('level0')
 
457
        self.wt_commit(level0, 'in branch level0')
 
458
        level1 = level0.bzrdir.sprout('level1').open_workingtree()
 
459
        self.wt_commit(level1, 'in branch level1')
 
460
        level2 = level1.bzrdir.sprout('level2').open_workingtree()
 
461
        self.wt_commit(level2, 'in branch level2')
 
462
        level1.merge_from_branch(level2.branch)
 
463
        self.wt_commit(level1, 'merge branch level2')
 
464
        level0.merge_from_branch(level1.branch)
 
465
        self.wt_commit(level0, 'merge branch level1')
113
466
 
114
467
    def test_merges_are_indented_by_level(self):
115
 
        self.build_tree(['parent/'])
116
 
        self.run_bzr('init', 'parent')
117
 
        self.run_bzr('commit', '-m', 'first post', '--unchanged', 'parent')
118
 
        self.run_bzr('branch', 'parent', 'child')
119
 
        self.run_bzr('commit', '-m', 'branch 1', '--unchanged', 'child')
120
 
        self.run_bzr('branch', 'child', 'smallerchild')
121
 
        self.run_bzr('commit', '-m', 'branch 2', '--unchanged', 'smallerchild')
122
 
        os.chdir('child')
123
 
        self.run_bzr('merge', '../smallerchild')
124
 
        self.run_bzr('commit', '-m', 'merge branch 2')
125
 
        os.chdir('../parent')
126
 
        self.run_bzr('merge', '../child')
127
 
        self.run_bzr('commit', '-m', 'merge branch 1')
128
 
        out,err = self.run_bzr('log')
129
 
        # the log will look something like:
130
 
#        self.assertEqual("""\
131
 
#------------------------------------------------------------
132
 
#revno: 2
133
 
#committer: Robert Collins <foo@example.com>
134
 
#branch nick: parent
135
 
#timestamp: Tue 2006-03-28 22:31:40 +1100
136
 
#message:
137
 
#  merge branch 1
138
 
#    ------------------------------------------------------------
139
 
#    merged: foo@example.com-20060328113140-91f43cfb46dc2863
140
 
#    committer: Robert Collins <foo@example.com>
141
 
#    branch nick: child
142
 
#    timestamp: Tue 2006-03-28 22:31:40 +1100
143
 
#    message:
144
 
#      merge branch 2
145
 
#        ------------------------------------------------------------
146
 
#        merged: foo@example.com-20060328113140-1ba24f850a0ef573
147
 
#        committer: Robert Collins <foo@example.com>
148
 
#        branch nick: smallerchild
149
 
#        timestamp: Tue 2006-03-28 22:31:40 +1100
150
 
#        message:
151
 
#          branch 2
152
 
#    ------------------------------------------------------------
153
 
#    merged: foo@example.com-20060328113140-5749a4757a8ac792
154
 
#    committer: Robert Collins <foo@example.com>
155
 
#    branch nick: child
156
 
#    timestamp: Tue 2006-03-28 22:31:40 +1100
157
 
#    message:
158
 
#      branch 1
159
 
#------------------------------------------------------------
160
 
#revno: 1
161
 
#committer: Robert Collins <foo@example.com>
162
 
#branch nick: parent
163
 
#timestamp: Tue 2006-03-28 22:31:39 +1100
164
 
#message:
165
 
#  first post
166
 
#""", out)
167
 
        # but we dont have a nice pattern matcher hooked up yet, so:
168
 
        # we check for the indenting of the commit message:
169
 
        self.assertTrue('  merge branch 1' in out)
170
 
        self.assertTrue('      merge branch 2' in out)
171
 
        self.assertTrue('          branch 2' in out)
172
 
        self.assertTrue('      branch 1' in out)
173
 
        self.assertTrue('  first post' in out)
174
 
        self.assertEqual('', err)
175
 
 
176
 
 
177
 
class TestLogEncodings(TestCaseInTempDir):
 
468
        self.run_bzr(['log', '-n0'], working_dir='level0')
 
469
        revnos_and_depth = [(r.revno, r.merge_depth)
 
470
                            for r in self.get_captured_revisions()]
 
471
        self.assertEqual([('2', 0), ('1.1.2', 1), ('1.2.1', 2), ('1.1.1', 1),
 
472
                          ('1', 0)],
 
473
                         [(r.revno, r.merge_depth)
 
474
                            for r in self.get_captured_revisions()])
 
475
 
 
476
    def test_force_merge_revisions_off(self):
 
477
        self.assertLogRevnos(['-n1'], ['2', '1'], working_dir='level0')
 
478
 
 
479
    def test_force_merge_revisions_on(self):
 
480
        self.assertLogRevnos(['-n0'], ['2', '1.1.2', '1.2.1', '1.1.1', '1'],
 
481
                             working_dir='level0')
 
482
 
 
483
    def test_include_merges(self):
 
484
        # Confirm --include-merges gives the same output as -n0
 
485
        self.assertLogRevnos(['--include-merges'],
 
486
                             ['2', '1.1.2', '1.2.1', '1.1.1', '1'],
 
487
                             working_dir='level0')
 
488
        self.assertLogRevnos(['--include-merges'],
 
489
                             ['2', '1.1.2', '1.2.1', '1.1.1', '1'],
 
490
                             working_dir='level0')
 
491
        out_im, err_im = self.run_bzr('log --include-merges',
 
492
                                      working_dir='level0')
 
493
        out_n0, err_n0 = self.run_bzr('log -n0', working_dir='level0')
 
494
        self.assertEqual('', err_im)
 
495
        self.assertEqual('', err_n0)
 
496
        self.assertEqual(out_im, out_n0)
 
497
 
 
498
    def test_force_merge_revisions_N(self):
 
499
        self.assertLogRevnos(['-n2'],
 
500
                             ['2', '1.1.2', '1.1.1', '1'],
 
501
                             working_dir='level0')
 
502
 
 
503
    def test_merges_single_merge_rev(self):
 
504
        self.assertLogRevnosAndDepths(['-n0', '-r1.1.2'],
 
505
                                      [('1.1.2', 0), ('1.2.1', 1)],
 
506
                                      working_dir='level0')
 
507
 
 
508
    def test_merges_partial_range(self):
 
509
        self.assertLogRevnosAndDepths(
 
510
                ['-n0', '-r1.1.1..1.1.2'],
 
511
                [('1.1.2', 0), ('1.2.1', 1), ('1.1.1', 0)],
 
512
                working_dir='level0')
 
513
 
 
514
    def test_merges_partial_range_ignore_before_lower_bound(self):
 
515
        """Dont show revisions before the lower bound's merged revs"""
 
516
        self.assertLogRevnosAndDepths(
 
517
                ['-n0', '-r1.1.2..2'],
 
518
                [('2', 0), ('1.1.2', 1), ('1.2.1', 2)],
 
519
                working_dir='level0')
 
520
 
 
521
 
 
522
class TestLogDiff(TestLogWithLogCatcher):
 
523
 
 
524
    # FIXME: We need specific tests for each LogFormatter about how the diffs
 
525
    # are displayed: --long indent them by depth, --short use a fixed
 
526
    # indent and --line does't display them. -- vila 10019
 
527
 
 
528
    def setUp(self):
 
529
        super(TestLogDiff, self).setUp()
 
530
        self.make_branch_with_diffs()
 
531
 
 
532
    def make_branch_with_diffs(self):
 
533
        level0 = self.make_branch_and_tree('level0')
 
534
        self.build_tree(['level0/file1', 'level0/file2'])
 
535
        level0.add('file1')
 
536
        level0.add('file2')
 
537
        self.wt_commit(level0, 'in branch level0')
 
538
 
 
539
        level1 = level0.bzrdir.sprout('level1').open_workingtree()
 
540
        self.build_tree_contents([('level1/file2', 'hello\n')])
 
541
        self.wt_commit(level1, 'in branch level1')
 
542
        level0.merge_from_branch(level1.branch)
 
543
        self.wt_commit(level0, 'merge branch level1')
 
544
 
 
545
    def _diff_file1_revno1(self):
 
546
        return """=== added file 'file1'
 
547
--- file1\t1970-01-01 00:00:00 +0000
 
548
+++ file1\t2005-11-22 00:00:00 +0000
 
549
@@ -0,0 +1,1 @@
 
550
+contents of level0/file1
 
551
 
 
552
"""
 
553
 
 
554
    def _diff_file2_revno2(self):
 
555
        return """=== modified file 'file2'
 
556
--- file2\t2005-11-22 00:00:00 +0000
 
557
+++ file2\t2005-11-22 00:00:01 +0000
 
558
@@ -1,1 +1,1 @@
 
559
-contents of level0/file2
 
560
+hello
 
561
 
 
562
"""
 
563
 
 
564
    def _diff_file2_revno1_1_1(self):
 
565
        return """=== modified file 'file2'
 
566
--- file2\t2005-11-22 00:00:00 +0000
 
567
+++ file2\t2005-11-22 00:00:01 +0000
 
568
@@ -1,1 +1,1 @@
 
569
-contents of level0/file2
 
570
+hello
 
571
 
 
572
"""
 
573
 
 
574
    def _diff_file2_revno1(self):
 
575
        return """=== added file 'file2'
 
576
--- file2\t1970-01-01 00:00:00 +0000
 
577
+++ file2\t2005-11-22 00:00:00 +0000
 
578
@@ -0,0 +1,1 @@
 
579
+contents of level0/file2
 
580
 
 
581
"""
 
582
 
 
583
    def assertLogRevnosAndDiff(self, args, expected,
 
584
                            working_dir='.'):
 
585
        self.run_bzr(['log', '-p'] + args, working_dir=working_dir)
 
586
        expected_revnos_and_depths = [
 
587
            (revno, depth) for revno, depth, diff in expected]
 
588
        # Check the revnos and depths first to make debugging easier
 
589
        self.assertEqual(expected_revnos_and_depths,
 
590
                         [(r.revno, r.merge_depth)
 
591
                           for r in self.get_captured_revisions()])
 
592
        # Now check the diffs, adding the revno  in case of failure
 
593
        fmt = 'In revno %s\n%s'
 
594
        for expected_rev, actual_rev in izip(expected,
 
595
                                             self.get_captured_revisions()):
 
596
            revno, depth, expected_diff = expected_rev
 
597
            actual_diff = actual_rev.diff
 
598
            self.assertEqualDiff(fmt % (revno, expected_diff),
 
599
                                 fmt % (revno, actual_diff))
 
600
 
 
601
    def test_log_diff_with_merges(self):
 
602
        self.assertLogRevnosAndDiff(
 
603
            ['-n0'],
 
604
            [('2', 0, self._diff_file2_revno2()),
 
605
             ('1.1.1', 1, self._diff_file2_revno1_1_1()),
 
606
             ('1', 0, self._diff_file1_revno1()
 
607
              + self._diff_file2_revno1())],
 
608
            working_dir='level0')
 
609
 
 
610
 
 
611
    def test_log_diff_file1(self):
 
612
        self.assertLogRevnosAndDiff(['-n0', 'file1'],
 
613
                                    [('1', 0, self._diff_file1_revno1())],
 
614
                                    working_dir='level0')
 
615
 
 
616
    def test_log_diff_file2(self):
 
617
        self.assertLogRevnosAndDiff(['-n1', 'file2'],
 
618
                                    [('2', 0, self._diff_file2_revno2()),
 
619
                                     ('1', 0, self._diff_file2_revno1())],
 
620
                                    working_dir='level0')
 
621
 
 
622
 
 
623
class TestLogUnicodeDiff(TestLog):
 
624
 
 
625
    def test_log_show_diff_non_ascii(self):
 
626
        # Smoke test for bug #328007 UnicodeDecodeError on 'log -p'
 
627
        message = u'Message with \xb5'
 
628
        body = 'Body with \xb5\n'
 
629
        wt = self.make_branch_and_tree('.')
 
630
        self.build_tree_contents([('foo', body)])
 
631
        wt.add('foo')
 
632
        wt.commit(message=message)
 
633
        # check that command won't fail with unicode error
 
634
        # don't care about exact output because we have other tests for this
 
635
        out,err = self.run_bzr('log -p --long')
 
636
        self.assertNotEqual('', out)
 
637
        self.assertEqual('', err)
 
638
        out,err = self.run_bzr('log -p --short')
 
639
        self.assertNotEqual('', out)
 
640
        self.assertEqual('', err)
 
641
        out,err = self.run_bzr('log -p --line')
 
642
        self.assertNotEqual('', out)
 
643
        self.assertEqual('', err)
 
644
 
 
645
 
 
646
class TestLogEncodings(tests.TestCaseInTempDir):
178
647
 
179
648
    _mu = u'\xb5'
180
649
    _message = u'Message with \xb5'
185
654
        'latin-1',
186
655
        'iso-8859-1',
187
656
        'cp437', # Common windows encoding
188
 
        'cp1251', # Alexander Belchenko's windows encoding
 
657
        'cp1251', # Russian windows encoding
189
658
        'cp1258', # Common windows encoding
190
659
    ]
191
660
    # Encodings which cannot encode mu
196
665
    ]
197
666
 
198
667
    def setUp(self):
199
 
        TestCaseInTempDir.setUp(self)
200
 
        self.user_encoding = bzrlib.user_encoding
201
 
 
202
 
    def tearDown(self):
203
 
        bzrlib.user_encoding = self.user_encoding
204
 
        TestCaseInTempDir.tearDown(self)
 
668
        super(TestLogEncodings, self).setUp()
 
669
        self.overrideAttr(osutils, '_cached_user_encoding')
205
670
 
206
671
    def create_branch(self):
207
672
        bzr = self.run_bzr
208
673
        bzr('init')
209
 
        open('a', 'wb').write('some stuff\n')
210
 
        bzr('add', 'a')
211
 
        bzr('commit', '-m', self._message)
 
674
        self.build_tree_contents([('a', 'some stuff\n')])
 
675
        bzr('add a')
 
676
        bzr(['commit', '-m', self._message])
212
677
 
213
678
    def try_encoding(self, encoding, fail=False):
214
679
        bzr = self.run_bzr
219
684
        else:
220
685
            encoded_msg = self._message.encode(encoding)
221
686
 
222
 
        old_encoding = bzrlib.user_encoding
 
687
        old_encoding = osutils._cached_user_encoding
223
688
        # This test requires that 'run_bzr' uses the current
224
689
        # bzrlib, because we override user_encoding, and expect
225
690
        # it to be used
226
691
        try:
227
 
            bzrlib.user_encoding = 'ascii'
 
692
            osutils._cached_user_encoding = 'ascii'
228
693
            # We should be able to handle any encoding
229
694
            out, err = bzr('log', encoding=encoding)
230
695
            if not fail:
235
700
            else:
236
701
                self.assertNotEqual(-1, out.find('Message with ?'))
237
702
        finally:
238
 
            bzrlib.user_encoding = old_encoding
 
703
            osutils._cached_user_encoding = old_encoding
239
704
 
240
705
    def test_log_handles_encoding(self):
241
706
        self.create_branch()
251
716
 
252
717
    def test_stdout_encoding(self):
253
718
        bzr = self.run_bzr
254
 
        bzrlib.user_encoding = "cp1251"
 
719
        osutils._cached_user_encoding = "cp1251"
255
720
 
256
721
        bzr('init')
257
722
        self.build_tree(['a'])
258
 
        bzr('add', 'a')
259
 
        bzr('commit', '-m', u'\u0422\u0435\u0441\u0442')
 
723
        bzr('add a')
 
724
        bzr(['commit', '-m', u'\u0422\u0435\u0441\u0442'])
260
725
        stdout, stderr = self.run_bzr('log', encoding='cp866')
261
726
 
262
727
        message = stdout.splitlines()[-1]
274
739
        # Make sure the cp1251 string is not found anywhere
275
740
        self.assertEquals(-1, stdout.find(test_in_cp1251))
276
741
 
 
742
 
 
743
class TestLogFile(TestLogWithLogCatcher):
 
744
 
 
745
    def test_log_local_branch_file(self):
 
746
        """We should be able to log files in local treeless branches"""
 
747
        tree = self.make_branch_and_tree('tree')
 
748
        self.build_tree(['tree/file'])
 
749
        tree.add('file')
 
750
        tree.commit('revision 1')
 
751
        tree.bzrdir.destroy_workingtree()
 
752
        self.run_bzr('log tree/file')
 
753
 
 
754
    def prepare_tree(self, complex=False):
 
755
        # The complex configuration includes deletes and renames
 
756
        tree = self.make_branch_and_tree('parent')
 
757
        self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
 
758
        tree.add('file1')
 
759
        tree.commit('add file1')
 
760
        tree.add('file2')
 
761
        tree.commit('add file2')
 
762
        tree.add('file3')
 
763
        tree.commit('add file3')
 
764
        child_tree = tree.bzrdir.sprout('child').open_workingtree()
 
765
        self.build_tree_contents([('child/file2', 'hello')])
 
766
        child_tree.commit(message='branch 1')
 
767
        tree.merge_from_branch(child_tree.branch)
 
768
        tree.commit(message='merge child branch')
 
769
        if complex:
 
770
            tree.remove('file2')
 
771
            tree.commit('remove file2')
 
772
            tree.rename_one('file3', 'file4')
 
773
            tree.commit('file3 is now called file4')
 
774
            tree.remove('file1')
 
775
            tree.commit('remove file1')
 
776
        os.chdir('parent')
 
777
 
 
778
    # FIXME: It would be good to parametrize the following tests against all
 
779
    # formatters. But the revisions selection is not *currently* part of the
 
780
    # LogFormatter contract, so using LogCatcher is sufficient -- vila 100118
 
781
    def test_log_file1(self):
 
782
        self.prepare_tree()
 
783
        self.assertLogRevnos(['-n0', 'file1'], ['1'])
 
784
 
 
785
    def test_log_file2(self):
 
786
        self.prepare_tree()
 
787
        # file2 full history
 
788
        self.assertLogRevnos(['-n0', 'file2'], ['4', '3.1.1', '2'])
 
789
        # file2 in a merge revision
 
790
        self.assertLogRevnos(['-n0', '-r3.1.1', 'file2'], ['3.1.1'])
 
791
        # file2 in a mainline revision
 
792
        self.assertLogRevnos(['-n0', '-r4', 'file2'], ['4', '3.1.1'])
 
793
        # file2 since a revision
 
794
        self.assertLogRevnos(['-n0', '-r3..', 'file2'], ['4', '3.1.1'])
 
795
        # file2 up to a revision
 
796
        self.assertLogRevnos(['-n0', '-r..3', 'file2'], ['2'])
 
797
 
 
798
    def test_log_file3(self):
 
799
        self.prepare_tree()
 
800
        self.assertLogRevnos(['-n0', 'file3'], ['3'])
 
801
 
 
802
    def test_log_file_historical_missing(self):
 
803
        # Check logging a deleted file gives an error if the
 
804
        # file isn't found at the end or start of the revision range
 
805
        self.prepare_tree(complex=True)
 
806
        err_msg = "Path unknown at end or start of revision range: file2"
 
807
        err = self.run_bzr('log file2', retcode=3)[1]
 
808
        self.assertContainsRe(err, err_msg)
 
809
 
 
810
    def test_log_file_historical_end(self):
 
811
        # Check logging a deleted file is ok if the file existed
 
812
        # at the end the revision range
 
813
        self.prepare_tree(complex=True)
 
814
        self.assertLogRevnos(['-n0', '-r..4', 'file2'], ['4', '3.1.1', '2'])
 
815
 
 
816
    def test_log_file_historical_start(self):
 
817
        # Check logging a deleted file is ok if the file existed
 
818
        # at the start of the revision range
 
819
        self.prepare_tree(complex=True)
 
820
        self.assertLogRevnos(['file1'], ['1'])
 
821
 
 
822
    def test_log_file_renamed(self):
 
823
        """File matched against revision range, not current tree."""
 
824
        self.prepare_tree(complex=True)
 
825
 
 
826
        # Check logging a renamed file gives an error by default
 
827
        err_msg = "Path unknown at end or start of revision range: file3"
 
828
        err = self.run_bzr('log file3', retcode=3)[1]
 
829
        self.assertContainsRe(err, err_msg)
 
830
 
 
831
        # Check we can see a renamed file if we give the right end revision
 
832
        self.assertLogRevnos(['-r..4', 'file3'], ['3'])
 
833
 
 
834
 
 
835
class TestLogMultiple(TestLogWithLogCatcher):
 
836
 
 
837
    def prepare_tree(self):
 
838
        tree = self.make_branch_and_tree('parent')
 
839
        self.build_tree([
 
840
            'parent/file1',
 
841
            'parent/file2',
 
842
            'parent/dir1/',
 
843
            'parent/dir1/file5',
 
844
            'parent/dir1/dir2/',
 
845
            'parent/dir1/dir2/file3',
 
846
            'parent/file4'])
 
847
        tree.add('file1')
 
848
        tree.commit('add file1')
 
849
        tree.add('file2')
 
850
        tree.commit('add file2')
 
851
        tree.add(['dir1', 'dir1/dir2', 'dir1/dir2/file3'])
 
852
        tree.commit('add file3')
 
853
        tree.add('file4')
 
854
        tree.commit('add file4')
 
855
        tree.add('dir1/file5')
 
856
        tree.commit('add file5')
 
857
        child_tree = tree.bzrdir.sprout('child').open_workingtree()
 
858
        self.build_tree_contents([('child/file2', 'hello')])
 
859
        child_tree.commit(message='branch 1')
 
860
        tree.merge_from_branch(child_tree.branch)
 
861
        tree.commit(message='merge child branch')
 
862
        os.chdir('parent')
 
863
 
 
864
    def test_log_files(self):
 
865
        """The log for multiple file should only list revs for those files"""
 
866
        self.prepare_tree()
 
867
        self.assertLogRevnos(['file1', 'file2', 'dir1/dir2/file3'],
 
868
                             ['6', '5.1.1', '3', '2', '1'])
 
869
 
 
870
    def test_log_directory(self):
 
871
        """The log for a directory should show all nested files."""
 
872
        self.prepare_tree()
 
873
        self.assertLogRevnos(['dir1'], ['5', '3'])
 
874
 
 
875
    def test_log_nested_directory(self):
 
876
        """The log for a directory should show all nested files."""
 
877
        self.prepare_tree()
 
878
        self.assertLogRevnos(['dir1/dir2'], ['3'])
 
879
 
 
880
    def test_log_in_nested_directory(self):
 
881
        """The log for a directory should show all nested files."""
 
882
        self.prepare_tree()
 
883
        os.chdir("dir1")
 
884
        self.assertLogRevnos(['.'], ['5', '3'])
 
885
 
 
886
    def test_log_files_and_directories(self):
 
887
        """Logging files and directories together should be fine."""
 
888
        self.prepare_tree()
 
889
        self.assertLogRevnos(['file4', 'dir1/dir2'], ['4', '3'])
 
890
 
 
891
    def test_log_files_and_dirs_in_nested_directory(self):
 
892
        """The log for a directory should show all nested files."""
 
893
        self.prepare_tree()
 
894
        os.chdir("dir1")
 
895
        self.assertLogRevnos(['dir2', 'file5'], ['5', '3'])