/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: 2019-08-12 20:24:50 UTC
  • mto: (7290.1.35 work)
  • mto: This revision was merged to the branch mainline in revision 7405.
  • Revision ID: jelmer@jelmer.uk-20190812202450-vdpamxay6sebo93w
Fix path to brz.

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
import re
 
19
 
 
20
from .. import (
 
21
    branchbuilder,
 
22
    errors,
 
23
    log,
 
24
    registry,
 
25
    revision,
 
26
    revisionspec,
 
27
    tests,
 
28
    gpg,
 
29
    trace,
 
30
    )
 
31
from ..sixish import (
 
32
    BytesIO,
 
33
    StringIO,
 
34
    unichr,
 
35
    )
 
36
 
 
37
 
 
38
class TestLogMixin(object):
 
39
 
 
40
    def wt_commit(self, wt, message, **kwargs):
 
41
        """Use some mostly fixed values for commits to simplify tests.
 
42
 
 
43
        Tests can use this function to get some commit attributes. The time
 
44
        stamp is incremented at each commit.
 
45
        """
 
46
        if getattr(self, 'timestamp', None) is None:
 
47
            self.timestamp = 1132617600  # Mon 2005-11-22 00:00:00 +0000
 
48
        else:
 
49
            self.timestamp += 1  # 1 second between each commit
 
50
        kwargs.setdefault('timestamp', self.timestamp)
 
51
        kwargs.setdefault('timezone', 0)  # UTC
 
52
        kwargs.setdefault('committer', 'Joe Foo <joe@foo.com>')
 
53
 
 
54
        return wt.commit(message, **kwargs)
 
55
 
 
56
 
 
57
class TestCaseForLogFormatter(tests.TestCaseWithTransport, TestLogMixin):
 
58
 
 
59
    def setUp(self):
 
60
        super(TestCaseForLogFormatter, self).setUp()
 
61
        # keep a reference to the "current" custom prop. handler registry
 
62
        self.properties_handler_registry = log.properties_handler_registry
 
63
        # Use a clean registry for log
 
64
        log.properties_handler_registry = registry.Registry()
 
65
 
 
66
        def restore():
 
67
            log.properties_handler_registry = self.properties_handler_registry
 
68
        self.addCleanup(restore)
 
69
 
 
70
    def assertFormatterResult(self, result, branch, formatter_class,
 
71
                              formatter_kwargs=None, show_log_kwargs=None):
 
72
        logfile = self.make_utf8_encoded_stringio()
 
73
        if formatter_kwargs is None:
 
74
            formatter_kwargs = {}
 
75
        formatter = formatter_class(to_file=logfile, **formatter_kwargs)
 
76
        if show_log_kwargs is None:
 
77
            show_log_kwargs = {}
 
78
        log.show_log(branch, formatter, **show_log_kwargs)
 
79
        self.assertEqualDiff(result, logfile.getvalue())
 
80
 
 
81
    def make_standard_commit(self, branch_nick, **kwargs):
 
82
        wt = self.make_branch_and_tree('.')
 
83
        wt.lock_write()
 
84
        self.addCleanup(wt.unlock)
 
85
        self.build_tree(['a'])
 
86
        wt.add(['a'])
 
87
        wt.branch.nick = branch_nick
 
88
        kwargs.setdefault('committer', 'Lorem Ipsum <test@example.com>')
 
89
        kwargs.setdefault('authors', ['John Doe <jdoe@example.com>'])
 
90
        self.wt_commit(wt, 'add a', **kwargs)
 
91
        return wt
 
92
 
 
93
    def make_commits_with_trailing_newlines(self, wt):
 
94
        """Helper method for LogFormatter tests"""
 
95
        b = wt.branch
 
96
        b.nick = 'test'
 
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')])
 
100
        wt.add('b')
 
101
        self.wt_commit(wt, 'multiline\nlog\nmessage\n', rev_id=b'a2')
 
102
 
 
103
        self.build_tree_contents([('c', b'just another manic monday\n')])
 
104
        wt.add('c')
 
105
        self.wt_commit(wt, 'single line with trailing newline\n', rev_id=b'a3')
 
106
        return b
 
107
 
 
108
    def _prepare_tree_with_merges(self, with_tags=False):
 
109
        wt = self.make_branch_and_memory_tree('.')
 
110
        wt.lock_write()
 
111
        self.addCleanup(wt.unlock)
 
112
        wt.add('')
 
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')
 
118
        if with_tags:
 
119
            branch = wt.branch
 
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')
 
124
        return wt
 
125
 
 
126
 
 
127
class LogCatcher(log.LogFormatter):
 
128
    """Pull log messages into a list rather than displaying them.
 
129
 
 
130
    To simplify testing we save logged revisions here rather than actually
 
131
    formatting anything, so that we can precisely check the result without
 
132
    being dependent on the formatting.
 
133
    """
 
134
 
 
135
    supports_merge_revisions = True
 
136
    supports_delta = True
 
137
    supports_diff = True
 
138
    preferred_levels = 0
 
139
 
 
140
    def __init__(self, *args, **kwargs):
 
141
        kwargs.update(dict(to_file=None))
 
142
        super(LogCatcher, self).__init__(*args, **kwargs)
 
143
        self.revisions = []
 
144
 
 
145
    def log_revision(self, revision):
 
146
        self.revisions.append(revision)
 
147
 
 
148
 
 
149
class TestShowLog(tests.TestCaseWithTransport):
 
150
 
 
151
    def checkDelta(self, delta, **kw):
 
152
        """Check the filenames touched by a delta are as expected.
 
153
 
 
154
        Caller only have to pass in the list of files for each part, all
 
155
        unspecified parts are considered empty (and checked as such).
 
156
        """
 
157
        for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
 
158
            # By default we expect an empty list
 
159
            expected = kw.get(n, [])
 
160
            # strip out only the path components
 
161
            got = [x[0] for x in getattr(delta, n)]
 
162
            self.assertEqual(expected, got)
 
163
 
 
164
    def assertInvalidRevisonNumber(self, br, start, end):
 
165
        lf = LogCatcher()
 
166
        self.assertRaises(errors.InvalidRevisionNumber,
 
167
                          log.show_log, br, lf,
 
168
                          start_revision=start, end_revision=end)
 
169
 
 
170
    def test_cur_revno(self):
 
171
        wt = self.make_branch_and_tree('.')
 
172
        b = wt.branch
 
173
 
 
174
        lf = LogCatcher()
 
175
        wt.commit('empty commit')
 
176
        log.show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
 
177
 
 
178
        # Since there is a single revision in the branch all the combinations
 
179
        # below should fail.
 
180
        self.assertInvalidRevisonNumber(b, 2, 1)
 
181
        self.assertInvalidRevisonNumber(b, 1, 2)
 
182
        self.assertInvalidRevisonNumber(b, 0, 2)
 
183
        self.assertInvalidRevisonNumber(b, -1, 1)
 
184
        self.assertInvalidRevisonNumber(b, 1, -1)
 
185
        self.assertInvalidRevisonNumber(b, 1, 0)
 
186
 
 
187
    def test_empty_branch(self):
 
188
        wt = self.make_branch_and_tree('.')
 
189
 
 
190
        lf = LogCatcher()
 
191
        log.show_log(wt.branch, lf)
 
192
        # no entries yet
 
193
        self.assertEqual([], lf.revisions)
 
194
 
 
195
    def test_empty_commit(self):
 
196
        wt = self.make_branch_and_tree('.')
 
197
 
 
198
        wt.commit('empty commit')
 
199
        lf = LogCatcher()
 
200
        log.show_log(wt.branch, lf, verbose=True)
 
201
        revs = lf.revisions
 
202
        self.assertEqual(1, len(revs))
 
203
        self.assertEqual('1', revs[0].revno)
 
204
        self.assertEqual('empty commit', revs[0].rev.message)
 
205
        self.checkDelta(revs[0].delta)
 
206
 
 
207
    def test_simple_commit(self):
 
208
        wt = self.make_branch_and_tree('.')
 
209
        wt.commit('empty commit')
 
210
        self.build_tree(['hello'])
 
211
        wt.add('hello')
 
212
        wt.commit('add one file',
 
213
                  committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
 
214
                            u'<test@example.com>')
 
215
        lf = LogCatcher()
 
216
        log.show_log(wt.branch, lf, verbose=True)
 
217
        self.assertEqual(2, len(lf.revisions))
 
218
        # first one is most recent
 
219
        log_entry = lf.revisions[0]
 
220
        self.assertEqual('2', log_entry.revno)
 
221
        self.assertEqual('add one file', log_entry.rev.message)
 
222
        self.checkDelta(log_entry.delta, added=['hello'])
 
223
 
 
224
    def test_commit_message_with_control_chars(self):
 
225
        wt = self.make_branch_and_tree('.')
 
226
        msg = u"All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
 
227
        msg = msg.replace(u'\r', u'\n')
 
228
        wt.commit(msg)
 
229
        lf = LogCatcher()
 
230
        log.show_log(wt.branch, lf, verbose=True)
 
231
        committed_msg = lf.revisions[0].rev.message
 
232
        if wt.branch.repository._serializer.squashes_xml_invalid_characters:
 
233
            self.assertNotEqual(msg, committed_msg)
 
234
            self.assertTrue(len(committed_msg) > len(msg))
 
235
        else:
 
236
            self.assertEqual(msg, committed_msg)
 
237
 
 
238
    def test_commit_message_without_control_chars(self):
 
239
        wt = self.make_branch_and_tree('.')
 
240
        # escaped.  As ElementTree apparently does some kind of
 
241
        # newline conversion, neither LF (\x0A) nor CR (\x0D) are
 
242
        # included in the test commit message, even though they are
 
243
        # valid XML 1.0 characters.
 
244
        msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
 
245
        wt.commit(msg)
 
246
        lf = LogCatcher()
 
247
        log.show_log(wt.branch, lf, verbose=True)
 
248
        committed_msg = lf.revisions[0].rev.message
 
249
        self.assertEqual(msg, committed_msg)
 
250
 
 
251
    def test_deltas_in_merge_revisions(self):
 
252
        """Check deltas created for both mainline and merge revisions"""
 
253
        wt = self.make_branch_and_tree('parent')
 
254
        self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
 
255
        wt.add('file1')
 
256
        wt.add('file2')
 
257
        wt.commit(message='add file1 and file2')
 
258
        self.run_bzr('branch parent child')
 
259
        os.unlink('child/file1')
 
260
        with open('child/file2', 'wb') as f:
 
261
            f.write(b'hello\n')
 
262
        self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
 
263
                      'child'])
 
264
        os.chdir('parent')
 
265
        self.run_bzr('merge ../child')
 
266
        wt.commit('merge child branch')
 
267
        os.chdir('..')
 
268
        b = wt.branch
 
269
        lf = LogCatcher()
 
270
        lf.supports_merge_revisions = True
 
271
        log.show_log(b, lf, verbose=True)
 
272
 
 
273
        revs = lf.revisions
 
274
        self.assertEqual(3, len(revs))
 
275
 
 
276
        logentry = revs[0]
 
277
        self.assertEqual('2', logentry.revno)
 
278
        self.assertEqual('merge child branch', logentry.rev.message)
 
279
        self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
 
280
 
 
281
        logentry = revs[1]
 
282
        self.assertEqual('1.1.1', logentry.revno)
 
283
        self.assertEqual('remove file1 and modify file2', logentry.rev.message)
 
284
        self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
 
285
 
 
286
        logentry = revs[2]
 
287
        self.assertEqual('1', logentry.revno)
 
288
        self.assertEqual('add file1 and file2', logentry.rev.message)
 
289
        self.checkDelta(logentry.delta, added=['file1', 'file2'])
 
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
 
 
345
 
 
346
class TestShortLogFormatter(TestCaseForLogFormatter):
 
347
 
 
348
    def test_trailing_newlines(self):
 
349
        wt = self.make_branch_and_tree('.')
 
350
        b = self.make_commits_with_trailing_newlines(wt)
 
351
        self.assertFormatterResult(b"""\
 
352
    3 Joe Foo\t2005-11-22
 
353
      single line with trailing newline
 
354
 
 
355
    2 Joe Foo\t2005-11-22
 
356
      multiline
 
357
      log
 
358
      message
 
359
 
 
360
    1 Joe Foo\t2005-11-22
 
361
      simple log message
 
362
 
 
363
""",
 
364
                                   b, log.ShortLogFormatter)
 
365
 
 
366
    def test_short_log_with_merges(self):
 
367
        wt = self._prepare_tree_with_merges()
 
368
        self.assertFormatterResult(b"""\
 
369
    2 Joe Foo\t2005-11-22 [merge]
 
370
      rev-2
 
371
 
 
372
    1 Joe Foo\t2005-11-22
 
373
      rev-1
 
374
 
 
375
""",
 
376
                                   wt.branch, log.ShortLogFormatter)
 
377
 
 
378
    def test_short_log_with_merges_and_advice(self):
 
379
        wt = self._prepare_tree_with_merges()
 
380
        self.assertFormatterResult(b"""\
 
381
    2 Joe Foo\t2005-11-22 [merge]
 
382
      rev-2
 
383
 
 
384
    1 Joe Foo\t2005-11-22
 
385
      rev-1
 
386
 
 
387
Use --include-merged or -n0 to see merged revisions.
 
388
""",
 
389
                                   wt.branch, log.ShortLogFormatter,
 
390
                                   formatter_kwargs=dict(show_advice=True))
 
391
 
 
392
    def test_short_log_with_merges_and_range(self):
 
393
        wt = self._prepare_tree_with_merges()
 
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"""\
 
399
    3 Joe Foo\t2005-11-22 [merge]
 
400
      rev-3b
 
401
 
 
402
    2 Joe Foo\t2005-11-22 [merge]
 
403
      rev-2
 
404
 
 
405
""",
 
406
                                   wt.branch, log.ShortLogFormatter,
 
407
                                   show_log_kwargs=dict(start_revision=2, end_revision=3))
 
408
 
 
409
    def test_short_log_with_tags(self):
 
410
        wt = self._prepare_tree_with_merges(with_tags=True)
 
411
        self.assertFormatterResult(b"""\
 
412
    3 Joe Foo\t2005-11-22 {v1.0, v1.0rc1}
 
413
      rev-3
 
414
 
 
415
    2 Joe Foo\t2005-11-22 {v0.2} [merge]
 
416
      rev-2
 
417
 
 
418
    1 Joe Foo\t2005-11-22
 
419
      rev-1
 
420
 
 
421
""",
 
422
                                   wt.branch, log.ShortLogFormatter)
 
423
 
 
424
    def test_short_log_single_merge_revision(self):
 
425
        wt = self._prepare_tree_with_merges()
 
426
        revspec = revisionspec.RevisionSpec.from_string('1.1.1')
 
427
        rev = revspec.in_history(wt.branch)
 
428
        self.assertFormatterResult(b"""\
 
429
      1.1.1 Joe Foo\t2005-11-22
 
430
            rev-merged
 
431
 
 
432
""",
 
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))
 
461
 
 
462
 
 
463
class TestShortLogFormatterWithMergeRevisions(TestCaseForLogFormatter):
 
464
 
 
465
    def test_short_merge_revs_log_with_merges(self):
 
466
        wt = self._prepare_tree_with_merges()
 
467
        # Note that the 1.1.1 indenting is in fact correct given that
 
468
        # the revision numbers are right justified within 5 characters
 
469
        # for mainline revnos and 9 characters for dotted revnos.
 
470
        self.assertFormatterResult(b"""\
 
471
    2 Joe Foo\t2005-11-22 [merge]
 
472
      rev-2
 
473
 
 
474
          1.1.1 Joe Foo\t2005-11-22
 
475
                rev-merged
 
476
 
 
477
    1 Joe Foo\t2005-11-22
 
478
      rev-1
 
479
 
 
480
""",
 
481
                                   wt.branch, log.ShortLogFormatter,
 
482
                                   formatter_kwargs=dict(levels=0))
 
483
 
 
484
    def test_short_merge_revs_log_single_merge_revision(self):
 
485
        wt = self._prepare_tree_with_merges()
 
486
        revspec = revisionspec.RevisionSpec.from_string('1.1.1')
 
487
        rev = revspec.in_history(wt.branch)
 
488
        self.assertFormatterResult(b"""\
 
489
      1.1.1 Joe Foo\t2005-11-22
 
490
            rev-merged
 
491
 
 
492
""",
 
493
                                   wt.branch, log.ShortLogFormatter,
 
494
                                   formatter_kwargs=dict(levels=0),
 
495
                                   show_log_kwargs=dict(start_revision=rev, end_revision=rev))
 
496
 
 
497
 
 
498
class TestLongLogFormatter(TestCaseForLogFormatter):
 
499
 
 
500
    def test_verbose_log(self):
 
501
        """Verbose log includes changed files
 
502
 
 
503
        bug #4676
 
504
        """
 
505
        wt = self.make_standard_commit('test_verbose_log', authors=[])
 
506
        self.assertFormatterResult(b'''\
 
507
------------------------------------------------------------
 
508
revno: 1
 
509
committer: Lorem Ipsum <test@example.com>
 
510
branch nick: test_verbose_log
 
511
timestamp: Tue 2005-11-22 00:00:00 +0000
 
512
message:
 
513
  add a
 
514
added:
 
515
  a
 
516
''',
 
517
                                   wt.branch, log.LongLogFormatter,
 
518
                                   show_log_kwargs=dict(verbose=True))
 
519
 
 
520
    def test_merges_are_indented_by_level(self):
 
521
        wt = self.make_branch_and_tree('parent')
 
522
        self.wt_commit(wt, 'first post')
 
523
        child_wt = wt.controldir.sprout('child').open_workingtree()
 
524
        self.wt_commit(child_wt, 'branch 1')
 
525
        smallerchild_wt = wt.controldir.sprout(
 
526
            'smallerchild').open_workingtree()
 
527
        self.wt_commit(smallerchild_wt, 'branch 2')
 
528
        child_wt.merge_from_branch(smallerchild_wt.branch)
 
529
        self.wt_commit(child_wt, 'merge branch 2')
 
530
        wt.merge_from_branch(child_wt.branch)
 
531
        self.wt_commit(wt, 'merge branch 1')
 
532
        self.assertFormatterResult(b"""\
 
533
------------------------------------------------------------
 
534
revno: 2 [merge]
 
535
committer: Joe Foo <joe@foo.com>
 
536
branch nick: parent
 
537
timestamp: Tue 2005-11-22 00:00:04 +0000
 
538
message:
 
539
  merge branch 1
 
540
    ------------------------------------------------------------
 
541
    revno: 1.1.2 [merge]
 
542
    committer: Joe Foo <joe@foo.com>
 
543
    branch nick: child
 
544
    timestamp: Tue 2005-11-22 00:00:03 +0000
 
545
    message:
 
546
      merge branch 2
 
547
        ------------------------------------------------------------
 
548
        revno: 1.2.1
 
549
        committer: Joe Foo <joe@foo.com>
 
550
        branch nick: smallerchild
 
551
        timestamp: Tue 2005-11-22 00:00:02 +0000
 
552
        message:
 
553
          branch 2
 
554
    ------------------------------------------------------------
 
555
    revno: 1.1.1
 
556
    committer: Joe Foo <joe@foo.com>
 
557
    branch nick: child
 
558
    timestamp: Tue 2005-11-22 00:00:01 +0000
 
559
    message:
 
560
      branch 1
 
561
------------------------------------------------------------
 
562
revno: 1
 
563
committer: Joe Foo <joe@foo.com>
 
564
branch nick: parent
 
565
timestamp: Tue 2005-11-22 00:00:00 +0000
 
566
message:
 
567
  first post
 
568
""",
 
569
                                   wt.branch, log.LongLogFormatter,
 
570
                                   formatter_kwargs=dict(levels=0),
 
571
                                   show_log_kwargs=dict(verbose=True))
 
572
 
 
573
    def test_verbose_merge_revisions_contain_deltas(self):
 
574
        wt = self.make_branch_and_tree('parent')
 
575
        self.build_tree(['parent/f1', 'parent/f2'])
 
576
        wt.add(['f1', 'f2'])
 
577
        self.wt_commit(wt, 'first post')
 
578
        child_wt = wt.controldir.sprout('child').open_workingtree()
 
579
        os.unlink('child/f1')
 
580
        self.build_tree_contents([('child/f2', b'hello\n')])
 
581
        self.wt_commit(child_wt, 'removed f1 and modified f2')
 
582
        wt.merge_from_branch(child_wt.branch)
 
583
        self.wt_commit(wt, 'merge branch 1')
 
584
        self.assertFormatterResult(b"""\
 
585
------------------------------------------------------------
 
586
revno: 2 [merge]
 
587
committer: Joe Foo <joe@foo.com>
 
588
branch nick: parent
 
589
timestamp: Tue 2005-11-22 00:00:02 +0000
 
590
message:
 
591
  merge branch 1
 
592
removed:
 
593
  f1
 
594
modified:
 
595
  f2
 
596
    ------------------------------------------------------------
 
597
    revno: 1.1.1
 
598
    committer: Joe Foo <joe@foo.com>
 
599
    branch nick: child
 
600
    timestamp: Tue 2005-11-22 00:00:01 +0000
 
601
    message:
 
602
      removed f1 and modified f2
 
603
    removed:
 
604
      f1
 
605
    modified:
 
606
      f2
 
607
------------------------------------------------------------
 
608
revno: 1
 
609
committer: Joe Foo <joe@foo.com>
 
610
branch nick: parent
 
611
timestamp: Tue 2005-11-22 00:00:00 +0000
 
612
message:
 
613
  first post
 
614
added:
 
615
  f1
 
616
  f2
 
617
""",
 
618
                                   wt.branch, log.LongLogFormatter,
 
619
                                   formatter_kwargs=dict(levels=0),
 
620
                                   show_log_kwargs=dict(verbose=True))
 
621
 
 
622
    def test_trailing_newlines(self):
 
623
        wt = self.make_branch_and_tree('.')
 
624
        b = self.make_commits_with_trailing_newlines(wt)
 
625
        self.assertFormatterResult(b"""\
 
626
------------------------------------------------------------
 
627
revno: 3
 
628
committer: Joe Foo <joe@foo.com>
 
629
branch nick: test
 
630
timestamp: Tue 2005-11-22 00:00:02 +0000
 
631
message:
 
632
  single line with trailing newline
 
633
------------------------------------------------------------
 
634
revno: 2
 
635
committer: Joe Foo <joe@foo.com>
 
636
branch nick: test
 
637
timestamp: Tue 2005-11-22 00:00:01 +0000
 
638
message:
 
639
  multiline
 
640
  log
 
641
  message
 
642
------------------------------------------------------------
 
643
revno: 1
 
644
committer: Joe Foo <joe@foo.com>
 
645
branch nick: test
 
646
timestamp: Tue 2005-11-22 00:00:00 +0000
 
647
message:
 
648
  simple log message
 
649
""",
 
650
                                   b, log.LongLogFormatter)
 
651
 
 
652
    def test_author_in_log(self):
 
653
        """Log includes the author name if it's set in
 
654
        the revision properties
 
655
        """
 
656
        wt = self.make_standard_commit('test_author_log',
 
657
                                       authors=['John Doe <jdoe@example.com>',
 
658
                                                'Jane Rey <jrey@example.com>'])
 
659
        self.assertFormatterResult(b"""\
 
660
------------------------------------------------------------
 
661
revno: 1
 
662
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
 
663
committer: Lorem Ipsum <test@example.com>
 
664
branch nick: test_author_log
 
665
timestamp: Tue 2005-11-22 00:00:00 +0000
 
666
message:
 
667
  add a
 
668
""",
 
669
                                   wt.branch, log.LongLogFormatter)
 
670
 
 
671
    def test_properties_in_log(self):
 
672
        """Log includes the custom properties returned by the registered
 
673
        handlers.
 
674
        """
 
675
        wt = self.make_standard_commit('test_properties_in_log')
 
676
 
 
677
        def trivial_custom_prop_handler(revision):
 
678
            return {'test_prop': 'test_value'}
 
679
 
 
680
        # Cleaned up in setUp()
 
681
        log.properties_handler_registry.register(
 
682
            'trivial_custom_prop_handler',
 
683
            trivial_custom_prop_handler)
 
684
        self.assertFormatterResult(b"""\
 
685
------------------------------------------------------------
 
686
revno: 1
 
687
test_prop: test_value
 
688
author: John Doe <jdoe@example.com>
 
689
committer: Lorem Ipsum <test@example.com>
 
690
branch nick: test_properties_in_log
 
691
timestamp: Tue 2005-11-22 00:00:00 +0000
 
692
message:
 
693
  add a
 
694
""",
 
695
                                   wt.branch, log.LongLogFormatter)
 
696
 
 
697
    def test_properties_in_short_log(self):
 
698
        """Log includes the custom properties returned by the registered
 
699
        handlers.
 
700
        """
 
701
        wt = self.make_standard_commit('test_properties_in_short_log')
 
702
 
 
703
        def trivial_custom_prop_handler(revision):
 
704
            return {'test_prop': 'test_value'}
 
705
 
 
706
        log.properties_handler_registry.register(
 
707
            'trivial_custom_prop_handler',
 
708
            trivial_custom_prop_handler)
 
709
        self.assertFormatterResult(b"""\
 
710
    1 John Doe\t2005-11-22
 
711
      test_prop: test_value
 
712
      add a
 
713
 
 
714
""",
 
715
                                   wt.branch, log.ShortLogFormatter)
 
716
 
 
717
    def test_error_in_properties_handler(self):
 
718
        """Log includes the custom properties returned by the registered
 
719
        handlers.
 
720
        """
 
721
        wt = self.make_standard_commit('error_in_properties_handler',
 
722
                                       revprops={u'first_prop': 'first_value'})
 
723
        sio = self.make_utf8_encoded_stringio()
 
724
        formatter = log.LongLogFormatter(to_file=sio)
 
725
 
 
726
        def trivial_custom_prop_handler(revision):
 
727
            raise Exception("a test error")
 
728
 
 
729
        log.properties_handler_registry.register(
 
730
            'trivial_custom_prop_handler',
 
731
            trivial_custom_prop_handler)
 
732
        log.show_log(wt.branch, formatter)
 
733
        self.assertContainsRe(
 
734
            sio.getvalue(), b'brz: ERROR: Exception: a test error')
 
735
 
 
736
    def test_properties_handler_bad_argument(self):
 
737
        wt = self.make_standard_commit('bad_argument',
 
738
                                       revprops={u'a_prop': 'test_value'})
 
739
        sio = self.make_utf8_encoded_stringio()
 
740
        formatter = log.LongLogFormatter(to_file=sio)
 
741
 
 
742
        def bad_argument_prop_handler(revision):
 
743
            return {'custom_prop_name': revision.properties['a_prop']}
 
744
 
 
745
        log.properties_handler_registry.register(
 
746
            'bad_argument_prop_handler',
 
747
            bad_argument_prop_handler)
 
748
 
 
749
        self.assertRaises(AttributeError, formatter.show_properties,
 
750
                          'a revision', '')
 
751
 
 
752
        revision = wt.branch.repository.get_revision(wt.branch.last_revision())
 
753
        formatter.show_properties(revision, '')
 
754
        self.assertEqualDiff(b'custom_prop_name: test_value\n',
 
755
                             sio.getvalue())
 
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
 
 
798
 
 
799
class TestLongLogFormatterWithoutMergeRevisions(TestCaseForLogFormatter):
 
800
 
 
801
    def test_long_verbose_log(self):
 
802
        """Verbose log includes changed files
 
803
 
 
804
        bug #4676
 
805
        """
 
806
        wt = self.make_standard_commit('test_long_verbose_log', authors=[])
 
807
        self.assertFormatterResult(b"""\
 
808
------------------------------------------------------------
 
809
revno: 1
 
810
committer: Lorem Ipsum <test@example.com>
 
811
branch nick: test_long_verbose_log
 
812
timestamp: Tue 2005-11-22 00:00:00 +0000
 
813
message:
 
814
  add a
 
815
added:
 
816
  a
 
817
""",
 
818
                                   wt.branch, log.LongLogFormatter,
 
819
                                   formatter_kwargs=dict(levels=1),
 
820
                                   show_log_kwargs=dict(verbose=True))
 
821
 
 
822
    def test_long_verbose_contain_deltas(self):
 
823
        wt = self.make_branch_and_tree('parent')
 
824
        self.build_tree(['parent/f1', 'parent/f2'])
 
825
        wt.add(['f1', 'f2'])
 
826
        self.wt_commit(wt, 'first post')
 
827
        child_wt = wt.controldir.sprout('child').open_workingtree()
 
828
        os.unlink('child/f1')
 
829
        self.build_tree_contents([('child/f2', b'hello\n')])
 
830
        self.wt_commit(child_wt, 'removed f1 and modified f2')
 
831
        wt.merge_from_branch(child_wt.branch)
 
832
        self.wt_commit(wt, 'merge branch 1')
 
833
        self.assertFormatterResult(b"""\
 
834
------------------------------------------------------------
 
835
revno: 2 [merge]
 
836
committer: Joe Foo <joe@foo.com>
 
837
branch nick: parent
 
838
timestamp: Tue 2005-11-22 00:00:02 +0000
 
839
message:
 
840
  merge branch 1
 
841
removed:
 
842
  f1
 
843
modified:
 
844
  f2
 
845
------------------------------------------------------------
 
846
revno: 1
 
847
committer: Joe Foo <joe@foo.com>
 
848
branch nick: parent
 
849
timestamp: Tue 2005-11-22 00:00:00 +0000
 
850
message:
 
851
  first post
 
852
added:
 
853
  f1
 
854
  f2
 
855
""",
 
856
                                   wt.branch, log.LongLogFormatter,
 
857
                                   formatter_kwargs=dict(levels=1),
 
858
                                   show_log_kwargs=dict(verbose=True))
 
859
 
 
860
    def test_long_trailing_newlines(self):
 
861
        wt = self.make_branch_and_tree('.')
 
862
        b = self.make_commits_with_trailing_newlines(wt)
 
863
        self.assertFormatterResult(b"""\
 
864
------------------------------------------------------------
 
865
revno: 3
 
866
committer: Joe Foo <joe@foo.com>
 
867
branch nick: test
 
868
timestamp: Tue 2005-11-22 00:00:02 +0000
 
869
message:
 
870
  single line with trailing newline
 
871
------------------------------------------------------------
 
872
revno: 2
 
873
committer: Joe Foo <joe@foo.com>
 
874
branch nick: test
 
875
timestamp: Tue 2005-11-22 00:00:01 +0000
 
876
message:
 
877
  multiline
 
878
  log
 
879
  message
 
880
------------------------------------------------------------
 
881
revno: 1
 
882
committer: Joe Foo <joe@foo.com>
 
883
branch nick: test
 
884
timestamp: Tue 2005-11-22 00:00:00 +0000
 
885
message:
 
886
  simple log message
 
887
""",
 
888
                                   b, log.LongLogFormatter,
 
889
                                   formatter_kwargs=dict(levels=1))
 
890
 
 
891
    def test_long_author_in_log(self):
 
892
        """Log includes the author name if it's set in
 
893
        the revision properties
 
894
        """
 
895
        wt = self.make_standard_commit('test_author_log')
 
896
        self.assertFormatterResult(b"""\
 
897
------------------------------------------------------------
 
898
revno: 1
 
899
author: John Doe <jdoe@example.com>
 
900
committer: Lorem Ipsum <test@example.com>
 
901
branch nick: test_author_log
 
902
timestamp: Tue 2005-11-22 00:00:00 +0000
 
903
message:
 
904
  add a
 
905
""",
 
906
                                   wt.branch, log.LongLogFormatter,
 
907
                                   formatter_kwargs=dict(levels=1))
 
908
 
 
909
    def test_long_properties_in_log(self):
 
910
        """Log includes the custom properties returned by the registered
 
911
        handlers.
 
912
        """
 
913
        wt = self.make_standard_commit('test_properties_in_log')
 
914
 
 
915
        def trivial_custom_prop_handler(revision):
 
916
            return {'test_prop': 'test_value'}
 
917
 
 
918
        log.properties_handler_registry.register(
 
919
            'trivial_custom_prop_handler',
 
920
            trivial_custom_prop_handler)
 
921
        self.assertFormatterResult(b"""\
 
922
------------------------------------------------------------
 
923
revno: 1
 
924
test_prop: test_value
 
925
author: John Doe <jdoe@example.com>
 
926
committer: Lorem Ipsum <test@example.com>
 
927
branch nick: test_properties_in_log
 
928
timestamp: Tue 2005-11-22 00:00:00 +0000
 
929
message:
 
930
  add a
 
931
""",
 
932
                                   wt.branch, log.LongLogFormatter,
 
933
                                   formatter_kwargs=dict(levels=1))
 
934
 
 
935
 
 
936
class TestLineLogFormatter(TestCaseForLogFormatter):
 
937
 
 
938
    def test_line_log(self):
 
939
        """Line log should show revno
 
940
 
 
941
        bug #5162
 
942
        """
 
943
        wt = self.make_standard_commit('test-line-log',
 
944
                                       committer='Line-Log-Formatter Tester <test@line.log>',
 
945
                                       authors=[])
 
946
        self.assertFormatterResult(b"""\
 
947
1: Line-Log-Formatte... 2005-11-22 add a
 
948
""",
 
949
                                   wt.branch, log.LineLogFormatter)
 
950
 
 
951
    def test_trailing_newlines(self):
 
952
        wt = self.make_branch_and_tree('.')
 
953
        b = self.make_commits_with_trailing_newlines(wt)
 
954
        self.assertFormatterResult(b"""\
 
955
3: Joe Foo 2005-11-22 single line with trailing newline
 
956
2: Joe Foo 2005-11-22 multiline
 
957
1: Joe Foo 2005-11-22 simple log message
 
958
""",
 
959
                                   b, log.LineLogFormatter)
 
960
 
 
961
    def test_line_log_single_merge_revision(self):
 
962
        wt = self._prepare_tree_with_merges()
 
963
        revspec = revisionspec.RevisionSpec.from_string('1.1.1')
 
964
        rev = revspec.in_history(wt.branch)
 
965
        self.assertFormatterResult(b"""\
 
966
1.1.1: Joe Foo 2005-11-22 rev-merged
 
967
""",
 
968
                                   wt.branch, log.LineLogFormatter,
 
969
                                   show_log_kwargs=dict(start_revision=rev, end_revision=rev))
 
970
 
 
971
    def test_line_log_with_tags(self):
 
972
        wt = self._prepare_tree_with_merges(with_tags=True)
 
973
        self.assertFormatterResult(b"""\
 
974
3: Joe Foo 2005-11-22 {v1.0, v1.0rc1} rev-3
 
975
2: Joe Foo 2005-11-22 [merge] {v0.2} rev-2
 
976
1: Joe Foo 2005-11-22 rev-1
 
977
""",
 
978
                                   wt.branch, log.LineLogFormatter)
 
979
 
 
980
 
 
981
class TestLineLogFormatterWithMergeRevisions(TestCaseForLogFormatter):
 
982
 
 
983
    def test_line_merge_revs_log(self):
 
984
        """Line log should show revno
 
985
 
 
986
        bug #5162
 
987
        """
 
988
        wt = self.make_standard_commit('test-line-log',
 
989
                                       committer='Line-Log-Formatter Tester <test@line.log>',
 
990
                                       authors=[])
 
991
        self.assertFormatterResult(b"""\
 
992
1: Line-Log-Formatte... 2005-11-22 add a
 
993
""",
 
994
                                   wt.branch, log.LineLogFormatter)
 
995
 
 
996
    def test_line_merge_revs_log_single_merge_revision(self):
 
997
        wt = self._prepare_tree_with_merges()
 
998
        revspec = revisionspec.RevisionSpec.from_string('1.1.1')
 
999
        rev = revspec.in_history(wt.branch)
 
1000
        self.assertFormatterResult(b"""\
 
1001
1.1.1: Joe Foo 2005-11-22 rev-merged
 
1002
""",
 
1003
                                   wt.branch, log.LineLogFormatter,
 
1004
                                   formatter_kwargs=dict(levels=0),
 
1005
                                   show_log_kwargs=dict(start_revision=rev, end_revision=rev))
 
1006
 
 
1007
    def test_line_merge_revs_log_with_merges(self):
 
1008
        wt = self._prepare_tree_with_merges()
 
1009
        self.assertFormatterResult(b"""\
 
1010
2: Joe Foo 2005-11-22 [merge] rev-2
 
1011
  1.1.1: Joe Foo 2005-11-22 rev-merged
 
1012
1: Joe Foo 2005-11-22 rev-1
 
1013
""",
 
1014
                                   wt.branch, log.LineLogFormatter,
 
1015
                                   formatter_kwargs=dict(levels=0))
 
1016
 
 
1017
 
 
1018
class TestGnuChangelogFormatter(TestCaseForLogFormatter):
 
1019
 
 
1020
    def test_gnu_changelog(self):
 
1021
        wt = self.make_standard_commit('nicky', authors=[])
 
1022
        self.assertFormatterResult(b'''\
 
1023
2005-11-22  Lorem Ipsum  <test@example.com>
 
1024
 
 
1025
\tadd a
 
1026
 
 
1027
''',
 
1028
                                   wt.branch, log.GnuChangelogLogFormatter)
 
1029
 
 
1030
    def test_with_authors(self):
 
1031
        wt = self.make_standard_commit('nicky',
 
1032
                                       authors=['Fooa Fooz <foo@example.com>',
 
1033
                                                'Bari Baro <bar@example.com>'])
 
1034
        self.assertFormatterResult(b'''\
 
1035
2005-11-22  Fooa Fooz  <foo@example.com>
 
1036
 
 
1037
\tadd a
 
1038
 
 
1039
''',
 
1040
                                   wt.branch, log.GnuChangelogLogFormatter)
 
1041
 
 
1042
    def test_verbose(self):
 
1043
        wt = self.make_standard_commit('nicky')
 
1044
        self.assertFormatterResult(b'''\
 
1045
2005-11-22  John Doe  <jdoe@example.com>
 
1046
 
 
1047
\t* a:
 
1048
 
 
1049
\tadd a
 
1050
 
 
1051
''',
 
1052
                                   wt.branch, log.GnuChangelogLogFormatter,
 
1053
                                   show_log_kwargs=dict(verbose=True))
 
1054
 
 
1055
 
 
1056
class TestShowChangedRevisions(tests.TestCaseWithTransport):
 
1057
 
 
1058
    def test_show_changed_revisions_verbose(self):
 
1059
        tree = self.make_branch_and_tree('tree_a')
 
1060
        self.build_tree(['tree_a/foo'])
 
1061
        tree.add('foo')
 
1062
        tree.commit('bar', rev_id=b'bar-id')
 
1063
        s = self.make_utf8_encoded_stringio()
 
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')
 
1067
 
 
1068
 
 
1069
class TestLogFormatter(tests.TestCase):
 
1070
 
 
1071
    def setUp(self):
 
1072
        super(TestLogFormatter, self).setUp()
 
1073
        self.rev = revision.Revision(b'a-id')
 
1074
        self.lf = log.LogFormatter(None)
 
1075
 
 
1076
    def test_short_committer(self):
 
1077
        def assertCommitter(expected, committer):
 
1078
            self.rev.committer = committer
 
1079
            self.assertEqual(expected, self.lf.short_committer(self.rev))
 
1080
 
 
1081
        assertCommitter('John Doe', 'John Doe <jdoe@example.com>')
 
1082
        assertCommitter('John Smith', 'John Smith <jsmith@example.com>')
 
1083
        assertCommitter('John Smith', 'John Smith')
 
1084
        assertCommitter('jsmith@example.com', 'jsmith@example.com')
 
1085
        assertCommitter('jsmith@example.com', '<jsmith@example.com>')
 
1086
        assertCommitter('John Smith', 'John Smith jsmith@example.com')
 
1087
 
 
1088
    def test_short_author(self):
 
1089
        def assertAuthor(expected, author):
 
1090
            self.rev.properties['author'] = author
 
1091
            self.assertEqual(expected, self.lf.short_author(self.rev))
 
1092
 
 
1093
        assertAuthor('John Smith', 'John Smith <jsmith@example.com>')
 
1094
        assertAuthor('John Smith', 'John Smith')
 
1095
        assertAuthor('jsmith@example.com', 'jsmith@example.com')
 
1096
        assertAuthor('jsmith@example.com', '<jsmith@example.com>')
 
1097
        assertAuthor('John Smith', 'John Smith jsmith@example.com')
 
1098
 
 
1099
    def test_short_author_from_committer(self):
 
1100
        self.rev.committer = 'John Doe <jdoe@example.com>'
 
1101
        self.assertEqual('John Doe', self.lf.short_author(self.rev))
 
1102
 
 
1103
    def test_short_author_from_authors(self):
 
1104
        self.rev.properties['authors'] = ('John Smith <jsmith@example.com>\n'
 
1105
                                          'Jane Rey <jrey@example.com>')
 
1106
        self.assertEqual('John Smith', self.lf.short_author(self.rev))
 
1107
 
 
1108
 
 
1109
class TestReverseByDepth(tests.TestCase):
 
1110
    """Test reverse_by_depth behavior.
 
1111
 
 
1112
    This is used to present revisions in forward (oldest first) order in a nice
 
1113
    layout.
 
1114
 
 
1115
    The tests use lighter revision description to ease reading.
 
1116
    """
 
1117
 
 
1118
    def assertReversed(self, forward, backward):
 
1119
        # Transform the descriptions to suit the API: tests use (revno, depth),
 
1120
        # while the API expects (revid, revno, depth)
 
1121
        def complete_revisions(l):
 
1122
            """Transform the description to suit the API.
 
1123
 
 
1124
            Tests use (revno, depth) whil the API expects (revid, revno, depth).
 
1125
            Since the revid is arbitrary, we just duplicate revno
 
1126
            """
 
1127
            return [(r, r, d) for r, d in l]
 
1128
        forward = complete_revisions(forward)
 
1129
        backward = complete_revisions(backward)
 
1130
        self.assertEqual(forward, log.reverse_by_depth(backward))
 
1131
 
 
1132
    def test_mainline_revisions(self):
 
1133
        self.assertReversed([('1', 0), ('2', 0)],
 
1134
                            [('2', 0), ('1', 0)])
 
1135
 
 
1136
    def test_merged_revisions(self):
 
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
 
 
1140
    def test_shifted_merged_revisions(self):
 
1141
        """Test irregular layout.
 
1142
 
 
1143
        Requesting revisions touching a file can produce "holes" in the depths.
 
1144
        """
 
1145
        self.assertReversed([('1', 0), ('2', 0), ('1.1', 2), ('1.2', 2), ],
 
1146
                            [('2', 0), ('1.2', 2), ('1.1', 2), ('1', 0), ])
 
1147
 
 
1148
    def test_merged_without_child_revisions(self):
 
1149
        """Test irregular layout.
 
1150
 
 
1151
        Revision ranges can produce "holes" in the depths.
 
1152
        """
 
1153
        # When a revision of higher depth doesn't follow one of lower depth, we
 
1154
        # assume a lower depth one is virtually there
 
1155
        self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
 
1156
                            [('4', 4), ('3', 3), ('2', 2), ('1', 2), ])
 
1157
        # So we get the same order after reversing below even if the original
 
1158
        # revisions are not in the same order.
 
1159
        self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
 
1160
                            [('3', 3), ('4', 4), ('2', 2), ('1', 2), ])
 
1161
 
 
1162
 
 
1163
class TestHistoryChange(tests.TestCaseWithTransport):
 
1164
 
 
1165
    def setup_a_tree(self):
 
1166
        tree = self.make_branch_and_tree('tree')
 
1167
        tree.lock_write()
 
1168
        self.addCleanup(tree.unlock)
 
1169
        tree.commit('1a', rev_id=b'1a')
 
1170
        tree.commit('2a', rev_id=b'2a')
 
1171
        tree.commit('3a', rev_id=b'3a')
 
1172
        return tree
 
1173
 
 
1174
    def setup_ab_tree(self):
 
1175
        tree = self.setup_a_tree()
 
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')
 
1180
        return tree
 
1181
 
 
1182
    def setup_ac_tree(self):
 
1183
        tree = self.setup_a_tree()
 
1184
        tree.set_last_revision(revision.NULL_REVISION)
 
1185
        tree.branch.set_last_revision_info(0, revision.NULL_REVISION)
 
1186
        tree.commit('1c', rev_id=b'1c')
 
1187
        tree.commit('2c', rev_id=b'2c')
 
1188
        tree.commit('3c', rev_id=b'3c')
 
1189
        return tree
 
1190
 
 
1191
    def test_all_new(self):
 
1192
        tree = self.setup_ab_tree()
 
1193
        old, new = log.get_history_change(b'1a', b'3a', tree.branch.repository)
 
1194
        self.assertEqual([], old)
 
1195
        self.assertEqual([b'2a', b'3a'], new)
 
1196
 
 
1197
    def test_all_old(self):
 
1198
        tree = self.setup_ab_tree()
 
1199
        old, new = log.get_history_change(b'3a', b'1a', tree.branch.repository)
 
1200
        self.assertEqual([], new)
 
1201
        self.assertEqual([b'2a', b'3a'], old)
 
1202
 
 
1203
    def test_null_old(self):
 
1204
        tree = self.setup_ab_tree()
 
1205
        old, new = log.get_history_change(revision.NULL_REVISION,
 
1206
                                          b'3a', tree.branch.repository)
 
1207
        self.assertEqual([], old)
 
1208
        self.assertEqual([b'1a', b'2a', b'3a'], new)
 
1209
 
 
1210
    def test_null_new(self):
 
1211
        tree = self.setup_ab_tree()
 
1212
        old, new = log.get_history_change(b'3a', revision.NULL_REVISION,
 
1213
                                          tree.branch.repository)
 
1214
        self.assertEqual([], new)
 
1215
        self.assertEqual([b'1a', b'2a', b'3a'], old)
 
1216
 
 
1217
    def test_diverged(self):
 
1218
        tree = self.setup_ab_tree()
 
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'])
 
1222
 
 
1223
    def test_unrelated(self):
 
1224
        tree = self.setup_ac_tree()
 
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'])
 
1228
 
 
1229
    def test_show_branch_change(self):
 
1230
        tree = self.setup_ab_tree()
 
1231
        s = StringIO()
 
1232
        log.show_branch_change(tree.branch, s, 3, b'3a')
 
1233
        self.assertContainsRe(s.getvalue(),
 
1234
                              '[*]{60}\nRemoved Revisions:\n(.|\n)*2a(.|\n)*3a(.|\n)*'
 
1235
                              '[*]{60}\n\nAdded Revisions:\n(.|\n)*2b(.|\n)*3b')
 
1236
 
 
1237
    def test_show_branch_change_no_change(self):
 
1238
        tree = self.setup_ab_tree()
 
1239
        s = StringIO()
 
1240
        log.show_branch_change(tree.branch, s, 3, b'3b')
 
1241
        self.assertEqual(s.getvalue(),
 
1242
                         'Nothing seems to have changed\n')
 
1243
 
 
1244
    def test_show_branch_change_no_old(self):
 
1245
        tree = self.setup_ab_tree()
 
1246
        s = StringIO()
 
1247
        log.show_branch_change(tree.branch, s, 2, b'2b')
 
1248
        self.assertContainsRe(s.getvalue(), 'Added Revisions:')
 
1249
        self.assertNotContainsRe(s.getvalue(), 'Removed Revisions:')
 
1250
 
 
1251
    def test_show_branch_change_no_new(self):
 
1252
        tree = self.setup_ab_tree()
 
1253
        tree.branch.set_last_revision_info(2, b'2b')
 
1254
        s = StringIO()
 
1255
        log.show_branch_change(tree.branch, s, 3, b'3b')
 
1256
        self.assertContainsRe(s.getvalue(), 'Removed Revisions:')
 
1257
        self.assertNotContainsRe(s.getvalue(), 'Added Revisions:')
 
1258
 
 
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
 
 
1378
 
 
1379
class TestLogWithBugs(TestCaseForLogFormatter, TestLogMixin):
 
1380
 
 
1381
    def setUp(self):
 
1382
        super(TestLogWithBugs, self).setUp()
 
1383
        log.properties_handler_registry.register(
 
1384
            'bugs_properties_handler',
 
1385
            log._bugs_properties_handler)
 
1386
 
 
1387
    def make_commits_with_bugs(self):
 
1388
        """Helper method for LogFormatter tests"""
 
1389
        tree = self.make_branch_and_tree(u'.')
 
1390
        self.build_tree(['a', 'b'])
 
1391
        tree.add('a')
 
1392
        self.wt_commit(tree, 'simple log message', rev_id=b'a1',
 
1393
                       revprops={u'bugs': 'test://bug/id fixed'})
 
1394
        tree.add('b')
 
1395
        self.wt_commit(tree, 'multiline\nlog\nmessage\n', rev_id=b'a2',
 
1396
                       authors=['Joe Bar <joe@bar.com>'],
 
1397
                       revprops={u'bugs': 'test://bug/id fixed\n'
 
1398
                                 'test://bug/2 fixed'})
 
1399
        return tree
 
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
""")
 
1429
 
 
1430
    def test_long_bugs(self):
 
1431
        tree = self.make_commits_with_bugs()
 
1432
        self.assertFormatterResult(b"""\
 
1433
------------------------------------------------------------
 
1434
revno: 2
 
1435
fixes bugs: test://bug/id test://bug/2
 
1436
author: Joe Bar <joe@bar.com>
 
1437
committer: Joe Foo <joe@foo.com>
 
1438
branch nick: work
 
1439
timestamp: Tue 2005-11-22 00:00:01 +0000
 
1440
message:
 
1441
  multiline
 
1442
  log
 
1443
  message
 
1444
------------------------------------------------------------
 
1445
revno: 1
 
1446
fixes bug: test://bug/id
 
1447
committer: Joe Foo <joe@foo.com>
 
1448
branch nick: work
 
1449
timestamp: Tue 2005-11-22 00:00:00 +0000
 
1450
message:
 
1451
  simple log message
 
1452
""",
 
1453
                                   tree.branch, log.LongLogFormatter)
 
1454
 
 
1455
    def test_short_bugs(self):
 
1456
        tree = self.make_commits_with_bugs()
 
1457
        self.assertFormatterResult(b"""\
 
1458
    2 Joe Bar\t2005-11-22
 
1459
      fixes bugs: test://bug/id test://bug/2
 
1460
      multiline
 
1461
      log
 
1462
      message
 
1463
 
 
1464
    1 Joe Foo\t2005-11-22
 
1465
      fixes bug: test://bug/id
 
1466
      simple log message
 
1467
 
 
1468
""",
 
1469
                                   tree.branch, log.ShortLogFormatter)
 
1470
 
 
1471
    def test_wrong_bugs_property(self):
 
1472
        tree = self.make_branch_and_tree(u'.')
 
1473
        self.build_tree(['foo'])
 
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")
 
1492
 
 
1493
    def test_bugs_handler_present(self):
 
1494
        self.properties_handler_registry.get('bugs_properties_handler')
 
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
 
 
1641
class TestLogExcludeAncestry(tests.TestCaseWithTransport):
 
1642
 
 
1643
    def make_branch_with_alternate_ancestries(self, relpath='.'):
 
1644
        # See test_merge_sorted_exclude_ancestry below for the difference with
 
1645
        # bt.per_branch.test_iter_merge_sorted_revision.
 
1646
        # TestIterMergeSortedRevisionsBushyGraph.
 
1647
        # make_branch_with_alternate_ancestries
 
1648
        # and test_merge_sorted_exclude_ancestry
 
1649
        # See the FIXME in assertLogRevnos too.
 
1650
        builder = branchbuilder.BranchBuilder(self.get_transport(relpath))
 
1651
        # 1
 
1652
        # |\
 
1653
        # 2 \
 
1654
        # |  |
 
1655
        # |  1.1.1
 
1656
        # |  | \
 
1657
        # |  |  1.2.1
 
1658
        # |  | /
 
1659
        # |  1.1.2
 
1660
        # | /
 
1661
        # 3
 
1662
        builder.start_series()
 
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')
 
1671
        builder.finish_series()
 
1672
        br = builder.get_branch()
 
1673
        br.lock_read()
 
1674
        self.addCleanup(br.unlock)
 
1675
        return br
 
1676
 
 
1677
    def assertLogRevnos(self, expected_revnos, b, start, end,
 
1678
                        exclude_common_ancestry, generate_merge_revisions=True):
 
1679
        # FIXME: the layering in log makes it hard to test intermediate levels,
 
1680
        # I wish adding filters with their parameters was easier...
 
1681
        # -- vila 20100413
 
1682
        iter_revs = log._calc_view_revisions(
 
1683
            b, start, end, direction='reverse',
 
1684
            generate_merge_revisions=generate_merge_revisions,
 
1685
            exclude_common_ancestry=exclude_common_ancestry)
 
1686
        self.assertEqual(expected_revnos,
 
1687
                         [revid for revid, revno, depth in iter_revs])
 
1688
 
 
1689
    def test_merge_sorted_exclude_ancestry(self):
 
1690
        b = self.make_branch_with_alternate_ancestries()
 
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)
 
1693
        # '2' is part of the '3' ancestry but not part of '1.1.1' ancestry so
 
1694
        # it should be mentioned even if merge_sort order will make it appear
 
1695
        # after 1.1.1
 
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)