1
# Copyright (C) 2005 by Canonical Ltd
2
# -*- coding: utf-8 -*-
5
# This program is free software; you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 2 of the License, or
8
# (at your option) any later version.
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
15
# You should have received a copy of the GNU General Public License
16
# along with this program; if not, write to the Free Software
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
from cStringIO import StringIO
22
from bzrlib.tests import BzrTestBase, TestCaseWithTransport
23
from bzrlib.log import (show_log,
29
from bzrlib.branch import Branch
30
from bzrlib.errors import InvalidRevisionNumber
33
class _LogEntry(object):
34
# should probably move into bzrlib.log?
38
class LogCatcher(LogFormatter):
39
"""Pull log messages into list rather than displaying them.
41
For ease of testing we save log messages here rather than actually
42
formatting them, so that we can precisely check the result without
43
being too dependent on the exact formatting.
45
We should also test the LogFormatter.
48
super(LogCatcher, self).__init__(to_file=None)
51
def show(self, revno, rev, delta):
59
class SimpleLogTest(TestCaseWithTransport):
61
def checkDelta(self, delta, **kw):
62
"""Check the filenames touched by a delta are as expected."""
63
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
64
expected = kw.get(n, [])
66
# tests are written with unix paths; fix them up for windows
68
# expected = [x.replace('/', os.sep) for x in expected]
70
# strip out only the path components
71
got = [x[0] for x in getattr(delta, n)]
72
self.assertEquals(expected, got)
74
def test_cur_revno(self):
75
wt = self.make_branch_and_tree('.')
79
wt.commit('empty commit')
80
show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
81
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
82
start_revision=2, end_revision=1)
83
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
84
start_revision=1, end_revision=2)
85
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
86
start_revision=0, end_revision=2)
87
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
88
start_revision=1, end_revision=0)
89
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
90
start_revision=-1, end_revision=1)
91
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
92
start_revision=1, end_revision=-1)
94
def test_cur_revno(self):
95
wt = self.make_branch_and_tree('.')
99
wt.commit('empty commit')
100
show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
101
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
102
start_revision=2, end_revision=1)
103
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
104
start_revision=1, end_revision=2)
105
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
106
start_revision=0, end_revision=2)
107
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
108
start_revision=1, end_revision=0)
109
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
110
start_revision=-1, end_revision=1)
111
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
112
start_revision=1, end_revision=-1)
114
def test_simple_log(self):
115
eq = self.assertEquals
117
wt = self.make_branch_and_tree('.')
125
wt.commit('empty commit')
127
show_log(b, lf, verbose=True)
129
eq(lf.logs[0].revno, 1)
130
eq(lf.logs[0].rev.message, 'empty commit')
132
self.log('log delta: %r' % d)
135
self.build_tree(['hello'])
137
wt.commit('add one file')
140
# log using regular thing
141
show_log(b, LongLogFormatter(lf))
143
for l in lf.readlines():
146
# get log as data structure
148
show_log(b, lf, verbose=True)
150
self.log('log entries:')
151
for logentry in lf.logs:
152
self.log('%4d %s' % (logentry.revno, logentry.rev.message))
154
# first one is most recent
155
logentry = lf.logs[0]
156
eq(logentry.revno, 2)
157
eq(logentry.rev.message, 'add one file')
159
self.log('log 2 delta: %r' % d)
160
# self.checkDelta(d, added=['hello'])
162
# commit a log message with control characters
163
msg = "All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
164
self.log("original commit message: %r", msg)
167
show_log(b, lf, verbose=True)
168
committed_msg = lf.logs[0].rev.message
169
self.log("escaped commit message: %r", committed_msg)
170
self.assert_(msg != committed_msg)
171
self.assert_(len(committed_msg) > len(msg))
173
# Check that log message with only XML-valid characters isn't
174
# escaped. As ElementTree apparently does some kind of
175
# newline conversion, neither LF (\x0A) nor CR (\x0D) are
176
# included in the test commit message, even though they are
177
# valid XML 1.0 characters.
178
msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
179
self.log("original commit message: %r", msg)
182
show_log(b, lf, verbose=True)
183
committed_msg = lf.logs[0].rev.message
184
self.log("escaped commit message: %r", committed_msg)
185
self.assert_(msg == committed_msg)
187
def test_trailing_newlines(self):
188
wt = self.make_branch_and_tree('.')
191
open('a', 'wb').write('hello moto\n')
193
wt.commit('simple log message', rev_id='a1'
194
, timestamp=1132586655.459960938, timezone=-6*3600
195
, committer='Joe Foo <joe@foo.com>')
196
open('b', 'wb').write('goodbye\n')
198
wt.commit('multiline\nlog\nmessage\n', rev_id='a2'
199
, timestamp=1132586842.411175966, timezone=-6*3600
200
, committer='Joe Foo <joe@foo.com>')
202
open('c', 'wb').write('just another manic monday\n')
204
wt.commit('single line with trailing newline\n', rev_id='a3'
205
, timestamp=1132587176.835228920, timezone=-6*3600
206
, committer = 'Joe Foo <joe@foo.com>')
209
lf = ShortLogFormatter(to_file=sio)
211
self.assertEquals(sio.getvalue(), """\
212
3 Joe Foo\t2005-11-21
213
single line with trailing newline
215
2 Joe Foo\t2005-11-21
220
1 Joe Foo\t2005-11-21
226
lf = LongLogFormatter(to_file=sio)
228
self.assertEquals(sio.getvalue(), """\
229
------------------------------------------------------------
231
committer: Joe Foo <joe@foo.com>
233
timestamp: Mon 2005-11-21 09:32:56 -0600
235
single line with trailing newline
236
------------------------------------------------------------
238
committer: Joe Foo <joe@foo.com>
240
timestamp: Mon 2005-11-21 09:27:22 -0600
245
------------------------------------------------------------
247
committer: Joe Foo <joe@foo.com>
249
timestamp: Mon 2005-11-21 09:24:15 -0600
254
def test_verbose_log(self):
255
"""Verbose log includes changed files
259
wt = self.make_branch_and_tree('.')
261
self.build_tree(['a'])
263
# XXX: why does a longer nick show up?
264
b.nick = 'test_verbose_log'
265
wt.commit(message='add a',
266
timestamp=1132711707,
268
committer='Lorem Ipsum <test@example.com>')
269
logfile = file('out.tmp', 'w+')
270
formatter = LongLogFormatter(to_file=logfile)
271
show_log(b, formatter, verbose=True)
274
log_contents = logfile.read()
275
self.assertEqualDiff(log_contents, '''\
276
------------------------------------------------------------
278
committer: Lorem Ipsum <test@example.com>
279
branch nick: test_verbose_log
280
timestamp: Wed 2005-11-23 12:08:27 +1000
287
def test_line_log(self):
288
"""Line log should show revno
292
wt = self.make_branch_and_tree('.')
294
self.build_tree(['a'])
296
b.nick = 'test-line-log'
297
wt.commit(message='add a',
298
timestamp=1132711707,
300
committer='Line-Log-Formatter Tester <test@line.log>')
301
logfile = file('out.tmp', 'w+')
302
formatter = LineLogFormatter(to_file=logfile)
303
show_log(b, formatter)
306
log_contents = logfile.read()
307
self.assertEqualDiff(log_contents, '1: Line-Log-Formatte... 2005-11-23 add a\n')
309
def make_tree_with_commits(self):
310
"""Create a tree with well-known revision ids"""
311
wt = self.make_branch_and_tree('tree1')
312
wt.commit('commit one', rev_id='1')
313
wt.commit('commit two', rev_id='2')
314
wt.commit('commit three', rev_id='3')
315
mainline_revs = [None, '1', '2', '3']
316
rev_nos = {'1': 1, '2': 2, '3': 3}
317
return mainline_revs, rev_nos, wt
319
def make_tree_with_merges(self):
320
"""Create a tree with well-known revision ids and a merge"""
321
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
322
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
323
tree2.commit('four-a', rev_id='4a')
324
wt.merge_from_branch(tree2.branch)
325
wt.commit('four-b', rev_id='4b')
326
mainline_revs.append('4b')
328
return mainline_revs, rev_nos, wt
330
def make_tree_with_many_merges(self):
331
"""Create a tree with well-known revision ids"""
332
wt = self.make_branch_and_tree('tree1')
333
wt.commit('commit one', rev_id='1')
334
wt.commit('commit two', rev_id='2')
335
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
336
tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
337
tree3.commit('commit three a', rev_id='3a')
338
tree2.merge_from_branch(tree3.branch)
339
tree2.commit('commit three b', rev_id='3b')
340
wt.merge_from_branch(tree2.branch)
341
wt.commit('commit three c', rev_id='3c')
342
tree2.commit('four-a', rev_id='4a')
343
wt.merge_from_branch(tree2.branch)
344
wt.commit('four-b', rev_id='4b')
345
mainline_revs = [None, '1', '2', '3c', '4b']
346
rev_nos = {'1': 1, '2': 2, '3c': 3, '4b': 4}
347
return mainline_revs, rev_nos, wt
349
def test_get_view_revisions_forward(self):
350
"""Test the get_view_revisions method"""
351
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
352
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
354
self.assertEqual(revisions, [('1', 1, 0), ('2', 2, 0), ('3', 3, 0)])
355
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
356
'forward', include_merges=False))
357
self.assertEqual(revisions, revisions2)
359
def test_get_view_revisions_reverse(self):
360
"""Test the get_view_revisions with reverse"""
361
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
362
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
364
self.assertEqual(revisions, [('3', 3, 0), ('2', 2, 0), ('1', 1, 0), ])
365
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
366
'reverse', include_merges=False))
367
self.assertEqual(revisions, revisions2)
369
def test_get_view_revisions_merge(self):
370
"""Test get_view_revisions when there are merges"""
371
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
372
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
374
self.assertEqual(revisions, [('1', 1, 0), ('2', 2, 0), ('3', 3, 0),
375
('4b', 4, 0), ('4a', None, 1)])
376
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
377
'forward', include_merges=False))
378
self.assertEqual(revisions, [('1', 1, 0), ('2', 2, 0), ('3', 3, 0),
381
def test_get_view_revisions_merge_reverse(self):
382
"""Test get_view_revisions in reverse when there are merges"""
383
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
384
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
386
self.assertEqual(revisions, [('4b', 4, 0), ('4a', None, 1),
387
('3', 3, 0), ('2', 2, 0), ('1', 1, 0)])
388
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
389
'reverse', include_merges=False))
390
self.assertEqual(revisions, [('4b', 4, 0), ('3', 3, 0), ('2', 2, 0),
393
def test_get_view_revisions_merge2(self):
394
"""Test get_view_revisions when there are merges"""
395
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
396
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
398
expected = [('1', 1, 0), ('2', 2, 0), ('3c', 3, 0), ('3a', None, 1),
399
('3b', None, 1), ('4b', 4, 0), ('4a', None, 1)]
400
self.assertEqual(revisions, expected)
401
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
402
'forward', include_merges=False))
403
self.assertEqual(revisions, [('1', 1, 0), ('2', 2, 0), ('3c', 3, 0),