1
# Copyright (C) 2005-2013, 2016 Canonical Ltd
1
# Copyright (C) 2005 by Canonical Ltd
2
# -*- coding: utf-8 -*-
3
5
# This program is free software; you can redistribute it and/or modify
4
6
# it under the terms of the GNU General Public License as published by
5
7
# the Free Software Foundation; either version 2 of the License, or
6
8
# (at your option) any later version.
8
10
# This program is distributed in the hope that it will be useful,
9
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
13
# GNU General Public License for more details.
13
15
# You should have received a copy of the GNU General Public License
14
16
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
31
from ..sixish import (
38
class TestLogMixin(object):
40
def wt_commit(self, wt, message, **kwargs):
41
"""Use some mostly fixed values for commits to simplify tests.
43
Tests can use this function to get some commit attributes. The time
44
stamp is incremented at each commit.
46
if getattr(self, 'timestamp', None) is None:
47
self.timestamp = 1132617600 # Mon 2005-11-22 00:00:00 +0000
49
self.timestamp += 1 # 1 second between each commit
50
kwargs.setdefault('timestamp', self.timestamp)
51
kwargs.setdefault('timezone', 0) # UTC
52
kwargs.setdefault('committer', 'Joe Foo <joe@foo.com>')
54
return wt.commit(message, **kwargs)
57
class TestCaseForLogFormatter(tests.TestCaseWithTransport, TestLogMixin):
60
super(TestCaseForLogFormatter, self).setUp()
61
# keep a reference to the "current" custom prop. handler registry
62
self.properties_handler_registry = log.properties_handler_registry
63
# Use a clean registry for log
64
log.properties_handler_registry = registry.Registry()
67
log.properties_handler_registry = self.properties_handler_registry
68
self.addCleanup(restore)
70
def assertFormatterResult(self, result, branch, formatter_class,
71
formatter_kwargs=None, show_log_kwargs=None):
72
logfile = self.make_utf8_encoded_stringio()
73
if formatter_kwargs is None:
75
formatter = formatter_class(to_file=logfile, **formatter_kwargs)
76
if show_log_kwargs is None:
78
log.show_log(branch, formatter, **show_log_kwargs)
79
self.assertEqualDiff(result, logfile.getvalue())
81
def make_standard_commit(self, branch_nick, **kwargs):
82
wt = self.make_branch_and_tree('.')
84
self.addCleanup(wt.unlock)
85
self.build_tree(['a'])
87
wt.branch.nick = branch_nick
88
kwargs.setdefault('committer', 'Lorem Ipsum <test@example.com>')
89
kwargs.setdefault('authors', ['John Doe <jdoe@example.com>'])
90
self.wt_commit(wt, 'add a', **kwargs)
93
def make_commits_with_trailing_newlines(self, wt):
94
"""Helper method for LogFormatter tests"""
97
self.build_tree_contents([('a', b'hello moto\n')])
98
self.wt_commit(wt, 'simple log message', rev_id=b'a1')
99
self.build_tree_contents([('b', b'goodbye\n')])
101
self.wt_commit(wt, 'multiline\nlog\nmessage\n', rev_id=b'a2')
103
self.build_tree_contents([('c', b'just another manic monday\n')])
105
self.wt_commit(wt, 'single line with trailing newline\n', rev_id=b'a3')
108
def _prepare_tree_with_merges(self, with_tags=False):
109
wt = self.make_branch_and_memory_tree('.')
111
self.addCleanup(wt.unlock)
113
self.wt_commit(wt, 'rev-1', rev_id=b'rev-1')
114
self.wt_commit(wt, 'rev-merged', rev_id=b'rev-2a')
115
wt.set_parent_ids([b'rev-1', b'rev-2a'])
116
wt.branch.set_last_revision_info(1, b'rev-1')
117
self.wt_commit(wt, 'rev-2', rev_id=b'rev-2b')
120
branch.tags.set_tag('v0.2', b'rev-2b')
121
self.wt_commit(wt, 'rev-3', rev_id=b'rev-3')
122
branch.tags.set_tag('v1.0rc1', b'rev-3')
123
branch.tags.set_tag('v1.0', b'rev-3')
127
class LogCatcher(log.LogFormatter):
128
"""Pull log messages into a list rather than displaying them.
130
To simplify testing we save logged revisions here rather than actually
131
formatting anything, so that we can precisely check the result without
132
being dependent on the formatting.
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.
135
supports_merge_revisions = True
136
supports_delta = True
140
def __init__(self, *args, **kwargs):
141
kwargs.update(dict(to_file=None))
142
super(LogCatcher, self).__init__(*args, **kwargs)
145
def log_revision(self, revision):
146
self.revisions.append(revision)
149
class TestShowLog(tests.TestCaseWithTransport):
42
super(LogCatcher, self).__init__(to_file=None)
46
def show(self, revno, rev, delta):
54
class SimpleLogTest(TestCaseWithTransport):
151
56
def checkDelta(self, delta, **kw):
152
"""Check the filenames touched by a delta are as expected.
154
Caller only have to pass in the list of files for each part, all
155
unspecified parts are considered empty (and checked as such).
57
"""Check the filenames touched by a delta are as expected."""
157
58
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
158
# By default we expect an empty list
159
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]
160
65
# strip out only the path components
161
got = [x.path[1] or x.path[0] for x in getattr(delta, n)]
162
self.assertEqual(expected, got)
164
def assertInvalidRevisonNumber(self, br, start, end):
166
self.assertRaises(errors.InvalidRevisionNumber,
167
log.show_log, br, lf,
168
start_revision=start, end_revision=end)
170
def test_cur_revno(self):
171
wt = self.make_branch_and_tree('.')
175
wt.commit('empty commit')
176
log.show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
178
# Since there is a single revision in the branch all the combinations
180
self.assertInvalidRevisonNumber(b, 2, 1)
181
self.assertInvalidRevisonNumber(b, 1, 2)
182
self.assertInvalidRevisonNumber(b, 0, 2)
183
self.assertInvalidRevisonNumber(b, -1, 1)
184
self.assertInvalidRevisonNumber(b, 1, -1)
185
self.assertInvalidRevisonNumber(b, 1, 0)
187
def test_empty_branch(self):
188
wt = self.make_branch_and_tree('.')
191
log.show_log(wt.branch, lf)
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('.')
193
self.assertEqual([], lf.revisions)
195
def test_empty_commit(self):
196
wt = self.make_branch_and_tree('.')
198
120
wt.commit('empty commit')
199
121
lf = LogCatcher()
200
log.show_log(wt.branch, lf, verbose=True)
202
self.assertEqual(1, len(revs))
203
self.assertEqual('1', revs[0].revno)
204
self.assertEqual('empty commit', revs[0].rev.message)
205
self.checkDelta(revs[0].delta)
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)
207
def test_simple_commit(self):
208
wt = self.make_branch_and_tree('.')
209
wt.commit('empty commit')
210
130
self.build_tree(['hello'])
212
wt.commit('add one file',
213
committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
214
u'<test@example.com>')
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
215
142
lf = LogCatcher()
216
log.show_log(wt.branch, lf, verbose=True)
217
self.assertEqual(2, len(lf.revisions))
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))
218
149
# first one is most recent
219
log_entry = lf.revisions[0]
220
self.assertEqual('2', log_entry.revno)
221
self.assertEqual('add one file', log_entry.rev.message)
222
self.checkDelta(log_entry.delta, added=['hello'])
224
def test_commit_message_with_control_chars(self):
225
wt = self.make_branch_and_tree('.')
226
msg = u"All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
227
msg = msg.replace(u'\r', u'\n')
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)
229
161
lf = LogCatcher()
230
log.show_log(wt.branch, lf, verbose=True)
231
committed_msg = lf.revisions[0].rev.message
232
if wt.branch.repository._serializer.squashes_xml_invalid_characters:
233
self.assertNotEqual(msg, committed_msg)
234
self.assertTrue(len(committed_msg) > len(msg))
236
self.assertEqual(msg, committed_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))
238
def test_commit_message_without_control_chars(self):
239
wt = self.make_branch_and_tree('.')
168
# Check that log message with only XML-valid characters isn't
240
169
# escaped. As ElementTree apparently does some kind of
241
170
# newline conversion, neither LF (\x0A) nor CR (\x0D) are
242
171
# included in the test commit message, even though they are
243
172
# valid XML 1.0 characters.
244
173
msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
174
self.log("original commit message: %r", msg)
246
176
lf = LogCatcher()
247
log.show_log(wt.branch, lf, verbose=True)
248
committed_msg = lf.revisions[0].rev.message
249
self.assertEqual(msg, committed_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)
251
def test_deltas_in_merge_revisions(self):
252
"""Check deltas created for both mainline and merge revisions"""
253
wt = self.make_branch_and_tree('parent')
254
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
257
wt.commit(message='add file1 and file2')
258
self.run_bzr('branch parent child')
259
os.unlink('child/file1')
260
with open('child/file2', 'wb') as f:
262
self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
265
self.run_bzr('merge ../child')
266
wt.commit('merge child branch')
182
def test_trailing_newlines(self):
183
wt = self.make_branch_and_tree('.')
270
lf.supports_merge_revisions = True
271
log.show_log(b, lf, verbose=True)
274
self.assertEqual(3, len(revs))
277
self.assertEqual('2', logentry.revno)
278
self.assertEqual('merge child branch', logentry.rev.message)
279
self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
282
self.assertEqual('1.1.1', logentry.revno)
283
self.assertEqual('remove file1 and modify file2', logentry.rev.message)
284
self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
287
self.assertEqual('1', logentry.revno)
288
self.assertEqual('add file1 and file2', logentry.rev.message)
289
self.checkDelta(logentry.delta, added=['file1', 'file2'])
291
def test_bug_842695_log_restricted_to_dir(self):
292
# Comments here indicate revision numbers in trunk # VVVVV
293
trunk = self.make_branch_and_tree('this')
294
trunk.commit('initial trunk') # 1
295
adder = trunk.controldir.sprout('adder').open_workingtree()
296
merger = trunk.controldir.sprout('merger').open_workingtree()
297
self.build_tree_contents([
299
('adder/dir/file', b'foo'),
301
adder.add(['dir', 'dir/file'])
302
adder.commit('added dir') # 1.1.1
303
trunk.merge_from_branch(adder.branch)
304
trunk.commit('merged adder into trunk') # 2
305
merger.merge_from_branch(trunk.branch)
306
merger.commit('merged trunk into merger') # 1.2.1
307
# Commits are processed in increments of 200 revisions, so
308
# make sure the two merges into trunk are in different chunks.
310
trunk.commit('intermediate commit %d' % i) # 3-202
311
trunk.merge_from_branch(merger.branch)
312
trunk.commit('merged merger into trunk') # 203
313
file_id = trunk.path2id('dir')
315
lf.supports_merge_revisions = True
316
log.show_log(trunk.branch, lf, file_id)
318
self.assertEqual(['2', '1.1.1'], [r.revno for r in lf.revisions])
319
except AssertionError:
320
raise tests.KnownFailure("bug #842695")
323
class TestFormatSignatureValidity(tests.TestCaseWithTransport):
325
def verify_revision_signature(self, revid, gpg_strategy):
326
return (gpg.SIGNATURE_VALID,
327
u'UTF8 Test \xa1\xb1\xc1\xd1\xe1\xf1 <jrandom@example.com>')
329
def test_format_signature_validity_utf(self):
330
"""Check that GPG signatures containing UTF-8 names are formatted
332
wt = self.make_branch_and_tree('.')
333
revid = wt.commit('empty commit')
334
repo = wt.branch.repository
335
# Monkey patch out checking if this rev is actually signed, since we
336
# can't sign it without a heavier TestCase and LoopbackGPGStrategy
337
# doesn't care anyways.
338
self.overrideAttr(repo, 'verify_revision_signature',
339
self.verify_revision_signature)
340
out = log.format_signature_validity(revid, wt.branch)
342
u'valid signature from UTF8 Test \xa1\xb1\xc1\xd1\xe1\xf1 <jrandom@example.com>',
346
class TestShortLogFormatter(TestCaseForLogFormatter):
348
def test_trailing_newlines(self):
349
wt = self.make_branch_and_tree('.')
350
b = self.make_commits_with_trailing_newlines(wt)
351
self.assertFormatterResult(b"""\
352
3 Joe Foo\t2005-11-22
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
353
208
single line with trailing newline
355
2 Joe Foo\t2005-11-22
210
2 Joe Foo\t2005-11-21
360
1 Joe Foo\t2005-11-22
215
1 Joe Foo\t2005-11-21
361
216
simple log message
364
b, log.ShortLogFormatter)
366
def test_short_log_with_merges(self):
367
wt = self._prepare_tree_with_merges()
368
self.assertFormatterResult(b"""\
369
2 Joe Foo\t2005-11-22 [merge]
372
1 Joe Foo\t2005-11-22
376
wt.branch, log.ShortLogFormatter)
378
def test_short_log_with_merges_and_advice(self):
379
wt = self._prepare_tree_with_merges()
380
self.assertFormatterResult(b"""\
381
2 Joe Foo\t2005-11-22 [merge]
384
1 Joe Foo\t2005-11-22
387
Use --include-merged or -n0 to see merged revisions.
389
wt.branch, log.ShortLogFormatter,
390
formatter_kwargs=dict(show_advice=True))
392
def test_short_log_with_merges_and_range(self):
393
wt = self._prepare_tree_with_merges()
394
self.wt_commit(wt, 'rev-3a', rev_id=b'rev-3a')
395
wt.branch.set_last_revision_info(2, b'rev-2b')
396
wt.set_parent_ids([b'rev-2b', b'rev-3a'])
397
self.wt_commit(wt, 'rev-3b', rev_id=b'rev-3b')
398
self.assertFormatterResult(b"""\
399
3 Joe Foo\t2005-11-22 [merge]
402
2 Joe Foo\t2005-11-22 [merge]
406
wt.branch, log.ShortLogFormatter,
407
show_log_kwargs=dict(start_revision=2, end_revision=3))
409
def test_short_log_with_tags(self):
410
wt = self._prepare_tree_with_merges(with_tags=True)
411
self.assertFormatterResult(b"""\
412
3 Joe Foo\t2005-11-22 {v1.0, v1.0rc1}
415
2 Joe Foo\t2005-11-22 {v0.2} [merge]
418
1 Joe Foo\t2005-11-22
422
wt.branch, log.ShortLogFormatter)
424
def test_short_log_single_merge_revision(self):
425
wt = self._prepare_tree_with_merges()
426
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
427
rev = revspec.in_history(wt.branch)
428
self.assertFormatterResult(b"""\
429
1.1.1 Joe Foo\t2005-11-22
433
wt.branch, log.ShortLogFormatter,
434
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
436
def test_show_ids(self):
437
wt = self.make_branch_and_tree('parent')
438
self.build_tree(['parent/f1', 'parent/f2'])
440
self.wt_commit(wt, 'first post', rev_id=b'a')
441
child_wt = wt.controldir.sprout('child').open_workingtree()
442
self.wt_commit(child_wt, 'branch 1 changes', rev_id=b'b')
443
wt.merge_from_branch(child_wt.branch)
444
self.wt_commit(wt, 'merge branch 1', rev_id=b'c')
445
self.assertFormatterResult(b"""\
446
2 Joe Foo\t2005-11-22 [merge]
450
1.1.1 Joe Foo\t2005-11-22
454
1 Joe Foo\t2005-11-22
459
wt.branch, log.ShortLogFormatter,
460
formatter_kwargs=dict(levels=0, show_ids=True))
463
class TestShortLogFormatterWithMergeRevisions(TestCaseForLogFormatter):
465
def test_short_merge_revs_log_with_merges(self):
466
wt = self._prepare_tree_with_merges()
467
# Note that the 1.1.1 indenting is in fact correct given that
468
# the revision numbers are right justified within 5 characters
469
# for mainline revnos and 9 characters for dotted revnos.
470
self.assertFormatterResult(b"""\
471
2 Joe Foo\t2005-11-22 [merge]
474
1.1.1 Joe Foo\t2005-11-22
477
1 Joe Foo\t2005-11-22
481
wt.branch, log.ShortLogFormatter,
482
formatter_kwargs=dict(levels=0))
484
def test_short_merge_revs_log_single_merge_revision(self):
485
wt = self._prepare_tree_with_merges()
486
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
487
rev = revspec.in_history(wt.branch)
488
self.assertFormatterResult(b"""\
489
1.1.1 Joe Foo\t2005-11-22
493
wt.branch, log.ShortLogFormatter,
494
formatter_kwargs=dict(levels=0),
495
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
498
class TestLongLogFormatter(TestCaseForLogFormatter):
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
500
249
def test_verbose_log(self):
501
250
"""Verbose log includes changed files
505
wt = self.make_standard_commit('test_verbose_log', authors=[])
506
self.assertFormatterResult(b'''\
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, '''\
507
271
------------------------------------------------------------
509
273
committer: Lorem Ipsum <test@example.com>
510
274
branch nick: test_verbose_log
511
timestamp: Tue 2005-11-22 00:00:00 +0000
517
wt.branch, log.LongLogFormatter,
518
show_log_kwargs=dict(verbose=True))
520
def test_merges_are_indented_by_level(self):
521
wt = self.make_branch_and_tree('parent')
522
self.wt_commit(wt, 'first post')
523
child_wt = wt.controldir.sprout('child').open_workingtree()
524
self.wt_commit(child_wt, 'branch 1')
525
smallerchild_wt = wt.controldir.sprout(
526
'smallerchild').open_workingtree()
527
self.wt_commit(smallerchild_wt, 'branch 2')
528
child_wt.merge_from_branch(smallerchild_wt.branch)
529
self.wt_commit(child_wt, 'merge branch 2')
530
wt.merge_from_branch(child_wt.branch)
531
self.wt_commit(wt, 'merge branch 1')
532
self.assertFormatterResult(b"""\
533
------------------------------------------------------------
535
committer: Joe Foo <joe@foo.com>
537
timestamp: Tue 2005-11-22 00:00:04 +0000
540
------------------------------------------------------------
542
committer: Joe Foo <joe@foo.com>
544
timestamp: Tue 2005-11-22 00:00:03 +0000
547
------------------------------------------------------------
549
committer: Joe Foo <joe@foo.com>
550
branch nick: smallerchild
551
timestamp: Tue 2005-11-22 00:00:02 +0000
554
------------------------------------------------------------
556
committer: Joe Foo <joe@foo.com>
558
timestamp: Tue 2005-11-22 00:00:01 +0000
561
------------------------------------------------------------
563
committer: Joe Foo <joe@foo.com>
565
timestamp: Tue 2005-11-22 00:00:00 +0000
569
wt.branch, log.LongLogFormatter,
570
formatter_kwargs=dict(levels=0),
571
show_log_kwargs=dict(verbose=True))
573
def test_verbose_merge_revisions_contain_deltas(self):
574
wt = self.make_branch_and_tree('parent')
575
self.build_tree(['parent/f1', 'parent/f2'])
577
self.wt_commit(wt, 'first post')
578
child_wt = wt.controldir.sprout('child').open_workingtree()
579
os.unlink('child/f1')
580
self.build_tree_contents([('child/f2', b'hello\n')])
581
self.wt_commit(child_wt, 'removed f1 and modified f2')
582
wt.merge_from_branch(child_wt.branch)
583
self.wt_commit(wt, 'merge branch 1')
584
self.assertFormatterResult(b"""\
585
------------------------------------------------------------
587
committer: Joe Foo <joe@foo.com>
589
timestamp: Tue 2005-11-22 00:00:02 +0000
596
------------------------------------------------------------
598
committer: Joe Foo <joe@foo.com>
600
timestamp: Tue 2005-11-22 00:00:01 +0000
602
removed f1 and modified f2
607
------------------------------------------------------------
609
committer: Joe Foo <joe@foo.com>
611
timestamp: Tue 2005-11-22 00:00:00 +0000
618
wt.branch, log.LongLogFormatter,
619
formatter_kwargs=dict(levels=0),
620
show_log_kwargs=dict(verbose=True))
622
def test_trailing_newlines(self):
623
wt = self.make_branch_and_tree('.')
624
b = self.make_commits_with_trailing_newlines(wt)
625
self.assertFormatterResult(b"""\
626
------------------------------------------------------------
628
committer: Joe Foo <joe@foo.com>
630
timestamp: Tue 2005-11-22 00:00:02 +0000
632
single line with trailing newline
633
------------------------------------------------------------
635
committer: Joe Foo <joe@foo.com>
637
timestamp: Tue 2005-11-22 00:00:01 +0000
642
------------------------------------------------------------
644
committer: Joe Foo <joe@foo.com>
646
timestamp: Tue 2005-11-22 00:00:00 +0000
650
b, log.LongLogFormatter)
652
def test_author_in_log(self):
653
"""Log includes the author name if it's set in
654
the revision properties
656
wt = self.make_standard_commit('test_author_log',
657
authors=['John Doe <jdoe@example.com>',
658
'Jane Rey <jrey@example.com>'])
659
self.assertFormatterResult(b"""\
660
------------------------------------------------------------
662
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
663
committer: Lorem Ipsum <test@example.com>
664
branch nick: test_author_log
665
timestamp: Tue 2005-11-22 00:00:00 +0000
669
wt.branch, log.LongLogFormatter)
671
def test_properties_in_log(self):
672
"""Log includes the custom properties returned by the registered
675
wt = self.make_standard_commit('test_properties_in_log')
677
def trivial_custom_prop_handler(revision):
678
return {'test_prop': 'test_value'}
680
# Cleaned up in setUp()
681
log.properties_handler_registry.register(
682
'trivial_custom_prop_handler',
683
trivial_custom_prop_handler)
684
self.assertFormatterResult(b"""\
685
------------------------------------------------------------
687
test_prop: test_value
688
author: John Doe <jdoe@example.com>
689
committer: Lorem Ipsum <test@example.com>
690
branch nick: test_properties_in_log
691
timestamp: Tue 2005-11-22 00:00:00 +0000
695
wt.branch, log.LongLogFormatter)
697
def test_properties_in_short_log(self):
698
"""Log includes the custom properties returned by the registered
701
wt = self.make_standard_commit('test_properties_in_short_log')
703
def trivial_custom_prop_handler(revision):
704
return {'test_prop': 'test_value'}
706
log.properties_handler_registry.register(
707
'trivial_custom_prop_handler',
708
trivial_custom_prop_handler)
709
self.assertFormatterResult(b"""\
710
1 John Doe\t2005-11-22
711
test_prop: test_value
715
wt.branch, log.ShortLogFormatter)
717
def test_error_in_properties_handler(self):
718
"""Log includes the custom properties returned by the registered
721
wt = self.make_standard_commit('error_in_properties_handler',
722
revprops={u'first_prop': 'first_value'})
723
sio = self.make_utf8_encoded_stringio()
724
formatter = log.LongLogFormatter(to_file=sio)
726
def trivial_custom_prop_handler(revision):
727
raise Exception("a test error")
729
log.properties_handler_registry.register(
730
'trivial_custom_prop_handler',
731
trivial_custom_prop_handler)
732
log.show_log(wt.branch, formatter)
733
self.assertContainsRe(
734
sio.getvalue(), b'brz: ERROR: Exception: a test error')
736
def test_properties_handler_bad_argument(self):
737
wt = self.make_standard_commit('bad_argument',
738
revprops={u'a_prop': 'test_value'})
739
sio = self.make_utf8_encoded_stringio()
740
formatter = log.LongLogFormatter(to_file=sio)
742
def bad_argument_prop_handler(revision):
743
return {'custom_prop_name': revision.properties['a_prop']}
745
log.properties_handler_registry.register(
746
'bad_argument_prop_handler',
747
bad_argument_prop_handler)
749
self.assertRaises(AttributeError, formatter.show_properties,
752
revision = wt.branch.repository.get_revision(wt.branch.last_revision())
753
formatter.show_properties(revision, '')
754
self.assertEqualDiff(b'custom_prop_name: test_value\n',
757
def test_show_ids(self):
758
wt = self.make_branch_and_tree('parent')
759
self.build_tree(['parent/f1', 'parent/f2'])
761
self.wt_commit(wt, 'first post', rev_id=b'a')
762
child_wt = wt.controldir.sprout('child').open_workingtree()
763
self.wt_commit(child_wt, 'branch 1 changes', rev_id=b'b')
764
wt.merge_from_branch(child_wt.branch)
765
self.wt_commit(wt, 'merge branch 1', rev_id=b'c')
766
self.assertFormatterResult(b"""\
767
------------------------------------------------------------
772
committer: Joe Foo <joe@foo.com>
774
timestamp: Tue 2005-11-22 00:00:02 +0000
777
------------------------------------------------------------
781
committer: Joe Foo <joe@foo.com>
783
timestamp: Tue 2005-11-22 00:00:01 +0000
786
------------------------------------------------------------
789
committer: Joe Foo <joe@foo.com>
791
timestamp: Tue 2005-11-22 00:00:00 +0000
795
wt.branch, log.LongLogFormatter,
796
formatter_kwargs=dict(levels=0, show_ids=True))
799
class TestLongLogFormatterWithoutMergeRevisions(TestCaseForLogFormatter):
801
def test_long_verbose_log(self):
802
"""Verbose log includes changed files
806
wt = self.make_standard_commit('test_long_verbose_log', authors=[])
807
self.assertFormatterResult(b"""\
808
------------------------------------------------------------
810
committer: Lorem Ipsum <test@example.com>
811
branch nick: test_long_verbose_log
812
timestamp: Tue 2005-11-22 00:00:00 +0000
818
wt.branch, log.LongLogFormatter,
819
formatter_kwargs=dict(levels=1),
820
show_log_kwargs=dict(verbose=True))
822
def test_long_verbose_contain_deltas(self):
823
wt = self.make_branch_and_tree('parent')
824
self.build_tree(['parent/f1', 'parent/f2'])
826
self.wt_commit(wt, 'first post')
827
child_wt = wt.controldir.sprout('child').open_workingtree()
828
os.unlink('child/f1')
829
self.build_tree_contents([('child/f2', b'hello\n')])
830
self.wt_commit(child_wt, 'removed f1 and modified f2')
831
wt.merge_from_branch(child_wt.branch)
832
self.wt_commit(wt, 'merge branch 1')
833
self.assertFormatterResult(b"""\
834
------------------------------------------------------------
836
committer: Joe Foo <joe@foo.com>
838
timestamp: Tue 2005-11-22 00:00:02 +0000
845
------------------------------------------------------------
847
committer: Joe Foo <joe@foo.com>
849
timestamp: Tue 2005-11-22 00:00:00 +0000
856
wt.branch, log.LongLogFormatter,
857
formatter_kwargs=dict(levels=1),
858
show_log_kwargs=dict(verbose=True))
860
def test_long_trailing_newlines(self):
861
wt = self.make_branch_and_tree('.')
862
b = self.make_commits_with_trailing_newlines(wt)
863
self.assertFormatterResult(b"""\
864
------------------------------------------------------------
866
committer: Joe Foo <joe@foo.com>
868
timestamp: Tue 2005-11-22 00:00:02 +0000
870
single line with trailing newline
871
------------------------------------------------------------
873
committer: Joe Foo <joe@foo.com>
875
timestamp: Tue 2005-11-22 00:00:01 +0000
880
------------------------------------------------------------
882
committer: Joe Foo <joe@foo.com>
884
timestamp: Tue 2005-11-22 00:00:00 +0000
888
b, log.LongLogFormatter,
889
formatter_kwargs=dict(levels=1))
891
def test_long_author_in_log(self):
892
"""Log includes the author name if it's set in
893
the revision properties
895
wt = self.make_standard_commit('test_author_log')
896
self.assertFormatterResult(b"""\
897
------------------------------------------------------------
899
author: John Doe <jdoe@example.com>
900
committer: Lorem Ipsum <test@example.com>
901
branch nick: test_author_log
902
timestamp: Tue 2005-11-22 00:00:00 +0000
906
wt.branch, log.LongLogFormatter,
907
formatter_kwargs=dict(levels=1))
909
def test_long_properties_in_log(self):
910
"""Log includes the custom properties returned by the registered
913
wt = self.make_standard_commit('test_properties_in_log')
915
def trivial_custom_prop_handler(revision):
916
return {'test_prop': 'test_value'}
918
log.properties_handler_registry.register(
919
'trivial_custom_prop_handler',
920
trivial_custom_prop_handler)
921
self.assertFormatterResult(b"""\
922
------------------------------------------------------------
924
test_prop: test_value
925
author: John Doe <jdoe@example.com>
926
committer: Lorem Ipsum <test@example.com>
927
branch nick: test_properties_in_log
928
timestamp: Tue 2005-11-22 00:00:00 +0000
932
wt.branch, log.LongLogFormatter,
933
formatter_kwargs=dict(levels=1))
936
class TestLineLogFormatter(TestCaseForLogFormatter):
938
def test_line_log(self):
939
"""Line log should show revno
943
wt = self.make_standard_commit('test-line-log',
944
committer='Line-Log-Formatter Tester <test@line.log>',
946
self.assertFormatterResult(b"""\
947
1: Line-Log-Formatte... 2005-11-22 add a
949
wt.branch, log.LineLogFormatter)
951
def test_trailing_newlines(self):
952
wt = self.make_branch_and_tree('.')
953
b = self.make_commits_with_trailing_newlines(wt)
954
self.assertFormatterResult(b"""\
955
3: Joe Foo 2005-11-22 single line with trailing newline
956
2: Joe Foo 2005-11-22 multiline
957
1: Joe Foo 2005-11-22 simple log message
959
b, log.LineLogFormatter)
961
def test_line_log_single_merge_revision(self):
962
wt = self._prepare_tree_with_merges()
963
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
964
rev = revspec.in_history(wt.branch)
965
self.assertFormatterResult(b"""\
966
1.1.1: Joe Foo 2005-11-22 rev-merged
968
wt.branch, log.LineLogFormatter,
969
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
971
def test_line_log_with_tags(self):
972
wt = self._prepare_tree_with_merges(with_tags=True)
973
self.assertFormatterResult(b"""\
974
3: Joe Foo 2005-11-22 {v1.0, v1.0rc1} rev-3
975
2: Joe Foo 2005-11-22 [merge] {v0.2} rev-2
976
1: Joe Foo 2005-11-22 rev-1
978
wt.branch, log.LineLogFormatter)
981
class TestLineLogFormatterWithMergeRevisions(TestCaseForLogFormatter):
983
def test_line_merge_revs_log(self):
984
"""Line log should show revno
988
wt = self.make_standard_commit('test-line-log',
989
committer='Line-Log-Formatter Tester <test@line.log>',
991
self.assertFormatterResult(b"""\
992
1: Line-Log-Formatte... 2005-11-22 add a
994
wt.branch, log.LineLogFormatter)
996
def test_line_merge_revs_log_single_merge_revision(self):
997
wt = self._prepare_tree_with_merges()
998
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
999
rev = revspec.in_history(wt.branch)
1000
self.assertFormatterResult(b"""\
1001
1.1.1: Joe Foo 2005-11-22 rev-merged
1003
wt.branch, log.LineLogFormatter,
1004
formatter_kwargs=dict(levels=0),
1005
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
1007
def test_line_merge_revs_log_with_merges(self):
1008
wt = self._prepare_tree_with_merges()
1009
self.assertFormatterResult(b"""\
1010
2: Joe Foo 2005-11-22 [merge] rev-2
1011
1.1.1: Joe Foo 2005-11-22 rev-merged
1012
1: Joe Foo 2005-11-22 rev-1
1014
wt.branch, log.LineLogFormatter,
1015
formatter_kwargs=dict(levels=0))
1018
class TestGnuChangelogFormatter(TestCaseForLogFormatter):
1020
def test_gnu_changelog(self):
1021
wt = self.make_standard_commit('nicky', authors=[])
1022
self.assertFormatterResult(b'''\
1023
2005-11-22 Lorem Ipsum <test@example.com>
1028
wt.branch, log.GnuChangelogLogFormatter)
1030
def test_with_authors(self):
1031
wt = self.make_standard_commit('nicky',
1032
authors=['Fooa Fooz <foo@example.com>',
1033
'Bari Baro <bar@example.com>'])
1034
self.assertFormatterResult(b'''\
1035
2005-11-22 Fooa Fooz <foo@example.com>
1040
wt.branch, log.GnuChangelogLogFormatter)
1042
def test_verbose(self):
1043
wt = self.make_standard_commit('nicky')
1044
self.assertFormatterResult(b'''\
1045
2005-11-22 John Doe <jdoe@example.com>
1052
wt.branch, log.GnuChangelogLogFormatter,
1053
show_log_kwargs=dict(verbose=True))
1056
class TestShowChangedRevisions(tests.TestCaseWithTransport):
1058
def test_show_changed_revisions_verbose(self):
1059
tree = self.make_branch_and_tree('tree_a')
1060
self.build_tree(['tree_a/foo'])
1062
tree.commit('bar', rev_id=b'bar-id')
1063
s = self.make_utf8_encoded_stringio()
1064
log.show_changed_revisions(tree.branch, [], [b'bar-id'], s)
1065
self.assertContainsRe(s.getvalue(), b'bar')
1066
self.assertNotContainsRe(s.getvalue(), b'foo')
1069
class TestLogFormatter(tests.TestCase):
1072
super(TestLogFormatter, self).setUp()
1073
self.rev = revision.Revision(b'a-id')
1074
self.lf = log.LogFormatter(None)
1076
def test_short_committer(self):
1077
def assertCommitter(expected, committer):
1078
self.rev.committer = committer
1079
self.assertEqual(expected, self.lf.short_committer(self.rev))
1081
assertCommitter('John Doe', 'John Doe <jdoe@example.com>')
1082
assertCommitter('John Smith', 'John Smith <jsmith@example.com>')
1083
assertCommitter('John Smith', 'John Smith')
1084
assertCommitter('jsmith@example.com', 'jsmith@example.com')
1085
assertCommitter('jsmith@example.com', '<jsmith@example.com>')
1086
assertCommitter('John Smith', 'John Smith jsmith@example.com')
1088
def test_short_author(self):
1089
def assertAuthor(expected, author):
1090
self.rev.properties['author'] = author
1091
self.assertEqual(expected, self.lf.short_author(self.rev))
1093
assertAuthor('John Smith', 'John Smith <jsmith@example.com>')
1094
assertAuthor('John Smith', 'John Smith')
1095
assertAuthor('jsmith@example.com', 'jsmith@example.com')
1096
assertAuthor('jsmith@example.com', '<jsmith@example.com>')
1097
assertAuthor('John Smith', 'John Smith jsmith@example.com')
1099
def test_short_author_from_committer(self):
1100
self.rev.committer = 'John Doe <jdoe@example.com>'
1101
self.assertEqual('John Doe', self.lf.short_author(self.rev))
1103
def test_short_author_from_authors(self):
1104
self.rev.properties['authors'] = ('John Smith <jsmith@example.com>\n'
1105
'Jane Rey <jrey@example.com>')
1106
self.assertEqual('John Smith', self.lf.short_author(self.rev))
1109
class TestReverseByDepth(tests.TestCase):
1110
"""Test reverse_by_depth behavior.
1112
This is used to present revisions in forward (oldest first) order in a nice
1115
The tests use lighter revision description to ease reading.
1118
def assertReversed(self, forward, backward):
1119
# Transform the descriptions to suit the API: tests use (revno, depth),
1120
# while the API expects (revid, revno, depth)
1121
def complete_revisions(l):
1122
"""Transform the description to suit the API.
1124
Tests use (revno, depth) whil the API expects (revid, revno, depth).
1125
Since the revid is arbitrary, we just duplicate revno
1127
return [(r, r, d) for r, d in l]
1128
forward = complete_revisions(forward)
1129
backward = complete_revisions(backward)
1130
self.assertEqual(forward, log.reverse_by_depth(backward))
1132
def test_mainline_revisions(self):
1133
self.assertReversed([('1', 0), ('2', 0)],
1134
[('2', 0), ('1', 0)])
1136
def test_merged_revisions(self):
1137
self.assertReversed([('1', 0), ('2', 0), ('2.2', 1), ('2.1', 1), ],
1138
[('2', 0), ('2.1', 1), ('2.2', 1), ('1', 0), ])
1140
def test_shifted_merged_revisions(self):
1141
"""Test irregular layout.
1143
Requesting revisions touching a file can produce "holes" in the depths.
1145
self.assertReversed([('1', 0), ('2', 0), ('1.1', 2), ('1.2', 2), ],
1146
[('2', 0), ('1.2', 2), ('1.1', 2), ('1', 0), ])
1148
def test_merged_without_child_revisions(self):
1149
"""Test irregular layout.
1151
Revision ranges can produce "holes" in the depths.
1153
# When a revision of higher depth doesn't follow one of lower depth, we
1154
# assume a lower depth one is virtually there
1155
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1156
[('4', 4), ('3', 3), ('2', 2), ('1', 2), ])
1157
# So we get the same order after reversing below even if the original
1158
# revisions are not in the same order.
1159
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1160
[('3', 3), ('4', 4), ('2', 2), ('1', 2), ])
1163
class TestHistoryChange(tests.TestCaseWithTransport):
1165
def setup_a_tree(self):
1166
tree = self.make_branch_and_tree('tree')
1168
self.addCleanup(tree.unlock)
1169
tree.commit('1a', rev_id=b'1a')
1170
tree.commit('2a', rev_id=b'2a')
1171
tree.commit('3a', rev_id=b'3a')
1174
def setup_ab_tree(self):
1175
tree = self.setup_a_tree()
1176
tree.set_last_revision(b'1a')
1177
tree.branch.set_last_revision_info(1, b'1a')
1178
tree.commit('2b', rev_id=b'2b')
1179
tree.commit('3b', rev_id=b'3b')
1182
def setup_ac_tree(self):
1183
tree = self.setup_a_tree()
1184
tree.set_last_revision(revision.NULL_REVISION)
1185
tree.branch.set_last_revision_info(0, revision.NULL_REVISION)
1186
tree.commit('1c', rev_id=b'1c')
1187
tree.commit('2c', rev_id=b'2c')
1188
tree.commit('3c', rev_id=b'3c')
1191
def test_all_new(self):
1192
tree = self.setup_ab_tree()
1193
old, new = log.get_history_change(b'1a', b'3a', tree.branch.repository)
1194
self.assertEqual([], old)
1195
self.assertEqual([b'2a', b'3a'], new)
1197
def test_all_old(self):
1198
tree = self.setup_ab_tree()
1199
old, new = log.get_history_change(b'3a', b'1a', tree.branch.repository)
1200
self.assertEqual([], new)
1201
self.assertEqual([b'2a', b'3a'], old)
1203
def test_null_old(self):
1204
tree = self.setup_ab_tree()
1205
old, new = log.get_history_change(revision.NULL_REVISION,
1206
b'3a', tree.branch.repository)
1207
self.assertEqual([], old)
1208
self.assertEqual([b'1a', b'2a', b'3a'], new)
1210
def test_null_new(self):
1211
tree = self.setup_ab_tree()
1212
old, new = log.get_history_change(b'3a', revision.NULL_REVISION,
1213
tree.branch.repository)
1214
self.assertEqual([], new)
1215
self.assertEqual([b'1a', b'2a', b'3a'], old)
1217
def test_diverged(self):
1218
tree = self.setup_ab_tree()
1219
old, new = log.get_history_change(b'3a', b'3b', tree.branch.repository)
1220
self.assertEqual(old, [b'2a', b'3a'])
1221
self.assertEqual(new, [b'2b', b'3b'])
1223
def test_unrelated(self):
1224
tree = self.setup_ac_tree()
1225
old, new = log.get_history_change(b'3a', b'3c', tree.branch.repository)
1226
self.assertEqual(old, [b'1a', b'2a', b'3a'])
1227
self.assertEqual(new, [b'1c', b'2c', b'3c'])
1229
def test_show_branch_change(self):
1230
tree = self.setup_ab_tree()
1232
log.show_branch_change(tree.branch, s, 3, b'3a')
1233
self.assertContainsRe(s.getvalue(),
1234
'[*]{60}\nRemoved Revisions:\n(.|\n)*2a(.|\n)*3a(.|\n)*'
1235
'[*]{60}\n\nAdded Revisions:\n(.|\n)*2b(.|\n)*3b')
1237
def test_show_branch_change_no_change(self):
1238
tree = self.setup_ab_tree()
1240
log.show_branch_change(tree.branch, s, 3, b'3b')
1241
self.assertEqual(s.getvalue(),
1242
'Nothing seems to have changed\n')
1244
def test_show_branch_change_no_old(self):
1245
tree = self.setup_ab_tree()
1247
log.show_branch_change(tree.branch, s, 2, b'2b')
1248
self.assertContainsRe(s.getvalue(), 'Added Revisions:')
1249
self.assertNotContainsRe(s.getvalue(), 'Removed Revisions:')
1251
def test_show_branch_change_no_new(self):
1252
tree = self.setup_ab_tree()
1253
tree.branch.set_last_revision_info(2, b'2b')
1255
log.show_branch_change(tree.branch, s, 3, b'3b')
1256
self.assertContainsRe(s.getvalue(), 'Removed Revisions:')
1257
self.assertNotContainsRe(s.getvalue(), 'Added Revisions:')
1260
class TestRevisionNotInBranch(TestCaseForLogFormatter):
1262
def setup_a_tree(self):
1263
tree = self.make_branch_and_tree('tree')
1265
self.addCleanup(tree.unlock)
1267
'committer': 'Joe Foo <joe@foo.com>',
1268
'timestamp': 1132617600, # Mon 2005-11-22 00:00:00 +0000
1269
'timezone': 0, # UTC
1271
tree.commit('commit 1a', rev_id=b'1a', **kwargs)
1272
tree.commit('commit 2a', rev_id=b'2a', **kwargs)
1273
tree.commit('commit 3a', rev_id=b'3a', **kwargs)
1276
def setup_ab_tree(self):
1277
tree = self.setup_a_tree()
1278
tree.set_last_revision(b'1a')
1279
tree.branch.set_last_revision_info(1, b'1a')
1281
'committer': 'Joe Foo <joe@foo.com>',
1282
'timestamp': 1132617600, # Mon 2005-11-22 00:00:00 +0000
1283
'timezone': 0, # UTC
1285
tree.commit('commit 2b', rev_id=b'2b', **kwargs)
1286
tree.commit('commit 3b', rev_id=b'3b', **kwargs)
1289
def test_one_revision(self):
1290
tree = self.setup_ab_tree()
1292
rev = revisionspec.RevisionInfo(tree.branch, None, b'3a')
1293
log.show_log(tree.branch, lf, verbose=True, start_revision=rev,
1295
self.assertEqual(1, len(lf.revisions))
1296
self.assertEqual(None, lf.revisions[0].revno) # Out-of-branch
1297
self.assertEqual(b'3a', lf.revisions[0].rev.revision_id)
1299
def test_many_revisions(self):
1300
tree = self.setup_ab_tree()
1302
start_rev = revisionspec.RevisionInfo(tree.branch, None, b'1a')
1303
end_rev = revisionspec.RevisionInfo(tree.branch, None, b'3a')
1304
log.show_log(tree.branch, lf, verbose=True, start_revision=start_rev,
1305
end_revision=end_rev)
1306
self.assertEqual(3, len(lf.revisions))
1307
self.assertEqual(None, lf.revisions[0].revno) # Out-of-branch
1308
self.assertEqual(b'3a', lf.revisions[0].rev.revision_id)
1309
self.assertEqual(None, lf.revisions[1].revno) # Out-of-branch
1310
self.assertEqual(b'2a', lf.revisions[1].rev.revision_id)
1311
self.assertEqual('1', lf.revisions[2].revno) # In-branch
1313
def test_long_format(self):
1314
tree = self.setup_ab_tree()
1315
start_rev = revisionspec.RevisionInfo(tree.branch, None, b'1a')
1316
end_rev = revisionspec.RevisionInfo(tree.branch, None, b'3a')
1317
self.assertFormatterResult(b"""\
1318
------------------------------------------------------------
1320
committer: Joe Foo <joe@foo.com>
1322
timestamp: Tue 2005-11-22 00:00:00 +0000
1325
------------------------------------------------------------
1327
committer: Joe Foo <joe@foo.com>
1329
timestamp: Tue 2005-11-22 00:00:00 +0000
1332
------------------------------------------------------------
1334
committer: Joe Foo <joe@foo.com>
1336
timestamp: Tue 2005-11-22 00:00:00 +0000
1340
tree.branch, log.LongLogFormatter, show_log_kwargs={
1341
'start_revision': start_rev, 'end_revision': end_rev
1344
def test_short_format(self):
1345
tree = self.setup_ab_tree()
1346
start_rev = revisionspec.RevisionInfo(tree.branch, None, b'1a')
1347
end_rev = revisionspec.RevisionInfo(tree.branch, None, b'3a')
1348
self.assertFormatterResult(b"""\
1357
1 Joe Foo\t2005-11-22
1361
tree.branch, log.ShortLogFormatter, show_log_kwargs={
1362
'start_revision': start_rev, 'end_revision': end_rev
1365
def test_line_format(self):
1366
tree = self.setup_ab_tree()
1367
start_rev = revisionspec.RevisionInfo(tree.branch, None, b'1a')
1368
end_rev = revisionspec.RevisionInfo(tree.branch, None, b'3a')
1369
self.assertFormatterResult(b"""\
1370
Joe Foo 2005-11-22 commit 3a
1371
Joe Foo 2005-11-22 commit 2a
1372
1: Joe Foo 2005-11-22 commit 1a
1374
tree.branch, log.LineLogFormatter, show_log_kwargs={
1375
'start_revision': start_rev, 'end_revision': end_rev
1379
class TestLogWithBugs(TestCaseForLogFormatter, TestLogMixin):
1382
super(TestLogWithBugs, self).setUp()
1383
log.properties_handler_registry.register(
1384
'bugs_properties_handler',
1385
log._bugs_properties_handler)
1387
def make_commits_with_bugs(self):
1388
"""Helper method for LogFormatter tests"""
1389
tree = self.make_branch_and_tree(u'.')
1390
self.build_tree(['a', 'b'])
1392
self.wt_commit(tree, 'simple log message', rev_id=b'a1',
1393
revprops={u'bugs': 'test://bug/id fixed'})
1395
self.wt_commit(tree, 'multiline\nlog\nmessage\n', rev_id=b'a2',
1396
authors=['Joe Bar <joe@bar.com>'],
1397
revprops={u'bugs': 'test://bug/id fixed\n'
1398
'test://bug/2 fixed'})
1401
def test_bug_broken(self):
1402
tree = self.make_branch_and_tree(u'.')
1403
self.build_tree(['a', 'b'])
1405
self.wt_commit(tree, 'simple log message', rev_id=b'a1',
1406
revprops={u'bugs': 'test://bua g/id fixed'})
1408
logfile = self.make_utf8_encoded_stringio()
1409
formatter = log.LongLogFormatter(to_file=logfile)
1410
log.show_log(tree.branch, formatter)
1412
self.assertContainsRe(
1414
b'brz: ERROR: breezy.bugtracker.InvalidLineInBugsProperty: '
1415
b'Invalid line in bugs property: \'test://bua g/id fixed\'')
1417
text = logfile.getvalue()
1418
self.assertEqualDiff(
1419
text[text.index(b'-' * 60):],
1421
------------------------------------------------------------
1423
committer: Joe Foo <joe@foo.com>
1425
timestamp: Tue 2005-11-22 00:00:00 +0000
1430
def test_long_bugs(self):
1431
tree = self.make_commits_with_bugs()
1432
self.assertFormatterResult(b"""\
1433
------------------------------------------------------------
1435
fixes bugs: test://bug/id test://bug/2
1436
author: Joe Bar <joe@bar.com>
1437
committer: Joe Foo <joe@foo.com>
1439
timestamp: Tue 2005-11-22 00:00:01 +0000
1444
------------------------------------------------------------
1446
fixes bug: test://bug/id
1447
committer: Joe Foo <joe@foo.com>
1449
timestamp: Tue 2005-11-22 00:00:00 +0000
1453
tree.branch, log.LongLogFormatter)
1455
def test_short_bugs(self):
1456
tree = self.make_commits_with_bugs()
1457
self.assertFormatterResult(b"""\
1458
2 Joe Bar\t2005-11-22
1459
fixes bugs: test://bug/id test://bug/2
1464
1 Joe Foo\t2005-11-22
1465
fixes bug: test://bug/id
1469
tree.branch, log.ShortLogFormatter)
1471
def test_wrong_bugs_property(self):
1472
tree = self.make_branch_and_tree(u'.')
1473
self.build_tree(['foo'])
1474
self.wt_commit(tree, 'simple log message', rev_id=b'a1',
1475
revprops={u'bugs': 'test://bug/id invalid_value'})
1477
logfile = self.make_utf8_encoded_stringio()
1478
formatter = log.ShortLogFormatter(to_file=logfile)
1479
log.show_log(tree.branch, formatter)
1481
lines = logfile.getvalue().splitlines()
1484
lines[0], b' 1 Joe Foo\t2005-11-22')
1488
b'brz: ERROR: breezy.bugtracker.InvalidBugStatus: Invalid '
1489
b'bug status: \'invalid_value\'')
1491
self.assertEqual(lines[-2], b" simple log message")
1493
def test_bugs_handler_present(self):
1494
self.properties_handler_registry.get('bugs_properties_handler')
1497
class TestLogForAuthors(TestCaseForLogFormatter):
1500
super(TestLogForAuthors, self).setUp()
1501
self.wt = self.make_standard_commit('nicky',
1502
authors=['John Doe <jdoe@example.com>',
1503
'Jane Rey <jrey@example.com>'])
1505
def assertFormatterResult(self, formatter, who, result):
1506
formatter_kwargs = dict()
1508
author_list_handler = log.author_list_registry.get(who)
1509
formatter_kwargs['author_list_handler'] = author_list_handler
1510
TestCaseForLogFormatter.assertFormatterResult(self, result,
1511
self.wt.branch, formatter, formatter_kwargs=formatter_kwargs)
1513
def test_line_default(self):
1514
self.assertFormatterResult(log.LineLogFormatter, None, b"""\
1515
1: John Doe 2005-11-22 add a
1518
def test_line_committer(self):
1519
self.assertFormatterResult(log.LineLogFormatter, 'committer', b"""\
1520
1: Lorem Ipsum 2005-11-22 add a
1523
def test_line_first(self):
1524
self.assertFormatterResult(log.LineLogFormatter, 'first', b"""\
1525
1: John Doe 2005-11-22 add a
1528
def test_line_all(self):
1529
self.assertFormatterResult(log.LineLogFormatter, 'all', b"""\
1530
1: John Doe, Jane Rey 2005-11-22 add a
1533
def test_short_default(self):
1534
self.assertFormatterResult(log.ShortLogFormatter, None, b"""\
1535
1 John Doe\t2005-11-22
1540
def test_short_committer(self):
1541
self.assertFormatterResult(log.ShortLogFormatter, 'committer', b"""\
1542
1 Lorem Ipsum\t2005-11-22
1547
def test_short_first(self):
1548
self.assertFormatterResult(log.ShortLogFormatter, 'first', b"""\
1549
1 John Doe\t2005-11-22
1554
def test_short_all(self):
1555
self.assertFormatterResult(log.ShortLogFormatter, 'all', b"""\
1556
1 John Doe, Jane Rey\t2005-11-22
1561
def test_long_default(self):
1562
self.assertFormatterResult(log.LongLogFormatter, None, b"""\
1563
------------------------------------------------------------
1565
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
1566
committer: Lorem Ipsum <test@example.com>
1568
timestamp: Tue 2005-11-22 00:00:00 +0000
1573
def test_long_committer(self):
1574
self.assertFormatterResult(log.LongLogFormatter, 'committer', b"""\
1575
------------------------------------------------------------
1577
committer: Lorem Ipsum <test@example.com>
1579
timestamp: Tue 2005-11-22 00:00:00 +0000
1584
def test_long_first(self):
1585
self.assertFormatterResult(log.LongLogFormatter, 'first', b"""\
1586
------------------------------------------------------------
1588
author: John Doe <jdoe@example.com>
1589
committer: Lorem Ipsum <test@example.com>
1591
timestamp: Tue 2005-11-22 00:00:00 +0000
1596
def test_long_all(self):
1597
self.assertFormatterResult(log.LongLogFormatter, 'all', b"""\
1598
------------------------------------------------------------
1600
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
1601
committer: Lorem Ipsum <test@example.com>
1603
timestamp: Tue 2005-11-22 00:00:00 +0000
1608
def test_gnu_changelog_default(self):
1609
self.assertFormatterResult(log.GnuChangelogLogFormatter, None, b"""\
1610
2005-11-22 John Doe <jdoe@example.com>
1616
def test_gnu_changelog_committer(self):
1617
self.assertFormatterResult(log.GnuChangelogLogFormatter, 'committer', b"""\
1618
2005-11-22 Lorem Ipsum <test@example.com>
1624
def test_gnu_changelog_first(self):
1625
self.assertFormatterResult(log.GnuChangelogLogFormatter, 'first', b"""\
1626
2005-11-22 John Doe <jdoe@example.com>
1632
def test_gnu_changelog_all(self):
1633
self.assertFormatterResult(log.GnuChangelogLogFormatter, 'all', b"""\
1634
2005-11-22 John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
1641
class TestLogExcludeAncestry(tests.TestCaseWithTransport):
1643
def make_branch_with_alternate_ancestries(self, relpath='.'):
1644
# See test_merge_sorted_exclude_ancestry below for the difference with
1645
# bt.per_branch.test_iter_merge_sorted_revision.
1646
# TestIterMergeSortedRevisionsBushyGraph.
1647
# make_branch_with_alternate_ancestries
1648
# and test_merge_sorted_exclude_ancestry
1649
# See the FIXME in assertLogRevnos too.
1650
builder = branchbuilder.BranchBuilder(self.get_transport(relpath))
1662
builder.start_series()
1663
builder.build_snapshot(None, [
1664
('add', ('', b'TREE_ROOT', 'directory', '')), ],
1666
builder.build_snapshot([b'1'], [], revision_id=b'1.1.1')
1667
builder.build_snapshot([b'1'], [], revision_id=b'2')
1668
builder.build_snapshot([b'1.1.1'], [], revision_id=b'1.2.1')
1669
builder.build_snapshot([b'1.1.1', b'1.2.1'], [], revision_id=b'1.1.2')
1670
builder.build_snapshot([b'2', b'1.1.2'], [], revision_id=b'3')
1671
builder.finish_series()
1672
br = builder.get_branch()
1674
self.addCleanup(br.unlock)
1677
def assertLogRevnos(self, expected_revnos, b, start, end,
1678
exclude_common_ancestry, generate_merge_revisions=True):
1679
# FIXME: the layering in log makes it hard to test intermediate levels,
1680
# I wish adding filters with their parameters was easier...
1682
iter_revs = log._calc_view_revisions(
1683
b, start, end, direction='reverse',
1684
generate_merge_revisions=generate_merge_revisions,
1685
exclude_common_ancestry=exclude_common_ancestry)
1686
self.assertEqual(expected_revnos,
1687
[revid for revid, revno, depth in iter_revs])
1689
def test_merge_sorted_exclude_ancestry(self):
1690
b = self.make_branch_with_alternate_ancestries()
1691
self.assertLogRevnos([b'3', b'1.1.2', b'1.2.1', b'1.1.1', b'2', b'1'],
1692
b, b'1', b'3', exclude_common_ancestry=False)
1693
# '2' is part of the '3' ancestry but not part of '1.1.1' ancestry so
1694
# it should be mentioned even if merge_sort order will make it appear
1696
self.assertLogRevnos([b'3', b'1.1.2', b'1.2.1', b'2'],
1697
b, b'1.1.1', b'3', exclude_common_ancestry=True)
1699
def test_merge_sorted_simple_revnos_exclude_ancestry(self):
1700
b = self.make_branch_with_alternate_ancestries()
1701
self.assertLogRevnos([b'3', b'2'],
1702
b, b'1', b'3', exclude_common_ancestry=True,
1703
generate_merge_revisions=False)
1704
self.assertLogRevnos([b'3', b'1.1.2', b'1.2.1', b'1.1.1', b'2'],
1705
b, b'1', b'3', exclude_common_ancestry=True,
1706
generate_merge_revisions=True)
1709
class TestLogDefaults(TestCaseForLogFormatter):
1710
def test_default_log_level(self):
1712
Test to ensure that specifying 'levels=1' to make_log_request_dict
1713
doesn't get overwritten when using a LogFormatter that supports more
1717
wt = self._prepare_tree_with_merges()
1720
class CustomLogFormatter(log.LogFormatter):
1721
def __init__(self, *args, **kwargs):
1722
super(CustomLogFormatter, self).__init__(*args, **kwargs)
1725
def get_levels(self):
1726
# log formatter supports all levels:
1729
def log_revision(self, revision):
1730
self.revisions.append(revision)
1732
log_formatter = LogCatcher()
1733
# First request we don't specify number of levels, we should get a
1734
# sensible default (whatever the LogFormatter handles - which in this
1735
# case is 0/everything):
1736
request = log.make_log_request_dict(limit=10)
1737
log.Logger(b, request).show(log_formatter)
1738
# should have all three revisions:
1739
self.assertEqual(len(log_formatter.revisions), 3)
1742
log_formatter = LogCatcher()
1743
# now explicitly request mainline revisions only:
1744
request = log.make_log_request_dict(limit=10, levels=1)
1745
log.Logger(b, request).show(log_formatter)
1746
# should now only have 2 revisions:
1747
self.assertEqual(len(log_formatter.revisions), 2)
275
timestamp: Wed 2005-11-23 12:08:27 +1000