/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

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