~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

  • Committer: Alexander Belchenko
  • Date: 2007-01-30 23:05:35 UTC
  • mto: This revision was merged to the branch mainline in revision 2259.
  • Revision ID: bialix@ukr.net-20070130230535-kx1rd478rtigyc3v
standalone installer: win98 support

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
 
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
 
    )
 
30
from bzrlib.errors import InvalidRevisionNumber
 
31
 
 
32
 
 
33
class _LogEntry(object):
 
34
    # should probably move into bzrlib.log?
 
35
    pass
39
36
 
40
37
 
41
38
class LogCatcher(LogFormatter):
47
44
 
48
45
    We should also test the LogFormatter.
49
46
    """
50
 
 
51
 
    supports_delta = True
52
 
 
53
47
    def __init__(self):
54
48
        super(LogCatcher, self).__init__(to_file=None)
55
49
        self.logs = []
56
50
 
57
 
    def log_revision(self, revision):
58
 
        self.logs.append(revision)
59
 
 
60
 
 
61
 
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):
62
60
 
63
61
    def checkDelta(self, delta, **kw):
64
62
        """Check the filenames touched by a delta are as expected."""
65
63
        for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
66
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
 
67
70
            # strip out only the path components
68
71
            got = [x[0] for x in getattr(delta, n)]
69
72
            self.assertEquals(expected, got)
111
114
 
112
115
        self.build_tree(['hello'])
113
116
        wt.add('hello')
114
 
        wt.commit('add one file',
115
 
                  committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
116
 
                            u'<test@example.com>')
 
117
        wt.commit('add one file')
117
118
 
118
 
        lf = self.make_utf8_encoded_stringio()
 
119
        lf = StringIO()
119
120
        # log using regular thing
120
121
        show_log(b, LongLogFormatter(lf))
121
122
        lf.seek(0)
136
137
        eq(logentry.rev.message, 'add one file')
137
138
        d = logentry.delta
138
139
        self.log('log 2 delta: %r' % d)
139
 
        self.checkDelta(d, added=['hello'])
 
140
        # self.checkDelta(d, added=['hello'])
140
141
        
141
142
        # commit a log message with control characters
142
143
        msg = "All 8-bit chars: " +  ''.join([unichr(x) for x in range(256)])
163
164
        self.log("escaped commit message: %r", committed_msg)
164
165
        self.assert_(msg == committed_msg)
165
166
 
166
 
    def test_deltas_in_merge_revisions(self):
167
 
        """Check deltas created for both mainline and merge revisions"""
168
 
        eq = self.assertEquals
169
 
        wt = self.make_branch_and_tree('parent')
170
 
        self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
171
 
        wt.add('file1')
172
 
        wt.add('file2')
173
 
        wt.commit(message='add file1 and file2')
174
 
        self.run_bzr('branch parent child')
175
 
        os.unlink('child/file1')
176
 
        file('child/file2', 'wb').write('hello\n')
177
 
        self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
178
 
            'child'])
179
 
        os.chdir('parent')
180
 
        self.run_bzr('merge ../child')
181
 
        wt.commit('merge child branch')
182
 
        os.chdir('..')
183
 
        b = wt.branch
184
 
        lf = LogCatcher()
185
 
        lf.supports_merge_revisions = True
186
 
        show_log(b, lf, verbose=True)
187
 
        eq(len(lf.logs),3)
188
 
        logentry = lf.logs[0]
189
 
        eq(logentry.revno, '2')
190
 
        eq(logentry.rev.message, 'merge child branch')
191
 
        d = logentry.delta
192
 
        self.checkDelta(d, removed=['file1'], modified=['file2'])
193
 
        logentry = lf.logs[1]
194
 
        eq(logentry.revno, '1.1.1')
195
 
        eq(logentry.rev.message, 'remove file1 and modify file2')
196
 
        d = logentry.delta
197
 
        self.checkDelta(d, removed=['file1'], modified=['file2'])
198
 
        logentry = lf.logs[2]
199
 
        eq(logentry.revno, '1')
200
 
        eq(logentry.rev.message, 'add file1 and file2')
201
 
        d = logentry.delta
202
 
        self.checkDelta(d, added=['file1', 'file2'])
203
 
 
204
 
    def test_merges_nonsupporting_formatter(self):
205
 
        """Tests that show_log will raise if the formatter doesn't
206
 
        support merge revisions."""
207
 
        wt = self.make_branch_and_memory_tree('.')
208
 
        wt.lock_write()
209
 
        try:
210
 
            wt.add('')
211
 
            wt.commit('rev-1', rev_id='rev-1',
212
 
                      timestamp=1132586655, timezone=36000,
213
 
                      committer='Joe Foo <joe@foo.com>')
214
 
            wt.commit('rev-merged', rev_id='rev-2a',
215
 
                      timestamp=1132586700, timezone=36000,
216
 
                      committer='Joe Foo <joe@foo.com>')
217
 
            wt.set_parent_ids(['rev-1', 'rev-2a'])
218
 
            wt.branch.set_last_revision_info(1, 'rev-1')
219
 
            wt.commit('rev-2', rev_id='rev-2b',
220
 
                      timestamp=1132586800, timezone=36000,
221
 
                      committer='Joe Foo <joe@foo.com>')
222
 
            logfile = self.make_utf8_encoded_stringio()
223
 
            formatter = ShortLogFormatter(to_file=logfile)
224
 
            wtb = wt.branch
225
 
            lf = LogCatcher()
226
 
            revspec = RevisionSpec.from_string('1.1.1')
227
 
            rev = revspec.in_history(wtb)
228
 
            self.assertRaises(BzrCommandError, show_log, wtb, lf,
229
 
                              start_revision=rev, end_revision=rev)
230
 
        finally:
231
 
            wt.unlock()
232
 
 
233
 
 
234
 
def make_commits_with_trailing_newlines(wt):
235
 
    """Helper method for LogFormatter tests"""    
236
 
    b = wt.branch
237
 
    b.nick='test'
238
 
    open('a', 'wb').write('hello moto\n')
239
 
    wt.add('a')
240
 
    wt.commit('simple log message', rev_id='a1',
241
 
              timestamp=1132586655.459960938, timezone=-6*3600,
242
 
              committer='Joe Foo <joe@foo.com>')
243
 
    open('b', 'wb').write('goodbye\n')
244
 
    wt.add('b')
245
 
    wt.commit('multiline\nlog\nmessage\n', rev_id='a2',
246
 
              timestamp=1132586842.411175966, timezone=-6*3600,
247
 
              committer='Joe Foo <joe@foo.com>',
248
 
              author='Joe Bar <joe@bar.com>')
249
 
 
250
 
    open('c', 'wb').write('just another manic monday\n')
251
 
    wt.add('c')
252
 
    wt.commit('single line with trailing newline\n', rev_id='a3',
253
 
              timestamp=1132587176.835228920, timezone=-6*3600,
254
 
              committer = 'Joe Foo <joe@foo.com>')
255
 
    return b
256
 
 
257
 
 
258
 
def normalize_log(log):
259
 
    """Replaces the variable lines of logs with fixed lines"""
260
 
    author = 'author: Dolor Sit <test@example.com>'
261
 
    committer = 'committer: Lorem Ipsum <test@example.com>'
262
 
    lines = log.splitlines(True)
263
 
    for idx,line in enumerate(lines):
264
 
        stripped_line = line.lstrip()
265
 
        indent = ' ' * (len(line) - len(stripped_line))
266
 
        if stripped_line.startswith('author:'):
267
 
            lines[idx] = indent + author + '\n'
268
 
        elif stripped_line.startswith('committer:'):
269
 
            lines[idx] = indent + committer + '\n'
270
 
        elif stripped_line.startswith('timestamp:'):
271
 
            lines[idx] = indent + 'timestamp: Just now\n'
272
 
    return ''.join(lines)
273
 
 
274
 
 
275
 
class TestShortLogFormatter(TestCaseWithTransport):
276
 
 
277
167
    def test_trailing_newlines(self):
278
168
        wt = self.make_branch_and_tree('.')
279
 
        b = make_commits_with_trailing_newlines(wt)
280
 
        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()
281
189
        lf = ShortLogFormatter(to_file=sio)
282
190
        show_log(b, lf)
283
 
        self.assertEqualDiff(sio.getvalue(), """\
 
191
        self.assertEquals(sio.getvalue(), """\
284
192
    3 Joe Foo\t2005-11-21
285
193
      single line with trailing newline
286
194
 
287
 
    2 Joe Bar\t2005-11-21
 
195
    2 Joe Foo\t2005-11-21
288
196
      multiline
289
197
      log
290
198
      message
294
202
 
295
203
""")
296
204
 
297
 
    def test_short_log_with_merges(self):
298
 
        wt = self.make_branch_and_memory_tree('.')
299
 
        wt.lock_write()
300
 
        try:
301
 
            wt.add('')
302
 
            wt.commit('rev-1', rev_id='rev-1',
303
 
                      timestamp=1132586655, timezone=36000,
304
 
                      committer='Joe Foo <joe@foo.com>')
305
 
            wt.commit('rev-merged', rev_id='rev-2a',
306
 
                      timestamp=1132586700, timezone=36000,
307
 
                      committer='Joe Foo <joe@foo.com>')
308
 
            wt.set_parent_ids(['rev-1', 'rev-2a'])
309
 
            wt.branch.set_last_revision_info(1, 'rev-1')
310
 
            wt.commit('rev-2', rev_id='rev-2b',
311
 
                      timestamp=1132586800, timezone=36000,
312
 
                      committer='Joe Foo <joe@foo.com>')
313
 
            logfile = self.make_utf8_encoded_stringio()
314
 
            formatter = ShortLogFormatter(to_file=logfile)
315
 
            show_log(wt.branch, formatter)
316
 
            self.assertEqualDiff(logfile.getvalue(), """\
317
 
    2 Joe Foo\t2005-11-22 [merge]
318
 
      rev-2
319
 
 
320
 
    1 Joe Foo\t2005-11-22
321
 
      rev-1
322
 
 
323
 
""")
324
 
        finally:
325
 
            wt.unlock()
326
 
 
327
 
    def test_short_log_single_merge_revision(self):
328
 
        wt = self.make_branch_and_memory_tree('.')
329
 
        wt.lock_write()
330
 
        try:
331
 
            wt.add('')
332
 
            wt.commit('rev-1', rev_id='rev-1',
333
 
                      timestamp=1132586655, timezone=36000,
334
 
                      committer='Joe Foo <joe@foo.com>')
335
 
            wt.commit('rev-merged', rev_id='rev-2a',
336
 
                      timestamp=1132586700, timezone=36000,
337
 
                      committer='Joe Foo <joe@foo.com>')
338
 
            wt.set_parent_ids(['rev-1', 'rev-2a'])
339
 
            wt.branch.set_last_revision_info(1, 'rev-1')
340
 
            wt.commit('rev-2', rev_id='rev-2b',
341
 
                      timestamp=1132586800, timezone=36000,
342
 
                      committer='Joe Foo <joe@foo.com>')
343
 
            logfile = self.make_utf8_encoded_stringio()
344
 
            formatter = ShortLogFormatter(to_file=logfile)
345
 
            revspec = RevisionSpec.from_string('1.1.1')
346
 
            wtb = wt.branch
347
 
            rev = revspec.in_history(wtb)
348
 
            show_log(wtb, formatter, start_revision=rev, end_revision=rev)
349
 
            self.assertEqualDiff(logfile.getvalue(), """\
350
 
1.1.1 Joe Foo\t2005-11-22
351
 
      rev-merged
352
 
 
353
 
""")
354
 
        finally:
355
 
            wt.unlock()
356
 
 
357
 
 
358
 
class TestLongLogFormatter(TestCaseWithTransport):
359
 
 
 
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
        
360
234
    def test_verbose_log(self):
361
235
        """Verbose log includes changed files
362
236
        
390
264
  a
391
265
''')
392
266
 
393
 
    def test_merges_are_indented_by_level(self):
394
 
        wt = self.make_branch_and_tree('parent')
395
 
        wt.commit('first post')
396
 
        self.run_bzr('branch parent child')
397
 
        self.run_bzr(['commit', '-m', 'branch 1', '--unchanged', 'child'])
398
 
        self.run_bzr('branch child smallerchild')
399
 
        self.run_bzr(['commit', '-m', 'branch 2', '--unchanged',
400
 
            'smallerchild'])
401
 
        os.chdir('child')
402
 
        self.run_bzr('merge ../smallerchild')
403
 
        self.run_bzr(['commit', '-m', 'merge branch 2'])
404
 
        os.chdir('../parent')
405
 
        self.run_bzr('merge ../child')
406
 
        wt.commit('merge branch 1')
407
 
        b = wt.branch
408
 
        sio = self.make_utf8_encoded_stringio()
409
 
        lf = LongLogFormatter(to_file=sio)
410
 
        show_log(b, lf, verbose=True)
411
 
        log = normalize_log(sio.getvalue())
412
 
        self.assertEqualDiff(log, """\
413
 
------------------------------------------------------------
414
 
revno: 2
415
 
committer: Lorem Ipsum <test@example.com>
416
 
branch nick: parent
417
 
timestamp: Just now
418
 
message:
419
 
  merge branch 1
420
 
    ------------------------------------------------------------
421
 
    revno: 1.1.2
422
 
    committer: Lorem Ipsum <test@example.com>
423
 
    branch nick: child
424
 
    timestamp: Just now
425
 
    message:
426
 
      merge branch 2
427
 
        ------------------------------------------------------------
428
 
        revno: 1.2.1
429
 
        committer: Lorem Ipsum <test@example.com>
430
 
        branch nick: smallerchild
431
 
        timestamp: Just now
432
 
        message:
433
 
          branch 2
434
 
    ------------------------------------------------------------
435
 
    revno: 1.1.1
436
 
    committer: Lorem Ipsum <test@example.com>
437
 
    branch nick: child
438
 
    timestamp: Just now
439
 
    message:
440
 
      branch 1
441
 
------------------------------------------------------------
442
 
revno: 1
443
 
committer: Lorem Ipsum <test@example.com>
444
 
branch nick: parent
445
 
timestamp: Just now
446
 
message:
447
 
  first post
448
 
""")
449
 
 
450
 
    def test_verbose_merge_revisions_contain_deltas(self):
451
 
        wt = self.make_branch_and_tree('parent')
452
 
        self.build_tree(['parent/f1', 'parent/f2'])
453
 
        wt.add(['f1','f2'])
454
 
        wt.commit('first post')
455
 
        self.run_bzr('branch parent child')
456
 
        os.unlink('child/f1')
457
 
        file('child/f2', 'wb').write('hello\n')
458
 
        self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
459
 
            'child'])
460
 
        os.chdir('parent')
461
 
        self.run_bzr('merge ../child')
462
 
        wt.commit('merge branch 1')
463
 
        b = wt.branch
464
 
        sio = self.make_utf8_encoded_stringio()
465
 
        lf = LongLogFormatter(to_file=sio)
466
 
        show_log(b, lf, verbose=True)
467
 
        log = normalize_log(sio.getvalue())
468
 
        self.assertEqualDiff(log, """\
469
 
------------------------------------------------------------
470
 
revno: 2
471
 
committer: Lorem Ipsum <test@example.com>
472
 
branch nick: parent
473
 
timestamp: Just now
474
 
message:
475
 
  merge branch 1
476
 
removed:
477
 
  f1
478
 
modified:
479
 
  f2
480
 
    ------------------------------------------------------------
481
 
    revno: 1.1.1
482
 
    committer: Lorem Ipsum <test@example.com>
483
 
    branch nick: child
484
 
    timestamp: Just now
485
 
    message:
486
 
      removed f1 and modified f2
487
 
    removed:
488
 
      f1
489
 
    modified:
490
 
      f2
491
 
------------------------------------------------------------
492
 
revno: 1
493
 
committer: Lorem Ipsum <test@example.com>
494
 
branch nick: parent
495
 
timestamp: Just now
496
 
message:
497
 
  first post
498
 
added:
499
 
  f1
500
 
  f2
501
 
""")
502
 
 
503
 
    def test_trailing_newlines(self):
504
 
        wt = self.make_branch_and_tree('.')
505
 
        b = make_commits_with_trailing_newlines(wt)
506
 
        sio = self.make_utf8_encoded_stringio()
507
 
        lf = LongLogFormatter(to_file=sio)
508
 
        show_log(b, lf)
509
 
        self.assertEqualDiff(sio.getvalue(), """\
510
 
------------------------------------------------------------
511
 
revno: 3
512
 
committer: Joe Foo <joe@foo.com>
513
 
branch nick: test
514
 
timestamp: Mon 2005-11-21 09:32:56 -0600
515
 
message:
516
 
  single line with trailing newline
517
 
------------------------------------------------------------
518
 
revno: 2
519
 
author: Joe Bar <joe@bar.com>
520
 
committer: Joe Foo <joe@foo.com>
521
 
branch nick: test
522
 
timestamp: Mon 2005-11-21 09:27:22 -0600
523
 
message:
524
 
  multiline
525
 
  log
526
 
  message
527
 
------------------------------------------------------------
528
 
revno: 1
529
 
committer: Joe Foo <joe@foo.com>
530
 
branch nick: test
531
 
timestamp: Mon 2005-11-21 09:24:15 -0600
532
 
message:
533
 
  simple log message
534
 
""")
535
 
 
536
 
    def test_author_in_log(self):
537
 
        """Log includes the author name if it's set in
538
 
        the revision properties
539
 
        """
540
 
        wt = self.make_branch_and_tree('.')
541
 
        b = wt.branch
542
 
        self.build_tree(['a'])
543
 
        wt.add('a')
544
 
        b.nick = 'test_author_log'
545
 
        wt.commit(message='add a',
546
 
                  timestamp=1132711707,
547
 
                  timezone=36000,
548
 
                  committer='Lorem Ipsum <test@example.com>',
549
 
                  author='John Doe <jdoe@example.com>')
550
 
        sio = StringIO()
551
 
        formatter = LongLogFormatter(to_file=sio)
552
 
        show_log(b, formatter)
553
 
        self.assertEqualDiff(sio.getvalue(), '''\
554
 
------------------------------------------------------------
555
 
revno: 1
556
 
author: John Doe <jdoe@example.com>
557
 
committer: Lorem Ipsum <test@example.com>
558
 
branch nick: test_author_log
559
 
timestamp: Wed 2005-11-23 12:08:27 +1000
560
 
message:
561
 
  add a
562
 
''')
563
 
 
564
 
 
565
 
 
566
 
class TestLineLogFormatter(TestCaseWithTransport):
567
 
 
568
267
    def test_line_log(self):
569
268
        """Line log should show revno
570
269
        
585
284
        logfile.flush()
586
285
        logfile.seek(0)
587
286
        log_contents = logfile.read()
588
 
        self.assertEqualDiff(log_contents,
589
 
            '1: Line-Log-Formatte... 2005-11-23 add a\n')
590
 
 
591
 
    def test_trailing_newlines(self):
592
 
        wt = self.make_branch_and_tree('.')
593
 
        b = make_commits_with_trailing_newlines(wt)
594
 
        sio = self.make_utf8_encoded_stringio()
595
 
        lf = LineLogFormatter(to_file=sio)
596
 
        show_log(b, lf)
597
 
        self.assertEqualDiff(sio.getvalue(), """\
598
 
3: Joe Foo 2005-11-21 single line with trailing newline
599
 
2: Joe Bar 2005-11-21 multiline
600
 
1: Joe Foo 2005-11-21 simple log message
601
 
""")
602
 
 
603
 
    def test_line_log_single_merge_revision(self):
604
 
        wt = self.make_branch_and_memory_tree('.')
605
 
        wt.lock_write()
606
 
        try:
607
 
            wt.add('')
608
 
            wt.commit('rev-1', rev_id='rev-1',
609
 
                      timestamp=1132586655, timezone=36000,
610
 
                      committer='Joe Foo <joe@foo.com>')
611
 
            wt.commit('rev-merged', rev_id='rev-2a',
612
 
                      timestamp=1132586700, timezone=36000,
613
 
                      committer='Joe Foo <joe@foo.com>')
614
 
            wt.set_parent_ids(['rev-1', 'rev-2a'])
615
 
            wt.branch.set_last_revision_info(1, 'rev-1')
616
 
            wt.commit('rev-2', rev_id='rev-2b',
617
 
                      timestamp=1132586800, timezone=36000,
618
 
                      committer='Joe Foo <joe@foo.com>')
619
 
            logfile = self.make_utf8_encoded_stringio()
620
 
            formatter = LineLogFormatter(to_file=logfile)
621
 
            revspec = RevisionSpec.from_string('1.1.1')
622
 
            wtb = wt.branch
623
 
            rev = revspec.in_history(wtb)
624
 
            show_log(wtb, formatter, start_revision=rev, end_revision=rev)
625
 
            self.assertEqualDiff(logfile.getvalue(), """\
626
 
1.1.1: Joe Foo 2005-11-22 rev-merged
627
 
""")
628
 
        finally:
629
 
            wt.unlock()
630
 
 
631
 
 
632
 
 
633
 
class TestGetViewRevisions(TestCaseWithTransport):
 
287
        self.assertEqualDiff(log_contents, '1: Line-Log-Formatte... 2005-11-23 add a\n')
634
288
 
635
289
    def make_tree_with_commits(self):
636
290
        """Create a tree with well-known revision ids"""
674
328
        full_rev_nos_for_reference = {
675
329
            '1': '1',
676
330
            '2': '2',
677
 
            '3a': '2.1.1', #first commit tree 3
678
 
            '3b': '2.2.1', # first commit tree 2
 
331
            '3a': '2.2.1', #first commit tree 3
 
332
            '3b': '2.1.1', # first commit tree 2
679
333
            '3c': '3', #merges 3b to main
680
 
            '4a': '2.2.2', # second commit tree 2
 
334
            '4a': '2.1.2', # second commit tree 2
681
335
            '4b': '4', # merges 4a to main
682
336
            }
683
337
        return mainline_revs, rev_nos, wt
738
392
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
739
393
                                            'forward'))
740
394
        expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
741
 
            ('3a', '2.1.1', 1), ('3b', '2.2.1', 1), ('4b', '4', 0),
742
 
            ('4a', '2.2.2', 1)]
 
395
            ('3a', '2.2.1', 1), ('3b', '2.1.1', 1), ('4b', '4', 0),
 
396
            ('4a', '2.1.2', 1)]
743
397
        self.assertEqual(expected, revisions)
744
398
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
745
399
                                             'forward', include_merges=False))
746
400
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
747
401
            ('4b', '4', 0)],
748
402
            revisions)
749
 
 
750
 
 
751
 
class TestGetRevisionsTouchingFileID(TestCaseWithTransport):
752
 
 
753
 
    def create_tree_with_single_merge(self):
754
 
        """Create a branch with a moderate layout.
755
 
 
756
 
        The revision graph looks like:
757
 
 
758
 
           A
759
 
           |\
760
 
           B C
761
 
           |/
762
 
           D
763
 
 
764
 
        In this graph, A introduced files f1 and f2 and f3.
765
 
        B modifies f1 and f3, and C modifies f2 and f3.
766
 
        D merges the changes from B and C and resolves the conflict for f3.
767
 
        """
768
 
        # TODO: jam 20070218 This seems like it could really be done
769
 
        #       with make_branch_and_memory_tree() if we could just
770
 
        #       create the content of those files.
771
 
        # TODO: jam 20070218 Another alternative is that we would really
772
 
        #       like to only create this tree 1 time for all tests that
773
 
        #       use it. Since 'log' only uses the tree in a readonly
774
 
        #       fashion, it seems a shame to regenerate an identical
775
 
        #       tree for each test.
776
 
        tree = self.make_branch_and_tree('tree')
777
 
        tree.lock_write()
778
 
        self.addCleanup(tree.unlock)
779
 
 
780
 
        self.build_tree_contents([('tree/f1', 'A\n'),
781
 
                                  ('tree/f2', 'A\n'),
782
 
                                  ('tree/f3', 'A\n'),
783
 
                                 ])
784
 
        tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
785
 
        tree.commit('A', rev_id='A')
786
 
 
787
 
        self.build_tree_contents([('tree/f2', 'A\nC\n'),
788
 
                                  ('tree/f3', 'A\nC\n'),
789
 
                                 ])
790
 
        tree.commit('C', rev_id='C')
791
 
        # Revert back to A to build the other history.
792
 
        tree.set_last_revision('A')
793
 
        tree.branch.set_last_revision_info(1, 'A')
794
 
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
795
 
                                  ('tree/f2', 'A\n'),
796
 
                                  ('tree/f3', 'A\nB\n'),
797
 
                                 ])
798
 
        tree.commit('B', rev_id='B')
799
 
        tree.set_parent_ids(['B', 'C'])
800
 
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
801
 
                                  ('tree/f2', 'A\nC\n'),
802
 
                                  ('tree/f3', 'A\nB\nC\n'),
803
 
                                 ])
804
 
        tree.commit('D', rev_id='D')
805
 
 
806
 
        # Switch to a read lock for this tree.
807
 
        # We still have addCleanup(unlock)
808
 
        tree.unlock()
809
 
        tree.lock_read()
810
 
        return tree
811
 
 
812
 
    def test_tree_with_single_merge(self):
813
 
        """Make sure the tree layout is correct."""
814
 
        tree = self.create_tree_with_single_merge()
815
 
        rev_A_tree = tree.branch.repository.revision_tree('A')
816
 
        rev_B_tree = tree.branch.repository.revision_tree('B')
817
 
 
818
 
        f1_changed = (u'f1', 'f1-id', 'file', True, False)
819
 
        f2_changed = (u'f2', 'f2-id', 'file', True, False)
820
 
        f3_changed = (u'f3', 'f3-id', 'file', True, False)
821
 
 
822
 
        delta = rev_B_tree.changes_from(rev_A_tree)
823
 
        self.assertEqual([f1_changed, f3_changed], delta.modified)
824
 
        self.assertEqual([], delta.renamed)
825
 
        self.assertEqual([], delta.added)
826
 
        self.assertEqual([], delta.removed)
827
 
 
828
 
        rev_C_tree = tree.branch.repository.revision_tree('C')
829
 
        delta = rev_C_tree.changes_from(rev_A_tree)
830
 
        self.assertEqual([f2_changed, f3_changed], delta.modified)
831
 
        self.assertEqual([], delta.renamed)
832
 
        self.assertEqual([], delta.added)
833
 
        self.assertEqual([], delta.removed)
834
 
 
835
 
        rev_D_tree = tree.branch.repository.revision_tree('D')
836
 
        delta = rev_D_tree.changes_from(rev_B_tree)
837
 
        self.assertEqual([f2_changed, f3_changed], delta.modified)
838
 
        self.assertEqual([], delta.renamed)
839
 
        self.assertEqual([], delta.added)
840
 
        self.assertEqual([], delta.removed)
841
 
 
842
 
        delta = rev_D_tree.changes_from(rev_C_tree)
843
 
        self.assertEqual([f1_changed, f3_changed], delta.modified)
844
 
        self.assertEqual([], delta.renamed)
845
 
        self.assertEqual([], delta.added)
846
 
        self.assertEqual([], delta.removed)
847
 
 
848
 
    def assertAllRevisionsForFileID(self, tree, file_id, revisions):
849
 
        """Make sure _filter_revisions_touching_file_id returns the right values.
850
 
 
851
 
        Get the return value from _filter_revisions_touching_file_id and make
852
 
        sure they are correct.
853
 
        """
854
 
        # The api for _get_revisions_touching_file_id is a little crazy,
855
 
        # So we do the setup here.
856
 
        mainline = tree.branch.revision_history()
857
 
        mainline.insert(0, None)
858
 
        revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
859
 
        view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
860
 
                                                'reverse', True)
861
 
        actual_revs = log._filter_revisions_touching_file_id(
862
 
                            tree.branch, 
863
 
                            file_id,
864
 
                            mainline,
865
 
                            list(view_revs_iter))
866
 
        self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
867
 
 
868
 
    def test_file_id_f1(self):
869
 
        tree = self.create_tree_with_single_merge()
870
 
        # f1 should be marked as modified by revisions A and B
871
 
        self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
872
 
 
873
 
    def test_file_id_f2(self):
874
 
        tree = self.create_tree_with_single_merge()
875
 
        # f2 should be marked as modified by revisions A, C, and D
876
 
        # because D merged the changes from C.
877
 
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
878
 
 
879
 
    def test_file_id_f3(self):
880
 
        tree = self.create_tree_with_single_merge()
881
 
        # f3 should be marked as modified by revisions A, B, C, and D
882
 
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
883
 
 
884
 
 
885
 
class TestShowChangedRevisions(TestCaseWithTransport):
886
 
 
887
 
    def test_show_changed_revisions_verbose(self):
888
 
        tree = self.make_branch_and_tree('tree_a')
889
 
        self.build_tree(['tree_a/foo'])
890
 
        tree.add('foo')
891
 
        tree.commit('bar', rev_id='bar-id')
892
 
        s = self.make_utf8_encoded_stringio()
893
 
        log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
894
 
        self.assertContainsRe(s.getvalue(), 'bar')
895
 
        self.assertNotContainsRe(s.getvalue(), 'foo')
896
 
 
897
 
 
898
 
class TestLogFormatter(TestCase):
899
 
 
900
 
    def test_short_committer(self):
901
 
        rev = Revision('a-id')
902
 
        rev.committer = 'John Doe <jdoe@example.com>'
903
 
        lf = LogFormatter(None)
904
 
        self.assertEqual('John Doe', lf.short_committer(rev))
905
 
        rev.committer = 'John Smith <jsmith@example.com>'
906
 
        self.assertEqual('John Smith', lf.short_committer(rev))
907
 
        rev.committer = 'John Smith'
908
 
        self.assertEqual('John Smith', lf.short_committer(rev))
909
 
        rev.committer = 'jsmith@example.com'
910
 
        self.assertEqual('jsmith@example.com', lf.short_committer(rev))
911
 
        rev.committer = '<jsmith@example.com>'
912
 
        self.assertEqual('jsmith@example.com', lf.short_committer(rev))
913
 
        rev.committer = 'John Smith jsmith@example.com'
914
 
        self.assertEqual('John Smith', lf.short_committer(rev))
915
 
 
916
 
    def test_short_author(self):
917
 
        rev = Revision('a-id')
918
 
        rev.committer = 'John Doe <jdoe@example.com>'
919
 
        lf = LogFormatter(None)
920
 
        self.assertEqual('John Doe', lf.short_author(rev))
921
 
        rev.properties['author'] = 'John Smith <jsmith@example.com>'
922
 
        self.assertEqual('John Smith', lf.short_author(rev))
923
 
        rev.properties['author'] = 'John Smith'
924
 
        self.assertEqual('John Smith', lf.short_author(rev))
925
 
        rev.properties['author'] = 'jsmith@example.com'
926
 
        self.assertEqual('jsmith@example.com', lf.short_author(rev))
927
 
        rev.properties['author'] = '<jsmith@example.com>'
928
 
        self.assertEqual('jsmith@example.com', lf.short_author(rev))
929
 
        rev.properties['author'] = 'John Smith jsmith@example.com'
930
 
        self.assertEqual('John Smith', lf.short_author(rev))