/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/tests/test_log.py

  • Committer: Jelmer Vernooij
  • Date: 2020-04-05 19:11:34 UTC
  • mto: (7490.7.16 work)
  • mto: This revision was merged to the branch mainline in revision 7501.
  • Revision ID: jelmer@jelmer.uk-20200405191134-0aebh8ikiwygxma5
Populate the .gitignore file.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005-2013, 2016 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
import os
18
 
from cStringIO import StringIO
 
18
import re
19
19
 
20
 
from bzrlib import (
 
20
from .. import (
21
21
    branchbuilder,
22
22
    errors,
23
23
    log,
24
24
    registry,
25
25
    revision,
26
26
    revisionspec,
27
 
    symbol_versioning,
28
27
    tests,
 
28
    gpg,
 
29
    trace,
 
30
    )
 
31
from ..sixish import (
 
32
    BytesIO,
 
33
    StringIO,
 
34
    unichr,
29
35
    )
30
36
 
31
37
 
38
44
        stamp is incremented at each commit.
39
45
        """
40
46
        if getattr(self, 'timestamp', None) is None:
41
 
            self.timestamp = 1132617600 # Mon 2005-11-22 00:00:00 +0000
 
47
            self.timestamp = 1132617600  # Mon 2005-11-22 00:00:00 +0000
42
48
        else:
43
 
            self.timestamp += 1 # 1 second between each commit
 
49
            self.timestamp += 1  # 1 second between each commit
44
50
        kwargs.setdefault('timestamp', self.timestamp)
45
 
        kwargs.setdefault('timezone', 0) # UTC
 
51
        kwargs.setdefault('timezone', 0)  # UTC
46
52
        kwargs.setdefault('committer', 'Joe Foo <joe@foo.com>')
47
53
 
48
54
        return wt.commit(message, **kwargs)
88
94
        """Helper method for LogFormatter tests"""
89
95
        b = wt.branch
90
96
        b.nick = 'test'
91
 
        self.build_tree_contents([('a', 'hello moto\n')])
92
 
        self.wt_commit(wt, 'simple log message', rev_id='a1')
93
 
        self.build_tree_contents([('b', 'goodbye\n')])
 
97
        self.build_tree_contents([('a', b'hello moto\n')])
 
98
        self.wt_commit(wt, 'simple log message', rev_id=b'a1')
 
99
        self.build_tree_contents([('b', b'goodbye\n')])
94
100
        wt.add('b')
95
 
        self.wt_commit(wt, 'multiline\nlog\nmessage\n', rev_id='a2')
 
101
        self.wt_commit(wt, 'multiline\nlog\nmessage\n', rev_id=b'a2')
96
102
 
97
 
        self.build_tree_contents([('c', 'just another manic monday\n')])
 
103
        self.build_tree_contents([('c', b'just another manic monday\n')])
98
104
        wt.add('c')
99
 
        self.wt_commit(wt, 'single line with trailing newline\n', rev_id='a3')
 
105
        self.wt_commit(wt, 'single line with trailing newline\n', rev_id=b'a3')
100
106
        return b
101
107
 
102
108
    def _prepare_tree_with_merges(self, with_tags=False):
104
110
        wt.lock_write()
105
111
        self.addCleanup(wt.unlock)
106
112
        wt.add('')
107
 
        self.wt_commit(wt, 'rev-1', rev_id='rev-1')
108
 
        self.wt_commit(wt, 'rev-merged', rev_id='rev-2a')
109
 
        wt.set_parent_ids(['rev-1', 'rev-2a'])
110
 
        wt.branch.set_last_revision_info(1, 'rev-1')
111
 
        self.wt_commit(wt, 'rev-2', rev_id='rev-2b')
 
113
        self.wt_commit(wt, 'rev-1', rev_id=b'rev-1')
 
114
        self.wt_commit(wt, 'rev-merged', rev_id=b'rev-2a')
 
115
        wt.set_parent_ids([b'rev-1', b'rev-2a'])
 
116
        wt.branch.set_last_revision_info(1, b'rev-1')
 
117
        self.wt_commit(wt, 'rev-2', rev_id=b'rev-2b')
112
118
        if with_tags:
113
119
            branch = wt.branch
114
 
            branch.tags.set_tag('v0.2', 'rev-2b')
115
 
            self.wt_commit(wt, 'rev-3', rev_id='rev-3')
116
 
            branch.tags.set_tag('v1.0rc1', 'rev-3')
117
 
            branch.tags.set_tag('v1.0', 'rev-3')
 
120
            branch.tags.set_tag('v0.2', b'rev-2b')
 
121
            self.wt_commit(wt, 'rev-3', rev_id=b'rev-3')
 
122
            branch.tags.set_tag('v1.0rc1', b'rev-3')
 
123
            branch.tags.set_tag('v1.0', b'rev-3')
118
124
        return wt
119
125
 
 
126
 
120
127
class LogCatcher(log.LogFormatter):
121
128
    """Pull log messages into a list rather than displaying them.
122
129
 
151
158
            # By default we expect an empty list
152
159
            expected = kw.get(n, [])
153
160
            # strip out only the path components
154
 
            got = [x[0] for x in getattr(delta, n)]
 
161
            got = [x.path[1] or x.path[0] for x in getattr(delta, n)]
155
162
            self.assertEqual(expected, got)
156
163
 
157
164
    def assertInvalidRevisonNumber(self, br, start, end):
173
180
        self.assertInvalidRevisonNumber(b, 2, 1)
174
181
        self.assertInvalidRevisonNumber(b, 1, 2)
175
182
        self.assertInvalidRevisonNumber(b, 0, 2)
176
 
        self.assertInvalidRevisonNumber(b, 1, 0)
177
183
        self.assertInvalidRevisonNumber(b, -1, 1)
178
184
        self.assertInvalidRevisonNumber(b, 1, -1)
 
185
        self.assertInvalidRevisonNumber(b, 1, 0)
179
186
 
180
187
    def test_empty_branch(self):
181
188
        wt = self.make_branch_and_tree('.')
216
223
 
217
224
    def test_commit_message_with_control_chars(self):
218
225
        wt = self.make_branch_and_tree('.')
219
 
        msg = u"All 8-bit chars: " +  ''.join([unichr(x) for x in range(256)])
 
226
        msg = u"All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
220
227
        msg = msg.replace(u'\r', u'\n')
221
228
        wt.commit(msg)
222
229
        lf = LogCatcher()
250
257
        wt.commit(message='add file1 and file2')
251
258
        self.run_bzr('branch parent child')
252
259
        os.unlink('child/file1')
253
 
        file('child/file2', 'wb').write('hello\n')
 
260
        with open('child/file2', 'wb') as f:
 
261
            f.write(b'hello\n')
254
262
        self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
255
 
            'child'])
 
263
                      'child'])
256
264
        os.chdir('parent')
257
265
        self.run_bzr('merge ../child')
258
266
        wt.commit('merge child branch')
280
288
        self.assertEqual('add file1 and file2', logentry.rev.message)
281
289
        self.checkDelta(logentry.delta, added=['file1', 'file2'])
282
290
 
 
291
    def test_bug_842695_log_restricted_to_dir(self):
 
292
        # Comments here indicate revision numbers in trunk  # VVVVV
 
293
        trunk = self.make_branch_and_tree('this')
 
294
        trunk.commit('initial trunk')                       # 1
 
295
        adder = trunk.controldir.sprout('adder').open_workingtree()
 
296
        merger = trunk.controldir.sprout('merger').open_workingtree()
 
297
        self.build_tree_contents([
 
298
            ('adder/dir/',),
 
299
            ('adder/dir/file', b'foo'),
 
300
            ])
 
301
        adder.add(['dir', 'dir/file'])
 
302
        adder.commit('added dir')                           # 1.1.1
 
303
        trunk.merge_from_branch(adder.branch)
 
304
        trunk.commit('merged adder into trunk')             # 2
 
305
        merger.merge_from_branch(trunk.branch)
 
306
        merger.commit('merged trunk into merger')           # 1.2.1
 
307
        # Commits are processed in increments of 200 revisions, so
 
308
        # make sure the two merges into trunk are in different chunks.
 
309
        for i in range(200):
 
310
            trunk.commit('intermediate commit %d' % i)      # 3-202
 
311
        trunk.merge_from_branch(merger.branch)
 
312
        trunk.commit('merged merger into trunk')            # 203
 
313
        file_id = trunk.path2id('dir')
 
314
        lf = LogCatcher()
 
315
        lf.supports_merge_revisions = True
 
316
        log.show_log(trunk.branch, lf, file_id)
 
317
        try:
 
318
            self.assertEqual(['2', '1.1.1'], [r.revno for r in lf.revisions])
 
319
        except AssertionError:
 
320
            raise tests.KnownFailure("bug #842695")
 
321
 
 
322
 
 
323
class TestFormatSignatureValidity(tests.TestCaseWithTransport):
 
324
 
 
325
    def verify_revision_signature(self, revid, gpg_strategy):
 
326
        return (gpg.SIGNATURE_VALID,
 
327
                u'UTF8 Test \xa1\xb1\xc1\xd1\xe1\xf1 <jrandom@example.com>')
 
328
 
 
329
    def test_format_signature_validity_utf(self):
 
330
        """Check that GPG signatures containing UTF-8 names are formatted
 
331
        correctly."""
 
332
        wt = self.make_branch_and_tree('.')
 
333
        revid = wt.commit('empty commit')
 
334
        repo = wt.branch.repository
 
335
        # Monkey patch out checking if this rev is actually signed, since we
 
336
        # can't sign it without a heavier TestCase and LoopbackGPGStrategy
 
337
        # doesn't care anyways.
 
338
        self.overrideAttr(repo, 'verify_revision_signature',
 
339
                          self.verify_revision_signature)
 
340
        out = log.format_signature_validity(revid, wt.branch)
 
341
        self.assertEqual(
 
342
            u'valid signature from UTF8 Test \xa1\xb1\xc1\xd1\xe1\xf1 <jrandom@example.com>',
 
343
            out)
 
344
 
283
345
 
284
346
class TestShortLogFormatter(TestCaseForLogFormatter):
285
347
 
286
348
    def test_trailing_newlines(self):
287
349
        wt = self.make_branch_and_tree('.')
288
350
        b = self.make_commits_with_trailing_newlines(wt)
289
 
        self.assertFormatterResult("""\
 
351
        self.assertFormatterResult(b"""\
290
352
    3 Joe Foo\t2005-11-22
291
353
      single line with trailing newline
292
354
 
299
361
      simple log message
300
362
 
301
363
""",
302
 
            b, log.ShortLogFormatter)
 
364
                                   b, log.ShortLogFormatter)
303
365
 
304
366
    def test_short_log_with_merges(self):
305
367
        wt = self._prepare_tree_with_merges()
306
 
        self.assertFormatterResult("""\
 
368
        self.assertFormatterResult(b"""\
307
369
    2 Joe Foo\t2005-11-22 [merge]
308
370
      rev-2
309
371
 
311
373
      rev-1
312
374
 
313
375
""",
314
 
            wt.branch, log.ShortLogFormatter)
 
376
                                   wt.branch, log.ShortLogFormatter)
315
377
 
316
378
    def test_short_log_with_merges_and_advice(self):
317
379
        wt = self._prepare_tree_with_merges()
318
 
        self.assertFormatterResult("""\
 
380
        self.assertFormatterResult(b"""\
319
381
    2 Joe Foo\t2005-11-22 [merge]
320
382
      rev-2
321
383
 
322
384
    1 Joe Foo\t2005-11-22
323
385
      rev-1
324
386
 
325
 
Use --include-merges or -n0 to see merged revisions.
 
387
Use --include-merged or -n0 to see merged revisions.
326
388
""",
327
 
            wt.branch, log.ShortLogFormatter,
328
 
            formatter_kwargs=dict(show_advice=True))
 
389
                                   wt.branch, log.ShortLogFormatter,
 
390
                                   formatter_kwargs=dict(show_advice=True))
329
391
 
330
392
    def test_short_log_with_merges_and_range(self):
331
393
        wt = self._prepare_tree_with_merges()
332
 
        self.wt_commit(wt, 'rev-3a', rev_id='rev-3a')
333
 
        wt.branch.set_last_revision_info(2, 'rev-2b')
334
 
        wt.set_parent_ids(['rev-2b', 'rev-3a'])
335
 
        self.wt_commit(wt, 'rev-3b', rev_id='rev-3b')
336
 
        self.assertFormatterResult("""\
 
394
        self.wt_commit(wt, 'rev-3a', rev_id=b'rev-3a')
 
395
        wt.branch.set_last_revision_info(2, b'rev-2b')
 
396
        wt.set_parent_ids([b'rev-2b', b'rev-3a'])
 
397
        self.wt_commit(wt, 'rev-3b', rev_id=b'rev-3b')
 
398
        self.assertFormatterResult(b"""\
337
399
    3 Joe Foo\t2005-11-22 [merge]
338
400
      rev-3b
339
401
 
341
403
      rev-2
342
404
 
343
405
""",
344
 
            wt.branch, log.ShortLogFormatter,
345
 
            show_log_kwargs=dict(start_revision=2, end_revision=3))
 
406
                                   wt.branch, log.ShortLogFormatter,
 
407
                                   show_log_kwargs=dict(start_revision=2, end_revision=3))
346
408
 
347
409
    def test_short_log_with_tags(self):
348
410
        wt = self._prepare_tree_with_merges(with_tags=True)
349
 
        self.assertFormatterResult("""\
 
411
        self.assertFormatterResult(b"""\
350
412
    3 Joe Foo\t2005-11-22 {v1.0, v1.0rc1}
351
413
      rev-3
352
414
 
357
419
      rev-1
358
420
 
359
421
""",
360
 
            wt.branch, log.ShortLogFormatter)
 
422
                                   wt.branch, log.ShortLogFormatter)
361
423
 
362
424
    def test_short_log_single_merge_revision(self):
363
425
        wt = self._prepare_tree_with_merges()
364
426
        revspec = revisionspec.RevisionSpec.from_string('1.1.1')
365
427
        rev = revspec.in_history(wt.branch)
366
 
        self.assertFormatterResult("""\
 
428
        self.assertFormatterResult(b"""\
367
429
      1.1.1 Joe Foo\t2005-11-22
368
430
            rev-merged
369
431
 
370
432
""",
371
 
            wt.branch, log.ShortLogFormatter,
372
 
            show_log_kwargs=dict(start_revision=rev, end_revision=rev))
 
433
                                   wt.branch, log.ShortLogFormatter,
 
434
                                   show_log_kwargs=dict(start_revision=rev, end_revision=rev))
 
435
 
 
436
    def test_show_ids(self):
 
437
        wt = self.make_branch_and_tree('parent')
 
438
        self.build_tree(['parent/f1', 'parent/f2'])
 
439
        wt.add(['f1', 'f2'])
 
440
        self.wt_commit(wt, 'first post', rev_id=b'a')
 
441
        child_wt = wt.controldir.sprout('child').open_workingtree()
 
442
        self.wt_commit(child_wt, 'branch 1 changes', rev_id=b'b')
 
443
        wt.merge_from_branch(child_wt.branch)
 
444
        self.wt_commit(wt, 'merge branch 1', rev_id=b'c')
 
445
        self.assertFormatterResult(b"""\
 
446
    2 Joe Foo\t2005-11-22 [merge]
 
447
      revision-id:c
 
448
      merge branch 1
 
449
 
 
450
          1.1.1 Joe Foo\t2005-11-22
 
451
                revision-id:b
 
452
                branch 1 changes
 
453
 
 
454
    1 Joe Foo\t2005-11-22
 
455
      revision-id:a
 
456
      first post
 
457
 
 
458
""",
 
459
                                   wt.branch, log.ShortLogFormatter,
 
460
                                   formatter_kwargs=dict(levels=0, show_ids=True))
373
461
 
374
462
 
375
463
class TestShortLogFormatterWithMergeRevisions(TestCaseForLogFormatter):
379
467
        # Note that the 1.1.1 indenting is in fact correct given that
380
468
        # the revision numbers are right justified within 5 characters
381
469
        # for mainline revnos and 9 characters for dotted revnos.
382
 
        self.assertFormatterResult("""\
 
470
        self.assertFormatterResult(b"""\
383
471
    2 Joe Foo\t2005-11-22 [merge]
384
472
      rev-2
385
473
 
390
478
      rev-1
391
479
 
392
480
""",
393
 
            wt.branch, log.ShortLogFormatter,
394
 
            formatter_kwargs=dict(levels=0))
 
481
                                   wt.branch, log.ShortLogFormatter,
 
482
                                   formatter_kwargs=dict(levels=0))
395
483
 
396
484
    def test_short_merge_revs_log_single_merge_revision(self):
397
485
        wt = self._prepare_tree_with_merges()
398
486
        revspec = revisionspec.RevisionSpec.from_string('1.1.1')
399
487
        rev = revspec.in_history(wt.branch)
400
 
        self.assertFormatterResult("""\
 
488
        self.assertFormatterResult(b"""\
401
489
      1.1.1 Joe Foo\t2005-11-22
402
490
            rev-merged
403
491
 
404
492
""",
405
 
            wt.branch, log.ShortLogFormatter,
406
 
            formatter_kwargs=dict(levels=0),
407
 
            show_log_kwargs=dict(start_revision=rev, end_revision=rev))
 
493
                                   wt.branch, log.ShortLogFormatter,
 
494
                                   formatter_kwargs=dict(levels=0),
 
495
                                   show_log_kwargs=dict(start_revision=rev, end_revision=rev))
408
496
 
409
497
 
410
498
class TestLongLogFormatter(TestCaseForLogFormatter):
415
503
        bug #4676
416
504
        """
417
505
        wt = self.make_standard_commit('test_verbose_log', authors=[])
418
 
        self.assertFormatterResult('''\
 
506
        self.assertFormatterResult(b'''\
419
507
------------------------------------------------------------
420
508
revno: 1
421
509
committer: Lorem Ipsum <test@example.com>
426
514
added:
427
515
  a
428
516
''',
429
 
            wt.branch, log.LongLogFormatter,
430
 
            show_log_kwargs=dict(verbose=True))
 
517
                                   wt.branch, log.LongLogFormatter,
 
518
                                   show_log_kwargs=dict(verbose=True))
431
519
 
432
520
    def test_merges_are_indented_by_level(self):
433
521
        wt = self.make_branch_and_tree('parent')
434
522
        self.wt_commit(wt, 'first post')
435
 
        child_wt = wt.bzrdir.sprout('child').open_workingtree()
 
523
        child_wt = wt.controldir.sprout('child').open_workingtree()
436
524
        self.wt_commit(child_wt, 'branch 1')
437
 
        smallerchild_wt = wt.bzrdir.sprout('smallerchild').open_workingtree()
 
525
        smallerchild_wt = wt.controldir.sprout(
 
526
            'smallerchild').open_workingtree()
438
527
        self.wt_commit(smallerchild_wt, 'branch 2')
439
528
        child_wt.merge_from_branch(smallerchild_wt.branch)
440
529
        self.wt_commit(child_wt, 'merge branch 2')
441
530
        wt.merge_from_branch(child_wt.branch)
442
531
        self.wt_commit(wt, 'merge branch 1')
443
 
        self.assertFormatterResult("""\
 
532
        self.assertFormatterResult(b"""\
444
533
------------------------------------------------------------
445
534
revno: 2 [merge]
446
535
committer: Joe Foo <joe@foo.com>
477
566
message:
478
567
  first post
479
568
""",
480
 
            wt.branch, log.LongLogFormatter,
481
 
            formatter_kwargs=dict(levels=0),
482
 
            show_log_kwargs=dict(verbose=True))
 
569
                                   wt.branch, log.LongLogFormatter,
 
570
                                   formatter_kwargs=dict(levels=0),
 
571
                                   show_log_kwargs=dict(verbose=True))
483
572
 
484
573
    def test_verbose_merge_revisions_contain_deltas(self):
485
574
        wt = self.make_branch_and_tree('parent')
486
575
        self.build_tree(['parent/f1', 'parent/f2'])
487
 
        wt.add(['f1','f2'])
 
576
        wt.add(['f1', 'f2'])
488
577
        self.wt_commit(wt, 'first post')
489
 
        child_wt = wt.bzrdir.sprout('child').open_workingtree()
 
578
        child_wt = wt.controldir.sprout('child').open_workingtree()
490
579
        os.unlink('child/f1')
491
 
        self.build_tree_contents([('child/f2', 'hello\n')])
 
580
        self.build_tree_contents([('child/f2', b'hello\n')])
492
581
        self.wt_commit(child_wt, 'removed f1 and modified f2')
493
582
        wt.merge_from_branch(child_wt.branch)
494
583
        self.wt_commit(wt, 'merge branch 1')
495
 
        self.assertFormatterResult("""\
 
584
        self.assertFormatterResult(b"""\
496
585
------------------------------------------------------------
497
586
revno: 2 [merge]
498
587
committer: Joe Foo <joe@foo.com>
526
615
  f1
527
616
  f2
528
617
""",
529
 
            wt.branch, log.LongLogFormatter,
530
 
            formatter_kwargs=dict(levels=0),
531
 
            show_log_kwargs=dict(verbose=True))
 
618
                                   wt.branch, log.LongLogFormatter,
 
619
                                   formatter_kwargs=dict(levels=0),
 
620
                                   show_log_kwargs=dict(verbose=True))
532
621
 
533
622
    def test_trailing_newlines(self):
534
623
        wt = self.make_branch_and_tree('.')
535
624
        b = self.make_commits_with_trailing_newlines(wt)
536
 
        self.assertFormatterResult("""\
 
625
        self.assertFormatterResult(b"""\
537
626
------------------------------------------------------------
538
627
revno: 3
539
628
committer: Joe Foo <joe@foo.com>
558
647
message:
559
648
  simple log message
560
649
""",
561
 
        b, log.LongLogFormatter)
 
650
                                   b, log.LongLogFormatter)
562
651
 
563
652
    def test_author_in_log(self):
564
653
        """Log includes the author name if it's set in
565
654
        the revision properties
566
655
        """
567
656
        wt = self.make_standard_commit('test_author_log',
568
 
            authors=['John Doe <jdoe@example.com>',
569
 
                     'Jane Rey <jrey@example.com>'])
570
 
        self.assertFormatterResult("""\
 
657
                                       authors=['John Doe <jdoe@example.com>',
 
658
                                                'Jane Rey <jrey@example.com>'])
 
659
        self.assertFormatterResult(b"""\
571
660
------------------------------------------------------------
572
661
revno: 1
573
662
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
577
666
message:
578
667
  add a
579
668
""",
580
 
        wt.branch, log.LongLogFormatter)
 
669
                                   wt.branch, log.LongLogFormatter)
581
670
 
582
671
    def test_properties_in_log(self):
583
672
        """Log includes the custom properties returned by the registered
584
673
        handlers.
585
674
        """
586
675
        wt = self.make_standard_commit('test_properties_in_log')
 
676
 
587
677
        def trivial_custom_prop_handler(revision):
588
 
            return {'test_prop':'test_value'}
 
678
            return {'test_prop': 'test_value'}
589
679
 
590
680
        # Cleaned up in setUp()
591
681
        log.properties_handler_registry.register(
592
682
            'trivial_custom_prop_handler',
593
683
            trivial_custom_prop_handler)
594
 
        self.assertFormatterResult("""\
 
684
        self.assertFormatterResult(b"""\
595
685
------------------------------------------------------------
596
686
revno: 1
597
687
test_prop: test_value
602
692
message:
603
693
  add a
604
694
""",
605
 
            wt.branch, log.LongLogFormatter)
 
695
                                   wt.branch, log.LongLogFormatter)
606
696
 
607
697
    def test_properties_in_short_log(self):
608
698
        """Log includes the custom properties returned by the registered
609
699
        handlers.
610
700
        """
611
701
        wt = self.make_standard_commit('test_properties_in_short_log')
 
702
 
612
703
        def trivial_custom_prop_handler(revision):
613
 
            return {'test_prop':'test_value'}
 
704
            return {'test_prop': 'test_value'}
614
705
 
615
706
        log.properties_handler_registry.register(
616
707
            'trivial_custom_prop_handler',
617
708
            trivial_custom_prop_handler)
618
 
        self.assertFormatterResult("""\
 
709
        self.assertFormatterResult(b"""\
619
710
    1 John Doe\t2005-11-22
620
711
      test_prop: test_value
621
712
      add a
622
713
 
623
714
""",
624
 
            wt.branch, log.ShortLogFormatter)
 
715
                                   wt.branch, log.ShortLogFormatter)
625
716
 
626
717
    def test_error_in_properties_handler(self):
627
718
        """Log includes the custom properties returned by the registered
628
719
        handlers.
629
720
        """
630
721
        wt = self.make_standard_commit('error_in_properties_handler',
631
 
            revprops={'first_prop':'first_value'})
 
722
                                       revprops={u'first_prop': 'first_value'})
632
723
        sio = self.make_utf8_encoded_stringio()
633
724
        formatter = log.LongLogFormatter(to_file=sio)
 
725
 
634
726
        def trivial_custom_prop_handler(revision):
635
 
            raise StandardError("a test error")
 
727
            raise Exception("a test error")
636
728
 
637
729
        log.properties_handler_registry.register(
638
730
            'trivial_custom_prop_handler',
639
731
            trivial_custom_prop_handler)
640
 
        self.assertRaises(StandardError, log.show_log, wt.branch, formatter,)
 
732
        log.show_log(wt.branch, formatter)
 
733
        self.assertContainsRe(
 
734
            sio.getvalue(), b'brz: ERROR: Exception: a test error')
641
735
 
642
736
    def test_properties_handler_bad_argument(self):
643
737
        wt = self.make_standard_commit('bad_argument',
644
 
              revprops={'a_prop':'test_value'})
 
738
                                       revprops={u'a_prop': 'test_value'})
645
739
        sio = self.make_utf8_encoded_stringio()
646
740
        formatter = log.LongLogFormatter(to_file=sio)
 
741
 
647
742
        def bad_argument_prop_handler(revision):
648
 
            return {'custom_prop_name':revision.properties['a_prop']}
 
743
            return {'custom_prop_name': revision.properties['a_prop']}
649
744
 
650
745
        log.properties_handler_registry.register(
651
746
            'bad_argument_prop_handler',
656
751
 
657
752
        revision = wt.branch.repository.get_revision(wt.branch.last_revision())
658
753
        formatter.show_properties(revision, '')
659
 
        self.assertEqualDiff('''custom_prop_name: test_value\n''',
 
754
        self.assertEqualDiff(b'custom_prop_name: test_value\n',
660
755
                             sio.getvalue())
661
756
 
 
757
    def test_show_ids(self):
 
758
        wt = self.make_branch_and_tree('parent')
 
759
        self.build_tree(['parent/f1', 'parent/f2'])
 
760
        wt.add(['f1', 'f2'])
 
761
        self.wt_commit(wt, 'first post', rev_id=b'a')
 
762
        child_wt = wt.controldir.sprout('child').open_workingtree()
 
763
        self.wt_commit(child_wt, 'branch 1 changes', rev_id=b'b')
 
764
        wt.merge_from_branch(child_wt.branch)
 
765
        self.wt_commit(wt, 'merge branch 1', rev_id=b'c')
 
766
        self.assertFormatterResult(b"""\
 
767
------------------------------------------------------------
 
768
revno: 2 [merge]
 
769
revision-id: c
 
770
parent: a
 
771
parent: b
 
772
committer: Joe Foo <joe@foo.com>
 
773
branch nick: parent
 
774
timestamp: Tue 2005-11-22 00:00:02 +0000
 
775
message:
 
776
  merge branch 1
 
777
    ------------------------------------------------------------
 
778
    revno: 1.1.1
 
779
    revision-id: b
 
780
    parent: a
 
781
    committer: Joe Foo <joe@foo.com>
 
782
    branch nick: child
 
783
    timestamp: Tue 2005-11-22 00:00:01 +0000
 
784
    message:
 
785
      branch 1 changes
 
786
------------------------------------------------------------
 
787
revno: 1
 
788
revision-id: a
 
789
committer: Joe Foo <joe@foo.com>
 
790
branch nick: parent
 
791
timestamp: Tue 2005-11-22 00:00:00 +0000
 
792
message:
 
793
  first post
 
794
""",
 
795
                                   wt.branch, log.LongLogFormatter,
 
796
                                   formatter_kwargs=dict(levels=0, show_ids=True))
 
797
 
662
798
 
663
799
class TestLongLogFormatterWithoutMergeRevisions(TestCaseForLogFormatter):
664
800
 
668
804
        bug #4676
669
805
        """
670
806
        wt = self.make_standard_commit('test_long_verbose_log', authors=[])
671
 
        self.assertFormatterResult("""\
 
807
        self.assertFormatterResult(b"""\
672
808
------------------------------------------------------------
673
809
revno: 1
674
810
committer: Lorem Ipsum <test@example.com>
679
815
added:
680
816
  a
681
817
""",
682
 
            wt.branch, log.LongLogFormatter,
683
 
            formatter_kwargs=dict(levels=1),
684
 
            show_log_kwargs=dict(verbose=True))
 
818
                                   wt.branch, log.LongLogFormatter,
 
819
                                   formatter_kwargs=dict(levels=1),
 
820
                                   show_log_kwargs=dict(verbose=True))
685
821
 
686
822
    def test_long_verbose_contain_deltas(self):
687
823
        wt = self.make_branch_and_tree('parent')
688
824
        self.build_tree(['parent/f1', 'parent/f2'])
689
 
        wt.add(['f1','f2'])
 
825
        wt.add(['f1', 'f2'])
690
826
        self.wt_commit(wt, 'first post')
691
 
        child_wt = wt.bzrdir.sprout('child').open_workingtree()
 
827
        child_wt = wt.controldir.sprout('child').open_workingtree()
692
828
        os.unlink('child/f1')
693
 
        self.build_tree_contents([('child/f2', 'hello\n')])
 
829
        self.build_tree_contents([('child/f2', b'hello\n')])
694
830
        self.wt_commit(child_wt, 'removed f1 and modified f2')
695
831
        wt.merge_from_branch(child_wt.branch)
696
832
        self.wt_commit(wt, 'merge branch 1')
697
 
        self.assertFormatterResult("""\
 
833
        self.assertFormatterResult(b"""\
698
834
------------------------------------------------------------
699
835
revno: 2 [merge]
700
836
committer: Joe Foo <joe@foo.com>
717
853
  f1
718
854
  f2
719
855
""",
720
 
            wt.branch, log.LongLogFormatter,
721
 
            formatter_kwargs=dict(levels=1),
722
 
            show_log_kwargs=dict(verbose=True))
 
856
                                   wt.branch, log.LongLogFormatter,
 
857
                                   formatter_kwargs=dict(levels=1),
 
858
                                   show_log_kwargs=dict(verbose=True))
723
859
 
724
860
    def test_long_trailing_newlines(self):
725
861
        wt = self.make_branch_and_tree('.')
726
862
        b = self.make_commits_with_trailing_newlines(wt)
727
 
        self.assertFormatterResult("""\
 
863
        self.assertFormatterResult(b"""\
728
864
------------------------------------------------------------
729
865
revno: 3
730
866
committer: Joe Foo <joe@foo.com>
749
885
message:
750
886
  simple log message
751
887
""",
752
 
        b, log.LongLogFormatter,
753
 
        formatter_kwargs=dict(levels=1))
 
888
                                   b, log.LongLogFormatter,
 
889
                                   formatter_kwargs=dict(levels=1))
754
890
 
755
891
    def test_long_author_in_log(self):
756
892
        """Log includes the author name if it's set in
757
893
        the revision properties
758
894
        """
759
895
        wt = self.make_standard_commit('test_author_log')
760
 
        self.assertFormatterResult("""\
 
896
        self.assertFormatterResult(b"""\
761
897
------------------------------------------------------------
762
898
revno: 1
763
899
author: John Doe <jdoe@example.com>
767
903
message:
768
904
  add a
769
905
""",
770
 
            wt.branch, log.LongLogFormatter,
771
 
            formatter_kwargs=dict(levels=1))
 
906
                                   wt.branch, log.LongLogFormatter,
 
907
                                   formatter_kwargs=dict(levels=1))
772
908
 
773
909
    def test_long_properties_in_log(self):
774
910
        """Log includes the custom properties returned by the registered
775
911
        handlers.
776
912
        """
777
913
        wt = self.make_standard_commit('test_properties_in_log')
 
914
 
778
915
        def trivial_custom_prop_handler(revision):
779
 
            return {'test_prop':'test_value'}
 
916
            return {'test_prop': 'test_value'}
780
917
 
781
918
        log.properties_handler_registry.register(
782
919
            'trivial_custom_prop_handler',
783
920
            trivial_custom_prop_handler)
784
 
        self.assertFormatterResult("""\
 
921
        self.assertFormatterResult(b"""\
785
922
------------------------------------------------------------
786
923
revno: 1
787
924
test_prop: test_value
792
929
message:
793
930
  add a
794
931
""",
795
 
            wt.branch, log.LongLogFormatter,
796
 
            formatter_kwargs=dict(levels=1))
 
932
                                   wt.branch, log.LongLogFormatter,
 
933
                                   formatter_kwargs=dict(levels=1))
797
934
 
798
935
 
799
936
class TestLineLogFormatter(TestCaseForLogFormatter):
804
941
        bug #5162
805
942
        """
806
943
        wt = self.make_standard_commit('test-line-log',
807
 
                committer='Line-Log-Formatter Tester <test@line.log>',
808
 
                authors=[])
809
 
        self.assertFormatterResult("""\
 
944
                                       committer='Line-Log-Formatter Tester <test@line.log>',
 
945
                                       authors=[])
 
946
        self.assertFormatterResult(b"""\
810
947
1: Line-Log-Formatte... 2005-11-22 add a
811
948
""",
812
 
            wt.branch, log.LineLogFormatter)
 
949
                                   wt.branch, log.LineLogFormatter)
813
950
 
814
951
    def test_trailing_newlines(self):
815
952
        wt = self.make_branch_and_tree('.')
816
953
        b = self.make_commits_with_trailing_newlines(wt)
817
 
        self.assertFormatterResult("""\
 
954
        self.assertFormatterResult(b"""\
818
955
3: Joe Foo 2005-11-22 single line with trailing newline
819
956
2: Joe Foo 2005-11-22 multiline
820
957
1: Joe Foo 2005-11-22 simple log message
821
958
""",
822
 
            b, log.LineLogFormatter)
 
959
                                   b, log.LineLogFormatter)
823
960
 
824
961
    def test_line_log_single_merge_revision(self):
825
962
        wt = self._prepare_tree_with_merges()
826
963
        revspec = revisionspec.RevisionSpec.from_string('1.1.1')
827
964
        rev = revspec.in_history(wt.branch)
828
 
        self.assertFormatterResult("""\
 
965
        self.assertFormatterResult(b"""\
829
966
1.1.1: Joe Foo 2005-11-22 rev-merged
830
967
""",
831
 
            wt.branch, log.LineLogFormatter,
832
 
            show_log_kwargs=dict(start_revision=rev, end_revision=rev))
 
968
                                   wt.branch, log.LineLogFormatter,
 
969
                                   show_log_kwargs=dict(start_revision=rev, end_revision=rev))
833
970
 
834
971
    def test_line_log_with_tags(self):
835
972
        wt = self._prepare_tree_with_merges(with_tags=True)
836
 
        self.assertFormatterResult("""\
 
973
        self.assertFormatterResult(b"""\
837
974
3: Joe Foo 2005-11-22 {v1.0, v1.0rc1} rev-3
838
975
2: Joe Foo 2005-11-22 [merge] {v0.2} rev-2
839
976
1: Joe Foo 2005-11-22 rev-1
840
977
""",
841
 
            wt.branch, log.LineLogFormatter)
 
978
                                   wt.branch, log.LineLogFormatter)
842
979
 
843
980
 
844
981
class TestLineLogFormatterWithMergeRevisions(TestCaseForLogFormatter):
849
986
        bug #5162
850
987
        """
851
988
        wt = self.make_standard_commit('test-line-log',
852
 
                committer='Line-Log-Formatter Tester <test@line.log>',
853
 
                authors=[])
854
 
        self.assertFormatterResult("""\
 
989
                                       committer='Line-Log-Formatter Tester <test@line.log>',
 
990
                                       authors=[])
 
991
        self.assertFormatterResult(b"""\
855
992
1: Line-Log-Formatte... 2005-11-22 add a
856
993
""",
857
 
            wt.branch, log.LineLogFormatter)
 
994
                                   wt.branch, log.LineLogFormatter)
858
995
 
859
996
    def test_line_merge_revs_log_single_merge_revision(self):
860
997
        wt = self._prepare_tree_with_merges()
861
998
        revspec = revisionspec.RevisionSpec.from_string('1.1.1')
862
999
        rev = revspec.in_history(wt.branch)
863
 
        self.assertFormatterResult("""\
 
1000
        self.assertFormatterResult(b"""\
864
1001
1.1.1: Joe Foo 2005-11-22 rev-merged
865
1002
""",
866
 
            wt.branch, log.LineLogFormatter,
867
 
            formatter_kwargs=dict(levels=0),
868
 
            show_log_kwargs=dict(start_revision=rev, end_revision=rev))
 
1003
                                   wt.branch, log.LineLogFormatter,
 
1004
                                   formatter_kwargs=dict(levels=0),
 
1005
                                   show_log_kwargs=dict(start_revision=rev, end_revision=rev))
869
1006
 
870
1007
    def test_line_merge_revs_log_with_merges(self):
871
1008
        wt = self._prepare_tree_with_merges()
872
 
        self.assertFormatterResult("""\
 
1009
        self.assertFormatterResult(b"""\
873
1010
2: Joe Foo 2005-11-22 [merge] rev-2
874
1011
  1.1.1: Joe Foo 2005-11-22 rev-merged
875
1012
1: Joe Foo 2005-11-22 rev-1
876
1013
""",
877
 
            wt.branch, log.LineLogFormatter,
878
 
            formatter_kwargs=dict(levels=0))
 
1014
                                   wt.branch, log.LineLogFormatter,
 
1015
                                   formatter_kwargs=dict(levels=0))
879
1016
 
880
1017
 
881
1018
class TestGnuChangelogFormatter(TestCaseForLogFormatter):
882
1019
 
883
1020
    def test_gnu_changelog(self):
884
1021
        wt = self.make_standard_commit('nicky', authors=[])
885
 
        self.assertFormatterResult('''\
 
1022
        self.assertFormatterResult(b'''\
886
1023
2005-11-22  Lorem Ipsum  <test@example.com>
887
1024
 
888
1025
\tadd a
889
1026
 
890
1027
''',
891
 
            wt.branch, log.GnuChangelogLogFormatter)
 
1028
                                   wt.branch, log.GnuChangelogLogFormatter)
892
1029
 
893
1030
    def test_with_authors(self):
894
1031
        wt = self.make_standard_commit('nicky',
895
 
            authors=['Fooa Fooz <foo@example.com>',
896
 
                     'Bari Baro <bar@example.com>'])
897
 
        self.assertFormatterResult('''\
 
1032
                                       authors=['Fooa Fooz <foo@example.com>',
 
1033
                                                'Bari Baro <bar@example.com>'])
 
1034
        self.assertFormatterResult(b'''\
898
1035
2005-11-22  Fooa Fooz  <foo@example.com>
899
1036
 
900
1037
\tadd a
901
1038
 
902
1039
''',
903
 
            wt.branch, log.GnuChangelogLogFormatter)
 
1040
                                   wt.branch, log.GnuChangelogLogFormatter)
904
1041
 
905
1042
    def test_verbose(self):
906
1043
        wt = self.make_standard_commit('nicky')
907
 
        self.assertFormatterResult('''\
 
1044
        self.assertFormatterResult(b'''\
908
1045
2005-11-22  John Doe  <jdoe@example.com>
909
1046
 
910
1047
\t* a:
912
1049
\tadd a
913
1050
 
914
1051
''',
915
 
            wt.branch, log.GnuChangelogLogFormatter,
916
 
            show_log_kwargs=dict(verbose=True))
917
 
 
918
 
class TestGetViewRevisions(tests.TestCaseWithTransport, TestLogMixin):
919
 
 
920
 
    def _get_view_revisions(self, *args, **kwargs):
921
 
        return self.applyDeprecated(symbol_versioning.deprecated_in((2, 2, 0)),
922
 
                                    log.get_view_revisions, *args, **kwargs)
923
 
 
924
 
    def make_tree_with_commits(self):
925
 
        """Create a tree with well-known revision ids"""
926
 
        wt = self.make_branch_and_tree('tree1')
927
 
        self.wt_commit(wt, 'commit one', rev_id='1')
928
 
        self.wt_commit(wt, 'commit two', rev_id='2')
929
 
        self.wt_commit(wt, 'commit three', rev_id='3')
930
 
        mainline_revs = [None, '1', '2', '3']
931
 
        rev_nos = {'1': 1, '2': 2, '3': 3}
932
 
        return mainline_revs, rev_nos, wt
933
 
 
934
 
    def make_tree_with_merges(self):
935
 
        """Create a tree with well-known revision ids and a merge"""
936
 
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
937
 
        tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
938
 
        self.wt_commit(tree2, 'four-a', rev_id='4a')
939
 
        wt.merge_from_branch(tree2.branch)
940
 
        self.wt_commit(wt, 'four-b', rev_id='4b')
941
 
        mainline_revs.append('4b')
942
 
        rev_nos['4b'] = 4
943
 
        # 4a: 3.1.1
944
 
        return mainline_revs, rev_nos, wt
945
 
 
946
 
    def make_branch_with_many_merges(self):
947
 
        """Create a tree with well-known revision ids"""
948
 
        builder = self.make_branch_builder('tree1')
949
 
        builder.start_series()
950
 
        builder.build_snapshot('1', None, [
951
 
            ('add', ('', 'TREE_ROOT', 'directory', '')),
952
 
            ('add', ('f', 'f-id', 'file', '1\n'))])
953
 
        builder.build_snapshot('2', ['1'], [])
954
 
        builder.build_snapshot('3a', ['2'], [
955
 
            ('modify', ('f-id', '1\n2\n3a\n'))])
956
 
        builder.build_snapshot('3b', ['2', '3a'], [
957
 
            ('modify', ('f-id', '1\n2\n3a\n'))])
958
 
        builder.build_snapshot('3c', ['2', '3b'], [
959
 
            ('modify', ('f-id', '1\n2\n3a\n'))])
960
 
        builder.build_snapshot('4a', ['3b'], [])
961
 
        builder.build_snapshot('4b', ['3c', '4a'], [])
962
 
        builder.finish_series()
963
 
 
964
 
        # 1
965
 
        # |
966
 
        # 2-.
967
 
        # |\ \
968
 
        # | | 3a
969
 
        # | |/
970
 
        # | 3b
971
 
        # |/|
972
 
        # 3c4a
973
 
        # |/
974
 
        # 4b
975
 
 
976
 
        mainline_revs = [None, '1', '2', '3c', '4b']
977
 
        rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
978
 
        full_rev_nos_for_reference = {
979
 
            '1': '1',
980
 
            '2': '2',
981
 
            '3a': '2.1.1', #first commit tree 3
982
 
            '3b': '2.2.1', # first commit tree 2
983
 
            '3c': '3', #merges 3b to main
984
 
            '4a': '2.2.2', # second commit tree 2
985
 
            '4b': '4', # merges 4a to main
986
 
            }
987
 
        return mainline_revs, rev_nos, builder.get_branch()
988
 
 
989
 
    def test_get_view_revisions_forward(self):
990
 
        """Test the get_view_revisions method"""
991
 
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
992
 
        wt.lock_read()
993
 
        self.addCleanup(wt.unlock)
994
 
        revisions = list(self._get_view_revisions(
995
 
                mainline_revs, rev_nos, wt.branch, 'forward'))
996
 
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
997
 
                         revisions)
998
 
        revisions2 = list(self._get_view_revisions(
999
 
                mainline_revs, rev_nos, wt.branch, 'forward',
1000
 
                include_merges=False))
1001
 
        self.assertEqual(revisions, revisions2)
1002
 
 
1003
 
    def test_get_view_revisions_reverse(self):
1004
 
        """Test the get_view_revisions with reverse"""
1005
 
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1006
 
        wt.lock_read()
1007
 
        self.addCleanup(wt.unlock)
1008
 
        revisions = list(self._get_view_revisions(
1009
 
                mainline_revs, rev_nos, wt.branch, 'reverse'))
1010
 
        self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
1011
 
                         revisions)
1012
 
        revisions2 = list(self._get_view_revisions(
1013
 
                mainline_revs, rev_nos, wt.branch, 'reverse',
1014
 
                include_merges=False))
1015
 
        self.assertEqual(revisions, revisions2)
1016
 
 
1017
 
    def test_get_view_revisions_merge(self):
1018
 
        """Test get_view_revisions when there are merges"""
1019
 
        mainline_revs, rev_nos, wt = self.make_tree_with_merges()
1020
 
        wt.lock_read()
1021
 
        self.addCleanup(wt.unlock)
1022
 
        revisions = list(self._get_view_revisions(
1023
 
                mainline_revs, rev_nos, wt.branch, 'forward'))
1024
 
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
1025
 
                          ('4b', '4', 0), ('4a', '3.1.1', 1)],
1026
 
                         revisions)
1027
 
        revisions = list(self._get_view_revisions(
1028
 
                mainline_revs, rev_nos, wt.branch, 'forward',
1029
 
                include_merges=False))
1030
 
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
1031
 
                          ('4b', '4', 0)],
1032
 
                         revisions)
1033
 
 
1034
 
    def test_get_view_revisions_merge_reverse(self):
1035
 
        """Test get_view_revisions in reverse when there are merges"""
1036
 
        mainline_revs, rev_nos, wt = self.make_tree_with_merges()
1037
 
        wt.lock_read()
1038
 
        self.addCleanup(wt.unlock)
1039
 
        revisions = list(self._get_view_revisions(
1040
 
                mainline_revs, rev_nos, wt.branch, 'reverse'))
1041
 
        self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
1042
 
                          ('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
1043
 
                         revisions)
1044
 
        revisions = list(self._get_view_revisions(
1045
 
                mainline_revs, rev_nos, wt.branch, 'reverse',
1046
 
                include_merges=False))
1047
 
        self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
1048
 
                          ('1', '1', 0)],
1049
 
                         revisions)
1050
 
 
1051
 
    def test_get_view_revisions_merge2(self):
1052
 
        """Test get_view_revisions when there are merges"""
1053
 
        mainline_revs, rev_nos, b = self.make_branch_with_many_merges()
1054
 
        b.lock_read()
1055
 
        self.addCleanup(b.unlock)
1056
 
        revisions = list(self._get_view_revisions(
1057
 
                mainline_revs, rev_nos, b, 'forward'))
1058
 
        expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
1059
 
                    ('3b', '2.2.1', 1), ('3a', '2.1.1', 2), ('4b', '4', 0),
1060
 
                    ('4a', '2.2.2', 1)]
1061
 
        self.assertEqual(expected, revisions)
1062
 
        revisions = list(self._get_view_revisions(
1063
 
                mainline_revs, rev_nos, b, 'forward',
1064
 
                include_merges=False))
1065
 
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
1066
 
                          ('4b', '4', 0)],
1067
 
                         revisions)
1068
 
 
1069
 
    def test_file_id_for_range(self):
1070
 
        mainline_revs, rev_nos, b = self.make_branch_with_many_merges()
1071
 
        b.lock_read()
1072
 
        self.addCleanup(b.unlock)
1073
 
 
1074
 
        def rev_from_rev_id(revid, branch):
1075
 
            revspec = revisionspec.RevisionSpec.from_string('revid:%s' % revid)
1076
 
            return revspec.in_history(branch)
1077
 
 
1078
 
        def view_revs(start_rev, end_rev, file_id, direction):
1079
 
            revs = self.applyDeprecated(
1080
 
                symbol_versioning.deprecated_in((2, 2, 0)),
1081
 
                log.calculate_view_revisions,
1082
 
                b,
1083
 
                start_rev, # start_revision
1084
 
                end_rev, # end_revision
1085
 
                direction, # direction
1086
 
                file_id, # specific_fileid
1087
 
                True, # generate_merge_revisions
1088
 
                )
1089
 
            return revs
1090
 
 
1091
 
        rev_3a = rev_from_rev_id('3a', b)
1092
 
        rev_4b = rev_from_rev_id('4b', b)
1093
 
        self.assertEqual([('3c', '3', 0), ('3b', '2.2.1', 1),
1094
 
                          ('3a', '2.1.1', 2)],
1095
 
                          view_revs(rev_3a, rev_4b, 'f-id', 'reverse'))
1096
 
        # Note: 3c still appears before 3a here because of depth-based sorting
1097
 
        self.assertEqual([('3c', '3', 0), ('3b', '2.2.1', 1),
1098
 
                          ('3a', '2.1.1', 2)],
1099
 
                          view_revs(rev_3a, rev_4b, 'f-id', 'forward'))
1100
 
 
1101
 
 
1102
 
class TestGetRevisionsTouchingFileID(tests.TestCaseWithTransport):
1103
 
 
1104
 
    def get_view_revisions(self, *args):
1105
 
        return self.applyDeprecated(symbol_versioning.deprecated_in((2, 2, 0)),
1106
 
                                    log.get_view_revisions, *args)
1107
 
 
1108
 
    def create_tree_with_single_merge(self):
1109
 
        """Create a branch with a moderate layout.
1110
 
 
1111
 
        The revision graph looks like:
1112
 
 
1113
 
           A
1114
 
           |\
1115
 
           B C
1116
 
           |/
1117
 
           D
1118
 
 
1119
 
        In this graph, A introduced files f1 and f2 and f3.
1120
 
        B modifies f1 and f3, and C modifies f2 and f3.
1121
 
        D merges the changes from B and C and resolves the conflict for f3.
1122
 
        """
1123
 
        # TODO: jam 20070218 This seems like it could really be done
1124
 
        #       with make_branch_and_memory_tree() if we could just
1125
 
        #       create the content of those files.
1126
 
        # TODO: jam 20070218 Another alternative is that we would really
1127
 
        #       like to only create this tree 1 time for all tests that
1128
 
        #       use it. Since 'log' only uses the tree in a readonly
1129
 
        #       fashion, it seems a shame to regenerate an identical
1130
 
        #       tree for each test.
1131
 
        # TODO: vila 20100122 One way to address the shame above will be to
1132
 
        #       create a memory tree during test parametrization and give a
1133
 
        #       *copy* of this tree to each test. Copying a memory tree ought
1134
 
        #       to be cheap, at least cheaper than creating them with such
1135
 
        #       complex setups.
1136
 
        tree = self.make_branch_and_tree('tree')
1137
 
        tree.lock_write()
1138
 
        self.addCleanup(tree.unlock)
1139
 
 
1140
 
        self.build_tree_contents([('tree/f1', 'A\n'),
1141
 
                                  ('tree/f2', 'A\n'),
1142
 
                                  ('tree/f3', 'A\n'),
1143
 
                                 ])
1144
 
        tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
1145
 
        tree.commit('A', rev_id='A')
1146
 
 
1147
 
        self.build_tree_contents([('tree/f2', 'A\nC\n'),
1148
 
                                  ('tree/f3', 'A\nC\n'),
1149
 
                                 ])
1150
 
        tree.commit('C', rev_id='C')
1151
 
        # Revert back to A to build the other history.
1152
 
        tree.set_last_revision('A')
1153
 
        tree.branch.set_last_revision_info(1, 'A')
1154
 
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
1155
 
                                  ('tree/f2', 'A\n'),
1156
 
                                  ('tree/f3', 'A\nB\n'),
1157
 
                                 ])
1158
 
        tree.commit('B', rev_id='B')
1159
 
        tree.set_parent_ids(['B', 'C'])
1160
 
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
1161
 
                                  ('tree/f2', 'A\nC\n'),
1162
 
                                  ('tree/f3', 'A\nB\nC\n'),
1163
 
                                 ])
1164
 
        tree.commit('D', rev_id='D')
1165
 
 
1166
 
        # Switch to a read lock for this tree.
1167
 
        # We still have an addCleanup(tree.unlock) pending
1168
 
        tree.unlock()
1169
 
        tree.lock_read()
1170
 
        return tree
1171
 
 
1172
 
    def check_delta(self, delta, **kw):
1173
 
        """Check the filenames touched by a delta are as expected.
1174
 
 
1175
 
        Caller only have to pass in the list of files for each part, all
1176
 
        unspecified parts are considered empty (and checked as such).
1177
 
        """
1178
 
        for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
1179
 
            # By default we expect an empty list
1180
 
            expected = kw.get(n, [])
1181
 
            # strip out only the path components
1182
 
            got = [x[0] for x in getattr(delta, n)]
1183
 
            self.assertEqual(expected, got)
1184
 
 
1185
 
    def test_tree_with_single_merge(self):
1186
 
        """Make sure the tree layout is correct."""
1187
 
        tree = self.create_tree_with_single_merge()
1188
 
        rev_A_tree = tree.branch.repository.revision_tree('A')
1189
 
        rev_B_tree = tree.branch.repository.revision_tree('B')
1190
 
        rev_C_tree = tree.branch.repository.revision_tree('C')
1191
 
        rev_D_tree = tree.branch.repository.revision_tree('D')
1192
 
 
1193
 
        self.check_delta(rev_B_tree.changes_from(rev_A_tree),
1194
 
                         modified=['f1', 'f3'])
1195
 
 
1196
 
        self.check_delta(rev_C_tree.changes_from(rev_A_tree),
1197
 
                         modified=['f2', 'f3'])
1198
 
 
1199
 
        self.check_delta(rev_D_tree.changes_from(rev_B_tree),
1200
 
                         modified=['f2', 'f3'])
1201
 
 
1202
 
        self.check_delta(rev_D_tree.changes_from(rev_C_tree),
1203
 
                         modified=['f1', 'f3'])
1204
 
 
1205
 
    def assertAllRevisionsForFileID(self, tree, file_id, revisions):
1206
 
        """Ensure _filter_revisions_touching_file_id returns the right values.
1207
 
 
1208
 
        Get the return value from _filter_revisions_touching_file_id and make
1209
 
        sure they are correct.
1210
 
        """
1211
 
        # The api for _filter_revisions_touching_file_id is a little crazy.
1212
 
        # So we do the setup here.
1213
 
        mainline = tree.branch.revision_history()
1214
 
        mainline.insert(0, None)
1215
 
        revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
1216
 
        view_revs_iter = self.get_view_revisions(
1217
 
            mainline, revnos, tree.branch, 'reverse', True)
1218
 
        actual_revs = log._filter_revisions_touching_file_id(
1219
 
            tree.branch, file_id, list(view_revs_iter))
1220
 
        self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
1221
 
 
1222
 
    def test_file_id_f1(self):
1223
 
        tree = self.create_tree_with_single_merge()
1224
 
        # f1 should be marked as modified by revisions A and B
1225
 
        self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
1226
 
 
1227
 
    def test_file_id_f2(self):
1228
 
        tree = self.create_tree_with_single_merge()
1229
 
        # f2 should be marked as modified by revisions A, C, and D
1230
 
        # because D merged the changes from C.
1231
 
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1232
 
 
1233
 
    def test_file_id_f3(self):
1234
 
        tree = self.create_tree_with_single_merge()
1235
 
        # f3 should be marked as modified by revisions A, B, C, and D
1236
 
        self.assertAllRevisionsForFileID(tree, 'f3-id', ['D', 'C', 'B', 'A'])
1237
 
 
1238
 
    def test_file_id_with_ghosts(self):
1239
 
        # This is testing bug #209948, where having a ghost would cause
1240
 
        # _filter_revisions_touching_file_id() to fail.
1241
 
        tree = self.create_tree_with_single_merge()
1242
 
        # We need to add a revision, so switch back to a write-locked tree
1243
 
        # (still a single addCleanup(tree.unlock) pending).
1244
 
        tree.unlock()
1245
 
        tree.lock_write()
1246
 
        first_parent = tree.last_revision()
1247
 
        tree.set_parent_ids([first_parent, 'ghost-revision-id'])
1248
 
        self.build_tree_contents([('tree/f1', 'A\nB\nXX\n')])
1249
 
        tree.commit('commit with a ghost', rev_id='XX')
1250
 
        self.assertAllRevisionsForFileID(tree, 'f1-id', ['XX', 'B', 'A'])
1251
 
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1252
 
 
1253
 
    def test_unknown_file_id(self):
1254
 
        tree = self.create_tree_with_single_merge()
1255
 
        self.assertAllRevisionsForFileID(tree, 'unknown', [])
1256
 
 
1257
 
    def test_empty_branch_unknown_file_id(self):
1258
 
        tree = self.make_branch_and_tree('tree')
1259
 
        self.assertAllRevisionsForFileID(tree, 'unknown', [])
 
1052
                                   wt.branch, log.GnuChangelogLogFormatter,
 
1053
                                   show_log_kwargs=dict(verbose=True))
1260
1054
 
1261
1055
 
1262
1056
class TestShowChangedRevisions(tests.TestCaseWithTransport):
1265
1059
        tree = self.make_branch_and_tree('tree_a')
1266
1060
        self.build_tree(['tree_a/foo'])
1267
1061
        tree.add('foo')
1268
 
        tree.commit('bar', rev_id='bar-id')
 
1062
        tree.commit('bar', rev_id=b'bar-id')
1269
1063
        s = self.make_utf8_encoded_stringio()
1270
 
        log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
1271
 
        self.assertContainsRe(s.getvalue(), 'bar')
1272
 
        self.assertNotContainsRe(s.getvalue(), 'foo')
 
1064
        log.show_changed_revisions(tree.branch, [], [b'bar-id'], s)
 
1065
        self.assertContainsRe(s.getvalue(), b'bar')
 
1066
        self.assertNotContainsRe(s.getvalue(), b'foo')
1273
1067
 
1274
1068
 
1275
1069
class TestLogFormatter(tests.TestCase):
1276
1070
 
1277
1071
    def setUp(self):
1278
1072
        super(TestLogFormatter, self).setUp()
1279
 
        self.rev = revision.Revision('a-id')
 
1073
        self.rev = revision.Revision(b'a-id')
1280
1074
        self.lf = log.LogFormatter(None)
1281
1075
 
1282
1076
    def test_short_committer(self):
1330
1124
            Tests use (revno, depth) whil the API expects (revid, revno, depth).
1331
1125
            Since the revid is arbitrary, we just duplicate revno
1332
1126
            """
1333
 
            return [ (r, r, d) for r, d in l]
 
1127
            return [(r, r, d) for r, d in l]
1334
1128
        forward = complete_revisions(forward)
1335
 
        backward= complete_revisions(backward)
 
1129
        backward = complete_revisions(backward)
1336
1130
        self.assertEqual(forward, log.reverse_by_depth(backward))
1337
1131
 
1338
 
 
1339
1132
    def test_mainline_revisions(self):
1340
 
        self.assertReversed([( '1', 0), ('2', 0)],
 
1133
        self.assertReversed([('1', 0), ('2', 0)],
1341
1134
                            [('2', 0), ('1', 0)])
1342
1135
 
1343
1136
    def test_merged_revisions(self):
1344
 
        self.assertReversed([('1', 0), ('2', 0), ('2.2', 1), ('2.1', 1),],
1345
 
                            [('2', 0), ('2.1', 1), ('2.2', 1), ('1', 0),])
 
1137
        self.assertReversed([('1', 0), ('2', 0), ('2.2', 1), ('2.1', 1), ],
 
1138
                            [('2', 0), ('2.1', 1), ('2.2', 1), ('1', 0), ])
 
1139
 
1346
1140
    def test_shifted_merged_revisions(self):
1347
1141
        """Test irregular layout.
1348
1142
 
1349
1143
        Requesting revisions touching a file can produce "holes" in the depths.
1350
1144
        """
1351
 
        self.assertReversed([('1', 0), ('2', 0), ('1.1', 2), ('1.2', 2),],
1352
 
                            [('2', 0), ('1.2', 2), ('1.1', 2), ('1', 0),])
 
1145
        self.assertReversed([('1', 0), ('2', 0), ('1.1', 2), ('1.2', 2), ],
 
1146
                            [('2', 0), ('1.2', 2), ('1.1', 2), ('1', 0), ])
1353
1147
 
1354
1148
    def test_merged_without_child_revisions(self):
1355
1149
        """Test irregular layout.
1359
1153
        # When a revision of higher depth doesn't follow one of lower depth, we
1360
1154
        # assume a lower depth one is virtually there
1361
1155
        self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1362
 
                            [('4', 4), ('3', 3), ('2', 2), ('1', 2),])
 
1156
                            [('4', 4), ('3', 3), ('2', 2), ('1', 2), ])
1363
1157
        # So we get the same order after reversing below even if the original
1364
1158
        # revisions are not in the same order.
1365
1159
        self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1366
 
                            [('3', 3), ('4', 4), ('2', 2), ('1', 2),])
 
1160
                            [('3', 3), ('4', 4), ('2', 2), ('1', 2), ])
1367
1161
 
1368
1162
 
1369
1163
class TestHistoryChange(tests.TestCaseWithTransport):
1372
1166
        tree = self.make_branch_and_tree('tree')
1373
1167
        tree.lock_write()
1374
1168
        self.addCleanup(tree.unlock)
1375
 
        tree.commit('1a', rev_id='1a')
1376
 
        tree.commit('2a', rev_id='2a')
1377
 
        tree.commit('3a', rev_id='3a')
 
1169
        tree.commit('1a', rev_id=b'1a')
 
1170
        tree.commit('2a', rev_id=b'2a')
 
1171
        tree.commit('3a', rev_id=b'3a')
1378
1172
        return tree
1379
1173
 
1380
1174
    def setup_ab_tree(self):
1381
1175
        tree = self.setup_a_tree()
1382
 
        tree.set_last_revision('1a')
1383
 
        tree.branch.set_last_revision_info(1, '1a')
1384
 
        tree.commit('2b', rev_id='2b')
1385
 
        tree.commit('3b', rev_id='3b')
 
1176
        tree.set_last_revision(b'1a')
 
1177
        tree.branch.set_last_revision_info(1, b'1a')
 
1178
        tree.commit('2b', rev_id=b'2b')
 
1179
        tree.commit('3b', rev_id=b'3b')
1386
1180
        return tree
1387
1181
 
1388
1182
    def setup_ac_tree(self):
1389
1183
        tree = self.setup_a_tree()
1390
1184
        tree.set_last_revision(revision.NULL_REVISION)
1391
1185
        tree.branch.set_last_revision_info(0, revision.NULL_REVISION)
1392
 
        tree.commit('1c', rev_id='1c')
1393
 
        tree.commit('2c', rev_id='2c')
1394
 
        tree.commit('3c', rev_id='3c')
 
1186
        tree.commit('1c', rev_id=b'1c')
 
1187
        tree.commit('2c', rev_id=b'2c')
 
1188
        tree.commit('3c', rev_id=b'3c')
1395
1189
        return tree
1396
1190
 
1397
1191
    def test_all_new(self):
1398
1192
        tree = self.setup_ab_tree()
1399
 
        old, new = log.get_history_change('1a', '3a', tree.branch.repository)
 
1193
        old, new = log.get_history_change(b'1a', b'3a', tree.branch.repository)
1400
1194
        self.assertEqual([], old)
1401
 
        self.assertEqual(['2a', '3a'], new)
 
1195
        self.assertEqual([b'2a', b'3a'], new)
1402
1196
 
1403
1197
    def test_all_old(self):
1404
1198
        tree = self.setup_ab_tree()
1405
 
        old, new = log.get_history_change('3a', '1a', tree.branch.repository)
 
1199
        old, new = log.get_history_change(b'3a', b'1a', tree.branch.repository)
1406
1200
        self.assertEqual([], new)
1407
 
        self.assertEqual(['2a', '3a'], old)
 
1201
        self.assertEqual([b'2a', b'3a'], old)
1408
1202
 
1409
1203
    def test_null_old(self):
1410
1204
        tree = self.setup_ab_tree()
1411
1205
        old, new = log.get_history_change(revision.NULL_REVISION,
1412
 
                                          '3a', tree.branch.repository)
 
1206
                                          b'3a', tree.branch.repository)
1413
1207
        self.assertEqual([], old)
1414
 
        self.assertEqual(['1a', '2a', '3a'], new)
 
1208
        self.assertEqual([b'1a', b'2a', b'3a'], new)
1415
1209
 
1416
1210
    def test_null_new(self):
1417
1211
        tree = self.setup_ab_tree()
1418
 
        old, new = log.get_history_change('3a', revision.NULL_REVISION,
 
1212
        old, new = log.get_history_change(b'3a', revision.NULL_REVISION,
1419
1213
                                          tree.branch.repository)
1420
1214
        self.assertEqual([], new)
1421
 
        self.assertEqual(['1a', '2a', '3a'], old)
 
1215
        self.assertEqual([b'1a', b'2a', b'3a'], old)
1422
1216
 
1423
1217
    def test_diverged(self):
1424
1218
        tree = self.setup_ab_tree()
1425
 
        old, new = log.get_history_change('3a', '3b', tree.branch.repository)
1426
 
        self.assertEqual(old, ['2a', '3a'])
1427
 
        self.assertEqual(new, ['2b', '3b'])
 
1219
        old, new = log.get_history_change(b'3a', b'3b', tree.branch.repository)
 
1220
        self.assertEqual(old, [b'2a', b'3a'])
 
1221
        self.assertEqual(new, [b'2b', b'3b'])
1428
1222
 
1429
1223
    def test_unrelated(self):
1430
1224
        tree = self.setup_ac_tree()
1431
 
        old, new = log.get_history_change('3a', '3c', tree.branch.repository)
1432
 
        self.assertEqual(old, ['1a', '2a', '3a'])
1433
 
        self.assertEqual(new, ['1c', '2c', '3c'])
 
1225
        old, new = log.get_history_change(b'3a', b'3c', tree.branch.repository)
 
1226
        self.assertEqual(old, [b'1a', b'2a', b'3a'])
 
1227
        self.assertEqual(new, [b'1c', b'2c', b'3c'])
1434
1228
 
1435
1229
    def test_show_branch_change(self):
1436
1230
        tree = self.setup_ab_tree()
1437
1231
        s = StringIO()
1438
 
        log.show_branch_change(tree.branch, s, 3, '3a')
 
1232
        log.show_branch_change(tree.branch, s, 3, b'3a')
1439
1233
        self.assertContainsRe(s.getvalue(),
1440
 
            '[*]{60}\nRemoved Revisions:\n(.|\n)*2a(.|\n)*3a(.|\n)*'
1441
 
            '[*]{60}\n\nAdded Revisions:\n(.|\n)*2b(.|\n)*3b')
 
1234
                              '[*]{60}\nRemoved Revisions:\n(.|\n)*2a(.|\n)*3a(.|\n)*'
 
1235
                              '[*]{60}\n\nAdded Revisions:\n(.|\n)*2b(.|\n)*3b')
1442
1236
 
1443
1237
    def test_show_branch_change_no_change(self):
1444
1238
        tree = self.setup_ab_tree()
1445
1239
        s = StringIO()
1446
 
        log.show_branch_change(tree.branch, s, 3, '3b')
 
1240
        log.show_branch_change(tree.branch, s, 3, b'3b')
1447
1241
        self.assertEqual(s.getvalue(),
1448
 
            'Nothing seems to have changed\n')
 
1242
                         'Nothing seems to have changed\n')
1449
1243
 
1450
1244
    def test_show_branch_change_no_old(self):
1451
1245
        tree = self.setup_ab_tree()
1452
1246
        s = StringIO()
1453
 
        log.show_branch_change(tree.branch, s, 2, '2b')
 
1247
        log.show_branch_change(tree.branch, s, 2, b'2b')
1454
1248
        self.assertContainsRe(s.getvalue(), 'Added Revisions:')
1455
1249
        self.assertNotContainsRe(s.getvalue(), 'Removed Revisions:')
1456
1250
 
1457
1251
    def test_show_branch_change_no_new(self):
1458
1252
        tree = self.setup_ab_tree()
1459
 
        tree.branch.set_last_revision_info(2, '2b')
 
1253
        tree.branch.set_last_revision_info(2, b'2b')
1460
1254
        s = StringIO()
1461
 
        log.show_branch_change(tree.branch, s, 3, '3b')
 
1255
        log.show_branch_change(tree.branch, s, 3, b'3b')
1462
1256
        self.assertContainsRe(s.getvalue(), 'Removed Revisions:')
1463
1257
        self.assertNotContainsRe(s.getvalue(), 'Added Revisions:')
1464
1258
 
1465
1259
 
 
1260
class TestRevisionNotInBranch(TestCaseForLogFormatter):
 
1261
 
 
1262
    def setup_a_tree(self):
 
1263
        tree = self.make_branch_and_tree('tree')
 
1264
        tree.lock_write()
 
1265
        self.addCleanup(tree.unlock)
 
1266
        kwargs = {
 
1267
            'committer': 'Joe Foo <joe@foo.com>',
 
1268
            'timestamp': 1132617600,  # Mon 2005-11-22 00:00:00 +0000
 
1269
            'timezone': 0,  # UTC
 
1270
        }
 
1271
        tree.commit('commit 1a', rev_id=b'1a', **kwargs)
 
1272
        tree.commit('commit 2a', rev_id=b'2a', **kwargs)
 
1273
        tree.commit('commit 3a', rev_id=b'3a', **kwargs)
 
1274
        return tree
 
1275
 
 
1276
    def setup_ab_tree(self):
 
1277
        tree = self.setup_a_tree()
 
1278
        tree.set_last_revision(b'1a')
 
1279
        tree.branch.set_last_revision_info(1, b'1a')
 
1280
        kwargs = {
 
1281
            'committer': 'Joe Foo <joe@foo.com>',
 
1282
            'timestamp': 1132617600,  # Mon 2005-11-22 00:00:00 +0000
 
1283
            'timezone': 0,  # UTC
 
1284
        }
 
1285
        tree.commit('commit 2b', rev_id=b'2b', **kwargs)
 
1286
        tree.commit('commit 3b', rev_id=b'3b', **kwargs)
 
1287
        return tree
 
1288
 
 
1289
    def test_one_revision(self):
 
1290
        tree = self.setup_ab_tree()
 
1291
        lf = LogCatcher()
 
1292
        rev = revisionspec.RevisionInfo(tree.branch, None, b'3a')
 
1293
        log.show_log(tree.branch, lf, verbose=True, start_revision=rev,
 
1294
                     end_revision=rev)
 
1295
        self.assertEqual(1, len(lf.revisions))
 
1296
        self.assertEqual(None, lf.revisions[0].revno)   # Out-of-branch
 
1297
        self.assertEqual(b'3a', lf.revisions[0].rev.revision_id)
 
1298
 
 
1299
    def test_many_revisions(self):
 
1300
        tree = self.setup_ab_tree()
 
1301
        lf = LogCatcher()
 
1302
        start_rev = revisionspec.RevisionInfo(tree.branch, None, b'1a')
 
1303
        end_rev = revisionspec.RevisionInfo(tree.branch, None, b'3a')
 
1304
        log.show_log(tree.branch, lf, verbose=True, start_revision=start_rev,
 
1305
                     end_revision=end_rev)
 
1306
        self.assertEqual(3, len(lf.revisions))
 
1307
        self.assertEqual(None, lf.revisions[0].revno)   # Out-of-branch
 
1308
        self.assertEqual(b'3a', lf.revisions[0].rev.revision_id)
 
1309
        self.assertEqual(None, lf.revisions[1].revno)   # Out-of-branch
 
1310
        self.assertEqual(b'2a', lf.revisions[1].rev.revision_id)
 
1311
        self.assertEqual('1', lf.revisions[2].revno)    # In-branch
 
1312
 
 
1313
    def test_long_format(self):
 
1314
        tree = self.setup_ab_tree()
 
1315
        start_rev = revisionspec.RevisionInfo(tree.branch, None, b'1a')
 
1316
        end_rev = revisionspec.RevisionInfo(tree.branch, None, b'3a')
 
1317
        self.assertFormatterResult(b"""\
 
1318
------------------------------------------------------------
 
1319
revision-id: 3a
 
1320
committer: Joe Foo <joe@foo.com>
 
1321
branch nick: tree
 
1322
timestamp: Tue 2005-11-22 00:00:00 +0000
 
1323
message:
 
1324
  commit 3a
 
1325
------------------------------------------------------------
 
1326
revision-id: 2a
 
1327
committer: Joe Foo <joe@foo.com>
 
1328
branch nick: tree
 
1329
timestamp: Tue 2005-11-22 00:00:00 +0000
 
1330
message:
 
1331
  commit 2a
 
1332
------------------------------------------------------------
 
1333
revno: 1
 
1334
committer: Joe Foo <joe@foo.com>
 
1335
branch nick: tree
 
1336
timestamp: Tue 2005-11-22 00:00:00 +0000
 
1337
message:
 
1338
  commit 1a
 
1339
""",
 
1340
                                   tree.branch, log.LongLogFormatter, show_log_kwargs={
 
1341
                                       'start_revision': start_rev, 'end_revision': end_rev
 
1342
                                       })
 
1343
 
 
1344
    def test_short_format(self):
 
1345
        tree = self.setup_ab_tree()
 
1346
        start_rev = revisionspec.RevisionInfo(tree.branch, None, b'1a')
 
1347
        end_rev = revisionspec.RevisionInfo(tree.branch, None, b'3a')
 
1348
        self.assertFormatterResult(b"""\
 
1349
      Joe Foo\t2005-11-22
 
1350
      revision-id:3a
 
1351
      commit 3a
 
1352
 
 
1353
      Joe Foo\t2005-11-22
 
1354
      revision-id:2a
 
1355
      commit 2a
 
1356
 
 
1357
    1 Joe Foo\t2005-11-22
 
1358
      commit 1a
 
1359
 
 
1360
""",
 
1361
                                   tree.branch, log.ShortLogFormatter, show_log_kwargs={
 
1362
                                       'start_revision': start_rev, 'end_revision': end_rev
 
1363
                                       })
 
1364
 
 
1365
    def test_line_format(self):
 
1366
        tree = self.setup_ab_tree()
 
1367
        start_rev = revisionspec.RevisionInfo(tree.branch, None, b'1a')
 
1368
        end_rev = revisionspec.RevisionInfo(tree.branch, None, b'3a')
 
1369
        self.assertFormatterResult(b"""\
 
1370
Joe Foo 2005-11-22 commit 3a
 
1371
Joe Foo 2005-11-22 commit 2a
 
1372
1: Joe Foo 2005-11-22 commit 1a
 
1373
""",
 
1374
                                   tree.branch, log.LineLogFormatter, show_log_kwargs={
 
1375
                                       'start_revision': start_rev, 'end_revision': end_rev
 
1376
                                       })
 
1377
 
1466
1378
 
1467
1379
class TestLogWithBugs(TestCaseForLogFormatter, TestLogMixin):
1468
1380
 
1469
1381
    def setUp(self):
1470
 
        TestCaseForLogFormatter.setUp(self)
 
1382
        super(TestLogWithBugs, self).setUp()
1471
1383
        log.properties_handler_registry.register(
1472
1384
            'bugs_properties_handler',
1473
1385
            log._bugs_properties_handler)
1477
1389
        tree = self.make_branch_and_tree(u'.')
1478
1390
        self.build_tree(['a', 'b'])
1479
1391
        tree.add('a')
1480
 
        self.wt_commit(tree, 'simple log message', rev_id='a1',
1481
 
                       revprops={'bugs': 'test://bug/id fixed'})
 
1392
        self.wt_commit(tree, 'simple log message', rev_id=b'a1',
 
1393
                       revprops={u'bugs': 'test://bug/id fixed'})
1482
1394
        tree.add('b')
1483
 
        self.wt_commit(tree, 'multiline\nlog\nmessage\n', rev_id='a2',
 
1395
        self.wt_commit(tree, 'multiline\nlog\nmessage\n', rev_id=b'a2',
1484
1396
                       authors=['Joe Bar <joe@bar.com>'],
1485
 
                       revprops={'bugs': 'test://bug/id fixed\n'
 
1397
                       revprops={u'bugs': 'test://bug/id fixed\n'
1486
1398
                                 'test://bug/2 fixed'})
1487
1399
        return tree
1488
1400
 
 
1401
    def test_bug_broken(self):
 
1402
        tree = self.make_branch_and_tree(u'.')
 
1403
        self.build_tree(['a', 'b'])
 
1404
        tree.add('a')
 
1405
        self.wt_commit(tree, 'simple log message', rev_id=b'a1',
 
1406
                       revprops={u'bugs': 'test://bua g/id fixed'})
 
1407
 
 
1408
        logfile = self.make_utf8_encoded_stringio()
 
1409
        formatter = log.LongLogFormatter(to_file=logfile)
 
1410
        log.show_log(tree.branch, formatter)
 
1411
 
 
1412
        self.assertContainsRe(
 
1413
            logfile.getvalue(),
 
1414
            b'brz: ERROR: breezy.bugtracker.InvalidLineInBugsProperty: '
 
1415
            b'Invalid line in bugs property: \'test://bua g/id fixed\'')
 
1416
 
 
1417
        text = logfile.getvalue()
 
1418
        self.assertEqualDiff(
 
1419
            text[text.index(b'-' * 60):],
 
1420
            b"""\
 
1421
------------------------------------------------------------
 
1422
revno: 1
 
1423
committer: Joe Foo <joe@foo.com>
 
1424
branch nick: work
 
1425
timestamp: Tue 2005-11-22 00:00:00 +0000
 
1426
message:
 
1427
  simple log message
 
1428
""")
1489
1429
 
1490
1430
    def test_long_bugs(self):
1491
1431
        tree = self.make_commits_with_bugs()
1492
 
        self.assertFormatterResult("""\
 
1432
        self.assertFormatterResult(b"""\
1493
1433
------------------------------------------------------------
1494
1434
revno: 2
1495
 
fixes bug(s): test://bug/id test://bug/2
 
1435
fixes bugs: test://bug/id test://bug/2
1496
1436
author: Joe Bar <joe@bar.com>
1497
1437
committer: Joe Foo <joe@foo.com>
1498
1438
branch nick: work
1503
1443
  message
1504
1444
------------------------------------------------------------
1505
1445
revno: 1
1506
 
fixes bug(s): test://bug/id
 
1446
fixes bug: test://bug/id
1507
1447
committer: Joe Foo <joe@foo.com>
1508
1448
branch nick: work
1509
1449
timestamp: Tue 2005-11-22 00:00:00 +0000
1510
1450
message:
1511
1451
  simple log message
1512
1452
""",
1513
 
            tree.branch, log.LongLogFormatter)
 
1453
                                   tree.branch, log.LongLogFormatter)
1514
1454
 
1515
1455
    def test_short_bugs(self):
1516
1456
        tree = self.make_commits_with_bugs()
1517
 
        self.assertFormatterResult("""\
 
1457
        self.assertFormatterResult(b"""\
1518
1458
    2 Joe Bar\t2005-11-22
1519
 
      fixes bug(s): test://bug/id test://bug/2
 
1459
      fixes bugs: test://bug/id test://bug/2
1520
1460
      multiline
1521
1461
      log
1522
1462
      message
1523
1463
 
1524
1464
    1 Joe Foo\t2005-11-22
1525
 
      fixes bug(s): test://bug/id
 
1465
      fixes bug: test://bug/id
1526
1466
      simple log message
1527
1467
 
1528
1468
""",
1529
 
            tree.branch, log.ShortLogFormatter)
 
1469
                                   tree.branch, log.ShortLogFormatter)
1530
1470
 
1531
1471
    def test_wrong_bugs_property(self):
1532
1472
        tree = self.make_branch_and_tree(u'.')
1533
1473
        self.build_tree(['foo'])
1534
 
        self.wt_commit(tree, 'simple log message', rev_id='a1',
1535
 
                       revprops={'bugs': 'test://bug/id invalid_value'})
1536
 
        self.assertFormatterResult("""\
1537
 
    1 Joe Foo\t2005-11-22
1538
 
      simple log message
1539
 
 
1540
 
""",
1541
 
            tree.branch, log.ShortLogFormatter)
 
1474
        self.wt_commit(tree, 'simple log message', rev_id=b'a1',
 
1475
                       revprops={u'bugs': 'test://bug/id invalid_value'})
 
1476
 
 
1477
        logfile = self.make_utf8_encoded_stringio()
 
1478
        formatter = log.ShortLogFormatter(to_file=logfile)
 
1479
        log.show_log(tree.branch, formatter)
 
1480
 
 
1481
        lines = logfile.getvalue().splitlines()
 
1482
 
 
1483
        self.assertEqual(
 
1484
            lines[0], b'    1 Joe Foo\t2005-11-22')
 
1485
 
 
1486
        self.assertEqual(
 
1487
            lines[1],
 
1488
            b'brz: ERROR: breezy.bugtracker.InvalidBugStatus: Invalid '
 
1489
            b'bug status: \'invalid_value\'')
 
1490
 
 
1491
        self.assertEqual(lines[-2], b"      simple log message")
1542
1492
 
1543
1493
    def test_bugs_handler_present(self):
1544
1494
        self.properties_handler_registry.get('bugs_properties_handler')
1545
1495
 
 
1496
 
 
1497
class TestLogForAuthors(TestCaseForLogFormatter):
 
1498
 
 
1499
    def setUp(self):
 
1500
        super(TestLogForAuthors, self).setUp()
 
1501
        self.wt = self.make_standard_commit('nicky',
 
1502
                                            authors=['John Doe <jdoe@example.com>',
 
1503
                                                     'Jane Rey <jrey@example.com>'])
 
1504
 
 
1505
    def assertFormatterResult(self, formatter, who, result):
 
1506
        formatter_kwargs = dict()
 
1507
        if who is not None:
 
1508
            author_list_handler = log.author_list_registry.get(who)
 
1509
            formatter_kwargs['author_list_handler'] = author_list_handler
 
1510
        TestCaseForLogFormatter.assertFormatterResult(self, result,
 
1511
                                                      self.wt.branch, formatter, formatter_kwargs=formatter_kwargs)
 
1512
 
 
1513
    def test_line_default(self):
 
1514
        self.assertFormatterResult(log.LineLogFormatter, None, b"""\
 
1515
1: John Doe 2005-11-22 add a
 
1516
""")
 
1517
 
 
1518
    def test_line_committer(self):
 
1519
        self.assertFormatterResult(log.LineLogFormatter, 'committer', b"""\
 
1520
1: Lorem Ipsum 2005-11-22 add a
 
1521
""")
 
1522
 
 
1523
    def test_line_first(self):
 
1524
        self.assertFormatterResult(log.LineLogFormatter, 'first', b"""\
 
1525
1: John Doe 2005-11-22 add a
 
1526
""")
 
1527
 
 
1528
    def test_line_all(self):
 
1529
        self.assertFormatterResult(log.LineLogFormatter, 'all', b"""\
 
1530
1: John Doe, Jane Rey 2005-11-22 add a
 
1531
""")
 
1532
 
 
1533
    def test_short_default(self):
 
1534
        self.assertFormatterResult(log.ShortLogFormatter, None, b"""\
 
1535
    1 John Doe\t2005-11-22
 
1536
      add a
 
1537
 
 
1538
""")
 
1539
 
 
1540
    def test_short_committer(self):
 
1541
        self.assertFormatterResult(log.ShortLogFormatter, 'committer', b"""\
 
1542
    1 Lorem Ipsum\t2005-11-22
 
1543
      add a
 
1544
 
 
1545
""")
 
1546
 
 
1547
    def test_short_first(self):
 
1548
        self.assertFormatterResult(log.ShortLogFormatter, 'first', b"""\
 
1549
    1 John Doe\t2005-11-22
 
1550
      add a
 
1551
 
 
1552
""")
 
1553
 
 
1554
    def test_short_all(self):
 
1555
        self.assertFormatterResult(log.ShortLogFormatter, 'all', b"""\
 
1556
    1 John Doe, Jane Rey\t2005-11-22
 
1557
      add a
 
1558
 
 
1559
""")
 
1560
 
 
1561
    def test_long_default(self):
 
1562
        self.assertFormatterResult(log.LongLogFormatter, None, b"""\
 
1563
------------------------------------------------------------
 
1564
revno: 1
 
1565
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
 
1566
committer: Lorem Ipsum <test@example.com>
 
1567
branch nick: nicky
 
1568
timestamp: Tue 2005-11-22 00:00:00 +0000
 
1569
message:
 
1570
  add a
 
1571
""")
 
1572
 
 
1573
    def test_long_committer(self):
 
1574
        self.assertFormatterResult(log.LongLogFormatter, 'committer', b"""\
 
1575
------------------------------------------------------------
 
1576
revno: 1
 
1577
committer: Lorem Ipsum <test@example.com>
 
1578
branch nick: nicky
 
1579
timestamp: Tue 2005-11-22 00:00:00 +0000
 
1580
message:
 
1581
  add a
 
1582
""")
 
1583
 
 
1584
    def test_long_first(self):
 
1585
        self.assertFormatterResult(log.LongLogFormatter, 'first', b"""\
 
1586
------------------------------------------------------------
 
1587
revno: 1
 
1588
author: John Doe <jdoe@example.com>
 
1589
committer: Lorem Ipsum <test@example.com>
 
1590
branch nick: nicky
 
1591
timestamp: Tue 2005-11-22 00:00:00 +0000
 
1592
message:
 
1593
  add a
 
1594
""")
 
1595
 
 
1596
    def test_long_all(self):
 
1597
        self.assertFormatterResult(log.LongLogFormatter, 'all', b"""\
 
1598
------------------------------------------------------------
 
1599
revno: 1
 
1600
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
 
1601
committer: Lorem Ipsum <test@example.com>
 
1602
branch nick: nicky
 
1603
timestamp: Tue 2005-11-22 00:00:00 +0000
 
1604
message:
 
1605
  add a
 
1606
""")
 
1607
 
 
1608
    def test_gnu_changelog_default(self):
 
1609
        self.assertFormatterResult(log.GnuChangelogLogFormatter, None, b"""\
 
1610
2005-11-22  John Doe  <jdoe@example.com>
 
1611
 
 
1612
\tadd a
 
1613
 
 
1614
""")
 
1615
 
 
1616
    def test_gnu_changelog_committer(self):
 
1617
        self.assertFormatterResult(log.GnuChangelogLogFormatter, 'committer', b"""\
 
1618
2005-11-22  Lorem Ipsum  <test@example.com>
 
1619
 
 
1620
\tadd a
 
1621
 
 
1622
""")
 
1623
 
 
1624
    def test_gnu_changelog_first(self):
 
1625
        self.assertFormatterResult(log.GnuChangelogLogFormatter, 'first', b"""\
 
1626
2005-11-22  John Doe  <jdoe@example.com>
 
1627
 
 
1628
\tadd a
 
1629
 
 
1630
""")
 
1631
 
 
1632
    def test_gnu_changelog_all(self):
 
1633
        self.assertFormatterResult(log.GnuChangelogLogFormatter, 'all', b"""\
 
1634
2005-11-22  John Doe  <jdoe@example.com>, Jane Rey  <jrey@example.com>
 
1635
 
 
1636
\tadd a
 
1637
 
 
1638
""")
 
1639
 
 
1640
 
1546
1641
class TestLogExcludeAncestry(tests.TestCaseWithTransport):
1547
1642
 
1548
1643
    def make_branch_with_alternate_ancestries(self, relpath='.'):
1549
1644
        # See test_merge_sorted_exclude_ancestry below for the difference with
1550
1645
        # bt.per_branch.test_iter_merge_sorted_revision.
1551
 
        # TestIterMergeSortedRevisionsBushyGraph. 
 
1646
        # TestIterMergeSortedRevisionsBushyGraph.
1552
1647
        # make_branch_with_alternate_ancestries
1553
1648
        # and test_merge_sorted_exclude_ancestry
1554
1649
        # See the FIXME in assertLogRevnos too.
1565
1660
        # | /
1566
1661
        # 3
1567
1662
        builder.start_series()
1568
 
        builder.build_snapshot('1', None, [
1569
 
            ('add', ('', 'TREE_ROOT', 'directory', '')),])
1570
 
        builder.build_snapshot('1.1.1', ['1'], [])
1571
 
        builder.build_snapshot('2', ['1'], [])
1572
 
        builder.build_snapshot('1.2.1', ['1.1.1'], [])
1573
 
        builder.build_snapshot('1.1.2', ['1.1.1', '1.2.1'], [])
1574
 
        builder.build_snapshot('3', ['2', '1.1.2'], [])
 
1663
        builder.build_snapshot(None, [
 
1664
            ('add', ('', b'TREE_ROOT', 'directory', '')), ],
 
1665
            revision_id=b'1')
 
1666
        builder.build_snapshot([b'1'], [], revision_id=b'1.1.1')
 
1667
        builder.build_snapshot([b'1'], [], revision_id=b'2')
 
1668
        builder.build_snapshot([b'1.1.1'], [], revision_id=b'1.2.1')
 
1669
        builder.build_snapshot([b'1.1.1', b'1.2.1'], [], revision_id=b'1.1.2')
 
1670
        builder.build_snapshot([b'2', b'1.1.2'], [], revision_id=b'3')
1575
1671
        builder.finish_series()
1576
1672
        br = builder.get_branch()
1577
1673
        br.lock_read()
1579
1675
        return br
1580
1676
 
1581
1677
    def assertLogRevnos(self, expected_revnos, b, start, end,
1582
 
                        exclude_common_ancestry):
 
1678
                        exclude_common_ancestry, generate_merge_revisions=True):
1583
1679
        # FIXME: the layering in log makes it hard to test intermediate levels,
1584
 
        # I wish adding filters with their parameters were easier...
 
1680
        # I wish adding filters with their parameters was easier...
1585
1681
        # -- vila 20100413
1586
1682
        iter_revs = log._calc_view_revisions(
1587
1683
            b, start, end, direction='reverse',
1588
 
            generate_merge_revisions=True,
 
1684
            generate_merge_revisions=generate_merge_revisions,
1589
1685
            exclude_common_ancestry=exclude_common_ancestry)
1590
1686
        self.assertEqual(expected_revnos,
1591
1687
                         [revid for revid, revno, depth in iter_revs])
1592
1688
 
1593
1689
    def test_merge_sorted_exclude_ancestry(self):
1594
1690
        b = self.make_branch_with_alternate_ancestries()
1595
 
        self.assertLogRevnos(['3', '1.1.2', '1.2.1', '1.1.1', '2', '1'],
1596
 
                             b, '1', '3', False)
 
1691
        self.assertLogRevnos([b'3', b'1.1.2', b'1.2.1', b'1.1.1', b'2', b'1'],
 
1692
                             b, b'1', b'3', exclude_common_ancestry=False)
1597
1693
        # '2' is part of the '3' ancestry but not part of '1.1.1' ancestry so
1598
1694
        # it should be mentioned even if merge_sort order will make it appear
1599
1695
        # after 1.1.1
1600
 
        self.assertLogRevnos(['3', '1.1.2', '1.2.1', '2'],
1601
 
                             b, '1.1.1', '3', True)
1602
 
 
1603
 
 
 
1696
        self.assertLogRevnos([b'3', b'1.1.2', b'1.2.1', b'2'],
 
1697
                             b, b'1.1.1', b'3', exclude_common_ancestry=True)
 
1698
 
 
1699
    def test_merge_sorted_simple_revnos_exclude_ancestry(self):
 
1700
        b = self.make_branch_with_alternate_ancestries()
 
1701
        self.assertLogRevnos([b'3', b'2'],
 
1702
                             b, b'1', b'3', exclude_common_ancestry=True,
 
1703
                             generate_merge_revisions=False)
 
1704
        self.assertLogRevnos([b'3', b'1.1.2', b'1.2.1', b'1.1.1', b'2'],
 
1705
                             b, b'1', b'3', exclude_common_ancestry=True,
 
1706
                             generate_merge_revisions=True)
 
1707
 
 
1708
 
 
1709
class TestLogDefaults(TestCaseForLogFormatter):
 
1710
    def test_default_log_level(self):
 
1711
        """
 
1712
        Test to ensure that specifying 'levels=1' to make_log_request_dict
 
1713
        doesn't get overwritten when using a LogFormatter that supports more
 
1714
        detail.
 
1715
        Fixes bug #747958.
 
1716
        """
 
1717
        wt = self._prepare_tree_with_merges()
 
1718
        b = wt.branch
 
1719
 
 
1720
        class CustomLogFormatter(log.LogFormatter):
 
1721
            def __init__(self, *args, **kwargs):
 
1722
                super(CustomLogFormatter, self).__init__(*args, **kwargs)
 
1723
                self.revisions = []
 
1724
 
 
1725
            def get_levels(self):
 
1726
                # log formatter supports all levels:
 
1727
                return 0
 
1728
 
 
1729
            def log_revision(self, revision):
 
1730
                self.revisions.append(revision)
 
1731
 
 
1732
        log_formatter = LogCatcher()
 
1733
        # First request we don't specify number of levels, we should get a
 
1734
        # sensible default (whatever the LogFormatter handles - which in this
 
1735
        # case is 0/everything):
 
1736
        request = log.make_log_request_dict(limit=10)
 
1737
        log.Logger(b, request).show(log_formatter)
 
1738
        # should have all three revisions:
 
1739
        self.assertEqual(len(log_formatter.revisions), 3)
 
1740
 
 
1741
        del log_formatter
 
1742
        log_formatter = LogCatcher()
 
1743
        # now explicitly request mainline revisions only:
 
1744
        request = log.make_log_request_dict(limit=10, levels=1)
 
1745
        log.Logger(b, request).show(log_formatter)
 
1746
        # should now only have 2 revisions:
 
1747
        self.assertEqual(len(log_formatter.revisions), 2)