~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Vincent Ladeuil
  • Date: 2010-02-03 07:18:36 UTC
  • mto: (5008.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 5009.
  • Revision ID: v.ladeuil+lp@free.fr-20100203071836-u9b86q68fr9ri5s6
Fix NEWS.

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