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 pseudo_merge(self, source, target):
320
revision_id = source.last_revision()
321
target.branch.fetch(source.branch, revision_id)
322
target.add_pending_merge(revision_id)
324
def make_tree_with_merges(self):
325
"""Create a tree with well-known revision ids and a merge"""
326
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
327
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
328
tree2.commit('four-a', rev_id='4a')
329
self.pseudo_merge(tree2, wt)
330
wt.commit('four-b', rev_id='4b')
331
mainline_revs.append('4b')
333
return mainline_revs, rev_nos, wt
335
def make_tree_with_many_merges(self):
336
"""Create a tree with well-known revision ids"""
337
wt = self.make_branch_and_tree('tree1')
338
wt.commit('commit one', rev_id='1')
339
wt.commit('commit two', rev_id='2')
340
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
341
tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
342
tree3.commit('commit three a', rev_id='3a')
343
self.pseudo_merge(tree3, tree2)
344
tree2.commit('commit three b', rev_id='3b')
345
self.pseudo_merge(tree2, wt)
346
wt.commit('commit three c', rev_id='3c')
347
tree2.commit('four-a', rev_id='4a')
348
self.pseudo_merge(tree2, wt)
349
wt.commit('four-b', rev_id='4b')
350
mainline_revs = [None, '1', '2', '3c', '4b']
351
rev_nos = {'1': 1, '2': 2, '3c': 3, '4b': 4}
352
return mainline_revs, rev_nos, wt
354
def test_get_view_revisions_forward(self):
355
"""Test the get_view_revisions method"""
356
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
357
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
359
self.assertEqual(revisions, [('1', 1, 0), ('2', 2, 0), ('3', 3, 0)])
360
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
361
'forward', include_merges=False))
362
self.assertEqual(revisions, revisions2)
364
def test_get_view_revisions_reverse(self):
365
"""Test the get_view_revisions with reverse"""
366
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
367
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
369
self.assertEqual(revisions, [('3', 3, 0), ('2', 2, 0), ('1', 1, 0), ])
370
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
371
'reverse', include_merges=False))
372
self.assertEqual(revisions, revisions2)
374
def test_get_view_revisions_merge(self):
375
"""Test get_view_revisions when there are merges"""
376
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
377
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
379
self.assertEqual(revisions, [('1', 1, 0), ('2', 2, 0), ('3', 3, 0),
380
('4b', 4, 0), ('4a', None, 1)])
381
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
382
'forward', include_merges=False))
383
self.assertEqual(revisions, [('1', 1, 0), ('2', 2, 0), ('3', 3, 0),
386
def test_get_view_revisions_merge_reverse(self):
387
"""Test get_view_revisions in reverse when there are merges"""
388
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
389
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
391
self.assertEqual(revisions, [('4b', 4, 0), ('4a', None, 1),
392
('3', 3, 0), ('2', 2, 0), ('1', 1, 0)])
393
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
394
'reverse', include_merges=False))
395
self.assertEqual(revisions, [('4b', 4, 0), ('3', 3, 0), ('2', 2, 0),
398
def test_get_view_revisions_merge2(self):
399
"""Test get_view_revisions when there are merges"""
400
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
401
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
403
expected = [('1', 1, 0), ('2', 2, 0), ('3c', 3, 0), ('3a', None, 1),
404
('3b', None, 1), ('4b', 4, 0), ('4a', None, 1)]
405
self.assertEqual(revisions, expected)
406
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
407
'forward', include_merges=False))
408
self.assertEqual(revisions, [('1', 1, 0), ('2', 2, 0), ('3c', 3, 0),