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 LogFormatter, show_log, LongLogFormatter, ShortLogFormatter
24
from bzrlib.branch import Branch
25
from bzrlib.errors import InvalidRevisionNumber
27
class _LogEntry(object):
28
# should probably move into bzrlib.log?
32
class LogCatcher(LogFormatter):
33
"""Pull log messages into list rather than displaying them.
35
For ease of testing we save log messages here rather than actually
36
formatting them, so that we can precisely check the result without
37
being too dependent on the exact formatting.
39
We should also test the LogFormatter.
42
super(LogCatcher, self).__init__(to_file=None)
46
def show(self, revno, rev, delta):
54
class SimpleLogTest(TestCaseWithTransport):
56
def checkDelta(self, delta, **kw):
57
"""Check the filenames touched by a delta are as expected."""
58
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
59
expected = kw.get(n, [])
61
# tests are written with unix paths; fix them up for windows
63
# expected = [x.replace('/', os.sep) for x in expected]
65
# strip out only the path components
66
got = [x[0] for x in getattr(delta, n)]
67
self.assertEquals(expected, got)
69
def test_cur_revno(self):
70
wt = self.make_branch_and_tree('.')
74
wt.commit('empty commit')
75
show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
76
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
77
start_revision=2, end_revision=1)
78
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
79
start_revision=1, end_revision=2)
80
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
81
start_revision=0, end_revision=2)
82
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
83
start_revision=1, end_revision=0)
84
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
85
start_revision=-1, end_revision=1)
86
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
87
start_revision=1, end_revision=-1)
89
def test_cur_revno(self):
90
wt = self.make_branch_and_tree('.')
94
wt.commit('empty commit')
95
show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
96
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
97
start_revision=2, end_revision=1)
98
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
99
start_revision=1, end_revision=2)
100
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
101
start_revision=0, end_revision=2)
102
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
103
start_revision=1, end_revision=0)
104
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
105
start_revision=-1, end_revision=1)
106
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
107
start_revision=1, end_revision=-1)
109
def test_simple_log(self):
110
eq = self.assertEquals
112
wt = self.make_branch_and_tree('.')
120
wt.commit('empty commit')
122
show_log(b, lf, verbose=True)
124
eq(lf.logs[0].revno, 1)
125
eq(lf.logs[0].rev.message, 'empty commit')
127
self.log('log delta: %r' % d)
130
self.build_tree(['hello'])
132
wt.commit('add one file')
135
# log using regular thing
136
show_log(b, LongLogFormatter(lf))
138
for l in lf.readlines():
141
# get log as data structure
143
show_log(b, lf, verbose=True)
145
self.log('log entries:')
146
for logentry in lf.logs:
147
self.log('%4d %s' % (logentry.revno, logentry.rev.message))
149
# first one is most recent
150
logentry = lf.logs[0]
151
eq(logentry.revno, 2)
152
eq(logentry.rev.message, 'add one file')
154
self.log('log 2 delta: %r' % d)
155
# self.checkDelta(d, added=['hello'])
157
# commit a log message with control characters
158
msg = "All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
159
self.log("original commit message: %r", msg)
162
show_log(b, lf, verbose=True)
163
committed_msg = lf.logs[0].rev.message
164
self.log("escaped commit message: %r", committed_msg)
165
self.assert_(msg != committed_msg)
166
self.assert_(len(committed_msg) > len(msg))
168
# Check that log message with only XML-valid characters isn't
169
# escaped. As ElementTree apparently does some kind of
170
# newline conversion, neither LF (\x0A) nor CR (\x0D) are
171
# included in the test commit message, even though they are
172
# valid XML 1.0 characters.
173
msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
174
self.log("original commit message: %r", msg)
177
show_log(b, lf, verbose=True)
178
committed_msg = lf.logs[0].rev.message
179
self.log("escaped commit message: %r", committed_msg)
180
self.assert_(msg == committed_msg)
182
def test_trailing_newlines(self):
183
wt = self.make_branch_and_tree('.')
186
open('a', 'wb').write('hello moto\n')
188
wt.commit('simple log message', rev_id='a1'
189
, timestamp=1132586655.459960938, timezone=-6*3600
190
, committer='Joe Foo <joe@foo.com>')
191
open('b', 'wb').write('goodbye\n')
193
wt.commit('multiline\nlog\nmessage\n', rev_id='a2'
194
, timestamp=1132586842.411175966, timezone=-6*3600
195
, committer='Joe Foo <joe@foo.com>')
197
open('c', 'wb').write('just another manic monday\n')
199
wt.commit('single line with trailing newline\n', rev_id='a3'
200
, timestamp=1132587176.835228920, timezone=-6*3600
201
, committer = 'Joe Foo <joe@foo.com>')
204
lf = ShortLogFormatter(to_file=sio)
206
self.assertEquals(sio.getvalue(), """\
207
3 Joe Foo\t2005-11-21
208
single line with trailing newline
210
2 Joe Foo\t2005-11-21
215
1 Joe Foo\t2005-11-21
221
lf = LongLogFormatter(to_file=sio)
223
self.assertEquals(sio.getvalue(), """\
224
------------------------------------------------------------
226
committer: Joe Foo <joe@foo.com>
228
timestamp: Mon 2005-11-21 09:32:56 -0600
230
single line with trailing newline
231
------------------------------------------------------------
233
committer: Joe Foo <joe@foo.com>
235
timestamp: Mon 2005-11-21 09:27:22 -0600
240
------------------------------------------------------------
242
committer: Joe Foo <joe@foo.com>
244
timestamp: Mon 2005-11-21 09:24:15 -0600
249
def test_verbose_log(self):
250
"""Verbose log includes changed files
254
wt = self.make_branch_and_tree('.')
256
self.build_tree(['a'])
258
# XXX: why does a longer nick show up?
259
b.nick = 'test_verbose_log'
260
wt.commit(message='add a',
261
timestamp=1132711707,
263
committer='Lorem Ipsum <test@example.com>')
264
logfile = file('out.tmp', 'w+')
265
formatter = LongLogFormatter(to_file=logfile)
266
show_log(b, formatter, verbose=True)
269
log_contents = logfile.read()
270
self.assertEqualDiff(log_contents, '''\
271
------------------------------------------------------------
273
committer: Lorem Ipsum <test@example.com>
274
branch nick: test_verbose_log
275
timestamp: Wed 2005-11-23 12:08:27 +1000