~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: 2009-02-10 04:54:18 UTC
  • mfrom: (3988.1.3 bzr.dev)
  • Revision ID: pqm@pqm.ubuntu.com-20090210045418-u1c0p4zpnp6nna3n
(Jelmer) Add specification for colocated branches.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
2
# -*- coding: utf-8 -*-
 
3
#
 
4
# This program is free software; you can redistribute it and/or modify
 
5
# it under the terms of the GNU General Public License as published by
 
6
# the Free Software Foundation; either version 2 of the License, or
 
7
# (at your option) any later version.
 
8
#
 
9
# This program is distributed in the hope that it will be useful,
 
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
12
# GNU General Public License for more details.
 
13
#
 
14
# You should have received a copy of the GNU General Public License
 
15
# along with this program; if not, write to the Free Software
 
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
17
 
 
18
 
 
19
"""Black-box tests for bzr log."""
 
20
 
 
21
import os, re
 
22
 
 
23
from bzrlib import osutils
 
24
from bzrlib.tests.blackbox import ExternalBase
 
25
from bzrlib.tests import KnownFailure, TestCaseInTempDir, TestCaseWithTransport
 
26
from bzrlib.tests.test_log import (
 
27
    normalize_log,
 
28
    )
 
29
from bzrlib.tests import test_log
 
30
 
 
31
 
 
32
class TestCaseWithoutPropsHandler(ExternalBase,
 
33
                                  test_log.TestCaseWithoutPropsHandler):
 
34
    pass
 
35
 
 
36
 
 
37
class TestLog(ExternalBase):
 
38
 
 
39
    def _prepare(self, path='.', format=None):
 
40
        tree = self.make_branch_and_tree(path, format=format)
 
41
        self.build_tree(
 
42
            [path + '/hello.txt', path + '/goodbye.txt', path + '/meep.txt'])
 
43
        tree.add('hello.txt')
 
44
        tree.commit(message='message1')
 
45
        tree.add('goodbye.txt')
 
46
        tree.commit(message='message2')
 
47
        tree.add('meep.txt')
 
48
        tree.commit(message='message3')
 
49
        self.full_log = self.run_bzr(["log", path])[0]
 
50
        return tree
 
51
 
 
52
    def test_log_null_end_revspec(self):
 
53
        self._prepare()
 
54
        self.assertTrue('revno: 1\n' in self.full_log)
 
55
        self.assertTrue('revno: 2\n' in self.full_log)
 
56
        self.assertTrue('revno: 3\n' in self.full_log)
 
57
        self.assertTrue('message:\n  message1\n' in self.full_log)
 
58
        self.assertTrue('message:\n  message2\n' in self.full_log)
 
59
        self.assertTrue('message:\n  message3\n' in self.full_log)
 
60
 
 
61
        log = self.run_bzr("log -r 1..")[0]
 
62
        self.assertEqualDiff(log, self.full_log)
 
63
 
 
64
    def test_log_null_begin_revspec(self):
 
65
        self._prepare()
 
66
        log = self.run_bzr("log -r ..3")[0]
 
67
        self.assertEqualDiff(self.full_log, log)
 
68
 
 
69
    def test_log_null_both_revspecs(self):
 
70
        self._prepare()
 
71
        log = self.run_bzr("log -r ..")[0]
 
72
        self.assertEqualDiff(self.full_log, log)
 
73
 
 
74
    def test_log_zero_revspec(self):
 
75
        self._prepare()
 
76
        self.run_bzr_error('bzr: ERROR: Logging revision 0 is invalid.',
 
77
                           ['log', '-r0'])
 
78
 
 
79
    def test_log_zero_begin_revspec(self):
 
80
        self._prepare()
 
81
        self.run_bzr_error('bzr: ERROR: Logging revision 0 is invalid.',
 
82
                           ['log', '-r0..2'])
 
83
 
 
84
    def test_log_zero_end_revspec(self):
 
85
        self._prepare()
 
86
        self.run_bzr_error('bzr: ERROR: Logging revision 0 is invalid.',
 
87
                           ['log', '-r-2..0'])
 
88
 
 
89
    def test_log_unsupported_timezone(self):
 
90
        self._prepare()
 
91
        self.run_bzr_error('bzr: ERROR: Unsupported timezone format "foo", '
 
92
                           'options are "utc", "original", "local".',
 
93
                           ['log', '--timezone', 'foo'])
 
94
 
 
95
    def test_log_negative_begin_revspec_full_log(self):
 
96
        self._prepare()
 
97
        log = self.run_bzr("log -r -3..")[0]
 
98
        self.assertEqualDiff(self.full_log, log)
 
99
 
 
100
    def test_log_negative_both_revspec_full_log(self):
 
101
        self._prepare()
 
102
        log = self.run_bzr("log -r -3..-1")[0]
 
103
        self.assertEqualDiff(self.full_log, log)
 
104
 
 
105
    def test_log_negative_both_revspec_partial(self):
 
106
        self._prepare()
 
107
        log = self.run_bzr("log -r -3..-2")[0]
 
108
        self.assertTrue('revno: 1\n' in log)
 
109
        self.assertTrue('revno: 2\n' in log)
 
110
        self.assertTrue('revno: 3\n' not in log)
 
111
 
 
112
    def test_log_negative_begin_revspec(self):
 
113
        self._prepare()
 
114
        log = self.run_bzr("log -r -2..")[0]
 
115
        self.assertTrue('revno: 1\n' not in log)
 
116
        self.assertTrue('revno: 2\n' in log)
 
117
        self.assertTrue('revno: 3\n' in log)
 
118
 
 
119
    def test_log_positive_revspecs(self):
 
120
        self._prepare()
 
121
        log = self.run_bzr("log -r 1..3")[0]
 
122
        self.assertEqualDiff(self.full_log, log)
 
123
 
 
124
    def test_log_reversed_revspecs(self):
 
125
        self._prepare()
 
126
        self.run_bzr_error(('bzr: ERROR: Start revision must be older than '
 
127
                            'the end revision.\n',),
 
128
                           ['log', '-r3..1'])
 
129
 
 
130
    def test_log_revno_n_path(self):
 
131
        self._prepare(path='branch1')
 
132
        self._prepare(path='branch2')
 
133
        log = self.run_bzr("log -r revno:2:branch1..revno:3:branch2",
 
134
                          retcode=3)[0]
 
135
        log = self.run_bzr("log -r revno:1:branch2..revno:3:branch2")[0]
 
136
        self.assertEqualDiff(self.full_log, log)
 
137
        log = self.run_bzr("log -r revno:1:branch2")[0]
 
138
        self.assertTrue('revno: 1\n' in log)
 
139
        self.assertTrue('revno: 2\n' not in log)
 
140
        self.assertTrue('branch nick: branch2\n' in log)
 
141
        self.assertTrue('branch nick: branch1\n' not in log)
 
142
 
 
143
    def test_log_nonexistent_revno(self):
 
144
        self._prepare()
 
145
        (out, err) = self.run_bzr_error(args="log -r 1234",
 
146
            error_regexes=["bzr: ERROR: Requested revision: '1234' "
 
147
                "does not exist in branch:"])
 
148
 
 
149
    def test_log_nonexistent_dotted_revno(self):
 
150
        self._prepare()
 
151
        (out, err) = self.run_bzr_error(args="log -r 123.123",
 
152
            error_regexes=["bzr: ERROR: Requested revision: '123.123' "
 
153
                "does not exist in branch:"])
 
154
 
 
155
    def test_log_change_revno(self):
 
156
        self._prepare()
 
157
        expected_log = self.run_bzr("log -r 1")[0]
 
158
        log = self.run_bzr("log -c 1")[0]
 
159
        self.assertEqualDiff(expected_log, log)
 
160
 
 
161
    def test_log_change_nonexistent_revno(self):
 
162
        self._prepare()
 
163
        (out, err) = self.run_bzr_error(args="log -c 1234",
 
164
            error_regexes=["bzr: ERROR: Requested revision: '1234' "
 
165
                "does not exist in branch:"])
 
166
 
 
167
    def test_log_change_nonexistent_dotted_revno(self):
 
168
        self._prepare()
 
169
        (out, err) = self.run_bzr_error(args="log -c 123.123",
 
170
            error_regexes=["bzr: ERROR: Requested revision: '123.123' "
 
171
                "does not exist in branch:"])
 
172
 
 
173
    def test_log_change_single_revno(self):
 
174
        self._prepare()
 
175
        self.run_bzr_error('bzr: ERROR: Option --change does not'
 
176
                           ' accept revision ranges',
 
177
                           ['log', '--change', '2..3'])
 
178
 
 
179
    def test_log_change_incompatible_with_revision(self):
 
180
        self._prepare()
 
181
        self.run_bzr_error('bzr: ERROR: --revision and --change'
 
182
                           ' are mutually exclusive',
 
183
                           ['log', '--change', '2', '--revision', '3'])
 
184
 
 
185
    def test_log_nonexistent_file(self):
 
186
        # files that don't exist in either the basis tree or working tree
 
187
        # should give an error
 
188
        wt = self.make_branch_and_tree('.')
 
189
        out, err = self.run_bzr('log does-not-exist', retcode=3)
 
190
        self.assertContainsRe(
 
191
            err, 'Path unknown at end or start of revision range: does-not-exist')
 
192
 
 
193
    def test_log_with_tags(self):
 
194
        tree = self._prepare(format='dirstate-tags')
 
195
        branch = tree.branch
 
196
        branch.tags.set_tag('tag1', branch.get_rev_id(1))
 
197
        branch.tags.set_tag('tag1.1', branch.get_rev_id(1))
 
198
        branch.tags.set_tag('tag3', branch.last_revision())
 
199
 
 
200
        log = self.run_bzr("log -r-1")[0]
 
201
        self.assertTrue('tags: tag3' in log)
 
202
 
 
203
        log = self.run_bzr("log -r1")[0]
 
204
        # I guess that we can't know the order of tags in the output
 
205
        # since dicts are unordered, need to check both possibilities
 
206
        self.assertContainsRe(log, r'tags: (tag1, tag1\.1|tag1\.1, tag1)')
 
207
 
 
208
    def test_merged_log_with_tags(self):
 
209
        branch1_tree = self._prepare(path='branch1', format='dirstate-tags')
 
210
        branch1 = branch1_tree.branch
 
211
        branch2_tree = branch1_tree.bzrdir.sprout('branch2').open_workingtree()
 
212
        branch1_tree.commit(message='foobar', allow_pointless=True)
 
213
        branch1.tags.set_tag('tag1', branch1.last_revision())
 
214
        os.chdir('branch2')
 
215
        self.run_bzr('merge ../branch1') # tags don't propagate otherwise
 
216
        branch2_tree.commit(message='merge branch 1')
 
217
        log = self.run_bzr("log -r-1")[0]
 
218
        self.assertContainsRe(log, r'    tags: tag1')
 
219
        log = self.run_bzr("log -r3.1.1")[0]
 
220
        self.assertContainsRe(log, r'tags: tag1')
 
221
 
 
222
    def test_log_limit(self):
 
223
        tree = self.make_branch_and_tree('.')
 
224
        # We want more commits than our batch size starts at
 
225
        for pos in range(10):
 
226
            tree.commit("%s" % pos)
 
227
        log = self.run_bzr("log --limit 2")[0]
 
228
        self.assertNotContainsRe(log, r'revno: 1\n')
 
229
        self.assertNotContainsRe(log, r'revno: 2\n')
 
230
        self.assertNotContainsRe(log, r'revno: 3\n')
 
231
        self.assertNotContainsRe(log, r'revno: 4\n')
 
232
        self.assertNotContainsRe(log, r'revno: 5\n')
 
233
        self.assertNotContainsRe(log, r'revno: 6\n')
 
234
        self.assertNotContainsRe(log, r'revno: 7\n')
 
235
        self.assertNotContainsRe(log, r'revno: 8\n')
 
236
        self.assertContainsRe(log, r'revno: 9\n')
 
237
        self.assertContainsRe(log, r'revno: 10\n')
 
238
 
 
239
    def test_log_limit_short(self):
 
240
        self._prepare()
 
241
        log = self.run_bzr("log -l 2")[0]
 
242
        self.assertNotContainsRe(log, r'revno: 1\n')
 
243
        self.assertContainsRe(log, r'revno: 2\n')
 
244
        self.assertContainsRe(log, r'revno: 3\n')
 
245
 
 
246
 
 
247
class TestLogVerbose(TestCaseWithTransport):
 
248
 
 
249
    def setUp(self):
 
250
        super(TestLogVerbose, self).setUp()
 
251
        tree = self.make_branch_and_tree('.')
 
252
        self.build_tree(['hello.txt'])
 
253
        tree.add('hello.txt')
 
254
        tree.commit(message='message1')
 
255
 
 
256
    def assertUseShortDeltaFormat(self, cmd):
 
257
        log = self.run_bzr(cmd)[0]
 
258
        # Check that we use the short status format
 
259
        self.assertContainsRe(log, '(?m)^\s*A  hello.txt$')
 
260
        self.assertNotContainsRe(log, '(?m)^\s*added:$')
 
261
 
 
262
    def assertUseLongDeltaFormat(self, cmd):
 
263
        log = self.run_bzr(cmd)[0]
 
264
        # Check that we use the long status format
 
265
        self.assertNotContainsRe(log, '(?m)^\s*A  hello.txt$')
 
266
        self.assertContainsRe(log, '(?m)^\s*added:$')
 
267
 
 
268
    def test_log_short_verbose(self):
 
269
        self.assertUseShortDeltaFormat(['log', '--short', '-v'])
 
270
 
 
271
    def test_log_short_verbose_verbose(self):
 
272
        self.assertUseLongDeltaFormat(['log', '--short', '-vv'])
 
273
 
 
274
    def test_log_long_verbose(self):
 
275
        # Check that we use the long status format, ignoring the verbosity
 
276
        # level
 
277
        self.assertUseLongDeltaFormat(['log', '--long', '-v'])
 
278
 
 
279
    def test_log_long_verbose_verbose(self):
 
280
        # Check that we use the long status format, ignoring the verbosity
 
281
        # level
 
282
        self.assertUseLongDeltaFormat(['log', '--long', '-vv'])
 
283
 
 
284
 
 
285
class TestLogMerges(TestCaseWithoutPropsHandler):
 
286
 
 
287
    def _prepare(self):
 
288
        parent_tree = self.make_branch_and_tree('parent')
 
289
        parent_tree.commit(message='first post', allow_pointless=True)
 
290
        child_tree = parent_tree.bzrdir.sprout('child').open_workingtree()
 
291
        child_tree.commit(message='branch 1', allow_pointless=True)
 
292
        smaller_tree = \
 
293
                child_tree.bzrdir.sprout('smallerchild').open_workingtree()
 
294
        smaller_tree.commit(message='branch 2', allow_pointless=True)
 
295
        child_tree.merge_from_branch(smaller_tree.branch)
 
296
        child_tree.commit(message='merge branch 2')
 
297
        parent_tree.merge_from_branch(child_tree.branch)
 
298
        parent_tree.commit(message='merge branch 1')
 
299
        os.chdir('parent')
 
300
 
 
301
    def _prepare_short(self):
 
302
        parent_tree = self.make_branch_and_tree('parent')
 
303
        parent_tree.commit(message='first post',
 
304
            timestamp=1132586700, timezone=36000,
 
305
            committer='Joe Foo <joe@foo.com>')
 
306
        child_tree = parent_tree.bzrdir.sprout('child').open_workingtree()
 
307
        child_tree.commit(message='branch 1',
 
308
            timestamp=1132586800, timezone=36000,
 
309
            committer='Joe Foo <joe@foo.com>')
 
310
        smaller_tree = \
 
311
                child_tree.bzrdir.sprout('smallerchild').open_workingtree()
 
312
        smaller_tree.commit(message='branch 2',
 
313
            timestamp=1132586900, timezone=36000,
 
314
            committer='Joe Foo <joe@foo.com>')
 
315
        child_tree.merge_from_branch(smaller_tree.branch)
 
316
        child_tree.commit(message='merge branch 2',
 
317
            timestamp=1132587000, timezone=36000,
 
318
            committer='Joe Foo <joe@foo.com>')
 
319
        parent_tree.merge_from_branch(child_tree.branch)
 
320
        parent_tree.commit(message='merge branch 1',
 
321
            timestamp=1132587100, timezone=36000,
 
322
            committer='Joe Foo <joe@foo.com>')
 
323
        os.chdir('parent')
 
324
 
 
325
    def test_merges_are_indented_by_level(self):
 
326
        self._prepare()
 
327
        out,err = self.run_bzr('log')
 
328
        self.assertEqual('', err)
 
329
        log = normalize_log(out)
 
330
        self.assertEqualDiff(log, """\
 
331
------------------------------------------------------------
 
332
revno: 2
 
333
committer: Lorem Ipsum <test@example.com>
 
334
branch nick: parent
 
335
timestamp: Just now
 
336
message:
 
337
  merge branch 1
 
338
    ------------------------------------------------------------
 
339
    revno: 1.1.2
 
340
    committer: Lorem Ipsum <test@example.com>
 
341
    branch nick: child
 
342
    timestamp: Just now
 
343
    message:
 
344
      merge branch 2
 
345
        ------------------------------------------------------------
 
346
        revno: 1.2.1
 
347
        committer: Lorem Ipsum <test@example.com>
 
348
        branch nick: smallerchild
 
349
        timestamp: Just now
 
350
        message:
 
351
          branch 2
 
352
    ------------------------------------------------------------
 
353
    revno: 1.1.1
 
354
    committer: Lorem Ipsum <test@example.com>
 
355
    branch nick: child
 
356
    timestamp: Just now
 
357
    message:
 
358
      branch 1
 
359
------------------------------------------------------------
 
360
revno: 1
 
361
committer: Lorem Ipsum <test@example.com>
 
362
branch nick: parent
 
363
timestamp: Just now
 
364
message:
 
365
  first post
 
366
""")
 
367
 
 
368
    def test_force_merge_revisions_off(self):
 
369
        self._prepare()
 
370
        out,err = self.run_bzr('log --long -n1')
 
371
        self.assertEqual('', err)
 
372
        log = normalize_log(out)
 
373
        self.assertEqualDiff(log, """\
 
374
------------------------------------------------------------
 
375
revno: 2
 
376
committer: Lorem Ipsum <test@example.com>
 
377
branch nick: parent
 
378
timestamp: Just now
 
379
message:
 
380
  merge branch 1
 
381
------------------------------------------------------------
 
382
revno: 1
 
383
committer: Lorem Ipsum <test@example.com>
 
384
branch nick: parent
 
385
timestamp: Just now
 
386
message:
 
387
  first post
 
388
""")
 
389
 
 
390
    def test_force_merge_revisions_on(self):
 
391
        self._prepare_short()
 
392
        out,err = self.run_bzr('log --short -n0')
 
393
        self.assertEqual('', err)
 
394
        log = normalize_log(out)
 
395
        self.assertEqualDiff(log, """\
 
396
    2 Joe Foo\t2005-11-22 [merge]
 
397
      merge branch 1
 
398
 
 
399
          1.1.2 Joe Foo\t2005-11-22 [merge]
 
400
                merge branch 2
 
401
 
 
402
              1.2.1 Joe Foo\t2005-11-22
 
403
                    branch 2
 
404
 
 
405
          1.1.1 Joe Foo\t2005-11-22
 
406
                branch 1
 
407
 
 
408
    1 Joe Foo\t2005-11-22
 
409
      first post
 
410
 
 
411
""")
 
412
 
 
413
    def test_force_merge_revisions_N(self):
 
414
        self._prepare_short()
 
415
        out,err = self.run_bzr('log --short -n2')
 
416
        self.assertEqual('', err)
 
417
        log = normalize_log(out)
 
418
        self.assertEqualDiff(log, """\
 
419
    2 Joe Foo\t2005-11-22 [merge]
 
420
      merge branch 1
 
421
 
 
422
          1.1.2 Joe Foo\t2005-11-22 [merge]
 
423
                merge branch 2
 
424
 
 
425
          1.1.1 Joe Foo\t2005-11-22
 
426
                branch 1
 
427
 
 
428
    1 Joe Foo\t2005-11-22
 
429
      first post
 
430
 
 
431
""")
 
432
 
 
433
    def test_merges_single_merge_rev(self):
 
434
        self._prepare()
 
435
        out,err = self.run_bzr('log -r1.1.2')
 
436
        self.assertEqual('', err)
 
437
        log = normalize_log(out)
 
438
        self.assertEqualDiff(log, """\
 
439
------------------------------------------------------------
 
440
revno: 1.1.2
 
441
committer: Lorem Ipsum <test@example.com>
 
442
branch nick: child
 
443
timestamp: Just now
 
444
message:
 
445
  merge branch 2
 
446
    ------------------------------------------------------------
 
447
    revno: 1.2.1
 
448
    committer: Lorem Ipsum <test@example.com>
 
449
    branch nick: smallerchild
 
450
    timestamp: Just now
 
451
    message:
 
452
      branch 2
 
453
""")
 
454
 
 
455
    def test_merges_partial_range(self):
 
456
        self._prepare()
 
457
        out, err = self.run_bzr('log -r1.1.1..1.1.2')
 
458
        self.assertEqual('', err)
 
459
        log = normalize_log(out)
 
460
        self.assertEqualDiff(log, """\
 
461
------------------------------------------------------------
 
462
revno: 1.1.2
 
463
committer: Lorem Ipsum <test@example.com>
 
464
branch nick: child
 
465
timestamp: Just now
 
466
message:
 
467
  merge branch 2
 
468
    ------------------------------------------------------------
 
469
    revno: 1.2.1
 
470
    committer: Lorem Ipsum <test@example.com>
 
471
    branch nick: smallerchild
 
472
    timestamp: Just now
 
473
    message:
 
474
      branch 2
 
475
------------------------------------------------------------
 
476
revno: 1.1.1
 
477
committer: Lorem Ipsum <test@example.com>
 
478
branch nick: child
 
479
timestamp: Just now
 
480
message:
 
481
  branch 1
 
482
""")
 
483
 
 
484
    def test_merges_nonsupporting_formatter(self):
 
485
        # This "feature" of log formatters is madness. If a log
 
486
        # formatter cannot display a dotted-revno, it ought to ignore it.
 
487
        # Otherwise, a linear sequence is always expected to be handled now.
 
488
        raise KnownFailure('log formatters must support linear sequences now')
 
489
        self._prepare()
 
490
        err_msg = 'Selected log formatter only supports mainline revisions.'
 
491
        # The single revision case is tested in the core tests
 
492
        # since all standard formatters support single merge revisions.
 
493
        out,err = self.run_bzr('log --short -r1..1.1.2', retcode=3)
 
494
        self.assertContainsRe(err, err_msg)
 
495
        out,err = self.run_bzr('log --short -r1.1.1..1.1.2', retcode=3)
 
496
        self.assertContainsRe(err, err_msg)
 
497
 
 
498
 
 
499
def subst_dates(string):
 
500
    """Replace date strings with constant values."""
 
501
    return re.sub(r'\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} [-\+]\d{4}',
 
502
                  'YYYY-MM-DD HH:MM:SS +ZZZZ', string)
 
503
 
 
504
 
 
505
class TestLogDiff(TestCaseWithoutPropsHandler):
 
506
 
 
507
    def _prepare(self):
 
508
        parent_tree = self.make_branch_and_tree('parent')
 
509
        self.build_tree(['parent/file1', 'parent/file2'])
 
510
        parent_tree.add('file1')
 
511
        parent_tree.add('file2')
 
512
        parent_tree.commit(message='first post',
 
513
            timestamp=1132586655, timezone=36000,
 
514
            committer='Lorem Ipsum <test@example.com>')
 
515
        child_tree = parent_tree.bzrdir.sprout('child').open_workingtree()
 
516
        self.build_tree_contents([('child/file2', 'hello\n')])
 
517
        child_tree.commit(message='branch 1',
 
518
            timestamp=1132586700, timezone=36000,
 
519
            committer='Lorem Ipsum <test@example.com>')
 
520
        parent_tree.merge_from_branch(child_tree.branch)
 
521
        parent_tree.commit(message='merge branch 1',
 
522
            timestamp=1132586800, timezone=36000,
 
523
            committer='Lorem Ipsum <test@example.com>')
 
524
        os.chdir('parent')
 
525
 
 
526
    def test_log_show_diff_long(self):
 
527
        self._prepare()
 
528
        out,err = self.run_bzr('log -p')
 
529
        self.assertEqual('', err)
 
530
        log = normalize_log(out)
 
531
        self.assertEqualDiff(subst_dates(log), """\
 
532
------------------------------------------------------------
 
533
revno: 2
 
534
committer: Lorem Ipsum <test@example.com>
 
535
branch nick: parent
 
536
timestamp: Just now
 
537
message:
 
538
  merge branch 1
 
539
diff:
 
540
=== modified file 'file2'
 
541
--- file2\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
542
+++ file2\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
543
@@ -1,1 +1,1 @@
 
544
-contents of parent/file2
 
545
+hello
 
546
    ------------------------------------------------------------
 
547
    revno: 1.1.1
 
548
    committer: Lorem Ipsum <test@example.com>
 
549
    branch nick: child
 
550
    timestamp: Just now
 
551
    message:
 
552
      branch 1
 
553
    diff:
 
554
    === modified file 'file2'
 
555
    --- file2\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
556
    +++ file2\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
557
    @@ -1,1 +1,1 @@
 
558
    -contents of parent/file2
 
559
    +hello
 
560
------------------------------------------------------------
 
561
revno: 1
 
562
committer: Lorem Ipsum <test@example.com>
 
563
branch nick: parent
 
564
timestamp: Just now
 
565
message:
 
566
  first post
 
567
diff:
 
568
=== added file 'file1'
 
569
--- file1\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
570
+++ file1\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
571
@@ -0,0 +1,1 @@
 
572
+contents of parent/file1
 
573
 
 
574
=== added file 'file2'
 
575
--- file2\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
576
+++ file2\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
577
@@ -0,0 +1,1 @@
 
578
+contents of parent/file2
 
579
""")
 
580
 
 
581
    def test_log_show_diff_short(self):
 
582
        self._prepare()
 
583
        out,err = self.run_bzr('log -p --short')
 
584
        self.assertEqual('', err)
 
585
        log = normalize_log(out)
 
586
        self.assertEqualDiff(subst_dates(log), """\
 
587
    2 Lorem Ipsum\t2005-11-22 [merge]
 
588
      merge branch 1
 
589
      === modified file 'file2'
 
590
      --- file2\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
591
      +++ file2\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
592
      @@ -1,1 +1,1 @@
 
593
      -contents of parent/file2
 
594
      +hello
 
595
 
 
596
    1 Lorem Ipsum\t2005-11-22
 
597
      first post
 
598
      === added file 'file1'
 
599
      --- file1\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
600
      +++ file1\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
601
      @@ -0,0 +1,1 @@
 
602
      +contents of parent/file1
 
603
      
 
604
      === added file 'file2'
 
605
      --- file2\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
606
      +++ file2\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
607
      @@ -0,0 +1,1 @@
 
608
      +contents of parent/file2
 
609
 
 
610
""")
 
611
 
 
612
    def test_log_show_diff_line(self):
 
613
        self._prepare()
 
614
        out,err = self.run_bzr('log -p --line')
 
615
        self.assertEqual('', err)
 
616
        log = normalize_log(out)
 
617
        # Not supported by this formatter so expect plain output
 
618
        self.assertEqualDiff(subst_dates(log), """\
 
619
2: Lorem Ipsum 2005-11-22 merge branch 1
 
620
1: Lorem Ipsum 2005-11-22 first post
 
621
""")
 
622
 
 
623
    def test_log_show_diff_file(self):
 
624
        """Only the diffs for the given file are to be shown"""
 
625
        self._prepare()
 
626
        out,err = self.run_bzr('log -p --short file2')
 
627
        self.assertEqual('', err)
 
628
        log = normalize_log(out)
 
629
        self.assertEqualDiff(subst_dates(log), """\
 
630
    2 Lorem Ipsum\t2005-11-22 [merge]
 
631
      merge branch 1
 
632
      === modified file 'file2'
 
633
      --- file2\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
634
      +++ file2\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
635
      @@ -1,1 +1,1 @@
 
636
      -contents of parent/file2
 
637
      +hello
 
638
 
 
639
    1 Lorem Ipsum\t2005-11-22
 
640
      first post
 
641
      === added file 'file2'
 
642
      --- file2\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
643
      +++ file2\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
644
      @@ -0,0 +1,1 @@
 
645
      +contents of parent/file2
 
646
 
 
647
""")
 
648
        out,err = self.run_bzr('log -p --short file1')
 
649
        self.assertEqual('', err)
 
650
        log = normalize_log(out)
 
651
        self.assertEqualDiff(subst_dates(log), """\
 
652
    1 Lorem Ipsum\t2005-11-22
 
653
      first post
 
654
      === added file 'file1'
 
655
      --- file1\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
656
      +++ file1\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
657
      @@ -0,0 +1,1 @@
 
658
      +contents of parent/file1
 
659
 
 
660
""")
 
661
 
 
662
 
 
663
class TestLogEncodings(TestCaseInTempDir):
 
664
 
 
665
    _mu = u'\xb5'
 
666
    _message = u'Message with \xb5'
 
667
 
 
668
    # Encodings which can encode mu
 
669
    good_encodings = [
 
670
        'utf-8',
 
671
        'latin-1',
 
672
        'iso-8859-1',
 
673
        'cp437', # Common windows encoding
 
674
        'cp1251', # Alexander Belchenko's windows encoding
 
675
        'cp1258', # Common windows encoding
 
676
    ]
 
677
    # Encodings which cannot encode mu
 
678
    bad_encodings = [
 
679
        'ascii',
 
680
        'iso-8859-2',
 
681
        'koi8_r',
 
682
    ]
 
683
 
 
684
    def setUp(self):
 
685
        TestCaseInTempDir.setUp(self)
 
686
        self.user_encoding = osutils._cached_user_encoding
 
687
 
 
688
    def tearDown(self):
 
689
        osutils._cached_user_encoding = self.user_encoding
 
690
        TestCaseInTempDir.tearDown(self)
 
691
 
 
692
    def create_branch(self):
 
693
        bzr = self.run_bzr
 
694
        bzr('init')
 
695
        open('a', 'wb').write('some stuff\n')
 
696
        bzr('add a')
 
697
        bzr(['commit', '-m', self._message])
 
698
 
 
699
    def try_encoding(self, encoding, fail=False):
 
700
        bzr = self.run_bzr
 
701
        if fail:
 
702
            self.assertRaises(UnicodeEncodeError,
 
703
                self._mu.encode, encoding)
 
704
            encoded_msg = self._message.encode(encoding, 'replace')
 
705
        else:
 
706
            encoded_msg = self._message.encode(encoding)
 
707
 
 
708
        old_encoding = osutils._cached_user_encoding
 
709
        # This test requires that 'run_bzr' uses the current
 
710
        # bzrlib, because we override user_encoding, and expect
 
711
        # it to be used
 
712
        try:
 
713
            osutils._cached_user_encoding = 'ascii'
 
714
            # We should be able to handle any encoding
 
715
            out, err = bzr('log', encoding=encoding)
 
716
            if not fail:
 
717
                # Make sure we wrote mu as we expected it to exist
 
718
                self.assertNotEqual(-1, out.find(encoded_msg))
 
719
                out_unicode = out.decode(encoding)
 
720
                self.assertNotEqual(-1, out_unicode.find(self._message))
 
721
            else:
 
722
                self.assertNotEqual(-1, out.find('Message with ?'))
 
723
        finally:
 
724
            osutils._cached_user_encoding = old_encoding
 
725
 
 
726
    def test_log_handles_encoding(self):
 
727
        self.create_branch()
 
728
 
 
729
        for encoding in self.good_encodings:
 
730
            self.try_encoding(encoding)
 
731
 
 
732
    def test_log_handles_bad_encoding(self):
 
733
        self.create_branch()
 
734
 
 
735
        for encoding in self.bad_encodings:
 
736
            self.try_encoding(encoding, fail=True)
 
737
 
 
738
    def test_stdout_encoding(self):
 
739
        bzr = self.run_bzr
 
740
        osutils._cached_user_encoding = "cp1251"
 
741
 
 
742
        bzr('init')
 
743
        self.build_tree(['a'])
 
744
        bzr('add a')
 
745
        bzr(['commit', '-m', u'\u0422\u0435\u0441\u0442'])
 
746
        stdout, stderr = self.run_bzr('log', encoding='cp866')
 
747
 
 
748
        message = stdout.splitlines()[-1]
 
749
 
 
750
        # explanation of the check:
 
751
        # u'\u0422\u0435\u0441\u0442' is word 'Test' in russian
 
752
        # in cp866  encoding this is string '\x92\xa5\xe1\xe2'
 
753
        # in cp1251 encoding this is string '\xd2\xe5\xf1\xf2'
 
754
        # This test should check that output of log command
 
755
        # encoded to sys.stdout.encoding
 
756
        test_in_cp866 = '\x92\xa5\xe1\xe2'
 
757
        test_in_cp1251 = '\xd2\xe5\xf1\xf2'
 
758
        # Make sure the log string is encoded in cp866
 
759
        self.assertEquals(test_in_cp866, message[2:])
 
760
        # Make sure the cp1251 string is not found anywhere
 
761
        self.assertEquals(-1, stdout.find(test_in_cp1251))
 
762
 
 
763
 
 
764
class TestLogFile(TestCaseWithTransport):
 
765
 
 
766
    def test_log_local_branch_file(self):
 
767
        """We should be able to log files in local treeless branches"""
 
768
        tree = self.make_branch_and_tree('tree')
 
769
        self.build_tree(['tree/file'])
 
770
        tree.add('file')
 
771
        tree.commit('revision 1')
 
772
        tree.bzrdir.destroy_workingtree()
 
773
        self.run_bzr('log tree/file')
 
774
 
 
775
    def prepare_tree(self, complex=False):
 
776
        # The complex configuration includes deletes and renames
 
777
        tree = self.make_branch_and_tree('parent')
 
778
        self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
 
779
        tree.add('file1')
 
780
        tree.commit('add file1')
 
781
        tree.add('file2')
 
782
        tree.commit('add file2')
 
783
        tree.add('file3')
 
784
        tree.commit('add file3')
 
785
        child_tree = tree.bzrdir.sprout('child').open_workingtree()
 
786
        self.build_tree_contents([('child/file2', 'hello')])
 
787
        child_tree.commit(message='branch 1')
 
788
        tree.merge_from_branch(child_tree.branch)
 
789
        tree.commit(message='merge child branch')
 
790
        if complex:
 
791
            tree.remove('file2')
 
792
            tree.commit('remove file2')
 
793
            tree.rename_one('file3', 'file4')
 
794
            tree.commit('file3 is now called file4')
 
795
            tree.remove('file1')
 
796
            tree.commit('remove file1')
 
797
        os.chdir('parent')
 
798
 
 
799
    def test_log_file(self):
 
800
        """The log for a particular file should only list revs for that file"""
 
801
        self.prepare_tree()
 
802
        log = self.run_bzr('log file1')[0]
 
803
        self.assertContainsRe(log, 'revno: 1\n')
 
804
        self.assertNotContainsRe(log, 'revno: 2\n')
 
805
        self.assertNotContainsRe(log, 'revno: 3\n')
 
806
        self.assertNotContainsRe(log, 'revno: 3.1.1\n')
 
807
        self.assertNotContainsRe(log, 'revno: 4\n')
 
808
        log = self.run_bzr('log file2')[0]
 
809
        self.assertNotContainsRe(log, 'revno: 1\n')
 
810
        self.assertContainsRe(log, 'revno: 2\n')
 
811
        self.assertNotContainsRe(log, 'revno: 3\n')
 
812
        self.assertContainsRe(log, 'revno: 3.1.1\n')
 
813
        self.assertContainsRe(log, 'revno: 4\n')
 
814
        log = self.run_bzr('log file3')[0]
 
815
        self.assertNotContainsRe(log, 'revno: 1\n')
 
816
        self.assertNotContainsRe(log, 'revno: 2\n')
 
817
        self.assertContainsRe(log, 'revno: 3\n')
 
818
        self.assertNotContainsRe(log, 'revno: 3.1.1\n')
 
819
        self.assertNotContainsRe(log, 'revno: 4\n')
 
820
        log = self.run_bzr('log -r3.1.1 file2')[0]
 
821
        self.assertNotContainsRe(log, 'revno: 1\n')
 
822
        self.assertNotContainsRe(log, 'revno: 2\n')
 
823
        self.assertNotContainsRe(log, 'revno: 3\n')
 
824
        self.assertContainsRe(log, 'revno: 3.1.1\n')
 
825
        self.assertNotContainsRe(log, 'revno: 4\n')
 
826
        log = self.run_bzr('log -r4 file2')[0]
 
827
        self.assertNotContainsRe(log, 'revno: 1\n')
 
828
        self.assertNotContainsRe(log, 'revno: 2\n')
 
829
        self.assertNotContainsRe(log, 'revno: 3\n')
 
830
        self.assertContainsRe(log, 'revno: 3.1.1\n')
 
831
        self.assertContainsRe(log, 'revno: 4\n')
 
832
        log = self.run_bzr('log -r3.. file2')[0]
 
833
        self.assertNotContainsRe(log, 'revno: 1\n')
 
834
        self.assertNotContainsRe(log, 'revno: 2\n')
 
835
        self.assertNotContainsRe(log, 'revno: 3\n')
 
836
        self.assertContainsRe(log, 'revno: 3.1.1\n')
 
837
        self.assertContainsRe(log, 'revno: 4\n')
 
838
        log = self.run_bzr('log -r..3 file2')[0]
 
839
        self.assertNotContainsRe(log, 'revno: 1\n')
 
840
        self.assertContainsRe(log, 'revno: 2\n')
 
841
        self.assertNotContainsRe(log, 'revno: 3\n')
 
842
        self.assertNotContainsRe(log, 'revno: 3.1.1\n')
 
843
        self.assertNotContainsRe(log, 'revno: 4\n')
 
844
 
 
845
    def test_log_file_historical_missing(self):
 
846
        # Check logging a deleted file gives an error if the
 
847
        # file isn't found at the end or start of the revision range
 
848
        self.prepare_tree(complex=True)
 
849
        err_msg = "Path unknown at end or start of revision range: file2"
 
850
        err = self.run_bzr('log file2', retcode=3)[1]
 
851
        self.assertContainsRe(err, err_msg)
 
852
 
 
853
    def test_log_file_historical_end(self):
 
854
        # Check logging a deleted file is ok if the file existed
 
855
        # at the end the revision range
 
856
        self.prepare_tree(complex=True)
 
857
        log, err = self.run_bzr('log -r..4 file2')
 
858
        self.assertEquals('', err)
 
859
        self.assertNotContainsRe(log, 'revno: 1\n')
 
860
        self.assertContainsRe(log, 'revno: 2\n')
 
861
        self.assertNotContainsRe(log, 'revno: 3\n')
 
862
        self.assertContainsRe(log, 'revno: 3.1.1\n')
 
863
        self.assertContainsRe(log, 'revno: 4\n')
 
864
 
 
865
    def test_log_file_historical_start(self):
 
866
        # Check logging a deleted file is ok if the file existed
 
867
        # at the start of the revision range
 
868
        self.prepare_tree(complex=True)
 
869
        log, err = self.run_bzr('log file1')
 
870
        self.assertEquals('', err)
 
871
        self.assertContainsRe(log, 'revno: 1\n')
 
872
        self.assertNotContainsRe(log, 'revno: 2\n')
 
873
        self.assertNotContainsRe(log, 'revno: 3\n')
 
874
        self.assertNotContainsRe(log, 'revno: 3.1.1\n')
 
875
        self.assertNotContainsRe(log, 'revno: 4\n')
 
876
 
 
877
    def test_log_file_renamed(self):
 
878
        """File matched against revision range, not current tree."""
 
879
        self.prepare_tree(complex=True)
 
880
 
 
881
        # Check logging a renamed file gives an error by default
 
882
        err_msg = "Path unknown at end or start of revision range: file3"
 
883
        err = self.run_bzr('log file3', retcode=3)[1]
 
884
        self.assertContainsRe(err, err_msg)
 
885
 
 
886
        # Check we can see a renamed file if we give the right end revision
 
887
        log, err = self.run_bzr('log -r..4 file3')
 
888
        self.assertEquals('', err)
 
889
        self.assertNotContainsRe(log, 'revno: 1\n')
 
890
        self.assertNotContainsRe(log, 'revno: 2\n')
 
891
        self.assertContainsRe(log, 'revno: 3\n')
 
892
        self.assertNotContainsRe(log, 'revno: 3.1.1\n')
 
893
        self.assertNotContainsRe(log, 'revno: 4\n')
 
894
 
 
895
    def test_line_log_file(self):
 
896
        """The line log for a file should only list relevant mainline revs"""
 
897
        # Note: this also implicitly  covers the short logging case.
 
898
        # We test using --line in preference to --short because matching
 
899
        # revnos in the output of --line is more reliable.
 
900
        self.prepare_tree()
 
901
 
 
902
        # full history of file1
 
903
        log = self.run_bzr('log --line file1')[0]
 
904
        self.assertContainsRe(log, '^1:', re.MULTILINE)
 
905
        self.assertNotContainsRe(log, '^2:', re.MULTILINE)
 
906
        self.assertNotContainsRe(log, '^3:', re.MULTILINE)
 
907
        self.assertNotContainsRe(log, '^3.1.1:', re.MULTILINE)
 
908
        self.assertNotContainsRe(log, '^4:', re.MULTILINE)
 
909
 
 
910
        # full history of file2
 
911
        log = self.run_bzr('log --line file2')[0]
 
912
        self.assertNotContainsRe(log, '^1:', re.MULTILINE)
 
913
        self.assertContainsRe(log, '^2:', re.MULTILINE)
 
914
        self.assertNotContainsRe(log, '^3:', re.MULTILINE)
 
915
        self.assertNotContainsRe(log, '^3.1.1:', re.MULTILINE)
 
916
        self.assertContainsRe(log, '^4:', re.MULTILINE)
 
917
 
 
918
        # full history of file3
 
919
        log = self.run_bzr('log --line file3')[0]
 
920
        self.assertNotContainsRe(log, '^1:', re.MULTILINE)
 
921
        self.assertNotContainsRe(log, '^2:', re.MULTILINE)
 
922
        self.assertContainsRe(log, '^3:', re.MULTILINE)
 
923
        self.assertNotContainsRe(log, '^3.1.1:', re.MULTILINE)
 
924
        self.assertNotContainsRe(log, '^4:', re.MULTILINE)
 
925
 
 
926
        # file in a merge revision
 
927
        log = self.run_bzr('log --line -r3.1.1 file2')[0]
 
928
        self.assertNotContainsRe(log, '^1:', re.MULTILINE)
 
929
        self.assertNotContainsRe(log, '^2:', re.MULTILINE)
 
930
        self.assertNotContainsRe(log, '^3:', re.MULTILINE)
 
931
        self.assertContainsRe(log, '^3.1.1:', re.MULTILINE)
 
932
        self.assertNotContainsRe(log, '^4:', re.MULTILINE)
 
933
 
 
934
        # file in a mainline revision
 
935
        log = self.run_bzr('log --line -r4 file2')[0]
 
936
        self.assertNotContainsRe(log, '^1:', re.MULTILINE)
 
937
        self.assertNotContainsRe(log, '^2:', re.MULTILINE)
 
938
        self.assertNotContainsRe(log, '^3:', re.MULTILINE)
 
939
        self.assertNotContainsRe(log, '^3.1.1:', re.MULTILINE)
 
940
        self.assertContainsRe(log, '^4:', re.MULTILINE)
 
941
 
 
942
        # file since a revision
 
943
        log = self.run_bzr('log --line -r3.. file2')[0]
 
944
        self.assertNotContainsRe(log, '^1:', re.MULTILINE)
 
945
        self.assertNotContainsRe(log, '^2:', re.MULTILINE)
 
946
        self.assertNotContainsRe(log, '^3:', re.MULTILINE)
 
947
        self.assertNotContainsRe(log, '^3.1.1:', re.MULTILINE)
 
948
        self.assertContainsRe(log, '^4:', re.MULTILINE)
 
949
 
 
950
        # file up to a revision
 
951
        log = self.run_bzr('log --line -r..3 file2')[0]
 
952
        self.assertNotContainsRe(log, '^1:', re.MULTILINE)
 
953
        self.assertContainsRe(log, '^2:', re.MULTILINE)
 
954
        self.assertNotContainsRe(log, '^3:', re.MULTILINE)
 
955
        self.assertNotContainsRe(log, '^3.1.1:', re.MULTILINE)
 
956
        self.assertNotContainsRe(log, '^4:', re.MULTILINE)