/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: Martin
  • Date: 2018-11-16 19:10:17 UTC
  • mto: This revision was merged to the branch mainline in revision 7177.
  • Revision ID: gzlist@googlemail.com-20181116191017-kyedz1qck0ovon3h
Remove lazy_regexp reset in bt.test_source

Show diffs side-by-side

added added

removed removed

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