~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

  • Committer: Patch Queue Manager
  • Date: 2013-07-27 12:46:39 UTC
  • mfrom: (6581.1.3 trunk)
  • Revision ID: pqm@pqm.ubuntu.com-20130727124639-ncw8s8ophdgysemn
(vila) Release 2.6.0 (Vincent Ladeuil)

Show diffs side-by-side

added added

removed removed

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