~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

Implement version 3 of the network protocol. (Andrew Bennetts)

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):
53
 
 
54
 
    def setUp(self):
55
 
        super(TestCaseForLogFormatter, self).setUp()
56
 
        # 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
59
 
        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.
 
20
from bzrlib import log
 
21
from bzrlib.tests import TestCase, TestCaseWithTransport
 
22
from bzrlib.log import (show_log,
 
23
                        get_view_revisions,
 
24
                        LogRevision,
 
25
                        LogFormatter,
 
26
                        LongLogFormatter,
 
27
                        ShortLogFormatter,
 
28
                        LineLogFormatter)
 
29
from bzrlib.branch import Branch
 
30
from bzrlib.errors import (
 
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 LogCatcher(LogFormatter):
 
42
    """Pull log messages into list rather than displaying them.
 
43
 
 
44
    For ease of testing we save log messages here rather than actually
 
45
    formatting them, so that we can precisely check the result without
 
46
    being too dependent on the exact formatting.
 
47
 
 
48
    We should also test the LogFormatter.
128
49
    """
129
50
 
130
 
    supports_merge_revisions = True
131
51
    supports_delta = True
132
 
    supports_diff = True
133
 
    preferred_levels = 0
134
52
 
135
 
    def __init__(self, *args, **kwargs):
136
 
        kwargs.update(dict(to_file=None))
137
 
        super(LogCatcher, self).__init__(*args, **kwargs)
138
 
        self.revisions = []
 
53
    def __init__(self):
 
54
        super(LogCatcher, self).__init__(to_file=None)
 
55
        self.logs = []
139
56
 
140
57
    def log_revision(self, revision):
141
 
        self.revisions.append(revision)
142
 
 
143
 
 
144
 
class TestShowLog(tests.TestCaseWithTransport):
 
58
        self.logs.append(revision)
 
59
 
 
60
 
 
61
class TestShowLog(TestCaseWithTransport):
145
62
 
146
63
    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
 
        """
 
64
        """Check the filenames touched by a delta are as expected."""
152
65
        for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
153
 
            # By default we expect an empty list
154
66
            expected = kw.get(n, [])
155
67
            # strip out only the path components
156
68
            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)
 
69
            self.assertEquals(expected, got)
164
70
 
165
71
    def test_cur_revno(self):
166
72
        wt = self.make_branch_and_tree('.')
168
74
 
169
75
        lf = LogCatcher()
170
76
        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):
 
77
        show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
 
78
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
79
                          start_revision=2, end_revision=1) 
 
80
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
81
                          start_revision=1, end_revision=2) 
 
82
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
83
                          start_revision=0, end_revision=2) 
 
84
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
85
                          start_revision=1, end_revision=0) 
 
86
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
87
                          start_revision=-1, end_revision=1) 
 
88
        self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
 
89
                          start_revision=1, end_revision=-1) 
 
90
 
 
91
    def test_simple_log(self):
 
92
        eq = self.assertEquals
 
93
        
183
94
        wt = self.make_branch_and_tree('.')
 
95
        b = wt.branch
184
96
 
185
97
        lf = LogCatcher()
186
 
        log.show_log(wt.branch, lf)
 
98
        show_log(b, lf)
187
99
        # no entries yet
188
 
        self.assertEqual([], lf.revisions)
189
 
 
190
 
    def test_empty_commit(self):
191
 
        wt = self.make_branch_and_tree('.')
 
100
        eq(lf.logs, [])
192
101
 
193
102
        wt.commit('empty commit')
194
103
        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)
 
104
        show_log(b, lf, verbose=True)
 
105
        eq(len(lf.logs), 1)
 
106
        eq(lf.logs[0].revno, '1')
 
107
        eq(lf.logs[0].rev.message, 'empty commit')
 
108
        d = lf.logs[0].delta
 
109
        self.log('log delta: %r' % d)
 
110
        self.checkDelta(d)
201
111
 
202
 
    def test_simple_commit(self):
203
 
        wt = self.make_branch_and_tree('.')
204
 
        wt.commit('empty commit')
205
112
        self.build_tree(['hello'])
206
113
        wt.add('hello')
207
114
        wt.commit('add one file',
208
115
                  committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
209
116
                            u'<test@example.com>')
 
117
 
 
118
        lf = self.make_utf8_encoded_stringio()
 
119
        # log using regular thing
 
120
        show_log(b, LongLogFormatter(lf))
 
121
        lf.seek(0)
 
122
        for l in lf.readlines():
 
123
            self.log(l)
 
124
 
 
125
        # get log as data structure
210
126
        lf = LogCatcher()
211
 
        log.show_log(wt.branch, lf, verbose=True)
212
 
        self.assertEqual(2, len(lf.revisions))
 
127
        show_log(b, lf, verbose=True)
 
128
        eq(len(lf.logs), 2)
 
129
        self.log('log entries:')
 
130
        for logentry in lf.logs:
 
131
            self.log('%4s %s' % (logentry.revno, logentry.rev.message))
 
132
        
213
133
        # 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')
 
134
        logentry = lf.logs[0]
 
135
        eq(logentry.revno, '2')
 
136
        eq(logentry.rev.message, 'add one file')
 
137
        d = logentry.delta
 
138
        self.log('log 2 delta: %r' % d)
 
139
        self.checkDelta(d, added=['hello'])
 
140
        
 
141
        # commit a log message with control characters
 
142
        msg = "All 8-bit chars: " +  ''.join([unichr(x) for x in range(256)])
 
143
        self.log("original commit message: %r", msg)
223
144
        wt.commit(msg)
224
145
        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)
 
146
        show_log(b, lf, verbose=True)
 
147
        committed_msg = lf.logs[0].rev.message
 
148
        self.log("escaped commit message: %r", committed_msg)
 
149
        self.assert_(msg != committed_msg)
 
150
        self.assert_(len(committed_msg) > len(msg))
232
151
 
233
 
    def test_commit_message_without_control_chars(self):
234
 
        wt = self.make_branch_and_tree('.')
 
152
        # Check that log message with only XML-valid characters isn't
235
153
        # escaped.  As ElementTree apparently does some kind of
236
154
        # newline conversion, neither LF (\x0A) nor CR (\x0D) are
237
155
        # included in the test commit message, even though they are
238
156
        # valid XML 1.0 characters.
239
157
        msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
 
158
        self.log("original commit message: %r", msg)
240
159
        wt.commit(msg)
241
160
        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)
 
161
        show_log(b, lf, verbose=True)
 
162
        committed_msg = lf.logs[0].rev.message
 
163
        self.log("escaped commit message: %r", committed_msg)
 
164
        self.assert_(msg == committed_msg)
245
165
 
246
166
    def test_deltas_in_merge_revisions(self):
247
167
        """Check deltas created for both mainline and merge revisions"""
 
168
        eq = self.assertEquals
248
169
        wt = self.make_branch_and_tree('parent')
249
170
        self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
250
171
        wt.add('file1')
252
173
        wt.commit(message='add file1 and file2')
253
174
        self.run_bzr('branch parent child')
254
175
        os.unlink('child/file1')
255
 
        with file('child/file2', 'wb') as f: f.write('hello\n')
 
176
        file('child/file2', 'wb').write('hello\n')
256
177
        self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
257
178
            'child'])
258
179
        os.chdir('parent')
262
183
        b = wt.branch
263
184
        lf = LogCatcher()
264
185
        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):
 
186
        show_log(b, lf, verbose=True)
 
187
        eq(len(lf.logs),3)
 
188
        logentry = lf.logs[0]
 
189
        eq(logentry.revno, '2')
 
190
        eq(logentry.rev.message, 'merge child branch')
 
191
        d = logentry.delta
 
192
        self.checkDelta(d, removed=['file1'], modified=['file2'])
 
193
        logentry = lf.logs[1]
 
194
        eq(logentry.revno, '1.1.1')
 
195
        eq(logentry.rev.message, 'remove file1 and modify file2')
 
196
        d = logentry.delta
 
197
        self.checkDelta(d, removed=['file1'], modified=['file2'])
 
198
        logentry = lf.logs[2]
 
199
        eq(logentry.revno, '1')
 
200
        eq(logentry.rev.message, 'add file1 and file2')
 
201
        d = logentry.delta
 
202
        self.checkDelta(d, added=['file1', 'file2'])
 
203
 
 
204
    def test_merges_nonsupporting_formatter(self):
 
205
        """Tests that show_log will raise if the formatter doesn't
 
206
        support merge revisions."""
 
207
        wt = self.make_branch_and_memory_tree('.')
 
208
        wt.lock_write()
 
209
        try:
 
210
            wt.add('')
 
211
            wt.commit('rev-1', rev_id='rev-1',
 
212
                      timestamp=1132586655, timezone=36000,
 
213
                      committer='Joe Foo <joe@foo.com>')
 
214
            wt.commit('rev-merged', rev_id='rev-2a',
 
215
                      timestamp=1132586700, timezone=36000,
 
216
                      committer='Joe Foo <joe@foo.com>')
 
217
            wt.set_parent_ids(['rev-1', 'rev-2a'])
 
218
            wt.branch.set_last_revision_info(1, 'rev-1')
 
219
            wt.commit('rev-2', rev_id='rev-2b',
 
220
                      timestamp=1132586800, timezone=36000,
 
221
                      committer='Joe Foo <joe@foo.com>')
 
222
            logfile = self.make_utf8_encoded_stringio()
 
223
            formatter = ShortLogFormatter(to_file=logfile)
 
224
            wtb = wt.branch
 
225
            lf = LogCatcher()
 
226
            revspec = RevisionSpec.from_string('1.1.1')
 
227
            rev = revspec.in_history(wtb)
 
228
            self.assertRaises(BzrCommandError, show_log, wtb, lf,
 
229
                              start_revision=rev, end_revision=rev)
 
230
        finally:
 
231
            wt.unlock()
 
232
 
 
233
 
 
234
def make_commits_with_trailing_newlines(wt):
 
235
    """Helper method for LogFormatter tests"""    
 
236
    b = wt.branch
 
237
    b.nick='test'
 
238
    open('a', 'wb').write('hello moto\n')
 
239
    wt.add('a')
 
240
    wt.commit('simple log message', rev_id='a1',
 
241
              timestamp=1132586655.459960938, timezone=-6*3600,
 
242
              committer='Joe Foo <joe@foo.com>')
 
243
    open('b', 'wb').write('goodbye\n')
 
244
    wt.add('b')
 
245
    wt.commit('multiline\nlog\nmessage\n', rev_id='a2',
 
246
              timestamp=1132586842.411175966, timezone=-6*3600,
 
247
              committer='Joe Foo <joe@foo.com>',
 
248
              author='Joe Bar <joe@bar.com>')
 
249
 
 
250
    open('c', 'wb').write('just another manic monday\n')
 
251
    wt.add('c')
 
252
    wt.commit('single line with trailing newline\n', rev_id='a3',
 
253
              timestamp=1132587176.835228920, timezone=-6*3600,
 
254
              committer = 'Joe Foo <joe@foo.com>')
 
255
    return b
 
256
 
 
257
 
 
258
def normalize_log(log):
 
259
    """Replaces the variable lines of logs with fixed lines"""
 
260
    author = 'author: Dolor Sit <test@example.com>'
 
261
    committer = 'committer: Lorem Ipsum <test@example.com>'
 
262
    lines = log.splitlines(True)
 
263
    for idx,line in enumerate(lines):
 
264
        stripped_line = line.lstrip()
 
265
        indent = ' ' * (len(line) - len(stripped_line))
 
266
        if stripped_line.startswith('author:'):
 
267
            lines[idx] = indent + author + '\n'
 
268
        elif stripped_line.startswith('committer:'):
 
269
            lines[idx] = indent + committer + '\n'
 
270
        elif stripped_line.startswith('timestamp:'):
 
271
            lines[idx] = indent + 'timestamp: Just now\n'
 
272
    return ''.join(lines)
 
273
 
 
274
 
 
275
class TestShortLogFormatter(TestCaseWithTransport):
319
276
 
320
277
    def test_trailing_newlines(self):
321
278
        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
 
279
        b = make_commits_with_trailing_newlines(wt)
 
280
        sio = self.make_utf8_encoded_stringio()
 
281
        lf = ShortLogFormatter(to_file=sio)
 
282
        show_log(b, lf)
 
283
        self.assertEqualDiff(sio.getvalue(), """\
 
284
    3 Joe Foo\t2005-11-21
325
285
      single line with trailing newline
326
286
 
327
 
    2 Joe Foo\t2005-11-22
 
287
    2 Joe Bar\t2005-11-21
328
288
      multiline
329
289
      log
330
290
      message
331
291
 
332
 
    1 Joe Foo\t2005-11-22
 
292
    1 Joe Foo\t2005-11-21
333
293
      simple log message
334
294
 
335
 
""",
336
 
            b, log.ShortLogFormatter)
 
295
""")
337
296
 
338
297
    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)
 
298
        wt = self.make_branch_and_memory_tree('.')
 
299
        wt.lock_write()
 
300
        try:
 
301
            wt.add('')
 
302
            wt.commit('rev-1', rev_id='rev-1',
 
303
                      timestamp=1132586655, timezone=36000,
 
304
                      committer='Joe Foo <joe@foo.com>')
 
305
            wt.commit('rev-merged', rev_id='rev-2a',
 
306
                      timestamp=1132586700, timezone=36000,
 
307
                      committer='Joe Foo <joe@foo.com>')
 
308
            wt.set_parent_ids(['rev-1', 'rev-2a'])
 
309
            wt.branch.set_last_revision_info(1, 'rev-1')
 
310
            wt.commit('rev-2', rev_id='rev-2b',
 
311
                      timestamp=1132586800, timezone=36000,
 
312
                      committer='Joe Foo <joe@foo.com>')
 
313
            logfile = self.make_utf8_encoded_stringio()
 
314
            formatter = ShortLogFormatter(to_file=logfile)
 
315
            show_log(wt.branch, formatter)
 
316
            self.assertEqualDiff(logfile.getvalue(), """\
 
317
    2 Joe Foo\t2005-11-22 [merge]
 
318
      rev-2
 
319
 
 
320
    1 Joe Foo\t2005-11-22
 
321
      rev-1
 
322
 
 
323
""")
 
324
        finally:
 
325
            wt.unlock()
395
326
 
396
327
    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):
 
328
        wt = self.make_branch_and_memory_tree('.')
 
329
        wt.lock_write()
 
330
        try:
 
331
            wt.add('')
 
332
            wt.commit('rev-1', rev_id='rev-1',
 
333
                      timestamp=1132586655, timezone=36000,
 
334
                      committer='Joe Foo <joe@foo.com>')
 
335
            wt.commit('rev-merged', rev_id='rev-2a',
 
336
                      timestamp=1132586700, timezone=36000,
 
337
                      committer='Joe Foo <joe@foo.com>')
 
338
            wt.set_parent_ids(['rev-1', 'rev-2a'])
 
339
            wt.branch.set_last_revision_info(1, 'rev-1')
 
340
            wt.commit('rev-2', rev_id='rev-2b',
 
341
                      timestamp=1132586800, timezone=36000,
 
342
                      committer='Joe Foo <joe@foo.com>')
 
343
            logfile = self.make_utf8_encoded_stringio()
 
344
            formatter = ShortLogFormatter(to_file=logfile)
 
345
            revspec = RevisionSpec.from_string('1.1.1')
 
346
            wtb = wt.branch
 
347
            rev = revspec.in_history(wtb)
 
348
            show_log(wtb, formatter, start_revision=rev, end_revision=rev)
 
349
            self.assertEqualDiff(logfile.getvalue(), """\
 
350
1.1.1 Joe Foo\t2005-11-22
 
351
      rev-merged
 
352
 
 
353
""")
 
354
        finally:
 
355
            wt.unlock()
 
356
 
 
357
 
 
358
class TestLongLogFormatter(TestCaseWithTransport):
471
359
 
472
360
    def test_verbose_log(self):
473
361
        """Verbose log includes changed files
474
 
 
 
362
        
475
363
        bug #4676
476
364
        """
477
 
        wt = self.make_standard_commit('test_verbose_log', authors=[])
478
 
        self.assertFormatterResult('''\
 
365
        wt = self.make_branch_and_tree('.')
 
366
        b = wt.branch
 
367
        self.build_tree(['a'])
 
368
        wt.add('a')
 
369
        # XXX: why does a longer nick show up?
 
370
        b.nick = 'test_verbose_log'
 
371
        wt.commit(message='add a', 
 
372
                  timestamp=1132711707, 
 
373
                  timezone=36000,
 
374
                  committer='Lorem Ipsum <test@example.com>')
 
375
        logfile = file('out.tmp', 'w+')
 
376
        formatter = LongLogFormatter(to_file=logfile)
 
377
        show_log(b, formatter, verbose=True)
 
378
        logfile.flush()
 
379
        logfile.seek(0)
 
380
        log_contents = logfile.read()
 
381
        self.assertEqualDiff(log_contents, '''\
479
382
------------------------------------------------------------
480
383
revno: 1
481
384
committer: Lorem Ipsum <test@example.com>
482
385
branch nick: test_verbose_log
483
 
timestamp: Tue 2005-11-22 00:00:00 +0000
 
386
timestamp: Wed 2005-11-23 12:08:27 +1000
484
387
message:
485
388
  add a
486
389
added:
487
390
  a
488
 
''',
489
 
            wt.branch, log.LongLogFormatter,
490
 
            show_log_kwargs=dict(verbose=True))
 
391
''')
491
392
 
492
393
    def test_merges_are_indented_by_level(self):
493
394
        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("""\
 
395
        wt.commit('first post')
 
396
        self.run_bzr('branch parent child')
 
397
        self.run_bzr(['commit', '-m', 'branch 1', '--unchanged', 'child'])
 
398
        self.run_bzr('branch child smallerchild')
 
399
        self.run_bzr(['commit', '-m', 'branch 2', '--unchanged',
 
400
            'smallerchild'])
 
401
        os.chdir('child')
 
402
        self.run_bzr('merge ../smallerchild')
 
403
        self.run_bzr(['commit', '-m', 'merge branch 2'])
 
404
        os.chdir('../parent')
 
405
        self.run_bzr('merge ../child')
 
406
        wt.commit('merge branch 1')
 
407
        b = wt.branch
 
408
        sio = self.make_utf8_encoded_stringio()
 
409
        lf = LongLogFormatter(to_file=sio)
 
410
        show_log(b, lf, verbose=True)
 
411
        log = normalize_log(sio.getvalue())
 
412
        self.assertEqualDiff(log, """\
504
413
------------------------------------------------------------
505
 
revno: 2 [merge]
506
 
committer: Joe Foo <joe@foo.com>
 
414
revno: 2
 
415
committer: Lorem Ipsum <test@example.com>
507
416
branch nick: parent
508
 
timestamp: Tue 2005-11-22 00:00:04 +0000
 
417
timestamp: Just now
509
418
message:
510
419
  merge branch 1
511
420
    ------------------------------------------------------------
512
 
    revno: 1.1.2 [merge]
513
 
    committer: Joe Foo <joe@foo.com>
 
421
    revno: 1.1.2
 
422
    committer: Lorem Ipsum <test@example.com>
514
423
    branch nick: child
515
 
    timestamp: Tue 2005-11-22 00:00:03 +0000
 
424
    timestamp: Just now
516
425
    message:
517
426
      merge branch 2
518
427
        ------------------------------------------------------------
519
428
        revno: 1.2.1
520
 
        committer: Joe Foo <joe@foo.com>
 
429
        committer: Lorem Ipsum <test@example.com>
521
430
        branch nick: smallerchild
522
 
        timestamp: Tue 2005-11-22 00:00:02 +0000
 
431
        timestamp: Just now
523
432
        message:
524
433
          branch 2
525
434
    ------------------------------------------------------------
526
435
    revno: 1.1.1
527
 
    committer: Joe Foo <joe@foo.com>
 
436
    committer: Lorem Ipsum <test@example.com>
528
437
    branch nick: child
529
 
    timestamp: Tue 2005-11-22 00:00:01 +0000
 
438
    timestamp: Just now
530
439
    message:
531
440
      branch 1
532
441
------------------------------------------------------------
533
442
revno: 1
534
 
committer: Joe Foo <joe@foo.com>
 
443
committer: Lorem Ipsum <test@example.com>
535
444
branch nick: parent
536
 
timestamp: Tue 2005-11-22 00:00:00 +0000
 
445
timestamp: Just now
537
446
message:
538
447
  first post
539
 
""",
540
 
            wt.branch, log.LongLogFormatter,
541
 
            formatter_kwargs=dict(levels=0),
542
 
            show_log_kwargs=dict(verbose=True))
 
448
""")
543
449
 
544
450
    def test_verbose_merge_revisions_contain_deltas(self):
545
451
        wt = self.make_branch_and_tree('parent')
546
452
        self.build_tree(['parent/f1', 'parent/f2'])
547
453
        wt.add(['f1','f2'])
548
 
        self.wt_commit(wt, 'first post')
549
 
        child_wt = wt.bzrdir.sprout('child').open_workingtree()
 
454
        wt.commit('first post')
 
455
        self.run_bzr('branch parent child')
550
456
        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("""\
 
457
        file('child/f2', 'wb').write('hello\n')
 
458
        self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
 
459
            'child'])
 
460
        os.chdir('parent')
 
461
        self.run_bzr('merge ../child')
 
462
        wt.commit('merge branch 1')
 
463
        b = wt.branch
 
464
        sio = self.make_utf8_encoded_stringio()
 
465
        lf = LongLogFormatter(to_file=sio)
 
466
        show_log(b, lf, verbose=True)
 
467
        log = normalize_log(sio.getvalue())
 
468
        self.assertEqualDiff(log, """\
556
469
------------------------------------------------------------
557
 
revno: 2 [merge]
558
 
committer: Joe Foo <joe@foo.com>
 
470
revno: 2
 
471
committer: Lorem Ipsum <test@example.com>
559
472
branch nick: parent
560
 
timestamp: Tue 2005-11-22 00:00:02 +0000
 
473
timestamp: Just now
561
474
message:
562
475
  merge branch 1
563
476
removed:
566
479
  f2
567
480
    ------------------------------------------------------------
568
481
    revno: 1.1.1
569
 
    committer: Joe Foo <joe@foo.com>
 
482
    committer: Lorem Ipsum <test@example.com>
570
483
    branch nick: child
571
 
    timestamp: Tue 2005-11-22 00:00:01 +0000
 
484
    timestamp: Just now
572
485
    message:
573
486
      removed f1 and modified f2
574
487
    removed:
577
490
      f2
578
491
------------------------------------------------------------
579
492
revno: 1
580
 
committer: Joe Foo <joe@foo.com>
 
493
committer: Lorem Ipsum <test@example.com>
581
494
branch nick: parent
582
 
timestamp: Tue 2005-11-22 00:00:00 +0000
 
495
timestamp: Just now
583
496
message:
584
497
  first post
585
498
added:
586
499
  f1
587
500
  f2
588
 
""",
589
 
            wt.branch, log.LongLogFormatter,
590
 
            formatter_kwargs=dict(levels=0),
591
 
            show_log_kwargs=dict(verbose=True))
 
501
""")
592
502
 
593
503
    def test_trailing_newlines(self):
594
504
        wt = self.make_branch_and_tree('.')
595
 
        b = self.make_commits_with_trailing_newlines(wt)
596
 
        self.assertFormatterResult("""\
 
505
        b = make_commits_with_trailing_newlines(wt)
 
506
        sio = self.make_utf8_encoded_stringio()
 
507
        lf = LongLogFormatter(to_file=sio)
 
508
        show_log(b, lf)
 
509
        self.assertEqualDiff(sio.getvalue(), """\
597
510
------------------------------------------------------------
598
511
revno: 3
599
512
committer: Joe Foo <joe@foo.com>
600
513
branch nick: test
601
 
timestamp: Tue 2005-11-22 00:00:02 +0000
 
514
timestamp: Mon 2005-11-21 09:32:56 -0600
602
515
message:
603
516
  single line with trailing newline
604
517
------------------------------------------------------------
605
518
revno: 2
 
519
author: Joe Bar <joe@bar.com>
606
520
committer: Joe Foo <joe@foo.com>
607
521
branch nick: test
608
 
timestamp: Tue 2005-11-22 00:00:01 +0000
 
522
timestamp: Mon 2005-11-21 09:27:22 -0600
609
523
message:
610
524
  multiline
611
525
  log
614
528
revno: 1
615
529
committer: Joe Foo <joe@foo.com>
616
530
branch nick: test
617
 
timestamp: Tue 2005-11-22 00:00:00 +0000
 
531
timestamp: Mon 2005-11-21 09:24:15 -0600
618
532
message:
619
533
  simple log message
620
 
""",
621
 
        b, log.LongLogFormatter)
 
534
""")
622
535
 
623
536
    def test_author_in_log(self):
624
537
        """Log includes the author name if it's set in
625
538
        the revision properties
626
539
        """
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("""\
631
 
------------------------------------------------------------
632
 
revno: 1
633
 
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
634
 
committer: Lorem Ipsum <test@example.com>
635
 
branch nick: test_author_log
636
 
timestamp: Tue 2005-11-22 00:00:00 +0000
637
 
message:
638
 
  add a
639
 
""",
640
 
        wt.branch, log.LongLogFormatter)
641
 
 
642
 
    def test_properties_in_log(self):
643
 
        """Log includes the custom properties returned by the registered
644
 
        handlers.
645
 
        """
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("""\
655
 
------------------------------------------------------------
656
 
revno: 1
657
 
test_prop: test_value
658
 
author: John Doe <jdoe@example.com>
659
 
committer: Lorem Ipsum <test@example.com>
660
 
branch nick: test_properties_in_log
661
 
timestamp: Tue 2005-11-22 00:00:00 +0000
662
 
message:
663
 
  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)
685
 
 
686
 
    def test_error_in_properties_handler(self):
687
 
        """Log includes the custom properties returned by the registered
688
 
        handlers.
689
 
        """
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
 
 
702
 
    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
540
        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("""\
 
541
        b = wt.branch
 
542
        self.build_tree(['a'])
 
543
        wt.add('a')
 
544
        b.nick = 'test_author_log'
 
545
        wt.commit(message='add a',
 
546
                  timestamp=1132711707,
 
547
                  timezone=36000,
 
548
                  committer='Lorem Ipsum <test@example.com>',
 
549
                  author='John Doe <jdoe@example.com>')
 
550
        sio = StringIO()
 
551
        formatter = LongLogFormatter(to_file=sio)
 
552
        show_log(b, formatter)
 
553
        self.assertEqualDiff(sio.getvalue(), '''\
862
554
------------------------------------------------------------
863
555
revno: 1
864
556
author: John Doe <jdoe@example.com>
865
557
committer: Lorem Ipsum <test@example.com>
866
558
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):
 
559
timestamp: Wed 2005-11-23 12:08:27 +1000
 
560
message:
 
561
  add a
 
562
''')
 
563
 
 
564
 
 
565
 
 
566
class TestLineLogFormatter(TestCaseWithTransport):
901
567
 
902
568
    def test_line_log(self):
903
569
        """Line log should show revno
904
 
 
 
570
        
905
571
        bug #5162
906
572
        """
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)
 
573
        wt = self.make_branch_and_tree('.')
 
574
        b = wt.branch
 
575
        self.build_tree(['a'])
 
576
        wt.add('a')
 
577
        b.nick = 'test-line-log'
 
578
        wt.commit(message='add a', 
 
579
                  timestamp=1132711707, 
 
580
                  timezone=36000,
 
581
                  committer='Line-Log-Formatter Tester <test@line.log>')
 
582
        logfile = file('out.tmp', 'w+')
 
583
        formatter = LineLogFormatter(to_file=logfile)
 
584
        show_log(b, formatter)
 
585
        logfile.flush()
 
586
        logfile.seek(0)
 
587
        log_contents = logfile.read()
 
588
        self.assertEqualDiff(log_contents,
 
589
            '1: Line-Log-Formatte... 2005-11-23 add a\n')
914
590
 
915
591
    def test_trailing_newlines(self):
916
592
        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)
 
593
        b = make_commits_with_trailing_newlines(wt)
 
594
        sio = self.make_utf8_encoded_stringio()
 
595
        lf = LineLogFormatter(to_file=sio)
 
596
        show_log(b, lf)
 
597
        self.assertEqualDiff(sio.getvalue(), """\
 
598
3: Joe Foo 2005-11-21 single line with trailing newline
 
599
2: Joe Bar 2005-11-21 multiline
 
600
1: Joe Foo 2005-11-21 simple log message
 
601
""")
924
602
 
925
603
    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):
 
604
        wt = self.make_branch_and_memory_tree('.')
 
605
        wt.lock_write()
 
606
        try:
 
607
            wt.add('')
 
608
            wt.commit('rev-1', rev_id='rev-1',
 
609
                      timestamp=1132586655, timezone=36000,
 
610
                      committer='Joe Foo <joe@foo.com>')
 
611
            wt.commit('rev-merged', rev_id='rev-2a',
 
612
                      timestamp=1132586700, timezone=36000,
 
613
                      committer='Joe Foo <joe@foo.com>')
 
614
            wt.set_parent_ids(['rev-1', 'rev-2a'])
 
615
            wt.branch.set_last_revision_info(1, 'rev-1')
 
616
            wt.commit('rev-2', rev_id='rev-2b',
 
617
                      timestamp=1132586800, timezone=36000,
 
618
                      committer='Joe Foo <joe@foo.com>')
 
619
            logfile = self.make_utf8_encoded_stringio()
 
620
            formatter = LineLogFormatter(to_file=logfile)
 
621
            revspec = RevisionSpec.from_string('1.1.1')
 
622
            wtb = wt.branch
 
623
            rev = revspec.in_history(wtb)
 
624
            show_log(wtb, formatter, start_revision=rev, end_revision=rev)
 
625
            self.assertEqualDiff(logfile.getvalue(), """\
 
626
1.1.1: Joe Foo 2005-11-22 rev-merged
 
627
""")
 
628
        finally:
 
629
            wt.unlock()
 
630
 
 
631
 
 
632
 
 
633
class TestGetViewRevisions(TestCaseWithTransport):
 
634
 
 
635
    def make_tree_with_commits(self):
 
636
        """Create a tree with well-known revision ids"""
 
637
        wt = self.make_branch_and_tree('tree1')
 
638
        wt.commit('commit one', rev_id='1')
 
639
        wt.commit('commit two', rev_id='2')
 
640
        wt.commit('commit three', rev_id='3')
 
641
        mainline_revs = [None, '1', '2', '3']
 
642
        rev_nos = {'1': 1, '2': 2, '3': 3}
 
643
        return mainline_revs, rev_nos, wt
 
644
 
 
645
    def make_tree_with_merges(self):
 
646
        """Create a tree with well-known revision ids and a merge"""
 
647
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
 
648
        tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
 
649
        tree2.commit('four-a', rev_id='4a')
 
650
        wt.merge_from_branch(tree2.branch)
 
651
        wt.commit('four-b', rev_id='4b')
 
652
        mainline_revs.append('4b')
 
653
        rev_nos['4b'] = 4
 
654
        # 4a: 3.1.1
 
655
        return mainline_revs, rev_nos, wt
 
656
 
 
657
    def make_tree_with_many_merges(self):
 
658
        """Create a tree with well-known revision ids"""
 
659
        wt = self.make_branch_and_tree('tree1')
 
660
        wt.commit('commit one', rev_id='1')
 
661
        wt.commit('commit two', rev_id='2')
 
662
        tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
 
663
        tree3.commit('commit three a', rev_id='3a')
 
664
        tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
 
665
        tree2.merge_from_branch(tree3.branch)
 
666
        tree2.commit('commit three b', rev_id='3b')
 
667
        wt.merge_from_branch(tree2.branch)
 
668
        wt.commit('commit three c', rev_id='3c')
 
669
        tree2.commit('four-a', rev_id='4a')
 
670
        wt.merge_from_branch(tree2.branch)
 
671
        wt.commit('four-b', rev_id='4b')
 
672
        mainline_revs = [None, '1', '2', '3c', '4b']
 
673
        rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
 
674
        full_rev_nos_for_reference = {
 
675
            '1': '1',
 
676
            '2': '2',
 
677
            '3a': '2.1.1', #first commit tree 3
 
678
            '3b': '2.2.1', # first commit tree 2
 
679
            '3c': '3', #merges 3b to main
 
680
            '4a': '2.2.2', # second commit tree 2
 
681
            '4b': '4', # merges 4a to main
 
682
            }
 
683
        return mainline_revs, rev_nos, wt
 
684
 
 
685
    def test_get_view_revisions_forward(self):
 
686
        """Test the get_view_revisions method"""
 
687
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
 
688
        wt.lock_read()
 
689
        self.addCleanup(wt.unlock)
 
690
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
691
                                            'forward'))
 
692
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
 
693
            revisions)
 
694
        revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
695
                                             'forward', include_merges=False))
 
696
        self.assertEqual(revisions, revisions2)
 
697
 
 
698
    def test_get_view_revisions_reverse(self):
 
699
        """Test the get_view_revisions with reverse"""
 
700
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
 
701
        wt.lock_read()
 
702
        self.addCleanup(wt.unlock)
 
703
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
704
                                            'reverse'))
 
705
        self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
 
706
            revisions)
 
707
        revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
708
                                             'reverse', include_merges=False))
 
709
        self.assertEqual(revisions, revisions2)
 
710
 
 
711
    def test_get_view_revisions_merge(self):
 
712
        """Test get_view_revisions when there are merges"""
 
713
        mainline_revs, rev_nos, wt = self.make_tree_with_merges()
 
714
        wt.lock_read()
 
715
        self.addCleanup(wt.unlock)
 
716
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
717
                                            'forward'))
 
718
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
 
719
            ('4b', '4', 0), ('4a', '3.1.1', 1)],
 
720
            revisions)
 
721
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
722
                                             'forward', include_merges=False))
 
723
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
 
724
            ('4b', '4', 0)],
 
725
            revisions)
 
726
 
 
727
    def test_get_view_revisions_merge_reverse(self):
 
728
        """Test get_view_revisions in reverse when there are merges"""
 
729
        mainline_revs, rev_nos, wt = self.make_tree_with_merges()
 
730
        wt.lock_read()
 
731
        self.addCleanup(wt.unlock)
 
732
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
733
                                            'reverse'))
 
734
        self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
 
735
            ('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
 
736
            revisions)
 
737
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
738
                                             'reverse', include_merges=False))
 
739
        self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
 
740
            ('1', '1', 0)],
 
741
            revisions)
 
742
 
 
743
    def test_get_view_revisions_merge2(self):
 
744
        """Test get_view_revisions when there are merges"""
 
745
        mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
 
746
        wt.lock_read()
 
747
        self.addCleanup(wt.unlock)
 
748
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
749
                                            'forward'))
 
750
        expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
 
751
            ('3a', '2.1.1', 1), ('3b', '2.2.1', 1), ('4b', '4', 0),
 
752
            ('4a', '2.2.2', 1)]
 
753
        self.assertEqual(expected, revisions)
 
754
        revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
 
755
                                             'forward', include_merges=False))
 
756
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
 
757
            ('4b', '4', 0)],
 
758
            revisions)
 
759
 
 
760
 
 
761
class TestGetRevisionsTouchingFileID(TestCaseWithTransport):
 
762
 
 
763
    def create_tree_with_single_merge(self):
 
764
        """Create a branch with a moderate layout.
 
765
 
 
766
        The revision graph looks like:
 
767
 
 
768
           A
 
769
           |\
 
770
           B C
 
771
           |/
 
772
           D
 
773
 
 
774
        In this graph, A introduced files f1 and f2 and f3.
 
775
        B modifies f1 and f3, and C modifies f2 and f3.
 
776
        D merges the changes from B and C and resolves the conflict for f3.
 
777
        """
 
778
        # TODO: jam 20070218 This seems like it could really be done
 
779
        #       with make_branch_and_memory_tree() if we could just
 
780
        #       create the content of those files.
 
781
        # TODO: jam 20070218 Another alternative is that we would really
 
782
        #       like to only create this tree 1 time for all tests that
 
783
        #       use it. Since 'log' only uses the tree in a readonly
 
784
        #       fashion, it seems a shame to regenerate an identical
 
785
        #       tree for each test.
 
786
        tree = self.make_branch_and_tree('tree')
 
787
        tree.lock_write()
 
788
        self.addCleanup(tree.unlock)
 
789
 
 
790
        self.build_tree_contents([('tree/f1', 'A\n'),
 
791
                                  ('tree/f2', 'A\n'),
 
792
                                  ('tree/f3', 'A\n'),
 
793
                                 ])
 
794
        tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
 
795
        tree.commit('A', rev_id='A')
 
796
 
 
797
        self.build_tree_contents([('tree/f2', 'A\nC\n'),
 
798
                                  ('tree/f3', 'A\nC\n'),
 
799
                                 ])
 
800
        tree.commit('C', rev_id='C')
 
801
        # Revert back to A to build the other history.
 
802
        tree.set_last_revision('A')
 
803
        tree.branch.set_last_revision_info(1, 'A')
 
804
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
 
805
                                  ('tree/f2', 'A\n'),
 
806
                                  ('tree/f3', 'A\nB\n'),
 
807
                                 ])
 
808
        tree.commit('B', rev_id='B')
 
809
        tree.set_parent_ids(['B', 'C'])
 
810
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
 
811
                                  ('tree/f2', 'A\nC\n'),
 
812
                                  ('tree/f3', 'A\nB\nC\n'),
 
813
                                 ])
 
814
        tree.commit('D', rev_id='D')
 
815
 
 
816
        # Switch to a read lock for this tree.
 
817
        # We still have addCleanup(unlock)
 
818
        tree.unlock()
 
819
        tree.lock_read()
 
820
        return tree
 
821
 
 
822
    def test_tree_with_single_merge(self):
 
823
        """Make sure the tree layout is correct."""
 
824
        tree = self.create_tree_with_single_merge()
 
825
        rev_A_tree = tree.branch.repository.revision_tree('A')
 
826
        rev_B_tree = tree.branch.repository.revision_tree('B')
 
827
 
 
828
        f1_changed = (u'f1', 'f1-id', 'file', True, False)
 
829
        f2_changed = (u'f2', 'f2-id', 'file', True, False)
 
830
        f3_changed = (u'f3', 'f3-id', 'file', True, False)
 
831
 
 
832
        delta = rev_B_tree.changes_from(rev_A_tree)
 
833
        self.assertEqual([f1_changed, f3_changed], delta.modified)
 
834
        self.assertEqual([], delta.renamed)
 
835
        self.assertEqual([], delta.added)
 
836
        self.assertEqual([], delta.removed)
 
837
 
 
838
        rev_C_tree = tree.branch.repository.revision_tree('C')
 
839
        delta = rev_C_tree.changes_from(rev_A_tree)
 
840
        self.assertEqual([f2_changed, f3_changed], delta.modified)
 
841
        self.assertEqual([], delta.renamed)
 
842
        self.assertEqual([], delta.added)
 
843
        self.assertEqual([], delta.removed)
 
844
 
 
845
        rev_D_tree = tree.branch.repository.revision_tree('D')
 
846
        delta = rev_D_tree.changes_from(rev_B_tree)
 
847
        self.assertEqual([f2_changed, f3_changed], delta.modified)
 
848
        self.assertEqual([], delta.renamed)
 
849
        self.assertEqual([], delta.added)
 
850
        self.assertEqual([], delta.removed)
 
851
 
 
852
        delta = rev_D_tree.changes_from(rev_C_tree)
 
853
        self.assertEqual([f1_changed, f3_changed], delta.modified)
 
854
        self.assertEqual([], delta.renamed)
 
855
        self.assertEqual([], delta.added)
 
856
        self.assertEqual([], delta.removed)
 
857
 
 
858
    def assertAllRevisionsForFileID(self, tree, file_id, revisions):
 
859
        """Make sure _filter_revisions_touching_file_id returns the right values.
 
860
 
 
861
        Get the return value from _filter_revisions_touching_file_id and make
 
862
        sure they are correct.
 
863
        """
 
864
        # The api for _get_revisions_touching_file_id is a little crazy,
 
865
        # So we do the setup here.
 
866
        mainline = tree.branch.revision_history()
 
867
        mainline.insert(0, None)
 
868
        revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
 
869
        view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
 
870
                                                'reverse', True)
 
871
        actual_revs = log._filter_revisions_touching_file_id(
 
872
                            tree.branch, 
 
873
                            file_id,
 
874
                            mainline,
 
875
                            list(view_revs_iter))
 
876
        self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
 
877
 
 
878
    def test_file_id_f1(self):
 
879
        tree = self.create_tree_with_single_merge()
 
880
        # f1 should be marked as modified by revisions A and B
 
881
        self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
 
882
 
 
883
    def test_file_id_f2(self):
 
884
        tree = self.create_tree_with_single_merge()
 
885
        # f2 should be marked as modified by revisions A, C, and D
 
886
        # because D merged the changes from C.
 
887
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
 
888
 
 
889
    def test_file_id_f3(self):
 
890
        tree = self.create_tree_with_single_merge()
 
891
        # f3 should be marked as modified by revisions A, B, C, and D
 
892
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
 
893
 
 
894
    def test_file_id_with_ghosts(self):
 
895
        # This is testing bug #209948, where having a ghost would cause
 
896
        # _filter_revisions_touching_file_id() to fail.
 
897
        tree = self.create_tree_with_single_merge()
 
898
        # We need to add a revision, so switch back to a write-locked tree
 
899
        tree.unlock()
 
900
        tree.lock_write()
 
901
        first_parent = tree.last_revision()
 
902
        tree.set_parent_ids([first_parent, 'ghost-revision-id'])
 
903
        self.build_tree_contents([('tree/f1', 'A\nB\nXX\n')])
 
904
        tree.commit('commit with a ghost', rev_id='XX')
 
905
        self.assertAllRevisionsForFileID(tree, 'f1-id', ['XX', 'B', 'A'])
 
906
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
 
907
 
 
908
 
 
909
class TestShowChangedRevisions(TestCaseWithTransport):
1021
910
 
1022
911
    def test_show_changed_revisions_verbose(self):
1023
912
        tree = self.make_branch_and_tree('tree_a')
1030
919
        self.assertNotContainsRe(s.getvalue(), 'foo')
1031
920
 
1032
921
 
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)
 
922
class TestLogFormatter(TestCase):
1039
923
 
1040
924
    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')
 
925
        rev = Revision('a-id')
 
926
        rev.committer = 'John Doe <jdoe@example.com>'
 
927
        lf = LogFormatter(None)
 
928
        self.assertEqual('John Doe', lf.short_committer(rev))
 
929
        rev.committer = 'John Smith <jsmith@example.com>'
 
930
        self.assertEqual('John Smith', lf.short_committer(rev))
 
931
        rev.committer = 'John Smith'
 
932
        self.assertEqual('John Smith', lf.short_committer(rev))
 
933
        rev.committer = 'jsmith@example.com'
 
934
        self.assertEqual('jsmith@example.com', lf.short_committer(rev))
 
935
        rev.committer = '<jsmith@example.com>'
 
936
        self.assertEqual('jsmith@example.com', lf.short_committer(rev))
 
937
        rev.committer = 'John Smith jsmith@example.com'
 
938
        self.assertEqual('John Smith', lf.short_committer(rev))
1051
939
 
1052
940
    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
 
 
 
941
        rev = Revision('a-id')
 
942
        rev.committer = 'John Doe <jdoe@example.com>'
 
943
        lf = LogFormatter(None)
 
944
        self.assertEqual('John Doe', lf.short_author(rev))
 
945
        rev.properties['author'] = 'John Smith <jsmith@example.com>'
 
946
        self.assertEqual('John Smith', lf.short_author(rev))
 
947
        rev.properties['author'] = 'John Smith'
 
948
        self.assertEqual('John Smith', lf.short_author(rev))
 
949
        rev.properties['author'] = 'jsmith@example.com'
 
950
        self.assertEqual('jsmith@example.com', lf.short_author(rev))
 
951
        rev.properties['author'] = '<jsmith@example.com>'
 
952
        self.assertEqual('jsmith@example.com', lf.short_author(rev))
 
953
        rev.properties['author'] = 'John Smith jsmith@example.com'
 
954
        self.assertEqual('John Smith', lf.short_author(rev))