/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: Canonical.com Patch Queue Manager
  • Date: 2009-02-05 21:53:22 UTC
  • mfrom: (3928.4.3 bug_295826)
  • Revision ID: pqm@pqm.ubuntu.com-20090205215322-dlhyepy2fid5i7w6
(jam) Minor tweak to setup.py documentation for bug #295826

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