/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-05-19 13:16:11 UTC
  • mto: (6968.4.3 git-archive)
  • mto: This revision was merged to the branch mainline in revision 6972.
  • Revision ID: jelmer@jelmer.uk-20180519131611-l9h9ud41j7qg1m03
Move tar/zip to breezy.archive.

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', b'hello moto\n')])
 
95
        self.wt_commit(wt, 'simple log message', rev_id=b'a1')
 
96
        self.build_tree_contents([('b', b'goodbye\n')])
 
97
        wt.add('b')
 
98
        self.wt_commit(wt, 'multiline\nlog\nmessage\n', rev_id=b'a2')
 
99
 
 
100
        self.build_tree_contents([('c', b'just another manic monday\n')])
 
101
        wt.add('c')
 
102
        self.wt_commit(wt, 'single line with trailing newline\n', rev_id=b'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=b'rev-1')
 
111
        self.wt_commit(wt, 'rev-merged', rev_id=b'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=b'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=b'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, 1)
 
181
        self.assertInvalidRevisonNumber(b, 1, -1)
 
182
        self.assertInvalidRevisonNumber(b, 1, 0)
 
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', b'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
 
 
321
    def verify_revision_signature(self, revid, gpg_strategy):
 
322
        return (gpg.SIGNATURE_VALID,
 
323
            u'UTF8 Test \xa1\xb1\xc1\xd1\xe1\xf1 <jrandom@example.com>')
 
324
 
 
325
    def test_format_signature_validity_utf(self):
 
326
        """Check that GPG signatures containing UTF-8 names are formatted
 
327
        correctly."""
 
328
        wt = self.make_branch_and_tree('.')
 
329
        revid = wt.commit('empty commit')
 
330
        repo = wt.branch.repository
 
331
        # Monkey patch out checking if this rev is actually signed, since we
 
332
        # can't sign it without a heavier TestCase and LoopbackGPGStrategy
 
333
        # doesn't care anyways.
 
334
        self.overrideAttr(repo, 'verify_revision_signature',
 
335
                self.verify_revision_signature)
 
336
        out = log.format_signature_validity(revid, wt.branch)
 
337
        self.assertEqual(
 
338
u'valid signature from UTF8 Test \xa1\xb1\xc1\xd1\xe1\xf1 <jrandom@example.com>',
 
339
                out)
 
340
 
 
341
 
 
342
class TestShortLogFormatter(TestCaseForLogFormatter):
 
343
 
 
344
    def test_trailing_newlines(self):
 
345
        wt = self.make_branch_and_tree('.')
 
346
        b = self.make_commits_with_trailing_newlines(wt)
 
347
        self.assertFormatterResult("""\
 
348
    3 Joe Foo\t2005-11-22
 
349
      single line with trailing newline
 
350
 
 
351
    2 Joe Foo\t2005-11-22
 
352
      multiline
 
353
      log
 
354
      message
 
355
 
 
356
    1 Joe Foo\t2005-11-22
 
357
      simple log message
 
358
 
 
359
""",
 
360
            b, log.ShortLogFormatter)
 
361
 
 
362
    def test_short_log_with_merges(self):
 
363
        wt = self._prepare_tree_with_merges()
 
364
        self.assertFormatterResult("""\
 
365
    2 Joe Foo\t2005-11-22 [merge]
 
366
      rev-2
 
367
 
 
368
    1 Joe Foo\t2005-11-22
 
369
      rev-1
 
370
 
 
371
""",
 
372
            wt.branch, log.ShortLogFormatter)
 
373
 
 
374
    def test_short_log_with_merges_and_advice(self):
 
375
        wt = self._prepare_tree_with_merges()
 
376
        self.assertFormatterResult("""\
 
377
    2 Joe Foo\t2005-11-22 [merge]
 
378
      rev-2
 
379
 
 
380
    1 Joe Foo\t2005-11-22
 
381
      rev-1
 
382
 
 
383
Use --include-merged or -n0 to see merged revisions.
 
384
""",
 
385
            wt.branch, log.ShortLogFormatter,
 
386
            formatter_kwargs=dict(show_advice=True))
 
387
 
 
388
    def test_short_log_with_merges_and_range(self):
 
389
        wt = self._prepare_tree_with_merges()
 
390
        self.wt_commit(wt, 'rev-3a', rev_id=b'rev-3a')
 
391
        wt.branch.set_last_revision_info(2, 'rev-2b')
 
392
        wt.set_parent_ids(['rev-2b', 'rev-3a'])
 
393
        self.wt_commit(wt, 'rev-3b', rev_id=b'rev-3b')
 
394
        self.assertFormatterResult("""\
 
395
    3 Joe Foo\t2005-11-22 [merge]
 
396
      rev-3b
 
397
 
 
398
    2 Joe Foo\t2005-11-22 [merge]
 
399
      rev-2
 
400
 
 
401
""",
 
402
            wt.branch, log.ShortLogFormatter,
 
403
            show_log_kwargs=dict(start_revision=2, end_revision=3))
 
404
 
 
405
    def test_short_log_with_tags(self):
 
406
        wt = self._prepare_tree_with_merges(with_tags=True)
 
407
        self.assertFormatterResult("""\
 
408
    3 Joe Foo\t2005-11-22 {v1.0, v1.0rc1}
 
409
      rev-3
 
410
 
 
411
    2 Joe Foo\t2005-11-22 {v0.2} [merge]
 
412
      rev-2
 
413
 
 
414
    1 Joe Foo\t2005-11-22
 
415
      rev-1
 
416
 
 
417
""",
 
418
            wt.branch, log.ShortLogFormatter)
 
419
 
 
420
    def test_short_log_single_merge_revision(self):
 
421
        wt = self._prepare_tree_with_merges()
 
422
        revspec = revisionspec.RevisionSpec.from_string('1.1.1')
 
423
        rev = revspec.in_history(wt.branch)
 
424
        self.assertFormatterResult("""\
 
425
      1.1.1 Joe Foo\t2005-11-22
 
426
            rev-merged
 
427
 
 
428
""",
 
429
            wt.branch, log.ShortLogFormatter,
 
430
            show_log_kwargs=dict(start_revision=rev, end_revision=rev))
 
431
 
 
432
    def test_show_ids(self):
 
433
        wt = self.make_branch_and_tree('parent')
 
434
        self.build_tree(['parent/f1', 'parent/f2'])
 
435
        wt.add(['f1', 'f2'])
 
436
        self.wt_commit(wt, 'first post', rev_id=b'a')
 
437
        child_wt = wt.controldir.sprout('child').open_workingtree()
 
438
        self.wt_commit(child_wt, 'branch 1 changes', rev_id=b'b')
 
439
        wt.merge_from_branch(child_wt.branch)
 
440
        self.wt_commit(wt, 'merge branch 1', rev_id=b'c')
 
441
        self.assertFormatterResult("""\
 
442
    2 Joe Foo\t2005-11-22 [merge]
 
443
      revision-id:c
 
444
      merge branch 1
 
445
 
 
446
          1.1.1 Joe Foo\t2005-11-22
 
447
                revision-id:b
 
448
                branch 1 changes
 
449
 
 
450
    1 Joe Foo\t2005-11-22
 
451
      revision-id:a
 
452
      first post
 
453
 
 
454
""",
 
455
            wt.branch, log.ShortLogFormatter,
 
456
            formatter_kwargs=dict(levels=0, show_ids=True))
 
457
 
 
458
 
 
459
class TestShortLogFormatterWithMergeRevisions(TestCaseForLogFormatter):
 
460
 
 
461
    def test_short_merge_revs_log_with_merges(self):
 
462
        wt = self._prepare_tree_with_merges()
 
463
        # Note that the 1.1.1 indenting is in fact correct given that
 
464
        # the revision numbers are right justified within 5 characters
 
465
        # for mainline revnos and 9 characters for dotted revnos.
 
466
        self.assertFormatterResult("""\
 
467
    2 Joe Foo\t2005-11-22 [merge]
 
468
      rev-2
 
469
 
 
470
          1.1.1 Joe Foo\t2005-11-22
 
471
                rev-merged
 
472
 
 
473
    1 Joe Foo\t2005-11-22
 
474
      rev-1
 
475
 
 
476
""",
 
477
            wt.branch, log.ShortLogFormatter,
 
478
            formatter_kwargs=dict(levels=0))
 
479
 
 
480
    def test_short_merge_revs_log_single_merge_revision(self):
 
481
        wt = self._prepare_tree_with_merges()
 
482
        revspec = revisionspec.RevisionSpec.from_string('1.1.1')
 
483
        rev = revspec.in_history(wt.branch)
 
484
        self.assertFormatterResult("""\
 
485
      1.1.1 Joe Foo\t2005-11-22
 
486
            rev-merged
 
487
 
 
488
""",
 
489
            wt.branch, log.ShortLogFormatter,
 
490
            formatter_kwargs=dict(levels=0),
 
491
            show_log_kwargs=dict(start_revision=rev, end_revision=rev))
 
492
 
 
493
 
 
494
class TestLongLogFormatter(TestCaseForLogFormatter):
 
495
 
 
496
    def test_verbose_log(self):
 
497
        """Verbose log includes changed files
 
498
 
 
499
        bug #4676
 
500
        """
 
501
        wt = self.make_standard_commit('test_verbose_log', authors=[])
 
502
        self.assertFormatterResult('''\
 
503
------------------------------------------------------------
 
504
revno: 1
 
505
committer: Lorem Ipsum <test@example.com>
 
506
branch nick: test_verbose_log
 
507
timestamp: Tue 2005-11-22 00:00:00 +0000
 
508
message:
 
509
  add a
 
510
added:
 
511
  a
 
512
''',
 
513
            wt.branch, log.LongLogFormatter,
 
514
            show_log_kwargs=dict(verbose=True))
 
515
 
 
516
    def test_merges_are_indented_by_level(self):
 
517
        wt = self.make_branch_and_tree('parent')
 
518
        self.wt_commit(wt, 'first post')
 
519
        child_wt = wt.controldir.sprout('child').open_workingtree()
 
520
        self.wt_commit(child_wt, 'branch 1')
 
521
        smallerchild_wt = wt.controldir.sprout('smallerchild').open_workingtree()
 
522
        self.wt_commit(smallerchild_wt, 'branch 2')
 
523
        child_wt.merge_from_branch(smallerchild_wt.branch)
 
524
        self.wt_commit(child_wt, 'merge branch 2')
 
525
        wt.merge_from_branch(child_wt.branch)
 
526
        self.wt_commit(wt, 'merge branch 1')
 
527
        self.assertFormatterResult("""\
 
528
------------------------------------------------------------
 
529
revno: 2 [merge]
 
530
committer: Joe Foo <joe@foo.com>
 
531
branch nick: parent
 
532
timestamp: Tue 2005-11-22 00:00:04 +0000
 
533
message:
 
534
  merge branch 1
 
535
    ------------------------------------------------------------
 
536
    revno: 1.1.2 [merge]
 
537
    committer: Joe Foo <joe@foo.com>
 
538
    branch nick: child
 
539
    timestamp: Tue 2005-11-22 00:00:03 +0000
 
540
    message:
 
541
      merge branch 2
 
542
        ------------------------------------------------------------
 
543
        revno: 1.2.1
 
544
        committer: Joe Foo <joe@foo.com>
 
545
        branch nick: smallerchild
 
546
        timestamp: Tue 2005-11-22 00:00:02 +0000
 
547
        message:
 
548
          branch 2
 
549
    ------------------------------------------------------------
 
550
    revno: 1.1.1
 
551
    committer: Joe Foo <joe@foo.com>
 
552
    branch nick: child
 
553
    timestamp: Tue 2005-11-22 00:00:01 +0000
 
554
    message:
 
555
      branch 1
 
556
------------------------------------------------------------
 
557
revno: 1
 
558
committer: Joe Foo <joe@foo.com>
 
559
branch nick: parent
 
560
timestamp: Tue 2005-11-22 00:00:00 +0000
 
561
message:
 
562
  first post
 
563
""",
 
564
            wt.branch, log.LongLogFormatter,
 
565
            formatter_kwargs=dict(levels=0),
 
566
            show_log_kwargs=dict(verbose=True))
 
567
 
 
568
    def test_verbose_merge_revisions_contain_deltas(self):
 
569
        wt = self.make_branch_and_tree('parent')
 
570
        self.build_tree(['parent/f1', 'parent/f2'])
 
571
        wt.add(['f1', 'f2'])
 
572
        self.wt_commit(wt, 'first post')
 
573
        child_wt = wt.controldir.sprout('child').open_workingtree()
 
574
        os.unlink('child/f1')
 
575
        self.build_tree_contents([('child/f2', b'hello\n')])
 
576
        self.wt_commit(child_wt, 'removed f1 and modified f2')
 
577
        wt.merge_from_branch(child_wt.branch)
 
578
        self.wt_commit(wt, 'merge branch 1')
 
579
        self.assertFormatterResult("""\
 
580
------------------------------------------------------------
 
581
revno: 2 [merge]
 
582
committer: Joe Foo <joe@foo.com>
 
583
branch nick: parent
 
584
timestamp: Tue 2005-11-22 00:00:02 +0000
 
585
message:
 
586
  merge branch 1
 
587
removed:
 
588
  f1
 
589
modified:
 
590
  f2
 
591
    ------------------------------------------------------------
 
592
    revno: 1.1.1
 
593
    committer: Joe Foo <joe@foo.com>
 
594
    branch nick: child
 
595
    timestamp: Tue 2005-11-22 00:00:01 +0000
 
596
    message:
 
597
      removed f1 and modified f2
 
598
    removed:
 
599
      f1
 
600
    modified:
 
601
      f2
 
602
------------------------------------------------------------
 
603
revno: 1
 
604
committer: Joe Foo <joe@foo.com>
 
605
branch nick: parent
 
606
timestamp: Tue 2005-11-22 00:00:00 +0000
 
607
message:
 
608
  first post
 
609
added:
 
610
  f1
 
611
  f2
 
612
""",
 
613
            wt.branch, log.LongLogFormatter,
 
614
            formatter_kwargs=dict(levels=0),
 
615
            show_log_kwargs=dict(verbose=True))
 
616
 
 
617
    def test_trailing_newlines(self):
 
618
        wt = self.make_branch_and_tree('.')
 
619
        b = self.make_commits_with_trailing_newlines(wt)
 
620
        self.assertFormatterResult("""\
 
621
------------------------------------------------------------
 
622
revno: 3
 
623
committer: Joe Foo <joe@foo.com>
 
624
branch nick: test
 
625
timestamp: Tue 2005-11-22 00:00:02 +0000
 
626
message:
 
627
  single line with trailing newline
 
628
------------------------------------------------------------
 
629
revno: 2
 
630
committer: Joe Foo <joe@foo.com>
 
631
branch nick: test
 
632
timestamp: Tue 2005-11-22 00:00:01 +0000
 
633
message:
 
634
  multiline
 
635
  log
 
636
  message
 
637
------------------------------------------------------------
 
638
revno: 1
 
639
committer: Joe Foo <joe@foo.com>
 
640
branch nick: test
 
641
timestamp: Tue 2005-11-22 00:00:00 +0000
 
642
message:
 
643
  simple log message
 
644
""",
 
645
        b, log.LongLogFormatter)
 
646
 
 
647
    def test_author_in_log(self):
 
648
        """Log includes the author name if it's set in
 
649
        the revision properties
 
650
        """
 
651
        wt = self.make_standard_commit('test_author_log',
 
652
            authors=['John Doe <jdoe@example.com>',
 
653
                     'Jane Rey <jrey@example.com>'])
 
654
        self.assertFormatterResult("""\
 
655
------------------------------------------------------------
 
656
revno: 1
 
657
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
 
658
committer: Lorem Ipsum <test@example.com>
 
659
branch nick: test_author_log
 
660
timestamp: Tue 2005-11-22 00:00:00 +0000
 
661
message:
 
662
  add a
 
663
""",
 
664
        wt.branch, log.LongLogFormatter)
 
665
 
 
666
    def test_properties_in_log(self):
 
667
        """Log includes the custom properties returned by the registered
 
668
        handlers.
 
669
        """
 
670
        wt = self.make_standard_commit('test_properties_in_log')
 
671
        def trivial_custom_prop_handler(revision):
 
672
            return {'test_prop':'test_value'}
 
673
 
 
674
        # Cleaned up in setUp()
 
675
        log.properties_handler_registry.register(
 
676
            'trivial_custom_prop_handler',
 
677
            trivial_custom_prop_handler)
 
678
        self.assertFormatterResult("""\
 
679
------------------------------------------------------------
 
680
revno: 1
 
681
test_prop: test_value
 
682
author: John Doe <jdoe@example.com>
 
683
committer: Lorem Ipsum <test@example.com>
 
684
branch nick: test_properties_in_log
 
685
timestamp: Tue 2005-11-22 00:00:00 +0000
 
686
message:
 
687
  add a
 
688
""",
 
689
            wt.branch, log.LongLogFormatter)
 
690
 
 
691
    def test_properties_in_short_log(self):
 
692
        """Log includes the custom properties returned by the registered
 
693
        handlers.
 
694
        """
 
695
        wt = self.make_standard_commit('test_properties_in_short_log')
 
696
        def trivial_custom_prop_handler(revision):
 
697
            return {'test_prop':'test_value'}
 
698
 
 
699
        log.properties_handler_registry.register(
 
700
            'trivial_custom_prop_handler',
 
701
            trivial_custom_prop_handler)
 
702
        self.assertFormatterResult("""\
 
703
    1 John Doe\t2005-11-22
 
704
      test_prop: test_value
 
705
      add a
 
706
 
 
707
""",
 
708
            wt.branch, log.ShortLogFormatter)
 
709
 
 
710
    def test_error_in_properties_handler(self):
 
711
        """Log includes the custom properties returned by the registered
 
712
        handlers.
 
713
        """
 
714
        wt = self.make_standard_commit('error_in_properties_handler',
 
715
            revprops={'first_prop':'first_value'})
 
716
        sio = self.make_utf8_encoded_stringio()
 
717
        formatter = log.LongLogFormatter(to_file=sio)
 
718
        def trivial_custom_prop_handler(revision):
 
719
            raise Exception("a test error")
 
720
 
 
721
        log.properties_handler_registry.register(
 
722
            'trivial_custom_prop_handler',
 
723
            trivial_custom_prop_handler)
 
724
        self.assertRaises(Exception, log.show_log, wt.branch, formatter,)
 
725
 
 
726
    def test_properties_handler_bad_argument(self):
 
727
        wt = self.make_standard_commit('bad_argument',
 
728
              revprops={'a_prop':'test_value'})
 
729
        sio = self.make_utf8_encoded_stringio()
 
730
        formatter = log.LongLogFormatter(to_file=sio)
 
731
        def bad_argument_prop_handler(revision):
 
732
            return {'custom_prop_name':revision.properties['a_prop']}
 
733
 
 
734
        log.properties_handler_registry.register(
 
735
            'bad_argument_prop_handler',
 
736
            bad_argument_prop_handler)
 
737
 
 
738
        self.assertRaises(AttributeError, formatter.show_properties,
 
739
                          'a revision', '')
 
740
 
 
741
        revision = wt.branch.repository.get_revision(wt.branch.last_revision())
 
742
        formatter.show_properties(revision, '')
 
743
        self.assertEqualDiff('''custom_prop_name: test_value\n''',
 
744
                             sio.getvalue())
 
745
 
 
746
    def test_show_ids(self):
 
747
        wt = self.make_branch_and_tree('parent')
 
748
        self.build_tree(['parent/f1', 'parent/f2'])
 
749
        wt.add(['f1', 'f2'])
 
750
        self.wt_commit(wt, 'first post', rev_id=b'a')
 
751
        child_wt = wt.controldir.sprout('child').open_workingtree()
 
752
        self.wt_commit(child_wt, 'branch 1 changes', rev_id=b'b')
 
753
        wt.merge_from_branch(child_wt.branch)
 
754
        self.wt_commit(wt, 'merge branch 1', rev_id=b'c')
 
755
        self.assertFormatterResult("""\
 
756
------------------------------------------------------------
 
757
revno: 2 [merge]
 
758
revision-id: c
 
759
parent: a
 
760
parent: b
 
761
committer: Joe Foo <joe@foo.com>
 
762
branch nick: parent
 
763
timestamp: Tue 2005-11-22 00:00:02 +0000
 
764
message:
 
765
  merge branch 1
 
766
    ------------------------------------------------------------
 
767
    revno: 1.1.1
 
768
    revision-id: b
 
769
    parent: a
 
770
    committer: Joe Foo <joe@foo.com>
 
771
    branch nick: child
 
772
    timestamp: Tue 2005-11-22 00:00:01 +0000
 
773
    message:
 
774
      branch 1 changes
 
775
------------------------------------------------------------
 
776
revno: 1
 
777
revision-id: a
 
778
committer: Joe Foo <joe@foo.com>
 
779
branch nick: parent
 
780
timestamp: Tue 2005-11-22 00:00:00 +0000
 
781
message:
 
782
  first post
 
783
""",
 
784
            wt.branch, log.LongLogFormatter,
 
785
            formatter_kwargs=dict(levels=0, show_ids=True))
 
786
 
 
787
 
 
788
class TestLongLogFormatterWithoutMergeRevisions(TestCaseForLogFormatter):
 
789
 
 
790
    def test_long_verbose_log(self):
 
791
        """Verbose log includes changed files
 
792
 
 
793
        bug #4676
 
794
        """
 
795
        wt = self.make_standard_commit('test_long_verbose_log', authors=[])
 
796
        self.assertFormatterResult("""\
 
797
------------------------------------------------------------
 
798
revno: 1
 
799
committer: Lorem Ipsum <test@example.com>
 
800
branch nick: test_long_verbose_log
 
801
timestamp: Tue 2005-11-22 00:00:00 +0000
 
802
message:
 
803
  add a
 
804
added:
 
805
  a
 
806
""",
 
807
            wt.branch, log.LongLogFormatter,
 
808
            formatter_kwargs=dict(levels=1),
 
809
            show_log_kwargs=dict(verbose=True))
 
810
 
 
811
    def test_long_verbose_contain_deltas(self):
 
812
        wt = self.make_branch_and_tree('parent')
 
813
        self.build_tree(['parent/f1', 'parent/f2'])
 
814
        wt.add(['f1', 'f2'])
 
815
        self.wt_commit(wt, 'first post')
 
816
        child_wt = wt.controldir.sprout('child').open_workingtree()
 
817
        os.unlink('child/f1')
 
818
        self.build_tree_contents([('child/f2', b'hello\n')])
 
819
        self.wt_commit(child_wt, 'removed f1 and modified f2')
 
820
        wt.merge_from_branch(child_wt.branch)
 
821
        self.wt_commit(wt, 'merge branch 1')
 
822
        self.assertFormatterResult("""\
 
823
------------------------------------------------------------
 
824
revno: 2 [merge]
 
825
committer: Joe Foo <joe@foo.com>
 
826
branch nick: parent
 
827
timestamp: Tue 2005-11-22 00:00:02 +0000
 
828
message:
 
829
  merge branch 1
 
830
removed:
 
831
  f1
 
832
modified:
 
833
  f2
 
834
------------------------------------------------------------
 
835
revno: 1
 
836
committer: Joe Foo <joe@foo.com>
 
837
branch nick: parent
 
838
timestamp: Tue 2005-11-22 00:00:00 +0000
 
839
message:
 
840
  first post
 
841
added:
 
842
  f1
 
843
  f2
 
844
""",
 
845
            wt.branch, log.LongLogFormatter,
 
846
            formatter_kwargs=dict(levels=1),
 
847
            show_log_kwargs=dict(verbose=True))
 
848
 
 
849
    def test_long_trailing_newlines(self):
 
850
        wt = self.make_branch_and_tree('.')
 
851
        b = self.make_commits_with_trailing_newlines(wt)
 
852
        self.assertFormatterResult("""\
 
853
------------------------------------------------------------
 
854
revno: 3
 
855
committer: Joe Foo <joe@foo.com>
 
856
branch nick: test
 
857
timestamp: Tue 2005-11-22 00:00:02 +0000
 
858
message:
 
859
  single line with trailing newline
 
860
------------------------------------------------------------
 
861
revno: 2
 
862
committer: Joe Foo <joe@foo.com>
 
863
branch nick: test
 
864
timestamp: Tue 2005-11-22 00:00:01 +0000
 
865
message:
 
866
  multiline
 
867
  log
 
868
  message
 
869
------------------------------------------------------------
 
870
revno: 1
 
871
committer: Joe Foo <joe@foo.com>
 
872
branch nick: test
 
873
timestamp: Tue 2005-11-22 00:00:00 +0000
 
874
message:
 
875
  simple log message
 
876
""",
 
877
        b, log.LongLogFormatter,
 
878
        formatter_kwargs=dict(levels=1))
 
879
 
 
880
    def test_long_author_in_log(self):
 
881
        """Log includes the author name if it's set in
 
882
        the revision properties
 
883
        """
 
884
        wt = self.make_standard_commit('test_author_log')
 
885
        self.assertFormatterResult("""\
 
886
------------------------------------------------------------
 
887
revno: 1
 
888
author: John Doe <jdoe@example.com>
 
889
committer: Lorem Ipsum <test@example.com>
 
890
branch nick: test_author_log
 
891
timestamp: Tue 2005-11-22 00:00:00 +0000
 
892
message:
 
893
  add a
 
894
""",
 
895
            wt.branch, log.LongLogFormatter,
 
896
            formatter_kwargs=dict(levels=1))
 
897
 
 
898
    def test_long_properties_in_log(self):
 
899
        """Log includes the custom properties returned by the registered
 
900
        handlers.
 
901
        """
 
902
        wt = self.make_standard_commit('test_properties_in_log')
 
903
        def trivial_custom_prop_handler(revision):
 
904
            return {'test_prop':'test_value'}
 
905
 
 
906
        log.properties_handler_registry.register(
 
907
            'trivial_custom_prop_handler',
 
908
            trivial_custom_prop_handler)
 
909
        self.assertFormatterResult("""\
 
910
------------------------------------------------------------
 
911
revno: 1
 
912
test_prop: test_value
 
913
author: John Doe <jdoe@example.com>
 
914
committer: Lorem Ipsum <test@example.com>
 
915
branch nick: test_properties_in_log
 
916
timestamp: Tue 2005-11-22 00:00:00 +0000
 
917
message:
 
918
  add a
 
919
""",
 
920
            wt.branch, log.LongLogFormatter,
 
921
            formatter_kwargs=dict(levels=1))
 
922
 
 
923
 
 
924
class TestLineLogFormatter(TestCaseForLogFormatter):
 
925
 
 
926
    def test_line_log(self):
 
927
        """Line log should show revno
 
928
 
 
929
        bug #5162
 
930
        """
 
931
        wt = self.make_standard_commit('test-line-log',
 
932
                committer='Line-Log-Formatter Tester <test@line.log>',
 
933
                authors=[])
 
934
        self.assertFormatterResult("""\
 
935
1: Line-Log-Formatte... 2005-11-22 add a
 
936
""",
 
937
            wt.branch, log.LineLogFormatter)
 
938
 
 
939
    def test_trailing_newlines(self):
 
940
        wt = self.make_branch_and_tree('.')
 
941
        b = self.make_commits_with_trailing_newlines(wt)
 
942
        self.assertFormatterResult("""\
 
943
3: Joe Foo 2005-11-22 single line with trailing newline
 
944
2: Joe Foo 2005-11-22 multiline
 
945
1: Joe Foo 2005-11-22 simple log message
 
946
""",
 
947
            b, log.LineLogFormatter)
 
948
 
 
949
    def test_line_log_single_merge_revision(self):
 
950
        wt = self._prepare_tree_with_merges()
 
951
        revspec = revisionspec.RevisionSpec.from_string('1.1.1')
 
952
        rev = revspec.in_history(wt.branch)
 
953
        self.assertFormatterResult("""\
 
954
1.1.1: Joe Foo 2005-11-22 rev-merged
 
955
""",
 
956
            wt.branch, log.LineLogFormatter,
 
957
            show_log_kwargs=dict(start_revision=rev, end_revision=rev))
 
958
 
 
959
    def test_line_log_with_tags(self):
 
960
        wt = self._prepare_tree_with_merges(with_tags=True)
 
961
        self.assertFormatterResult("""\
 
962
3: Joe Foo 2005-11-22 {v1.0, v1.0rc1} rev-3
 
963
2: Joe Foo 2005-11-22 [merge] {v0.2} rev-2
 
964
1: Joe Foo 2005-11-22 rev-1
 
965
""",
 
966
            wt.branch, log.LineLogFormatter)
 
967
 
 
968
 
 
969
class TestLineLogFormatterWithMergeRevisions(TestCaseForLogFormatter):
 
970
 
 
971
    def test_line_merge_revs_log(self):
 
972
        """Line log should show revno
 
973
 
 
974
        bug #5162
 
975
        """
 
976
        wt = self.make_standard_commit('test-line-log',
 
977
                committer='Line-Log-Formatter Tester <test@line.log>',
 
978
                authors=[])
 
979
        self.assertFormatterResult("""\
 
980
1: Line-Log-Formatte... 2005-11-22 add a
 
981
""",
 
982
            wt.branch, log.LineLogFormatter)
 
983
 
 
984
    def test_line_merge_revs_log_single_merge_revision(self):
 
985
        wt = self._prepare_tree_with_merges()
 
986
        revspec = revisionspec.RevisionSpec.from_string('1.1.1')
 
987
        rev = revspec.in_history(wt.branch)
 
988
        self.assertFormatterResult("""\
 
989
1.1.1: Joe Foo 2005-11-22 rev-merged
 
990
""",
 
991
            wt.branch, log.LineLogFormatter,
 
992
            formatter_kwargs=dict(levels=0),
 
993
            show_log_kwargs=dict(start_revision=rev, end_revision=rev))
 
994
 
 
995
    def test_line_merge_revs_log_with_merges(self):
 
996
        wt = self._prepare_tree_with_merges()
 
997
        self.assertFormatterResult("""\
 
998
2: Joe Foo 2005-11-22 [merge] rev-2
 
999
  1.1.1: Joe Foo 2005-11-22 rev-merged
 
1000
1: Joe Foo 2005-11-22 rev-1
 
1001
""",
 
1002
            wt.branch, log.LineLogFormatter,
 
1003
            formatter_kwargs=dict(levels=0))
 
1004
 
 
1005
 
 
1006
class TestGnuChangelogFormatter(TestCaseForLogFormatter):
 
1007
 
 
1008
    def test_gnu_changelog(self):
 
1009
        wt = self.make_standard_commit('nicky', authors=[])
 
1010
        self.assertFormatterResult('''\
 
1011
2005-11-22  Lorem Ipsum  <test@example.com>
 
1012
 
 
1013
\tadd a
 
1014
 
 
1015
''',
 
1016
            wt.branch, log.GnuChangelogLogFormatter)
 
1017
 
 
1018
    def test_with_authors(self):
 
1019
        wt = self.make_standard_commit('nicky',
 
1020
            authors=['Fooa Fooz <foo@example.com>',
 
1021
                     'Bari Baro <bar@example.com>'])
 
1022
        self.assertFormatterResult('''\
 
1023
2005-11-22  Fooa Fooz  <foo@example.com>
 
1024
 
 
1025
\tadd a
 
1026
 
 
1027
''',
 
1028
            wt.branch, log.GnuChangelogLogFormatter)
 
1029
 
 
1030
    def test_verbose(self):
 
1031
        wt = self.make_standard_commit('nicky')
 
1032
        self.assertFormatterResult('''\
 
1033
2005-11-22  John Doe  <jdoe@example.com>
 
1034
 
 
1035
\t* a:
 
1036
 
 
1037
\tadd a
 
1038
 
 
1039
''',
 
1040
            wt.branch, log.GnuChangelogLogFormatter,
 
1041
            show_log_kwargs=dict(verbose=True))
 
1042
 
 
1043
 
 
1044
class TestShowChangedRevisions(tests.TestCaseWithTransport):
 
1045
 
 
1046
    def test_show_changed_revisions_verbose(self):
 
1047
        tree = self.make_branch_and_tree('tree_a')
 
1048
        self.build_tree(['tree_a/foo'])
 
1049
        tree.add('foo')
 
1050
        tree.commit('bar', rev_id=b'bar-id')
 
1051
        s = self.make_utf8_encoded_stringio()
 
1052
        log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
 
1053
        self.assertContainsRe(s.getvalue(), 'bar')
 
1054
        self.assertNotContainsRe(s.getvalue(), 'foo')
 
1055
 
 
1056
 
 
1057
class TestLogFormatter(tests.TestCase):
 
1058
 
 
1059
    def setUp(self):
 
1060
        super(TestLogFormatter, self).setUp()
 
1061
        self.rev = revision.Revision('a-id')
 
1062
        self.lf = log.LogFormatter(None)
 
1063
 
 
1064
    def test_short_committer(self):
 
1065
        def assertCommitter(expected, committer):
 
1066
            self.rev.committer = committer
 
1067
            self.assertEqual(expected, self.lf.short_committer(self.rev))
 
1068
 
 
1069
        assertCommitter('John Doe', 'John Doe <jdoe@example.com>')
 
1070
        assertCommitter('John Smith', 'John Smith <jsmith@example.com>')
 
1071
        assertCommitter('John Smith', 'John Smith')
 
1072
        assertCommitter('jsmith@example.com', 'jsmith@example.com')
 
1073
        assertCommitter('jsmith@example.com', '<jsmith@example.com>')
 
1074
        assertCommitter('John Smith', 'John Smith jsmith@example.com')
 
1075
 
 
1076
    def test_short_author(self):
 
1077
        def assertAuthor(expected, author):
 
1078
            self.rev.properties['author'] = author
 
1079
            self.assertEqual(expected, self.lf.short_author(self.rev))
 
1080
 
 
1081
        assertAuthor('John Smith', 'John Smith <jsmith@example.com>')
 
1082
        assertAuthor('John Smith', 'John Smith')
 
1083
        assertAuthor('jsmith@example.com', 'jsmith@example.com')
 
1084
        assertAuthor('jsmith@example.com', '<jsmith@example.com>')
 
1085
        assertAuthor('John Smith', 'John Smith jsmith@example.com')
 
1086
 
 
1087
    def test_short_author_from_committer(self):
 
1088
        self.rev.committer = 'John Doe <jdoe@example.com>'
 
1089
        self.assertEqual('John Doe', self.lf.short_author(self.rev))
 
1090
 
 
1091
    def test_short_author_from_authors(self):
 
1092
        self.rev.properties['authors'] = ('John Smith <jsmith@example.com>\n'
 
1093
                                          'Jane Rey <jrey@example.com>')
 
1094
        self.assertEqual('John Smith', self.lf.short_author(self.rev))
 
1095
 
 
1096
 
 
1097
class TestReverseByDepth(tests.TestCase):
 
1098
    """Test reverse_by_depth behavior.
 
1099
 
 
1100
    This is used to present revisions in forward (oldest first) order in a nice
 
1101
    layout.
 
1102
 
 
1103
    The tests use lighter revision description to ease reading.
 
1104
    """
 
1105
 
 
1106
    def assertReversed(self, forward, backward):
 
1107
        # Transform the descriptions to suit the API: tests use (revno, depth),
 
1108
        # while the API expects (revid, revno, depth)
 
1109
        def complete_revisions(l):
 
1110
            """Transform the description to suit the API.
 
1111
 
 
1112
            Tests use (revno, depth) whil the API expects (revid, revno, depth).
 
1113
            Since the revid is arbitrary, we just duplicate revno
 
1114
            """
 
1115
            return [ (r, r, d) for r, d in l]
 
1116
        forward = complete_revisions(forward)
 
1117
        backward= complete_revisions(backward)
 
1118
        self.assertEqual(forward, log.reverse_by_depth(backward))
 
1119
 
 
1120
 
 
1121
    def test_mainline_revisions(self):
 
1122
        self.assertReversed([( '1', 0), ('2', 0)],
 
1123
                            [('2', 0), ('1', 0)])
 
1124
 
 
1125
    def test_merged_revisions(self):
 
1126
        self.assertReversed([('1', 0), ('2', 0), ('2.2', 1), ('2.1', 1),],
 
1127
                            [('2', 0), ('2.1', 1), ('2.2', 1), ('1', 0),])
 
1128
    def test_shifted_merged_revisions(self):
 
1129
        """Test irregular layout.
 
1130
 
 
1131
        Requesting revisions touching a file can produce "holes" in the depths.
 
1132
        """
 
1133
        self.assertReversed([('1', 0), ('2', 0), ('1.1', 2), ('1.2', 2),],
 
1134
                            [('2', 0), ('1.2', 2), ('1.1', 2), ('1', 0),])
 
1135
 
 
1136
    def test_merged_without_child_revisions(self):
 
1137
        """Test irregular layout.
 
1138
 
 
1139
        Revision ranges can produce "holes" in the depths.
 
1140
        """
 
1141
        # When a revision of higher depth doesn't follow one of lower depth, we
 
1142
        # assume a lower depth one is virtually there
 
1143
        self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
 
1144
                            [('4', 4), ('3', 3), ('2', 2), ('1', 2),])
 
1145
        # So we get the same order after reversing below even if the original
 
1146
        # revisions are not in the same order.
 
1147
        self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
 
1148
                            [('3', 3), ('4', 4), ('2', 2), ('1', 2),])
 
1149
 
 
1150
 
 
1151
class TestHistoryChange(tests.TestCaseWithTransport):
 
1152
 
 
1153
    def setup_a_tree(self):
 
1154
        tree = self.make_branch_and_tree('tree')
 
1155
        tree.lock_write()
 
1156
        self.addCleanup(tree.unlock)
 
1157
        tree.commit('1a', rev_id=b'1a')
 
1158
        tree.commit('2a', rev_id=b'2a')
 
1159
        tree.commit('3a', rev_id=b'3a')
 
1160
        return tree
 
1161
 
 
1162
    def setup_ab_tree(self):
 
1163
        tree = self.setup_a_tree()
 
1164
        tree.set_last_revision('1a')
 
1165
        tree.branch.set_last_revision_info(1, '1a')
 
1166
        tree.commit('2b', rev_id=b'2b')
 
1167
        tree.commit('3b', rev_id=b'3b')
 
1168
        return tree
 
1169
 
 
1170
    def setup_ac_tree(self):
 
1171
        tree = self.setup_a_tree()
 
1172
        tree.set_last_revision(revision.NULL_REVISION)
 
1173
        tree.branch.set_last_revision_info(0, revision.NULL_REVISION)
 
1174
        tree.commit('1c', rev_id=b'1c')
 
1175
        tree.commit('2c', rev_id=b'2c')
 
1176
        tree.commit('3c', rev_id=b'3c')
 
1177
        return tree
 
1178
 
 
1179
    def test_all_new(self):
 
1180
        tree = self.setup_ab_tree()
 
1181
        old, new = log.get_history_change('1a', '3a', tree.branch.repository)
 
1182
        self.assertEqual([], old)
 
1183
        self.assertEqual(['2a', '3a'], new)
 
1184
 
 
1185
    def test_all_old(self):
 
1186
        tree = self.setup_ab_tree()
 
1187
        old, new = log.get_history_change('3a', '1a', tree.branch.repository)
 
1188
        self.assertEqual([], new)
 
1189
        self.assertEqual(['2a', '3a'], old)
 
1190
 
 
1191
    def test_null_old(self):
 
1192
        tree = self.setup_ab_tree()
 
1193
        old, new = log.get_history_change(revision.NULL_REVISION,
 
1194
                                          '3a', tree.branch.repository)
 
1195
        self.assertEqual([], old)
 
1196
        self.assertEqual(['1a', '2a', '3a'], new)
 
1197
 
 
1198
    def test_null_new(self):
 
1199
        tree = self.setup_ab_tree()
 
1200
        old, new = log.get_history_change('3a', revision.NULL_REVISION,
 
1201
                                          tree.branch.repository)
 
1202
        self.assertEqual([], new)
 
1203
        self.assertEqual(['1a', '2a', '3a'], old)
 
1204
 
 
1205
    def test_diverged(self):
 
1206
        tree = self.setup_ab_tree()
 
1207
        old, new = log.get_history_change('3a', '3b', tree.branch.repository)
 
1208
        self.assertEqual(old, ['2a', '3a'])
 
1209
        self.assertEqual(new, ['2b', '3b'])
 
1210
 
 
1211
    def test_unrelated(self):
 
1212
        tree = self.setup_ac_tree()
 
1213
        old, new = log.get_history_change('3a', '3c', tree.branch.repository)
 
1214
        self.assertEqual(old, ['1a', '2a', '3a'])
 
1215
        self.assertEqual(new, ['1c', '2c', '3c'])
 
1216
 
 
1217
    def test_show_branch_change(self):
 
1218
        tree = self.setup_ab_tree()
 
1219
        s = BytesIO()
 
1220
        log.show_branch_change(tree.branch, s, 3, '3a')
 
1221
        self.assertContainsRe(s.getvalue(),
 
1222
            '[*]{60}\nRemoved Revisions:\n(.|\n)*2a(.|\n)*3a(.|\n)*'
 
1223
            '[*]{60}\n\nAdded Revisions:\n(.|\n)*2b(.|\n)*3b')
 
1224
 
 
1225
    def test_show_branch_change_no_change(self):
 
1226
        tree = self.setup_ab_tree()
 
1227
        s = BytesIO()
 
1228
        log.show_branch_change(tree.branch, s, 3, '3b')
 
1229
        self.assertEqual(s.getvalue(),
 
1230
            'Nothing seems to have changed\n')
 
1231
 
 
1232
    def test_show_branch_change_no_old(self):
 
1233
        tree = self.setup_ab_tree()
 
1234
        s = BytesIO()
 
1235
        log.show_branch_change(tree.branch, s, 2, '2b')
 
1236
        self.assertContainsRe(s.getvalue(), 'Added Revisions:')
 
1237
        self.assertNotContainsRe(s.getvalue(), 'Removed Revisions:')
 
1238
 
 
1239
    def test_show_branch_change_no_new(self):
 
1240
        tree = self.setup_ab_tree()
 
1241
        tree.branch.set_last_revision_info(2, '2b')
 
1242
        s = BytesIO()
 
1243
        log.show_branch_change(tree.branch, s, 3, '3b')
 
1244
        self.assertContainsRe(s.getvalue(), 'Removed Revisions:')
 
1245
        self.assertNotContainsRe(s.getvalue(), 'Added Revisions:')
 
1246
 
 
1247
 
 
1248
class TestRevisionNotInBranch(TestCaseForLogFormatter):
 
1249
 
 
1250
    def setup_a_tree(self):
 
1251
        tree = self.make_branch_and_tree('tree')
 
1252
        tree.lock_write()
 
1253
        self.addCleanup(tree.unlock)
 
1254
        kwargs = {
 
1255
            'committer': 'Joe Foo <joe@foo.com>',
 
1256
            'timestamp': 1132617600, # Mon 2005-11-22 00:00:00 +0000
 
1257
            'timezone': 0, # UTC
 
1258
        }
 
1259
        tree.commit('commit 1a', rev_id=b'1a', **kwargs)
 
1260
        tree.commit('commit 2a', rev_id=b'2a', **kwargs)
 
1261
        tree.commit('commit 3a', rev_id=b'3a', **kwargs)
 
1262
        return tree
 
1263
 
 
1264
    def setup_ab_tree(self):
 
1265
        tree = self.setup_a_tree()
 
1266
        tree.set_last_revision('1a')
 
1267
        tree.branch.set_last_revision_info(1, '1a')
 
1268
        kwargs = {
 
1269
            'committer': 'Joe Foo <joe@foo.com>',
 
1270
            'timestamp': 1132617600, # Mon 2005-11-22 00:00:00 +0000
 
1271
            'timezone': 0, # UTC
 
1272
        }
 
1273
        tree.commit('commit 2b', rev_id=b'2b', **kwargs)
 
1274
        tree.commit('commit 3b', rev_id=b'3b', **kwargs)
 
1275
        return tree
 
1276
 
 
1277
    def test_one_revision(self):
 
1278
        tree = self.setup_ab_tree()
 
1279
        lf = LogCatcher()
 
1280
        rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
 
1281
        log.show_log(tree.branch, lf, verbose=True, start_revision=rev,
 
1282
                     end_revision=rev)
 
1283
        self.assertEqual(1, len(lf.revisions))
 
1284
        self.assertEqual(None, lf.revisions[0].revno)   # Out-of-branch
 
1285
        self.assertEqual('3a', lf.revisions[0].rev.revision_id)
 
1286
 
 
1287
    def test_many_revisions(self):
 
1288
        tree = self.setup_ab_tree()
 
1289
        lf = LogCatcher()
 
1290
        start_rev = revisionspec.RevisionInfo(tree.branch, None, '1a')
 
1291
        end_rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
 
1292
        log.show_log(tree.branch, lf, verbose=True, start_revision=start_rev,
 
1293
                     end_revision=end_rev)
 
1294
        self.assertEqual(3, len(lf.revisions))
 
1295
        self.assertEqual(None, lf.revisions[0].revno)   # Out-of-branch
 
1296
        self.assertEqual('3a', lf.revisions[0].rev.revision_id)
 
1297
        self.assertEqual(None, lf.revisions[1].revno)   # Out-of-branch
 
1298
        self.assertEqual('2a', lf.revisions[1].rev.revision_id)
 
1299
        self.assertEqual('1', lf.revisions[2].revno)    # In-branch
 
1300
 
 
1301
    def test_long_format(self):
 
1302
        tree = self.setup_ab_tree()
 
1303
        start_rev = revisionspec.RevisionInfo(tree.branch, None, '1a')
 
1304
        end_rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
 
1305
        self.assertFormatterResult("""\
 
1306
------------------------------------------------------------
 
1307
revision-id: 3a
 
1308
committer: Joe Foo <joe@foo.com>
 
1309
branch nick: tree
 
1310
timestamp: Tue 2005-11-22 00:00:00 +0000
 
1311
message:
 
1312
  commit 3a
 
1313
------------------------------------------------------------
 
1314
revision-id: 2a
 
1315
committer: Joe Foo <joe@foo.com>
 
1316
branch nick: tree
 
1317
timestamp: Tue 2005-11-22 00:00:00 +0000
 
1318
message:
 
1319
  commit 2a
 
1320
------------------------------------------------------------
 
1321
revno: 1
 
1322
committer: Joe Foo <joe@foo.com>
 
1323
branch nick: tree
 
1324
timestamp: Tue 2005-11-22 00:00:00 +0000
 
1325
message:
 
1326
  commit 1a
 
1327
""",
 
1328
            tree.branch, log.LongLogFormatter, show_log_kwargs={
 
1329
                'start_revision': start_rev, 'end_revision': end_rev
 
1330
            })
 
1331
 
 
1332
    def test_short_format(self):
 
1333
        tree = self.setup_ab_tree()
 
1334
        start_rev = revisionspec.RevisionInfo(tree.branch, None, '1a')
 
1335
        end_rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
 
1336
        self.assertFormatterResult("""\
 
1337
      Joe Foo\t2005-11-22
 
1338
      revision-id:3a
 
1339
      commit 3a
 
1340
 
 
1341
      Joe Foo\t2005-11-22
 
1342
      revision-id:2a
 
1343
      commit 2a
 
1344
 
 
1345
    1 Joe Foo\t2005-11-22
 
1346
      commit 1a
 
1347
 
 
1348
""",
 
1349
            tree.branch, log.ShortLogFormatter, show_log_kwargs={
 
1350
                'start_revision': start_rev, 'end_revision': end_rev
 
1351
            })
 
1352
 
 
1353
    def test_line_format(self):
 
1354
        tree = self.setup_ab_tree()
 
1355
        start_rev = revisionspec.RevisionInfo(tree.branch, None, '1a')
 
1356
        end_rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
 
1357
        self.assertFormatterResult("""\
 
1358
Joe Foo 2005-11-22 commit 3a
 
1359
Joe Foo 2005-11-22 commit 2a
 
1360
1: Joe Foo 2005-11-22 commit 1a
 
1361
""",
 
1362
            tree.branch, log.LineLogFormatter, show_log_kwargs={
 
1363
                'start_revision': start_rev, 'end_revision': end_rev
 
1364
            })
 
1365
 
 
1366
 
 
1367
class TestLogWithBugs(TestCaseForLogFormatter, TestLogMixin):
 
1368
 
 
1369
    def setUp(self):
 
1370
        super(TestLogWithBugs, self).setUp()
 
1371
        log.properties_handler_registry.register(
 
1372
            'bugs_properties_handler',
 
1373
            log._bugs_properties_handler)
 
1374
 
 
1375
    def make_commits_with_bugs(self):
 
1376
        """Helper method for LogFormatter tests"""
 
1377
        tree = self.make_branch_and_tree(u'.')
 
1378
        self.build_tree(['a', 'b'])
 
1379
        tree.add('a')
 
1380
        self.wt_commit(tree, 'simple log message', rev_id=b'a1',
 
1381
                       revprops={'bugs': 'test://bug/id fixed'})
 
1382
        tree.add('b')
 
1383
        self.wt_commit(tree, 'multiline\nlog\nmessage\n', rev_id=b'a2',
 
1384
                       authors=['Joe Bar <joe@bar.com>'],
 
1385
                       revprops={'bugs': 'test://bug/id fixed\n'
 
1386
                                 'test://bug/2 fixed'})
 
1387
        return tree
 
1388
 
 
1389
 
 
1390
    def test_long_bugs(self):
 
1391
        tree = self.make_commits_with_bugs()
 
1392
        self.assertFormatterResult("""\
 
1393
------------------------------------------------------------
 
1394
revno: 2
 
1395
fixes bugs: test://bug/id test://bug/2
 
1396
author: Joe Bar <joe@bar.com>
 
1397
committer: Joe Foo <joe@foo.com>
 
1398
branch nick: work
 
1399
timestamp: Tue 2005-11-22 00:00:01 +0000
 
1400
message:
 
1401
  multiline
 
1402
  log
 
1403
  message
 
1404
------------------------------------------------------------
 
1405
revno: 1
 
1406
fixes bug: test://bug/id
 
1407
committer: Joe Foo <joe@foo.com>
 
1408
branch nick: work
 
1409
timestamp: Tue 2005-11-22 00:00:00 +0000
 
1410
message:
 
1411
  simple log message
 
1412
""",
 
1413
            tree.branch, log.LongLogFormatter)
 
1414
 
 
1415
    def test_short_bugs(self):
 
1416
        tree = self.make_commits_with_bugs()
 
1417
        self.assertFormatterResult("""\
 
1418
    2 Joe Bar\t2005-11-22
 
1419
      fixes bugs: test://bug/id test://bug/2
 
1420
      multiline
 
1421
      log
 
1422
      message
 
1423
 
 
1424
    1 Joe Foo\t2005-11-22
 
1425
      fixes bug: test://bug/id
 
1426
      simple log message
 
1427
 
 
1428
""",
 
1429
            tree.branch, log.ShortLogFormatter)
 
1430
 
 
1431
    def test_wrong_bugs_property(self):
 
1432
        tree = self.make_branch_and_tree(u'.')
 
1433
        self.build_tree(['foo'])
 
1434
        self.wt_commit(tree, 'simple log message', rev_id=b'a1',
 
1435
                       revprops={'bugs': 'test://bug/id invalid_value'})
 
1436
        self.assertFormatterResult("""\
 
1437
    1 Joe Foo\t2005-11-22
 
1438
      simple log message
 
1439
 
 
1440
""",
 
1441
            tree.branch, log.ShortLogFormatter)
 
1442
 
 
1443
    def test_bugs_handler_present(self):
 
1444
        self.properties_handler_registry.get('bugs_properties_handler')
 
1445
 
 
1446
 
 
1447
class TestLogForAuthors(TestCaseForLogFormatter):
 
1448
 
 
1449
    def setUp(self):
 
1450
        super(TestLogForAuthors, self).setUp()
 
1451
        self.wt = self.make_standard_commit('nicky',
 
1452
            authors=['John Doe <jdoe@example.com>',
 
1453
                     'Jane Rey <jrey@example.com>'])
 
1454
 
 
1455
    def assertFormatterResult(self, formatter, who, result):
 
1456
        formatter_kwargs = dict()
 
1457
        if who is not None:
 
1458
            author_list_handler = log.author_list_registry.get(who)
 
1459
            formatter_kwargs['author_list_handler'] = author_list_handler
 
1460
        TestCaseForLogFormatter.assertFormatterResult(self, result,
 
1461
            self.wt.branch, formatter, formatter_kwargs=formatter_kwargs)
 
1462
 
 
1463
    def test_line_default(self):
 
1464
        self.assertFormatterResult(log.LineLogFormatter, None, """\
 
1465
1: John Doe 2005-11-22 add a
 
1466
""")
 
1467
 
 
1468
    def test_line_committer(self):
 
1469
        self.assertFormatterResult(log.LineLogFormatter, 'committer', """\
 
1470
1: Lorem Ipsum 2005-11-22 add a
 
1471
""")
 
1472
 
 
1473
    def test_line_first(self):
 
1474
        self.assertFormatterResult(log.LineLogFormatter, 'first', """\
 
1475
1: John Doe 2005-11-22 add a
 
1476
""")
 
1477
 
 
1478
    def test_line_all(self):
 
1479
        self.assertFormatterResult(log.LineLogFormatter, 'all', """\
 
1480
1: John Doe, Jane Rey 2005-11-22 add a
 
1481
""")
 
1482
 
 
1483
 
 
1484
    def test_short_default(self):
 
1485
        self.assertFormatterResult(log.ShortLogFormatter, None, """\
 
1486
    1 John Doe\t2005-11-22
 
1487
      add a
 
1488
 
 
1489
""")
 
1490
 
 
1491
    def test_short_committer(self):
 
1492
        self.assertFormatterResult(log.ShortLogFormatter, 'committer', """\
 
1493
    1 Lorem Ipsum\t2005-11-22
 
1494
      add a
 
1495
 
 
1496
""")
 
1497
 
 
1498
    def test_short_first(self):
 
1499
        self.assertFormatterResult(log.ShortLogFormatter, 'first', """\
 
1500
    1 John Doe\t2005-11-22
 
1501
      add a
 
1502
 
 
1503
""")
 
1504
 
 
1505
    def test_short_all(self):
 
1506
        self.assertFormatterResult(log.ShortLogFormatter, 'all', """\
 
1507
    1 John Doe, Jane Rey\t2005-11-22
 
1508
      add a
 
1509
 
 
1510
""")
 
1511
 
 
1512
    def test_long_default(self):
 
1513
        self.assertFormatterResult(log.LongLogFormatter, None, """\
 
1514
------------------------------------------------------------
 
1515
revno: 1
 
1516
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
 
1517
committer: Lorem Ipsum <test@example.com>
 
1518
branch nick: nicky
 
1519
timestamp: Tue 2005-11-22 00:00:00 +0000
 
1520
message:
 
1521
  add a
 
1522
""")
 
1523
 
 
1524
    def test_long_committer(self):
 
1525
        self.assertFormatterResult(log.LongLogFormatter, 'committer', """\
 
1526
------------------------------------------------------------
 
1527
revno: 1
 
1528
committer: Lorem Ipsum <test@example.com>
 
1529
branch nick: nicky
 
1530
timestamp: Tue 2005-11-22 00:00:00 +0000
 
1531
message:
 
1532
  add a
 
1533
""")
 
1534
 
 
1535
    def test_long_first(self):
 
1536
        self.assertFormatterResult(log.LongLogFormatter, 'first', """\
 
1537
------------------------------------------------------------
 
1538
revno: 1
 
1539
author: John Doe <jdoe@example.com>
 
1540
committer: Lorem Ipsum <test@example.com>
 
1541
branch nick: nicky
 
1542
timestamp: Tue 2005-11-22 00:00:00 +0000
 
1543
message:
 
1544
  add a
 
1545
""")
 
1546
 
 
1547
    def test_long_all(self):
 
1548
        self.assertFormatterResult(log.LongLogFormatter, 'all', """\
 
1549
------------------------------------------------------------
 
1550
revno: 1
 
1551
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
 
1552
committer: Lorem Ipsum <test@example.com>
 
1553
branch nick: nicky
 
1554
timestamp: Tue 2005-11-22 00:00:00 +0000
 
1555
message:
 
1556
  add a
 
1557
""")
 
1558
 
 
1559
    def test_gnu_changelog_default(self):
 
1560
        self.assertFormatterResult(log.GnuChangelogLogFormatter, None, """\
 
1561
2005-11-22  John Doe  <jdoe@example.com>
 
1562
 
 
1563
\tadd a
 
1564
 
 
1565
""")
 
1566
 
 
1567
    def test_gnu_changelog_committer(self):
 
1568
        self.assertFormatterResult(log.GnuChangelogLogFormatter, 'committer', """\
 
1569
2005-11-22  Lorem Ipsum  <test@example.com>
 
1570
 
 
1571
\tadd a
 
1572
 
 
1573
""")
 
1574
 
 
1575
    def test_gnu_changelog_first(self):
 
1576
        self.assertFormatterResult(log.GnuChangelogLogFormatter, 'first', """\
 
1577
2005-11-22  John Doe  <jdoe@example.com>
 
1578
 
 
1579
\tadd a
 
1580
 
 
1581
""")
 
1582
 
 
1583
    def test_gnu_changelog_all(self):
 
1584
        self.assertFormatterResult(log.GnuChangelogLogFormatter, 'all', """\
 
1585
2005-11-22  John Doe  <jdoe@example.com>, Jane Rey  <jrey@example.com>
 
1586
 
 
1587
\tadd a
 
1588
 
 
1589
""")
 
1590
 
 
1591
 
 
1592
class TestLogExcludeAncestry(tests.TestCaseWithTransport):
 
1593
 
 
1594
    def make_branch_with_alternate_ancestries(self, relpath='.'):
 
1595
        # See test_merge_sorted_exclude_ancestry below for the difference with
 
1596
        # bt.per_branch.test_iter_merge_sorted_revision.
 
1597
        # TestIterMergeSortedRevisionsBushyGraph.
 
1598
        # make_branch_with_alternate_ancestries
 
1599
        # and test_merge_sorted_exclude_ancestry
 
1600
        # See the FIXME in assertLogRevnos too.
 
1601
        builder = branchbuilder.BranchBuilder(self.get_transport(relpath))
 
1602
        # 1
 
1603
        # |\
 
1604
        # 2 \
 
1605
        # |  |
 
1606
        # |  1.1.1
 
1607
        # |  | \
 
1608
        # |  |  1.2.1
 
1609
        # |  | /
 
1610
        # |  1.1.2
 
1611
        # | /
 
1612
        # 3
 
1613
        builder.start_series()
 
1614
        builder.build_snapshot(None, [
 
1615
            ('add', ('', 'TREE_ROOT', 'directory', '')),],
 
1616
            revision_id='1')
 
1617
        builder.build_snapshot(['1'], [], revision_id='1.1.1')
 
1618
        builder.build_snapshot(['1'], [], revision_id='2')
 
1619
        builder.build_snapshot(['1.1.1'], [], revision_id='1.2.1')
 
1620
        builder.build_snapshot(['1.1.1', '1.2.1'], [], revision_id='1.1.2')
 
1621
        builder.build_snapshot(['2', '1.1.2'], [], revision_id='3')
 
1622
        builder.finish_series()
 
1623
        br = builder.get_branch()
 
1624
        br.lock_read()
 
1625
        self.addCleanup(br.unlock)
 
1626
        return br
 
1627
 
 
1628
    def assertLogRevnos(self, expected_revnos, b, start, end,
 
1629
                        exclude_common_ancestry, generate_merge_revisions=True):
 
1630
        # FIXME: the layering in log makes it hard to test intermediate levels,
 
1631
        # I wish adding filters with their parameters was easier...
 
1632
        # -- vila 20100413
 
1633
        iter_revs = log._calc_view_revisions(
 
1634
            b, start, end, direction='reverse',
 
1635
            generate_merge_revisions=generate_merge_revisions,
 
1636
            exclude_common_ancestry=exclude_common_ancestry)
 
1637
        self.assertEqual(expected_revnos,
 
1638
                         [revid for revid, revno, depth in iter_revs])
 
1639
 
 
1640
    def test_merge_sorted_exclude_ancestry(self):
 
1641
        b = self.make_branch_with_alternate_ancestries()
 
1642
        self.assertLogRevnos(['3', '1.1.2', '1.2.1', '1.1.1', '2', '1'],
 
1643
                             b, '1', '3', exclude_common_ancestry=False)
 
1644
        # '2' is part of the '3' ancestry but not part of '1.1.1' ancestry so
 
1645
        # it should be mentioned even if merge_sort order will make it appear
 
1646
        # after 1.1.1
 
1647
        self.assertLogRevnos(['3', '1.1.2', '1.2.1', '2'],
 
1648
                             b, '1.1.1', '3', exclude_common_ancestry=True)
 
1649
 
 
1650
    def test_merge_sorted_simple_revnos_exclude_ancestry(self):
 
1651
        b = self.make_branch_with_alternate_ancestries()
 
1652
        self.assertLogRevnos(['3', '2'],
 
1653
                             b, '1', '3', exclude_common_ancestry=True,
 
1654
                             generate_merge_revisions=False)
 
1655
        self.assertLogRevnos(['3', '1.1.2', '1.2.1', '1.1.1', '2'],
 
1656
                             b, '1', '3', exclude_common_ancestry=True,
 
1657
                             generate_merge_revisions=True)
 
1658
 
 
1659
 
 
1660
class TestLogDefaults(TestCaseForLogFormatter):
 
1661
    def test_default_log_level(self):
 
1662
        """
 
1663
        Test to ensure that specifying 'levels=1' to make_log_request_dict
 
1664
        doesn't get overwritten when using a LogFormatter that supports more
 
1665
        detail.
 
1666
        Fixes bug #747958.
 
1667
        """
 
1668
        wt = self._prepare_tree_with_merges()
 
1669
        b = wt.branch
 
1670
 
 
1671
        class CustomLogFormatter(log.LogFormatter):
 
1672
            def __init__(self, *args, **kwargs):
 
1673
                super(CustomLogFormatter, self).__init__(*args, **kwargs)
 
1674
                self.revisions = []
 
1675
            def get_levels(self):
 
1676
                # log formatter supports all levels:
 
1677
                return 0
 
1678
            def log_revision(self, revision):
 
1679
                self.revisions.append(revision)
 
1680
 
 
1681
        log_formatter = LogCatcher()
 
1682
        # First request we don't specify number of levels, we should get a
 
1683
        # sensible default (whatever the LogFormatter handles - which in this
 
1684
        # case is 0/everything):
 
1685
        request = log.make_log_request_dict(limit=10)
 
1686
        log.Logger(b, request).show(log_formatter)
 
1687
        # should have all three revisions:
 
1688
        self.assertEqual(len(log_formatter.revisions), 3)
 
1689
 
 
1690
        del log_formatter
 
1691
        log_formatter = LogCatcher()
 
1692
        # now explicitly request mainline revisions only:
 
1693
        request = log.make_log_request_dict(limit=10, levels=1)
 
1694
        log.Logger(b, request).show(log_formatter)
 
1695
        # should now only have 2 revisions:
 
1696
        self.assertEqual(len(log_formatter.revisions), 2)
 
1697