~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

  • Committer: mbp at sourcefrog
  • Date: 2005-03-23 23:54:45 UTC
  • Revision ID: mbp@sourcefrog.net-20050323235445-a185b1b1f2a86dfe
mention "info" in top-level help

Show diffs side-by-side

added added

removed removed

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