/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: Robert Collins
  • Date: 2009-04-30 06:16:30 UTC
  • mfrom: (3331.4.1 test-suite.lock_checking)
  • mto: This revision was merged to the branch mainline in revision 4317.
  • Revision ID: robertc@robertcollins.net-20090430061630-fj6xih0prt4cw3s8
Update lock debugging support patch.

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