~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

  • Committer: Martin Pool
  • Date: 2007-04-04 06:17:31 UTC
  • mto: This revision was merged to the branch mainline in revision 2397.
  • Revision ID: mbp@sourcefrog.net-20070404061731-tt2xrzllqhbodn83
Contents of TODO file moved into bug tracker

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2005 Canonical Ltd
 
2
# -*- coding: utf-8 -*-
 
3
# vim: encoding=utf-8
2
4
#
3
5
# This program is free software; you can redistribute it and/or modify
4
6
# it under the terms of the GNU General Public License as published by
17
19
import os
18
20
from cStringIO import StringIO
19
21
 
20
 
from bzrlib import log
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,
 
22
from bzrlib.tests import BzrTestBase, TestCaseWithTransport
 
23
from bzrlib.log import (show_log, 
 
24
                        get_view_revisions, 
 
25
                        LogFormatter, 
 
26
                        LongLogFormatter, 
 
27
                        ShortLogFormatter, 
28
28
                        LineLogFormatter)
29
29
from bzrlib.branch import Branch
30
30
from bzrlib.errors import InvalidRevisionNumber
31
 
from bzrlib.revision import Revision
 
31
 
 
32
 
 
33
class _LogEntry(object):
 
34
    # should probably move into bzrlib.log?
 
35
    pass
32
36
 
33
37
 
34
38
class LogCatcher(LogFormatter):
40
44
 
41
45
    We should also test the LogFormatter.
42
46
    """
43
 
 
44
 
    supports_delta = True
45
 
 
46
47
    def __init__(self):
47
48
        super(LogCatcher, self).__init__(to_file=None)
48
49
        self.logs = []
49
50
 
50
 
    def log_revision(self, revision):
51
 
        self.logs.append(revision)
52
 
 
53
 
 
54
 
class TestShowLog(TestCaseWithTransport):
 
51
    def show(self, revno, rev, delta):
 
52
        le = _LogEntry()
 
53
        le.revno = revno
 
54
        le.rev = rev
 
55
        le.delta = delta
 
56
        self.logs.append(le)
 
57
 
 
58
 
 
59
class SimpleLogTest(TestCaseWithTransport):
55
60
 
56
61
    def checkDelta(self, delta, **kw):
57
62
        """Check the filenames touched by a delta are as expected."""
58
63
        for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
59
64
            expected = kw.get(n, [])
 
65
 
 
66
            # tests are written with unix paths; fix them up for windows
 
67
            #if os.sep != '/':
 
68
            #    expected = [x.replace('/', os.sep) for x in expected]
 
69
 
60
70
            # strip out only the path components
61
71
            got = [x[0] for x in getattr(delta, n)]
62
72
            self.assertEquals(expected, got)
104
114
 
105
115
        self.build_tree(['hello'])
106
116
        wt.add('hello')
107
 
        wt.commit('add one file',
108
 
                  committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
109
 
                            u'<test@example.com>')
 
117
        wt.commit('add one file')
110
118
 
111
 
        lf = self.make_utf8_encoded_stringio()
 
119
        lf = StringIO()
112
120
        # log using regular thing
113
121
        show_log(b, LongLogFormatter(lf))
114
122
        lf.seek(0)
129
137
        eq(logentry.rev.message, 'add one file')
130
138
        d = logentry.delta
131
139
        self.log('log 2 delta: %r' % d)
132
 
        self.checkDelta(d, added=['hello'])
 
140
        # self.checkDelta(d, added=['hello'])
133
141
        
134
142
        # commit a log message with control characters
135
143
        msg = "All 8-bit chars: " +  ''.join([unichr(x) for x in range(256)])
156
164
        self.log("escaped commit message: %r", committed_msg)
157
165
        self.assert_(msg == committed_msg)
158
166
 
159
 
    def test_deltas_in_merge_revisions(self):
160
 
        """Check deltas created for both mainline and merge revisions"""
161
 
        eq = self.assertEquals
162
 
        wt = self.make_branch_and_tree('parent')
163
 
        self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
164
 
        wt.add('file1')
165
 
        wt.add('file2')
166
 
        wt.commit(message='add file1 and file2')
167
 
        self.run_bzr('branch parent child')
168
 
        os.unlink('child/file1')
169
 
        file('child/file2', 'wb').write('hello\n')
170
 
        self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
171
 
            'child'])
172
 
        os.chdir('parent')
173
 
        self.run_bzr('merge ../child')
174
 
        wt.commit('merge child branch')
175
 
        os.chdir('..')
176
 
        b = wt.branch
177
 
        lf = LogCatcher()
178
 
        lf.supports_merge_revisions = True
179
 
        show_log(b, lf, verbose=True)
180
 
        eq(len(lf.logs),3)
181
 
        logentry = lf.logs[0]
182
 
        eq(logentry.revno, '2')
183
 
        eq(logentry.rev.message, 'merge child branch')
184
 
        d = logentry.delta
185
 
        self.checkDelta(d, removed=['file1'], modified=['file2'])
186
 
        logentry = lf.logs[1]
187
 
        eq(logentry.revno, '1.1.1')
188
 
        eq(logentry.rev.message, 'remove file1 and modify file2')
189
 
        d = logentry.delta
190
 
        self.checkDelta(d, removed=['file1'], modified=['file2'])
191
 
        logentry = lf.logs[2]
192
 
        eq(logentry.revno, '1')
193
 
        eq(logentry.rev.message, 'add file1 and file2')
194
 
        d = logentry.delta
195
 
        self.checkDelta(d, added=['file1', 'file2'])
196
 
 
197
 
 
198
 
def make_commits_with_trailing_newlines(wt):
199
 
    """Helper method for LogFormatter tests"""    
200
 
    b = wt.branch
201
 
    b.nick='test'
202
 
    open('a', 'wb').write('hello moto\n')
203
 
    wt.add('a')
204
 
    wt.commit('simple log message', rev_id='a1',
205
 
              timestamp=1132586655.459960938, timezone=-6*3600,
206
 
              committer='Joe Foo <joe@foo.com>')
207
 
    open('b', 'wb').write('goodbye\n')
208
 
    wt.add('b')
209
 
    wt.commit('multiline\nlog\nmessage\n', rev_id='a2',
210
 
              timestamp=1132586842.411175966, timezone=-6*3600,
211
 
              committer='Joe Foo <joe@foo.com>',
212
 
              author='Joe Bar <joe@bar.com>')
213
 
 
214
 
    open('c', 'wb').write('just another manic monday\n')
215
 
    wt.add('c')
216
 
    wt.commit('single line with trailing newline\n', rev_id='a3',
217
 
              timestamp=1132587176.835228920, timezone=-6*3600,
218
 
              committer = 'Joe Foo <joe@foo.com>')
219
 
    return b
220
 
 
221
 
 
222
 
class TestShortLogFormatter(TestCaseWithTransport):
223
 
 
224
167
    def test_trailing_newlines(self):
225
168
        wt = self.make_branch_and_tree('.')
226
 
        b = make_commits_with_trailing_newlines(wt)
227
 
        sio = self.make_utf8_encoded_stringio()
 
169
        b = wt.branch
 
170
        b.nick='test'
 
171
        open('a', 'wb').write('hello moto\n')
 
172
        wt.add('a')
 
173
        wt.commit('simple log message', rev_id='a1'
 
174
                , timestamp=1132586655.459960938, timezone=-6*3600
 
175
                , committer='Joe Foo <joe@foo.com>')
 
176
        open('b', 'wb').write('goodbye\n')
 
177
        wt.add('b')
 
178
        wt.commit('multiline\nlog\nmessage\n', rev_id='a2'
 
179
                , timestamp=1132586842.411175966, timezone=-6*3600
 
180
                , committer='Joe Foo <joe@foo.com>')
 
181
 
 
182
        open('c', 'wb').write('just another manic monday\n')
 
183
        wt.add('c')
 
184
        wt.commit('single line with trailing newline\n', rev_id='a3'
 
185
                , timestamp=1132587176.835228920, timezone=-6*3600
 
186
                , committer = 'Joe Foo <joe@foo.com>')
 
187
 
 
188
        sio = StringIO()
228
189
        lf = ShortLogFormatter(to_file=sio)
229
190
        show_log(b, lf)
230
191
        self.assertEquals(sio.getvalue(), """\
231
192
    3 Joe Foo\t2005-11-21
232
193
      single line with trailing newline
233
194
 
234
 
    2 Joe Bar\t2005-11-21
 
195
    2 Joe Foo\t2005-11-21
235
196
      multiline
236
197
      log
237
198
      message
241
202
 
242
203
""")
243
204
 
244
 
 
245
 
class TestLongLogFormatter(TestCaseWithTransport):
246
 
 
247
 
    def normalize_log(self,log):
248
 
        """Replaces the variable lines of logs with fixed lines"""
249
 
        author = 'author: Dolor Sit <test@example.com>'
250
 
        committer = 'committer: Lorem Ipsum <test@example.com>'
251
 
        lines = log.splitlines(True)
252
 
        for idx,line in enumerate(lines):
253
 
            stripped_line = line.lstrip()
254
 
            indent = ' ' * (len(line) - len(stripped_line))
255
 
            if stripped_line.startswith('author:'):
256
 
                lines[idx] = indent + author + '\n'
257
 
            elif stripped_line.startswith('committer:'):
258
 
                lines[idx] = indent + committer + '\n'
259
 
            elif stripped_line.startswith('timestamp:'):
260
 
                lines[idx] = indent + 'timestamp: Just now\n'
261
 
        return ''.join(lines)
262
 
 
 
205
        sio = StringIO()
 
206
        lf = LongLogFormatter(to_file=sio)
 
207
        show_log(b, lf)
 
208
        self.assertEquals(sio.getvalue(), """\
 
209
------------------------------------------------------------
 
210
revno: 3
 
211
committer: Joe Foo <joe@foo.com>
 
212
branch nick: test
 
213
timestamp: Mon 2005-11-21 09:32:56 -0600
 
214
message:
 
215
  single line with trailing newline
 
216
------------------------------------------------------------
 
217
revno: 2
 
218
committer: Joe Foo <joe@foo.com>
 
219
branch nick: test
 
220
timestamp: Mon 2005-11-21 09:27:22 -0600
 
221
message:
 
222
  multiline
 
223
  log
 
224
  message
 
225
------------------------------------------------------------
 
226
revno: 1
 
227
committer: Joe Foo <joe@foo.com>
 
228
branch nick: test
 
229
timestamp: Mon 2005-11-21 09:24:15 -0600
 
230
message:
 
231
  simple log message
 
232
""")
 
233
        
263
234
    def test_verbose_log(self):
264
235
        """Verbose log includes changed files
265
236
        
293
264
  a
294
265
''')
295
266
 
296
 
    def test_merges_are_indented_by_level(self):
297
 
        wt = self.make_branch_and_tree('parent')
298
 
        wt.commit('first post')
299
 
        self.run_bzr('branch parent child')
300
 
        self.run_bzr(['commit', '-m', 'branch 1', '--unchanged', 'child'])
301
 
        self.run_bzr('branch child smallerchild')
302
 
        self.run_bzr(['commit', '-m', 'branch 2', '--unchanged',
303
 
            'smallerchild'])
304
 
        os.chdir('child')
305
 
        self.run_bzr('merge ../smallerchild')
306
 
        self.run_bzr(['commit', '-m', 'merge branch 2'])
307
 
        os.chdir('../parent')
308
 
        self.run_bzr('merge ../child')
309
 
        wt.commit('merge branch 1')
310
 
        b = wt.branch
311
 
        sio = self.make_utf8_encoded_stringio()
312
 
        lf = LongLogFormatter(to_file=sio)
313
 
        show_log(b, lf, verbose=True)
314
 
        log = self.normalize_log(sio.getvalue())
315
 
        self.assertEqualDiff("""\
316
 
------------------------------------------------------------
317
 
revno: 2
318
 
committer: Lorem Ipsum <test@example.com>
319
 
branch nick: parent
320
 
timestamp: Just now
321
 
message:
322
 
  merge branch 1
323
 
    ------------------------------------------------------------
324
 
    revno: 1.1.2
325
 
    committer: Lorem Ipsum <test@example.com>
326
 
    branch nick: child
327
 
    timestamp: Just now
328
 
    message:
329
 
      merge branch 2
330
 
        ------------------------------------------------------------
331
 
        revno: 1.1.1.1.1
332
 
        committer: Lorem Ipsum <test@example.com>
333
 
        branch nick: smallerchild
334
 
        timestamp: Just now
335
 
        message:
336
 
          branch 2
337
 
    ------------------------------------------------------------
338
 
    revno: 1.1.1
339
 
    committer: Lorem Ipsum <test@example.com>
340
 
    branch nick: child
341
 
    timestamp: Just now
342
 
    message:
343
 
      branch 1
344
 
------------------------------------------------------------
345
 
revno: 1
346
 
committer: Lorem Ipsum <test@example.com>
347
 
branch nick: parent
348
 
timestamp: Just now
349
 
message:
350
 
  first post
351
 
""", log)
352
 
 
353
 
    def test_verbose_merge_revisions_contain_deltas(self):
354
 
        wt = self.make_branch_and_tree('parent')
355
 
        self.build_tree(['parent/f1', 'parent/f2'])
356
 
        wt.add(['f1','f2'])
357
 
        wt.commit('first post')
358
 
        self.run_bzr('branch parent child')
359
 
        os.unlink('child/f1')
360
 
        file('child/f2', 'wb').write('hello\n')
361
 
        self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
362
 
            'child'])
363
 
        os.chdir('parent')
364
 
        self.run_bzr('merge ../child')
365
 
        wt.commit('merge branch 1')
366
 
        b = wt.branch
367
 
        sio = self.make_utf8_encoded_stringio()
368
 
        lf = LongLogFormatter(to_file=sio)
369
 
        show_log(b, lf, verbose=True)
370
 
        log = self.normalize_log(sio.getvalue())
371
 
        self.assertEqualDiff("""\
372
 
------------------------------------------------------------
373
 
revno: 2
374
 
committer: Lorem Ipsum <test@example.com>
375
 
branch nick: parent
376
 
timestamp: Just now
377
 
message:
378
 
  merge branch 1
379
 
removed:
380
 
  f1
381
 
modified:
382
 
  f2
383
 
    ------------------------------------------------------------
384
 
    revno: 1.1.1
385
 
    committer: Lorem Ipsum <test@example.com>
386
 
    branch nick: child
387
 
    timestamp: Just now
388
 
    message:
389
 
      removed f1 and modified f2
390
 
    removed:
391
 
      f1
392
 
    modified:
393
 
      f2
394
 
------------------------------------------------------------
395
 
revno: 1
396
 
committer: Lorem Ipsum <test@example.com>
397
 
branch nick: parent
398
 
timestamp: Just now
399
 
message:
400
 
  first post
401
 
added:
402
 
  f1
403
 
  f2
404
 
""", log)
405
 
 
406
 
    def test_trailing_newlines(self):
407
 
        wt = self.make_branch_and_tree('.')
408
 
        b = make_commits_with_trailing_newlines(wt)
409
 
        sio = self.make_utf8_encoded_stringio()
410
 
        lf = LongLogFormatter(to_file=sio)
411
 
        show_log(b, lf)
412
 
        self.assertEqualDiff(sio.getvalue(), """\
413
 
------------------------------------------------------------
414
 
revno: 3
415
 
committer: Joe Foo <joe@foo.com>
416
 
branch nick: test
417
 
timestamp: Mon 2005-11-21 09:32:56 -0600
418
 
message:
419
 
  single line with trailing newline
420
 
------------------------------------------------------------
421
 
revno: 2
422
 
author: Joe Bar <joe@bar.com>
423
 
committer: Joe Foo <joe@foo.com>
424
 
branch nick: test
425
 
timestamp: Mon 2005-11-21 09:27:22 -0600
426
 
message:
427
 
  multiline
428
 
  log
429
 
  message
430
 
------------------------------------------------------------
431
 
revno: 1
432
 
committer: Joe Foo <joe@foo.com>
433
 
branch nick: test
434
 
timestamp: Mon 2005-11-21 09:24:15 -0600
435
 
message:
436
 
  simple log message
437
 
""")
438
 
 
439
 
    def test_author_in_log(self):
440
 
        """Log includes the author name if it's set in
441
 
        the revision properties
442
 
        """
443
 
        wt = self.make_branch_and_tree('.')
444
 
        b = wt.branch
445
 
        self.build_tree(['a'])
446
 
        wt.add('a')
447
 
        b.nick = 'test_author_log'
448
 
        wt.commit(message='add a',
449
 
                  timestamp=1132711707,
450
 
                  timezone=36000,
451
 
                  committer='Lorem Ipsum <test@example.com>',
452
 
                  author='John Doe <jdoe@example.com>')
453
 
        sio = StringIO()
454
 
        formatter = LongLogFormatter(to_file=sio)
455
 
        show_log(b, formatter)
456
 
        self.assertEqualDiff(sio.getvalue(), '''\
457
 
------------------------------------------------------------
458
 
revno: 1
459
 
author: John Doe <jdoe@example.com>
460
 
committer: Lorem Ipsum <test@example.com>
461
 
branch nick: test_author_log
462
 
timestamp: Wed 2005-11-23 12:08:27 +1000
463
 
message:
464
 
  add a
465
 
''')
466
 
 
467
 
 
468
 
 
469
 
class TestLineLogFormatter(TestCaseWithTransport):
470
 
 
471
267
    def test_line_log(self):
472
268
        """Line log should show revno
473
269
        
490
286
        log_contents = logfile.read()
491
287
        self.assertEqualDiff(log_contents, '1: Line-Log-Formatte... 2005-11-23 add a\n')
492
288
 
493
 
    def test_short_log_with_merges(self):
494
 
        wt = self.make_branch_and_memory_tree('.')
495
 
        wt.lock_write()
496
 
        try:
497
 
            wt.add('')
498
 
            wt.commit('rev-1', rev_id='rev-1',
499
 
                      timestamp=1132586655, timezone=36000,
500
 
                      committer='Joe Foo <joe@foo.com>')
501
 
            wt.commit('rev-merged', rev_id='rev-2a',
502
 
                      timestamp=1132586700, timezone=36000,
503
 
                      committer='Joe Foo <joe@foo.com>')
504
 
            wt.set_parent_ids(['rev-1', 'rev-2a'])
505
 
            wt.branch.set_last_revision_info(1, 'rev-1')
506
 
            wt.commit('rev-2', rev_id='rev-2b',
507
 
                      timestamp=1132586800, timezone=36000,
508
 
                      committer='Joe Foo <joe@foo.com>')
509
 
            logfile = self.make_utf8_encoded_stringio()
510
 
            formatter = ShortLogFormatter(to_file=logfile)
511
 
            show_log(wt.branch, formatter)
512
 
            logfile.flush()
513
 
            self.assertEqualDiff("""\
514
 
    2 Joe Foo\t2005-11-22 [merge]
515
 
      rev-2
516
 
 
517
 
    1 Joe Foo\t2005-11-22
518
 
      rev-1
519
 
 
520
 
""", logfile.getvalue())
521
 
        finally:
522
 
            wt.unlock()
523
 
 
524
 
    def test_trailing_newlines(self):
525
 
        wt = self.make_branch_and_tree('.')
526
 
        b = make_commits_with_trailing_newlines(wt)
527
 
        sio = self.make_utf8_encoded_stringio()
528
 
        lf = LineLogFormatter(to_file=sio)
529
 
        show_log(b, lf)
530
 
        self.assertEqualDiff(sio.getvalue(), """\
531
 
3: Joe Foo 2005-11-21 single line with trailing newline
532
 
2: Joe Bar 2005-11-21 multiline
533
 
1: Joe Foo 2005-11-21 simple log message
534
 
""")
535
 
 
536
 
 
537
 
class TestGetViewRevisions(TestCaseWithTransport):
538
 
 
539
289
    def make_tree_with_commits(self):
540
290
        """Create a tree with well-known revision ids"""
541
291
        wt = self.make_branch_and_tree('tree1')
650
400
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
651
401
            ('4b', '4', 0)],
652
402
            revisions)
653
 
 
654
 
 
655
 
class TestGetRevisionsTouchingFileID(TestCaseWithTransport):
656
 
 
657
 
    def create_tree_with_single_merge(self):
658
 
        """Create a branch with a moderate layout.
659
 
 
660
 
        The revision graph looks like:
661
 
 
662
 
           A
663
 
           |\
664
 
           B C
665
 
           |/
666
 
           D
667
 
 
668
 
        In this graph, A introduced files f1 and f2 and f3.
669
 
        B modifies f1 and f3, and C modifies f2 and f3.
670
 
        D merges the changes from B and C and resolves the conflict for f3.
671
 
        """
672
 
        # TODO: jam 20070218 This seems like it could really be done
673
 
        #       with make_branch_and_memory_tree() if we could just
674
 
        #       create the content of those files.
675
 
        # TODO: jam 20070218 Another alternative is that we would really
676
 
        #       like to only create this tree 1 time for all tests that
677
 
        #       use it. Since 'log' only uses the tree in a readonly
678
 
        #       fashion, it seems a shame to regenerate an identical
679
 
        #       tree for each test.
680
 
        tree = self.make_branch_and_tree('tree')
681
 
        tree.lock_write()
682
 
        self.addCleanup(tree.unlock)
683
 
 
684
 
        self.build_tree_contents([('tree/f1', 'A\n'),
685
 
                                  ('tree/f2', 'A\n'),
686
 
                                  ('tree/f3', 'A\n'),
687
 
                                 ])
688
 
        tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
689
 
        tree.commit('A', rev_id='A')
690
 
 
691
 
        self.build_tree_contents([('tree/f2', 'A\nC\n'),
692
 
                                  ('tree/f3', 'A\nC\n'),
693
 
                                 ])
694
 
        tree.commit('C', rev_id='C')
695
 
        # Revert back to A to build the other history.
696
 
        tree.set_last_revision('A')
697
 
        tree.branch.set_last_revision_info(1, 'A')
698
 
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
699
 
                                  ('tree/f2', 'A\n'),
700
 
                                  ('tree/f3', 'A\nB\n'),
701
 
                                 ])
702
 
        tree.commit('B', rev_id='B')
703
 
        tree.set_parent_ids(['B', 'C'])
704
 
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
705
 
                                  ('tree/f2', 'A\nC\n'),
706
 
                                  ('tree/f3', 'A\nB\nC\n'),
707
 
                                 ])
708
 
        tree.commit('D', rev_id='D')
709
 
 
710
 
        # Switch to a read lock for this tree.
711
 
        # We still have addCleanup(unlock)
712
 
        tree.unlock()
713
 
        tree.lock_read()
714
 
        return tree
715
 
 
716
 
    def test_tree_with_single_merge(self):
717
 
        """Make sure the tree layout is correct."""
718
 
        tree = self.create_tree_with_single_merge()
719
 
        rev_A_tree = tree.branch.repository.revision_tree('A')
720
 
        rev_B_tree = tree.branch.repository.revision_tree('B')
721
 
 
722
 
        f1_changed = (u'f1', 'f1-id', 'file', True, False)
723
 
        f2_changed = (u'f2', 'f2-id', 'file', True, False)
724
 
        f3_changed = (u'f3', 'f3-id', 'file', True, False)
725
 
 
726
 
        delta = rev_B_tree.changes_from(rev_A_tree)
727
 
        self.assertEqual([f1_changed, f3_changed], delta.modified)
728
 
        self.assertEqual([], delta.renamed)
729
 
        self.assertEqual([], delta.added)
730
 
        self.assertEqual([], delta.removed)
731
 
 
732
 
        rev_C_tree = tree.branch.repository.revision_tree('C')
733
 
        delta = rev_C_tree.changes_from(rev_A_tree)
734
 
        self.assertEqual([f2_changed, f3_changed], delta.modified)
735
 
        self.assertEqual([], delta.renamed)
736
 
        self.assertEqual([], delta.added)
737
 
        self.assertEqual([], delta.removed)
738
 
 
739
 
        rev_D_tree = tree.branch.repository.revision_tree('D')
740
 
        delta = rev_D_tree.changes_from(rev_B_tree)
741
 
        self.assertEqual([f2_changed, f3_changed], delta.modified)
742
 
        self.assertEqual([], delta.renamed)
743
 
        self.assertEqual([], delta.added)
744
 
        self.assertEqual([], delta.removed)
745
 
 
746
 
        delta = rev_D_tree.changes_from(rev_C_tree)
747
 
        self.assertEqual([f1_changed, f3_changed], delta.modified)
748
 
        self.assertEqual([], delta.renamed)
749
 
        self.assertEqual([], delta.added)
750
 
        self.assertEqual([], delta.removed)
751
 
 
752
 
    def assertAllRevisionsForFileID(self, tree, file_id, revisions):
753
 
        """Make sure _filter_revisions_touching_file_id returns the right values.
754
 
 
755
 
        Get the return value from _filter_revisions_touching_file_id and make
756
 
        sure they are correct.
757
 
        """
758
 
        # The api for _get_revisions_touching_file_id is a little crazy,
759
 
        # So we do the setup here.
760
 
        mainline = tree.branch.revision_history()
761
 
        mainline.insert(0, None)
762
 
        revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
763
 
        view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
764
 
                                                'reverse', True)
765
 
        actual_revs = log._filter_revisions_touching_file_id(
766
 
                            tree.branch, 
767
 
                            file_id,
768
 
                            mainline,
769
 
                            list(view_revs_iter))
770
 
        self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
771
 
 
772
 
    def test_file_id_f1(self):
773
 
        tree = self.create_tree_with_single_merge()
774
 
        # f1 should be marked as modified by revisions A and B
775
 
        self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
776
 
 
777
 
    def test_file_id_f2(self):
778
 
        tree = self.create_tree_with_single_merge()
779
 
        # f2 should be marked as modified by revisions A, C, and D
780
 
        # because D merged the changes from C.
781
 
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
782
 
 
783
 
    def test_file_id_f3(self):
784
 
        tree = self.create_tree_with_single_merge()
785
 
        # f3 should be marked as modified by revisions A, B, C, and D
786
 
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
787
 
 
788
 
 
789
 
class TestShowChangedRevisions(TestCaseWithTransport):
790
 
 
791
 
    def test_show_changed_revisions_verbose(self):
792
 
        tree = self.make_branch_and_tree('tree_a')
793
 
        self.build_tree(['tree_a/foo'])
794
 
        tree.add('foo')
795
 
        tree.commit('bar', rev_id='bar-id')
796
 
        s = self.make_utf8_encoded_stringio()
797
 
        log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
798
 
        self.assertContainsRe(s.getvalue(), 'bar')
799
 
        self.assertNotContainsRe(s.getvalue(), 'foo')
800
 
 
801
 
 
802
 
class TestLogFormatter(TestCase):
803
 
 
804
 
    def test_short_committer(self):
805
 
        rev = Revision('a-id')
806
 
        rev.committer = 'John Doe <jdoe@example.com>'
807
 
        lf = LogFormatter(None)
808
 
        self.assertEqual('John Doe', lf.short_committer(rev))
809
 
 
810
 
    def test_short_author(self):
811
 
        rev = Revision('a-id')
812
 
        rev.committer = 'John Doe <jdoe@example.com>'
813
 
        lf = LogFormatter(None)
814
 
        self.assertEqual('John Doe', lf.short_author(rev))
815
 
        rev.properties['author'] = 'John Smith <jsmith@example.com>'
816
 
        self.assertEqual('John Smith', lf.short_author(rev))