/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: Vincent Ladeuil
  • Date: 2008-11-25 08:25:20 UTC
  • mto: (3855.1.1 bzr.integration)
  • mto: This revision was merged to the branch mainline in revision 3856.
  • Revision ID: v.ladeuil+lp@free.fr-20081125082520-lz0wmaa3xsjvpdz0
Fix typo.

* bzrlib/log.py:
(reverse_by_depth): Fix typo in comment.

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.assertEquals(expected, got)
 
79
 
 
80
    def test_cur_revno(self):
 
81
        wt = self.make_branch_and_tree('.')
 
82
        b = wt.branch
 
83
 
 
84
        lf = LogCatcher()
 
85
        wt.commit('empty commit')
 
86
        log.show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
 
87
 
 
88
        def assertInvalidRev(start, end):
 
89
            self.assertRaises(errors.InvalidRevisionNumber,
 
90
                              log.show_log, b, lf,
 
91
                              start_revision=start, end_revision=end)
 
92
 
 
93
        # Since there is a single revision in the branch all the combinations
 
94
        # below should fail.
 
95
        assertInvalidRev(2, 1)
 
96
        assertInvalidRev(1, 2)
 
97
        assertInvalidRev(0, 2)
 
98
        assertInvalidRev(1, 0)
 
99
        assertInvalidRev(-1, 1)
 
100
        assertInvalidRev(1, -1)
 
101
 
 
102
    def test_empty_branch(self):
 
103
        wt = self.make_branch_and_tree('.')
 
104
 
 
105
        lf = LogCatcher()
 
106
        log.show_log(wt.branch, lf)
 
107
        # no entries yet
 
108
        self.assertEquals(lf.logs, [])
 
109
 
 
110
    def test_empty_commit(self):
 
111
        wt = self.make_branch_and_tree('.')
 
112
 
 
113
        wt.commit('empty commit')
 
114
        lf = LogCatcher()
 
115
        log.show_log(wt.branch, lf, verbose=True)
 
116
        self.assertEquals(len(lf.logs), 1)
 
117
        self.assertEquals(lf.logs[0].revno, '1')
 
118
        self.assertEquals(lf.logs[0].rev.message, 'empty commit')
 
119
        self.checkDelta(lf.logs[0].delta)
 
120
 
 
121
    def test_simple_commit(self):
 
122
        wt = self.make_branch_and_tree('.')
 
123
        wt.commit('empty commit')
 
124
        self.build_tree(['hello'])
 
125
        wt.add('hello')
 
126
        wt.commit('add one file',
 
127
                  committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
 
128
                            u'<test@example.com>')
 
129
        lf = LogCatcher()
 
130
        log.show_log(wt.branch, lf, verbose=True)
 
131
        self.assertEquals(len(lf.logs), 2)
 
132
        # first one is most recent
 
133
        log_entry = lf.logs[0]
 
134
        self.assertEquals(log_entry.revno, '2')
 
135
        self.assertEquals(log_entry.rev.message, 'add one file')
 
136
        self.checkDelta(log_entry.delta, added=['hello'])
 
137
 
 
138
    def test_commit_message_with_control_chars(self):
 
139
        wt = self.make_branch_and_tree('.')
 
140
        msg = "All 8-bit chars: " +  ''.join([unichr(x) for x in range(256)])
 
141
        wt.commit(msg)
 
142
        lf = LogCatcher()
 
143
        log.show_log(wt.branch, lf, verbose=True)
 
144
        committed_msg = lf.logs[0].rev.message
 
145
        self.assert_(msg != committed_msg)
 
146
        self.assert_(len(committed_msg) > len(msg))
 
147
 
 
148
    def test_commit_message_without_control_chars(self):
 
149
        wt = self.make_branch_and_tree('.')
 
150
        # escaped.  As ElementTree apparently does some kind of
 
151
        # newline conversion, neither LF (\x0A) nor CR (\x0D) are
 
152
        # included in the test commit message, even though they are
 
153
        # valid XML 1.0 characters.
 
154
        msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
 
155
        wt.commit(msg)
 
156
        lf = LogCatcher()
 
157
        log.show_log(wt.branch, lf, verbose=True)
 
158
        committed_msg = lf.logs[0].rev.message
 
159
        self.assert_(msg == committed_msg)
 
160
 
 
161
    def test_deltas_in_merge_revisions(self):
 
162
        """Check deltas created for both mainline and merge revisions"""
 
163
        eq = self.assertEquals
 
164
        wt = self.make_branch_and_tree('parent')
 
165
        self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
 
166
        wt.add('file1')
 
167
        wt.add('file2')
 
168
        wt.commit(message='add file1 and file2')
 
169
        self.run_bzr('branch parent child')
 
170
        os.unlink('child/file1')
 
171
        file('child/file2', 'wb').write('hello\n')
 
172
        self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
 
173
            'child'])
 
174
        os.chdir('parent')
 
175
        self.run_bzr('merge ../child')
 
176
        wt.commit('merge child branch')
 
177
        os.chdir('..')
 
178
        b = wt.branch
 
179
        lf = LogCatcher()
 
180
        lf.supports_merge_revisions = True
 
181
        log.show_log(b, lf, verbose=True)
 
182
        self.assertEquals(len(lf.logs),3)
 
183
        logentry = lf.logs[0]
 
184
        self.assertEquals(logentry.revno, '2')
 
185
        self.assertEquals(logentry.rev.message, 'merge child branch')
 
186
        d = logentry.delta
 
187
        self.checkDelta(d, removed=['file1'], modified=['file2'])
 
188
        logentry = lf.logs[1]
 
189
        self.assertEquals(logentry.revno, '1.1.1')
 
190
        self.assertEquals(logentry.rev.message, 'remove file1 and modify file2')
 
191
        d = logentry.delta
 
192
        self.checkDelta(d, removed=['file1'], modified=['file2'])
 
193
        logentry = lf.logs[2]
 
194
        self.assertEquals(logentry.revno, '1')
 
195
        self.assertEquals(logentry.rev.message, 'add file1 and file2')
 
196
        d = logentry.delta
 
197
        self.checkDelta(d, added=['file1', 'file2'])
 
198
 
 
199
    def test_merges_nonsupporting_formatter(self):
 
200
        """Tests that show_log will raise if the formatter doesn't
 
201
        support merge revisions."""
 
202
        wt = self.make_branch_and_memory_tree('.')
 
203
        wt.lock_write()
 
204
        self.addCleanup(wt.unlock)
 
205
        wt.add('')
 
206
        wt.commit('rev-1', rev_id='rev-1',
 
207
                  timestamp=1132586655, timezone=36000,
 
208
                  committer='Joe Foo <joe@foo.com>')
 
209
        wt.commit('rev-merged', rev_id='rev-2a',
 
210
                  timestamp=1132586700, timezone=36000,
 
211
                  committer='Joe Foo <joe@foo.com>')
 
212
        wt.set_parent_ids(['rev-1', 'rev-2a'])
 
213
        wt.branch.set_last_revision_info(1, 'rev-1')
 
214
        wt.commit('rev-2', rev_id='rev-2b',
 
215
                  timestamp=1132586800, timezone=36000,
 
216
                  committer='Joe Foo <joe@foo.com>')
 
217
        logfile = self.make_utf8_encoded_stringio()
 
218
        formatter = log.ShortLogFormatter(to_file=logfile)
 
219
        wtb = wt.branch
 
220
        lf = LogCatcher()
 
221
        revspec = revisionspec.RevisionSpec.from_string('1.1.1')
 
222
        rev = revspec.in_history(wtb)
 
223
        self.assertRaises(errors.BzrCommandError, log.show_log, wtb, lf,
 
224
                          start_revision=rev, end_revision=rev)
 
225
 
 
226
 
 
227
def make_commits_with_trailing_newlines(wt):
 
228
    """Helper method for LogFormatter tests"""
 
229
    b = wt.branch
 
230
    b.nick='test'
 
231
    open('a', 'wb').write('hello moto\n')
 
232
    wt.add('a')
 
233
    wt.commit('simple log message', rev_id='a1',
 
234
              timestamp=1132586655.459960938, timezone=-6*3600,
 
235
              committer='Joe Foo <joe@foo.com>')
 
236
    open('b', 'wb').write('goodbye\n')
 
237
    wt.add('b')
 
238
    wt.commit('multiline\nlog\nmessage\n', rev_id='a2',
 
239
              timestamp=1132586842.411175966, timezone=-6*3600,
 
240
              committer='Joe Foo <joe@foo.com>',
 
241
              author='Joe Bar <joe@bar.com>')
 
242
 
 
243
    open('c', 'wb').write('just another manic monday\n')
 
244
    wt.add('c')
 
245
    wt.commit('single line with trailing newline\n', rev_id='a3',
 
246
              timestamp=1132587176.835228920, timezone=-6*3600,
 
247
              committer = 'Joe Foo <joe@foo.com>')
 
248
    return b
 
249
 
 
250
 
 
251
def normalize_log(log):
 
252
    """Replaces the variable lines of logs with fixed lines"""
 
253
    author = 'author: Dolor Sit <test@example.com>'
 
254
    committer = 'committer: Lorem Ipsum <test@example.com>'
 
255
    lines = log.splitlines(True)
 
256
    for idx,line in enumerate(lines):
 
257
        stripped_line = line.lstrip()
 
258
        indent = ' ' * (len(line) - len(stripped_line))
 
259
        if stripped_line.startswith('author:'):
 
260
            lines[idx] = indent + author + '\n'
 
261
        elif stripped_line.startswith('committer:'):
 
262
            lines[idx] = indent + committer + '\n'
 
263
        elif stripped_line.startswith('timestamp:'):
 
264
            lines[idx] = indent + 'timestamp: Just now\n'
 
265
    return ''.join(lines)
 
266
 
 
267
 
 
268
class TestShortLogFormatter(tests.TestCaseWithTransport):
 
269
 
 
270
    def test_trailing_newlines(self):
 
271
        wt = self.make_branch_and_tree('.')
 
272
        b = make_commits_with_trailing_newlines(wt)
 
273
        sio = self.make_utf8_encoded_stringio()
 
274
        lf = log.ShortLogFormatter(to_file=sio)
 
275
        log.show_log(b, lf)
 
276
        self.assertEqualDiff(sio.getvalue(), """\
 
277
    3 Joe Foo\t2005-11-21
 
278
      single line with trailing newline
 
279
 
 
280
    2 Joe Bar\t2005-11-21
 
281
      multiline
 
282
      log
 
283
      message
 
284
 
 
285
    1 Joe Foo\t2005-11-21
 
286
      simple log message
 
287
 
 
288
""")
 
289
 
 
290
    def test_short_log_with_merges(self):
 
291
        wt = self.make_branch_and_memory_tree('.')
 
292
        wt.lock_write()
 
293
        self.addCleanup(wt.unlock)
 
294
        wt.add('')
 
295
        wt.commit('rev-1', rev_id='rev-1',
 
296
                  timestamp=1132586655, timezone=36000,
 
297
                  committer='Joe Foo <joe@foo.com>')
 
298
        wt.commit('rev-merged', rev_id='rev-2a',
 
299
                  timestamp=1132586700, timezone=36000,
 
300
                  committer='Joe Foo <joe@foo.com>')
 
301
        wt.set_parent_ids(['rev-1', 'rev-2a'])
 
302
        wt.branch.set_last_revision_info(1, 'rev-1')
 
303
        wt.commit('rev-2', rev_id='rev-2b',
 
304
                  timestamp=1132586800, timezone=36000,
 
305
                  committer='Joe Foo <joe@foo.com>')
 
306
        logfile = self.make_utf8_encoded_stringio()
 
307
        formatter = log.ShortLogFormatter(to_file=logfile)
 
308
        log.show_log(wt.branch, formatter)
 
309
        self.assertEqualDiff(logfile.getvalue(), """\
 
310
    2 Joe Foo\t2005-11-22 [merge]
 
311
      rev-2
 
312
 
 
313
    1 Joe Foo\t2005-11-22
 
314
      rev-1
 
315
 
 
316
""")
 
317
 
 
318
    def test_short_log_single_merge_revision(self):
 
319
        wt = self.make_branch_and_memory_tree('.')
 
320
        wt.lock_write()
 
321
        self.addCleanup(wt.unlock)
 
322
        wt.add('')
 
323
        wt.commit('rev-1', rev_id='rev-1',
 
324
                  timestamp=1132586655, timezone=36000,
 
325
                  committer='Joe Foo <joe@foo.com>')
 
326
        wt.commit('rev-merged', rev_id='rev-2a',
 
327
                  timestamp=1132586700, timezone=36000,
 
328
                  committer='Joe Foo <joe@foo.com>')
 
329
        wt.set_parent_ids(['rev-1', 'rev-2a'])
 
330
        wt.branch.set_last_revision_info(1, 'rev-1')
 
331
        wt.commit('rev-2', rev_id='rev-2b',
 
332
                  timestamp=1132586800, timezone=36000,
 
333
                  committer='Joe Foo <joe@foo.com>')
 
334
        logfile = self.make_utf8_encoded_stringio()
 
335
        formatter = log.ShortLogFormatter(to_file=logfile)
 
336
        revspec = revisionspec.RevisionSpec.from_string('1.1.1')
 
337
        wtb = wt.branch
 
338
        rev = revspec.in_history(wtb)
 
339
        log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
 
340
        self.assertEqualDiff(logfile.getvalue(), """\
 
341
1.1.1 Joe Foo\t2005-11-22
 
342
      rev-merged
 
343
 
 
344
""")
 
345
 
 
346
 
 
347
class TestLongLogFormatter(TestCaseWithoutPropsHandler):
 
348
 
 
349
    def test_verbose_log(self):
 
350
        """Verbose log includes changed files
 
351
        
 
352
        bug #4676
 
353
        """
 
354
        wt = self.make_branch_and_tree('.')
 
355
        b = wt.branch
 
356
        self.build_tree(['a'])
 
357
        wt.add('a')
 
358
        # XXX: why does a longer nick show up?
 
359
        b.nick = 'test_verbose_log'
 
360
        wt.commit(message='add a',
 
361
                  timestamp=1132711707,
 
362
                  timezone=36000,
 
363
                  committer='Lorem Ipsum <test@example.com>')
 
364
        logfile = file('out.tmp', 'w+')
 
365
        formatter = log.LongLogFormatter(to_file=logfile)
 
366
        log.show_log(b, formatter, verbose=True)
 
367
        logfile.flush()
 
368
        logfile.seek(0)
 
369
        log_contents = logfile.read()
 
370
        self.assertEqualDiff(log_contents, '''\
 
371
------------------------------------------------------------
 
372
revno: 1
 
373
committer: Lorem Ipsum <test@example.com>
 
374
branch nick: test_verbose_log
 
375
timestamp: Wed 2005-11-23 12:08:27 +1000
 
376
message:
 
377
  add a
 
378
added:
 
379
  a
 
380
''')
 
381
 
 
382
    def test_merges_are_indented_by_level(self):
 
383
        wt = self.make_branch_and_tree('parent')
 
384
        wt.commit('first post')
 
385
        self.run_bzr('branch parent child')
 
386
        self.run_bzr(['commit', '-m', 'branch 1', '--unchanged', 'child'])
 
387
        self.run_bzr('branch child smallerchild')
 
388
        self.run_bzr(['commit', '-m', 'branch 2', '--unchanged',
 
389
            'smallerchild'])
 
390
        os.chdir('child')
 
391
        self.run_bzr('merge ../smallerchild')
 
392
        self.run_bzr(['commit', '-m', 'merge branch 2'])
 
393
        os.chdir('../parent')
 
394
        self.run_bzr('merge ../child')
 
395
        wt.commit('merge branch 1')
 
396
        b = wt.branch
 
397
        sio = self.make_utf8_encoded_stringio()
 
398
        lf = log.LongLogFormatter(to_file=sio)
 
399
        log.show_log(b, lf, verbose=True)
 
400
        the_log = normalize_log(sio.getvalue())
 
401
        self.assertEqualDiff(the_log, """\
 
402
------------------------------------------------------------
 
403
revno: 2
 
404
committer: Lorem Ipsum <test@example.com>
 
405
branch nick: parent
 
406
timestamp: Just now
 
407
message:
 
408
  merge branch 1
 
409
    ------------------------------------------------------------
 
410
    revno: 1.1.2
 
411
    committer: Lorem Ipsum <test@example.com>
 
412
    branch nick: child
 
413
    timestamp: Just now
 
414
    message:
 
415
      merge branch 2
 
416
        ------------------------------------------------------------
 
417
        revno: 1.2.1
 
418
        committer: Lorem Ipsum <test@example.com>
 
419
        branch nick: smallerchild
 
420
        timestamp: Just now
 
421
        message:
 
422
          branch 2
 
423
    ------------------------------------------------------------
 
424
    revno: 1.1.1
 
425
    committer: Lorem Ipsum <test@example.com>
 
426
    branch nick: child
 
427
    timestamp: Just now
 
428
    message:
 
429
      branch 1
 
430
------------------------------------------------------------
 
431
revno: 1
 
432
committer: Lorem Ipsum <test@example.com>
 
433
branch nick: parent
 
434
timestamp: Just now
 
435
message:
 
436
  first post
 
437
""")
 
438
 
 
439
    def test_verbose_merge_revisions_contain_deltas(self):
 
440
        wt = self.make_branch_and_tree('parent')
 
441
        self.build_tree(['parent/f1', 'parent/f2'])
 
442
        wt.add(['f1','f2'])
 
443
        wt.commit('first post')
 
444
        self.run_bzr('branch parent child')
 
445
        os.unlink('child/f1')
 
446
        file('child/f2', 'wb').write('hello\n')
 
447
        self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
 
448
            'child'])
 
449
        os.chdir('parent')
 
450
        self.run_bzr('merge ../child')
 
451
        wt.commit('merge branch 1')
 
452
        b = wt.branch
 
453
        sio = self.make_utf8_encoded_stringio()
 
454
        lf = log.LongLogFormatter(to_file=sio)
 
455
        log.show_log(b, lf, verbose=True)
 
456
        the_log = normalize_log(sio.getvalue())
 
457
        self.assertEqualDiff(the_log, """\
 
458
------------------------------------------------------------
 
459
revno: 2
 
460
committer: Lorem Ipsum <test@example.com>
 
461
branch nick: parent
 
462
timestamp: Just now
 
463
message:
 
464
  merge branch 1
 
465
removed:
 
466
  f1
 
467
modified:
 
468
  f2
 
469
    ------------------------------------------------------------
 
470
    revno: 1.1.1
 
471
    committer: Lorem Ipsum <test@example.com>
 
472
    branch nick: child
 
473
    timestamp: Just now
 
474
    message:
 
475
      removed f1 and modified f2
 
476
    removed:
 
477
      f1
 
478
    modified:
 
479
      f2
 
480
------------------------------------------------------------
 
481
revno: 1
 
482
committer: Lorem Ipsum <test@example.com>
 
483
branch nick: parent
 
484
timestamp: Just now
 
485
message:
 
486
  first post
 
487
added:
 
488
  f1
 
489
  f2
 
490
""")
 
491
 
 
492
    def test_trailing_newlines(self):
 
493
        wt = self.make_branch_and_tree('.')
 
494
        b = make_commits_with_trailing_newlines(wt)
 
495
        sio = self.make_utf8_encoded_stringio()
 
496
        lf = log.LongLogFormatter(to_file=sio)
 
497
        log.show_log(b, lf)
 
498
        self.assertEqualDiff(sio.getvalue(), """\
 
499
------------------------------------------------------------
 
500
revno: 3
 
501
committer: Joe Foo <joe@foo.com>
 
502
branch nick: test
 
503
timestamp: Mon 2005-11-21 09:32:56 -0600
 
504
message:
 
505
  single line with trailing newline
 
506
------------------------------------------------------------
 
507
revno: 2
 
508
author: Joe Bar <joe@bar.com>
 
509
committer: Joe Foo <joe@foo.com>
 
510
branch nick: test
 
511
timestamp: Mon 2005-11-21 09:27:22 -0600
 
512
message:
 
513
  multiline
 
514
  log
 
515
  message
 
516
------------------------------------------------------------
 
517
revno: 1
 
518
committer: Joe Foo <joe@foo.com>
 
519
branch nick: test
 
520
timestamp: Mon 2005-11-21 09:24:15 -0600
 
521
message:
 
522
  simple log message
 
523
""")
 
524
 
 
525
    def test_author_in_log(self):
 
526
        """Log includes the author name if it's set in
 
527
        the revision properties
 
528
        """
 
529
        wt = self.make_branch_and_tree('.')
 
530
        b = wt.branch
 
531
        self.build_tree(['a'])
 
532
        wt.add('a')
 
533
        b.nick = 'test_author_log'
 
534
        wt.commit(message='add a',
 
535
                  timestamp=1132711707,
 
536
                  timezone=36000,
 
537
                  committer='Lorem Ipsum <test@example.com>',
 
538
                  author='John Doe <jdoe@example.com>')
 
539
        sio = StringIO()
 
540
        formatter = log.LongLogFormatter(to_file=sio)
 
541
        log.show_log(b, formatter)
 
542
        self.assertEqualDiff(sio.getvalue(), '''\
 
543
------------------------------------------------------------
 
544
revno: 1
 
545
author: John Doe <jdoe@example.com>
 
546
committer: Lorem Ipsum <test@example.com>
 
547
branch nick: test_author_log
 
548
timestamp: Wed 2005-11-23 12:08:27 +1000
 
549
message:
 
550
  add a
 
551
''')
 
552
 
 
553
    def test_properties_in_log(self):
 
554
        """Log includes the custom properties returned by the registered 
 
555
        handlers.
 
556
        """
 
557
        wt = self.make_branch_and_tree('.')
 
558
        b = wt.branch
 
559
        self.build_tree(['a'])
 
560
        wt.add('a')
 
561
        b.nick = 'test_properties_in_log'
 
562
        wt.commit(message='add a',
 
563
                  timestamp=1132711707,
 
564
                  timezone=36000,
 
565
                  committer='Lorem Ipsum <test@example.com>',
 
566
                  author='John Doe <jdoe@example.com>')
 
567
        sio = StringIO()
 
568
        formatter = log.LongLogFormatter(to_file=sio)
 
569
        try:
 
570
            def trivial_custom_prop_handler(revision):
 
571
                return {'test_prop':'test_value'}
 
572
 
 
573
            log.properties_handler_registry.register(
 
574
                'trivial_custom_prop_handler',
 
575
                trivial_custom_prop_handler)
 
576
            log.show_log(b, formatter)
 
577
        finally:
 
578
            log.properties_handler_registry.remove(
 
579
                'trivial_custom_prop_handler')
 
580
            self.assertEqualDiff(sio.getvalue(), '''\
 
581
------------------------------------------------------------
 
582
revno: 1
 
583
test_prop: test_value
 
584
author: John Doe <jdoe@example.com>
 
585
committer: Lorem Ipsum <test@example.com>
 
586
branch nick: test_properties_in_log
 
587
timestamp: Wed 2005-11-23 12:08:27 +1000
 
588
message:
 
589
  add a
 
590
''')
 
591
 
 
592
    def test_error_in_properties_handler(self):
 
593
        """Log includes the custom properties returned by the registered 
 
594
        handlers.
 
595
        """
 
596
        wt = self.make_branch_and_tree('.')
 
597
        b = wt.branch
 
598
        self.build_tree(['a'])
 
599
        wt.add('a')
 
600
        b.nick = 'test_author_log'
 
601
        wt.commit(message='add a',
 
602
                  timestamp=1132711707,
 
603
                  timezone=36000,
 
604
                  committer='Lorem Ipsum <test@example.com>',
 
605
                  author='John Doe <jdoe@example.com>',
 
606
                  revprops={'first_prop':'first_value'})
 
607
        sio = StringIO()
 
608
        formatter = log.LongLogFormatter(to_file=sio)
 
609
        try:
 
610
            def trivial_custom_prop_handler(revision):
 
611
                raise StandardError("a test error")
 
612
 
 
613
            log.properties_handler_registry.register(
 
614
                'trivial_custom_prop_handler',
 
615
                trivial_custom_prop_handler)
 
616
            self.assertRaises(StandardError, log.show_log, b, formatter,)
 
617
        finally:
 
618
            log.properties_handler_registry.remove(
 
619
                'trivial_custom_prop_handler')
 
620
 
 
621
    def test_properties_handler_bad_argument(self):
 
622
        wt = self.make_branch_and_tree('.')
 
623
        b = wt.branch
 
624
        self.build_tree(['a'])
 
625
        wt.add('a')
 
626
        b.nick = 'test_author_log'
 
627
        wt.commit(message='add a',
 
628
                  timestamp=1132711707,
 
629
                  timezone=36000,
 
630
                  committer='Lorem Ipsum <test@example.com>',
 
631
                  author='John Doe <jdoe@example.com>',
 
632
                  revprops={'a_prop':'test_value'})
 
633
        sio = StringIO()
 
634
        formatter = log.LongLogFormatter(to_file=sio)
 
635
        try:
 
636
            def bad_argument_prop_handler(revision):
 
637
                return {'custom_prop_name':revision.properties['a_prop']}
 
638
 
 
639
            log.properties_handler_registry.register(
 
640
                'bad_argument_prop_handler',
 
641
                bad_argument_prop_handler)
 
642
 
 
643
            self.assertRaises(AttributeError, formatter.show_properties,
 
644
                'a revision', '')
 
645
 
 
646
            revision = b.repository.get_revision(b.last_revision())
 
647
            formatter.show_properties(revision, '')
 
648
            self.assertEqualDiff(sio.getvalue(),
 
649
                '''custom_prop_name: test_value\n''')
 
650
        finally:
 
651
            log.properties_handler_registry.remove(
 
652
                'bad_argument_prop_handler')
 
653
 
 
654
 
 
655
class TestLineLogFormatter(tests.TestCaseWithTransport):
 
656
 
 
657
    def test_line_log(self):
 
658
        """Line log should show revno
 
659
        
 
660
        bug #5162
 
661
        """
 
662
        wt = self.make_branch_and_tree('.')
 
663
        b = wt.branch
 
664
        self.build_tree(['a'])
 
665
        wt.add('a')
 
666
        b.nick = 'test-line-log'
 
667
        wt.commit(message='add a',
 
668
                  timestamp=1132711707,
 
669
                  timezone=36000,
 
670
                  committer='Line-Log-Formatter Tester <test@line.log>')
 
671
        logfile = file('out.tmp', 'w+')
 
672
        formatter = log.LineLogFormatter(to_file=logfile)
 
673
        log.show_log(b, formatter)
 
674
        logfile.flush()
 
675
        logfile.seek(0)
 
676
        log_contents = logfile.read()
 
677
        self.assertEqualDiff(log_contents,
 
678
            '1: Line-Log-Formatte... 2005-11-23 add a\n')
 
679
 
 
680
    def test_trailing_newlines(self):
 
681
        wt = self.make_branch_and_tree('.')
 
682
        b = make_commits_with_trailing_newlines(wt)
 
683
        sio = self.make_utf8_encoded_stringio()
 
684
        lf = log.LineLogFormatter(to_file=sio)
 
685
        log.show_log(b, lf)
 
686
        self.assertEqualDiff(sio.getvalue(), """\
 
687
3: Joe Foo 2005-11-21 single line with trailing newline
 
688
2: Joe Bar 2005-11-21 multiline
 
689
1: Joe Foo 2005-11-21 simple log message
 
690
""")
 
691
 
 
692
    def test_line_log_single_merge_revision(self):
 
693
        wt = self.make_branch_and_memory_tree('.')
 
694
        wt.lock_write()
 
695
        self.addCleanup(wt.unlock)
 
696
        wt.add('')
 
697
        wt.commit('rev-1', rev_id='rev-1',
 
698
                  timestamp=1132586655, timezone=36000,
 
699
                  committer='Joe Foo <joe@foo.com>')
 
700
        wt.commit('rev-merged', rev_id='rev-2a',
 
701
                  timestamp=1132586700, timezone=36000,
 
702
                  committer='Joe Foo <joe@foo.com>')
 
703
        wt.set_parent_ids(['rev-1', 'rev-2a'])
 
704
        wt.branch.set_last_revision_info(1, 'rev-1')
 
705
        wt.commit('rev-2', rev_id='rev-2b',
 
706
                  timestamp=1132586800, timezone=36000,
 
707
                  committer='Joe Foo <joe@foo.com>')
 
708
        logfile = self.make_utf8_encoded_stringio()
 
709
        formatter = log.LineLogFormatter(to_file=logfile)
 
710
        revspec = revisionspec.RevisionSpec.from_string('1.1.1')
 
711
        wtb = wt.branch
 
712
        rev = revspec.in_history(wtb)
 
713
        log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
 
714
        self.assertEqualDiff(logfile.getvalue(), """\
 
715
1.1.1: Joe Foo 2005-11-22 rev-merged
 
716
""")
 
717
 
 
718
 
 
719
 
 
720
class TestGetViewRevisions(tests.TestCaseWithTransport):
 
721
 
 
722
    def make_tree_with_commits(self):
 
723
        """Create a tree with well-known revision ids"""
 
724
        wt = self.make_branch_and_tree('tree1')
 
725
        wt.commit('commit one', rev_id='1')
 
726
        wt.commit('commit two', rev_id='2')
 
727
        wt.commit('commit three', rev_id='3')
 
728
        mainline_revs = [None, '1', '2', '3']
 
729
        rev_nos = {'1': 1, '2': 2, '3': 3}
 
730
        return mainline_revs, rev_nos, wt
 
731
 
 
732
    def make_tree_with_merges(self):
 
733
        """Create a tree with well-known revision ids and a merge"""
 
734
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
 
735
        tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
 
736
        tree2.commit('four-a', rev_id='4a')
 
737
        wt.merge_from_branch(tree2.branch)
 
738
        wt.commit('four-b', rev_id='4b')
 
739
        mainline_revs.append('4b')
 
740
        rev_nos['4b'] = 4
 
741
        # 4a: 3.1.1
 
742
        return mainline_revs, rev_nos, wt
 
743
 
 
744
    def make_tree_with_many_merges(self):
 
745
        """Create a tree with well-known revision ids"""
 
746
        wt = self.make_branch_and_tree('tree1')
 
747
        self.build_tree_contents([('tree1/f', '1\n')])
 
748
        wt.add(['f'], ['f-id'])
 
749
        wt.commit('commit one', rev_id='1')
 
750
        wt.commit('commit two', rev_id='2')
 
751
 
 
752
        tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
 
753
        self.build_tree_contents([('tree3/f', '1\n2\n3a\n')])
 
754
        tree3.commit('commit three a', rev_id='3a')
 
755
 
 
756
        tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
 
757
        tree2.merge_from_branch(tree3.branch)
 
758
        tree2.commit('commit three b', rev_id='3b')
 
759
 
 
760
        wt.merge_from_branch(tree2.branch)
 
761
        wt.commit('commit three c', rev_id='3c')
 
762
        tree2.commit('four-a', rev_id='4a')
 
763
 
 
764
        wt.merge_from_branch(tree2.branch)
 
765
        wt.commit('four-b', rev_id='4b')
 
766
 
 
767
        mainline_revs = [None, '1', '2', '3c', '4b']
 
768
        rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
 
769
        full_rev_nos_for_reference = {
 
770
            '1': '1',
 
771
            '2': '2',
 
772
            '3a': '2.1.1', #first commit tree 3
 
773
            '3b': '2.2.1', # first commit tree 2
 
774
            '3c': '3', #merges 3b to main
 
775
            '4a': '2.2.2', # second commit tree 2
 
776
            '4b': '4', # merges 4a to main
 
777
            }
 
778
        return mainline_revs, rev_nos, wt
 
779
 
 
780
    def test_get_view_revisions_forward(self):
 
781
        """Test the get_view_revisions method"""
 
782
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
 
783
        wt.lock_read()
 
784
        self.addCleanup(wt.unlock)
 
785
        revisions = list(log.get_view_revisions(
 
786
                mainline_revs, rev_nos, wt.branch, 'forward'))
 
787
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
 
788
                         revisions)
 
789
        revisions2 = list(log.get_view_revisions(
 
790
                mainline_revs, rev_nos, wt.branch, 'forward',
 
791
                include_merges=False))
 
792
        self.assertEqual(revisions, revisions2)
 
793
 
 
794
    def test_get_view_revisions_reverse(self):
 
795
        """Test the get_view_revisions with reverse"""
 
796
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
 
797
        wt.lock_read()
 
798
        self.addCleanup(wt.unlock)
 
799
        revisions = list(log.get_view_revisions(
 
800
                mainline_revs, rev_nos, wt.branch, 'reverse'))
 
801
        self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
 
802
                         revisions)
 
803
        revisions2 = list(log.get_view_revisions(
 
804
                mainline_revs, rev_nos, wt.branch, 'reverse',
 
805
                include_merges=False))
 
806
        self.assertEqual(revisions, revisions2)
 
807
 
 
808
    def test_get_view_revisions_merge(self):
 
809
        """Test get_view_revisions when there are merges"""
 
810
        mainline_revs, rev_nos, wt = self.make_tree_with_merges()
 
811
        wt.lock_read()
 
812
        self.addCleanup(wt.unlock)
 
813
        revisions = list(log.get_view_revisions(
 
814
                mainline_revs, rev_nos, wt.branch, 'forward'))
 
815
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
 
816
                          ('4b', '4', 0), ('4a', '3.1.1', 1)],
 
817
                         revisions)
 
818
        revisions = list(log.get_view_revisions(
 
819
                mainline_revs, rev_nos, wt.branch, 'forward',
 
820
                include_merges=False))
 
821
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
 
822
                          ('4b', '4', 0)],
 
823
                         revisions)
 
824
 
 
825
    def test_get_view_revisions_merge_reverse(self):
 
826
        """Test get_view_revisions in reverse when there are merges"""
 
827
        mainline_revs, rev_nos, wt = self.make_tree_with_merges()
 
828
        wt.lock_read()
 
829
        self.addCleanup(wt.unlock)
 
830
        revisions = list(log.get_view_revisions(
 
831
                mainline_revs, rev_nos, wt.branch, 'reverse'))
 
832
        self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
 
833
                          ('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
 
834
                         revisions)
 
835
        revisions = list(log.get_view_revisions(
 
836
                mainline_revs, rev_nos, wt.branch, 'reverse',
 
837
                include_merges=False))
 
838
        self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
 
839
                          ('1', '1', 0)],
 
840
                         revisions)
 
841
 
 
842
    def test_get_view_revisions_merge2(self):
 
843
        """Test get_view_revisions when there are merges"""
 
844
        mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
 
845
        wt.lock_read()
 
846
        self.addCleanup(wt.unlock)
 
847
        revisions = list(log.get_view_revisions(
 
848
                mainline_revs, rev_nos, wt.branch, 'forward'))
 
849
        expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
 
850
                    ('3a', '2.1.1', 1), ('3b', '2.2.1', 1), ('4b', '4', 0),
 
851
                    ('4a', '2.2.2', 1)]
 
852
        self.assertEqual(expected, revisions)
 
853
        revisions = list(log.get_view_revisions(
 
854
                mainline_revs, rev_nos, wt.branch, 'forward',
 
855
                include_merges=False))
 
856
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
 
857
                          ('4b', '4', 0)],
 
858
                         revisions)
 
859
 
 
860
 
 
861
    def test_file_id_for_range(self):
 
862
        mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
 
863
        wt.lock_read()
 
864
        self.addCleanup(wt.unlock)
 
865
 
 
866
        def rev_from_rev_id(revid, branch):
 
867
            revspec = revisionspec.RevisionSpec.from_string('revid:%s' % revid)
 
868
            return revspec.in_history(branch)
 
869
 
 
870
        def view_revs(start_rev, end_rev, file_id, direction):
 
871
            revs = log.calculate_view_revisions(
 
872
                wt.branch,
 
873
                start_rev, # start_revision
 
874
                end_rev, # end_revision
 
875
                direction, # direction
 
876
                file_id, # specific_fileid
 
877
                True, # generate_merge_revisions
 
878
                True, # allow_single_merge_revision
 
879
                )
 
880
            return revs
 
881
 
 
882
        rev_3a = rev_from_rev_id('3a', wt.branch)
 
883
        rev_4b = rev_from_rev_id('4b', wt.branch)
 
884
        self.assertEquals([('3c', '3', 0), ('3a', '2.1.1', 1)],
 
885
                          view_revs(rev_3a, rev_4b, 'f-id', 'reverse'))
 
886
        # Note that the depth is 0 for 3a because depths are normalized, but
 
887
        # there is still a bug somewhere... most probably in
 
888
        # _filter_revision_range and/or get_view_revisions still around a bad
 
889
        # use of reverse_by_depth
 
890
        self.assertEquals([('3a', '2.1.1', 0)],
 
891
                          view_revs(rev_3a, rev_4b, 'f-id', 'forward'))
 
892
 
 
893
 
 
894
class TestGetRevisionsTouchingFileID(tests.TestCaseWithTransport):
 
895
 
 
896
    def create_tree_with_single_merge(self):
 
897
        """Create a branch with a moderate layout.
 
898
 
 
899
        The revision graph looks like:
 
900
 
 
901
           A
 
902
           |\
 
903
           B C
 
904
           |/
 
905
           D
 
906
 
 
907
        In this graph, A introduced files f1 and f2 and f3.
 
908
        B modifies f1 and f3, and C modifies f2 and f3.
 
909
        D merges the changes from B and C and resolves the conflict for f3.
 
910
        """
 
911
        # TODO: jam 20070218 This seems like it could really be done
 
912
        #       with make_branch_and_memory_tree() if we could just
 
913
        #       create the content of those files.
 
914
        # TODO: jam 20070218 Another alternative is that we would really
 
915
        #       like to only create this tree 1 time for all tests that
 
916
        #       use it. Since 'log' only uses the tree in a readonly
 
917
        #       fashion, it seems a shame to regenerate an identical
 
918
        #       tree for each test.
 
919
        tree = self.make_branch_and_tree('tree')
 
920
        tree.lock_write()
 
921
        self.addCleanup(tree.unlock)
 
922
 
 
923
        self.build_tree_contents([('tree/f1', 'A\n'),
 
924
                                  ('tree/f2', 'A\n'),
 
925
                                  ('tree/f3', 'A\n'),
 
926
                                 ])
 
927
        tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
 
928
        tree.commit('A', rev_id='A')
 
929
 
 
930
        self.build_tree_contents([('tree/f2', 'A\nC\n'),
 
931
                                  ('tree/f3', 'A\nC\n'),
 
932
                                 ])
 
933
        tree.commit('C', rev_id='C')
 
934
        # Revert back to A to build the other history.
 
935
        tree.set_last_revision('A')
 
936
        tree.branch.set_last_revision_info(1, 'A')
 
937
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
 
938
                                  ('tree/f2', 'A\n'),
 
939
                                  ('tree/f3', 'A\nB\n'),
 
940
                                 ])
 
941
        tree.commit('B', rev_id='B')
 
942
        tree.set_parent_ids(['B', 'C'])
 
943
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
 
944
                                  ('tree/f2', 'A\nC\n'),
 
945
                                  ('tree/f3', 'A\nB\nC\n'),
 
946
                                 ])
 
947
        tree.commit('D', rev_id='D')
 
948
 
 
949
        # Switch to a read lock for this tree.
 
950
        # We still have an addCleanup(tree.unlock) pending
 
951
        tree.unlock()
 
952
        tree.lock_read()
 
953
        return tree
 
954
 
 
955
    def check_delta(self, delta, **kw):
 
956
        """Check the filenames touched by a delta are as expected.
 
957
 
 
958
        Caller only have to pass in the list of files for each part, all
 
959
        unspecified parts are considered empty (and checked as such).
 
960
        """
 
961
        for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
 
962
            # By default we expect an empty list
 
963
            expected = kw.get(n, [])
 
964
            # strip out only the path components
 
965
            got = [x[0] for x in getattr(delta, n)]
 
966
            self.assertEquals(expected, got)
 
967
 
 
968
    def test_tree_with_single_merge(self):
 
969
        """Make sure the tree layout is correct."""
 
970
        tree = self.create_tree_with_single_merge()
 
971
        rev_A_tree = tree.branch.repository.revision_tree('A')
 
972
        rev_B_tree = tree.branch.repository.revision_tree('B')
 
973
        rev_C_tree = tree.branch.repository.revision_tree('C')
 
974
        rev_D_tree = tree.branch.repository.revision_tree('D')
 
975
 
 
976
        self.check_delta(rev_B_tree.changes_from(rev_A_tree),
 
977
                         modified=['f1', 'f3'])
 
978
 
 
979
        self.check_delta(rev_C_tree.changes_from(rev_A_tree),
 
980
                         modified=['f2', 'f3'])
 
981
 
 
982
        self.check_delta(rev_D_tree.changes_from(rev_B_tree),
 
983
                         modified=['f2', 'f3'])
 
984
 
 
985
        self.check_delta(rev_D_tree.changes_from(rev_C_tree),
 
986
                         modified=['f1', 'f3'])
 
987
 
 
988
    def assertAllRevisionsForFileID(self, tree, file_id, revisions):
 
989
        """Ensure _filter_revisions_touching_file_id returns the right values.
 
990
 
 
991
        Get the return value from _filter_revisions_touching_file_id and make
 
992
        sure they are correct.
 
993
        """
 
994
        # The api for _filter_revisions_touching_file_id is a little crazy.
 
995
        # So we do the setup here.
 
996
        mainline = tree.branch.revision_history()
 
997
        mainline.insert(0, None)
 
998
        revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
 
999
        view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
 
1000
                                                'reverse', True)
 
1001
        actual_revs = log._filter_revisions_touching_file_id(
 
1002
                            tree.branch,
 
1003
                            file_id,
 
1004
                            list(view_revs_iter))
 
1005
        self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
 
1006
 
 
1007
    def test_file_id_f1(self):
 
1008
        tree = self.create_tree_with_single_merge()
 
1009
        # f1 should be marked as modified by revisions A and B
 
1010
        self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
 
1011
 
 
1012
    def test_file_id_f2(self):
 
1013
        tree = self.create_tree_with_single_merge()
 
1014
        # f2 should be marked as modified by revisions A, C, and D
 
1015
        # because D merged the changes from C.
 
1016
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
 
1017
 
 
1018
    def test_file_id_f3(self):
 
1019
        tree = self.create_tree_with_single_merge()
 
1020
        # f3 should be marked as modified by revisions A, B, C, and D
 
1021
        self.assertAllRevisionsForFileID(tree, 'f3-id', ['D', 'C', 'B', 'A'])
 
1022
 
 
1023
    def test_file_id_with_ghosts(self):
 
1024
        # This is testing bug #209948, where having a ghost would cause
 
1025
        # _filter_revisions_touching_file_id() to fail.
 
1026
        tree = self.create_tree_with_single_merge()
 
1027
        # We need to add a revision, so switch back to a write-locked tree
 
1028
        # (still a single addCleanup(tree.unlock) pending).
 
1029
        tree.unlock()
 
1030
        tree.lock_write()
 
1031
        first_parent = tree.last_revision()
 
1032
        tree.set_parent_ids([first_parent, 'ghost-revision-id'])
 
1033
        self.build_tree_contents([('tree/f1', 'A\nB\nXX\n')])
 
1034
        tree.commit('commit with a ghost', rev_id='XX')
 
1035
        self.assertAllRevisionsForFileID(tree, 'f1-id', ['XX', 'B', 'A'])
 
1036
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
 
1037
 
 
1038
 
 
1039
class TestShowChangedRevisions(tests.TestCaseWithTransport):
 
1040
 
 
1041
    def test_show_changed_revisions_verbose(self):
 
1042
        tree = self.make_branch_and_tree('tree_a')
 
1043
        self.build_tree(['tree_a/foo'])
 
1044
        tree.add('foo')
 
1045
        tree.commit('bar', rev_id='bar-id')
 
1046
        s = self.make_utf8_encoded_stringio()
 
1047
        log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
 
1048
        self.assertContainsRe(s.getvalue(), 'bar')
 
1049
        self.assertNotContainsRe(s.getvalue(), 'foo')
 
1050
 
 
1051
 
 
1052
class TestLogFormatter(tests.TestCase):
 
1053
 
 
1054
    def test_short_committer(self):
 
1055
        rev = revision.Revision('a-id')
 
1056
        rev.committer = 'John Doe <jdoe@example.com>'
 
1057
        lf = log.LogFormatter(None)
 
1058
        self.assertEqual('John Doe', lf.short_committer(rev))
 
1059
        rev.committer = 'John Smith <jsmith@example.com>'
 
1060
        self.assertEqual('John Smith', lf.short_committer(rev))
 
1061
        rev.committer = 'John Smith'
 
1062
        self.assertEqual('John Smith', lf.short_committer(rev))
 
1063
        rev.committer = 'jsmith@example.com'
 
1064
        self.assertEqual('jsmith@example.com', lf.short_committer(rev))
 
1065
        rev.committer = '<jsmith@example.com>'
 
1066
        self.assertEqual('jsmith@example.com', lf.short_committer(rev))
 
1067
        rev.committer = 'John Smith jsmith@example.com'
 
1068
        self.assertEqual('John Smith', lf.short_committer(rev))
 
1069
 
 
1070
    def test_short_author(self):
 
1071
        rev = revision.Revision('a-id')
 
1072
        rev.committer = 'John Doe <jdoe@example.com>'
 
1073
        lf = log.LogFormatter(None)
 
1074
        self.assertEqual('John Doe', lf.short_author(rev))
 
1075
        rev.properties['author'] = 'John Smith <jsmith@example.com>'
 
1076
        self.assertEqual('John Smith', lf.short_author(rev))
 
1077
        rev.properties['author'] = 'John Smith'
 
1078
        self.assertEqual('John Smith', lf.short_author(rev))
 
1079
        rev.properties['author'] = 'jsmith@example.com'
 
1080
        self.assertEqual('jsmith@example.com', lf.short_author(rev))
 
1081
        rev.properties['author'] = '<jsmith@example.com>'
 
1082
        self.assertEqual('jsmith@example.com', lf.short_author(rev))
 
1083
        rev.properties['author'] = 'John Smith jsmith@example.com'
 
1084
        self.assertEqual('John Smith', lf.short_author(rev))
 
1085
 
 
1086
 
 
1087
class TestReverseByDepth(tests.TestCase):
 
1088
    """Test reverse_by_depth behavior.
 
1089
 
 
1090
    This is used to present revisions in forward (oldest first) order in a nice
 
1091
    layout.
 
1092
 
 
1093
    The tests use lighter revision description to ease reading.
 
1094
    """
 
1095
 
 
1096
    def assertReversed(self, forward, backward):
 
1097
        # Transform the descriptions to suit the API: tests use (revno, depth),
 
1098
        # while the API expects (revid, revno, depth)
 
1099
        def complete_revisions(l):
 
1100
            """Transform the description to suit the API.
 
1101
 
 
1102
            Tests use (revno, depth) whil the API expects (revid, revno, depth).
 
1103
            Since the revid is arbitrary, we just duplicate revno
 
1104
            """
 
1105
            return [ (r, r, d) for r, d in l]
 
1106
        forward = complete_revisions(forward)
 
1107
        backward= complete_revisions(backward)
 
1108
        self.assertEqual(forward, log.reverse_by_depth(backward))
 
1109
 
 
1110
 
 
1111
    def test_mainline_revisions(self):
 
1112
        self.assertReversed([( '1', 0), ('2', 0)],
 
1113
                            [('2', 0), ('1', 0)])
 
1114
 
 
1115
    def test_merged_revisions(self):
 
1116
        self.assertReversed([('1', 0), ('2', 0), ('2.2', 1), ('2.1', 1),],
 
1117
                            [('2', 0), ('2.1', 1), ('2.2', 1), ('1', 0),])
 
1118
    def test_shifted_merged_revisions(self):
 
1119
        """Test irregular layout.
 
1120
 
 
1121
        Requesting revisions touching a file can produce "holes" in the depths.
 
1122
        """
 
1123
        self.assertReversed([('1', 0), ('2', 0), ('1.1', 2), ('1.2', 2),],
 
1124
                            [('2', 0), ('1.2', 2), ('1.1', 2), ('1', 0),])
 
1125
 
 
1126
    def test_merged_without_child_revisions(self):
 
1127
        """Test irregular layout.
 
1128
 
 
1129
        Revision ranges can produce "holes" in the depths.
 
1130
        """
 
1131
        # When a revision of higher depth doesn't follow one of lower depth, we
 
1132
        # assume a lower depth one is virtually there
 
1133
        self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
 
1134
                            [('4', 4), ('3', 3), ('2', 2), ('1', 2),])
 
1135
        # So we get the same order after reversing below even if the original
 
1136
        # revisions are not in the same order.
 
1137
        self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
 
1138
                            [('3', 3), ('4', 4), ('2', 2), ('1', 2),])