~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

  • Committer: Wouter van Heyst
  • Date: 2006-06-07 16:05:27 UTC
  • mto: This revision was merged to the branch mainline in revision 1752.
  • Revision ID: larstiq@larstiq.dyndns.org-20060607160527-2b3649154d0e2e84
more code cleanup

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
2
 
#
 
1
# Copyright (C) 2005 by Canonical Ltd
 
2
# -*- coding: utf-8 -*-
 
3
# vim: encoding=utf-8
 
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
5
7
# the Free Software Foundation; either version 2 of the License, or
6
8
# (at your option) any later version.
7
 
#
 
9
 
8
10
# This program is distributed in the hope that it will be useful,
9
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
13
# GNU General Public License for more details.
12
 
#
 
14
 
13
15
# You should have received a copy of the GNU General Public License
14
16
# along with this program; if not, write to the Free Software
15
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
19
import os
18
20
from cStringIO import StringIO
19
21
 
20
 
from bzrlib import log
21
 
from bzrlib.tests import TestCaseWithTransport
22
 
from bzrlib.log import (show_log,
23
 
                        get_view_revisions,
24
 
                        LogRevision,
25
 
                        LogFormatter,
26
 
                        LongLogFormatter,
27
 
                        ShortLogFormatter,
28
 
                        LineLogFormatter)
 
22
from bzrlib.tests import BzrTestBase, TestCaseWithTransport
 
23
from bzrlib.log import (LogFormatter, show_log, LongLogFormatter,
 
24
                        ShortLogFormatter, LineLogFormatter)
29
25
from bzrlib.branch import Branch
30
26
from bzrlib.errors import InvalidRevisionNumber
31
27
 
32
28
 
 
29
class _LogEntry(object):
 
30
    # should probably move into bzrlib.log?
 
31
    pass
 
32
 
 
33
 
33
34
class LogCatcher(LogFormatter):
34
35
    """Pull log messages into list rather than displaying them.
35
36
 
39
40
 
40
41
    We should also test the LogFormatter.
41
42
    """
42
 
 
43
 
    supports_delta = True
44
 
 
45
43
    def __init__(self):
46
44
        super(LogCatcher, self).__init__(to_file=None)
47
45
        self.logs = []
48
46
 
49
 
    def log_revision(self, revision):
50
 
        self.logs.append(revision)
51
 
 
52
 
 
53
 
class TestShowLog(TestCaseWithTransport):
 
47
    def show(self, revno, rev, delta):
 
48
        le = _LogEntry()
 
49
        le.revno = revno
 
50
        le.rev = rev
 
51
        le.delta = delta
 
52
        self.logs.append(le)
 
53
 
 
54
 
 
55
class SimpleLogTest(TestCaseWithTransport):
54
56
 
55
57
    def checkDelta(self, delta, **kw):
56
58
        """Check the filenames touched by a delta are as expected."""
57
59
        for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
58
60
            expected = kw.get(n, [])
 
61
 
 
62
            # tests are written with unix paths; fix them up for windows
 
63
            #if os.sep != '/':
 
64
            #    expected = [x.replace('/', os.sep) for x in expected]
 
65
 
59
66
            # strip out only the path components
60
67
            got = [x[0] for x in getattr(delta, n)]
61
68
            self.assertEquals(expected, got)
80
87
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
81
88
                          start_revision=1, end_revision=-1) 
82
89
 
 
90
    def test_cur_revno(self):
 
91
        wt = self.make_branch_and_tree('.')
 
92
        b = wt.branch
 
93
 
 
94
        lf = LogCatcher()
 
95
        wt.commit('empty commit')
 
96
        show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
 
97
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
98
                          start_revision=2, end_revision=1) 
 
99
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
100
                          start_revision=1, end_revision=2) 
 
101
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
102
                          start_revision=0, end_revision=2) 
 
103
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
104
                          start_revision=1, end_revision=0) 
 
105
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
106
                          start_revision=-1, end_revision=1) 
 
107
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
108
                          start_revision=1, end_revision=-1) 
 
109
 
83
110
    def test_simple_log(self):
84
111
        eq = self.assertEquals
85
112
        
95
122
        lf = LogCatcher()
96
123
        show_log(b, lf, verbose=True)
97
124
        eq(len(lf.logs), 1)
98
 
        eq(lf.logs[0].revno, '1')
 
125
        eq(lf.logs[0].revno, 1)
99
126
        eq(lf.logs[0].rev.message, 'empty commit')
100
127
        d = lf.logs[0].delta
101
128
        self.log('log delta: %r' % d)
118
145
        eq(len(lf.logs), 2)
119
146
        self.log('log entries:')
120
147
        for logentry in lf.logs:
121
 
            self.log('%4s %s' % (logentry.revno, logentry.rev.message))
 
148
            self.log('%4d %s' % (logentry.revno, logentry.rev.message))
122
149
        
123
150
        # first one is most recent
124
151
        logentry = lf.logs[0]
125
 
        eq(logentry.revno, '2')
 
152
        eq(logentry.revno, 2)
126
153
        eq(logentry.rev.message, 'add one file')
127
154
        d = logentry.delta
128
155
        self.log('log 2 delta: %r' % d)
129
 
        self.checkDelta(d, added=['hello'])
 
156
        # self.checkDelta(d, added=['hello'])
130
157
        
131
158
        # commit a log message with control characters
132
159
        msg = "All 8-bit chars: " +  ''.join([unichr(x) for x in range(256)])
153
180
        self.log("escaped commit message: %r", committed_msg)
154
181
        self.assert_(msg == committed_msg)
155
182
 
156
 
    def test_deltas_in_merge_revisions(self):
157
 
        """Check deltas created for both mainline and merge revisions"""
158
 
        eq = self.assertEquals
159
 
        wt = self.make_branch_and_tree('parent')
160
 
        self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
161
 
        wt.add('file1')
162
 
        wt.add('file2')
163
 
        wt.commit(message='add file1 and file2')
164
 
        self.run_bzr('branch', 'parent', 'child')
165
 
        os.unlink('child/file1')
166
 
        print >> file('child/file2', 'wb'), 'hello'
167
 
        self.run_bzr('commit', '-m', 'remove file1 and modify file2', 'child')
168
 
        os.chdir('parent')
169
 
        self.run_bzr('merge', '../child')
170
 
        wt.commit('merge child branch')
171
 
        os.chdir('..')
172
 
        b = wt.branch
173
 
        lf = LogCatcher()
174
 
        lf.supports_merge_revisions = True
175
 
        show_log(b, lf, verbose=True)
176
 
        eq(len(lf.logs),3)
177
 
        logentry = lf.logs[0]
178
 
        eq(logentry.revno, '2')
179
 
        eq(logentry.rev.message, 'merge child branch')
180
 
        d = logentry.delta
181
 
        self.checkDelta(d, removed=['file1'], modified=['file2'])
182
 
        logentry = lf.logs[1]
183
 
        eq(logentry.revno, '1.1.1')
184
 
        eq(logentry.rev.message, 'remove file1 and modify file2')
185
 
        d = logentry.delta
186
 
        self.checkDelta(d, removed=['file1'], modified=['file2'])
187
 
        logentry = lf.logs[2]
188
 
        eq(logentry.revno, '1')
189
 
        eq(logentry.rev.message, 'add file1 and file2')
190
 
        d = logentry.delta
191
 
        self.checkDelta(d, added=['file1', 'file2'])
192
 
 
193
 
 
194
 
def make_commits_with_trailing_newlines(wt):
195
 
    """Helper method for LogFormatter tests"""    
196
 
    b = wt.branch
197
 
    b.nick='test'
198
 
    open('a', 'wb').write('hello moto\n')
199
 
    wt.add('a')
200
 
    wt.commit('simple log message', rev_id='a1'
201
 
            , timestamp=1132586655.459960938, timezone=-6*3600
202
 
            , committer='Joe Foo <joe@foo.com>')
203
 
    open('b', 'wb').write('goodbye\n')
204
 
    wt.add('b')
205
 
    wt.commit('multiline\nlog\nmessage\n', rev_id='a2'
206
 
            , timestamp=1132586842.411175966, timezone=-6*3600
207
 
            , committer='Joe Foo <joe@foo.com>')
208
 
 
209
 
    open('c', 'wb').write('just another manic monday\n')
210
 
    wt.add('c')
211
 
    wt.commit('single line with trailing newline\n', rev_id='a3'
212
 
            , timestamp=1132587176.835228920, timezone=-6*3600
213
 
            , committer = 'Joe Foo <joe@foo.com>')
214
 
    return b
215
 
 
216
 
 
217
 
class TestShortLogFormatter(TestCaseWithTransport):
218
 
 
219
183
    def test_trailing_newlines(self):
220
184
        wt = self.make_branch_and_tree('.')
221
 
        b = make_commits_with_trailing_newlines(wt)
 
185
        b = wt.branch
 
186
        b.nick='test'
 
187
        open('a', 'wb').write('hello moto\n')
 
188
        wt.add('a')
 
189
        wt.commit('simple log message', rev_id='a1'
 
190
                , timestamp=1132586655.459960938, timezone=-6*3600
 
191
                , committer='Joe Foo <joe@foo.com>')
 
192
        open('b', 'wb').write('goodbye\n')
 
193
        wt.add('b')
 
194
        wt.commit('multiline\nlog\nmessage\n', rev_id='a2'
 
195
                , timestamp=1132586842.411175966, timezone=-6*3600
 
196
                , committer='Joe Foo <joe@foo.com>')
 
197
 
 
198
        open('c', 'wb').write('just another manic monday\n')
 
199
        wt.add('c')
 
200
        wt.commit('single line with trailing newline\n', rev_id='a3'
 
201
                , timestamp=1132587176.835228920, timezone=-6*3600
 
202
                , committer = 'Joe Foo <joe@foo.com>')
 
203
 
222
204
        sio = StringIO()
223
205
        lf = ShortLogFormatter(to_file=sio)
224
206
        show_log(b, lf)
236
218
 
237
219
""")
238
220
 
239
 
 
240
 
class TestLongLogFormatter(TestCaseWithTransport):
241
 
 
242
 
    def normalize_log(self,log):
243
 
        """Replaces the variable lines of logs with fixed lines"""
244
 
        committer = 'committer: Lorem Ipsum <test@example.com>'
245
 
        lines = log.splitlines(True)
246
 
        for idx,line in enumerate(lines):
247
 
            stripped_line = line.lstrip()
248
 
            indent = ' ' * (len(line) - len(stripped_line))
249
 
            if stripped_line.startswith('committer:'):
250
 
                lines[idx] = indent + committer + '\n'
251
 
            if stripped_line.startswith('timestamp:'):
252
 
                lines[idx] = indent + 'timestamp: Just now\n'
253
 
        return ''.join(lines)
254
 
 
 
221
        sio = StringIO()
 
222
        lf = LongLogFormatter(to_file=sio)
 
223
        show_log(b, lf)
 
224
        self.assertEquals(sio.getvalue(), """\
 
225
------------------------------------------------------------
 
226
revno: 3
 
227
committer: Joe Foo <joe@foo.com>
 
228
branch nick: test
 
229
timestamp: Mon 2005-11-21 09:32:56 -0600
 
230
message:
 
231
  single line with trailing newline
 
232
------------------------------------------------------------
 
233
revno: 2
 
234
committer: Joe Foo <joe@foo.com>
 
235
branch nick: test
 
236
timestamp: Mon 2005-11-21 09:27:22 -0600
 
237
message:
 
238
  multiline
 
239
  log
 
240
  message
 
241
------------------------------------------------------------
 
242
revno: 1
 
243
committer: Joe Foo <joe@foo.com>
 
244
branch nick: test
 
245
timestamp: Mon 2005-11-21 09:24:15 -0600
 
246
message:
 
247
  simple log message
 
248
""")
 
249
        
255
250
    def test_verbose_log(self):
256
251
        """Verbose log includes changed files
257
252
        
285
280
  a
286
281
''')
287
282
 
288
 
    def test_merges_are_indented_by_level(self):
289
 
        wt = self.make_branch_and_tree('parent')
290
 
        wt.commit('first post')
291
 
        self.run_bzr('branch', 'parent', 'child')
292
 
        self.run_bzr('commit', '-m', 'branch 1', '--unchanged', 'child')
293
 
        self.run_bzr('branch', 'child', 'smallerchild')
294
 
        self.run_bzr('commit', '-m', 'branch 2', '--unchanged', 'smallerchild')
295
 
        os.chdir('child')
296
 
        self.run_bzr('merge', '../smallerchild')
297
 
        self.run_bzr('commit', '-m', 'merge branch 2')
298
 
        os.chdir('../parent')
299
 
        self.run_bzr('merge', '../child')
300
 
        wt.commit('merge branch 1')
301
 
        b = wt.branch
302
 
        sio = StringIO()
303
 
        lf = LongLogFormatter(to_file=sio)
304
 
        show_log(b, lf, verbose=True)
305
 
        log = self.normalize_log(sio.getvalue())
306
 
        self.assertEqualDiff("""\
307
 
------------------------------------------------------------
308
 
revno: 2
309
 
committer: Lorem Ipsum <test@example.com>
310
 
branch nick: parent
311
 
timestamp: Just now
312
 
message:
313
 
  merge branch 1
314
 
    ------------------------------------------------------------
315
 
    revno: 1.1.2
316
 
    committer: Lorem Ipsum <test@example.com>
317
 
    branch nick: child
318
 
    timestamp: Just now
319
 
    message:
320
 
      merge branch 2
321
 
        ------------------------------------------------------------
322
 
        revno: 1.1.1.1.1
323
 
        committer: Lorem Ipsum <test@example.com>
324
 
        branch nick: smallerchild
325
 
        timestamp: Just now
326
 
        message:
327
 
          branch 2
328
 
    ------------------------------------------------------------
329
 
    revno: 1.1.1
330
 
    committer: Lorem Ipsum <test@example.com>
331
 
    branch nick: child
332
 
    timestamp: Just now
333
 
    message:
334
 
      branch 1
335
 
------------------------------------------------------------
336
 
revno: 1
337
 
committer: Lorem Ipsum <test@example.com>
338
 
branch nick: parent
339
 
timestamp: Just now
340
 
message:
341
 
  first post
342
 
""", log)
343
 
 
344
 
    def test_verbose_merge_revisions_contain_deltas(self):
345
 
        wt = self.make_branch_and_tree('parent')
346
 
        self.build_tree(['parent/f1', 'parent/f2'])
347
 
        wt.add(['f1','f2'])
348
 
        wt.commit('first post')
349
 
        self.run_bzr('branch', 'parent', 'child')
350
 
        os.unlink('child/f1')
351
 
        print >> file('child/f2', 'wb'), 'hello'
352
 
        self.run_bzr('commit', '-m', 'removed f1 and modified f2', 'child')
353
 
        os.chdir('parent')
354
 
        self.run_bzr('merge', '../child')
355
 
        wt.commit('merge branch 1')
356
 
        b = wt.branch
357
 
        sio = StringIO()
358
 
        lf = LongLogFormatter(to_file=sio)
359
 
        show_log(b, lf, verbose=True)
360
 
        log = self.normalize_log(sio.getvalue())
361
 
        self.assertEqualDiff("""\
362
 
------------------------------------------------------------
363
 
revno: 2
364
 
committer: Lorem Ipsum <test@example.com>
365
 
branch nick: parent
366
 
timestamp: Just now
367
 
message:
368
 
  merge branch 1
369
 
removed:
370
 
  f1
371
 
modified:
372
 
  f2
373
 
    ------------------------------------------------------------
374
 
    revno: 1.1.1
375
 
    committer: Lorem Ipsum <test@example.com>
376
 
    branch nick: child
377
 
    timestamp: Just now
378
 
    message:
379
 
      removed f1 and modified f2
380
 
    removed:
381
 
      f1
382
 
    modified:
383
 
      f2
384
 
------------------------------------------------------------
385
 
revno: 1
386
 
committer: Lorem Ipsum <test@example.com>
387
 
branch nick: parent
388
 
timestamp: Just now
389
 
message:
390
 
  first post
391
 
added:
392
 
  f1
393
 
  f2
394
 
""", log)
395
 
 
396
 
    def test_trailing_newlines(self):
397
 
        wt = self.make_branch_and_tree('.')
398
 
        b = make_commits_with_trailing_newlines(wt)
399
 
        sio = StringIO()
400
 
        lf = LongLogFormatter(to_file=sio)
401
 
        show_log(b, lf)
402
 
        self.assertEqualDiff(sio.getvalue(), """\
403
 
------------------------------------------------------------
404
 
revno: 3
405
 
committer: Joe Foo <joe@foo.com>
406
 
branch nick: test
407
 
timestamp: Mon 2005-11-21 09:32:56 -0600
408
 
message:
409
 
  single line with trailing newline
410
 
------------------------------------------------------------
411
 
revno: 2
412
 
committer: Joe Foo <joe@foo.com>
413
 
branch nick: test
414
 
timestamp: Mon 2005-11-21 09:27:22 -0600
415
 
message:
416
 
  multiline
417
 
  log
418
 
  message
419
 
------------------------------------------------------------
420
 
revno: 1
421
 
committer: Joe Foo <joe@foo.com>
422
 
branch nick: test
423
 
timestamp: Mon 2005-11-21 09:24:15 -0600
424
 
message:
425
 
  simple log message
426
 
""")
427
 
 
428
 
 
429
 
class TestLineLogFormatter(TestCaseWithTransport):
430
 
 
431
283
    def test_line_log(self):
432
284
        """Line log should show revno
433
285
        
449
301
        logfile.seek(0)
450
302
        log_contents = logfile.read()
451
303
        self.assertEqualDiff(log_contents, '1: Line-Log-Formatte... 2005-11-23 add a\n')
452
 
 
453
 
    def test_short_log_with_merges(self):
454
 
        wt = self.make_branch_and_memory_tree('.')
455
 
        wt.lock_write()
456
 
        try:
457
 
            wt.add('')
458
 
            wt.commit('rev-1', rev_id='rev-1',
459
 
                      timestamp=1132586655, timezone=36000,
460
 
                      committer='Joe Foo <joe@foo.com>')
461
 
            wt.commit('rev-merged', rev_id='rev-2a',
462
 
                      timestamp=1132586700, timezone=36000,
463
 
                      committer='Joe Foo <joe@foo.com>')
464
 
            wt.set_parent_ids(['rev-1', 'rev-2a'])
465
 
            wt.branch.set_last_revision_info(1, 'rev-1')
466
 
            wt.commit('rev-2', rev_id='rev-2b',
467
 
                      timestamp=1132586800, timezone=36000,
468
 
                      committer='Joe Foo <joe@foo.com>')
469
 
            logfile = StringIO()
470
 
            formatter = ShortLogFormatter(to_file=logfile)
471
 
            show_log(wt.branch, formatter)
472
 
            logfile.flush()
473
 
            self.assertEqualDiff("""\
474
 
    2 Joe Foo\t2005-11-22 [merge]
475
 
      rev-2
476
 
 
477
 
    1 Joe Foo\t2005-11-22
478
 
      rev-1
479
 
 
480
 
""", logfile.getvalue())
481
 
        finally:
482
 
            wt.unlock()
483
 
 
484
 
    def test_trailing_newlines(self):
485
 
        wt = self.make_branch_and_tree('.')
486
 
        b = make_commits_with_trailing_newlines(wt)
487
 
        sio = StringIO()
488
 
        lf = LineLogFormatter(to_file=sio)
489
 
        show_log(b, lf)
490
 
        self.assertEqualDiff(sio.getvalue(), """\
491
 
3: Joe Foo 2005-11-21 single line with trailing newline
492
 
2: Joe Foo 2005-11-21 multiline
493
 
1: Joe Foo 2005-11-21 simple log message
494
 
""")
495
 
 
496
 
 
497
 
class TestGetViewRevisions(TestCaseWithTransport):
498
 
 
499
 
    def make_tree_with_commits(self):
500
 
        """Create a tree with well-known revision ids"""
501
 
        wt = self.make_branch_and_tree('tree1')
502
 
        wt.commit('commit one', rev_id='1')
503
 
        wt.commit('commit two', rev_id='2')
504
 
        wt.commit('commit three', rev_id='3')
505
 
        mainline_revs = [None, '1', '2', '3']
506
 
        rev_nos = {'1': 1, '2': 2, '3': 3}
507
 
        return mainline_revs, rev_nos, wt
508
 
 
509
 
    def make_tree_with_merges(self):
510
 
        """Create a tree with well-known revision ids and a merge"""
511
 
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
512
 
        tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
513
 
        tree2.commit('four-a', rev_id='4a')
514
 
        wt.merge_from_branch(tree2.branch)
515
 
        wt.commit('four-b', rev_id='4b')
516
 
        mainline_revs.append('4b')
517
 
        rev_nos['4b'] = 4
518
 
        # 4a: 3.1.1
519
 
        return mainline_revs, rev_nos, wt
520
 
 
521
 
    def make_tree_with_many_merges(self):
522
 
        """Create a tree with well-known revision ids"""
523
 
        wt = self.make_branch_and_tree('tree1')
524
 
        wt.commit('commit one', rev_id='1')
525
 
        wt.commit('commit two', rev_id='2')
526
 
        tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
527
 
        tree3.commit('commit three a', rev_id='3a')
528
 
        tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
529
 
        tree2.merge_from_branch(tree3.branch)
530
 
        tree2.commit('commit three b', rev_id='3b')
531
 
        wt.merge_from_branch(tree2.branch)
532
 
        wt.commit('commit three c', rev_id='3c')
533
 
        tree2.commit('four-a', rev_id='4a')
534
 
        wt.merge_from_branch(tree2.branch)
535
 
        wt.commit('four-b', rev_id='4b')
536
 
        mainline_revs = [None, '1', '2', '3c', '4b']
537
 
        rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
538
 
        full_rev_nos_for_reference = {
539
 
            '1': '1',
540
 
            '2': '2',
541
 
            '3a': '2.2.1', #first commit tree 3
542
 
            '3b': '2.1.1', # first commit tree 2
543
 
            '3c': '3', #merges 3b to main
544
 
            '4a': '2.1.2', # second commit tree 2
545
 
            '4b': '4', # merges 4a to main
546
 
            }
547
 
        return mainline_revs, rev_nos, wt
548
 
 
549
 
    def test_get_view_revisions_forward(self):
550
 
        """Test the get_view_revisions method"""
551
 
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
552
 
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
553
 
                                            'forward'))
554
 
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
555
 
            revisions)
556
 
        revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
557
 
                                             'forward', include_merges=False))
558
 
        self.assertEqual(revisions, revisions2)
559
 
 
560
 
    def test_get_view_revisions_reverse(self):
561
 
        """Test the get_view_revisions with reverse"""
562
 
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
563
 
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
564
 
                                            'reverse'))
565
 
        self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
566
 
            revisions)
567
 
        revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
568
 
                                             'reverse', include_merges=False))
569
 
        self.assertEqual(revisions, revisions2)
570
 
 
571
 
    def test_get_view_revisions_merge(self):
572
 
        """Test get_view_revisions when there are merges"""
573
 
        mainline_revs, rev_nos, wt = self.make_tree_with_merges()
574
 
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
575
 
                                            'forward'))
576
 
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
577
 
            ('4b', '4', 0), ('4a', '3.1.1', 1)],
578
 
            revisions)
579
 
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
580
 
                                             'forward', include_merges=False))
581
 
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
582
 
            ('4b', '4', 0)],
583
 
            revisions)
584
 
 
585
 
    def test_get_view_revisions_merge_reverse(self):
586
 
        """Test get_view_revisions in reverse when there are merges"""
587
 
        mainline_revs, rev_nos, wt = self.make_tree_with_merges()
588
 
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
589
 
                                            'reverse'))
590
 
        self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
591
 
            ('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
592
 
            revisions)
593
 
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
594
 
                                             'reverse', include_merges=False))
595
 
        self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
596
 
            ('1', '1', 0)],
597
 
            revisions)
598
 
 
599
 
    def test_get_view_revisions_merge2(self):
600
 
        """Test get_view_revisions when there are merges"""
601
 
        mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
602
 
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
603
 
                                            'forward'))
604
 
        expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
605
 
            ('3a', '2.2.1', 1), ('3b', '2.1.1', 1), ('4b', '4', 0),
606
 
            ('4a', '2.1.2', 1)]
607
 
        self.assertEqual(expected, revisions)
608
 
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
609
 
                                             'forward', include_merges=False))
610
 
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
611
 
            ('4b', '4', 0)],
612
 
            revisions)
613
 
 
614
 
 
615
 
class TestGetRevisionsTouchingFileID(TestCaseWithTransport):
616
 
 
617
 
    def create_tree_with_single_merge(self):
618
 
        """Create a branch with a moderate layout.
619
 
 
620
 
        The revision graph looks like:
621
 
 
622
 
           A
623
 
           |\
624
 
           B C
625
 
           |/
626
 
           D
627
 
 
628
 
        In this graph, A introduced files f1 and f2 and f3.
629
 
        B modifies f1 and f3, and C modifies f2 and f3.
630
 
        D merges the changes from B and C and resolves the conflict for f3.
631
 
        """
632
 
        # TODO: jam 20070218 This seems like it could really be done
633
 
        #       with make_branch_and_memory_tree() if we could just
634
 
        #       create the content of those files.
635
 
        # TODO: jam 20070218 Another alternative is that we would really
636
 
        #       like to only create this tree 1 time for all tests that
637
 
        #       use it. Since 'log' only uses the tree in a readonly
638
 
        #       fashion, it seems a shame to regenerate an identical
639
 
        #       tree for each test.
640
 
        tree = self.make_branch_and_tree('tree')
641
 
        tree.lock_write()
642
 
        self.addCleanup(tree.unlock)
643
 
 
644
 
        self.build_tree_contents([('tree/f1', 'A\n'),
645
 
                                  ('tree/f2', 'A\n'),
646
 
                                  ('tree/f3', 'A\n'),
647
 
                                 ])
648
 
        tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
649
 
        tree.commit('A', rev_id='A')
650
 
 
651
 
        self.build_tree_contents([('tree/f2', 'A\nC\n'),
652
 
                                  ('tree/f3', 'A\nC\n'),
653
 
                                 ])
654
 
        tree.commit('C', rev_id='C')
655
 
        # Revert back to A to build the other history.
656
 
        tree.set_last_revision('A')
657
 
        tree.branch.set_last_revision_info(1, 'A')
658
 
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
659
 
                                  ('tree/f2', 'A\n'),
660
 
                                  ('tree/f3', 'A\nB\n'),
661
 
                                 ])
662
 
        tree.commit('B', rev_id='B')
663
 
        tree.set_parent_ids(['B', 'C'])
664
 
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
665
 
                                  ('tree/f2', 'A\nC\n'),
666
 
                                  ('tree/f3', 'A\nB\nC\n'),
667
 
                                 ])
668
 
        tree.commit('D', rev_id='D')
669
 
 
670
 
        # Switch to a read lock for this tree.
671
 
        # We still have addCleanup(unlock)
672
 
        tree.unlock()
673
 
        tree.lock_read()
674
 
        return tree
675
 
 
676
 
    def test_tree_with_single_merge(self):
677
 
        """Make sure the tree layout is correct."""
678
 
        tree = self.create_tree_with_single_merge()
679
 
        rev_A_tree = tree.branch.repository.revision_tree('A')
680
 
        rev_B_tree = tree.branch.repository.revision_tree('B')
681
 
 
682
 
        f1_changed = (u'f1', 'f1-id', 'file', True, False)
683
 
        f2_changed = (u'f2', 'f2-id', 'file', True, False)
684
 
        f3_changed = (u'f3', 'f3-id', 'file', True, False)
685
 
 
686
 
        delta = rev_B_tree.changes_from(rev_A_tree)
687
 
        self.assertEqual([f1_changed, f3_changed], delta.modified)
688
 
        self.assertEqual([], delta.renamed)
689
 
        self.assertEqual([], delta.added)
690
 
        self.assertEqual([], delta.removed)
691
 
 
692
 
        rev_C_tree = tree.branch.repository.revision_tree('C')
693
 
        delta = rev_C_tree.changes_from(rev_A_tree)
694
 
        self.assertEqual([f2_changed, f3_changed], delta.modified)
695
 
        self.assertEqual([], delta.renamed)
696
 
        self.assertEqual([], delta.added)
697
 
        self.assertEqual([], delta.removed)
698
 
 
699
 
        rev_D_tree = tree.branch.repository.revision_tree('D')
700
 
        delta = rev_D_tree.changes_from(rev_B_tree)
701
 
        self.assertEqual([f2_changed, f3_changed], delta.modified)
702
 
        self.assertEqual([], delta.renamed)
703
 
        self.assertEqual([], delta.added)
704
 
        self.assertEqual([], delta.removed)
705
 
 
706
 
        delta = rev_D_tree.changes_from(rev_C_tree)
707
 
        self.assertEqual([f1_changed, f3_changed], delta.modified)
708
 
        self.assertEqual([], delta.renamed)
709
 
        self.assertEqual([], delta.added)
710
 
        self.assertEqual([], delta.removed)
711
 
 
712
 
    def assertAllRevisionsForFileID(self, tree, file_id, revisions):
713
 
        """Make sure _filter_revisions_touching_file_id returns the right values.
714
 
 
715
 
        Get the return value from _filter_revisions_touching_file_id and make
716
 
        sure they are correct.
717
 
        """
718
 
        # The api for _get_revisions_touching_file_id is a little crazy,
719
 
        # So we do the setup here.
720
 
        mainline = tree.branch.revision_history()
721
 
        mainline.insert(0, None)
722
 
        revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
723
 
        view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
724
 
                                                'reverse', True)
725
 
        actual_revs = log._filter_revisions_touching_file_id(
726
 
                            tree.branch, 
727
 
                            file_id,
728
 
                            mainline,
729
 
                            list(view_revs_iter))
730
 
        self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
731
 
 
732
 
    def test_file_id_f1(self):
733
 
        tree = self.create_tree_with_single_merge()
734
 
        # f1 should be marked as modified by revisions A and B
735
 
        self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
736
 
 
737
 
    def test_file_id_f2(self):
738
 
        tree = self.create_tree_with_single_merge()
739
 
        # f2 should be marked as modified by revisions A, C, and D
740
 
        # because D merged the changes from C.
741
 
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
742
 
 
743
 
    def test_file_id_f3(self):
744
 
        tree = self.create_tree_with_single_merge()
745
 
        # f3 should be marked as modified by revisions A, B, C, and D
746
 
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])