~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

  • Committer: Vincent Ladeuil
  • Date: 2009-06-17 16:28:02 UTC
  • mfrom: (4371.5.1 gdfo-heads)
  • mto: This revision was merged to the branch mainline in revision 4466.
  • Revision ID: v.ladeuil+lp@free.fr-20090617162802-gcbr56os427emput
Merge min_gdfo patch by jam

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:')