~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2011-08-17 18:13:57 UTC
  • mfrom: (5268.7.29 transport-segments)
  • Revision ID: pqm@pqm.ubuntu.com-20110817181357-y5q5eth1hk8bl3om
(jelmer) Allow specifying the colocated branch to use in the branch URL,
 and retrieving the branch name using ControlDir._get_selected_branch.
 (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

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