~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: 2011-05-04 12:10:51 UTC
  • mfrom: (5819.1.4 777007-developer-doc)
  • Revision ID: pqm@pqm.ubuntu.com-20110504121051-aovlsmqiivjmc4fc
(jelmer) Small fixes to developer documentation. (Jonathan Riddell)

Show diffs side-by-side

added added

removed removed

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