~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/selftest/testlog.py

[merge] fix \t in commit messages

Show diffs side-by-side

added added

removed removed

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