~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-08-17 13:49:05 UTC
  • mfrom: (1711.2.129 jam-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20060817134905-0dec610d2fcd6663
(bialix) 'make html-docs' produces html documentation

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2005 by 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)
81
91
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
82
92
                          start_revision=1, end_revision=-1) 
83
93
 
 
94
    def test_cur_revno(self):
 
95
        wt = self.make_branch_and_tree('.')
 
96
        b = wt.branch
 
97
 
 
98
        lf = LogCatcher()
 
99
        wt.commit('empty commit')
 
100
        show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
 
101
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
102
                          start_revision=2, end_revision=1) 
 
103
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
104
                          start_revision=1, end_revision=2) 
 
105
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
106
                          start_revision=0, end_revision=2) 
 
107
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
108
                          start_revision=1, end_revision=0) 
 
109
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
110
                          start_revision=-1, end_revision=1) 
 
111
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
112
                          start_revision=1, end_revision=-1) 
 
113
 
84
114
    def test_simple_log(self):
85
115
        eq = self.assertEquals
86
116
        
96
126
        lf = LogCatcher()
97
127
        show_log(b, lf, verbose=True)
98
128
        eq(len(lf.logs), 1)
99
 
        eq(lf.logs[0].revno, '1')
 
129
        eq(lf.logs[0].revno, 1)
100
130
        eq(lf.logs[0].rev.message, 'empty commit')
101
131
        d = lf.logs[0].delta
102
132
        self.log('log delta: %r' % d)
104
134
 
105
135
        self.build_tree(['hello'])
106
136
        wt.add('hello')
107
 
        wt.commit('add one file',
108
 
                  committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
109
 
                            u'<test@example.com>')
 
137
        wt.commit('add one file')
110
138
 
111
 
        lf = self.make_utf8_encoded_stringio()
 
139
        lf = StringIO()
112
140
        # log using regular thing
113
141
        show_log(b, LongLogFormatter(lf))
114
142
        lf.seek(0)
121
149
        eq(len(lf.logs), 2)
122
150
        self.log('log entries:')
123
151
        for logentry in lf.logs:
124
 
            self.log('%4s %s' % (logentry.revno, logentry.rev.message))
 
152
            self.log('%4d %s' % (logentry.revno, logentry.rev.message))
125
153
        
126
154
        # first one is most recent
127
155
        logentry = lf.logs[0]
128
 
        eq(logentry.revno, '2')
 
156
        eq(logentry.revno, 2)
129
157
        eq(logentry.rev.message, 'add one file')
130
158
        d = logentry.delta
131
159
        self.log('log 2 delta: %r' % d)
132
 
        self.checkDelta(d, added=['hello'])
 
160
        # self.checkDelta(d, added=['hello'])
133
161
        
134
162
        # commit a log message with control characters
135
163
        msg = "All 8-bit chars: " +  ''.join([unichr(x) for x in range(256)])
156
184
        self.log("escaped commit message: %r", committed_msg)
157
185
        self.assert_(msg == committed_msg)
158
186
 
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
187
    def test_trailing_newlines(self):
225
188
        wt = self.make_branch_and_tree('.')
226
 
        b = make_commits_with_trailing_newlines(wt)
227
 
        sio = self.make_utf8_encoded_stringio()
 
189
        b = wt.branch
 
190
        b.nick='test'
 
191
        open('a', 'wb').write('hello moto\n')
 
192
        wt.add('a')
 
193
        wt.commit('simple log message', rev_id='a1'
 
194
                , timestamp=1132586655.459960938, timezone=-6*3600
 
195
                , committer='Joe Foo <joe@foo.com>')
 
196
        open('b', 'wb').write('goodbye\n')
 
197
        wt.add('b')
 
198
        wt.commit('multiline\nlog\nmessage\n', rev_id='a2'
 
199
                , timestamp=1132586842.411175966, timezone=-6*3600
 
200
                , committer='Joe Foo <joe@foo.com>')
 
201
 
 
202
        open('c', 'wb').write('just another manic monday\n')
 
203
        wt.add('c')
 
204
        wt.commit('single line with trailing newline\n', rev_id='a3'
 
205
                , timestamp=1132587176.835228920, timezone=-6*3600
 
206
                , committer = 'Joe Foo <joe@foo.com>')
 
207
 
 
208
        sio = StringIO()
228
209
        lf = ShortLogFormatter(to_file=sio)
229
210
        show_log(b, lf)
230
211
        self.assertEquals(sio.getvalue(), """\
231
212
    3 Joe Foo\t2005-11-21
232
213
      single line with trailing newline
233
214
 
234
 
    2 Joe Bar\t2005-11-21
 
215
    2 Joe Foo\t2005-11-21
235
216
      multiline
236
217
      log
237
218
      message
241
222
 
242
223
""")
243
224
 
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
 
 
 
225
        sio = StringIO()
 
226
        lf = LongLogFormatter(to_file=sio)
 
227
        show_log(b, lf)
 
228
        self.assertEquals(sio.getvalue(), """\
 
229
------------------------------------------------------------
 
230
revno: 3
 
231
committer: Joe Foo <joe@foo.com>
 
232
branch nick: test
 
233
timestamp: Mon 2005-11-21 09:32:56 -0600
 
234
message:
 
235
  single line with trailing newline
 
236
------------------------------------------------------------
 
237
revno: 2
 
238
committer: Joe Foo <joe@foo.com>
 
239
branch nick: test
 
240
timestamp: Mon 2005-11-21 09:27:22 -0600
 
241
message:
 
242
  multiline
 
243
  log
 
244
  message
 
245
------------------------------------------------------------
 
246
revno: 1
 
247
committer: Joe Foo <joe@foo.com>
 
248
branch nick: test
 
249
timestamp: Mon 2005-11-21 09:24:15 -0600
 
250
message:
 
251
  simple log message
 
252
""")
 
253
        
263
254
    def test_verbose_log(self):
264
255
        """Verbose log includes changed files
265
256
        
293
284
  a
294
285
''')
295
286
 
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
287
    def test_line_log(self):
472
288
        """Line log should show revno
473
289
        
490
306
        log_contents = logfile.read()
491
307
        self.assertEqualDiff(log_contents, '1: Line-Log-Formatte... 2005-11-23 add a\n')
492
308
 
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
309
    def make_tree_with_commits(self):
540
310
        """Create a tree with well-known revision ids"""
541
311
        wt = self.make_branch_and_tree('tree1')
546
316
        rev_nos = {'1': 1, '2': 2, '3': 3}
547
317
        return mainline_revs, rev_nos, wt
548
318
 
 
319
    def pseudo_merge(self, source, target):
 
320
        revision_id = source.last_revision()
 
321
        target.branch.fetch(source.branch, revision_id)
 
322
        target.add_pending_merge(revision_id)
 
323
 
549
324
    def make_tree_with_merges(self):
550
325
        """Create a tree with well-known revision ids and a merge"""
551
326
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
552
327
        tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
553
328
        tree2.commit('four-a', rev_id='4a')
554
 
        wt.merge_from_branch(tree2.branch)
 
329
        self.pseudo_merge(tree2, wt)
555
330
        wt.commit('four-b', rev_id='4b')
556
331
        mainline_revs.append('4b')
557
332
        rev_nos['4b'] = 4
558
 
        # 4a: 3.1.1
559
333
        return mainline_revs, rev_nos, wt
560
334
 
561
335
    def make_tree_with_many_merges(self):
563
337
        wt = self.make_branch_and_tree('tree1')
564
338
        wt.commit('commit one', rev_id='1')
565
339
        wt.commit('commit two', rev_id='2')
 
340
        tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
566
341
        tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
567
342
        tree3.commit('commit three a', rev_id='3a')
568
 
        tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
569
 
        tree2.merge_from_branch(tree3.branch)
 
343
        self.pseudo_merge(tree3, tree2)
570
344
        tree2.commit('commit three b', rev_id='3b')
571
 
        wt.merge_from_branch(tree2.branch)
 
345
        self.pseudo_merge(tree2, wt)
572
346
        wt.commit('commit three c', rev_id='3c')
573
347
        tree2.commit('four-a', rev_id='4a')
574
 
        wt.merge_from_branch(tree2.branch)
 
348
        self.pseudo_merge(tree2, wt)
575
349
        wt.commit('four-b', rev_id='4b')
576
350
        mainline_revs = [None, '1', '2', '3c', '4b']
577
 
        rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
578
 
        full_rev_nos_for_reference = {
579
 
            '1': '1',
580
 
            '2': '2',
581
 
            '3a': '2.2.1', #first commit tree 3
582
 
            '3b': '2.1.1', # first commit tree 2
583
 
            '3c': '3', #merges 3b to main
584
 
            '4a': '2.1.2', # second commit tree 2
585
 
            '4b': '4', # merges 4a to main
586
 
            }
 
351
        rev_nos = {'1': 1, '2': 2, '3c': 3, '4b': 4}
587
352
        return mainline_revs, rev_nos, wt
588
353
 
589
354
    def test_get_view_revisions_forward(self):
591
356
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
592
357
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
593
358
                                            'forward'))
594
 
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
595
 
            revisions)
 
359
        self.assertEqual(revisions, [('1', 1, 0), ('2', 2, 0), ('3', 3, 0)])
596
360
        revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
597
361
                                             'forward', include_merges=False))
598
362
        self.assertEqual(revisions, revisions2)
602
366
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
603
367
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
604
368
                                            'reverse'))
605
 
        self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
606
 
            revisions)
 
369
        self.assertEqual(revisions, [('3', 3, 0), ('2', 2, 0), ('1', 1, 0), ])
607
370
        revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
608
371
                                             'reverse', include_merges=False))
609
372
        self.assertEqual(revisions, revisions2)
613
376
        mainline_revs, rev_nos, wt = self.make_tree_with_merges()
614
377
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
615
378
                                            'forward'))
616
 
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
617
 
            ('4b', '4', 0), ('4a', '3.1.1', 1)],
618
 
            revisions)
 
379
        self.assertEqual(revisions, [('1', 1, 0), ('2', 2, 0), ('3', 3, 0),
 
380
                                     ('4b', 4, 0), ('4a', None, 1)])
619
381
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
620
382
                                             'forward', include_merges=False))
621
 
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
622
 
            ('4b', '4', 0)],
623
 
            revisions)
 
383
        self.assertEqual(revisions, [('1', 1, 0), ('2', 2, 0), ('3', 3, 0),
 
384
                                     ('4b', 4, 0)])
624
385
 
625
386
    def test_get_view_revisions_merge_reverse(self):
626
387
        """Test get_view_revisions in reverse when there are merges"""
627
388
        mainline_revs, rev_nos, wt = self.make_tree_with_merges()
628
389
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
629
390
                                            'reverse'))
630
 
        self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
631
 
            ('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
632
 
            revisions)
 
391
        self.assertEqual(revisions, [('4b', 4, 0), ('4a', None, 1), 
 
392
                                     ('3', 3, 0), ('2', 2, 0), ('1', 1, 0)])
633
393
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
634
394
                                             'reverse', include_merges=False))
635
 
        self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
636
 
            ('1', '1', 0)],
637
 
            revisions)
 
395
        self.assertEqual(revisions, [('4b', 4, 0), ('3', 3, 0), ('2', 2, 0),
 
396
                                     ('1', 1, 0)])
638
397
 
639
398
    def test_get_view_revisions_merge2(self):
640
399
        """Test get_view_revisions when there are merges"""
641
400
        mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
642
401
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
643
402
                                            'forward'))
644
 
        expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
645
 
            ('3a', '2.2.1', 1), ('3b', '2.1.1', 1), ('4b', '4', 0),
646
 
            ('4a', '2.1.2', 1)]
647
 
        self.assertEqual(expected, revisions)
 
403
        expected = [('1', 1, 0), ('2', 2, 0), ('3c', 3, 0), ('3a', None, 1),
 
404
                    ('3b', None, 1), ('4b', 4, 0), ('4a', None, 1)]
 
405
        self.assertEqual(revisions, expected)
648
406
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
649
407
                                             'forward', include_merges=False))
650
 
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
651
 
            ('4b', '4', 0)],
652
 
            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))
 
408
        self.assertEqual(revisions, [('1', 1, 0), ('2', 2, 0), ('3c', 3, 0),
 
409
                                     ('4b', 4, 0)])