~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2008-04-07 07:52:50 UTC
  • mfrom: (3340.1.1 208418-1.4)
  • Revision ID: pqm@pqm.ubuntu.com-20080407075250-phs53xnslo8boaeo
Return the correct knit serialisation method in _StreamAccess.
        (Andrew Bennetts, Martin Pool, Robert Collins)

Show diffs side-by-side

added added

removed removed

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