~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

  • Committer: Aaron Bentley
  • Date: 2007-06-11 14:59:52 UTC
  • mto: (2520.5.2 bzr.mpbundle)
  • mto: This revision was merged to the branch mainline in revision 2631.
  • Revision ID: abentley@panoramicfeedback.com-20070611145952-cwt4480gphdhen6l
Get installation started

Show diffs side-by-side

added added

removed removed

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