~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-07-02 00:49:15 UTC
  • mfrom: (1706.2.8 bzr.dev.lsprof)
  • Revision ID: pqm@pqm.ubuntu.com-20060702004915-501855cc9fc14e10
(robey) updates for lsprof

Show diffs side-by-side

added added

removed removed

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