~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

Merge bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2005 by Canonical Ltd
 
2
# -*- coding: utf-8 -*-
 
3
# vim: encoding=utf-8
2
4
#
3
5
# This program is free software; you can redistribute it and/or modify
4
6
# it under the terms of the GNU General Public License as published by
12
14
#
13
15
# You should have received a copy of the GNU General Public License
14
16
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
18
 
17
19
import os
18
20
from cStringIO import StringIO
19
21
 
20
 
from bzrlib import (
21
 
    errors,
22
 
    log,
23
 
    registry,
24
 
    revision,
25
 
    revisionspec,
26
 
    tests,
27
 
    )
28
 
 
29
 
 
30
 
class TestCaseWithoutPropsHandler(tests.TestCaseWithTransport):
31
 
 
32
 
    def setUp(self):
33
 
        super(TestCaseWithoutPropsHandler, self).setUp()
34
 
        # keep a reference to the "current" custom prop. handler registry
35
 
        self.properties_handler_registry = log.properties_handler_registry
36
 
        # clean up the registry in log
37
 
        log.properties_handler_registry = registry.Registry()
38
 
 
39
 
    def _cleanup(self):
40
 
        super(TestCaseWithoutPropsHandler, self)._cleanup()
41
 
        # restore the custom properties handler registry
42
 
        log.properties_handler_registry = self.properties_handler_registry
43
 
 
44
 
 
45
 
class LogCatcher(log.LogFormatter):
 
22
from bzrlib.tests import BzrTestBase, TestCaseWithTransport
 
23
from bzrlib.log import (show_log, 
 
24
                        get_view_revisions, 
 
25
                        LogFormatter, 
 
26
                        LongLogFormatter, 
 
27
                        ShortLogFormatter, 
 
28
                        LineLogFormatter)
 
29
from bzrlib.branch import Branch
 
30
from bzrlib.errors import InvalidRevisionNumber
 
31
 
 
32
 
 
33
class _LogEntry(object):
 
34
    # should probably move into bzrlib.log?
 
35
    pass
 
36
 
 
37
 
 
38
class LogCatcher(LogFormatter):
46
39
    """Pull log messages into list rather than displaying them.
47
40
 
48
41
    For ease of testing we save log messages here rather than actually
51
44
 
52
45
    We should also test the LogFormatter.
53
46
    """
54
 
 
55
 
    supports_delta = True
56
 
 
57
47
    def __init__(self):
58
48
        super(LogCatcher, self).__init__(to_file=None)
59
49
        self.logs = []
60
50
 
61
 
    def log_revision(self, revision):
62
 
        self.logs.append(revision)
63
 
 
64
 
 
65
 
class TestShowLog(tests.TestCaseWithTransport):
 
51
    def show(self, revno, rev, delta):
 
52
        le = _LogEntry()
 
53
        le.revno = revno
 
54
        le.rev = rev
 
55
        le.delta = delta
 
56
        self.logs.append(le)
 
57
 
 
58
 
 
59
class SimpleLogTest(TestCaseWithTransport):
66
60
 
67
61
    def checkDelta(self, delta, **kw):
68
 
        """Check the filenames touched by a delta are as expected.
69
 
 
70
 
        Caller only have to pass in the list of files for each part, all
71
 
        unspecified parts are considered empty (and checked as such).
72
 
        """
 
62
        """Check the filenames touched by a delta are as expected."""
73
63
        for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
74
 
            # By default we expect an empty list
75
64
            expected = kw.get(n, [])
 
65
 
 
66
            # tests are written with unix paths; fix them up for windows
 
67
            #if os.sep != '/':
 
68
            #    expected = [x.replace('/', os.sep) for x in expected]
 
69
 
76
70
            # strip out only the path components
77
71
            got = [x[0] for x in getattr(delta, n)]
78
 
            self.assertEqual(expected, got)
79
 
 
80
 
    def assertInvalidRevisonNumber(self, br, start, end):
81
 
        lf = LogCatcher()
82
 
        self.assertRaises(errors.InvalidRevisionNumber,
83
 
                          log.show_log, br, lf,
84
 
                          start_revision=start, end_revision=end)
85
 
 
86
 
    def test_cur_revno(self):
87
 
        wt = self.make_branch_and_tree('.')
88
 
        b = wt.branch
89
 
 
90
 
        lf = LogCatcher()
91
 
        wt.commit('empty commit')
92
 
        log.show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
93
 
 
94
 
        # Since there is a single revision in the branch all the combinations
95
 
        # below should fail.
96
 
        self.assertInvalidRevisonNumber(b, 2, 1)
97
 
        self.assertInvalidRevisonNumber(b, 1, 2)
98
 
        self.assertInvalidRevisonNumber(b, 0, 2)
99
 
        self.assertInvalidRevisonNumber(b, 1, 0)
100
 
        self.assertInvalidRevisonNumber(b, -1, 1)
101
 
        self.assertInvalidRevisonNumber(b, 1, -1)
102
 
 
103
 
    def test_empty_branch(self):
104
 
        wt = self.make_branch_and_tree('.')
105
 
 
106
 
        lf = LogCatcher()
107
 
        log.show_log(wt.branch, lf)
 
72
            self.assertEquals(expected, got)
 
73
 
 
74
    def test_cur_revno(self):
 
75
        wt = self.make_branch_and_tree('.')
 
76
        b = wt.branch
 
77
 
 
78
        lf = LogCatcher()
 
79
        wt.commit('empty commit')
 
80
        show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
 
81
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
82
                          start_revision=2, end_revision=1) 
 
83
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
84
                          start_revision=1, end_revision=2) 
 
85
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
86
                          start_revision=0, end_revision=2) 
 
87
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
88
                          start_revision=1, end_revision=0) 
 
89
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
90
                          start_revision=-1, end_revision=1) 
 
91
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
92
                          start_revision=1, end_revision=-1) 
 
93
 
 
94
    def test_cur_revno(self):
 
95
        wt = self.make_branch_and_tree('.')
 
96
        b = wt.branch
 
97
 
 
98
        lf = LogCatcher()
 
99
        wt.commit('empty commit')
 
100
        show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
 
101
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
102
                          start_revision=2, end_revision=1) 
 
103
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
104
                          start_revision=1, end_revision=2) 
 
105
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
106
                          start_revision=0, end_revision=2) 
 
107
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
108
                          start_revision=1, end_revision=0) 
 
109
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
110
                          start_revision=-1, end_revision=1) 
 
111
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
112
                          start_revision=1, end_revision=-1) 
 
113
 
 
114
    def test_simple_log(self):
 
115
        eq = self.assertEquals
 
116
        
 
117
        wt = self.make_branch_and_tree('.')
 
118
        b = wt.branch
 
119
 
 
120
        lf = LogCatcher()
 
121
        show_log(b, lf)
108
122
        # no entries yet
109
 
        self.assertEqual([], lf.logs)
110
 
 
111
 
    def test_empty_commit(self):
112
 
        wt = self.make_branch_and_tree('.')
 
123
        eq(lf.logs, [])
113
124
 
114
125
        wt.commit('empty commit')
115
126
        lf = LogCatcher()
116
 
        log.show_log(wt.branch, lf, verbose=True)
117
 
        self.assertEqual(1, len(lf.logs))
118
 
        self.assertEqual('1', lf.logs[0].revno)
119
 
        self.assertEqual('empty commit', lf.logs[0].rev.message)
120
 
        self.checkDelta(lf.logs[0].delta)
 
127
        show_log(b, lf, verbose=True)
 
128
        eq(len(lf.logs), 1)
 
129
        eq(lf.logs[0].revno, 1)
 
130
        eq(lf.logs[0].rev.message, 'empty commit')
 
131
        d = lf.logs[0].delta
 
132
        self.log('log delta: %r' % d)
 
133
        self.checkDelta(d)
121
134
 
122
 
    def test_simple_commit(self):
123
 
        wt = self.make_branch_and_tree('.')
124
 
        wt.commit('empty commit')
125
135
        self.build_tree(['hello'])
126
136
        wt.add('hello')
127
 
        wt.commit('add one file',
128
 
                  committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
129
 
                            u'<test@example.com>')
 
137
        wt.commit('add one file')
 
138
 
 
139
        lf = StringIO()
 
140
        # log using regular thing
 
141
        show_log(b, LongLogFormatter(lf))
 
142
        lf.seek(0)
 
143
        for l in lf.readlines():
 
144
            self.log(l)
 
145
 
 
146
        # get log as data structure
130
147
        lf = LogCatcher()
131
 
        log.show_log(wt.branch, lf, verbose=True)
132
 
        self.assertEqual(2, len(lf.logs))
 
148
        show_log(b, lf, verbose=True)
 
149
        eq(len(lf.logs), 2)
 
150
        self.log('log entries:')
 
151
        for logentry in lf.logs:
 
152
            self.log('%4d %s' % (logentry.revno, logentry.rev.message))
 
153
        
133
154
        # first one is most recent
134
 
        log_entry = lf.logs[0]
135
 
        self.assertEqual('2', log_entry.revno)
136
 
        self.assertEqual('add one file', log_entry.rev.message)
137
 
        self.checkDelta(log_entry.delta, added=['hello'])
138
 
 
139
 
    def test_commit_message_with_control_chars(self):
140
 
        wt = self.make_branch_and_tree('.')
141
 
        msg = u"All 8-bit chars: " +  ''.join([unichr(x) for x in range(256)])
142
 
        msg = msg.replace(u'\r', u'\n')
 
155
        logentry = lf.logs[0]
 
156
        eq(logentry.revno, 2)
 
157
        eq(logentry.rev.message, 'add one file')
 
158
        d = logentry.delta
 
159
        self.log('log 2 delta: %r' % d)
 
160
        # self.checkDelta(d, added=['hello'])
 
161
        
 
162
        # commit a log message with control characters
 
163
        msg = "All 8-bit chars: " +  ''.join([unichr(x) for x in range(256)])
 
164
        self.log("original commit message: %r", msg)
143
165
        wt.commit(msg)
144
166
        lf = LogCatcher()
145
 
        log.show_log(wt.branch, lf, verbose=True)
 
167
        show_log(b, lf, verbose=True)
146
168
        committed_msg = lf.logs[0].rev.message
147
 
        self.assertNotEqual(msg, committed_msg)
148
 
        self.assertTrue(len(committed_msg) > len(msg))
 
169
        self.log("escaped commit message: %r", committed_msg)
 
170
        self.assert_(msg != committed_msg)
 
171
        self.assert_(len(committed_msg) > len(msg))
149
172
 
150
 
    def test_commit_message_without_control_chars(self):
151
 
        wt = self.make_branch_and_tree('.')
 
173
        # Check that log message with only XML-valid characters isn't
152
174
        # escaped.  As ElementTree apparently does some kind of
153
175
        # newline conversion, neither LF (\x0A) nor CR (\x0D) are
154
176
        # included in the test commit message, even though they are
155
177
        # valid XML 1.0 characters.
156
178
        msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
 
179
        self.log("original commit message: %r", msg)
157
180
        wt.commit(msg)
158
181
        lf = LogCatcher()
159
 
        log.show_log(wt.branch, lf, verbose=True)
 
182
        show_log(b, lf, verbose=True)
160
183
        committed_msg = lf.logs[0].rev.message
161
 
        self.assertEqual(msg, committed_msg)
162
 
 
163
 
    def test_deltas_in_merge_revisions(self):
164
 
        """Check deltas created for both mainline and merge revisions"""
165
 
        wt = self.make_branch_and_tree('parent')
166
 
        self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
167
 
        wt.add('file1')
168
 
        wt.add('file2')
169
 
        wt.commit(message='add file1 and file2')
170
 
        self.run_bzr('branch parent child')
171
 
        os.unlink('child/file1')
172
 
        file('child/file2', 'wb').write('hello\n')
173
 
        self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
174
 
            'child'])
175
 
        os.chdir('parent')
176
 
        self.run_bzr('merge ../child')
177
 
        wt.commit('merge child branch')
178
 
        os.chdir('..')
179
 
        b = wt.branch
180
 
        lf = LogCatcher()
181
 
        lf.supports_merge_revisions = True
182
 
        log.show_log(b, lf, verbose=True)
183
 
 
184
 
        self.assertEqual(3, len(lf.logs))
185
 
 
186
 
        logentry = lf.logs[0]
187
 
        self.assertEqual('2', logentry.revno)
188
 
        self.assertEqual('merge child branch', logentry.rev.message)
189
 
        self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
190
 
 
191
 
        logentry = lf.logs[1]
192
 
        self.assertEqual('1.1.1', logentry.revno)
193
 
        self.assertEqual('remove file1 and modify file2', logentry.rev.message)
194
 
        self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
195
 
 
196
 
        logentry = lf.logs[2]
197
 
        self.assertEqual('1', logentry.revno)
198
 
        self.assertEqual('add file1 and file2', logentry.rev.message)
199
 
        self.checkDelta(logentry.delta, added=['file1', 'file2'])
200
 
 
201
 
 
202
 
def make_commits_with_trailing_newlines(wt):
203
 
    """Helper method for LogFormatter tests"""
204
 
    b = wt.branch
205
 
    b.nick='test'
206
 
    open('a', 'wb').write('hello moto\n')
207
 
    wt.add('a')
208
 
    wt.commit('simple log message', rev_id='a1',
209
 
              timestamp=1132586655.459960938, timezone=-6*3600,
210
 
              committer='Joe Foo <joe@foo.com>')
211
 
    open('b', 'wb').write('goodbye\n')
212
 
    wt.add('b')
213
 
    wt.commit('multiline\nlog\nmessage\n', rev_id='a2',
214
 
              timestamp=1132586842.411175966, timezone=-6*3600,
215
 
              committer='Joe Foo <joe@foo.com>',
216
 
              authors=['Joe Bar <joe@bar.com>'])
217
 
 
218
 
    open('c', 'wb').write('just another manic monday\n')
219
 
    wt.add('c')
220
 
    wt.commit('single line with trailing newline\n', rev_id='a3',
221
 
              timestamp=1132587176.835228920, timezone=-6*3600,
222
 
              committer = 'Joe Foo <joe@foo.com>')
223
 
    return b
224
 
 
225
 
 
226
 
def normalize_log(log):
227
 
    """Replaces the variable lines of logs with fixed lines"""
228
 
    author = 'author: Dolor Sit <test@example.com>'
229
 
    committer = 'committer: Lorem Ipsum <test@example.com>'
230
 
    lines = log.splitlines(True)
231
 
    for idx,line in enumerate(lines):
232
 
        stripped_line = line.lstrip()
233
 
        indent = ' ' * (len(line) - len(stripped_line))
234
 
        if stripped_line.startswith('author:'):
235
 
            lines[idx] = indent + author + '\n'
236
 
        elif stripped_line.startswith('committer:'):
237
 
            lines[idx] = indent + committer + '\n'
238
 
        elif stripped_line.startswith('timestamp:'):
239
 
            lines[idx] = indent + 'timestamp: Just now\n'
240
 
    return ''.join(lines)
241
 
 
242
 
 
243
 
class TestShortLogFormatter(tests.TestCaseWithTransport):
 
184
        self.log("escaped commit message: %r", committed_msg)
 
185
        self.assert_(msg == committed_msg)
244
186
 
245
187
    def test_trailing_newlines(self):
246
188
        wt = self.make_branch_and_tree('.')
247
 
        b = make_commits_with_trailing_newlines(wt)
248
 
        sio = self.make_utf8_encoded_stringio()
249
 
        lf = log.ShortLogFormatter(to_file=sio)
250
 
        log.show_log(b, lf)
251
 
        self.assertEqualDiff("""\
 
189
        b = wt.branch
 
190
        b.nick='test'
 
191
        open('a', 'wb').write('hello moto\n')
 
192
        wt.add('a')
 
193
        wt.commit('simple log message', rev_id='a1'
 
194
                , timestamp=1132586655.459960938, timezone=-6*3600
 
195
                , committer='Joe Foo <joe@foo.com>')
 
196
        open('b', 'wb').write('goodbye\n')
 
197
        wt.add('b')
 
198
        wt.commit('multiline\nlog\nmessage\n', rev_id='a2'
 
199
                , timestamp=1132586842.411175966, timezone=-6*3600
 
200
                , committer='Joe Foo <joe@foo.com>')
 
201
 
 
202
        open('c', 'wb').write('just another manic monday\n')
 
203
        wt.add('c')
 
204
        wt.commit('single line with trailing newline\n', rev_id='a3'
 
205
                , timestamp=1132587176.835228920, timezone=-6*3600
 
206
                , committer = 'Joe Foo <joe@foo.com>')
 
207
 
 
208
        sio = StringIO()
 
209
        lf = ShortLogFormatter(to_file=sio)
 
210
        show_log(b, lf)
 
211
        self.assertEquals(sio.getvalue(), """\
252
212
    3 Joe Foo\t2005-11-21
253
213
      single line with trailing newline
254
214
 
255
 
    2 Joe Bar\t2005-11-21
 
215
    2 Joe Foo\t2005-11-21
256
216
      multiline
257
217
      log
258
218
      message
260
220
    1 Joe Foo\t2005-11-21
261
221
      simple log message
262
222
 
263
 
""",
264
 
                             sio.getvalue())
265
 
 
266
 
    def _prepare_tree_with_merges(self, with_tags=False):
267
 
        wt = self.make_branch_and_memory_tree('.')
268
 
        wt.lock_write()
269
 
        self.addCleanup(wt.unlock)
270
 
        wt.add('')
271
 
        wt.commit('rev-1', rev_id='rev-1',
272
 
                  timestamp=1132586655, timezone=36000,
273
 
                  committer='Joe Foo <joe@foo.com>')
274
 
        wt.commit('rev-merged', rev_id='rev-2a',
275
 
                  timestamp=1132586700, timezone=36000,
276
 
                  committer='Joe Foo <joe@foo.com>')
277
 
        wt.set_parent_ids(['rev-1', 'rev-2a'])
278
 
        wt.branch.set_last_revision_info(1, 'rev-1')
279
 
        wt.commit('rev-2', rev_id='rev-2b',
280
 
                  timestamp=1132586800, timezone=36000,
281
 
                  committer='Joe Foo <joe@foo.com>')
282
 
        if with_tags:
283
 
            branch = wt.branch
284
 
            branch.tags.set_tag('v0.2', 'rev-2b')
285
 
            wt.commit('rev-3', rev_id='rev-3',
286
 
                      timestamp=1132586900, timezone=36000,
287
 
                      committer='Jane Foo <jane@foo.com>')
288
 
            branch.tags.set_tag('v1.0rc1', 'rev-3')
289
 
            branch.tags.set_tag('v1.0', 'rev-3')
290
 
        return wt
291
 
 
292
 
    def test_short_log_with_merges(self):
293
 
        wt = self._prepare_tree_with_merges()
294
 
        logfile = self.make_utf8_encoded_stringio()
295
 
        formatter = log.ShortLogFormatter(to_file=logfile)
296
 
        log.show_log(wt.branch, formatter)
297
 
        self.assertEqualDiff("""\
298
 
    2 Joe Foo\t2005-11-22 [merge]
299
 
      rev-2
300
 
 
301
 
    1 Joe Foo\t2005-11-22
302
 
      rev-1
303
 
 
304
 
Use --levels 0 (or -n0) to see merged revisions.
305
 
""",
306
 
                             logfile.getvalue())
307
 
 
308
 
    def test_short_log_with_merges_and_range(self):
309
 
        wt = self.make_branch_and_memory_tree('.')
310
 
        wt.lock_write()
311
 
        self.addCleanup(wt.unlock)
312
 
        wt.add('')
313
 
        wt.commit('rev-1', rev_id='rev-1',
314
 
                  timestamp=1132586655, timezone=36000,
315
 
                  committer='Joe Foo <joe@foo.com>')
316
 
        wt.commit('rev-merged', rev_id='rev-2a',
317
 
                  timestamp=1132586700, timezone=36000,
318
 
                  committer='Joe Foo <joe@foo.com>')
319
 
        wt.branch.set_last_revision_info(1, 'rev-1')
320
 
        wt.set_parent_ids(['rev-1', 'rev-2a'])
321
 
        wt.commit('rev-2b', rev_id='rev-2b',
322
 
                  timestamp=1132586800, timezone=36000,
323
 
                  committer='Joe Foo <joe@foo.com>')
324
 
        wt.commit('rev-3a', rev_id='rev-3a',
325
 
                  timestamp=1132586800, timezone=36000,
326
 
                  committer='Joe Foo <joe@foo.com>')
327
 
        wt.branch.set_last_revision_info(2, 'rev-2b')
328
 
        wt.set_parent_ids(['rev-2b', 'rev-3a'])
329
 
        wt.commit('rev-3b', rev_id='rev-3b',
330
 
                  timestamp=1132586800, timezone=36000,
331
 
                  committer='Joe Foo <joe@foo.com>')
332
 
        logfile = self.make_utf8_encoded_stringio()
333
 
        formatter = log.ShortLogFormatter(to_file=logfile)
334
 
        log.show_log(wt.branch, formatter,
335
 
            start_revision=2, end_revision=3)
336
 
        self.assertEqualDiff("""\
337
 
    3 Joe Foo\t2005-11-22 [merge]
338
 
      rev-3b
339
 
 
340
 
    2 Joe Foo\t2005-11-22 [merge]
341
 
      rev-2b
342
 
 
343
 
Use --levels 0 (or -n0) to see merged revisions.
344
 
""",
345
 
                             logfile.getvalue())
346
 
 
347
 
    def test_short_log_with_tags(self):
348
 
        wt = self._prepare_tree_with_merges(with_tags=True)
349
 
        logfile = self.make_utf8_encoded_stringio()
350
 
        formatter = log.ShortLogFormatter(to_file=logfile)
351
 
        log.show_log(wt.branch, formatter)
352
 
        self.assertEqualDiff("""\
353
 
    3 Jane Foo\t2005-11-22 {v1.0, v1.0rc1}
354
 
      rev-3
355
 
 
356
 
    2 Joe Foo\t2005-11-22 {v0.2} [merge]
357
 
      rev-2
358
 
 
359
 
    1 Joe Foo\t2005-11-22
360
 
      rev-1
361
 
 
362
 
Use --levels 0 (or -n0) to see merged revisions.
363
 
""",
364
 
                             logfile.getvalue())
365
 
 
366
 
    def test_short_log_single_merge_revision(self):
367
 
        wt = self.make_branch_and_memory_tree('.')
368
 
        wt.lock_write()
369
 
        self.addCleanup(wt.unlock)
370
 
        wt.add('')
371
 
        wt.commit('rev-1', rev_id='rev-1',
372
 
                  timestamp=1132586655, timezone=36000,
373
 
                  committer='Joe Foo <joe@foo.com>')
374
 
        wt.commit('rev-merged', rev_id='rev-2a',
375
 
                  timestamp=1132586700, timezone=36000,
376
 
                  committer='Joe Foo <joe@foo.com>')
377
 
        wt.set_parent_ids(['rev-1', 'rev-2a'])
378
 
        wt.branch.set_last_revision_info(1, 'rev-1')
379
 
        wt.commit('rev-2', rev_id='rev-2b',
380
 
                  timestamp=1132586800, timezone=36000,
381
 
                  committer='Joe Foo <joe@foo.com>')
382
 
        logfile = self.make_utf8_encoded_stringio()
383
 
        formatter = log.ShortLogFormatter(to_file=logfile)
384
 
        revspec = revisionspec.RevisionSpec.from_string('1.1.1')
385
 
        wtb = wt.branch
386
 
        rev = revspec.in_history(wtb)
387
 
        log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
388
 
        self.assertEqualDiff("""\
389
 
      1.1.1 Joe Foo\t2005-11-22
390
 
            rev-merged
391
 
 
392
 
""",
393
 
                             logfile.getvalue())
394
 
 
395
 
 
396
 
class TestShortLogFormatterWithMergeRevisions(tests.TestCaseWithTransport):
397
 
 
398
 
    def test_short_merge_revs_log_with_merges(self):
399
 
        wt = self.make_branch_and_memory_tree('.')
400
 
        wt.lock_write()
401
 
        self.addCleanup(wt.unlock)
402
 
        wt.add('')
403
 
        wt.commit('rev-1', rev_id='rev-1',
404
 
                  timestamp=1132586655, timezone=36000,
405
 
                  committer='Joe Foo <joe@foo.com>')
406
 
        wt.commit('rev-merged', rev_id='rev-2a',
407
 
                  timestamp=1132586700, timezone=36000,
408
 
                  committer='Joe Foo <joe@foo.com>')
409
 
        wt.set_parent_ids(['rev-1', 'rev-2a'])
410
 
        wt.branch.set_last_revision_info(1, 'rev-1')
411
 
        wt.commit('rev-2', rev_id='rev-2b',
412
 
                  timestamp=1132586800, timezone=36000,
413
 
                  committer='Joe Foo <joe@foo.com>')
414
 
        logfile = self.make_utf8_encoded_stringio()
415
 
        formatter = log.ShortLogFormatter(to_file=logfile, levels=0)
416
 
        log.show_log(wt.branch, formatter)
417
 
        # Note that the 1.1.1 indenting is in fact correct given that
418
 
        # the revision numbers are right justified within 5 characters
419
 
        # for mainline revnos and 9 characters for dotted revnos.
420
 
        self.assertEqualDiff("""\
421
 
    2 Joe Foo\t2005-11-22 [merge]
422
 
      rev-2
423
 
 
424
 
          1.1.1 Joe Foo\t2005-11-22
425
 
                rev-merged
426
 
 
427
 
    1 Joe Foo\t2005-11-22
428
 
      rev-1
429
 
 
430
 
""",
431
 
                             logfile.getvalue())
432
 
 
433
 
    def test_short_merge_revs_log_single_merge_revision(self):
434
 
        wt = self.make_branch_and_memory_tree('.')
435
 
        wt.lock_write()
436
 
        self.addCleanup(wt.unlock)
437
 
        wt.add('')
438
 
        wt.commit('rev-1', rev_id='rev-1',
439
 
                  timestamp=1132586655, timezone=36000,
440
 
                  committer='Joe Foo <joe@foo.com>')
441
 
        wt.commit('rev-merged', rev_id='rev-2a',
442
 
                  timestamp=1132586700, timezone=36000,
443
 
                  committer='Joe Foo <joe@foo.com>')
444
 
        wt.set_parent_ids(['rev-1', 'rev-2a'])
445
 
        wt.branch.set_last_revision_info(1, 'rev-1')
446
 
        wt.commit('rev-2', rev_id='rev-2b',
447
 
                  timestamp=1132586800, timezone=36000,
448
 
                  committer='Joe Foo <joe@foo.com>')
449
 
        logfile = self.make_utf8_encoded_stringio()
450
 
        formatter = log.ShortLogFormatter(to_file=logfile, levels=0)
451
 
        revspec = revisionspec.RevisionSpec.from_string('1.1.1')
452
 
        wtb = wt.branch
453
 
        rev = revspec.in_history(wtb)
454
 
        log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
455
 
        self.assertEqualDiff("""\
456
 
      1.1.1 Joe Foo\t2005-11-22
457
 
            rev-merged
458
 
 
459
 
""",
460
 
                             logfile.getvalue())
461
 
 
462
 
 
463
 
class TestLongLogFormatter(TestCaseWithoutPropsHandler):
464
 
 
 
223
""")
 
224
 
 
225
        sio = StringIO()
 
226
        lf = LongLogFormatter(to_file=sio)
 
227
        show_log(b, lf)
 
228
        self.assertEquals(sio.getvalue(), """\
 
229
------------------------------------------------------------
 
230
revno: 3
 
231
committer: Joe Foo <joe@foo.com>
 
232
branch nick: test
 
233
timestamp: Mon 2005-11-21 09:32:56 -0600
 
234
message:
 
235
  single line with trailing newline
 
236
------------------------------------------------------------
 
237
revno: 2
 
238
committer: Joe Foo <joe@foo.com>
 
239
branch nick: test
 
240
timestamp: Mon 2005-11-21 09:27:22 -0600
 
241
message:
 
242
  multiline
 
243
  log
 
244
  message
 
245
------------------------------------------------------------
 
246
revno: 1
 
247
committer: Joe Foo <joe@foo.com>
 
248
branch nick: test
 
249
timestamp: Mon 2005-11-21 09:24:15 -0600
 
250
message:
 
251
  simple log message
 
252
""")
 
253
        
465
254
    def test_verbose_log(self):
466
255
        """Verbose log includes changed files
467
 
 
468
 
        bug #4676
469
 
        """
470
 
        wt = self.make_branch_and_tree('.')
471
 
        b = wt.branch
472
 
        self.build_tree(['a'])
473
 
        wt.add('a')
474
 
        # XXX: why does a longer nick show up?
475
 
        b.nick = 'test_verbose_log'
476
 
        wt.commit(message='add a',
477
 
                  timestamp=1132711707,
478
 
                  timezone=36000,
479
 
                  committer='Lorem Ipsum <test@example.com>')
480
 
        logfile = file('out.tmp', 'w+')
481
 
        formatter = log.LongLogFormatter(to_file=logfile)
482
 
        log.show_log(b, formatter, verbose=True)
483
 
        logfile.flush()
484
 
        logfile.seek(0)
485
 
        log_contents = logfile.read()
486
 
        self.assertEqualDiff('''\
487
 
------------------------------------------------------------
488
 
revno: 1
489
 
committer: Lorem Ipsum <test@example.com>
490
 
branch nick: test_verbose_log
491
 
timestamp: Wed 2005-11-23 12:08:27 +1000
492
 
message:
493
 
  add a
494
 
added:
495
 
  a
496
 
''',
497
 
                             log_contents)
498
 
 
499
 
    def test_merges_are_indented_by_level(self):
500
 
        wt = self.make_branch_and_tree('parent')
501
 
        wt.commit('first post')
502
 
        self.run_bzr('branch parent child')
503
 
        self.run_bzr(['commit', '-m', 'branch 1', '--unchanged', 'child'])
504
 
        self.run_bzr('branch child smallerchild')
505
 
        self.run_bzr(['commit', '-m', 'branch 2', '--unchanged',
506
 
            'smallerchild'])
507
 
        os.chdir('child')
508
 
        self.run_bzr('merge ../smallerchild')
509
 
        self.run_bzr(['commit', '-m', 'merge branch 2'])
510
 
        os.chdir('../parent')
511
 
        self.run_bzr('merge ../child')
512
 
        wt.commit('merge branch 1')
513
 
        b = wt.branch
514
 
        sio = self.make_utf8_encoded_stringio()
515
 
        lf = log.LongLogFormatter(to_file=sio, levels=0)
516
 
        log.show_log(b, lf, verbose=True)
517
 
        the_log = normalize_log(sio.getvalue())
518
 
        self.assertEqualDiff("""\
519
 
------------------------------------------------------------
520
 
revno: 2 [merge]
521
 
committer: Lorem Ipsum <test@example.com>
522
 
branch nick: parent
523
 
timestamp: Just now
524
 
message:
525
 
  merge branch 1
526
 
    ------------------------------------------------------------
527
 
    revno: 1.1.2 [merge]
528
 
    committer: Lorem Ipsum <test@example.com>
529
 
    branch nick: child
530
 
    timestamp: Just now
531
 
    message:
532
 
      merge branch 2
533
 
        ------------------------------------------------------------
534
 
        revno: 1.2.1
535
 
        committer: Lorem Ipsum <test@example.com>
536
 
        branch nick: smallerchild
537
 
        timestamp: Just now
538
 
        message:
539
 
          branch 2
540
 
    ------------------------------------------------------------
541
 
    revno: 1.1.1
542
 
    committer: Lorem Ipsum <test@example.com>
543
 
    branch nick: child
544
 
    timestamp: Just now
545
 
    message:
546
 
      branch 1
547
 
------------------------------------------------------------
548
 
revno: 1
549
 
committer: Lorem Ipsum <test@example.com>
550
 
branch nick: parent
551
 
timestamp: Just now
552
 
message:
553
 
  first post
554
 
""",
555
 
                             the_log)
556
 
 
557
 
    def test_verbose_merge_revisions_contain_deltas(self):
558
 
        wt = self.make_branch_and_tree('parent')
559
 
        self.build_tree(['parent/f1', 'parent/f2'])
560
 
        wt.add(['f1','f2'])
561
 
        wt.commit('first post')
562
 
        self.run_bzr('branch parent child')
563
 
        os.unlink('child/f1')
564
 
        file('child/f2', 'wb').write('hello\n')
565
 
        self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
566
 
            'child'])
567
 
        os.chdir('parent')
568
 
        self.run_bzr('merge ../child')
569
 
        wt.commit('merge branch 1')
570
 
        b = wt.branch
571
 
        sio = self.make_utf8_encoded_stringio()
572
 
        lf = log.LongLogFormatter(to_file=sio, levels=0)
573
 
        log.show_log(b, lf, verbose=True)
574
 
        the_log = normalize_log(sio.getvalue())
575
 
        self.assertEqualDiff("""\
576
 
------------------------------------------------------------
577
 
revno: 2 [merge]
578
 
committer: Lorem Ipsum <test@example.com>
579
 
branch nick: parent
580
 
timestamp: Just now
581
 
message:
582
 
  merge branch 1
583
 
removed:
584
 
  f1
585
 
modified:
586
 
  f2
587
 
    ------------------------------------------------------------
588
 
    revno: 1.1.1
589
 
    committer: Lorem Ipsum <test@example.com>
590
 
    branch nick: child
591
 
    timestamp: Just now
592
 
    message:
593
 
      removed f1 and modified f2
594
 
    removed:
595
 
      f1
596
 
    modified:
597
 
      f2
598
 
------------------------------------------------------------
599
 
revno: 1
600
 
committer: Lorem Ipsum <test@example.com>
601
 
branch nick: parent
602
 
timestamp: Just now
603
 
message:
604
 
  first post
605
 
added:
606
 
  f1
607
 
  f2
608
 
""",
609
 
                             the_log)
610
 
 
611
 
    def test_trailing_newlines(self):
612
 
        wt = self.make_branch_and_tree('.')
613
 
        b = make_commits_with_trailing_newlines(wt)
614
 
        sio = self.make_utf8_encoded_stringio()
615
 
        lf = log.LongLogFormatter(to_file=sio)
616
 
        log.show_log(b, lf)
617
 
        self.assertEqualDiff("""\
618
 
------------------------------------------------------------
619
 
revno: 3
620
 
committer: Joe Foo <joe@foo.com>
621
 
branch nick: test
622
 
timestamp: Mon 2005-11-21 09:32:56 -0600
623
 
message:
624
 
  single line with trailing newline
625
 
------------------------------------------------------------
626
 
revno: 2
627
 
author: Joe Bar <joe@bar.com>
628
 
committer: Joe Foo <joe@foo.com>
629
 
branch nick: test
630
 
timestamp: Mon 2005-11-21 09:27:22 -0600
631
 
message:
632
 
  multiline
633
 
  log
634
 
  message
635
 
------------------------------------------------------------
636
 
revno: 1
637
 
committer: Joe Foo <joe@foo.com>
638
 
branch nick: test
639
 
timestamp: Mon 2005-11-21 09:24:15 -0600
640
 
message:
641
 
  simple log message
642
 
""",
643
 
                             sio.getvalue())
644
 
 
645
 
    def test_author_in_log(self):
646
 
        """Log includes the author name if it's set in
647
 
        the revision properties
648
 
        """
649
 
        wt = self.make_branch_and_tree('.')
650
 
        b = wt.branch
651
 
        self.build_tree(['a'])
652
 
        wt.add('a')
653
 
        b.nick = 'test_author_log'
654
 
        wt.commit(message='add a',
655
 
                  timestamp=1132711707,
656
 
                  timezone=36000,
657
 
                  committer='Lorem Ipsum <test@example.com>',
658
 
                  authors=['John Doe <jdoe@example.com>',
659
 
                           'Jane Rey <jrey@example.com>'])
660
 
        sio = StringIO()
661
 
        formatter = log.LongLogFormatter(to_file=sio)
662
 
        log.show_log(b, formatter)
663
 
        self.assertEqualDiff('''\
664
 
------------------------------------------------------------
665
 
revno: 1
666
 
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
667
 
committer: Lorem Ipsum <test@example.com>
668
 
branch nick: test_author_log
669
 
timestamp: Wed 2005-11-23 12:08:27 +1000
670
 
message:
671
 
  add a
672
 
''',
673
 
                             sio.getvalue())
674
 
 
675
 
    def test_properties_in_log(self):
676
 
        """Log includes the custom properties returned by the registered
677
 
        handlers.
678
 
        """
679
 
        wt = self.make_branch_and_tree('.')
680
 
        b = wt.branch
681
 
        self.build_tree(['a'])
682
 
        wt.add('a')
683
 
        b.nick = 'test_properties_in_log'
684
 
        wt.commit(message='add a',
685
 
                  timestamp=1132711707,
686
 
                  timezone=36000,
687
 
                  committer='Lorem Ipsum <test@example.com>',
688
 
                  authors=['John Doe <jdoe@example.com>'])
689
 
        sio = StringIO()
690
 
        formatter = log.LongLogFormatter(to_file=sio)
691
 
        try:
692
 
            def trivial_custom_prop_handler(revision):
693
 
                return {'test_prop':'test_value'}
694
 
 
695
 
            log.properties_handler_registry.register(
696
 
                'trivial_custom_prop_handler',
697
 
                trivial_custom_prop_handler)
698
 
            log.show_log(b, formatter)
699
 
        finally:
700
 
            log.properties_handler_registry.remove(
701
 
                'trivial_custom_prop_handler')
702
 
            self.assertEqualDiff('''\
703
 
------------------------------------------------------------
704
 
revno: 1
705
 
test_prop: test_value
706
 
author: John Doe <jdoe@example.com>
707
 
committer: Lorem Ipsum <test@example.com>
708
 
branch nick: test_properties_in_log
709
 
timestamp: Wed 2005-11-23 12:08:27 +1000
710
 
message:
711
 
  add a
712
 
''',
713
 
                                 sio.getvalue())
714
 
 
715
 
    def test_properties_in_short_log(self):
716
 
        """Log includes the custom properties returned by the registered
717
 
        handlers.
718
 
        """
719
 
        wt = self.make_branch_and_tree('.')
720
 
        b = wt.branch
721
 
        self.build_tree(['a'])
722
 
        wt.add('a')
723
 
        b.nick = 'test_properties_in_short_log'
724
 
        wt.commit(message='add a',
725
 
                  timestamp=1132711707,
726
 
                  timezone=36000,
727
 
                  committer='Lorem Ipsum <test@example.com>',
728
 
                  authors=['John Doe <jdoe@example.com>'])
729
 
        sio = StringIO()
730
 
        formatter = log.ShortLogFormatter(to_file=sio)
731
 
        try:
732
 
            def trivial_custom_prop_handler(revision):
733
 
                return {'test_prop':'test_value'}
734
 
 
735
 
            log.properties_handler_registry.register(
736
 
                'trivial_custom_prop_handler',
737
 
                trivial_custom_prop_handler)
738
 
            log.show_log(b, formatter)
739
 
        finally:
740
 
            log.properties_handler_registry.remove(
741
 
                'trivial_custom_prop_handler')
742
 
            self.assertEqualDiff('''\
743
 
    1 John Doe\t2005-11-23
744
 
      test_prop: test_value
745
 
      add a
746
 
 
747
 
''',
748
 
                                 sio.getvalue())
749
 
 
750
 
    def test_error_in_properties_handler(self):
751
 
        """Log includes the custom properties returned by the registered
752
 
        handlers.
753
 
        """
754
 
        wt = self.make_branch_and_tree('.')
755
 
        b = wt.branch
756
 
        self.build_tree(['a'])
757
 
        wt.add('a')
758
 
        b.nick = 'test_author_log'
759
 
        wt.commit(message='add a',
760
 
                  timestamp=1132711707,
761
 
                  timezone=36000,
762
 
                  committer='Lorem Ipsum <test@example.com>',
763
 
                  authors=['John Doe <jdoe@example.com>'],
764
 
                  revprops={'first_prop':'first_value'})
765
 
        sio = StringIO()
766
 
        formatter = log.LongLogFormatter(to_file=sio)
767
 
        try:
768
 
            def trivial_custom_prop_handler(revision):
769
 
                raise StandardError("a test error")
770
 
 
771
 
            log.properties_handler_registry.register(
772
 
                'trivial_custom_prop_handler',
773
 
                trivial_custom_prop_handler)
774
 
            self.assertRaises(StandardError, log.show_log, b, formatter,)
775
 
        finally:
776
 
            log.properties_handler_registry.remove(
777
 
                'trivial_custom_prop_handler')
778
 
 
779
 
    def test_properties_handler_bad_argument(self):
780
 
        wt = self.make_branch_and_tree('.')
781
 
        b = wt.branch
782
 
        self.build_tree(['a'])
783
 
        wt.add('a')
784
 
        b.nick = 'test_author_log'
785
 
        wt.commit(message='add a',
786
 
                  timestamp=1132711707,
787
 
                  timezone=36000,
788
 
                  committer='Lorem Ipsum <test@example.com>',
789
 
                  authors=['John Doe <jdoe@example.com>'],
790
 
                  revprops={'a_prop':'test_value'})
791
 
        sio = StringIO()
792
 
        formatter = log.LongLogFormatter(to_file=sio)
793
 
        try:
794
 
            def bad_argument_prop_handler(revision):
795
 
                return {'custom_prop_name':revision.properties['a_prop']}
796
 
 
797
 
            log.properties_handler_registry.register(
798
 
                'bad_argument_prop_handler',
799
 
                bad_argument_prop_handler)
800
 
 
801
 
            self.assertRaises(AttributeError, formatter.show_properties,
802
 
                              'a revision', '')
803
 
 
804
 
            revision = b.repository.get_revision(b.last_revision())
805
 
            formatter.show_properties(revision, '')
806
 
            self.assertEqualDiff('''custom_prop_name: test_value\n''',
807
 
                                 sio.getvalue())
808
 
        finally:
809
 
            log.properties_handler_registry.remove(
810
 
                'bad_argument_prop_handler')
811
 
 
812
 
 
813
 
class TestLongLogFormatterWithoutMergeRevisions(TestCaseWithoutPropsHandler):
814
 
 
815
 
    def test_long_verbose_log(self):
816
 
        """Verbose log includes changed files
817
 
 
818
 
        bug #4676
819
 
        """
820
 
        wt = self.make_branch_and_tree('.')
821
 
        b = wt.branch
822
 
        self.build_tree(['a'])
823
 
        wt.add('a')
824
 
        # XXX: why does a longer nick show up?
825
 
        b.nick = 'test_verbose_log'
826
 
        wt.commit(message='add a',
827
 
                  timestamp=1132711707,
828
 
                  timezone=36000,
829
 
                  committer='Lorem Ipsum <test@example.com>')
830
 
        logfile = file('out.tmp', 'w+')
831
 
        formatter = log.LongLogFormatter(to_file=logfile, levels=1)
832
 
        log.show_log(b, formatter, verbose=True)
833
 
        logfile.flush()
834
 
        logfile.seek(0)
835
 
        log_contents = logfile.read()
836
 
        self.assertEqualDiff('''\
837
 
------------------------------------------------------------
838
 
revno: 1
839
 
committer: Lorem Ipsum <test@example.com>
840
 
branch nick: test_verbose_log
841
 
timestamp: Wed 2005-11-23 12:08:27 +1000
842
 
message:
843
 
  add a
844
 
added:
845
 
  a
846
 
''',
847
 
                             log_contents)
848
 
 
849
 
    def test_long_verbose_contain_deltas(self):
850
 
        wt = self.make_branch_and_tree('parent')
851
 
        self.build_tree(['parent/f1', 'parent/f2'])
852
 
        wt.add(['f1','f2'])
853
 
        wt.commit('first post')
854
 
        self.run_bzr('branch parent child')
855
 
        os.unlink('child/f1')
856
 
        file('child/f2', 'wb').write('hello\n')
857
 
        self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
858
 
            'child'])
859
 
        os.chdir('parent')
860
 
        self.run_bzr('merge ../child')
861
 
        wt.commit('merge branch 1')
862
 
        b = wt.branch
863
 
        sio = self.make_utf8_encoded_stringio()
864
 
        lf = log.LongLogFormatter(to_file=sio, levels=1)
865
 
        log.show_log(b, lf, verbose=True)
866
 
        the_log = normalize_log(sio.getvalue())
867
 
        self.assertEqualDiff("""\
868
 
------------------------------------------------------------
869
 
revno: 2 [merge]
870
 
committer: Lorem Ipsum <test@example.com>
871
 
branch nick: parent
872
 
timestamp: Just now
873
 
message:
874
 
  merge branch 1
875
 
removed:
876
 
  f1
877
 
modified:
878
 
  f2
879
 
------------------------------------------------------------
880
 
revno: 1
881
 
committer: Lorem Ipsum <test@example.com>
882
 
branch nick: parent
883
 
timestamp: Just now
884
 
message:
885
 
  first post
886
 
added:
887
 
  f1
888
 
  f2
889
 
------------------------------------------------------------
890
 
Use --levels 0 (or -n0) to see merged revisions.
891
 
""",
892
 
                             the_log)
893
 
 
894
 
    def test_long_trailing_newlines(self):
895
 
        wt = self.make_branch_and_tree('.')
896
 
        b = make_commits_with_trailing_newlines(wt)
897
 
        sio = self.make_utf8_encoded_stringio()
898
 
        lf = log.LongLogFormatter(to_file=sio, levels=1)
899
 
        log.show_log(b, lf)
900
 
        self.assertEqualDiff("""\
901
 
------------------------------------------------------------
902
 
revno: 3
903
 
committer: Joe Foo <joe@foo.com>
904
 
branch nick: test
905
 
timestamp: Mon 2005-11-21 09:32:56 -0600
906
 
message:
907
 
  single line with trailing newline
908
 
------------------------------------------------------------
909
 
revno: 2
910
 
author: Joe Bar <joe@bar.com>
911
 
committer: Joe Foo <joe@foo.com>
912
 
branch nick: test
913
 
timestamp: Mon 2005-11-21 09:27:22 -0600
914
 
message:
915
 
  multiline
916
 
  log
917
 
  message
918
 
------------------------------------------------------------
919
 
revno: 1
920
 
committer: Joe Foo <joe@foo.com>
921
 
branch nick: test
922
 
timestamp: Mon 2005-11-21 09:24:15 -0600
923
 
message:
924
 
  simple log message
925
 
""",
926
 
                             sio.getvalue())
927
 
 
928
 
    def test_long_author_in_log(self):
929
 
        """Log includes the author name if it's set in
930
 
        the revision properties
931
 
        """
932
 
        wt = self.make_branch_and_tree('.')
933
 
        b = wt.branch
934
 
        self.build_tree(['a'])
935
 
        wt.add('a')
936
 
        b.nick = 'test_author_log'
937
 
        wt.commit(message='add a',
938
 
                  timestamp=1132711707,
939
 
                  timezone=36000,
940
 
                  committer='Lorem Ipsum <test@example.com>',
941
 
                  authors=['John Doe <jdoe@example.com>'])
942
 
        sio = StringIO()
943
 
        formatter = log.LongLogFormatter(to_file=sio, levels=1)
944
 
        log.show_log(b, formatter)
945
 
        self.assertEqualDiff('''\
946
 
------------------------------------------------------------
947
 
revno: 1
948
 
author: John Doe <jdoe@example.com>
949
 
committer: Lorem Ipsum <test@example.com>
950
 
branch nick: test_author_log
951
 
timestamp: Wed 2005-11-23 12:08:27 +1000
952
 
message:
953
 
  add a
954
 
''',
955
 
                             sio.getvalue())
956
 
 
957
 
    def test_long_properties_in_log(self):
958
 
        """Log includes the custom properties returned by the registered
959
 
        handlers.
960
 
        """
961
 
        wt = self.make_branch_and_tree('.')
962
 
        b = wt.branch
963
 
        self.build_tree(['a'])
964
 
        wt.add('a')
965
 
        b.nick = 'test_properties_in_log'
966
 
        wt.commit(message='add a',
967
 
                  timestamp=1132711707,
968
 
                  timezone=36000,
969
 
                  committer='Lorem Ipsum <test@example.com>',
970
 
                  authors=['John Doe <jdoe@example.com>'])
971
 
        sio = StringIO()
972
 
        formatter = log.LongLogFormatter(to_file=sio, levels=1)
973
 
        try:
974
 
            def trivial_custom_prop_handler(revision):
975
 
                return {'test_prop':'test_value'}
976
 
 
977
 
            log.properties_handler_registry.register(
978
 
                'trivial_custom_prop_handler',
979
 
                trivial_custom_prop_handler)
980
 
            log.show_log(b, formatter)
981
 
        finally:
982
 
            log.properties_handler_registry.remove(
983
 
                'trivial_custom_prop_handler')
984
 
            self.assertEqualDiff('''\
985
 
------------------------------------------------------------
986
 
revno: 1
987
 
test_prop: test_value
988
 
author: John Doe <jdoe@example.com>
989
 
committer: Lorem Ipsum <test@example.com>
990
 
branch nick: test_properties_in_log
991
 
timestamp: Wed 2005-11-23 12:08:27 +1000
992
 
message:
993
 
  add a
994
 
''',
995
 
                                 sio.getvalue())
996
 
 
997
 
 
998
 
class TestLineLogFormatter(tests.TestCaseWithTransport):
 
256
        
 
257
        bug #4676
 
258
        """
 
259
        wt = self.make_branch_and_tree('.')
 
260
        b = wt.branch
 
261
        self.build_tree(['a'])
 
262
        wt.add('a')
 
263
        # XXX: why does a longer nick show up?
 
264
        b.nick = 'test_verbose_log'
 
265
        wt.commit(message='add a', 
 
266
                  timestamp=1132711707, 
 
267
                  timezone=36000,
 
268
                  committer='Lorem Ipsum <test@example.com>')
 
269
        logfile = file('out.tmp', 'w+')
 
270
        formatter = LongLogFormatter(to_file=logfile)
 
271
        show_log(b, formatter, verbose=True)
 
272
        logfile.flush()
 
273
        logfile.seek(0)
 
274
        log_contents = logfile.read()
 
275
        self.assertEqualDiff(log_contents, '''\
 
276
------------------------------------------------------------
 
277
revno: 1
 
278
committer: Lorem Ipsum <test@example.com>
 
279
branch nick: test_verbose_log
 
280
timestamp: Wed 2005-11-23 12:08:27 +1000
 
281
message:
 
282
  add a
 
283
added:
 
284
  a
 
285
''')
999
286
 
1000
287
    def test_line_log(self):
1001
288
        """Line log should show revno
1002
 
 
1003
 
        bug #5162
1004
 
        """
1005
 
        wt = self.make_branch_and_tree('.')
1006
 
        b = wt.branch
1007
 
        self.build_tree(['a'])
1008
 
        wt.add('a')
1009
 
        b.nick = 'test-line-log'
1010
 
        wt.commit(message='add a',
1011
 
                  timestamp=1132711707,
1012
 
                  timezone=36000,
1013
 
                  committer='Line-Log-Formatter Tester <test@line.log>')
1014
 
        logfile = file('out.tmp', 'w+')
1015
 
        formatter = log.LineLogFormatter(to_file=logfile)
1016
 
        log.show_log(b, formatter)
1017
 
        logfile.flush()
1018
 
        logfile.seek(0)
1019
 
        log_contents = logfile.read()
1020
 
        self.assertEqualDiff('1: Line-Log-Formatte... 2005-11-23 add a\n',
1021
 
                             log_contents)
1022
 
 
1023
 
    def test_trailing_newlines(self):
1024
 
        wt = self.make_branch_and_tree('.')
1025
 
        b = make_commits_with_trailing_newlines(wt)
1026
 
        sio = self.make_utf8_encoded_stringio()
1027
 
        lf = log.LineLogFormatter(to_file=sio)
1028
 
        log.show_log(b, lf)
1029
 
        self.assertEqualDiff("""\
1030
 
3: Joe Foo 2005-11-21 single line with trailing newline
1031
 
2: Joe Bar 2005-11-21 multiline
1032
 
1: Joe Foo 2005-11-21 simple log message
1033
 
""",
1034
 
                             sio.getvalue())
1035
 
 
1036
 
    def _prepare_tree_with_merges(self, with_tags=False):
1037
 
        wt = self.make_branch_and_memory_tree('.')
1038
 
        wt.lock_write()
1039
 
        self.addCleanup(wt.unlock)
1040
 
        wt.add('')
1041
 
        wt.commit('rev-1', rev_id='rev-1',
1042
 
                  timestamp=1132586655, timezone=36000,
1043
 
                  committer='Joe Foo <joe@foo.com>')
1044
 
        wt.commit('rev-merged', rev_id='rev-2a',
1045
 
                  timestamp=1132586700, timezone=36000,
1046
 
                  committer='Joe Foo <joe@foo.com>')
1047
 
        wt.set_parent_ids(['rev-1', 'rev-2a'])
1048
 
        wt.branch.set_last_revision_info(1, 'rev-1')
1049
 
        wt.commit('rev-2', rev_id='rev-2b',
1050
 
                  timestamp=1132586800, timezone=36000,
1051
 
                  committer='Joe Foo <joe@foo.com>')
1052
 
        if with_tags:
1053
 
            branch = wt.branch
1054
 
            branch.tags.set_tag('v0.2', 'rev-2b')
1055
 
            wt.commit('rev-3', rev_id='rev-3',
1056
 
                      timestamp=1132586900, timezone=36000,
1057
 
                      committer='Jane Foo <jane@foo.com>')
1058
 
            branch.tags.set_tag('v1.0rc1', 'rev-3')
1059
 
            branch.tags.set_tag('v1.0', 'rev-3')
1060
 
        return wt
1061
 
 
1062
 
    def test_line_log_single_merge_revision(self):
1063
 
        wt = self._prepare_tree_with_merges()
1064
 
        logfile = self.make_utf8_encoded_stringio()
1065
 
        formatter = log.LineLogFormatter(to_file=logfile)
1066
 
        revspec = revisionspec.RevisionSpec.from_string('1.1.1')
1067
 
        wtb = wt.branch
1068
 
        rev = revspec.in_history(wtb)
1069
 
        log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
1070
 
        self.assertEqualDiff("""\
1071
 
1.1.1: Joe Foo 2005-11-22 rev-merged
1072
 
""",
1073
 
                             logfile.getvalue())
1074
 
 
1075
 
    def test_line_log_with_tags(self):
1076
 
        wt = self._prepare_tree_with_merges(with_tags=True)
1077
 
        logfile = self.make_utf8_encoded_stringio()
1078
 
        formatter = log.LineLogFormatter(to_file=logfile)
1079
 
        log.show_log(wt.branch, formatter)
1080
 
        self.assertEqualDiff("""\
1081
 
3: Jane Foo 2005-11-22 {v1.0, v1.0rc1} rev-3
1082
 
2: Joe Foo 2005-11-22 [merge] {v0.2} rev-2
1083
 
1: Joe Foo 2005-11-22 rev-1
1084
 
""",
1085
 
                             logfile.getvalue())
1086
 
 
1087
 
class TestLineLogFormatterWithMergeRevisions(tests.TestCaseWithTransport):
1088
 
 
1089
 
    def test_line_merge_revs_log(self):
1090
 
        """Line log should show revno
1091
 
 
1092
 
        bug #5162
1093
 
        """
1094
 
        wt = self.make_branch_and_tree('.')
1095
 
        b = wt.branch
1096
 
        self.build_tree(['a'])
1097
 
        wt.add('a')
1098
 
        b.nick = 'test-line-log'
1099
 
        wt.commit(message='add a',
1100
 
                  timestamp=1132711707,
1101
 
                  timezone=36000,
1102
 
                  committer='Line-Log-Formatter Tester <test@line.log>')
1103
 
        logfile = file('out.tmp', 'w+')
1104
 
        formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1105
 
        log.show_log(b, formatter)
1106
 
        logfile.flush()
1107
 
        logfile.seek(0)
1108
 
        log_contents = logfile.read()
1109
 
        self.assertEqualDiff('1: Line-Log-Formatte... 2005-11-23 add a\n',
1110
 
                             log_contents)
1111
 
 
1112
 
    def test_line_merge_revs_log_single_merge_revision(self):
1113
 
        wt = self.make_branch_and_memory_tree('.')
1114
 
        wt.lock_write()
1115
 
        self.addCleanup(wt.unlock)
1116
 
        wt.add('')
1117
 
        wt.commit('rev-1', rev_id='rev-1',
1118
 
                  timestamp=1132586655, timezone=36000,
1119
 
                  committer='Joe Foo <joe@foo.com>')
1120
 
        wt.commit('rev-merged', rev_id='rev-2a',
1121
 
                  timestamp=1132586700, timezone=36000,
1122
 
                  committer='Joe Foo <joe@foo.com>')
1123
 
        wt.set_parent_ids(['rev-1', 'rev-2a'])
1124
 
        wt.branch.set_last_revision_info(1, 'rev-1')
1125
 
        wt.commit('rev-2', rev_id='rev-2b',
1126
 
                  timestamp=1132586800, timezone=36000,
1127
 
                  committer='Joe Foo <joe@foo.com>')
1128
 
        logfile = self.make_utf8_encoded_stringio()
1129
 
        formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1130
 
        revspec = revisionspec.RevisionSpec.from_string('1.1.1')
1131
 
        wtb = wt.branch
1132
 
        rev = revspec.in_history(wtb)
1133
 
        log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
1134
 
        self.assertEqualDiff("""\
1135
 
1.1.1: Joe Foo 2005-11-22 rev-merged
1136
 
""",
1137
 
                             logfile.getvalue())
1138
 
 
1139
 
    def test_line_merge_revs_log_with_merges(self):
1140
 
        wt = self.make_branch_and_memory_tree('.')
1141
 
        wt.lock_write()
1142
 
        self.addCleanup(wt.unlock)
1143
 
        wt.add('')
1144
 
        wt.commit('rev-1', rev_id='rev-1',
1145
 
                  timestamp=1132586655, timezone=36000,
1146
 
                  committer='Joe Foo <joe@foo.com>')
1147
 
        wt.commit('rev-merged', rev_id='rev-2a',
1148
 
                  timestamp=1132586700, timezone=36000,
1149
 
                  committer='Joe Foo <joe@foo.com>')
1150
 
        wt.set_parent_ids(['rev-1', 'rev-2a'])
1151
 
        wt.branch.set_last_revision_info(1, 'rev-1')
1152
 
        wt.commit('rev-2', rev_id='rev-2b',
1153
 
                  timestamp=1132586800, timezone=36000,
1154
 
                  committer='Joe Foo <joe@foo.com>')
1155
 
        logfile = self.make_utf8_encoded_stringio()
1156
 
        formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1157
 
        log.show_log(wt.branch, formatter)
1158
 
        self.assertEqualDiff("""\
1159
 
2: Joe Foo 2005-11-22 [merge] rev-2
1160
 
  1.1.1: Joe Foo 2005-11-22 rev-merged
1161
 
1: Joe Foo 2005-11-22 rev-1
1162
 
""",
1163
 
                             logfile.getvalue())
1164
 
 
1165
 
class TestGetViewRevisions(tests.TestCaseWithTransport):
 
289
        
 
290
        bug #5162
 
291
        """
 
292
        wt = self.make_branch_and_tree('.')
 
293
        b = wt.branch
 
294
        self.build_tree(['a'])
 
295
        wt.add('a')
 
296
        b.nick = 'test-line-log'
 
297
        wt.commit(message='add a', 
 
298
                  timestamp=1132711707, 
 
299
                  timezone=36000,
 
300
                  committer='Line-Log-Formatter Tester <test@line.log>')
 
301
        logfile = file('out.tmp', 'w+')
 
302
        formatter = LineLogFormatter(to_file=logfile)
 
303
        show_log(b, formatter)
 
304
        logfile.flush()
 
305
        logfile.seek(0)
 
306
        log_contents = logfile.read()
 
307
        self.assertEqualDiff(log_contents, '1: Line-Log-Formatte... 2005-11-23 add a\n')
1166
308
 
1167
309
    def make_tree_with_commits(self):
1168
310
        """Create a tree with well-known revision ids"""
1183
325
        wt.commit('four-b', rev_id='4b')
1184
326
        mainline_revs.append('4b')
1185
327
        rev_nos['4b'] = 4
1186
 
        # 4a: 3.1.1
1187
328
        return mainline_revs, rev_nos, wt
1188
329
 
1189
330
    def make_tree_with_many_merges(self):
1190
331
        """Create a tree with well-known revision ids"""
1191
332
        wt = self.make_branch_and_tree('tree1')
1192
 
        self.build_tree_contents([('tree1/f', '1\n')])
1193
 
        wt.add(['f'], ['f-id'])
1194
333
        wt.commit('commit one', rev_id='1')
1195
334
        wt.commit('commit two', rev_id='2')
1196
 
 
 
335
        tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
1197
336
        tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
1198
 
        self.build_tree_contents([('tree3/f', '1\n2\n3a\n')])
1199
337
        tree3.commit('commit three a', rev_id='3a')
1200
 
 
1201
 
        tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
1202
338
        tree2.merge_from_branch(tree3.branch)
1203
339
        tree2.commit('commit three b', rev_id='3b')
1204
 
 
1205
340
        wt.merge_from_branch(tree2.branch)
1206
341
        wt.commit('commit three c', rev_id='3c')
1207
342
        tree2.commit('four-a', rev_id='4a')
1208
 
 
1209
343
        wt.merge_from_branch(tree2.branch)
1210
344
        wt.commit('four-b', rev_id='4b')
1211
 
 
1212
345
        mainline_revs = [None, '1', '2', '3c', '4b']
1213
 
        rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
1214
 
        full_rev_nos_for_reference = {
1215
 
            '1': '1',
1216
 
            '2': '2',
1217
 
            '3a': '2.1.1', #first commit tree 3
1218
 
            '3b': '2.2.1', # first commit tree 2
1219
 
            '3c': '3', #merges 3b to main
1220
 
            '4a': '2.2.2', # second commit tree 2
1221
 
            '4b': '4', # merges 4a to main
1222
 
            }
 
346
        rev_nos = {'1': 1, '2': 2, '3c': 3, '4b': 4}
1223
347
        return mainline_revs, rev_nos, wt
1224
348
 
1225
349
    def test_get_view_revisions_forward(self):
1226
350
        """Test the get_view_revisions method"""
1227
351
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1228
 
        wt.lock_read()
1229
 
        self.addCleanup(wt.unlock)
1230
 
        revisions = list(log.get_view_revisions(
1231
 
                mainline_revs, rev_nos, wt.branch, 'forward'))
1232
 
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
1233
 
                         revisions)
1234
 
        revisions2 = list(log.get_view_revisions(
1235
 
                mainline_revs, rev_nos, wt.branch, 'forward',
1236
 
                include_merges=False))
 
352
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
353
                                            'forward'))
 
354
        self.assertEqual(revisions, [('1', 1, 0), ('2', 2, 0), ('3', 3, 0)])
 
355
        revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
356
                                             'forward', include_merges=False))
1237
357
        self.assertEqual(revisions, revisions2)
1238
358
 
1239
359
    def test_get_view_revisions_reverse(self):
1240
360
        """Test the get_view_revisions with reverse"""
1241
361
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1242
 
        wt.lock_read()
1243
 
        self.addCleanup(wt.unlock)
1244
 
        revisions = list(log.get_view_revisions(
1245
 
                mainline_revs, rev_nos, wt.branch, 'reverse'))
1246
 
        self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
1247
 
                         revisions)
1248
 
        revisions2 = list(log.get_view_revisions(
1249
 
                mainline_revs, rev_nos, wt.branch, 'reverse',
1250
 
                include_merges=False))
 
362
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
363
                                            'reverse'))
 
364
        self.assertEqual(revisions, [('3', 3, 0), ('2', 2, 0), ('1', 1, 0), ])
 
365
        revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
366
                                             'reverse', include_merges=False))
1251
367
        self.assertEqual(revisions, revisions2)
1252
368
 
1253
369
    def test_get_view_revisions_merge(self):
1254
370
        """Test get_view_revisions when there are merges"""
1255
371
        mainline_revs, rev_nos, wt = self.make_tree_with_merges()
1256
 
        wt.lock_read()
1257
 
        self.addCleanup(wt.unlock)
1258
 
        revisions = list(log.get_view_revisions(
1259
 
                mainline_revs, rev_nos, wt.branch, 'forward'))
1260
 
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
1261
 
                          ('4b', '4', 0), ('4a', '3.1.1', 1)],
1262
 
                         revisions)
1263
 
        revisions = list(log.get_view_revisions(
1264
 
                mainline_revs, rev_nos, wt.branch, 'forward',
1265
 
                include_merges=False))
1266
 
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
1267
 
                          ('4b', '4', 0)],
1268
 
                         revisions)
 
372
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
373
                                            'forward'))
 
374
        self.assertEqual(revisions, [('1', 1, 0), ('2', 2, 0), ('3', 3, 0),
 
375
                                     ('4b', 4, 0), ('4a', None, 1)])
 
376
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
377
                                             'forward', include_merges=False))
 
378
        self.assertEqual(revisions, [('1', 1, 0), ('2', 2, 0), ('3', 3, 0),
 
379
                                     ('4b', 4, 0)])
1269
380
 
1270
381
    def test_get_view_revisions_merge_reverse(self):
1271
382
        """Test get_view_revisions in reverse when there are merges"""
1272
383
        mainline_revs, rev_nos, wt = self.make_tree_with_merges()
1273
 
        wt.lock_read()
1274
 
        self.addCleanup(wt.unlock)
1275
 
        revisions = list(log.get_view_revisions(
1276
 
                mainline_revs, rev_nos, wt.branch, 'reverse'))
1277
 
        self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
1278
 
                          ('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
1279
 
                         revisions)
1280
 
        revisions = list(log.get_view_revisions(
1281
 
                mainline_revs, rev_nos, wt.branch, 'reverse',
1282
 
                include_merges=False))
1283
 
        self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
1284
 
                          ('1', '1', 0)],
1285
 
                         revisions)
 
384
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
385
                                            'reverse'))
 
386
        self.assertEqual(revisions, [('4b', 4, 0), ('4a', None, 1), 
 
387
                                     ('3', 3, 0), ('2', 2, 0), ('1', 1, 0)])
 
388
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
389
                                             'reverse', include_merges=False))
 
390
        self.assertEqual(revisions, [('4b', 4, 0), ('3', 3, 0), ('2', 2, 0),
 
391
                                     ('1', 1, 0)])
1286
392
 
1287
393
    def test_get_view_revisions_merge2(self):
1288
394
        """Test get_view_revisions when there are merges"""
1289
395
        mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
1290
 
        wt.lock_read()
1291
 
        self.addCleanup(wt.unlock)
1292
 
        revisions = list(log.get_view_revisions(
1293
 
                mainline_revs, rev_nos, wt.branch, 'forward'))
1294
 
        expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
1295
 
                    ('3a', '2.1.1', 1), ('3b', '2.2.1', 1), ('4b', '4', 0),
1296
 
                    ('4a', '2.2.2', 1)]
1297
 
        self.assertEqual(expected, revisions)
1298
 
        revisions = list(log.get_view_revisions(
1299
 
                mainline_revs, rev_nos, wt.branch, 'forward',
1300
 
                include_merges=False))
1301
 
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
1302
 
                          ('4b', '4', 0)],
1303
 
                         revisions)
1304
 
 
1305
 
 
1306
 
    def test_file_id_for_range(self):
1307
 
        mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
1308
 
        wt.lock_read()
1309
 
        self.addCleanup(wt.unlock)
1310
 
 
1311
 
        def rev_from_rev_id(revid, branch):
1312
 
            revspec = revisionspec.RevisionSpec.from_string('revid:%s' % revid)
1313
 
            return revspec.in_history(branch)
1314
 
 
1315
 
        def view_revs(start_rev, end_rev, file_id, direction):
1316
 
            revs = log.calculate_view_revisions(
1317
 
                wt.branch,
1318
 
                start_rev, # start_revision
1319
 
                end_rev, # end_revision
1320
 
                direction, # direction
1321
 
                file_id, # specific_fileid
1322
 
                True, # generate_merge_revisions
1323
 
                )
1324
 
            return revs
1325
 
 
1326
 
        rev_3a = rev_from_rev_id('3a', wt.branch)
1327
 
        rev_4b = rev_from_rev_id('4b', wt.branch)
1328
 
        self.assertEqual([('3c', '3', 0), ('3a', '2.1.1', 1)],
1329
 
                          view_revs(rev_3a, rev_4b, 'f-id', 'reverse'))
1330
 
        # Note: 3c still appears before 3a here because of depth-based sorting
1331
 
        self.assertEqual([('3c', '3', 0), ('3a', '2.1.1', 1)],
1332
 
                          view_revs(rev_3a, rev_4b, 'f-id', 'forward'))
1333
 
 
1334
 
 
1335
 
class TestGetRevisionsTouchingFileID(tests.TestCaseWithTransport):
1336
 
 
1337
 
    def create_tree_with_single_merge(self):
1338
 
        """Create a branch with a moderate layout.
1339
 
 
1340
 
        The revision graph looks like:
1341
 
 
1342
 
           A
1343
 
           |\
1344
 
           B C
1345
 
           |/
1346
 
           D
1347
 
 
1348
 
        In this graph, A introduced files f1 and f2 and f3.
1349
 
        B modifies f1 and f3, and C modifies f2 and f3.
1350
 
        D merges the changes from B and C and resolves the conflict for f3.
1351
 
        """
1352
 
        # TODO: jam 20070218 This seems like it could really be done
1353
 
        #       with make_branch_and_memory_tree() if we could just
1354
 
        #       create the content of those files.
1355
 
        # TODO: jam 20070218 Another alternative is that we would really
1356
 
        #       like to only create this tree 1 time for all tests that
1357
 
        #       use it. Since 'log' only uses the tree in a readonly
1358
 
        #       fashion, it seems a shame to regenerate an identical
1359
 
        #       tree for each test.
1360
 
        tree = self.make_branch_and_tree('tree')
1361
 
        tree.lock_write()
1362
 
        self.addCleanup(tree.unlock)
1363
 
 
1364
 
        self.build_tree_contents([('tree/f1', 'A\n'),
1365
 
                                  ('tree/f2', 'A\n'),
1366
 
                                  ('tree/f3', 'A\n'),
1367
 
                                 ])
1368
 
        tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
1369
 
        tree.commit('A', rev_id='A')
1370
 
 
1371
 
        self.build_tree_contents([('tree/f2', 'A\nC\n'),
1372
 
                                  ('tree/f3', 'A\nC\n'),
1373
 
                                 ])
1374
 
        tree.commit('C', rev_id='C')
1375
 
        # Revert back to A to build the other history.
1376
 
        tree.set_last_revision('A')
1377
 
        tree.branch.set_last_revision_info(1, 'A')
1378
 
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
1379
 
                                  ('tree/f2', 'A\n'),
1380
 
                                  ('tree/f3', 'A\nB\n'),
1381
 
                                 ])
1382
 
        tree.commit('B', rev_id='B')
1383
 
        tree.set_parent_ids(['B', 'C'])
1384
 
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
1385
 
                                  ('tree/f2', 'A\nC\n'),
1386
 
                                  ('tree/f3', 'A\nB\nC\n'),
1387
 
                                 ])
1388
 
        tree.commit('D', rev_id='D')
1389
 
 
1390
 
        # Switch to a read lock for this tree.
1391
 
        # We still have an addCleanup(tree.unlock) pending
1392
 
        tree.unlock()
1393
 
        tree.lock_read()
1394
 
        return tree
1395
 
 
1396
 
    def check_delta(self, delta, **kw):
1397
 
        """Check the filenames touched by a delta are as expected.
1398
 
 
1399
 
        Caller only have to pass in the list of files for each part, all
1400
 
        unspecified parts are considered empty (and checked as such).
1401
 
        """
1402
 
        for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
1403
 
            # By default we expect an empty list
1404
 
            expected = kw.get(n, [])
1405
 
            # strip out only the path components
1406
 
            got = [x[0] for x in getattr(delta, n)]
1407
 
            self.assertEqual(expected, got)
1408
 
 
1409
 
    def test_tree_with_single_merge(self):
1410
 
        """Make sure the tree layout is correct."""
1411
 
        tree = self.create_tree_with_single_merge()
1412
 
        rev_A_tree = tree.branch.repository.revision_tree('A')
1413
 
        rev_B_tree = tree.branch.repository.revision_tree('B')
1414
 
        rev_C_tree = tree.branch.repository.revision_tree('C')
1415
 
        rev_D_tree = tree.branch.repository.revision_tree('D')
1416
 
 
1417
 
        self.check_delta(rev_B_tree.changes_from(rev_A_tree),
1418
 
                         modified=['f1', 'f3'])
1419
 
 
1420
 
        self.check_delta(rev_C_tree.changes_from(rev_A_tree),
1421
 
                         modified=['f2', 'f3'])
1422
 
 
1423
 
        self.check_delta(rev_D_tree.changes_from(rev_B_tree),
1424
 
                         modified=['f2', 'f3'])
1425
 
 
1426
 
        self.check_delta(rev_D_tree.changes_from(rev_C_tree),
1427
 
                         modified=['f1', 'f3'])
1428
 
 
1429
 
    def assertAllRevisionsForFileID(self, tree, file_id, revisions):
1430
 
        """Ensure _filter_revisions_touching_file_id returns the right values.
1431
 
 
1432
 
        Get the return value from _filter_revisions_touching_file_id and make
1433
 
        sure they are correct.
1434
 
        """
1435
 
        # The api for _filter_revisions_touching_file_id is a little crazy.
1436
 
        # So we do the setup here.
1437
 
        mainline = tree.branch.revision_history()
1438
 
        mainline.insert(0, None)
1439
 
        revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
1440
 
        view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
1441
 
                                                'reverse', True)
1442
 
        actual_revs = log._filter_revisions_touching_file_id(
1443
 
                            tree.branch,
1444
 
                            file_id,
1445
 
                            list(view_revs_iter))
1446
 
        self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
1447
 
 
1448
 
    def test_file_id_f1(self):
1449
 
        tree = self.create_tree_with_single_merge()
1450
 
        # f1 should be marked as modified by revisions A and B
1451
 
        self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
1452
 
 
1453
 
    def test_file_id_f2(self):
1454
 
        tree = self.create_tree_with_single_merge()
1455
 
        # f2 should be marked as modified by revisions A, C, and D
1456
 
        # because D merged the changes from C.
1457
 
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1458
 
 
1459
 
    def test_file_id_f3(self):
1460
 
        tree = self.create_tree_with_single_merge()
1461
 
        # f3 should be marked as modified by revisions A, B, C, and D
1462
 
        self.assertAllRevisionsForFileID(tree, 'f3-id', ['D', 'C', 'B', 'A'])
1463
 
 
1464
 
    def test_file_id_with_ghosts(self):
1465
 
        # This is testing bug #209948, where having a ghost would cause
1466
 
        # _filter_revisions_touching_file_id() to fail.
1467
 
        tree = self.create_tree_with_single_merge()
1468
 
        # We need to add a revision, so switch back to a write-locked tree
1469
 
        # (still a single addCleanup(tree.unlock) pending).
1470
 
        tree.unlock()
1471
 
        tree.lock_write()
1472
 
        first_parent = tree.last_revision()
1473
 
        tree.set_parent_ids([first_parent, 'ghost-revision-id'])
1474
 
        self.build_tree_contents([('tree/f1', 'A\nB\nXX\n')])
1475
 
        tree.commit('commit with a ghost', rev_id='XX')
1476
 
        self.assertAllRevisionsForFileID(tree, 'f1-id', ['XX', 'B', 'A'])
1477
 
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1478
 
 
1479
 
    def test_unknown_file_id(self):
1480
 
        tree = self.create_tree_with_single_merge()
1481
 
        self.assertAllRevisionsForFileID(tree, 'unknown', [])
1482
 
 
1483
 
    def test_empty_branch_unknown_file_id(self):
1484
 
        tree = self.make_branch_and_tree('tree')
1485
 
        self.assertAllRevisionsForFileID(tree, 'unknown', [])
1486
 
 
1487
 
 
1488
 
class TestShowChangedRevisions(tests.TestCaseWithTransport):
1489
 
 
1490
 
    def test_show_changed_revisions_verbose(self):
1491
 
        tree = self.make_branch_and_tree('tree_a')
1492
 
        self.build_tree(['tree_a/foo'])
1493
 
        tree.add('foo')
1494
 
        tree.commit('bar', rev_id='bar-id')
1495
 
        s = self.make_utf8_encoded_stringio()
1496
 
        log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
1497
 
        self.assertContainsRe(s.getvalue(), 'bar')
1498
 
        self.assertNotContainsRe(s.getvalue(), 'foo')
1499
 
 
1500
 
 
1501
 
class TestLogFormatter(tests.TestCase):
1502
 
 
1503
 
    def test_short_committer(self):
1504
 
        rev = revision.Revision('a-id')
1505
 
        rev.committer = 'John Doe <jdoe@example.com>'
1506
 
        lf = log.LogFormatter(None)
1507
 
        self.assertEqual('John Doe', lf.short_committer(rev))
1508
 
        rev.committer = 'John Smith <jsmith@example.com>'
1509
 
        self.assertEqual('John Smith', lf.short_committer(rev))
1510
 
        rev.committer = 'John Smith'
1511
 
        self.assertEqual('John Smith', lf.short_committer(rev))
1512
 
        rev.committer = 'jsmith@example.com'
1513
 
        self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1514
 
        rev.committer = '<jsmith@example.com>'
1515
 
        self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1516
 
        rev.committer = 'John Smith jsmith@example.com'
1517
 
        self.assertEqual('John Smith', lf.short_committer(rev))
1518
 
 
1519
 
    def test_short_author(self):
1520
 
        rev = revision.Revision('a-id')
1521
 
        rev.committer = 'John Doe <jdoe@example.com>'
1522
 
        lf = log.LogFormatter(None)
1523
 
        self.assertEqual('John Doe', lf.short_author(rev))
1524
 
        rev.properties['author'] = 'John Smith <jsmith@example.com>'
1525
 
        self.assertEqual('John Smith', lf.short_author(rev))
1526
 
        rev.properties['author'] = 'John Smith'
1527
 
        self.assertEqual('John Smith', lf.short_author(rev))
1528
 
        rev.properties['author'] = 'jsmith@example.com'
1529
 
        self.assertEqual('jsmith@example.com', lf.short_author(rev))
1530
 
        rev.properties['author'] = '<jsmith@example.com>'
1531
 
        self.assertEqual('jsmith@example.com', lf.short_author(rev))
1532
 
        rev.properties['author'] = 'John Smith jsmith@example.com'
1533
 
        self.assertEqual('John Smith', lf.short_author(rev))
1534
 
        del rev.properties['author']
1535
 
        rev.properties['authors'] = ('John Smith <jsmith@example.com>\n'
1536
 
                'Jane Rey <jrey@example.com>')
1537
 
        self.assertEqual('John Smith', lf.short_author(rev))
1538
 
 
1539
 
 
1540
 
class TestReverseByDepth(tests.TestCase):
1541
 
    """Test reverse_by_depth behavior.
1542
 
 
1543
 
    This is used to present revisions in forward (oldest first) order in a nice
1544
 
    layout.
1545
 
 
1546
 
    The tests use lighter revision description to ease reading.
1547
 
    """
1548
 
 
1549
 
    def assertReversed(self, forward, backward):
1550
 
        # Transform the descriptions to suit the API: tests use (revno, depth),
1551
 
        # while the API expects (revid, revno, depth)
1552
 
        def complete_revisions(l):
1553
 
            """Transform the description to suit the API.
1554
 
 
1555
 
            Tests use (revno, depth) whil the API expects (revid, revno, depth).
1556
 
            Since the revid is arbitrary, we just duplicate revno
1557
 
            """
1558
 
            return [ (r, r, d) for r, d in l]
1559
 
        forward = complete_revisions(forward)
1560
 
        backward= complete_revisions(backward)
1561
 
        self.assertEqual(forward, log.reverse_by_depth(backward))
1562
 
 
1563
 
 
1564
 
    def test_mainline_revisions(self):
1565
 
        self.assertReversed([( '1', 0), ('2', 0)],
1566
 
                            [('2', 0), ('1', 0)])
1567
 
 
1568
 
    def test_merged_revisions(self):
1569
 
        self.assertReversed([('1', 0), ('2', 0), ('2.2', 1), ('2.1', 1),],
1570
 
                            [('2', 0), ('2.1', 1), ('2.2', 1), ('1', 0),])
1571
 
    def test_shifted_merged_revisions(self):
1572
 
        """Test irregular layout.
1573
 
 
1574
 
        Requesting revisions touching a file can produce "holes" in the depths.
1575
 
        """
1576
 
        self.assertReversed([('1', 0), ('2', 0), ('1.1', 2), ('1.2', 2),],
1577
 
                            [('2', 0), ('1.2', 2), ('1.1', 2), ('1', 0),])
1578
 
 
1579
 
    def test_merged_without_child_revisions(self):
1580
 
        """Test irregular layout.
1581
 
 
1582
 
        Revision ranges can produce "holes" in the depths.
1583
 
        """
1584
 
        # When a revision of higher depth doesn't follow one of lower depth, we
1585
 
        # assume a lower depth one is virtually there
1586
 
        self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1587
 
                            [('4', 4), ('3', 3), ('2', 2), ('1', 2),])
1588
 
        # So we get the same order after reversing below even if the original
1589
 
        # revisions are not in the same order.
1590
 
        self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1591
 
                            [('3', 3), ('4', 4), ('2', 2), ('1', 2),])
1592
 
 
1593
 
 
1594
 
class TestHistoryChange(tests.TestCaseWithTransport):
1595
 
 
1596
 
    def setup_a_tree(self):
1597
 
        tree = self.make_branch_and_tree('tree')
1598
 
        tree.lock_write()
1599
 
        self.addCleanup(tree.unlock)
1600
 
        tree.commit('1a', rev_id='1a')
1601
 
        tree.commit('2a', rev_id='2a')
1602
 
        tree.commit('3a', rev_id='3a')
1603
 
        return tree
1604
 
 
1605
 
    def setup_ab_tree(self):
1606
 
        tree = self.setup_a_tree()
1607
 
        tree.set_last_revision('1a')
1608
 
        tree.branch.set_last_revision_info(1, '1a')
1609
 
        tree.commit('2b', rev_id='2b')
1610
 
        tree.commit('3b', rev_id='3b')
1611
 
        return tree
1612
 
 
1613
 
    def setup_ac_tree(self):
1614
 
        tree = self.setup_a_tree()
1615
 
        tree.set_last_revision(revision.NULL_REVISION)
1616
 
        tree.branch.set_last_revision_info(0, revision.NULL_REVISION)
1617
 
        tree.commit('1c', rev_id='1c')
1618
 
        tree.commit('2c', rev_id='2c')
1619
 
        tree.commit('3c', rev_id='3c')
1620
 
        return tree
1621
 
 
1622
 
    def test_all_new(self):
1623
 
        tree = self.setup_ab_tree()
1624
 
        old, new = log.get_history_change('1a', '3a', tree.branch.repository)
1625
 
        self.assertEqual([], old)
1626
 
        self.assertEqual(['2a', '3a'], new)
1627
 
 
1628
 
    def test_all_old(self):
1629
 
        tree = self.setup_ab_tree()
1630
 
        old, new = log.get_history_change('3a', '1a', tree.branch.repository)
1631
 
        self.assertEqual([], new)
1632
 
        self.assertEqual(['2a', '3a'], old)
1633
 
 
1634
 
    def test_null_old(self):
1635
 
        tree = self.setup_ab_tree()
1636
 
        old, new = log.get_history_change(revision.NULL_REVISION,
1637
 
                                          '3a', tree.branch.repository)
1638
 
        self.assertEqual([], old)
1639
 
        self.assertEqual(['1a', '2a', '3a'], new)
1640
 
 
1641
 
    def test_null_new(self):
1642
 
        tree = self.setup_ab_tree()
1643
 
        old, new = log.get_history_change('3a', revision.NULL_REVISION,
1644
 
                                          tree.branch.repository)
1645
 
        self.assertEqual([], new)
1646
 
        self.assertEqual(['1a', '2a', '3a'], old)
1647
 
 
1648
 
    def test_diverged(self):
1649
 
        tree = self.setup_ab_tree()
1650
 
        old, new = log.get_history_change('3a', '3b', tree.branch.repository)
1651
 
        self.assertEqual(old, ['2a', '3a'])
1652
 
        self.assertEqual(new, ['2b', '3b'])
1653
 
 
1654
 
    def test_unrelated(self):
1655
 
        tree = self.setup_ac_tree()
1656
 
        old, new = log.get_history_change('3a', '3c', tree.branch.repository)
1657
 
        self.assertEqual(old, ['1a', '2a', '3a'])
1658
 
        self.assertEqual(new, ['1c', '2c', '3c'])
1659
 
 
1660
 
    def test_show_branch_change(self):
1661
 
        tree = self.setup_ab_tree()
1662
 
        s = StringIO()
1663
 
        log.show_branch_change(tree.branch, s, 3, '3a')
1664
 
        self.assertContainsRe(s.getvalue(),
1665
 
            '[*]{60}\nRemoved Revisions:\n(.|\n)*2a(.|\n)*3a(.|\n)*'
1666
 
            '[*]{60}\n\nAdded Revisions:\n(.|\n)*2b(.|\n)*3b')
1667
 
 
1668
 
    def test_show_branch_change_no_change(self):
1669
 
        tree = self.setup_ab_tree()
1670
 
        s = StringIO()
1671
 
        log.show_branch_change(tree.branch, s, 3, '3b')
1672
 
        self.assertEqual(s.getvalue(),
1673
 
            'Nothing seems to have changed\n')
1674
 
 
1675
 
    def test_show_branch_change_no_old(self):
1676
 
        tree = self.setup_ab_tree()
1677
 
        s = StringIO()
1678
 
        log.show_branch_change(tree.branch, s, 2, '2b')
1679
 
        self.assertContainsRe(s.getvalue(), 'Added Revisions:')
1680
 
        self.assertNotContainsRe(s.getvalue(), 'Removed Revisions:')
1681
 
 
1682
 
    def test_show_branch_change_no_new(self):
1683
 
        tree = self.setup_ab_tree()
1684
 
        tree.branch.set_last_revision_info(2, '2b')
1685
 
        s = StringIO()
1686
 
        log.show_branch_change(tree.branch, s, 3, '3b')
1687
 
        self.assertContainsRe(s.getvalue(), 'Removed Revisions:')
1688
 
        self.assertNotContainsRe(s.getvalue(), 'Added Revisions:')
 
396
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
397
                                            'forward'))
 
398
        expected = [('1', 1, 0), ('2', 2, 0), ('3c', 3, 0), ('3a', None, 1),
 
399
                    ('3b', None, 1), ('4b', 4, 0), ('4a', None, 1)]
 
400
        self.assertEqual(revisions, expected)
 
401
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
402
                                             'forward', include_merges=False))
 
403
        self.assertEqual(revisions, [('1', 1, 0), ('2', 2, 0), ('3c', 3, 0),
 
404
                                     ('4b', 4, 0)])