~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

  • Committer: John Arbash Meinel
  • Date: 2010-02-17 17:11:16 UTC
  • mfrom: (4797.2.17 2.1)
  • mto: (4797.2.18 2.1)
  • mto: This revision was merged to the branch mainline in revision 5055.
  • Revision ID: john@arbash-meinel.com-20100217171116-h7t9223ystbnx5h8
merge bzr.2.1 in preparation for NEWS entry.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2005-2010 Canonical Ltd
2
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
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
import os
18
18
from cStringIO import StringIO
19
19
 
20
 
from bzrlib import log
21
 
from bzrlib.tests import TestCase, TestCaseWithTransport
22
 
from bzrlib.log import (show_log,
23
 
                        get_view_revisions,
24
 
                        LogRevision,
25
 
                        LogFormatter,
26
 
                        LongLogFormatter,
27
 
                        ShortLogFormatter,
28
 
                        LineLogFormatter)
29
 
from bzrlib.branch import Branch
30
 
from bzrlib.errors import InvalidRevisionNumber
31
 
from bzrlib.revision import Revision
32
 
 
33
 
 
34
 
class LogCatcher(LogFormatter):
35
 
    """Pull log messages into list rather than displaying them.
36
 
 
37
 
    For ease of testing we save log messages here rather than actually
38
 
    formatting them, so that we can precisely check the result without
39
 
    being too dependent on the exact formatting.
40
 
 
41
 
    We should also test the LogFormatter.
 
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.
42
109
    """
43
110
 
44
111
    supports_delta = True
45
112
 
46
113
    def __init__(self):
47
114
        super(LogCatcher, self).__init__(to_file=None)
48
 
        self.logs = []
 
115
        self.revisions = []
49
116
 
50
117
    def log_revision(self, revision):
51
 
        self.logs.append(revision)
52
 
 
53
 
 
54
 
class TestShowLog(TestCaseWithTransport):
 
118
        self.revisions.append(revision)
 
119
 
 
120
 
 
121
class TestShowLog(tests.TestCaseWithTransport):
55
122
 
56
123
    def checkDelta(self, delta, **kw):
57
 
        """Check the filenames touched by a delta are as expected."""
 
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
        """
58
129
        for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
 
130
            # By default we expect an empty list
59
131
            expected = kw.get(n, [])
60
132
            # strip out only the path components
61
133
            got = [x[0] for x in getattr(delta, n)]
62
 
            self.assertEquals(expected, got)
 
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)
63
141
 
64
142
    def test_cur_revno(self):
65
143
        wt = self.make_branch_and_tree('.')
67
145
 
68
146
        lf = LogCatcher()
69
147
        wt.commit('empty commit')
70
 
        show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
71
 
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
72
 
                          start_revision=2, end_revision=1) 
73
 
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
74
 
                          start_revision=1, end_revision=2) 
75
 
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
76
 
                          start_revision=0, end_revision=2) 
77
 
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
78
 
                          start_revision=1, end_revision=0) 
79
 
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
80
 
                          start_revision=-1, end_revision=1) 
81
 
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
82
 
                          start_revision=1, end_revision=-1) 
83
 
 
84
 
    def test_simple_log(self):
85
 
        eq = self.assertEquals
86
 
        
 
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):
87
160
        wt = self.make_branch_and_tree('.')
88
 
        b = wt.branch
89
161
 
90
162
        lf = LogCatcher()
91
 
        show_log(b, lf)
 
163
        log.show_log(wt.branch, lf)
92
164
        # no entries yet
93
 
        eq(lf.logs, [])
 
165
        self.assertEqual([], lf.revisions)
 
166
 
 
167
    def test_empty_commit(self):
 
168
        wt = self.make_branch_and_tree('.')
94
169
 
95
170
        wt.commit('empty commit')
96
171
        lf = LogCatcher()
97
 
        show_log(b, lf, verbose=True)
98
 
        eq(len(lf.logs), 1)
99
 
        eq(lf.logs[0].revno, '1')
100
 
        eq(lf.logs[0].rev.message, 'empty commit')
101
 
        d = lf.logs[0].delta
102
 
        self.log('log delta: %r' % d)
103
 
        self.checkDelta(d)
 
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)
104
178
 
 
179
    def test_simple_commit(self):
 
180
        wt = self.make_branch_and_tree('.')
 
181
        wt.commit('empty commit')
105
182
        self.build_tree(['hello'])
106
183
        wt.add('hello')
107
184
        wt.commit('add one file',
108
185
                  committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
109
186
                            u'<test@example.com>')
110
 
 
111
 
        lf = self.make_utf8_encoded_stringio()
112
 
        # log using regular thing
113
 
        show_log(b, LongLogFormatter(lf))
114
 
        lf.seek(0)
115
 
        for l in lf.readlines():
116
 
            self.log(l)
117
 
 
118
 
        # get log as data structure
119
187
        lf = LogCatcher()
120
 
        show_log(b, lf, verbose=True)
121
 
        eq(len(lf.logs), 2)
122
 
        self.log('log entries:')
123
 
        for logentry in lf.logs:
124
 
            self.log('%4s %s' % (logentry.revno, logentry.rev.message))
125
 
        
 
188
        log.show_log(wt.branch, lf, verbose=True)
 
189
        self.assertEqual(2, len(lf.revisions))
126
190
        # first one is most recent
127
 
        logentry = lf.logs[0]
128
 
        eq(logentry.revno, '2')
129
 
        eq(logentry.rev.message, 'add one file')
130
 
        d = logentry.delta
131
 
        self.log('log 2 delta: %r' % d)
132
 
        self.checkDelta(d, added=['hello'])
133
 
        
134
 
        # commit a log message with control characters
135
 
        msg = "All 8-bit chars: " +  ''.join([unichr(x) for x in range(256)])
136
 
        self.log("original commit message: %r", msg)
 
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')
137
200
        wt.commit(msg)
138
201
        lf = LogCatcher()
139
 
        show_log(b, lf, verbose=True)
140
 
        committed_msg = lf.logs[0].rev.message
141
 
        self.log("escaped commit message: %r", committed_msg)
142
 
        self.assert_(msg != committed_msg)
143
 
        self.assert_(len(committed_msg) > len(msg))
 
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)
144
209
 
145
 
        # Check that log message with only XML-valid characters isn't
 
210
    def test_commit_message_without_control_chars(self):
 
211
        wt = self.make_branch_and_tree('.')
146
212
        # escaped.  As ElementTree apparently does some kind of
147
213
        # newline conversion, neither LF (\x0A) nor CR (\x0D) are
148
214
        # included in the test commit message, even though they are
149
215
        # valid XML 1.0 characters.
150
216
        msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
151
 
        self.log("original commit message: %r", msg)
152
217
        wt.commit(msg)
153
218
        lf = LogCatcher()
154
 
        show_log(b, lf, verbose=True)
155
 
        committed_msg = lf.logs[0].rev.message
156
 
        self.log("escaped commit message: %r", committed_msg)
157
 
        self.assert_(msg == committed_msg)
 
219
        log.show_log(wt.branch, lf, verbose=True)
 
220
        committed_msg = lf.revisions[0].rev.message
 
221
        self.assertEqual(msg, committed_msg)
158
222
 
159
223
    def test_deltas_in_merge_revisions(self):
160
224
        """Check deltas created for both mainline and merge revisions"""
161
 
        eq = self.assertEquals
162
225
        wt = self.make_branch_and_tree('parent')
163
226
        self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
164
227
        wt.add('file1')
166
229
        wt.commit(message='add file1 and file2')
167
230
        self.run_bzr('branch parent child')
168
231
        os.unlink('child/file1')
169
 
        print >> file('child/file2', 'wb'), 'hello'
 
232
        file('child/file2', 'wb').write('hello\n')
170
233
        self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
171
234
            'child'])
172
235
        os.chdir('parent')
176
239
        b = wt.branch
177
240
        lf = LogCatcher()
178
241
        lf.supports_merge_revisions = True
179
 
        show_log(b, lf, verbose=True)
180
 
        eq(len(lf.logs),3)
181
 
        logentry = lf.logs[0]
182
 
        eq(logentry.revno, '2')
183
 
        eq(logentry.rev.message, 'merge child branch')
184
 
        d = logentry.delta
185
 
        self.checkDelta(d, removed=['file1'], modified=['file2'])
186
 
        logentry = lf.logs[1]
187
 
        eq(logentry.revno, '1.1.1')
188
 
        eq(logentry.rev.message, 'remove file1 and modify file2')
189
 
        d = logentry.delta
190
 
        self.checkDelta(d, removed=['file1'], modified=['file2'])
191
 
        logentry = lf.logs[2]
192
 
        eq(logentry.revno, '1')
193
 
        eq(logentry.rev.message, 'add file1 and file2')
194
 
        d = logentry.delta
195
 
        self.checkDelta(d, added=['file1', 'file2'])
 
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'])
196
261
 
197
262
 
198
263
def make_commits_with_trailing_newlines(wt):
199
 
    """Helper method for LogFormatter tests"""    
 
264
    """Helper method for LogFormatter tests"""
200
265
    b = wt.branch
201
266
    b.nick='test'
202
267
    open('a', 'wb').write('hello moto\n')
209
274
    wt.commit('multiline\nlog\nmessage\n', rev_id='a2',
210
275
              timestamp=1132586842.411175966, timezone=-6*3600,
211
276
              committer='Joe Foo <joe@foo.com>',
212
 
              author='Joe Bar <joe@bar.com>')
 
277
              authors=['Joe Bar <joe@bar.com>'])
213
278
 
214
279
    open('c', 'wb').write('just another manic monday\n')
215
280
    wt.add('c')
219
284
    return b
220
285
 
221
286
 
222
 
class TestShortLogFormatter(TestCaseWithTransport):
 
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):
223
305
 
224
306
    def test_trailing_newlines(self):
225
307
        wt = self.make_branch_and_tree('.')
226
308
        b = make_commits_with_trailing_newlines(wt)
227
 
        sio = self.make_utf8_encoded_stringio()
228
 
        lf = ShortLogFormatter(to_file=sio)
229
 
        show_log(b, lf)
230
 
        self.assertEquals(sio.getvalue(), """\
 
309
        self.assertFormatterResult("""\
231
310
    3 Joe Foo\t2005-11-21
232
311
      single line with trailing newline
233
312
 
239
318
    1 Joe Foo\t2005-11-21
240
319
      simple log message
241
320
 
242
 
""")
243
 
 
244
 
 
245
 
class TestLongLogFormatter(TestCaseWithTransport):
246
 
 
247
 
    def normalize_log(self,log):
248
 
        """Replaces the variable lines of logs with fixed lines"""
249
 
        author = 'author: Dolor Sit <test@example.com>'
250
 
        committer = 'committer: Lorem Ipsum <test@example.com>'
251
 
        lines = log.splitlines(True)
252
 
        for idx,line in enumerate(lines):
253
 
            stripped_line = line.lstrip()
254
 
            indent = ' ' * (len(line) - len(stripped_line))
255
 
            if stripped_line.startswith('author:'):
256
 
                lines[idx] = indent + author + '\n'
257
 
            elif stripped_line.startswith('committer:'):
258
 
                lines[idx] = indent + committer + '\n'
259
 
            elif stripped_line.startswith('timestamp:'):
260
 
                lines[idx] = indent + 'timestamp: Just now\n'
261
 
        return ''.join(lines)
 
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):
262
491
 
263
492
    def test_verbose_log(self):
264
493
        """Verbose log includes changed files
265
 
        
 
494
 
266
495
        bug #4676
267
496
        """
268
 
        wt = self.make_branch_and_tree('.')
269
 
        b = wt.branch
270
 
        self.build_tree(['a'])
271
 
        wt.add('a')
272
 
        # XXX: why does a longer nick show up?
273
 
        b.nick = 'test_verbose_log'
274
 
        wt.commit(message='add a', 
275
 
                  timestamp=1132711707, 
276
 
                  timezone=36000,
277
 
                  committer='Lorem Ipsum <test@example.com>')
278
 
        logfile = file('out.tmp', 'w+')
279
 
        formatter = LongLogFormatter(to_file=logfile)
280
 
        show_log(b, formatter, verbose=True)
281
 
        logfile.flush()
282
 
        logfile.seek(0)
283
 
        log_contents = logfile.read()
284
 
        self.assertEqualDiff(log_contents, '''\
 
497
        wt = self.make_standard_commit('test_verbose_log', authors=[])
 
498
        self.assertFormatterResult('''\
285
499
------------------------------------------------------------
286
500
revno: 1
287
501
committer: Lorem Ipsum <test@example.com>
291
505
  add a
292
506
added:
293
507
  a
294
 
''')
 
508
''',
 
509
            wt.branch, log.LongLogFormatter,
 
510
            show_log_kwargs=dict(verbose=True))
295
511
 
296
512
    def test_merges_are_indented_by_level(self):
297
513
        wt = self.make_branch_and_tree('parent')
307
523
        os.chdir('../parent')
308
524
        self.run_bzr('merge ../child')
309
525
        wt.commit('merge branch 1')
310
 
        b = wt.branch
311
 
        sio = self.make_utf8_encoded_stringio()
312
 
        lf = LongLogFormatter(to_file=sio)
313
 
        show_log(b, lf, verbose=True)
314
 
        log = self.normalize_log(sio.getvalue())
315
 
        self.assertEqualDiff("""\
 
526
        self.assertFormatterResult("""\
316
527
------------------------------------------------------------
317
 
revno: 2
 
528
revno: 2 [merge]
318
529
committer: Lorem Ipsum <test@example.com>
319
530
branch nick: parent
320
531
timestamp: Just now
321
532
message:
322
533
  merge branch 1
323
534
    ------------------------------------------------------------
324
 
    revno: 1.1.2
 
535
    revno: 1.1.2 [merge]
325
536
    committer: Lorem Ipsum <test@example.com>
326
537
    branch nick: child
327
538
    timestamp: Just now
328
539
    message:
329
540
      merge branch 2
330
541
        ------------------------------------------------------------
331
 
        revno: 1.1.1.1.1
 
542
        revno: 1.2.1
332
543
        committer: Lorem Ipsum <test@example.com>
333
544
        branch nick: smallerchild
334
545
        timestamp: Just now
348
559
timestamp: Just now
349
560
message:
350
561
  first post
351
 
""", log)
 
562
""",
 
563
            wt.branch, log.LongLogFormatter,
 
564
            formatter_kwargs=dict(levels=0),
 
565
            show_log_kwargs=dict(verbose=True),
 
566
            normalize=True)
352
567
 
353
568
    def test_verbose_merge_revisions_contain_deltas(self):
354
569
        wt = self.make_branch_and_tree('parent')
357
572
        wt.commit('first post')
358
573
        self.run_bzr('branch parent child')
359
574
        os.unlink('child/f1')
360
 
        print >> file('child/f2', 'wb'), 'hello'
 
575
        file('child/f2', 'wb').write('hello\n')
361
576
        self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
362
577
            'child'])
363
578
        os.chdir('parent')
364
579
        self.run_bzr('merge ../child')
365
580
        wt.commit('merge branch 1')
366
 
        b = wt.branch
367
 
        sio = self.make_utf8_encoded_stringio()
368
 
        lf = LongLogFormatter(to_file=sio)
369
 
        show_log(b, lf, verbose=True)
370
 
        log = self.normalize_log(sio.getvalue())
371
 
        self.assertEqualDiff("""\
 
581
        self.assertFormatterResult("""\
372
582
------------------------------------------------------------
373
 
revno: 2
 
583
revno: 2 [merge]
374
584
committer: Lorem Ipsum <test@example.com>
375
585
branch nick: parent
376
586
timestamp: Just now
401
611
added:
402
612
  f1
403
613
  f2
404
 
""", log)
 
614
""",
 
615
            wt.branch, log.LongLogFormatter,
 
616
            formatter_kwargs=dict(levels=0),
 
617
            show_log_kwargs=dict(verbose=True),
 
618
            normalize=True)
405
619
 
406
620
    def test_trailing_newlines(self):
407
621
        wt = self.make_branch_and_tree('.')
408
622
        b = make_commits_with_trailing_newlines(wt)
409
 
        sio = self.make_utf8_encoded_stringio()
410
 
        lf = LongLogFormatter(to_file=sio)
411
 
        show_log(b, lf)
412
 
        self.assertEqualDiff(sio.getvalue(), """\
 
623
        self.assertFormatterResult("""\
413
624
------------------------------------------------------------
414
625
revno: 3
415
626
committer: Joe Foo <joe@foo.com>
434
645
timestamp: Mon 2005-11-21 09:24:15 -0600
435
646
message:
436
647
  simple log message
437
 
""")
 
648
""",
 
649
        b, log.LongLogFormatter)
438
650
 
439
651
    def test_author_in_log(self):
440
652
        """Log includes the author name if it's set in
441
653
        the revision properties
442
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):
443
814
        wt = self.make_branch_and_tree('.')
444
 
        b = wt.branch
445
 
        self.build_tree(['a'])
446
 
        wt.add('a')
447
 
        b.nick = 'test_author_log'
448
 
        wt.commit(message='add a',
449
 
                  timestamp=1132711707,
450
 
                  timezone=36000,
451
 
                  committer='Lorem Ipsum <test@example.com>',
452
 
                  author='John Doe <jdoe@example.com>')
453
 
        sio = StringIO()
454
 
        formatter = LongLogFormatter(to_file=sio)
455
 
        show_log(b, formatter)
456
 
        self.assertEqualDiff(sio.getvalue(), '''\
 
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("""\
457
851
------------------------------------------------------------
458
852
revno: 1
459
853
author: John Doe <jdoe@example.com>
462
856
timestamp: Wed 2005-11-23 12:08:27 +1000
463
857
message:
464
858
  add a
465
 
''')
466
 
 
467
 
 
468
 
 
469
 
class TestLineLogFormatter(TestCaseWithTransport):
 
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):
470
890
 
471
891
    def test_line_log(self):
472
892
        """Line log should show revno
473
 
        
 
893
 
474
894
        bug #5162
475
895
        """
476
 
        wt = self.make_branch_and_tree('.')
477
 
        b = wt.branch
478
 
        self.build_tree(['a'])
479
 
        wt.add('a')
480
 
        b.nick = 'test-line-log'
481
 
        wt.commit(message='add a', 
482
 
                  timestamp=1132711707, 
483
 
                  timezone=36000,
484
 
                  committer='Line-Log-Formatter Tester <test@line.log>')
485
 
        logfile = file('out.tmp', 'w+')
486
 
        formatter = LineLogFormatter(to_file=logfile)
487
 
        show_log(b, formatter)
488
 
        logfile.flush()
489
 
        logfile.seek(0)
490
 
        log_contents = logfile.read()
491
 
        self.assertEqualDiff(log_contents, '1: Line-Log-Formatte... 2005-11-23 add a\n')
492
 
 
493
 
    def test_short_log_with_merges(self):
494
 
        wt = self.make_branch_and_memory_tree('.')
495
 
        wt.lock_write()
496
 
        try:
497
 
            wt.add('')
498
 
            wt.commit('rev-1', rev_id='rev-1',
499
 
                      timestamp=1132586655, timezone=36000,
500
 
                      committer='Joe Foo <joe@foo.com>')
501
 
            wt.commit('rev-merged', rev_id='rev-2a',
502
 
                      timestamp=1132586700, timezone=36000,
503
 
                      committer='Joe Foo <joe@foo.com>')
504
 
            wt.set_parent_ids(['rev-1', 'rev-2a'])
505
 
            wt.branch.set_last_revision_info(1, 'rev-1')
506
 
            wt.commit('rev-2', rev_id='rev-2b',
507
 
                      timestamp=1132586800, timezone=36000,
508
 
                      committer='Joe Foo <joe@foo.com>')
509
 
            logfile = self.make_utf8_encoded_stringio()
510
 
            formatter = ShortLogFormatter(to_file=logfile)
511
 
            show_log(wt.branch, formatter)
512
 
            logfile.flush()
513
 
            self.assertEqualDiff("""\
514
 
    2 Joe Foo\t2005-11-22 [merge]
515
 
      rev-2
516
 
 
517
 
    1 Joe Foo\t2005-11-22
518
 
      rev-1
519
 
 
520
 
""", logfile.getvalue())
521
 
        finally:
522
 
            wt.unlock()
 
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)
523
903
 
524
904
    def test_trailing_newlines(self):
525
905
        wt = self.make_branch_and_tree('.')
526
906
        b = make_commits_with_trailing_newlines(wt)
527
 
        sio = self.make_utf8_encoded_stringio()
528
 
        lf = LineLogFormatter(to_file=sio)
529
 
        show_log(b, lf)
530
 
        self.assertEqualDiff(sio.getvalue(), """\
 
907
        self.assertFormatterResult("""\
531
908
3: Joe Foo 2005-11-21 single line with trailing newline
532
909
2: Joe Bar 2005-11-21 multiline
533
910
1: Joe Foo 2005-11-21 simple log message
534
 
""")
535
 
 
536
 
 
537
 
class TestGetViewRevisions(TestCaseWithTransport):
 
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):
538
1000
 
539
1001
    def make_tree_with_commits(self):
540
1002
        """Create a tree with well-known revision ids"""
558
1020
        # 4a: 3.1.1
559
1021
        return mainline_revs, rev_nos, wt
560
1022
 
561
 
    def make_tree_with_many_merges(self):
 
1023
    def make_branch_with_many_merges(self):
562
1024
        """Create a tree with well-known revision ids"""
563
 
        wt = self.make_branch_and_tree('tree1')
564
 
        wt.commit('commit one', rev_id='1')
565
 
        wt.commit('commit two', rev_id='2')
566
 
        tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
567
 
        tree3.commit('commit three a', rev_id='3a')
568
 
        tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
569
 
        tree2.merge_from_branch(tree3.branch)
570
 
        tree2.commit('commit three b', rev_id='3b')
571
 
        wt.merge_from_branch(tree2.branch)
572
 
        wt.commit('commit three c', rev_id='3c')
573
 
        tree2.commit('four-a', rev_id='4a')
574
 
        wt.merge_from_branch(tree2.branch)
575
 
        wt.commit('four-b', rev_id='4b')
 
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
 
576
1053
        mainline_revs = [None, '1', '2', '3c', '4b']
577
1054
        rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
578
1055
        full_rev_nos_for_reference = {
579
1056
            '1': '1',
580
1057
            '2': '2',
581
 
            '3a': '2.2.1', #first commit tree 3
582
 
            '3b': '2.1.1', # first commit tree 2
 
1058
            '3a': '2.1.1', #first commit tree 3
 
1059
            '3b': '2.2.1', # first commit tree 2
583
1060
            '3c': '3', #merges 3b to main
584
 
            '4a': '2.1.2', # second commit tree 2
 
1061
            '4a': '2.2.2', # second commit tree 2
585
1062
            '4b': '4', # merges 4a to main
586
1063
            }
587
 
        return mainline_revs, rev_nos, wt
 
1064
        return mainline_revs, rev_nos, builder.get_branch()
588
1065
 
589
1066
    def test_get_view_revisions_forward(self):
590
1067
        """Test the get_view_revisions method"""
591
1068
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
592
 
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
593
 
                                            'forward'))
 
1069
        wt.lock_read()
 
1070
        self.addCleanup(wt.unlock)
 
1071
        revisions = list(log.get_view_revisions(
 
1072
                mainline_revs, rev_nos, wt.branch, 'forward'))
594
1073
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
595
 
            revisions)
596
 
        revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
597
 
                                             'forward', include_merges=False))
 
1074
                         revisions)
 
1075
        revisions2 = list(log.get_view_revisions(
 
1076
                mainline_revs, rev_nos, wt.branch, 'forward',
 
1077
                include_merges=False))
598
1078
        self.assertEqual(revisions, revisions2)
599
1079
 
600
1080
    def test_get_view_revisions_reverse(self):
601
1081
        """Test the get_view_revisions with reverse"""
602
1082
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
603
 
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
604
 
                                            'reverse'))
 
1083
        wt.lock_read()
 
1084
        self.addCleanup(wt.unlock)
 
1085
        revisions = list(log.get_view_revisions(
 
1086
                mainline_revs, rev_nos, wt.branch, 'reverse'))
605
1087
        self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
606
 
            revisions)
607
 
        revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
608
 
                                             'reverse', include_merges=False))
 
1088
                         revisions)
 
1089
        revisions2 = list(log.get_view_revisions(
 
1090
                mainline_revs, rev_nos, wt.branch, 'reverse',
 
1091
                include_merges=False))
609
1092
        self.assertEqual(revisions, revisions2)
610
1093
 
611
1094
    def test_get_view_revisions_merge(self):
612
1095
        """Test get_view_revisions when there are merges"""
613
1096
        mainline_revs, rev_nos, wt = self.make_tree_with_merges()
614
 
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
615
 
                                            'forward'))
616
 
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
617
 
            ('4b', '4', 0), ('4a', '3.1.1', 1)],
618
 
            revisions)
619
 
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
620
 
                                             'forward', include_merges=False))
621
 
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
622
 
            ('4b', '4', 0)],
623
 
            revisions)
 
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)
624
1110
 
625
1111
    def test_get_view_revisions_merge_reverse(self):
626
1112
        """Test get_view_revisions in reverse when there are merges"""
627
1113
        mainline_revs, rev_nos, wt = self.make_tree_with_merges()
628
 
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
629
 
                                            'reverse'))
 
1114
        wt.lock_read()
 
1115
        self.addCleanup(wt.unlock)
 
1116
        revisions = list(log.get_view_revisions(
 
1117
                mainline_revs, rev_nos, wt.branch, 'reverse'))
630
1118
        self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
631
 
            ('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
632
 
            revisions)
633
 
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
634
 
                                             'reverse', include_merges=False))
 
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))
635
1124
        self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
636
 
            ('1', '1', 0)],
637
 
            revisions)
 
1125
                          ('1', '1', 0)],
 
1126
                         revisions)
638
1127
 
639
1128
    def test_get_view_revisions_merge2(self):
640
1129
        """Test get_view_revisions when there are merges"""
641
 
        mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
642
 
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
643
 
                                            'forward'))
 
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'))
644
1135
        expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
645
 
            ('3a', '2.2.1', 1), ('3b', '2.1.1', 1), ('4b', '4', 0),
646
 
            ('4a', '2.1.2', 1)]
 
1136
                    ('3b', '2.2.1', 1), ('3a', '2.1.1', 2), ('4b', '4', 0),
 
1137
                    ('4a', '2.2.2', 1)]
647
1138
        self.assertEqual(expected, revisions)
648
 
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
649
 
                                             'forward', include_merges=False))
 
1139
        revisions = list(log.get_view_revisions(
 
1140
                mainline_revs, rev_nos, b, 'forward',
 
1141
                include_merges=False))
650
1142
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
651
 
            ('4b', '4', 0)],
652
 
            revisions)
653
 
 
654
 
 
655
 
class TestGetRevisionsTouchingFileID(TestCaseWithTransport):
 
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):
656
1177
 
657
1178
    def create_tree_with_single_merge(self):
658
1179
        """Create a branch with a moderate layout.
708
1229
        tree.commit('D', rev_id='D')
709
1230
 
710
1231
        # Switch to a read lock for this tree.
711
 
        # We still have addCleanup(unlock)
 
1232
        # We still have an addCleanup(tree.unlock) pending
712
1233
        tree.unlock()
713
1234
        tree.lock_read()
714
1235
        return tree
715
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
 
716
1250
    def test_tree_with_single_merge(self):
717
1251
        """Make sure the tree layout is correct."""
718
1252
        tree = self.create_tree_with_single_merge()
719
1253
        rev_A_tree = tree.branch.repository.revision_tree('A')
720
1254
        rev_B_tree = tree.branch.repository.revision_tree('B')
721
 
 
722
 
        f1_changed = (u'f1', 'f1-id', 'file', True, False)
723
 
        f2_changed = (u'f2', 'f2-id', 'file', True, False)
724
 
        f3_changed = (u'f3', 'f3-id', 'file', True, False)
725
 
 
726
 
        delta = rev_B_tree.changes_from(rev_A_tree)
727
 
        self.assertEqual([f1_changed, f3_changed], delta.modified)
728
 
        self.assertEqual([], delta.renamed)
729
 
        self.assertEqual([], delta.added)
730
 
        self.assertEqual([], delta.removed)
731
 
 
732
1255
        rev_C_tree = tree.branch.repository.revision_tree('C')
733
 
        delta = rev_C_tree.changes_from(rev_A_tree)
734
 
        self.assertEqual([f2_changed, f3_changed], delta.modified)
735
 
        self.assertEqual([], delta.renamed)
736
 
        self.assertEqual([], delta.added)
737
 
        self.assertEqual([], delta.removed)
738
 
 
739
1256
        rev_D_tree = tree.branch.repository.revision_tree('D')
740
 
        delta = rev_D_tree.changes_from(rev_B_tree)
741
 
        self.assertEqual([f2_changed, f3_changed], delta.modified)
742
 
        self.assertEqual([], delta.renamed)
743
 
        self.assertEqual([], delta.added)
744
 
        self.assertEqual([], delta.removed)
745
 
 
746
 
        delta = rev_D_tree.changes_from(rev_C_tree)
747
 
        self.assertEqual([f1_changed, f3_changed], delta.modified)
748
 
        self.assertEqual([], delta.renamed)
749
 
        self.assertEqual([], delta.added)
750
 
        self.assertEqual([], delta.removed)
 
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'])
751
1269
 
752
1270
    def assertAllRevisionsForFileID(self, tree, file_id, revisions):
753
 
        """Make sure _filter_revisions_touching_file_id returns the right values.
 
1271
        """Ensure _filter_revisions_touching_file_id returns the right values.
754
1272
 
755
1273
        Get the return value from _filter_revisions_touching_file_id and make
756
1274
        sure they are correct.
757
1275
        """
758
 
        # The api for _get_revisions_touching_file_id is a little crazy,
 
1276
        # The api for _filter_revisions_touching_file_id is a little crazy.
759
1277
        # So we do the setup here.
760
1278
        mainline = tree.branch.revision_history()
761
1279
        mainline.insert(0, None)
763
1281
        view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
764
1282
                                                'reverse', True)
765
1283
        actual_revs = log._filter_revisions_touching_file_id(
766
 
                            tree.branch, 
 
1284
                            tree.branch,
767
1285
                            file_id,
768
 
                            mainline,
769
1286
                            list(view_revs_iter))
770
1287
        self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
771
1288
 
783
1300
    def test_file_id_f3(self):
784
1301
        tree = self.create_tree_with_single_merge()
785
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'])
786
1318
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
787
1319
 
788
 
 
789
 
class TestShowChangedRevisions(TestCaseWithTransport):
 
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):
790
1330
 
791
1331
    def test_show_changed_revisions_verbose(self):
792
1332
        tree = self.make_branch_and_tree('tree_a')
799
1339
        self.assertNotContainsRe(s.getvalue(), 'foo')
800
1340
 
801
1341
 
802
 
class TestLogFormatter(TestCase):
 
1342
class TestLogFormatter(tests.TestCase):
803
1343
 
804
1344
    def test_short_committer(self):
805
 
        rev = Revision('a-id')
 
1345
        rev = revision.Revision('a-id')
806
1346
        rev.committer = 'John Doe <jdoe@example.com>'
807
 
        lf = LogFormatter(None)
 
1347
        lf = log.LogFormatter(None)
808
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))
809
1359
 
810
1360
    def test_short_author(self):
811
 
        rev = Revision('a-id')
 
1361
        rev = revision.Revision('a-id')
812
1362
        rev.committer = 'John Doe <jdoe@example.com>'
813
 
        lf = LogFormatter(None)
 
1363
        lf = log.LogFormatter(None)
814
1364
        self.assertEqual('John Doe', lf.short_author(rev))
815
1365
        rev.properties['author'] = 'John Smith <jsmith@example.com>'
816
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')