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.branch.fetch(tree2.branch, '4a')
325
wt.add_pending_merge('4a')
326
wt.commit('four-b', rev_id='4b')
327
mainline_revs.append('4b')
329
return mainline_revs, rev_nos, wt
331
def test_get_view_revisions_forward(self):
332
"""Test the get_view_revisions method"""
333
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
334
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
336
self.assertEqual(revisions, [('1', 1, 0), ('2', 2, 0), ('3', 3, 0)])
337
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
338
'forward', include_merges=False))
339
self.assertEqual(revisions, revisions2)
341
def test_get_view_revisions_reverse(self):
342
"""Test the get_view_revisions with reverse"""
343
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
344
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
346
self.assertEqual(revisions, [('3', 3, 0), ('2', 2, 0), ('1', 1, 0), ])
347
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
348
'reverse', include_merges=False))
349
self.assertEqual(revisions, revisions2)
351
def test_get_view_revisions_merge(self):
352
"""Test get_view_revisions when there are merges"""
353
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
354
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
356
self.assertEqual(revisions, [('1', 1, 0), ('2', 2, 0), ('3', 3, 0),
357
('4a', None, 1), ('4b', 4, 0)])
358
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
359
'forward', include_merges=False))
360
self.assertEqual(revisions, [('1', 1, 0), ('2', 2, 0), ('3', 3, 0),