~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

  • Committer: Patch Queue Manager
  • Date: 2016-04-21 04:10:52 UTC
  • mfrom: (6616.1.1 fix-en-user-guide)
  • Revision ID: pqm@pqm.ubuntu.com-20160421041052-clcye7ns1qcl2n7w
(richard-wilbur) Ensure build of English use guide always uses English text
 even when user's locale specifies a different language. (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

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