~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: 2010-02-05 11:33:58 UTC
  • mfrom: (5008.1.1 integration)
  • Revision ID: pqm@pqm.ubuntu.com-20100205113358-wd4d49omal1hus75
(vila) Add --take-this and --take-other actions to bzr resolve

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) 2005, 2006, 2007, 2009 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)
91
 
 
92
 
 
93
 
class TestLogMerges(ExternalBase):
 
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')
94
449
 
95
450
    def test_merges_are_indented_by_level(self):
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):
 
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):
159
630
 
160
631
    _mu = u'\xb5'
161
632
    _message = u'Message with \xb5'
166
637
        'latin-1',
167
638
        'iso-8859-1',
168
639
        'cp437', # Common windows encoding
169
 
        'cp1251', # Alexander Belchenko's windows encoding
 
640
        'cp1251', # Russian windows encoding
170
641
        'cp1258', # Common windows encoding
171
642
    ]
172
643
    # Encodings which cannot encode mu
177
648
    ]
178
649
 
179
650
    def setUp(self):
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)
 
651
        super(TestLogEncodings, self).setUp()
 
652
        self.overrideAttr(osutils, '_cached_user_encoding')
186
653
 
187
654
    def create_branch(self):
188
655
        bzr = self.run_bzr
189
656
        bzr('init')
190
 
        open('a', 'wb').write('some stuff\n')
191
 
        bzr('add', 'a')
192
 
        bzr('commit', '-m', self._message)
 
657
        self.build_tree_contents([('a', 'some stuff\n')])
 
658
        bzr('add a')
 
659
        bzr(['commit', '-m', self._message])
193
660
 
194
661
    def try_encoding(self, encoding, fail=False):
195
662
        bzr = self.run_bzr
200
667
        else:
201
668
            encoded_msg = self._message.encode(encoding)
202
669
 
203
 
        old_encoding = bzrlib.user_encoding
 
670
        old_encoding = osutils._cached_user_encoding
204
671
        # This test requires that 'run_bzr' uses the current
205
672
        # bzrlib, because we override user_encoding, and expect
206
673
        # it to be used
207
674
        try:
208
 
            bzrlib.user_encoding = 'ascii'
 
675
            osutils._cached_user_encoding = 'ascii'
209
676
            # We should be able to handle any encoding
210
677
            out, err = bzr('log', encoding=encoding)
211
678
            if not fail:
216
683
            else:
217
684
                self.assertNotEqual(-1, out.find('Message with ?'))
218
685
        finally:
219
 
            bzrlib.user_encoding = old_encoding
 
686
            osutils._cached_user_encoding = old_encoding
220
687
 
221
688
    def test_log_handles_encoding(self):
222
689
        self.create_branch()
232
699
 
233
700
    def test_stdout_encoding(self):
234
701
        bzr = self.run_bzr
235
 
        bzrlib.user_encoding = "cp1251"
 
702
        osutils._cached_user_encoding = "cp1251"
236
703
 
237
704
        bzr('init')
238
705
        self.build_tree(['a'])
239
 
        bzr('add', 'a')
240
 
        bzr('commit', '-m', u'\u0422\u0435\u0441\u0442')
 
706
        bzr('add a')
 
707
        bzr(['commit', '-m', u'\u0422\u0435\u0441\u0442'])
241
708
        stdout, stderr = self.run_bzr('log', encoding='cp866')
242
709
 
243
710
        message = stdout.splitlines()[-1]
255
722
        # Make sure the cp1251 string is not found anywhere
256
723
        self.assertEquals(-1, stdout.find(test_in_cp1251))
257
724
 
 
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'])