/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to breezy/tests/test_log.py

  • Committer: Jelmer Vernooij
  • Date: 2020-05-24 00:39:50 UTC
  • mto: This revision was merged to the branch mainline in revision 7504.
  • Revision ID: jelmer@jelmer.uk-20200524003950-bbc545r76vc5yajg
Add github action.

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
from io import (
 
18
    BytesIO,
 
19
    StringIO,
 
20
    )
 
21
import os
 
22
import re
 
23
 
 
24
from .. import (
 
25
    branchbuilder,
 
26
    errors,
 
27
    log,
 
28
    registry,
 
29
    revision,
 
30
    revisionspec,
 
31
    tests,
 
32
    gpg,
 
33
    trace,
 
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.path[1] or x.path[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([chr(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([chr(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
        log.show_log(wt.branch, formatter)
 
732
        self.assertContainsRe(
 
733
            sio.getvalue(), b'brz: ERROR: Exception: a test error')
 
734
 
 
735
    def test_properties_handler_bad_argument(self):
 
736
        wt = self.make_standard_commit('bad_argument',
 
737
                                       revprops={u'a_prop': 'test_value'})
 
738
        sio = self.make_utf8_encoded_stringio()
 
739
        formatter = log.LongLogFormatter(to_file=sio)
 
740
 
 
741
        def bad_argument_prop_handler(revision):
 
742
            return {'custom_prop_name': revision.properties['a_prop']}
 
743
 
 
744
        log.properties_handler_registry.register(
 
745
            'bad_argument_prop_handler',
 
746
            bad_argument_prop_handler)
 
747
 
 
748
        self.assertRaises(AttributeError, formatter.show_properties,
 
749
                          'a revision', '')
 
750
 
 
751
        revision = wt.branch.repository.get_revision(wt.branch.last_revision())
 
752
        formatter.show_properties(revision, '')
 
753
        self.assertEqualDiff(b'custom_prop_name: test_value\n',
 
754
                             sio.getvalue())
 
755
 
 
756
    def test_show_ids(self):
 
757
        wt = self.make_branch_and_tree('parent')
 
758
        self.build_tree(['parent/f1', 'parent/f2'])
 
759
        wt.add(['f1', 'f2'])
 
760
        self.wt_commit(wt, 'first post', rev_id=b'a')
 
761
        child_wt = wt.controldir.sprout('child').open_workingtree()
 
762
        self.wt_commit(child_wt, 'branch 1 changes', rev_id=b'b')
 
763
        wt.merge_from_branch(child_wt.branch)
 
764
        self.wt_commit(wt, 'merge branch 1', rev_id=b'c')
 
765
        self.assertFormatterResult(b"""\
 
766
------------------------------------------------------------
 
767
revno: 2 [merge]
 
768
revision-id: c
 
769
parent: a
 
770
parent: b
 
771
committer: Joe Foo <joe@foo.com>
 
772
branch nick: parent
 
773
timestamp: Tue 2005-11-22 00:00:02 +0000
 
774
message:
 
775
  merge branch 1
 
776
    ------------------------------------------------------------
 
777
    revno: 1.1.1
 
778
    revision-id: b
 
779
    parent: a
 
780
    committer: Joe Foo <joe@foo.com>
 
781
    branch nick: child
 
782
    timestamp: Tue 2005-11-22 00:00:01 +0000
 
783
    message:
 
784
      branch 1 changes
 
785
------------------------------------------------------------
 
786
revno: 1
 
787
revision-id: a
 
788
committer: Joe Foo <joe@foo.com>
 
789
branch nick: parent
 
790
timestamp: Tue 2005-11-22 00:00:00 +0000
 
791
message:
 
792
  first post
 
793
""",
 
794
                                   wt.branch, log.LongLogFormatter,
 
795
                                   formatter_kwargs=dict(levels=0, show_ids=True))
 
796
 
 
797
 
 
798
class TestLongLogFormatterWithoutMergeRevisions(TestCaseForLogFormatter):
 
799
 
 
800
    def test_long_verbose_log(self):
 
801
        """Verbose log includes changed files
 
802
 
 
803
        bug #4676
 
804
        """
 
805
        wt = self.make_standard_commit('test_long_verbose_log', authors=[])
 
806
        self.assertFormatterResult(b"""\
 
807
------------------------------------------------------------
 
808
revno: 1
 
809
committer: Lorem Ipsum <test@example.com>
 
810
branch nick: test_long_verbose_log
 
811
timestamp: Tue 2005-11-22 00:00:00 +0000
 
812
message:
 
813
  add a
 
814
added:
 
815
  a
 
816
""",
 
817
                                   wt.branch, log.LongLogFormatter,
 
818
                                   formatter_kwargs=dict(levels=1),
 
819
                                   show_log_kwargs=dict(verbose=True))
 
820
 
 
821
    def test_long_verbose_contain_deltas(self):
 
822
        wt = self.make_branch_and_tree('parent')
 
823
        self.build_tree(['parent/f1', 'parent/f2'])
 
824
        wt.add(['f1', 'f2'])
 
825
        self.wt_commit(wt, 'first post')
 
826
        child_wt = wt.controldir.sprout('child').open_workingtree()
 
827
        os.unlink('child/f1')
 
828
        self.build_tree_contents([('child/f2', b'hello\n')])
 
829
        self.wt_commit(child_wt, 'removed f1 and modified f2')
 
830
        wt.merge_from_branch(child_wt.branch)
 
831
        self.wt_commit(wt, 'merge branch 1')
 
832
        self.assertFormatterResult(b"""\
 
833
------------------------------------------------------------
 
834
revno: 2 [merge]
 
835
committer: Joe Foo <joe@foo.com>
 
836
branch nick: parent
 
837
timestamp: Tue 2005-11-22 00:00:02 +0000
 
838
message:
 
839
  merge branch 1
 
840
removed:
 
841
  f1
 
842
modified:
 
843
  f2
 
844
------------------------------------------------------------
 
845
revno: 1
 
846
committer: Joe Foo <joe@foo.com>
 
847
branch nick: parent
 
848
timestamp: Tue 2005-11-22 00:00:00 +0000
 
849
message:
 
850
  first post
 
851
added:
 
852
  f1
 
853
  f2
 
854
""",
 
855
                                   wt.branch, log.LongLogFormatter,
 
856
                                   formatter_kwargs=dict(levels=1),
 
857
                                   show_log_kwargs=dict(verbose=True))
 
858
 
 
859
    def test_long_trailing_newlines(self):
 
860
        wt = self.make_branch_and_tree('.')
 
861
        b = self.make_commits_with_trailing_newlines(wt)
 
862
        self.assertFormatterResult(b"""\
 
863
------------------------------------------------------------
 
864
revno: 3
 
865
committer: Joe Foo <joe@foo.com>
 
866
branch nick: test
 
867
timestamp: Tue 2005-11-22 00:00:02 +0000
 
868
message:
 
869
  single line with trailing newline
 
870
------------------------------------------------------------
 
871
revno: 2
 
872
committer: Joe Foo <joe@foo.com>
 
873
branch nick: test
 
874
timestamp: Tue 2005-11-22 00:00:01 +0000
 
875
message:
 
876
  multiline
 
877
  log
 
878
  message
 
879
------------------------------------------------------------
 
880
revno: 1
 
881
committer: Joe Foo <joe@foo.com>
 
882
branch nick: test
 
883
timestamp: Tue 2005-11-22 00:00:00 +0000
 
884
message:
 
885
  simple log message
 
886
""",
 
887
                                   b, log.LongLogFormatter,
 
888
                                   formatter_kwargs=dict(levels=1))
 
889
 
 
890
    def test_long_author_in_log(self):
 
891
        """Log includes the author name if it's set in
 
892
        the revision properties
 
893
        """
 
894
        wt = self.make_standard_commit('test_author_log')
 
895
        self.assertFormatterResult(b"""\
 
896
------------------------------------------------------------
 
897
revno: 1
 
898
author: John Doe <jdoe@example.com>
 
899
committer: Lorem Ipsum <test@example.com>
 
900
branch nick: test_author_log
 
901
timestamp: Tue 2005-11-22 00:00:00 +0000
 
902
message:
 
903
  add a
 
904
""",
 
905
                                   wt.branch, log.LongLogFormatter,
 
906
                                   formatter_kwargs=dict(levels=1))
 
907
 
 
908
    def test_long_properties_in_log(self):
 
909
        """Log includes the custom properties returned by the registered
 
910
        handlers.
 
911
        """
 
912
        wt = self.make_standard_commit('test_properties_in_log')
 
913
 
 
914
        def trivial_custom_prop_handler(revision):
 
915
            return {'test_prop': 'test_value'}
 
916
 
 
917
        log.properties_handler_registry.register(
 
918
            'trivial_custom_prop_handler',
 
919
            trivial_custom_prop_handler)
 
920
        self.assertFormatterResult(b"""\
 
921
------------------------------------------------------------
 
922
revno: 1
 
923
test_prop: test_value
 
924
author: John Doe <jdoe@example.com>
 
925
committer: Lorem Ipsum <test@example.com>
 
926
branch nick: test_properties_in_log
 
927
timestamp: Tue 2005-11-22 00:00:00 +0000
 
928
message:
 
929
  add a
 
930
""",
 
931
                                   wt.branch, log.LongLogFormatter,
 
932
                                   formatter_kwargs=dict(levels=1))
 
933
 
 
934
 
 
935
class TestLineLogFormatter(TestCaseForLogFormatter):
 
936
 
 
937
    def test_line_log(self):
 
938
        """Line log should show revno
 
939
 
 
940
        bug #5162
 
941
        """
 
942
        wt = self.make_standard_commit('test-line-log',
 
943
                                       committer='Line-Log-Formatter Tester <test@line.log>',
 
944
                                       authors=[])
 
945
        self.assertFormatterResult(b"""\
 
946
1: Line-Log-Formatte... 2005-11-22 add a
 
947
""",
 
948
                                   wt.branch, log.LineLogFormatter)
 
949
 
 
950
    def test_trailing_newlines(self):
 
951
        wt = self.make_branch_and_tree('.')
 
952
        b = self.make_commits_with_trailing_newlines(wt)
 
953
        self.assertFormatterResult(b"""\
 
954
3: Joe Foo 2005-11-22 single line with trailing newline
 
955
2: Joe Foo 2005-11-22 multiline
 
956
1: Joe Foo 2005-11-22 simple log message
 
957
""",
 
958
                                   b, log.LineLogFormatter)
 
959
 
 
960
    def test_line_log_single_merge_revision(self):
 
961
        wt = self._prepare_tree_with_merges()
 
962
        revspec = revisionspec.RevisionSpec.from_string('1.1.1')
 
963
        rev = revspec.in_history(wt.branch)
 
964
        self.assertFormatterResult(b"""\
 
965
1.1.1: Joe Foo 2005-11-22 rev-merged
 
966
""",
 
967
                                   wt.branch, log.LineLogFormatter,
 
968
                                   show_log_kwargs=dict(start_revision=rev, end_revision=rev))
 
969
 
 
970
    def test_line_log_with_tags(self):
 
971
        wt = self._prepare_tree_with_merges(with_tags=True)
 
972
        self.assertFormatterResult(b"""\
 
973
3: Joe Foo 2005-11-22 {v1.0, v1.0rc1} rev-3
 
974
2: Joe Foo 2005-11-22 [merge] {v0.2} rev-2
 
975
1: Joe Foo 2005-11-22 rev-1
 
976
""",
 
977
                                   wt.branch, log.LineLogFormatter)
 
978
 
 
979
 
 
980
class TestLineLogFormatterWithMergeRevisions(TestCaseForLogFormatter):
 
981
 
 
982
    def test_line_merge_revs_log(self):
 
983
        """Line log should show revno
 
984
 
 
985
        bug #5162
 
986
        """
 
987
        wt = self.make_standard_commit('test-line-log',
 
988
                                       committer='Line-Log-Formatter Tester <test@line.log>',
 
989
                                       authors=[])
 
990
        self.assertFormatterResult(b"""\
 
991
1: Line-Log-Formatte... 2005-11-22 add a
 
992
""",
 
993
                                   wt.branch, log.LineLogFormatter)
 
994
 
 
995
    def test_line_merge_revs_log_single_merge_revision(self):
 
996
        wt = self._prepare_tree_with_merges()
 
997
        revspec = revisionspec.RevisionSpec.from_string('1.1.1')
 
998
        rev = revspec.in_history(wt.branch)
 
999
        self.assertFormatterResult(b"""\
 
1000
1.1.1: Joe Foo 2005-11-22 rev-merged
 
1001
""",
 
1002
                                   wt.branch, log.LineLogFormatter,
 
1003
                                   formatter_kwargs=dict(levels=0),
 
1004
                                   show_log_kwargs=dict(start_revision=rev, end_revision=rev))
 
1005
 
 
1006
    def test_line_merge_revs_log_with_merges(self):
 
1007
        wt = self._prepare_tree_with_merges()
 
1008
        self.assertFormatterResult(b"""\
 
1009
2: Joe Foo 2005-11-22 [merge] rev-2
 
1010
  1.1.1: Joe Foo 2005-11-22 rev-merged
 
1011
1: Joe Foo 2005-11-22 rev-1
 
1012
""",
 
1013
                                   wt.branch, log.LineLogFormatter,
 
1014
                                   formatter_kwargs=dict(levels=0))
 
1015
 
 
1016
 
 
1017
class TestGnuChangelogFormatter(TestCaseForLogFormatter):
 
1018
 
 
1019
    def test_gnu_changelog(self):
 
1020
        wt = self.make_standard_commit('nicky', authors=[])
 
1021
        self.assertFormatterResult(b'''\
 
1022
2005-11-22  Lorem Ipsum  <test@example.com>
 
1023
 
 
1024
\tadd a
 
1025
 
 
1026
''',
 
1027
                                   wt.branch, log.GnuChangelogLogFormatter)
 
1028
 
 
1029
    def test_with_authors(self):
 
1030
        wt = self.make_standard_commit('nicky',
 
1031
                                       authors=['Fooa Fooz <foo@example.com>',
 
1032
                                                'Bari Baro <bar@example.com>'])
 
1033
        self.assertFormatterResult(b'''\
 
1034
2005-11-22  Fooa Fooz  <foo@example.com>
 
1035
 
 
1036
\tadd a
 
1037
 
 
1038
''',
 
1039
                                   wt.branch, log.GnuChangelogLogFormatter)
 
1040
 
 
1041
    def test_verbose(self):
 
1042
        wt = self.make_standard_commit('nicky')
 
1043
        self.assertFormatterResult(b'''\
 
1044
2005-11-22  John Doe  <jdoe@example.com>
 
1045
 
 
1046
\t* a:
 
1047
 
 
1048
\tadd a
 
1049
 
 
1050
''',
 
1051
                                   wt.branch, log.GnuChangelogLogFormatter,
 
1052
                                   show_log_kwargs=dict(verbose=True))
 
1053
 
 
1054
 
 
1055
class TestShowChangedRevisions(tests.TestCaseWithTransport):
 
1056
 
 
1057
    def test_show_changed_revisions_verbose(self):
 
1058
        tree = self.make_branch_and_tree('tree_a')
 
1059
        self.build_tree(['tree_a/foo'])
 
1060
        tree.add('foo')
 
1061
        tree.commit('bar', rev_id=b'bar-id')
 
1062
        s = self.make_utf8_encoded_stringio()
 
1063
        log.show_changed_revisions(tree.branch, [], [b'bar-id'], s)
 
1064
        self.assertContainsRe(s.getvalue(), b'bar')
 
1065
        self.assertNotContainsRe(s.getvalue(), b'foo')
 
1066
 
 
1067
 
 
1068
class TestLogFormatter(tests.TestCase):
 
1069
 
 
1070
    def setUp(self):
 
1071
        super(TestLogFormatter, self).setUp()
 
1072
        self.rev = revision.Revision(b'a-id')
 
1073
        self.lf = log.LogFormatter(None)
 
1074
 
 
1075
    def test_short_committer(self):
 
1076
        def assertCommitter(expected, committer):
 
1077
            self.rev.committer = committer
 
1078
            self.assertEqual(expected, self.lf.short_committer(self.rev))
 
1079
 
 
1080
        assertCommitter('John Doe', 'John Doe <jdoe@example.com>')
 
1081
        assertCommitter('John Smith', 'John Smith <jsmith@example.com>')
 
1082
        assertCommitter('John Smith', 'John Smith')
 
1083
        assertCommitter('jsmith@example.com', 'jsmith@example.com')
 
1084
        assertCommitter('jsmith@example.com', '<jsmith@example.com>')
 
1085
        assertCommitter('John Smith', 'John Smith jsmith@example.com')
 
1086
 
 
1087
    def test_short_author(self):
 
1088
        def assertAuthor(expected, author):
 
1089
            self.rev.properties['author'] = author
 
1090
            self.assertEqual(expected, self.lf.short_author(self.rev))
 
1091
 
 
1092
        assertAuthor('John Smith', 'John Smith <jsmith@example.com>')
 
1093
        assertAuthor('John Smith', 'John Smith')
 
1094
        assertAuthor('jsmith@example.com', 'jsmith@example.com')
 
1095
        assertAuthor('jsmith@example.com', '<jsmith@example.com>')
 
1096
        assertAuthor('John Smith', 'John Smith jsmith@example.com')
 
1097
 
 
1098
    def test_short_author_from_committer(self):
 
1099
        self.rev.committer = 'John Doe <jdoe@example.com>'
 
1100
        self.assertEqual('John Doe', self.lf.short_author(self.rev))
 
1101
 
 
1102
    def test_short_author_from_authors(self):
 
1103
        self.rev.properties['authors'] = ('John Smith <jsmith@example.com>\n'
 
1104
                                          'Jane Rey <jrey@example.com>')
 
1105
        self.assertEqual('John Smith', self.lf.short_author(self.rev))
 
1106
 
 
1107
 
 
1108
class TestReverseByDepth(tests.TestCase):
 
1109
    """Test reverse_by_depth behavior.
 
1110
 
 
1111
    This is used to present revisions in forward (oldest first) order in a nice
 
1112
    layout.
 
1113
 
 
1114
    The tests use lighter revision description to ease reading.
 
1115
    """
 
1116
 
 
1117
    def assertReversed(self, forward, backward):
 
1118
        # Transform the descriptions to suit the API: tests use (revno, depth),
 
1119
        # while the API expects (revid, revno, depth)
 
1120
        def complete_revisions(l):
 
1121
            """Transform the description to suit the API.
 
1122
 
 
1123
            Tests use (revno, depth) whil the API expects (revid, revno, depth).
 
1124
            Since the revid is arbitrary, we just duplicate revno
 
1125
            """
 
1126
            return [(r, r, d) for r, d in l]
 
1127
        forward = complete_revisions(forward)
 
1128
        backward = complete_revisions(backward)
 
1129
        self.assertEqual(forward, log.reverse_by_depth(backward))
 
1130
 
 
1131
    def test_mainline_revisions(self):
 
1132
        self.assertReversed([('1', 0), ('2', 0)],
 
1133
                            [('2', 0), ('1', 0)])
 
1134
 
 
1135
    def test_merged_revisions(self):
 
1136
        self.assertReversed([('1', 0), ('2', 0), ('2.2', 1), ('2.1', 1), ],
 
1137
                            [('2', 0), ('2.1', 1), ('2.2', 1), ('1', 0), ])
 
1138
 
 
1139
    def test_shifted_merged_revisions(self):
 
1140
        """Test irregular layout.
 
1141
 
 
1142
        Requesting revisions touching a file can produce "holes" in the depths.
 
1143
        """
 
1144
        self.assertReversed([('1', 0), ('2', 0), ('1.1', 2), ('1.2', 2), ],
 
1145
                            [('2', 0), ('1.2', 2), ('1.1', 2), ('1', 0), ])
 
1146
 
 
1147
    def test_merged_without_child_revisions(self):
 
1148
        """Test irregular layout.
 
1149
 
 
1150
        Revision ranges can produce "holes" in the depths.
 
1151
        """
 
1152
        # When a revision of higher depth doesn't follow one of lower depth, we
 
1153
        # assume a lower depth one is virtually there
 
1154
        self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
 
1155
                            [('4', 4), ('3', 3), ('2', 2), ('1', 2), ])
 
1156
        # So we get the same order after reversing below even if the original
 
1157
        # revisions are not in the same order.
 
1158
        self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
 
1159
                            [('3', 3), ('4', 4), ('2', 2), ('1', 2), ])
 
1160
 
 
1161
 
 
1162
class TestHistoryChange(tests.TestCaseWithTransport):
 
1163
 
 
1164
    def setup_a_tree(self):
 
1165
        tree = self.make_branch_and_tree('tree')
 
1166
        tree.lock_write()
 
1167
        self.addCleanup(tree.unlock)
 
1168
        tree.commit('1a', rev_id=b'1a')
 
1169
        tree.commit('2a', rev_id=b'2a')
 
1170
        tree.commit('3a', rev_id=b'3a')
 
1171
        return tree
 
1172
 
 
1173
    def setup_ab_tree(self):
 
1174
        tree = self.setup_a_tree()
 
1175
        tree.set_last_revision(b'1a')
 
1176
        tree.branch.set_last_revision_info(1, b'1a')
 
1177
        tree.commit('2b', rev_id=b'2b')
 
1178
        tree.commit('3b', rev_id=b'3b')
 
1179
        return tree
 
1180
 
 
1181
    def setup_ac_tree(self):
 
1182
        tree = self.setup_a_tree()
 
1183
        tree.set_last_revision(revision.NULL_REVISION)
 
1184
        tree.branch.set_last_revision_info(0, revision.NULL_REVISION)
 
1185
        tree.commit('1c', rev_id=b'1c')
 
1186
        tree.commit('2c', rev_id=b'2c')
 
1187
        tree.commit('3c', rev_id=b'3c')
 
1188
        return tree
 
1189
 
 
1190
    def test_all_new(self):
 
1191
        tree = self.setup_ab_tree()
 
1192
        old, new = log.get_history_change(b'1a', b'3a', tree.branch.repository)
 
1193
        self.assertEqual([], old)
 
1194
        self.assertEqual([b'2a', b'3a'], new)
 
1195
 
 
1196
    def test_all_old(self):
 
1197
        tree = self.setup_ab_tree()
 
1198
        old, new = log.get_history_change(b'3a', b'1a', tree.branch.repository)
 
1199
        self.assertEqual([], new)
 
1200
        self.assertEqual([b'2a', b'3a'], old)
 
1201
 
 
1202
    def test_null_old(self):
 
1203
        tree = self.setup_ab_tree()
 
1204
        old, new = log.get_history_change(revision.NULL_REVISION,
 
1205
                                          b'3a', tree.branch.repository)
 
1206
        self.assertEqual([], old)
 
1207
        self.assertEqual([b'1a', b'2a', b'3a'], new)
 
1208
 
 
1209
    def test_null_new(self):
 
1210
        tree = self.setup_ab_tree()
 
1211
        old, new = log.get_history_change(b'3a', revision.NULL_REVISION,
 
1212
                                          tree.branch.repository)
 
1213
        self.assertEqual([], new)
 
1214
        self.assertEqual([b'1a', b'2a', b'3a'], old)
 
1215
 
 
1216
    def test_diverged(self):
 
1217
        tree = self.setup_ab_tree()
 
1218
        old, new = log.get_history_change(b'3a', b'3b', tree.branch.repository)
 
1219
        self.assertEqual(old, [b'2a', b'3a'])
 
1220
        self.assertEqual(new, [b'2b', b'3b'])
 
1221
 
 
1222
    def test_unrelated(self):
 
1223
        tree = self.setup_ac_tree()
 
1224
        old, new = log.get_history_change(b'3a', b'3c', tree.branch.repository)
 
1225
        self.assertEqual(old, [b'1a', b'2a', b'3a'])
 
1226
        self.assertEqual(new, [b'1c', b'2c', b'3c'])
 
1227
 
 
1228
    def test_show_branch_change(self):
 
1229
        tree = self.setup_ab_tree()
 
1230
        s = StringIO()
 
1231
        log.show_branch_change(tree.branch, s, 3, b'3a')
 
1232
        self.assertContainsRe(s.getvalue(),
 
1233
                              '[*]{60}\nRemoved Revisions:\n(.|\n)*2a(.|\n)*3a(.|\n)*'
 
1234
                              '[*]{60}\n\nAdded Revisions:\n(.|\n)*2b(.|\n)*3b')
 
1235
 
 
1236
    def test_show_branch_change_no_change(self):
 
1237
        tree = self.setup_ab_tree()
 
1238
        s = StringIO()
 
1239
        log.show_branch_change(tree.branch, s, 3, b'3b')
 
1240
        self.assertEqual(s.getvalue(),
 
1241
                         'Nothing seems to have changed\n')
 
1242
 
 
1243
    def test_show_branch_change_no_old(self):
 
1244
        tree = self.setup_ab_tree()
 
1245
        s = StringIO()
 
1246
        log.show_branch_change(tree.branch, s, 2, b'2b')
 
1247
        self.assertContainsRe(s.getvalue(), 'Added Revisions:')
 
1248
        self.assertNotContainsRe(s.getvalue(), 'Removed Revisions:')
 
1249
 
 
1250
    def test_show_branch_change_no_new(self):
 
1251
        tree = self.setup_ab_tree()
 
1252
        tree.branch.set_last_revision_info(2, b'2b')
 
1253
        s = StringIO()
 
1254
        log.show_branch_change(tree.branch, s, 3, b'3b')
 
1255
        self.assertContainsRe(s.getvalue(), 'Removed Revisions:')
 
1256
        self.assertNotContainsRe(s.getvalue(), 'Added Revisions:')
 
1257
 
 
1258
 
 
1259
class TestRevisionNotInBranch(TestCaseForLogFormatter):
 
1260
 
 
1261
    def setup_a_tree(self):
 
1262
        tree = self.make_branch_and_tree('tree')
 
1263
        tree.lock_write()
 
1264
        self.addCleanup(tree.unlock)
 
1265
        kwargs = {
 
1266
            'committer': 'Joe Foo <joe@foo.com>',
 
1267
            'timestamp': 1132617600,  # Mon 2005-11-22 00:00:00 +0000
 
1268
            'timezone': 0,  # UTC
 
1269
        }
 
1270
        tree.commit('commit 1a', rev_id=b'1a', **kwargs)
 
1271
        tree.commit('commit 2a', rev_id=b'2a', **kwargs)
 
1272
        tree.commit('commit 3a', rev_id=b'3a', **kwargs)
 
1273
        return tree
 
1274
 
 
1275
    def setup_ab_tree(self):
 
1276
        tree = self.setup_a_tree()
 
1277
        tree.set_last_revision(b'1a')
 
1278
        tree.branch.set_last_revision_info(1, b'1a')
 
1279
        kwargs = {
 
1280
            'committer': 'Joe Foo <joe@foo.com>',
 
1281
            'timestamp': 1132617600,  # Mon 2005-11-22 00:00:00 +0000
 
1282
            'timezone': 0,  # UTC
 
1283
        }
 
1284
        tree.commit('commit 2b', rev_id=b'2b', **kwargs)
 
1285
        tree.commit('commit 3b', rev_id=b'3b', **kwargs)
 
1286
        return tree
 
1287
 
 
1288
    def test_one_revision(self):
 
1289
        tree = self.setup_ab_tree()
 
1290
        lf = LogCatcher()
 
1291
        rev = revisionspec.RevisionInfo(tree.branch, None, b'3a')
 
1292
        log.show_log(tree.branch, lf, verbose=True, start_revision=rev,
 
1293
                     end_revision=rev)
 
1294
        self.assertEqual(1, len(lf.revisions))
 
1295
        self.assertEqual(None, lf.revisions[0].revno)   # Out-of-branch
 
1296
        self.assertEqual(b'3a', lf.revisions[0].rev.revision_id)
 
1297
 
 
1298
    def test_many_revisions(self):
 
1299
        tree = self.setup_ab_tree()
 
1300
        lf = LogCatcher()
 
1301
        start_rev = revisionspec.RevisionInfo(tree.branch, None, b'1a')
 
1302
        end_rev = revisionspec.RevisionInfo(tree.branch, None, b'3a')
 
1303
        log.show_log(tree.branch, lf, verbose=True, start_revision=start_rev,
 
1304
                     end_revision=end_rev)
 
1305
        self.assertEqual(3, len(lf.revisions))
 
1306
        self.assertEqual(None, lf.revisions[0].revno)   # Out-of-branch
 
1307
        self.assertEqual(b'3a', lf.revisions[0].rev.revision_id)
 
1308
        self.assertEqual(None, lf.revisions[1].revno)   # Out-of-branch
 
1309
        self.assertEqual(b'2a', lf.revisions[1].rev.revision_id)
 
1310
        self.assertEqual('1', lf.revisions[2].revno)    # In-branch
 
1311
 
 
1312
    def test_long_format(self):
 
1313
        tree = self.setup_ab_tree()
 
1314
        start_rev = revisionspec.RevisionInfo(tree.branch, None, b'1a')
 
1315
        end_rev = revisionspec.RevisionInfo(tree.branch, None, b'3a')
 
1316
        self.assertFormatterResult(b"""\
 
1317
------------------------------------------------------------
 
1318
revision-id: 3a
 
1319
committer: Joe Foo <joe@foo.com>
 
1320
branch nick: tree
 
1321
timestamp: Tue 2005-11-22 00:00:00 +0000
 
1322
message:
 
1323
  commit 3a
 
1324
------------------------------------------------------------
 
1325
revision-id: 2a
 
1326
committer: Joe Foo <joe@foo.com>
 
1327
branch nick: tree
 
1328
timestamp: Tue 2005-11-22 00:00:00 +0000
 
1329
message:
 
1330
  commit 2a
 
1331
------------------------------------------------------------
 
1332
revno: 1
 
1333
committer: Joe Foo <joe@foo.com>
 
1334
branch nick: tree
 
1335
timestamp: Tue 2005-11-22 00:00:00 +0000
 
1336
message:
 
1337
  commit 1a
 
1338
""",
 
1339
                                   tree.branch, log.LongLogFormatter, show_log_kwargs={
 
1340
                                       'start_revision': start_rev, 'end_revision': end_rev
 
1341
                                       })
 
1342
 
 
1343
    def test_short_format(self):
 
1344
        tree = self.setup_ab_tree()
 
1345
        start_rev = revisionspec.RevisionInfo(tree.branch, None, b'1a')
 
1346
        end_rev = revisionspec.RevisionInfo(tree.branch, None, b'3a')
 
1347
        self.assertFormatterResult(b"""\
 
1348
      Joe Foo\t2005-11-22
 
1349
      revision-id:3a
 
1350
      commit 3a
 
1351
 
 
1352
      Joe Foo\t2005-11-22
 
1353
      revision-id:2a
 
1354
      commit 2a
 
1355
 
 
1356
    1 Joe Foo\t2005-11-22
 
1357
      commit 1a
 
1358
 
 
1359
""",
 
1360
                                   tree.branch, log.ShortLogFormatter, show_log_kwargs={
 
1361
                                       'start_revision': start_rev, 'end_revision': end_rev
 
1362
                                       })
 
1363
 
 
1364
    def test_line_format(self):
 
1365
        tree = self.setup_ab_tree()
 
1366
        start_rev = revisionspec.RevisionInfo(tree.branch, None, b'1a')
 
1367
        end_rev = revisionspec.RevisionInfo(tree.branch, None, b'3a')
 
1368
        self.assertFormatterResult(b"""\
 
1369
Joe Foo 2005-11-22 commit 3a
 
1370
Joe Foo 2005-11-22 commit 2a
 
1371
1: Joe Foo 2005-11-22 commit 1a
 
1372
""",
 
1373
                                   tree.branch, log.LineLogFormatter, show_log_kwargs={
 
1374
                                       'start_revision': start_rev, 'end_revision': end_rev
 
1375
                                       })
 
1376
 
 
1377
 
 
1378
class TestLogWithBugs(TestCaseForLogFormatter, TestLogMixin):
 
1379
 
 
1380
    def setUp(self):
 
1381
        super(TestLogWithBugs, self).setUp()
 
1382
        log.properties_handler_registry.register(
 
1383
            'bugs_properties_handler',
 
1384
            log._bugs_properties_handler)
 
1385
 
 
1386
    def make_commits_with_bugs(self):
 
1387
        """Helper method for LogFormatter tests"""
 
1388
        tree = self.make_branch_and_tree(u'.')
 
1389
        self.build_tree(['a', 'b'])
 
1390
        tree.add('a')
 
1391
        self.wt_commit(tree, 'simple log message', rev_id=b'a1',
 
1392
                       revprops={u'bugs': 'test://bug/id fixed'})
 
1393
        tree.add('b')
 
1394
        self.wt_commit(tree, 'multiline\nlog\nmessage\n', rev_id=b'a2',
 
1395
                       authors=['Joe Bar <joe@bar.com>'],
 
1396
                       revprops={u'bugs': 'test://bug/id fixed\n'
 
1397
                                 'test://bug/2 fixed'})
 
1398
        return tree
 
1399
 
 
1400
    def test_bug_broken(self):
 
1401
        tree = self.make_branch_and_tree(u'.')
 
1402
        self.build_tree(['a', 'b'])
 
1403
        tree.add('a')
 
1404
        self.wt_commit(tree, 'simple log message', rev_id=b'a1',
 
1405
                       revprops={u'bugs': 'test://bua g/id fixed'})
 
1406
 
 
1407
        logfile = self.make_utf8_encoded_stringio()
 
1408
        formatter = log.LongLogFormatter(to_file=logfile)
 
1409
        log.show_log(tree.branch, formatter)
 
1410
 
 
1411
        self.assertContainsRe(
 
1412
            logfile.getvalue(),
 
1413
            b'brz: ERROR: breezy.bugtracker.InvalidLineInBugsProperty: '
 
1414
            b'Invalid line in bugs property: \'test://bua g/id fixed\'')
 
1415
 
 
1416
        text = logfile.getvalue()
 
1417
        self.assertEqualDiff(
 
1418
            text[text.index(b'-' * 60):],
 
1419
            b"""\
 
1420
------------------------------------------------------------
 
1421
revno: 1
 
1422
committer: Joe Foo <joe@foo.com>
 
1423
branch nick: work
 
1424
timestamp: Tue 2005-11-22 00:00:00 +0000
 
1425
message:
 
1426
  simple log message
 
1427
""")
 
1428
 
 
1429
    def test_long_bugs(self):
 
1430
        tree = self.make_commits_with_bugs()
 
1431
        self.assertFormatterResult(b"""\
 
1432
------------------------------------------------------------
 
1433
revno: 2
 
1434
fixes bugs: test://bug/id test://bug/2
 
1435
author: Joe Bar <joe@bar.com>
 
1436
committer: Joe Foo <joe@foo.com>
 
1437
branch nick: work
 
1438
timestamp: Tue 2005-11-22 00:00:01 +0000
 
1439
message:
 
1440
  multiline
 
1441
  log
 
1442
  message
 
1443
------------------------------------------------------------
 
1444
revno: 1
 
1445
fixes bug: test://bug/id
 
1446
committer: Joe Foo <joe@foo.com>
 
1447
branch nick: work
 
1448
timestamp: Tue 2005-11-22 00:00:00 +0000
 
1449
message:
 
1450
  simple log message
 
1451
""",
 
1452
                                   tree.branch, log.LongLogFormatter)
 
1453
 
 
1454
    def test_short_bugs(self):
 
1455
        tree = self.make_commits_with_bugs()
 
1456
        self.assertFormatterResult(b"""\
 
1457
    2 Joe Bar\t2005-11-22
 
1458
      fixes bugs: test://bug/id test://bug/2
 
1459
      multiline
 
1460
      log
 
1461
      message
 
1462
 
 
1463
    1 Joe Foo\t2005-11-22
 
1464
      fixes bug: test://bug/id
 
1465
      simple log message
 
1466
 
 
1467
""",
 
1468
                                   tree.branch, log.ShortLogFormatter)
 
1469
 
 
1470
    def test_wrong_bugs_property(self):
 
1471
        tree = self.make_branch_and_tree(u'.')
 
1472
        self.build_tree(['foo'])
 
1473
        self.wt_commit(tree, 'simple log message', rev_id=b'a1',
 
1474
                       revprops={u'bugs': 'test://bug/id invalid_value'})
 
1475
 
 
1476
        logfile = self.make_utf8_encoded_stringio()
 
1477
        formatter = log.ShortLogFormatter(to_file=logfile)
 
1478
        log.show_log(tree.branch, formatter)
 
1479
 
 
1480
        lines = logfile.getvalue().splitlines()
 
1481
 
 
1482
        self.assertEqual(
 
1483
            lines[0], b'    1 Joe Foo\t2005-11-22')
 
1484
 
 
1485
        self.assertEqual(
 
1486
            lines[1],
 
1487
            b'brz: ERROR: breezy.bugtracker.InvalidBugStatus: Invalid '
 
1488
            b'bug status: \'invalid_value\'')
 
1489
 
 
1490
        self.assertEqual(lines[-2], b"      simple log message")
 
1491
 
 
1492
    def test_bugs_handler_present(self):
 
1493
        self.properties_handler_registry.get('bugs_properties_handler')
 
1494
 
 
1495
 
 
1496
class TestLogForAuthors(TestCaseForLogFormatter):
 
1497
 
 
1498
    def setUp(self):
 
1499
        super(TestLogForAuthors, self).setUp()
 
1500
        self.wt = self.make_standard_commit('nicky',
 
1501
                                            authors=['John Doe <jdoe@example.com>',
 
1502
                                                     'Jane Rey <jrey@example.com>'])
 
1503
 
 
1504
    def assertFormatterResult(self, formatter, who, result):
 
1505
        formatter_kwargs = dict()
 
1506
        if who is not None:
 
1507
            author_list_handler = log.author_list_registry.get(who)
 
1508
            formatter_kwargs['author_list_handler'] = author_list_handler
 
1509
        TestCaseForLogFormatter.assertFormatterResult(self, result,
 
1510
                                                      self.wt.branch, formatter, formatter_kwargs=formatter_kwargs)
 
1511
 
 
1512
    def test_line_default(self):
 
1513
        self.assertFormatterResult(log.LineLogFormatter, None, b"""\
 
1514
1: John Doe 2005-11-22 add a
 
1515
""")
 
1516
 
 
1517
    def test_line_committer(self):
 
1518
        self.assertFormatterResult(log.LineLogFormatter, 'committer', b"""\
 
1519
1: Lorem Ipsum 2005-11-22 add a
 
1520
""")
 
1521
 
 
1522
    def test_line_first(self):
 
1523
        self.assertFormatterResult(log.LineLogFormatter, 'first', b"""\
 
1524
1: John Doe 2005-11-22 add a
 
1525
""")
 
1526
 
 
1527
    def test_line_all(self):
 
1528
        self.assertFormatterResult(log.LineLogFormatter, 'all', b"""\
 
1529
1: John Doe, Jane Rey 2005-11-22 add a
 
1530
""")
 
1531
 
 
1532
    def test_short_default(self):
 
1533
        self.assertFormatterResult(log.ShortLogFormatter, None, b"""\
 
1534
    1 John Doe\t2005-11-22
 
1535
      add a
 
1536
 
 
1537
""")
 
1538
 
 
1539
    def test_short_committer(self):
 
1540
        self.assertFormatterResult(log.ShortLogFormatter, 'committer', b"""\
 
1541
    1 Lorem Ipsum\t2005-11-22
 
1542
      add a
 
1543
 
 
1544
""")
 
1545
 
 
1546
    def test_short_first(self):
 
1547
        self.assertFormatterResult(log.ShortLogFormatter, 'first', b"""\
 
1548
    1 John Doe\t2005-11-22
 
1549
      add a
 
1550
 
 
1551
""")
 
1552
 
 
1553
    def test_short_all(self):
 
1554
        self.assertFormatterResult(log.ShortLogFormatter, 'all', b"""\
 
1555
    1 John Doe, Jane Rey\t2005-11-22
 
1556
      add a
 
1557
 
 
1558
""")
 
1559
 
 
1560
    def test_long_default(self):
 
1561
        self.assertFormatterResult(log.LongLogFormatter, None, b"""\
 
1562
------------------------------------------------------------
 
1563
revno: 1
 
1564
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
 
1565
committer: Lorem Ipsum <test@example.com>
 
1566
branch nick: nicky
 
1567
timestamp: Tue 2005-11-22 00:00:00 +0000
 
1568
message:
 
1569
  add a
 
1570
""")
 
1571
 
 
1572
    def test_long_committer(self):
 
1573
        self.assertFormatterResult(log.LongLogFormatter, 'committer', b"""\
 
1574
------------------------------------------------------------
 
1575
revno: 1
 
1576
committer: Lorem Ipsum <test@example.com>
 
1577
branch nick: nicky
 
1578
timestamp: Tue 2005-11-22 00:00:00 +0000
 
1579
message:
 
1580
  add a
 
1581
""")
 
1582
 
 
1583
    def test_long_first(self):
 
1584
        self.assertFormatterResult(log.LongLogFormatter, 'first', b"""\
 
1585
------------------------------------------------------------
 
1586
revno: 1
 
1587
author: John Doe <jdoe@example.com>
 
1588
committer: Lorem Ipsum <test@example.com>
 
1589
branch nick: nicky
 
1590
timestamp: Tue 2005-11-22 00:00:00 +0000
 
1591
message:
 
1592
  add a
 
1593
""")
 
1594
 
 
1595
    def test_long_all(self):
 
1596
        self.assertFormatterResult(log.LongLogFormatter, 'all', b"""\
 
1597
------------------------------------------------------------
 
1598
revno: 1
 
1599
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
 
1600
committer: Lorem Ipsum <test@example.com>
 
1601
branch nick: nicky
 
1602
timestamp: Tue 2005-11-22 00:00:00 +0000
 
1603
message:
 
1604
  add a
 
1605
""")
 
1606
 
 
1607
    def test_gnu_changelog_default(self):
 
1608
        self.assertFormatterResult(log.GnuChangelogLogFormatter, None, b"""\
 
1609
2005-11-22  John Doe  <jdoe@example.com>
 
1610
 
 
1611
\tadd a
 
1612
 
 
1613
""")
 
1614
 
 
1615
    def test_gnu_changelog_committer(self):
 
1616
        self.assertFormatterResult(log.GnuChangelogLogFormatter, 'committer', b"""\
 
1617
2005-11-22  Lorem Ipsum  <test@example.com>
 
1618
 
 
1619
\tadd a
 
1620
 
 
1621
""")
 
1622
 
 
1623
    def test_gnu_changelog_first(self):
 
1624
        self.assertFormatterResult(log.GnuChangelogLogFormatter, 'first', b"""\
 
1625
2005-11-22  John Doe  <jdoe@example.com>
 
1626
 
 
1627
\tadd a
 
1628
 
 
1629
""")
 
1630
 
 
1631
    def test_gnu_changelog_all(self):
 
1632
        self.assertFormatterResult(log.GnuChangelogLogFormatter, 'all', b"""\
 
1633
2005-11-22  John Doe  <jdoe@example.com>, Jane Rey  <jrey@example.com>
 
1634
 
 
1635
\tadd a
 
1636
 
 
1637
""")
 
1638
 
 
1639
 
 
1640
class TestLogExcludeAncestry(tests.TestCaseWithTransport):
 
1641
 
 
1642
    def make_branch_with_alternate_ancestries(self, relpath='.'):
 
1643
        # See test_merge_sorted_exclude_ancestry below for the difference with
 
1644
        # bt.per_branch.test_iter_merge_sorted_revision.
 
1645
        # TestIterMergeSortedRevisionsBushyGraph.
 
1646
        # make_branch_with_alternate_ancestries
 
1647
        # and test_merge_sorted_exclude_ancestry
 
1648
        # See the FIXME in assertLogRevnos too.
 
1649
        builder = branchbuilder.BranchBuilder(self.get_transport(relpath))
 
1650
        # 1
 
1651
        # |\
 
1652
        # 2 \
 
1653
        # |  |
 
1654
        # |  1.1.1
 
1655
        # |  | \
 
1656
        # |  |  1.2.1
 
1657
        # |  | /
 
1658
        # |  1.1.2
 
1659
        # | /
 
1660
        # 3
 
1661
        builder.start_series()
 
1662
        builder.build_snapshot(None, [
 
1663
            ('add', ('', b'TREE_ROOT', 'directory', '')), ],
 
1664
            revision_id=b'1')
 
1665
        builder.build_snapshot([b'1'], [], revision_id=b'1.1.1')
 
1666
        builder.build_snapshot([b'1'], [], revision_id=b'2')
 
1667
        builder.build_snapshot([b'1.1.1'], [], revision_id=b'1.2.1')
 
1668
        builder.build_snapshot([b'1.1.1', b'1.2.1'], [], revision_id=b'1.1.2')
 
1669
        builder.build_snapshot([b'2', b'1.1.2'], [], revision_id=b'3')
 
1670
        builder.finish_series()
 
1671
        br = builder.get_branch()
 
1672
        br.lock_read()
 
1673
        self.addCleanup(br.unlock)
 
1674
        return br
 
1675
 
 
1676
    def assertLogRevnos(self, expected_revnos, b, start, end,
 
1677
                        exclude_common_ancestry, generate_merge_revisions=True):
 
1678
        # FIXME: the layering in log makes it hard to test intermediate levels,
 
1679
        # I wish adding filters with their parameters was easier...
 
1680
        # -- vila 20100413
 
1681
        iter_revs = log._calc_view_revisions(
 
1682
            b, start, end, direction='reverse',
 
1683
            generate_merge_revisions=generate_merge_revisions,
 
1684
            exclude_common_ancestry=exclude_common_ancestry)
 
1685
        self.assertEqual(expected_revnos,
 
1686
                         [revid for revid, revno, depth in iter_revs])
 
1687
 
 
1688
    def test_merge_sorted_exclude_ancestry(self):
 
1689
        b = self.make_branch_with_alternate_ancestries()
 
1690
        self.assertLogRevnos([b'3', b'1.1.2', b'1.2.1', b'1.1.1', b'2', b'1'],
 
1691
                             b, b'1', b'3', exclude_common_ancestry=False)
 
1692
        # '2' is part of the '3' ancestry but not part of '1.1.1' ancestry so
 
1693
        # it should be mentioned even if merge_sort order will make it appear
 
1694
        # after 1.1.1
 
1695
        self.assertLogRevnos([b'3', b'1.1.2', b'1.2.1', b'2'],
 
1696
                             b, b'1.1.1', b'3', exclude_common_ancestry=True)
 
1697
 
 
1698
    def test_merge_sorted_simple_revnos_exclude_ancestry(self):
 
1699
        b = self.make_branch_with_alternate_ancestries()
 
1700
        self.assertLogRevnos([b'3', b'2'],
 
1701
                             b, b'1', b'3', exclude_common_ancestry=True,
 
1702
                             generate_merge_revisions=False)
 
1703
        self.assertLogRevnos([b'3', b'1.1.2', b'1.2.1', b'1.1.1', b'2'],
 
1704
                             b, b'1', b'3', exclude_common_ancestry=True,
 
1705
                             generate_merge_revisions=True)
 
1706
 
 
1707
 
 
1708
class TestLogDefaults(TestCaseForLogFormatter):
 
1709
    def test_default_log_level(self):
 
1710
        """
 
1711
        Test to ensure that specifying 'levels=1' to make_log_request_dict
 
1712
        doesn't get overwritten when using a LogFormatter that supports more
 
1713
        detail.
 
1714
        Fixes bug #747958.
 
1715
        """
 
1716
        wt = self._prepare_tree_with_merges()
 
1717
        b = wt.branch
 
1718
 
 
1719
        class CustomLogFormatter(log.LogFormatter):
 
1720
            def __init__(self, *args, **kwargs):
 
1721
                super(CustomLogFormatter, self).__init__(*args, **kwargs)
 
1722
                self.revisions = []
 
1723
 
 
1724
            def get_levels(self):
 
1725
                # log formatter supports all levels:
 
1726
                return 0
 
1727
 
 
1728
            def log_revision(self, revision):
 
1729
                self.revisions.append(revision)
 
1730
 
 
1731
        log_formatter = LogCatcher()
 
1732
        # First request we don't specify number of levels, we should get a
 
1733
        # sensible default (whatever the LogFormatter handles - which in this
 
1734
        # case is 0/everything):
 
1735
        request = log.make_log_request_dict(limit=10)
 
1736
        log.Logger(b, request).show(log_formatter)
 
1737
        # should have all three revisions:
 
1738
        self.assertEqual(len(log_formatter.revisions), 3)
 
1739
 
 
1740
        del log_formatter
 
1741
        log_formatter = LogCatcher()
 
1742
        # now explicitly request mainline revisions only:
 
1743
        request = log.make_log_request_dict(limit=10, levels=1)
 
1744
        log.Logger(b, request).show(log_formatter)
 
1745
        # should now only have 2 revisions:
 
1746
        self.assertEqual(len(log_formatter.revisions), 2)