~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-09-29 22:03:03 UTC
  • mfrom: (5416.2.6 jam-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20100929220303-cr95h8iwtggco721
(mbp) Add 'break-lock --force'

Show diffs side-by-side

added added

removed removed

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