/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: 2018-02-18 19:34:37 UTC
  • mfrom: (6857.1.1 fix-import-stacked)
  • Revision ID: jelmer@jelmer.uk-20180218193437-pytr0vyldq867owo
Merge lp:~jelmer/brz/fix-import-stacked.

Show diffs side-by-side

added added

removed removed

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