~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: 2014-10-06 16:32:42 UTC
  • mfrom: (6597.2.4 split-diff-tests)
  • Revision ID: pqm@pqm.ubuntu.com-20141006163242-c2cll01cwc24grkk
(vila) Split some tests to be able to get finer grained failures (Vincent
 Ladeuil)

Show diffs side-by-side

added added

removed removed

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