~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

Merge bzr.dev to resolve conflicts

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