~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

  • Committer: Martin Pool
  • Date: 2005-05-03 08:00:27 UTC
  • Revision ID: mbp@sourcefrog.net-20050503080027-908edb5b39982198
doc

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 TestCase, 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
 
from bzrlib.revision import Revision
32
 
 
33
 
 
34
 
class LogCatcher(LogFormatter):
35
 
    """Pull log messages into list rather than displaying them.
36
 
 
37
 
    For ease of testing we save log messages here rather than actually
38
 
    formatting them, so that we can precisely check the result without
39
 
    being too dependent on the exact formatting.
40
 
 
41
 
    We should also test the LogFormatter.
42
 
    """
43
 
 
44
 
    supports_delta = True
45
 
 
46
 
    def __init__(self):
47
 
        super(LogCatcher, self).__init__(to_file=None)
48
 
        self.logs = []
49
 
 
50
 
    def log_revision(self, revision):
51
 
        self.logs.append(revision)
52
 
 
53
 
 
54
 
class TestShowLog(TestCaseWithTransport):
55
 
 
56
 
    def checkDelta(self, delta, **kw):
57
 
        """Check the filenames touched by a delta are as expected."""
58
 
        for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
59
 
            expected = kw.get(n, [])
60
 
            # strip out only the path components
61
 
            got = [x[0] for x in getattr(delta, n)]
62
 
            self.assertEquals(expected, got)
63
 
 
64
 
    def test_cur_revno(self):
65
 
        wt = self.make_branch_and_tree('.')
66
 
        b = wt.branch
67
 
 
68
 
        lf = LogCatcher()
69
 
        wt.commit('empty commit')
70
 
        show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
71
 
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
72
 
                          start_revision=2, end_revision=1) 
73
 
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
74
 
                          start_revision=1, end_revision=2) 
75
 
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
76
 
                          start_revision=0, end_revision=2) 
77
 
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
78
 
                          start_revision=1, end_revision=0) 
79
 
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
80
 
                          start_revision=-1, end_revision=1) 
81
 
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
82
 
                          start_revision=1, end_revision=-1) 
83
 
 
84
 
    def test_simple_log(self):
85
 
        eq = self.assertEquals
86
 
        
87
 
        wt = self.make_branch_and_tree('.')
88
 
        b = wt.branch
89
 
 
90
 
        lf = LogCatcher()
91
 
        show_log(b, lf)
92
 
        # no entries yet
93
 
        eq(lf.logs, [])
94
 
 
95
 
        wt.commit('empty commit')
96
 
        lf = LogCatcher()
97
 
        show_log(b, lf, verbose=True)
98
 
        eq(len(lf.logs), 1)
99
 
        eq(lf.logs[0].revno, '1')
100
 
        eq(lf.logs[0].rev.message, 'empty commit')
101
 
        d = lf.logs[0].delta
102
 
        self.log('log delta: %r' % d)
103
 
        self.checkDelta(d)
104
 
 
105
 
        self.build_tree(['hello'])
106
 
        wt.add('hello')
107
 
        wt.commit('add one file',
108
 
                  committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
109
 
                            u'<test@example.com>')
110
 
 
111
 
        lf = self.make_utf8_encoded_stringio()
112
 
        # log using regular thing
113
 
        show_log(b, LongLogFormatter(lf))
114
 
        lf.seek(0)
115
 
        for l in lf.readlines():
116
 
            self.log(l)
117
 
 
118
 
        # get log as data structure
119
 
        lf = LogCatcher()
120
 
        show_log(b, lf, verbose=True)
121
 
        eq(len(lf.logs), 2)
122
 
        self.log('log entries:')
123
 
        for logentry in lf.logs:
124
 
            self.log('%4s %s' % (logentry.revno, logentry.rev.message))
125
 
        
126
 
        # first one is most recent
127
 
        logentry = lf.logs[0]
128
 
        eq(logentry.revno, '2')
129
 
        eq(logentry.rev.message, 'add one file')
130
 
        d = logentry.delta
131
 
        self.log('log 2 delta: %r' % d)
132
 
        self.checkDelta(d, added=['hello'])
133
 
        
134
 
        # commit a log message with control characters
135
 
        msg = "All 8-bit chars: " +  ''.join([unichr(x) for x in range(256)])
136
 
        self.log("original commit message: %r", msg)
137
 
        wt.commit(msg)
138
 
        lf = LogCatcher()
139
 
        show_log(b, lf, verbose=True)
140
 
        committed_msg = lf.logs[0].rev.message
141
 
        self.log("escaped commit message: %r", committed_msg)
142
 
        self.assert_(msg != committed_msg)
143
 
        self.assert_(len(committed_msg) > len(msg))
144
 
 
145
 
        # Check that log message with only XML-valid characters isn't
146
 
        # escaped.  As ElementTree apparently does some kind of
147
 
        # newline conversion, neither LF (\x0A) nor CR (\x0D) are
148
 
        # included in the test commit message, even though they are
149
 
        # valid XML 1.0 characters.
150
 
        msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
151
 
        self.log("original commit message: %r", msg)
152
 
        wt.commit(msg)
153
 
        lf = LogCatcher()
154
 
        show_log(b, lf, verbose=True)
155
 
        committed_msg = lf.logs[0].rev.message
156
 
        self.log("escaped commit message: %r", committed_msg)
157
 
        self.assert_(msg == committed_msg)
158
 
 
159
 
    def test_deltas_in_merge_revisions(self):
160
 
        """Check deltas created for both mainline and merge revisions"""
161
 
        eq = self.assertEquals
162
 
        wt = self.make_branch_and_tree('parent')
163
 
        self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
164
 
        wt.add('file1')
165
 
        wt.add('file2')
166
 
        wt.commit(message='add file1 and file2')
167
 
        self.run_bzr('branch parent child')
168
 
        os.unlink('child/file1')
169
 
        print >> file('child/file2', 'wb'), 'hello'
170
 
        self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
171
 
            'child'])
172
 
        os.chdir('parent')
173
 
        self.run_bzr('merge ../child')
174
 
        wt.commit('merge child branch')
175
 
        os.chdir('..')
176
 
        b = wt.branch
177
 
        lf = LogCatcher()
178
 
        lf.supports_merge_revisions = True
179
 
        show_log(b, lf, verbose=True)
180
 
        eq(len(lf.logs),3)
181
 
        logentry = lf.logs[0]
182
 
        eq(logentry.revno, '2')
183
 
        eq(logentry.rev.message, 'merge child branch')
184
 
        d = logentry.delta
185
 
        self.checkDelta(d, removed=['file1'], modified=['file2'])
186
 
        logentry = lf.logs[1]
187
 
        eq(logentry.revno, '1.1.1')
188
 
        eq(logentry.rev.message, 'remove file1 and modify file2')
189
 
        d = logentry.delta
190
 
        self.checkDelta(d, removed=['file1'], modified=['file2'])
191
 
        logentry = lf.logs[2]
192
 
        eq(logentry.revno, '1')
193
 
        eq(logentry.rev.message, 'add file1 and file2')
194
 
        d = logentry.delta
195
 
        self.checkDelta(d, added=['file1', 'file2'])
196
 
 
197
 
 
198
 
def make_commits_with_trailing_newlines(wt):
199
 
    """Helper method for LogFormatter tests"""    
200
 
    b = wt.branch
201
 
    b.nick='test'
202
 
    open('a', 'wb').write('hello moto\n')
203
 
    wt.add('a')
204
 
    wt.commit('simple log message', rev_id='a1',
205
 
              timestamp=1132586655.459960938, timezone=-6*3600,
206
 
              committer='Joe Foo <joe@foo.com>')
207
 
    open('b', 'wb').write('goodbye\n')
208
 
    wt.add('b')
209
 
    wt.commit('multiline\nlog\nmessage\n', rev_id='a2',
210
 
              timestamp=1132586842.411175966, timezone=-6*3600,
211
 
              committer='Joe Foo <joe@foo.com>',
212
 
              author='Joe Bar <joe@bar.com>')
213
 
 
214
 
    open('c', 'wb').write('just another manic monday\n')
215
 
    wt.add('c')
216
 
    wt.commit('single line with trailing newline\n', rev_id='a3',
217
 
              timestamp=1132587176.835228920, timezone=-6*3600,
218
 
              committer = 'Joe Foo <joe@foo.com>')
219
 
    return b
220
 
 
221
 
 
222
 
class TestShortLogFormatter(TestCaseWithTransport):
223
 
 
224
 
    def test_trailing_newlines(self):
225
 
        wt = self.make_branch_and_tree('.')
226
 
        b = make_commits_with_trailing_newlines(wt)
227
 
        sio = self.make_utf8_encoded_stringio()
228
 
        lf = ShortLogFormatter(to_file=sio)
229
 
        show_log(b, lf)
230
 
        self.assertEquals(sio.getvalue(), """\
231
 
    3 Joe Foo\t2005-11-21
232
 
      single line with trailing newline
233
 
 
234
 
    2 Joe Bar\t2005-11-21
235
 
      multiline
236
 
      log
237
 
      message
238
 
 
239
 
    1 Joe Foo\t2005-11-21
240
 
      simple log message
241
 
 
242
 
""")
243
 
 
244
 
 
245
 
class TestLongLogFormatter(TestCaseWithTransport):
246
 
 
247
 
    def normalize_log(self,log):
248
 
        """Replaces the variable lines of logs with fixed lines"""
249
 
        author = 'author: Dolor Sit <test@example.com>'
250
 
        committer = 'committer: Lorem Ipsum <test@example.com>'
251
 
        lines = log.splitlines(True)
252
 
        for idx,line in enumerate(lines):
253
 
            stripped_line = line.lstrip()
254
 
            indent = ' ' * (len(line) - len(stripped_line))
255
 
            if stripped_line.startswith('author:'):
256
 
                lines[idx] = indent + author + '\n'
257
 
            elif stripped_line.startswith('committer:'):
258
 
                lines[idx] = indent + committer + '\n'
259
 
            elif stripped_line.startswith('timestamp:'):
260
 
                lines[idx] = indent + 'timestamp: Just now\n'
261
 
        return ''.join(lines)
262
 
 
263
 
    def test_verbose_log(self):
264
 
        """Verbose log includes changed files
265
 
        
266
 
        bug #4676
267
 
        """
268
 
        wt = self.make_branch_and_tree('.')
269
 
        b = wt.branch
270
 
        self.build_tree(['a'])
271
 
        wt.add('a')
272
 
        # XXX: why does a longer nick show up?
273
 
        b.nick = 'test_verbose_log'
274
 
        wt.commit(message='add a', 
275
 
                  timestamp=1132711707, 
276
 
                  timezone=36000,
277
 
                  committer='Lorem Ipsum <test@example.com>')
278
 
        logfile = file('out.tmp', 'w+')
279
 
        formatter = LongLogFormatter(to_file=logfile)
280
 
        show_log(b, formatter, verbose=True)
281
 
        logfile.flush()
282
 
        logfile.seek(0)
283
 
        log_contents = logfile.read()
284
 
        self.assertEqualDiff(log_contents, '''\
285
 
------------------------------------------------------------
286
 
revno: 1
287
 
committer: Lorem Ipsum <test@example.com>
288
 
branch nick: test_verbose_log
289
 
timestamp: Wed 2005-11-23 12:08:27 +1000
290
 
message:
291
 
  add a
292
 
added:
293
 
  a
294
 
''')
295
 
 
296
 
    def test_merges_are_indented_by_level(self):
297
 
        wt = self.make_branch_and_tree('parent')
298
 
        wt.commit('first post')
299
 
        self.run_bzr('branch parent child')
300
 
        self.run_bzr(['commit', '-m', 'branch 1', '--unchanged', 'child'])
301
 
        self.run_bzr('branch child smallerchild')
302
 
        self.run_bzr(['commit', '-m', 'branch 2', '--unchanged',
303
 
            'smallerchild'])
304
 
        os.chdir('child')
305
 
        self.run_bzr('merge ../smallerchild')
306
 
        self.run_bzr(['commit', '-m', 'merge branch 2'])
307
 
        os.chdir('../parent')
308
 
        self.run_bzr('merge ../child')
309
 
        wt.commit('merge branch 1')
310
 
        b = wt.branch
311
 
        sio = self.make_utf8_encoded_stringio()
312
 
        lf = LongLogFormatter(to_file=sio)
313
 
        show_log(b, lf, verbose=True)
314
 
        log = self.normalize_log(sio.getvalue())
315
 
        self.assertEqualDiff("""\
316
 
------------------------------------------------------------
317
 
revno: 2
318
 
committer: Lorem Ipsum <test@example.com>
319
 
branch nick: parent
320
 
timestamp: Just now
321
 
message:
322
 
  merge branch 1
323
 
    ------------------------------------------------------------
324
 
    revno: 1.1.2
325
 
    committer: Lorem Ipsum <test@example.com>
326
 
    branch nick: child
327
 
    timestamp: Just now
328
 
    message:
329
 
      merge branch 2
330
 
        ------------------------------------------------------------
331
 
        revno: 1.1.1.1.1
332
 
        committer: Lorem Ipsum <test@example.com>
333
 
        branch nick: smallerchild
334
 
        timestamp: Just now
335
 
        message:
336
 
          branch 2
337
 
    ------------------------------------------------------------
338
 
    revno: 1.1.1
339
 
    committer: Lorem Ipsum <test@example.com>
340
 
    branch nick: child
341
 
    timestamp: Just now
342
 
    message:
343
 
      branch 1
344
 
------------------------------------------------------------
345
 
revno: 1
346
 
committer: Lorem Ipsum <test@example.com>
347
 
branch nick: parent
348
 
timestamp: Just now
349
 
message:
350
 
  first post
351
 
""", log)
352
 
 
353
 
    def test_verbose_merge_revisions_contain_deltas(self):
354
 
        wt = self.make_branch_and_tree('parent')
355
 
        self.build_tree(['parent/f1', 'parent/f2'])
356
 
        wt.add(['f1','f2'])
357
 
        wt.commit('first post')
358
 
        self.run_bzr('branch parent child')
359
 
        os.unlink('child/f1')
360
 
        print >> file('child/f2', 'wb'), 'hello'
361
 
        self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
362
 
            'child'])
363
 
        os.chdir('parent')
364
 
        self.run_bzr('merge ../child')
365
 
        wt.commit('merge branch 1')
366
 
        b = wt.branch
367
 
        sio = self.make_utf8_encoded_stringio()
368
 
        lf = LongLogFormatter(to_file=sio)
369
 
        show_log(b, lf, verbose=True)
370
 
        log = self.normalize_log(sio.getvalue())
371
 
        self.assertEqualDiff("""\
372
 
------------------------------------------------------------
373
 
revno: 2
374
 
committer: Lorem Ipsum <test@example.com>
375
 
branch nick: parent
376
 
timestamp: Just now
377
 
message:
378
 
  merge branch 1
379
 
removed:
380
 
  f1
381
 
modified:
382
 
  f2
383
 
    ------------------------------------------------------------
384
 
    revno: 1.1.1
385
 
    committer: Lorem Ipsum <test@example.com>
386
 
    branch nick: child
387
 
    timestamp: Just now
388
 
    message:
389
 
      removed f1 and modified f2
390
 
    removed:
391
 
      f1
392
 
    modified:
393
 
      f2
394
 
------------------------------------------------------------
395
 
revno: 1
396
 
committer: Lorem Ipsum <test@example.com>
397
 
branch nick: parent
398
 
timestamp: Just now
399
 
message:
400
 
  first post
401
 
added:
402
 
  f1
403
 
  f2
404
 
""", log)
405
 
 
406
 
    def test_trailing_newlines(self):
407
 
        wt = self.make_branch_and_tree('.')
408
 
        b = make_commits_with_trailing_newlines(wt)
409
 
        sio = self.make_utf8_encoded_stringio()
410
 
        lf = LongLogFormatter(to_file=sio)
411
 
        show_log(b, lf)
412
 
        self.assertEqualDiff(sio.getvalue(), """\
413
 
------------------------------------------------------------
414
 
revno: 3
415
 
committer: Joe Foo <joe@foo.com>
416
 
branch nick: test
417
 
timestamp: Mon 2005-11-21 09:32:56 -0600
418
 
message:
419
 
  single line with trailing newline
420
 
------------------------------------------------------------
421
 
revno: 2
422
 
author: Joe Bar <joe@bar.com>
423
 
committer: Joe Foo <joe@foo.com>
424
 
branch nick: test
425
 
timestamp: Mon 2005-11-21 09:27:22 -0600
426
 
message:
427
 
  multiline
428
 
  log
429
 
  message
430
 
------------------------------------------------------------
431
 
revno: 1
432
 
committer: Joe Foo <joe@foo.com>
433
 
branch nick: test
434
 
timestamp: Mon 2005-11-21 09:24:15 -0600
435
 
message:
436
 
  simple log message
437
 
""")
438
 
 
439
 
    def test_author_in_log(self):
440
 
        """Log includes the author name if it's set in
441
 
        the revision properties
442
 
        """
443
 
        wt = self.make_branch_and_tree('.')
444
 
        b = wt.branch
445
 
        self.build_tree(['a'])
446
 
        wt.add('a')
447
 
        b.nick = 'test_author_log'
448
 
        wt.commit(message='add a',
449
 
                  timestamp=1132711707,
450
 
                  timezone=36000,
451
 
                  committer='Lorem Ipsum <test@example.com>',
452
 
                  author='John Doe <jdoe@example.com>')
453
 
        sio = StringIO()
454
 
        formatter = LongLogFormatter(to_file=sio)
455
 
        show_log(b, formatter)
456
 
        self.assertEqualDiff(sio.getvalue(), '''\
457
 
------------------------------------------------------------
458
 
revno: 1
459
 
author: John Doe <jdoe@example.com>
460
 
committer: Lorem Ipsum <test@example.com>
461
 
branch nick: test_author_log
462
 
timestamp: Wed 2005-11-23 12:08:27 +1000
463
 
message:
464
 
  add a
465
 
''')
466
 
 
467
 
 
468
 
 
469
 
class TestLineLogFormatter(TestCaseWithTransport):
470
 
 
471
 
    def test_line_log(self):
472
 
        """Line log should show revno
473
 
        
474
 
        bug #5162
475
 
        """
476
 
        wt = self.make_branch_and_tree('.')
477
 
        b = wt.branch
478
 
        self.build_tree(['a'])
479
 
        wt.add('a')
480
 
        b.nick = 'test-line-log'
481
 
        wt.commit(message='add a', 
482
 
                  timestamp=1132711707, 
483
 
                  timezone=36000,
484
 
                  committer='Line-Log-Formatter Tester <test@line.log>')
485
 
        logfile = file('out.tmp', 'w+')
486
 
        formatter = LineLogFormatter(to_file=logfile)
487
 
        show_log(b, formatter)
488
 
        logfile.flush()
489
 
        logfile.seek(0)
490
 
        log_contents = logfile.read()
491
 
        self.assertEqualDiff(log_contents, '1: Line-Log-Formatte... 2005-11-23 add a\n')
492
 
 
493
 
    def test_short_log_with_merges(self):
494
 
        wt = self.make_branch_and_memory_tree('.')
495
 
        wt.lock_write()
496
 
        try:
497
 
            wt.add('')
498
 
            wt.commit('rev-1', rev_id='rev-1',
499
 
                      timestamp=1132586655, timezone=36000,
500
 
                      committer='Joe Foo <joe@foo.com>')
501
 
            wt.commit('rev-merged', rev_id='rev-2a',
502
 
                      timestamp=1132586700, timezone=36000,
503
 
                      committer='Joe Foo <joe@foo.com>')
504
 
            wt.set_parent_ids(['rev-1', 'rev-2a'])
505
 
            wt.branch.set_last_revision_info(1, 'rev-1')
506
 
            wt.commit('rev-2', rev_id='rev-2b',
507
 
                      timestamp=1132586800, timezone=36000,
508
 
                      committer='Joe Foo <joe@foo.com>')
509
 
            logfile = self.make_utf8_encoded_stringio()
510
 
            formatter = ShortLogFormatter(to_file=logfile)
511
 
            show_log(wt.branch, formatter)
512
 
            logfile.flush()
513
 
            self.assertEqualDiff("""\
514
 
    2 Joe Foo\t2005-11-22 [merge]
515
 
      rev-2
516
 
 
517
 
    1 Joe Foo\t2005-11-22
518
 
      rev-1
519
 
 
520
 
""", logfile.getvalue())
521
 
        finally:
522
 
            wt.unlock()
523
 
 
524
 
    def test_trailing_newlines(self):
525
 
        wt = self.make_branch_and_tree('.')
526
 
        b = make_commits_with_trailing_newlines(wt)
527
 
        sio = self.make_utf8_encoded_stringio()
528
 
        lf = LineLogFormatter(to_file=sio)
529
 
        show_log(b, lf)
530
 
        self.assertEqualDiff(sio.getvalue(), """\
531
 
3: Joe Foo 2005-11-21 single line with trailing newline
532
 
2: Joe Bar 2005-11-21 multiline
533
 
1: Joe Foo 2005-11-21 simple log message
534
 
""")
535
 
 
536
 
 
537
 
class TestGetViewRevisions(TestCaseWithTransport):
538
 
 
539
 
    def make_tree_with_commits(self):
540
 
        """Create a tree with well-known revision ids"""
541
 
        wt = self.make_branch_and_tree('tree1')
542
 
        wt.commit('commit one', rev_id='1')
543
 
        wt.commit('commit two', rev_id='2')
544
 
        wt.commit('commit three', rev_id='3')
545
 
        mainline_revs = [None, '1', '2', '3']
546
 
        rev_nos = {'1': 1, '2': 2, '3': 3}
547
 
        return mainline_revs, rev_nos, wt
548
 
 
549
 
    def make_tree_with_merges(self):
550
 
        """Create a tree with well-known revision ids and a merge"""
551
 
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
552
 
        tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
553
 
        tree2.commit('four-a', rev_id='4a')
554
 
        wt.merge_from_branch(tree2.branch)
555
 
        wt.commit('four-b', rev_id='4b')
556
 
        mainline_revs.append('4b')
557
 
        rev_nos['4b'] = 4
558
 
        # 4a: 3.1.1
559
 
        return mainline_revs, rev_nos, wt
560
 
 
561
 
    def make_tree_with_many_merges(self):
562
 
        """Create a tree with well-known revision ids"""
563
 
        wt = self.make_branch_and_tree('tree1')
564
 
        wt.commit('commit one', rev_id='1')
565
 
        wt.commit('commit two', rev_id='2')
566
 
        tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
567
 
        tree3.commit('commit three a', rev_id='3a')
568
 
        tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
569
 
        tree2.merge_from_branch(tree3.branch)
570
 
        tree2.commit('commit three b', rev_id='3b')
571
 
        wt.merge_from_branch(tree2.branch)
572
 
        wt.commit('commit three c', rev_id='3c')
573
 
        tree2.commit('four-a', rev_id='4a')
574
 
        wt.merge_from_branch(tree2.branch)
575
 
        wt.commit('four-b', rev_id='4b')
576
 
        mainline_revs = [None, '1', '2', '3c', '4b']
577
 
        rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
578
 
        full_rev_nos_for_reference = {
579
 
            '1': '1',
580
 
            '2': '2',
581
 
            '3a': '2.2.1', #first commit tree 3
582
 
            '3b': '2.1.1', # first commit tree 2
583
 
            '3c': '3', #merges 3b to main
584
 
            '4a': '2.1.2', # second commit tree 2
585
 
            '4b': '4', # merges 4a to main
586
 
            }
587
 
        return mainline_revs, rev_nos, wt
588
 
 
589
 
    def test_get_view_revisions_forward(self):
590
 
        """Test the get_view_revisions method"""
591
 
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
592
 
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
593
 
                                            'forward'))
594
 
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
595
 
            revisions)
596
 
        revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
597
 
                                             'forward', include_merges=False))
598
 
        self.assertEqual(revisions, revisions2)
599
 
 
600
 
    def test_get_view_revisions_reverse(self):
601
 
        """Test the get_view_revisions with reverse"""
602
 
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
603
 
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
604
 
                                            'reverse'))
605
 
        self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
606
 
            revisions)
607
 
        revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
608
 
                                             'reverse', include_merges=False))
609
 
        self.assertEqual(revisions, revisions2)
610
 
 
611
 
    def test_get_view_revisions_merge(self):
612
 
        """Test get_view_revisions when there are merges"""
613
 
        mainline_revs, rev_nos, wt = self.make_tree_with_merges()
614
 
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
615
 
                                            'forward'))
616
 
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
617
 
            ('4b', '4', 0), ('4a', '3.1.1', 1)],
618
 
            revisions)
619
 
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
620
 
                                             'forward', include_merges=False))
621
 
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
622
 
            ('4b', '4', 0)],
623
 
            revisions)
624
 
 
625
 
    def test_get_view_revisions_merge_reverse(self):
626
 
        """Test get_view_revisions in reverse when there are merges"""
627
 
        mainline_revs, rev_nos, wt = self.make_tree_with_merges()
628
 
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
629
 
                                            'reverse'))
630
 
        self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
631
 
            ('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
632
 
            revisions)
633
 
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
634
 
                                             'reverse', include_merges=False))
635
 
        self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
636
 
            ('1', '1', 0)],
637
 
            revisions)
638
 
 
639
 
    def test_get_view_revisions_merge2(self):
640
 
        """Test get_view_revisions when there are merges"""
641
 
        mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
642
 
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
643
 
                                            'forward'))
644
 
        expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
645
 
            ('3a', '2.2.1', 1), ('3b', '2.1.1', 1), ('4b', '4', 0),
646
 
            ('4a', '2.1.2', 1)]
647
 
        self.assertEqual(expected, revisions)
648
 
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
649
 
                                             'forward', include_merges=False))
650
 
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
651
 
            ('4b', '4', 0)],
652
 
            revisions)
653
 
 
654
 
 
655
 
class TestGetRevisionsTouchingFileID(TestCaseWithTransport):
656
 
 
657
 
    def create_tree_with_single_merge(self):
658
 
        """Create a branch with a moderate layout.
659
 
 
660
 
        The revision graph looks like:
661
 
 
662
 
           A
663
 
           |\
664
 
           B C
665
 
           |/
666
 
           D
667
 
 
668
 
        In this graph, A introduced files f1 and f2 and f3.
669
 
        B modifies f1 and f3, and C modifies f2 and f3.
670
 
        D merges the changes from B and C and resolves the conflict for f3.
671
 
        """
672
 
        # TODO: jam 20070218 This seems like it could really be done
673
 
        #       with make_branch_and_memory_tree() if we could just
674
 
        #       create the content of those files.
675
 
        # TODO: jam 20070218 Another alternative is that we would really
676
 
        #       like to only create this tree 1 time for all tests that
677
 
        #       use it. Since 'log' only uses the tree in a readonly
678
 
        #       fashion, it seems a shame to regenerate an identical
679
 
        #       tree for each test.
680
 
        tree = self.make_branch_and_tree('tree')
681
 
        tree.lock_write()
682
 
        self.addCleanup(tree.unlock)
683
 
 
684
 
        self.build_tree_contents([('tree/f1', 'A\n'),
685
 
                                  ('tree/f2', 'A\n'),
686
 
                                  ('tree/f3', 'A\n'),
687
 
                                 ])
688
 
        tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
689
 
        tree.commit('A', rev_id='A')
690
 
 
691
 
        self.build_tree_contents([('tree/f2', 'A\nC\n'),
692
 
                                  ('tree/f3', 'A\nC\n'),
693
 
                                 ])
694
 
        tree.commit('C', rev_id='C')
695
 
        # Revert back to A to build the other history.
696
 
        tree.set_last_revision('A')
697
 
        tree.branch.set_last_revision_info(1, 'A')
698
 
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
699
 
                                  ('tree/f2', 'A\n'),
700
 
                                  ('tree/f3', 'A\nB\n'),
701
 
                                 ])
702
 
        tree.commit('B', rev_id='B')
703
 
        tree.set_parent_ids(['B', 'C'])
704
 
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
705
 
                                  ('tree/f2', 'A\nC\n'),
706
 
                                  ('tree/f3', 'A\nB\nC\n'),
707
 
                                 ])
708
 
        tree.commit('D', rev_id='D')
709
 
 
710
 
        # Switch to a read lock for this tree.
711
 
        # We still have addCleanup(unlock)
712
 
        tree.unlock()
713
 
        tree.lock_read()
714
 
        return tree
715
 
 
716
 
    def test_tree_with_single_merge(self):
717
 
        """Make sure the tree layout is correct."""
718
 
        tree = self.create_tree_with_single_merge()
719
 
        rev_A_tree = tree.branch.repository.revision_tree('A')
720
 
        rev_B_tree = tree.branch.repository.revision_tree('B')
721
 
 
722
 
        f1_changed = (u'f1', 'f1-id', 'file', True, False)
723
 
        f2_changed = (u'f2', 'f2-id', 'file', True, False)
724
 
        f3_changed = (u'f3', 'f3-id', 'file', True, False)
725
 
 
726
 
        delta = rev_B_tree.changes_from(rev_A_tree)
727
 
        self.assertEqual([f1_changed, f3_changed], delta.modified)
728
 
        self.assertEqual([], delta.renamed)
729
 
        self.assertEqual([], delta.added)
730
 
        self.assertEqual([], delta.removed)
731
 
 
732
 
        rev_C_tree = tree.branch.repository.revision_tree('C')
733
 
        delta = rev_C_tree.changes_from(rev_A_tree)
734
 
        self.assertEqual([f2_changed, f3_changed], delta.modified)
735
 
        self.assertEqual([], delta.renamed)
736
 
        self.assertEqual([], delta.added)
737
 
        self.assertEqual([], delta.removed)
738
 
 
739
 
        rev_D_tree = tree.branch.repository.revision_tree('D')
740
 
        delta = rev_D_tree.changes_from(rev_B_tree)
741
 
        self.assertEqual([f2_changed, f3_changed], delta.modified)
742
 
        self.assertEqual([], delta.renamed)
743
 
        self.assertEqual([], delta.added)
744
 
        self.assertEqual([], delta.removed)
745
 
 
746
 
        delta = rev_D_tree.changes_from(rev_C_tree)
747
 
        self.assertEqual([f1_changed, f3_changed], delta.modified)
748
 
        self.assertEqual([], delta.renamed)
749
 
        self.assertEqual([], delta.added)
750
 
        self.assertEqual([], delta.removed)
751
 
 
752
 
    def assertAllRevisionsForFileID(self, tree, file_id, revisions):
753
 
        """Make sure _filter_revisions_touching_file_id returns the right values.
754
 
 
755
 
        Get the return value from _filter_revisions_touching_file_id and make
756
 
        sure they are correct.
757
 
        """
758
 
        # The api for _get_revisions_touching_file_id is a little crazy,
759
 
        # So we do the setup here.
760
 
        mainline = tree.branch.revision_history()
761
 
        mainline.insert(0, None)
762
 
        revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
763
 
        view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
764
 
                                                'reverse', True)
765
 
        actual_revs = log._filter_revisions_touching_file_id(
766
 
                            tree.branch, 
767
 
                            file_id,
768
 
                            mainline,
769
 
                            list(view_revs_iter))
770
 
        self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
771
 
 
772
 
    def test_file_id_f1(self):
773
 
        tree = self.create_tree_with_single_merge()
774
 
        # f1 should be marked as modified by revisions A and B
775
 
        self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
776
 
 
777
 
    def test_file_id_f2(self):
778
 
        tree = self.create_tree_with_single_merge()
779
 
        # f2 should be marked as modified by revisions A, C, and D
780
 
        # because D merged the changes from C.
781
 
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
782
 
 
783
 
    def test_file_id_f3(self):
784
 
        tree = self.create_tree_with_single_merge()
785
 
        # f3 should be marked as modified by revisions A, B, C, and D
786
 
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
787
 
 
788
 
 
789
 
class TestShowChangedRevisions(TestCaseWithTransport):
790
 
 
791
 
    def test_show_changed_revisions_verbose(self):
792
 
        tree = self.make_branch_and_tree('tree_a')
793
 
        self.build_tree(['tree_a/foo'])
794
 
        tree.add('foo')
795
 
        tree.commit('bar', rev_id='bar-id')
796
 
        s = self.make_utf8_encoded_stringio()
797
 
        log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
798
 
        self.assertContainsRe(s.getvalue(), 'bar')
799
 
        self.assertNotContainsRe(s.getvalue(), 'foo')
800
 
 
801
 
 
802
 
class TestLogFormatter(TestCase):
803
 
 
804
 
    def test_short_committer(self):
805
 
        rev = Revision('a-id')
806
 
        rev.committer = 'John Doe <jdoe@example.com>'
807
 
        lf = LogFormatter(None)
808
 
        self.assertEqual('John Doe', lf.short_committer(rev))
809
 
 
810
 
    def test_short_author(self):
811
 
        rev = Revision('a-id')
812
 
        rev.committer = 'John Doe <jdoe@example.com>'
813
 
        lf = LogFormatter(None)
814
 
        self.assertEqual('John Doe', lf.short_author(rev))
815
 
        rev.properties['author'] = 'John Smith <jsmith@example.com>'
816
 
        self.assertEqual('John Smith', lf.short_author(rev))