1
# Copyright (C) 2005 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_simple_log(self):
95
eq = self.assertEquals
97
wt = self.make_branch_and_tree('.')
105
wt.commit('empty commit')
107
show_log(b, lf, verbose=True)
109
eq(lf.logs[0].revno, '1')
110
eq(lf.logs[0].rev.message, 'empty commit')
112
self.log('log delta: %r' % d)
115
self.build_tree(['hello'])
117
wt.commit('add one file')
120
# log using regular thing
121
show_log(b, LongLogFormatter(lf))
123
for l in lf.readlines():
126
# get log as data structure
128
show_log(b, lf, verbose=True)
130
self.log('log entries:')
131
for logentry in lf.logs:
132
self.log('%4s %s' % (logentry.revno, logentry.rev.message))
134
# first one is most recent
135
logentry = lf.logs[0]
136
eq(logentry.revno, '2')
137
eq(logentry.rev.message, 'add one file')
139
self.log('log 2 delta: %r' % d)
140
# self.checkDelta(d, added=['hello'])
142
# commit a log message with control characters
143
msg = "All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
144
self.log("original commit message: %r", msg)
147
show_log(b, lf, verbose=True)
148
committed_msg = lf.logs[0].rev.message
149
self.log("escaped commit message: %r", committed_msg)
150
self.assert_(msg != committed_msg)
151
self.assert_(len(committed_msg) > len(msg))
153
# Check that log message with only XML-valid characters isn't
154
# escaped. As ElementTree apparently does some kind of
155
# newline conversion, neither LF (\x0A) nor CR (\x0D) are
156
# included in the test commit message, even though they are
157
# valid XML 1.0 characters.
158
msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 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)
167
def test_trailing_newlines(self):
168
wt = self.make_branch_and_tree('.')
171
open('a', 'wb').write('hello moto\n')
173
wt.commit('simple log message', rev_id='a1'
174
, timestamp=1132586655.459960938, timezone=-6*3600
175
, committer='Joe Foo <joe@foo.com>')
176
open('b', 'wb').write('goodbye\n')
178
wt.commit('multiline\nlog\nmessage\n', rev_id='a2'
179
, timestamp=1132586842.411175966, timezone=-6*3600
180
, committer='Joe Foo <joe@foo.com>')
182
open('c', 'wb').write('just another manic monday\n')
184
wt.commit('single line with trailing newline\n', rev_id='a3'
185
, timestamp=1132587176.835228920, timezone=-6*3600
186
, committer = 'Joe Foo <joe@foo.com>')
189
lf = ShortLogFormatter(to_file=sio)
191
self.assertEquals(sio.getvalue(), """\
192
3 Joe Foo\t2005-11-21
193
single line with trailing newline
195
2 Joe Foo\t2005-11-21
200
1 Joe Foo\t2005-11-21
206
lf = LongLogFormatter(to_file=sio)
208
self.assertEquals(sio.getvalue(), """\
209
------------------------------------------------------------
211
committer: Joe Foo <joe@foo.com>
213
timestamp: Mon 2005-11-21 09:32:56 -0600
215
single line with trailing newline
216
------------------------------------------------------------
218
committer: Joe Foo <joe@foo.com>
220
timestamp: Mon 2005-11-21 09:27:22 -0600
225
------------------------------------------------------------
227
committer: Joe Foo <joe@foo.com>
229
timestamp: Mon 2005-11-21 09:24:15 -0600
234
def test_verbose_log(self):
235
"""Verbose log includes changed files
239
wt = self.make_branch_and_tree('.')
241
self.build_tree(['a'])
243
# XXX: why does a longer nick show up?
244
b.nick = 'test_verbose_log'
245
wt.commit(message='add a',
246
timestamp=1132711707,
248
committer='Lorem Ipsum <test@example.com>')
249
logfile = file('out.tmp', 'w+')
250
formatter = LongLogFormatter(to_file=logfile)
251
show_log(b, formatter, verbose=True)
254
log_contents = logfile.read()
255
self.assertEqualDiff(log_contents, '''\
256
------------------------------------------------------------
258
committer: Lorem Ipsum <test@example.com>
259
branch nick: test_verbose_log
260
timestamp: Wed 2005-11-23 12:08:27 +1000
267
def test_line_log(self):
268
"""Line log should show revno
272
wt = self.make_branch_and_tree('.')
274
self.build_tree(['a'])
276
b.nick = 'test-line-log'
277
wt.commit(message='add a',
278
timestamp=1132711707,
280
committer='Line-Log-Formatter Tester <test@line.log>')
281
logfile = file('out.tmp', 'w+')
282
formatter = LineLogFormatter(to_file=logfile)
283
show_log(b, formatter)
286
log_contents = logfile.read()
287
self.assertEqualDiff(log_contents, '1: Line-Log-Formatte... 2005-11-23 add a\n')
289
def make_tree_with_commits(self):
290
"""Create a tree with well-known revision ids"""
291
wt = self.make_branch_and_tree('tree1')
292
wt.commit('commit one', rev_id='1')
293
wt.commit('commit two', rev_id='2')
294
wt.commit('commit three', rev_id='3')
295
mainline_revs = [None, '1', '2', '3']
296
rev_nos = {'1': 1, '2': 2, '3': 3}
297
return mainline_revs, rev_nos, wt
299
def make_tree_with_merges(self):
300
"""Create a tree with well-known revision ids and a merge"""
301
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
302
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
303
tree2.commit('four-a', rev_id='4a')
304
wt.merge_from_branch(tree2.branch)
305
wt.commit('four-b', rev_id='4b')
306
mainline_revs.append('4b')
309
return mainline_revs, rev_nos, wt
311
def make_tree_with_many_merges(self):
312
"""Create a tree with well-known revision ids"""
313
wt = self.make_branch_and_tree('tree1')
314
wt.commit('commit one', rev_id='1')
315
wt.commit('commit two', rev_id='2')
316
tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
317
tree3.commit('commit three a', rev_id='3a')
318
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
319
tree2.merge_from_branch(tree3.branch)
320
tree2.commit('commit three b', rev_id='3b')
321
wt.merge_from_branch(tree2.branch)
322
wt.commit('commit three c', rev_id='3c')
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 = [None, '1', '2', '3c', '4b']
327
rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
328
full_rev_nos_for_reference = {
331
'3a': '2.2.1', #first commit tree 3
332
'3b': '2.1.1', # first commit tree 2
333
'3c': '3', #merges 3b to main
334
'4a': '2.1.2', # second commit tree 2
335
'4b': '4', # merges 4a to main
337
return mainline_revs, rev_nos, wt
339
def test_get_view_revisions_forward(self):
340
"""Test the get_view_revisions method"""
341
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
342
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
344
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
346
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
347
'forward', include_merges=False))
348
self.assertEqual(revisions, revisions2)
350
def test_get_view_revisions_reverse(self):
351
"""Test the get_view_revisions with reverse"""
352
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
353
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
355
self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
357
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
358
'reverse', include_merges=False))
359
self.assertEqual(revisions, revisions2)
361
def test_get_view_revisions_merge(self):
362
"""Test get_view_revisions when there are merges"""
363
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
364
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
366
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
367
('4b', '4', 0), ('4a', '3.1.1', 1)],
369
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
370
'forward', include_merges=False))
371
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
375
def test_get_view_revisions_merge_reverse(self):
376
"""Test get_view_revisions in reverse when there are merges"""
377
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
378
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
380
self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
381
('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
383
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
384
'reverse', include_merges=False))
385
self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
389
def test_get_view_revisions_merge2(self):
390
"""Test get_view_revisions when there are merges"""
391
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
392
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
394
expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
395
('3a', '2.2.1', 1), ('3b', '2.1.1', 1), ('4b', '4', 0),
397
self.assertEqual(expected, revisions)
398
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
399
'forward', include_merges=False))
400
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),