/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

  • Committer: Martin Pool
  • Date: 2007-07-05 00:12:25 UTC
  • mfrom: (2586 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2588.
  • Revision ID: mbp@sourcefrog.net-20070705001225-sgr71qfxpj9ghc83
merge trunk and fix run_bzr call in merge_directive tests

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
import os
 
18
from cStringIO import StringIO
 
19
 
 
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)
 
29
from bzrlib.branch import Branch
 
30
from bzrlib.errors import InvalidRevisionNumber
 
31
 
 
32
 
 
33
class LogCatcher(LogFormatter):
 
34
    """Pull log messages into list rather than displaying them.
 
35
 
 
36
    For ease of testing we save log messages here rather than actually
 
37
    formatting them, so that we can precisely check the result without
 
38
    being too dependent on the exact formatting.
 
39
 
 
40
    We should also test the LogFormatter.
 
41
    """
 
42
 
 
43
    supports_delta = True
 
44
 
 
45
    def __init__(self):
 
46
        super(LogCatcher, self).__init__(to_file=None)
 
47
        self.logs = []
 
48
 
 
49
    def log_revision(self, revision):
 
50
        self.logs.append(revision)
 
51
 
 
52
 
 
53
class TestShowLog(TestCaseWithTransport):
 
54
 
 
55
    def checkDelta(self, delta, **kw):
 
56
        """Check the filenames touched by a delta are as expected."""
 
57
        for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
 
58
            expected = kw.get(n, [])
 
59
            # strip out only the path components
 
60
            got = [x[0] for x in getattr(delta, n)]
 
61
            self.assertEquals(expected, got)
 
62
 
 
63
    def test_cur_revno(self):
 
64
        wt = self.make_branch_and_tree('.')
 
65
        b = wt.branch
 
66
 
 
67
        lf = LogCatcher()
 
68
        wt.commit('empty commit')
 
69
        show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
 
70
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
71
                          start_revision=2, end_revision=1) 
 
72
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
73
                          start_revision=1, end_revision=2) 
 
74
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
75
                          start_revision=0, end_revision=2) 
 
76
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
77
                          start_revision=1, end_revision=0) 
 
78
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
79
                          start_revision=-1, end_revision=1) 
 
80
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
81
                          start_revision=1, end_revision=-1) 
 
82
 
 
83
    def test_simple_log(self):
 
84
        eq = self.assertEquals
 
85
        
 
86
        wt = self.make_branch_and_tree('.')
 
87
        b = wt.branch
 
88
 
 
89
        lf = LogCatcher()
 
90
        show_log(b, lf)
 
91
        # no entries yet
 
92
        eq(lf.logs, [])
 
93
 
 
94
        wt.commit('empty commit')
 
95
        lf = LogCatcher()
 
96
        show_log(b, lf, verbose=True)
 
97
        eq(len(lf.logs), 1)
 
98
        eq(lf.logs[0].revno, '1')
 
99
        eq(lf.logs[0].rev.message, 'empty commit')
 
100
        d = lf.logs[0].delta
 
101
        self.log('log delta: %r' % d)
 
102
        self.checkDelta(d)
 
103
 
 
104
        self.build_tree(['hello'])
 
105
        wt.add('hello')
 
106
        wt.commit('add one file')
 
107
 
 
108
        lf = StringIO()
 
109
        # log using regular thing
 
110
        show_log(b, LongLogFormatter(lf))
 
111
        lf.seek(0)
 
112
        for l in lf.readlines():
 
113
            self.log(l)
 
114
 
 
115
        # get log as data structure
 
116
        lf = LogCatcher()
 
117
        show_log(b, lf, verbose=True)
 
118
        eq(len(lf.logs), 2)
 
119
        self.log('log entries:')
 
120
        for logentry in lf.logs:
 
121
            self.log('%4s %s' % (logentry.revno, logentry.rev.message))
 
122
        
 
123
        # first one is most recent
 
124
        logentry = lf.logs[0]
 
125
        eq(logentry.revno, '2')
 
126
        eq(logentry.rev.message, 'add one file')
 
127
        d = logentry.delta
 
128
        self.log('log 2 delta: %r' % d)
 
129
        self.checkDelta(d, added=['hello'])
 
130
        
 
131
        # commit a log message with control characters
 
132
        msg = "All 8-bit chars: " +  ''.join([unichr(x) for x in range(256)])
 
133
        self.log("original commit message: %r", msg)
 
134
        wt.commit(msg)
 
135
        lf = LogCatcher()
 
136
        show_log(b, lf, verbose=True)
 
137
        committed_msg = lf.logs[0].rev.message
 
138
        self.log("escaped commit message: %r", committed_msg)
 
139
        self.assert_(msg != committed_msg)
 
140
        self.assert_(len(committed_msg) > len(msg))
 
141
 
 
142
        # Check that log message with only XML-valid characters isn't
 
143
        # escaped.  As ElementTree apparently does some kind of
 
144
        # newline conversion, neither LF (\x0A) nor CR (\x0D) are
 
145
        # included in the test commit message, even though they are
 
146
        # valid XML 1.0 characters.
 
147
        msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
 
148
        self.log("original commit message: %r", msg)
 
149
        wt.commit(msg)
 
150
        lf = LogCatcher()
 
151
        show_log(b, lf, verbose=True)
 
152
        committed_msg = lf.logs[0].rev.message
 
153
        self.log("escaped commit message: %r", committed_msg)
 
154
        self.assert_(msg == committed_msg)
 
155
 
 
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
    def test_trailing_newlines(self):
 
220
        wt = self.make_branch_and_tree('.')
 
221
        b = make_commits_with_trailing_newlines(wt)
 
222
        sio = StringIO()
 
223
        lf = ShortLogFormatter(to_file=sio)
 
224
        show_log(b, lf)
 
225
        self.assertEquals(sio.getvalue(), """\
 
226
    3 Joe Foo\t2005-11-21
 
227
      single line with trailing newline
 
228
 
 
229
    2 Joe Foo\t2005-11-21
 
230
      multiline
 
231
      log
 
232
      message
 
233
 
 
234
    1 Joe Foo\t2005-11-21
 
235
      simple log message
 
236
 
 
237
""")
 
238
 
 
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
 
 
255
    def test_verbose_log(self):
 
256
        """Verbose log includes changed files
 
257
        
 
258
        bug #4676
 
259
        """
 
260
        wt = self.make_branch_and_tree('.')
 
261
        b = wt.branch
 
262
        self.build_tree(['a'])
 
263
        wt.add('a')
 
264
        # XXX: why does a longer nick show up?
 
265
        b.nick = 'test_verbose_log'
 
266
        wt.commit(message='add a', 
 
267
                  timestamp=1132711707, 
 
268
                  timezone=36000,
 
269
                  committer='Lorem Ipsum <test@example.com>')
 
270
        logfile = file('out.tmp', 'w+')
 
271
        formatter = LongLogFormatter(to_file=logfile)
 
272
        show_log(b, formatter, verbose=True)
 
273
        logfile.flush()
 
274
        logfile.seek(0)
 
275
        log_contents = logfile.read()
 
276
        self.assertEqualDiff(log_contents, '''\
 
277
------------------------------------------------------------
 
278
revno: 1
 
279
committer: Lorem Ipsum <test@example.com>
 
280
branch nick: test_verbose_log
 
281
timestamp: Wed 2005-11-23 12:08:27 +1000
 
282
message:
 
283
  add a
 
284
added:
 
285
  a
 
286
''')
 
287
 
 
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
    def test_line_log(self):
 
432
        """Line log should show revno
 
433
        
 
434
        bug #5162
 
435
        """
 
436
        wt = self.make_branch_and_tree('.')
 
437
        b = wt.branch
 
438
        self.build_tree(['a'])
 
439
        wt.add('a')
 
440
        b.nick = 'test-line-log'
 
441
        wt.commit(message='add a', 
 
442
                  timestamp=1132711707, 
 
443
                  timezone=36000,
 
444
                  committer='Line-Log-Formatter Tester <test@line.log>')
 
445
        logfile = file('out.tmp', 'w+')
 
446
        formatter = LineLogFormatter(to_file=logfile)
 
447
        show_log(b, formatter)
 
448
        logfile.flush()
 
449
        logfile.seek(0)
 
450
        log_contents = logfile.read()
 
451
        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'])