~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

  • Committer: mbp at sourcefrog
  • Date: 2005-03-24 00:44:18 UTC
  • Revision ID: mbp@sourcefrog.net-20050324004418-b4a050f656c07f5f
show space usage for various stores in the info command

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
 
    def test_author_in_log(self):
432
 
        """Log includes the author name if it's set in
433
 
        the revision properties
434
 
        """
435
 
        wt = self.make_branch_and_tree('.')
436
 
        b = wt.branch
437
 
        self.build_tree(['a'])
438
 
        wt.add('a')
439
 
        b.nick = 'test_author_log'
440
 
        wt.commit(message='add a',
441
 
                  timestamp=1132711707,
442
 
                  timezone=36000,
443
 
                  committer='Lorem Ipsum <test@example.com>',
444
 
                  author='John Doe <jdoe@example.com>')
445
 
        sio = StringIO()
446
 
        formatter = LongLogFormatter(to_file=sio)
447
 
        show_log(b, formatter)
448
 
        self.assertEqualDiff(sio.getvalue(), '''\
449
 
------------------------------------------------------------
450
 
revno: 1
451
 
committer: Lorem Ipsum <test@example.com>
452
 
author: John Doe <jdoe@example.com>
453
 
branch nick: test_author_log
454
 
timestamp: Wed 2005-11-23 12:08:27 +1000
455
 
message:
456
 
  add a
457
 
''')
458
 
 
459
 
 
460
 
 
461
 
class TestLineLogFormatter(TestCaseWithTransport):
462
 
 
463
 
    def test_line_log(self):
464
 
        """Line log should show revno
465
 
        
466
 
        bug #5162
467
 
        """
468
 
        wt = self.make_branch_and_tree('.')
469
 
        b = wt.branch
470
 
        self.build_tree(['a'])
471
 
        wt.add('a')
472
 
        b.nick = 'test-line-log'
473
 
        wt.commit(message='add a', 
474
 
                  timestamp=1132711707, 
475
 
                  timezone=36000,
476
 
                  committer='Line-Log-Formatter Tester <test@line.log>')
477
 
        logfile = file('out.tmp', 'w+')
478
 
        formatter = LineLogFormatter(to_file=logfile)
479
 
        show_log(b, formatter)
480
 
        logfile.flush()
481
 
        logfile.seek(0)
482
 
        log_contents = logfile.read()
483
 
        self.assertEqualDiff(log_contents, '1: Line-Log-Formatte... 2005-11-23 add a\n')
484
 
 
485
 
    def test_short_log_with_merges(self):
486
 
        wt = self.make_branch_and_memory_tree('.')
487
 
        wt.lock_write()
488
 
        try:
489
 
            wt.add('')
490
 
            wt.commit('rev-1', rev_id='rev-1',
491
 
                      timestamp=1132586655, timezone=36000,
492
 
                      committer='Joe Foo <joe@foo.com>')
493
 
            wt.commit('rev-merged', rev_id='rev-2a',
494
 
                      timestamp=1132586700, timezone=36000,
495
 
                      committer='Joe Foo <joe@foo.com>')
496
 
            wt.set_parent_ids(['rev-1', 'rev-2a'])
497
 
            wt.branch.set_last_revision_info(1, 'rev-1')
498
 
            wt.commit('rev-2', rev_id='rev-2b',
499
 
                      timestamp=1132586800, timezone=36000,
500
 
                      committer='Joe Foo <joe@foo.com>')
501
 
            logfile = StringIO()
502
 
            formatter = ShortLogFormatter(to_file=logfile)
503
 
            show_log(wt.branch, formatter)
504
 
            logfile.flush()
505
 
            self.assertEqualDiff("""\
506
 
    2 Joe Foo\t2005-11-22 [merge]
507
 
      rev-2
508
 
 
509
 
    1 Joe Foo\t2005-11-22
510
 
      rev-1
511
 
 
512
 
""", logfile.getvalue())
513
 
        finally:
514
 
            wt.unlock()
515
 
 
516
 
    def test_trailing_newlines(self):
517
 
        wt = self.make_branch_and_tree('.')
518
 
        b = make_commits_with_trailing_newlines(wt)
519
 
        sio = StringIO()
520
 
        lf = LineLogFormatter(to_file=sio)
521
 
        show_log(b, lf)
522
 
        self.assertEqualDiff(sio.getvalue(), """\
523
 
3: Joe Foo 2005-11-21 single line with trailing newline
524
 
2: Joe Foo 2005-11-21 multiline
525
 
1: Joe Foo 2005-11-21 simple log message
526
 
""")
527
 
 
528
 
 
529
 
class TestGetViewRevisions(TestCaseWithTransport):
530
 
 
531
 
    def make_tree_with_commits(self):
532
 
        """Create a tree with well-known revision ids"""
533
 
        wt = self.make_branch_and_tree('tree1')
534
 
        wt.commit('commit one', rev_id='1')
535
 
        wt.commit('commit two', rev_id='2')
536
 
        wt.commit('commit three', rev_id='3')
537
 
        mainline_revs = [None, '1', '2', '3']
538
 
        rev_nos = {'1': 1, '2': 2, '3': 3}
539
 
        return mainline_revs, rev_nos, wt
540
 
 
541
 
    def make_tree_with_merges(self):
542
 
        """Create a tree with well-known revision ids and a merge"""
543
 
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
544
 
        tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
545
 
        tree2.commit('four-a', rev_id='4a')
546
 
        wt.merge_from_branch(tree2.branch)
547
 
        wt.commit('four-b', rev_id='4b')
548
 
        mainline_revs.append('4b')
549
 
        rev_nos['4b'] = 4
550
 
        # 4a: 3.1.1
551
 
        return mainline_revs, rev_nos, wt
552
 
 
553
 
    def make_tree_with_many_merges(self):
554
 
        """Create a tree with well-known revision ids"""
555
 
        wt = self.make_branch_and_tree('tree1')
556
 
        wt.commit('commit one', rev_id='1')
557
 
        wt.commit('commit two', rev_id='2')
558
 
        tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
559
 
        tree3.commit('commit three a', rev_id='3a')
560
 
        tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
561
 
        tree2.merge_from_branch(tree3.branch)
562
 
        tree2.commit('commit three b', rev_id='3b')
563
 
        wt.merge_from_branch(tree2.branch)
564
 
        wt.commit('commit three c', rev_id='3c')
565
 
        tree2.commit('four-a', rev_id='4a')
566
 
        wt.merge_from_branch(tree2.branch)
567
 
        wt.commit('four-b', rev_id='4b')
568
 
        mainline_revs = [None, '1', '2', '3c', '4b']
569
 
        rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
570
 
        full_rev_nos_for_reference = {
571
 
            '1': '1',
572
 
            '2': '2',
573
 
            '3a': '2.2.1', #first commit tree 3
574
 
            '3b': '2.1.1', # first commit tree 2
575
 
            '3c': '3', #merges 3b to main
576
 
            '4a': '2.1.2', # second commit tree 2
577
 
            '4b': '4', # merges 4a to main
578
 
            }
579
 
        return mainline_revs, rev_nos, wt
580
 
 
581
 
    def test_get_view_revisions_forward(self):
582
 
        """Test the get_view_revisions method"""
583
 
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
584
 
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
585
 
                                            'forward'))
586
 
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
587
 
            revisions)
588
 
        revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
589
 
                                             'forward', include_merges=False))
590
 
        self.assertEqual(revisions, revisions2)
591
 
 
592
 
    def test_get_view_revisions_reverse(self):
593
 
        """Test the get_view_revisions with reverse"""
594
 
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
595
 
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
596
 
                                            'reverse'))
597
 
        self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
598
 
            revisions)
599
 
        revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
600
 
                                             'reverse', include_merges=False))
601
 
        self.assertEqual(revisions, revisions2)
602
 
 
603
 
    def test_get_view_revisions_merge(self):
604
 
        """Test get_view_revisions when there are merges"""
605
 
        mainline_revs, rev_nos, wt = self.make_tree_with_merges()
606
 
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
607
 
                                            'forward'))
608
 
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
609
 
            ('4b', '4', 0), ('4a', '3.1.1', 1)],
610
 
            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), ('3', '3', 0),
614
 
            ('4b', '4', 0)],
615
 
            revisions)
616
 
 
617
 
    def test_get_view_revisions_merge_reverse(self):
618
 
        """Test get_view_revisions in reverse when there are merges"""
619
 
        mainline_revs, rev_nos, wt = self.make_tree_with_merges()
620
 
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
621
 
                                            'reverse'))
622
 
        self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
623
 
            ('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
624
 
            revisions)
625
 
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
626
 
                                             'reverse', include_merges=False))
627
 
        self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
628
 
            ('1', '1', 0)],
629
 
            revisions)
630
 
 
631
 
    def test_get_view_revisions_merge2(self):
632
 
        """Test get_view_revisions when there are merges"""
633
 
        mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
634
 
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
635
 
                                            'forward'))
636
 
        expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
637
 
            ('3a', '2.2.1', 1), ('3b', '2.1.1', 1), ('4b', '4', 0),
638
 
            ('4a', '2.1.2', 1)]
639
 
        self.assertEqual(expected, revisions)
640
 
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
641
 
                                             'forward', include_merges=False))
642
 
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
643
 
            ('4b', '4', 0)],
644
 
            revisions)
645
 
 
646
 
 
647
 
class TestGetRevisionsTouchingFileID(TestCaseWithTransport):
648
 
 
649
 
    def create_tree_with_single_merge(self):
650
 
        """Create a branch with a moderate layout.
651
 
 
652
 
        The revision graph looks like:
653
 
 
654
 
           A
655
 
           |\
656
 
           B C
657
 
           |/
658
 
           D
659
 
 
660
 
        In this graph, A introduced files f1 and f2 and f3.
661
 
        B modifies f1 and f3, and C modifies f2 and f3.
662
 
        D merges the changes from B and C and resolves the conflict for f3.
663
 
        """
664
 
        # TODO: jam 20070218 This seems like it could really be done
665
 
        #       with make_branch_and_memory_tree() if we could just
666
 
        #       create the content of those files.
667
 
        # TODO: jam 20070218 Another alternative is that we would really
668
 
        #       like to only create this tree 1 time for all tests that
669
 
        #       use it. Since 'log' only uses the tree in a readonly
670
 
        #       fashion, it seems a shame to regenerate an identical
671
 
        #       tree for each test.
672
 
        tree = self.make_branch_and_tree('tree')
673
 
        tree.lock_write()
674
 
        self.addCleanup(tree.unlock)
675
 
 
676
 
        self.build_tree_contents([('tree/f1', 'A\n'),
677
 
                                  ('tree/f2', 'A\n'),
678
 
                                  ('tree/f3', 'A\n'),
679
 
                                 ])
680
 
        tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
681
 
        tree.commit('A', rev_id='A')
682
 
 
683
 
        self.build_tree_contents([('tree/f2', 'A\nC\n'),
684
 
                                  ('tree/f3', 'A\nC\n'),
685
 
                                 ])
686
 
        tree.commit('C', rev_id='C')
687
 
        # Revert back to A to build the other history.
688
 
        tree.set_last_revision('A')
689
 
        tree.branch.set_last_revision_info(1, 'A')
690
 
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
691
 
                                  ('tree/f2', 'A\n'),
692
 
                                  ('tree/f3', 'A\nB\n'),
693
 
                                 ])
694
 
        tree.commit('B', rev_id='B')
695
 
        tree.set_parent_ids(['B', 'C'])
696
 
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
697
 
                                  ('tree/f2', 'A\nC\n'),
698
 
                                  ('tree/f3', 'A\nB\nC\n'),
699
 
                                 ])
700
 
        tree.commit('D', rev_id='D')
701
 
 
702
 
        # Switch to a read lock for this tree.
703
 
        # We still have addCleanup(unlock)
704
 
        tree.unlock()
705
 
        tree.lock_read()
706
 
        return tree
707
 
 
708
 
    def test_tree_with_single_merge(self):
709
 
        """Make sure the tree layout is correct."""
710
 
        tree = self.create_tree_with_single_merge()
711
 
        rev_A_tree = tree.branch.repository.revision_tree('A')
712
 
        rev_B_tree = tree.branch.repository.revision_tree('B')
713
 
 
714
 
        f1_changed = (u'f1', 'f1-id', 'file', True, False)
715
 
        f2_changed = (u'f2', 'f2-id', 'file', True, False)
716
 
        f3_changed = (u'f3', 'f3-id', 'file', True, False)
717
 
 
718
 
        delta = rev_B_tree.changes_from(rev_A_tree)
719
 
        self.assertEqual([f1_changed, f3_changed], delta.modified)
720
 
        self.assertEqual([], delta.renamed)
721
 
        self.assertEqual([], delta.added)
722
 
        self.assertEqual([], delta.removed)
723
 
 
724
 
        rev_C_tree = tree.branch.repository.revision_tree('C')
725
 
        delta = rev_C_tree.changes_from(rev_A_tree)
726
 
        self.assertEqual([f2_changed, f3_changed], delta.modified)
727
 
        self.assertEqual([], delta.renamed)
728
 
        self.assertEqual([], delta.added)
729
 
        self.assertEqual([], delta.removed)
730
 
 
731
 
        rev_D_tree = tree.branch.repository.revision_tree('D')
732
 
        delta = rev_D_tree.changes_from(rev_B_tree)
733
 
        self.assertEqual([f2_changed, f3_changed], delta.modified)
734
 
        self.assertEqual([], delta.renamed)
735
 
        self.assertEqual([], delta.added)
736
 
        self.assertEqual([], delta.removed)
737
 
 
738
 
        delta = rev_D_tree.changes_from(rev_C_tree)
739
 
        self.assertEqual([f1_changed, f3_changed], delta.modified)
740
 
        self.assertEqual([], delta.renamed)
741
 
        self.assertEqual([], delta.added)
742
 
        self.assertEqual([], delta.removed)
743
 
 
744
 
    def assertAllRevisionsForFileID(self, tree, file_id, revisions):
745
 
        """Make sure _filter_revisions_touching_file_id returns the right values.
746
 
 
747
 
        Get the return value from _filter_revisions_touching_file_id and make
748
 
        sure they are correct.
749
 
        """
750
 
        # The api for _get_revisions_touching_file_id is a little crazy,
751
 
        # So we do the setup here.
752
 
        mainline = tree.branch.revision_history()
753
 
        mainline.insert(0, None)
754
 
        revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
755
 
        view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
756
 
                                                'reverse', True)
757
 
        actual_revs = log._filter_revisions_touching_file_id(
758
 
                            tree.branch, 
759
 
                            file_id,
760
 
                            mainline,
761
 
                            list(view_revs_iter))
762
 
        self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
763
 
 
764
 
    def test_file_id_f1(self):
765
 
        tree = self.create_tree_with_single_merge()
766
 
        # f1 should be marked as modified by revisions A and B
767
 
        self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
768
 
 
769
 
    def test_file_id_f2(self):
770
 
        tree = self.create_tree_with_single_merge()
771
 
        # f2 should be marked as modified by revisions A, C, and D
772
 
        # because D merged the changes from C.
773
 
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
774
 
 
775
 
    def test_file_id_f3(self):
776
 
        tree = self.create_tree_with_single_merge()
777
 
        # f3 should be marked as modified by revisions A, B, C, and D
778
 
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
779
 
 
780
 
 
781
 
class TestShowChangedRevisions(TestCaseWithTransport):
782
 
 
783
 
    def test_show_changed_revisions_verbose(self):
784
 
        tree = self.make_branch_and_tree('tree_a')
785
 
        self.build_tree(['tree_a/foo'])
786
 
        tree.add('foo')
787
 
        tree.commit('bar', rev_id='bar-id')
788
 
        s = StringIO()
789
 
        log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
790
 
        self.assertContainsRe(s.getvalue(), 'bar')
791
 
        self.assertNotContainsRe(s.getvalue(), 'foo')