~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

  • Committer: Martin Pool
  • Date: 2010-01-29 10:36:23 UTC
  • mto: This revision was merged to the branch mainline in revision 4992.
  • Revision ID: mbp@sourcefrog.net-20100129103623-hywka5hymo5z13jw
Change url to canonical.com or wiki, plus some doc improvements in passing

Show diffs side-by-side

added added

removed removed

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