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_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('%4s %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')
329
return mainline_revs, rev_nos, wt
331
def make_tree_with_many_merges(self):
332
"""Create a tree with well-known revision ids"""
333
wt = self.make_branch_and_tree('tree1')
334
wt.commit('commit one', rev_id='1')
335
wt.commit('commit two', rev_id='2')
336
tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
337
tree3.commit('commit three a', rev_id='3a')
338
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
339
tree2.merge_from_branch(tree3.branch)
340
tree2.commit('commit three b', rev_id='3b')
341
wt.merge_from_branch(tree2.branch)
342
wt.commit('commit three c', rev_id='3c')
343
tree2.commit('four-a', rev_id='4a')
344
wt.merge_from_branch(tree2.branch)
345
wt.commit('four-b', rev_id='4b')
346
mainline_revs = [None, '1', '2', '3c', '4b']
347
rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
348
full_rev_nos_for_reference = {
351
'3a': '2.2.1', #first commit tree 3
352
'3b': '2.1.1', # first commit tree 2
353
'3c': '3', #merges 3b to main
354
'4a': '2.1.2', # second commit tree 2
355
'4b': '4', # merges 4a to main
357
return mainline_revs, rev_nos, wt
359
def test_get_view_revisions_forward(self):
360
"""Test the get_view_revisions method"""
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([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
366
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
367
'forward', include_merges=False))
368
self.assertEqual(revisions, revisions2)
370
def test_get_view_revisions_reverse(self):
371
"""Test the get_view_revisions with reverse"""
372
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
373
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
375
self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
377
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
378
'reverse', include_merges=False))
379
self.assertEqual(revisions, revisions2)
381
def test_get_view_revisions_merge(self):
382
"""Test get_view_revisions 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([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
387
('4b', '4', 0), ('4a', '3.1.1', 1)],
389
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
390
'forward', include_merges=False))
391
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
395
def test_get_view_revisions_merge_reverse(self):
396
"""Test get_view_revisions in reverse when there are merges"""
397
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
398
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
400
self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
401
('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
403
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
404
'reverse', include_merges=False))
405
self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
409
def test_get_view_revisions_merge2(self):
410
"""Test get_view_revisions when there are merges"""
411
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
412
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
414
expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
415
('3a', '2.2.1', 1), ('3b', '2.1.1', 1), ('4b', '4', 0),
417
self.assertEqual(expected, revisions)
418
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
419
'forward', include_merges=False))
420
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),