~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: 2007-08-01 17:14:51 UTC
  • mfrom: (2662.1.1 bzr.easy_install)
  • Revision ID: pqm@pqm.ubuntu.com-20070801171451-en3tds1hzlru2j83
allow ``easy_install bzr`` runs without fatal errors. (#125521, bialix,
 r=mbp)

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',
 
168
            'child'])
 
169
        os.chdir('parent')
 
170
        self.run_bzr('merge ../child')
 
171
        wt.commit('merge child branch')
 
172
        os.chdir('..')
 
173
        b = wt.branch
 
174
        lf = LogCatcher()
 
175
        lf.supports_merge_revisions = True
 
176
        show_log(b, lf, verbose=True)
 
177
        eq(len(lf.logs),3)
 
178
        logentry = lf.logs[0]
 
179
        eq(logentry.revno, '2')
 
180
        eq(logentry.rev.message, 'merge child branch')
 
181
        d = logentry.delta
 
182
        self.checkDelta(d, removed=['file1'], modified=['file2'])
 
183
        logentry = lf.logs[1]
 
184
        eq(logentry.revno, '1.1.1')
 
185
        eq(logentry.rev.message, 'remove file1 and modify file2')
 
186
        d = logentry.delta
 
187
        self.checkDelta(d, removed=['file1'], modified=['file2'])
 
188
        logentry = lf.logs[2]
 
189
        eq(logentry.revno, '1')
 
190
        eq(logentry.rev.message, 'add file1 and file2')
 
191
        d = logentry.delta
 
192
        self.checkDelta(d, added=['file1', 'file2'])
 
193
 
 
194
 
 
195
def make_commits_with_trailing_newlines(wt):
 
196
    """Helper method for LogFormatter tests"""    
 
197
    b = wt.branch
 
198
    b.nick='test'
 
199
    open('a', 'wb').write('hello moto\n')
 
200
    wt.add('a')
 
201
    wt.commit('simple log message', rev_id='a1'
 
202
            , timestamp=1132586655.459960938, timezone=-6*3600
 
203
            , committer='Joe Foo <joe@foo.com>')
 
204
    open('b', 'wb').write('goodbye\n')
 
205
    wt.add('b')
 
206
    wt.commit('multiline\nlog\nmessage\n', rev_id='a2'
 
207
            , timestamp=1132586842.411175966, timezone=-6*3600
 
208
            , committer='Joe Foo <joe@foo.com>')
 
209
 
 
210
    open('c', 'wb').write('just another manic monday\n')
 
211
    wt.add('c')
 
212
    wt.commit('single line with trailing newline\n', rev_id='a3'
 
213
            , timestamp=1132587176.835228920, timezone=-6*3600
 
214
            , committer = 'Joe Foo <joe@foo.com>')
 
215
    return b
 
216
 
 
217
 
 
218
class TestShortLogFormatter(TestCaseWithTransport):
 
219
 
 
220
    def test_trailing_newlines(self):
 
221
        wt = self.make_branch_and_tree('.')
 
222
        b = make_commits_with_trailing_newlines(wt)
 
223
        sio = StringIO()
 
224
        lf = ShortLogFormatter(to_file=sio)
 
225
        show_log(b, lf)
 
226
        self.assertEquals(sio.getvalue(), """\
 
227
    3 Joe Foo\t2005-11-21
 
228
      single line with trailing newline
 
229
 
 
230
    2 Joe Foo\t2005-11-21
 
231
      multiline
 
232
      log
 
233
      message
 
234
 
 
235
    1 Joe Foo\t2005-11-21
 
236
      simple log message
 
237
 
 
238
""")
 
239
 
 
240
 
 
241
class TestLongLogFormatter(TestCaseWithTransport):
 
242
 
 
243
    def normalize_log(self,log):
 
244
        """Replaces the variable lines of logs with fixed lines"""
 
245
        committer = 'committer: Lorem Ipsum <test@example.com>'
 
246
        lines = log.splitlines(True)
 
247
        for idx,line in enumerate(lines):
 
248
            stripped_line = line.lstrip()
 
249
            indent = ' ' * (len(line) - len(stripped_line))
 
250
            if stripped_line.startswith('committer:'):
 
251
                lines[idx] = indent + committer + '\n'
 
252
            if stripped_line.startswith('timestamp:'):
 
253
                lines[idx] = indent + 'timestamp: Just now\n'
 
254
        return ''.join(lines)
 
255
 
 
256
    def test_verbose_log(self):
 
257
        """Verbose log includes changed files
 
258
        
 
259
        bug #4676
 
260
        """
 
261
        wt = self.make_branch_and_tree('.')
 
262
        b = wt.branch
 
263
        self.build_tree(['a'])
 
264
        wt.add('a')
 
265
        # XXX: why does a longer nick show up?
 
266
        b.nick = 'test_verbose_log'
 
267
        wt.commit(message='add a', 
 
268
                  timestamp=1132711707, 
 
269
                  timezone=36000,
 
270
                  committer='Lorem Ipsum <test@example.com>')
 
271
        logfile = file('out.tmp', 'w+')
 
272
        formatter = LongLogFormatter(to_file=logfile)
 
273
        show_log(b, formatter, verbose=True)
 
274
        logfile.flush()
 
275
        logfile.seek(0)
 
276
        log_contents = logfile.read()
 
277
        self.assertEqualDiff(log_contents, '''\
 
278
------------------------------------------------------------
 
279
revno: 1
 
280
committer: Lorem Ipsum <test@example.com>
 
281
branch nick: test_verbose_log
 
282
timestamp: Wed 2005-11-23 12:08:27 +1000
 
283
message:
 
284
  add a
 
285
added:
 
286
  a
 
287
''')
 
288
 
 
289
    def test_merges_are_indented_by_level(self):
 
290
        wt = self.make_branch_and_tree('parent')
 
291
        wt.commit('first post')
 
292
        self.run_bzr('branch parent child')
 
293
        self.run_bzr(['commit', '-m', 'branch 1', '--unchanged', 'child'])
 
294
        self.run_bzr('branch child smallerchild')
 
295
        self.run_bzr(['commit', '-m', 'branch 2', '--unchanged',
 
296
            'smallerchild'])
 
297
        os.chdir('child')
 
298
        self.run_bzr('merge ../smallerchild')
 
299
        self.run_bzr(['commit', '-m', 'merge branch 2'])
 
300
        os.chdir('../parent')
 
301
        self.run_bzr('merge ../child')
 
302
        wt.commit('merge branch 1')
 
303
        b = wt.branch
 
304
        sio = StringIO()
 
305
        lf = LongLogFormatter(to_file=sio)
 
306
        show_log(b, lf, verbose=True)
 
307
        log = self.normalize_log(sio.getvalue())
 
308
        self.assertEqualDiff("""\
 
309
------------------------------------------------------------
 
310
revno: 2
 
311
committer: Lorem Ipsum <test@example.com>
 
312
branch nick: parent
 
313
timestamp: Just now
 
314
message:
 
315
  merge branch 1
 
316
    ------------------------------------------------------------
 
317
    revno: 1.1.2
 
318
    committer: Lorem Ipsum <test@example.com>
 
319
    branch nick: child
 
320
    timestamp: Just now
 
321
    message:
 
322
      merge branch 2
 
323
        ------------------------------------------------------------
 
324
        revno: 1.1.1.1.1
 
325
        committer: Lorem Ipsum <test@example.com>
 
326
        branch nick: smallerchild
 
327
        timestamp: Just now
 
328
        message:
 
329
          branch 2
 
330
    ------------------------------------------------------------
 
331
    revno: 1.1.1
 
332
    committer: Lorem Ipsum <test@example.com>
 
333
    branch nick: child
 
334
    timestamp: Just now
 
335
    message:
 
336
      branch 1
 
337
------------------------------------------------------------
 
338
revno: 1
 
339
committer: Lorem Ipsum <test@example.com>
 
340
branch nick: parent
 
341
timestamp: Just now
 
342
message:
 
343
  first post
 
344
""", log)
 
345
 
 
346
    def test_verbose_merge_revisions_contain_deltas(self):
 
347
        wt = self.make_branch_and_tree('parent')
 
348
        self.build_tree(['parent/f1', 'parent/f2'])
 
349
        wt.add(['f1','f2'])
 
350
        wt.commit('first post')
 
351
        self.run_bzr('branch parent child')
 
352
        os.unlink('child/f1')
 
353
        print >> file('child/f2', 'wb'), 'hello'
 
354
        self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
 
355
            'child'])
 
356
        os.chdir('parent')
 
357
        self.run_bzr('merge ../child')
 
358
        wt.commit('merge branch 1')
 
359
        b = wt.branch
 
360
        sio = StringIO()
 
361
        lf = LongLogFormatter(to_file=sio)
 
362
        show_log(b, lf, verbose=True)
 
363
        log = self.normalize_log(sio.getvalue())
 
364
        self.assertEqualDiff("""\
 
365
------------------------------------------------------------
 
366
revno: 2
 
367
committer: Lorem Ipsum <test@example.com>
 
368
branch nick: parent
 
369
timestamp: Just now
 
370
message:
 
371
  merge branch 1
 
372
removed:
 
373
  f1
 
374
modified:
 
375
  f2
 
376
    ------------------------------------------------------------
 
377
    revno: 1.1.1
 
378
    committer: Lorem Ipsum <test@example.com>
 
379
    branch nick: child
 
380
    timestamp: Just now
 
381
    message:
 
382
      removed f1 and modified f2
 
383
    removed:
 
384
      f1
 
385
    modified:
 
386
      f2
 
387
------------------------------------------------------------
 
388
revno: 1
 
389
committer: Lorem Ipsum <test@example.com>
 
390
branch nick: parent
 
391
timestamp: Just now
 
392
message:
 
393
  first post
 
394
added:
 
395
  f1
 
396
  f2
 
397
""", log)
 
398
 
 
399
    def test_trailing_newlines(self):
 
400
        wt = self.make_branch_and_tree('.')
 
401
        b = make_commits_with_trailing_newlines(wt)
 
402
        sio = StringIO()
 
403
        lf = LongLogFormatter(to_file=sio)
 
404
        show_log(b, lf)
 
405
        self.assertEqualDiff(sio.getvalue(), """\
 
406
------------------------------------------------------------
 
407
revno: 3
 
408
committer: Joe Foo <joe@foo.com>
 
409
branch nick: test
 
410
timestamp: Mon 2005-11-21 09:32:56 -0600
 
411
message:
 
412
  single line with trailing newline
 
413
------------------------------------------------------------
 
414
revno: 2
 
415
committer: Joe Foo <joe@foo.com>
 
416
branch nick: test
 
417
timestamp: Mon 2005-11-21 09:27:22 -0600
 
418
message:
 
419
  multiline
 
420
  log
 
421
  message
 
422
------------------------------------------------------------
 
423
revno: 1
 
424
committer: Joe Foo <joe@foo.com>
 
425
branch nick: test
 
426
timestamp: Mon 2005-11-21 09:24:15 -0600
 
427
message:
 
428
  simple log message
 
429
""")
 
430
 
 
431
 
 
432
class TestLineLogFormatter(TestCaseWithTransport):
 
433
 
 
434
    def test_line_log(self):
 
435
        """Line log should show revno
 
436
        
 
437
        bug #5162
 
438
        """
 
439
        wt = self.make_branch_and_tree('.')
 
440
        b = wt.branch
 
441
        self.build_tree(['a'])
 
442
        wt.add('a')
 
443
        b.nick = 'test-line-log'
 
444
        wt.commit(message='add a', 
 
445
                  timestamp=1132711707, 
 
446
                  timezone=36000,
 
447
                  committer='Line-Log-Formatter Tester <test@line.log>')
 
448
        logfile = file('out.tmp', 'w+')
 
449
        formatter = LineLogFormatter(to_file=logfile)
 
450
        show_log(b, formatter)
 
451
        logfile.flush()
 
452
        logfile.seek(0)
 
453
        log_contents = logfile.read()
 
454
        self.assertEqualDiff(log_contents, '1: Line-Log-Formatte... 2005-11-23 add a\n')
 
455
 
 
456
    def test_short_log_with_merges(self):
 
457
        wt = self.make_branch_and_memory_tree('.')
 
458
        wt.lock_write()
 
459
        try:
 
460
            wt.add('')
 
461
            wt.commit('rev-1', rev_id='rev-1',
 
462
                      timestamp=1132586655, timezone=36000,
 
463
                      committer='Joe Foo <joe@foo.com>')
 
464
            wt.commit('rev-merged', rev_id='rev-2a',
 
465
                      timestamp=1132586700, timezone=36000,
 
466
                      committer='Joe Foo <joe@foo.com>')
 
467
            wt.set_parent_ids(['rev-1', 'rev-2a'])
 
468
            wt.branch.set_last_revision_info(1, 'rev-1')
 
469
            wt.commit('rev-2', rev_id='rev-2b',
 
470
                      timestamp=1132586800, timezone=36000,
 
471
                      committer='Joe Foo <joe@foo.com>')
 
472
            logfile = StringIO()
 
473
            formatter = ShortLogFormatter(to_file=logfile)
 
474
            show_log(wt.branch, formatter)
 
475
            logfile.flush()
 
476
            self.assertEqualDiff("""\
 
477
    2 Joe Foo\t2005-11-22 [merge]
 
478
      rev-2
 
479
 
 
480
    1 Joe Foo\t2005-11-22
 
481
      rev-1
 
482
 
 
483
""", logfile.getvalue())
 
484
        finally:
 
485
            wt.unlock()
 
486
 
 
487
    def test_trailing_newlines(self):
 
488
        wt = self.make_branch_and_tree('.')
 
489
        b = make_commits_with_trailing_newlines(wt)
 
490
        sio = StringIO()
 
491
        lf = LineLogFormatter(to_file=sio)
 
492
        show_log(b, lf)
 
493
        self.assertEqualDiff(sio.getvalue(), """\
 
494
3: Joe Foo 2005-11-21 single line with trailing newline
 
495
2: Joe Foo 2005-11-21 multiline
 
496
1: Joe Foo 2005-11-21 simple log message
 
497
""")
 
498
 
 
499
 
 
500
class TestGetViewRevisions(TestCaseWithTransport):
 
501
 
 
502
    def make_tree_with_commits(self):
 
503
        """Create a tree with well-known revision ids"""
 
504
        wt = self.make_branch_and_tree('tree1')
 
505
        wt.commit('commit one', rev_id='1')
 
506
        wt.commit('commit two', rev_id='2')
 
507
        wt.commit('commit three', rev_id='3')
 
508
        mainline_revs = [None, '1', '2', '3']
 
509
        rev_nos = {'1': 1, '2': 2, '3': 3}
 
510
        return mainline_revs, rev_nos, wt
 
511
 
 
512
    def make_tree_with_merges(self):
 
513
        """Create a tree with well-known revision ids and a merge"""
 
514
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
 
515
        tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
 
516
        tree2.commit('four-a', rev_id='4a')
 
517
        wt.merge_from_branch(tree2.branch)
 
518
        wt.commit('four-b', rev_id='4b')
 
519
        mainline_revs.append('4b')
 
520
        rev_nos['4b'] = 4
 
521
        # 4a: 3.1.1
 
522
        return mainline_revs, rev_nos, wt
 
523
 
 
524
    def make_tree_with_many_merges(self):
 
525
        """Create a tree with well-known revision ids"""
 
526
        wt = self.make_branch_and_tree('tree1')
 
527
        wt.commit('commit one', rev_id='1')
 
528
        wt.commit('commit two', rev_id='2')
 
529
        tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
 
530
        tree3.commit('commit three a', rev_id='3a')
 
531
        tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
 
532
        tree2.merge_from_branch(tree3.branch)
 
533
        tree2.commit('commit three b', rev_id='3b')
 
534
        wt.merge_from_branch(tree2.branch)
 
535
        wt.commit('commit three c', rev_id='3c')
 
536
        tree2.commit('four-a', rev_id='4a')
 
537
        wt.merge_from_branch(tree2.branch)
 
538
        wt.commit('four-b', rev_id='4b')
 
539
        mainline_revs = [None, '1', '2', '3c', '4b']
 
540
        rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
 
541
        full_rev_nos_for_reference = {
 
542
            '1': '1',
 
543
            '2': '2',
 
544
            '3a': '2.2.1', #first commit tree 3
 
545
            '3b': '2.1.1', # first commit tree 2
 
546
            '3c': '3', #merges 3b to main
 
547
            '4a': '2.1.2', # second commit tree 2
 
548
            '4b': '4', # merges 4a to main
 
549
            }
 
550
        return mainline_revs, rev_nos, wt
 
551
 
 
552
    def test_get_view_revisions_forward(self):
 
553
        """Test the get_view_revisions method"""
 
554
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
 
555
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
556
                                            'forward'))
 
557
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
 
558
            revisions)
 
559
        revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
560
                                             'forward', include_merges=False))
 
561
        self.assertEqual(revisions, revisions2)
 
562
 
 
563
    def test_get_view_revisions_reverse(self):
 
564
        """Test the get_view_revisions with reverse"""
 
565
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
 
566
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
567
                                            'reverse'))
 
568
        self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
 
569
            revisions)
 
570
        revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
571
                                             'reverse', include_merges=False))
 
572
        self.assertEqual(revisions, revisions2)
 
573
 
 
574
    def test_get_view_revisions_merge(self):
 
575
        """Test get_view_revisions when there are merges"""
 
576
        mainline_revs, rev_nos, wt = self.make_tree_with_merges()
 
577
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
578
                                            'forward'))
 
579
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
 
580
            ('4b', '4', 0), ('4a', '3.1.1', 1)],
 
581
            revisions)
 
582
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
583
                                             'forward', include_merges=False))
 
584
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
 
585
            ('4b', '4', 0)],
 
586
            revisions)
 
587
 
 
588
    def test_get_view_revisions_merge_reverse(self):
 
589
        """Test get_view_revisions in reverse when there are merges"""
 
590
        mainline_revs, rev_nos, wt = self.make_tree_with_merges()
 
591
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
592
                                            'reverse'))
 
593
        self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
 
594
            ('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
 
595
            revisions)
 
596
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
597
                                             'reverse', include_merges=False))
 
598
        self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
 
599
            ('1', '1', 0)],
 
600
            revisions)
 
601
 
 
602
    def test_get_view_revisions_merge2(self):
 
603
        """Test get_view_revisions when there are merges"""
 
604
        mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
 
605
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
606
                                            'forward'))
 
607
        expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
 
608
            ('3a', '2.2.1', 1), ('3b', '2.1.1', 1), ('4b', '4', 0),
 
609
            ('4a', '2.1.2', 1)]
 
610
        self.assertEqual(expected, revisions)
 
611
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
612
                                             'forward', include_merges=False))
 
613
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
 
614
            ('4b', '4', 0)],
 
615
            revisions)
 
616
 
 
617
 
 
618
class TestGetRevisionsTouchingFileID(TestCaseWithTransport):
 
619
 
 
620
    def create_tree_with_single_merge(self):
 
621
        """Create a branch with a moderate layout.
 
622
 
 
623
        The revision graph looks like:
 
624
 
 
625
           A
 
626
           |\
 
627
           B C
 
628
           |/
 
629
           D
 
630
 
 
631
        In this graph, A introduced files f1 and f2 and f3.
 
632
        B modifies f1 and f3, and C modifies f2 and f3.
 
633
        D merges the changes from B and C and resolves the conflict for f3.
 
634
        """
 
635
        # TODO: jam 20070218 This seems like it could really be done
 
636
        #       with make_branch_and_memory_tree() if we could just
 
637
        #       create the content of those files.
 
638
        # TODO: jam 20070218 Another alternative is that we would really
 
639
        #       like to only create this tree 1 time for all tests that
 
640
        #       use it. Since 'log' only uses the tree in a readonly
 
641
        #       fashion, it seems a shame to regenerate an identical
 
642
        #       tree for each test.
 
643
        tree = self.make_branch_and_tree('tree')
 
644
        tree.lock_write()
 
645
        self.addCleanup(tree.unlock)
 
646
 
 
647
        self.build_tree_contents([('tree/f1', 'A\n'),
 
648
                                  ('tree/f2', 'A\n'),
 
649
                                  ('tree/f3', 'A\n'),
 
650
                                 ])
 
651
        tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
 
652
        tree.commit('A', rev_id='A')
 
653
 
 
654
        self.build_tree_contents([('tree/f2', 'A\nC\n'),
 
655
                                  ('tree/f3', 'A\nC\n'),
 
656
                                 ])
 
657
        tree.commit('C', rev_id='C')
 
658
        # Revert back to A to build the other history.
 
659
        tree.set_last_revision('A')
 
660
        tree.branch.set_last_revision_info(1, 'A')
 
661
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
 
662
                                  ('tree/f2', 'A\n'),
 
663
                                  ('tree/f3', 'A\nB\n'),
 
664
                                 ])
 
665
        tree.commit('B', rev_id='B')
 
666
        tree.set_parent_ids(['B', 'C'])
 
667
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
 
668
                                  ('tree/f2', 'A\nC\n'),
 
669
                                  ('tree/f3', 'A\nB\nC\n'),
 
670
                                 ])
 
671
        tree.commit('D', rev_id='D')
 
672
 
 
673
        # Switch to a read lock for this tree.
 
674
        # We still have addCleanup(unlock)
 
675
        tree.unlock()
 
676
        tree.lock_read()
 
677
        return tree
 
678
 
 
679
    def test_tree_with_single_merge(self):
 
680
        """Make sure the tree layout is correct."""
 
681
        tree = self.create_tree_with_single_merge()
 
682
        rev_A_tree = tree.branch.repository.revision_tree('A')
 
683
        rev_B_tree = tree.branch.repository.revision_tree('B')
 
684
 
 
685
        f1_changed = (u'f1', 'f1-id', 'file', True, False)
 
686
        f2_changed = (u'f2', 'f2-id', 'file', True, False)
 
687
        f3_changed = (u'f3', 'f3-id', 'file', True, False)
 
688
 
 
689
        delta = rev_B_tree.changes_from(rev_A_tree)
 
690
        self.assertEqual([f1_changed, f3_changed], delta.modified)
 
691
        self.assertEqual([], delta.renamed)
 
692
        self.assertEqual([], delta.added)
 
693
        self.assertEqual([], delta.removed)
 
694
 
 
695
        rev_C_tree = tree.branch.repository.revision_tree('C')
 
696
        delta = rev_C_tree.changes_from(rev_A_tree)
 
697
        self.assertEqual([f2_changed, f3_changed], delta.modified)
 
698
        self.assertEqual([], delta.renamed)
 
699
        self.assertEqual([], delta.added)
 
700
        self.assertEqual([], delta.removed)
 
701
 
 
702
        rev_D_tree = tree.branch.repository.revision_tree('D')
 
703
        delta = rev_D_tree.changes_from(rev_B_tree)
 
704
        self.assertEqual([f2_changed, f3_changed], delta.modified)
 
705
        self.assertEqual([], delta.renamed)
 
706
        self.assertEqual([], delta.added)
 
707
        self.assertEqual([], delta.removed)
 
708
 
 
709
        delta = rev_D_tree.changes_from(rev_C_tree)
 
710
        self.assertEqual([f1_changed, f3_changed], delta.modified)
 
711
        self.assertEqual([], delta.renamed)
 
712
        self.assertEqual([], delta.added)
 
713
        self.assertEqual([], delta.removed)
 
714
 
 
715
    def assertAllRevisionsForFileID(self, tree, file_id, revisions):
 
716
        """Make sure _filter_revisions_touching_file_id returns the right values.
 
717
 
 
718
        Get the return value from _filter_revisions_touching_file_id and make
 
719
        sure they are correct.
 
720
        """
 
721
        # The api for _get_revisions_touching_file_id is a little crazy,
 
722
        # So we do the setup here.
 
723
        mainline = tree.branch.revision_history()
 
724
        mainline.insert(0, None)
 
725
        revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
 
726
        view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
 
727
                                                'reverse', True)
 
728
        actual_revs = log._filter_revisions_touching_file_id(
 
729
                            tree.branch, 
 
730
                            file_id,
 
731
                            mainline,
 
732
                            list(view_revs_iter))
 
733
        self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
 
734
 
 
735
    def test_file_id_f1(self):
 
736
        tree = self.create_tree_with_single_merge()
 
737
        # f1 should be marked as modified by revisions A and B
 
738
        self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
 
739
 
 
740
    def test_file_id_f2(self):
 
741
        tree = self.create_tree_with_single_merge()
 
742
        # f2 should be marked as modified by revisions A, C, and D
 
743
        # because D merged the changes from C.
 
744
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
 
745
 
 
746
    def test_file_id_f3(self):
 
747
        tree = self.create_tree_with_single_merge()
 
748
        # f3 should be marked as modified by revisions A, B, C, and D
 
749
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])