~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/selftest/testlog.py

  • Committer: Martin Pool
  • Date: 2005-11-04 01:46:31 UTC
  • mto: (1185.33.49 bzr.dev)
  • mto: This revision was merged to the branch mainline in revision 1512.
  • Revision ID: mbp@sourcefrog.net-20051104014631-750e0ad4172c952c
Make biobench directly executable

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