~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

  • Committer: Olaf Conradi
  • Date: 2006-03-29 21:07:20 UTC
  • mto: (1661.1.1 bzr.mbp.remember)
  • mto: This revision was merged to the branch mainline in revision 1663.
  • Revision ID: olaf@conradi.org-20060329210720-8e43fffa2a24d1a4
Re-added AmbiguousBase with a deprecated warning.

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