~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

  • Committer: Martin Pool
  • Date: 2007-11-29 07:12:42 UTC
  • mto: This revision was merged to the branch mainline in revision 3048.
  • Revision ID: mbp@sourcefrog.net-20071129071242-1tcn2a18547udlvw
Fix up calls to TestCase.build_tree passing a string rather than a list

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