/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/selftest/testlog.py

  • Committer: Martin Pool
  • Date: 2005-08-04 22:04:40 UTC
  • Revision ID: mbp@sourcefrog.net-20050804220440-99562df8151d1ac5
- add pending merge from aaron

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
2
 
#
 
1
# Copyright (C) 2005 by Canonical Ltd
 
2
 
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
#
 
7
 
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
#
 
12
 
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
import os
18
 
from cStringIO import StringIO
19
18
 
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)
 
19
from bzrlib.selftest import BzrTestBase
 
20
from bzrlib.log import LogFormatter, show_log, LongLogFormatter
29
21
from bzrlib.branch import Branch
30
 
from bzrlib.errors import InvalidRevisionNumber
31
 
from bzrlib.revision import Revision
 
22
 
 
23
class _LogEntry(object):
 
24
    # should probably move into bzrlib.log?
 
25
    pass
32
26
 
33
27
 
34
28
class LogCatcher(LogFormatter):
40
34
 
41
35
    We should also test the LogFormatter.
42
36
    """
43
 
 
44
 
    supports_delta = True
45
 
 
46
37
    def __init__(self):
47
38
        super(LogCatcher, self).__init__(to_file=None)
48
39
        self.logs = []
49
 
 
50
 
    def log_revision(self, revision):
51
 
        self.logs.append(revision)
52
 
 
53
 
 
54
 
class TestShowLog(TestCaseWithTransport):
55
 
 
 
40
        
 
41
        
 
42
    def show(self, revno, rev, delta):
 
43
        le = _LogEntry()
 
44
        le.revno = revno
 
45
        le.rev = rev
 
46
        le.delta = delta
 
47
        self.logs.append(le)
 
48
 
 
49
 
 
50
class SimpleLogTest(BzrTestBase):
56
51
    def checkDelta(self, delta, **kw):
57
52
        """Check the filenames touched by a delta are as expected."""
58
53
        for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
59
54
            expected = kw.get(n, [])
 
55
 
 
56
            # tests are written with unix paths; fix them up for windows
 
57
            if os.sep != '/':
 
58
                expected = [x.replace('/', os.sep) for x in expected]
 
59
 
60
60
            # strip out only the path components
61
61
            got = [x[0] for x in getattr(delta, n)]
62
62
            self.assertEquals(expected, got)
63
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):
 
64
    
 
65
    def runTest(self):
85
66
        eq = self.assertEquals
 
67
        ass = self.assert_
86
68
        
87
 
        wt = self.make_branch_and_tree('.')
88
 
        b = wt.branch
 
69
        b = Branch('.', init=True)
89
70
 
90
71
        lf = LogCatcher()
91
72
        show_log(b, lf)
92
73
        # no entries yet
93
74
        eq(lf.logs, [])
94
75
 
95
 
        wt.commit('empty commit')
 
76
 
 
77
        b.commit('empty commit')
96
78
        lf = LogCatcher()
97
79
        show_log(b, lf, verbose=True)
98
80
        eq(len(lf.logs), 1)
99
 
        eq(lf.logs[0].revno, '1')
 
81
        eq(lf.logs[0].revno, 1)
100
82
        eq(lf.logs[0].rev.message, 'empty commit')
101
83
        d = lf.logs[0].delta
102
84
        self.log('log delta: %r' % d)
103
85
        self.checkDelta(d)
104
86
 
 
87
 
105
88
        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()
 
89
        b.add('hello')
 
90
        b.commit('add one file')
112
91
        # log using regular thing
113
 
        show_log(b, LongLogFormatter(lf))
114
 
        lf.seek(0)
115
 
        for l in lf.readlines():
116
 
            self.log(l)
 
92
        show_log(b, LongLogFormatter(self.TEST_LOG))
117
93
 
118
94
        # get log as data structure
119
95
        lf = LogCatcher()
121
97
        eq(len(lf.logs), 2)
122
98
        self.log('log entries:')
123
99
        for logentry in lf.logs:
124
 
            self.log('%4s %s' % (logentry.revno, logentry.rev.message))
 
100
            self.log('%4d %s' % (logentry.revno, logentry.rev.message))
125
101
        
126
102
        # first one is most recent
127
103
        logentry = lf.logs[0]
128
 
        eq(logentry.revno, '2')
 
104
        eq(logentry.revno, 2)
129
105
        eq(logentry.rev.message, 'add one file')
130
106
        d = logentry.delta
131
107
        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))
 
108
        # self.checkDelta(d, added=['hello'])
 
109