~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

  • Committer: John Arbash Meinel
  • Author(s): Mark Hammond
  • Date: 2008-09-09 17:02:21 UTC
  • mto: This revision was merged to the branch mainline in revision 3697.
  • Revision ID: john@arbash-meinel.com-20080909170221-svim3jw2mrz0amp3
An updated transparent icon for bzr.

Show diffs side-by-side

added added

removed removed

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