1
# Copyright (C) 2005 by Canonical Ltd
1
# Copyright (C) 2005-2011 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
# GNU General Public License for more details.
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
18
from cStringIO import StringIO
20
from bzrlib.tests import BzrTestBase, TestCaseInTempDir
21
from bzrlib.log import LogFormatter, show_log, LongLogFormatter, ShortLogFormatter
22
from bzrlib.branch import Branch
23
from bzrlib.errors import InvalidRevisionNumber
25
class _LogEntry(object):
26
# should probably move into bzrlib.log?
30
class LogCatcher(LogFormatter):
31
"""Pull log messages into list rather than displaying them.
33
For ease of testing we save log messages here rather than actually
34
formatting them, so that we can precisely check the result without
35
being too dependent on the exact formatting.
37
We should also test the LogFormatter.
33
class TestLogMixin(object):
35
def wt_commit(self, wt, message, **kwargs):
36
"""Use some mostly fixed values for commits to simplify tests.
38
Tests can use this function to get some commit attributes. The time
39
stamp is incremented at each commit.
41
if getattr(self, 'timestamp', None) is None:
42
self.timestamp = 1132617600 # Mon 2005-11-22 00:00:00 +0000
44
self.timestamp += 1 # 1 second between each commit
45
kwargs.setdefault('timestamp', self.timestamp)
46
kwargs.setdefault('timezone', 0) # UTC
47
kwargs.setdefault('committer', 'Joe Foo <joe@foo.com>')
49
return wt.commit(message, **kwargs)
52
class TestCaseForLogFormatter(tests.TestCaseWithTransport, TestLogMixin):
55
super(TestCaseForLogFormatter, self).setUp()
56
# keep a reference to the "current" custom prop. handler registry
57
self.properties_handler_registry = log.properties_handler_registry
58
# Use a clean registry for log
59
log.properties_handler_registry = registry.Registry()
62
log.properties_handler_registry = self.properties_handler_registry
63
self.addCleanup(restore)
65
def assertFormatterResult(self, result, branch, formatter_class,
66
formatter_kwargs=None, show_log_kwargs=None):
67
logfile = self.make_utf8_encoded_stringio()
68
if formatter_kwargs is None:
70
formatter = formatter_class(to_file=logfile, **formatter_kwargs)
71
if show_log_kwargs is None:
73
log.show_log(branch, formatter, **show_log_kwargs)
74
self.assertEqualDiff(result, logfile.getvalue())
76
def make_standard_commit(self, branch_nick, **kwargs):
77
wt = self.make_branch_and_tree('.')
79
self.addCleanup(wt.unlock)
80
self.build_tree(['a'])
82
wt.branch.nick = branch_nick
83
kwargs.setdefault('committer', 'Lorem Ipsum <test@example.com>')
84
kwargs.setdefault('authors', ['John Doe <jdoe@example.com>'])
85
self.wt_commit(wt, 'add a', **kwargs)
88
def make_commits_with_trailing_newlines(self, wt):
89
"""Helper method for LogFormatter tests"""
92
self.build_tree_contents([('a', 'hello moto\n')])
93
self.wt_commit(wt, 'simple log message', rev_id='a1')
94
self.build_tree_contents([('b', 'goodbye\n')])
96
self.wt_commit(wt, 'multiline\nlog\nmessage\n', rev_id='a2')
98
self.build_tree_contents([('c', 'just another manic monday\n')])
100
self.wt_commit(wt, 'single line with trailing newline\n', rev_id='a3')
103
def _prepare_tree_with_merges(self, with_tags=False):
104
wt = self.make_branch_and_memory_tree('.')
106
self.addCleanup(wt.unlock)
108
self.wt_commit(wt, 'rev-1', rev_id='rev-1')
109
self.wt_commit(wt, 'rev-merged', rev_id='rev-2a')
110
wt.set_parent_ids(['rev-1', 'rev-2a'])
111
wt.branch.set_last_revision_info(1, 'rev-1')
112
self.wt_commit(wt, 'rev-2', rev_id='rev-2b')
115
branch.tags.set_tag('v0.2', 'rev-2b')
116
self.wt_commit(wt, 'rev-3', rev_id='rev-3')
117
branch.tags.set_tag('v1.0rc1', 'rev-3')
118
branch.tags.set_tag('v1.0', 'rev-3')
122
class LogCatcher(log.LogFormatter):
123
"""Pull log messages into a list rather than displaying them.
125
To simplify testing we save logged revisions here rather than actually
126
formatting anything, so that we can precisely check the result without
127
being dependent on the formatting.
40
super(LogCatcher, self).__init__(to_file=None)
44
def show(self, revno, rev, delta):
52
class SimpleLogTest(TestCaseInTempDir):
130
supports_merge_revisions = True
131
supports_delta = True
135
def __init__(self, *args, **kwargs):
136
kwargs.update(dict(to_file=None))
137
super(LogCatcher, self).__init__(*args, **kwargs)
140
def log_revision(self, revision):
141
self.revisions.append(revision)
144
class TestShowLog(tests.TestCaseWithTransport):
54
146
def checkDelta(self, delta, **kw):
55
"""Check the filenames touched by a delta are as expected."""
147
"""Check the filenames touched by a delta are as expected.
149
Caller only have to pass in the list of files for each part, all
150
unspecified parts are considered empty (and checked as such).
56
152
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
153
# By default we expect an empty list
57
154
expected = kw.get(n, [])
59
# tests are written with unix paths; fix them up for windows
61
# expected = [x.replace('/', os.sep) for x in expected]
63
155
# strip out only the path components
64
156
got = [x[0] for x in getattr(delta, n)]
65
self.assertEquals(expected, got)
67
def test_cur_revno(self):
68
b = Branch(u'.', init=True)
71
b.working_tree().commit('empty commit')
72
show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
73
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
74
start_revision=2, end_revision=1)
75
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
76
start_revision=1, end_revision=2)
77
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
78
start_revision=0, end_revision=2)
79
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
80
start_revision=1, end_revision=0)
81
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
82
start_revision=-1, end_revision=1)
83
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
84
start_revision=1, end_revision=-1)
86
def test_cur_revno(self):
87
b = Branch.initialize(u'.')
90
b.working_tree().commit('empty commit')
91
show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
92
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
93
start_revision=2, end_revision=1)
94
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
95
start_revision=1, end_revision=2)
96
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
97
start_revision=0, end_revision=2)
98
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
99
start_revision=1, end_revision=0)
100
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
101
start_revision=-1, end_revision=1)
102
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
103
start_revision=1, end_revision=-1)
105
def test_simple_log(self):
106
eq = self.assertEquals
108
b = Branch.initialize(u'.')
157
self.assertEqual(expected, got)
159
def assertInvalidRevisonNumber(self, br, start, end):
161
self.assertRaises(errors.InvalidRevisionNumber,
162
log.show_log, br, lf,
163
start_revision=start, end_revision=end)
165
def test_cur_revno(self):
166
wt = self.make_branch_and_tree('.')
170
wt.commit('empty commit')
171
log.show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
173
# Since there is a single revision in the branch all the combinations
175
self.assertInvalidRevisonNumber(b, 2, 1)
176
self.assertInvalidRevisonNumber(b, 1, 2)
177
self.assertInvalidRevisonNumber(b, 0, 2)
178
self.assertInvalidRevisonNumber(b, 1, 0)
179
self.assertInvalidRevisonNumber(b, -1, 1)
180
self.assertInvalidRevisonNumber(b, 1, -1)
182
def test_empty_branch(self):
183
wt = self.make_branch_and_tree('.')
186
log.show_log(wt.branch, lf)
115
b.working_tree().commit('empty commit')
188
self.assertEqual([], lf.revisions)
190
def test_empty_commit(self):
191
wt = self.make_branch_and_tree('.')
193
wt.commit('empty commit')
116
194
lf = LogCatcher()
117
show_log(b, lf, verbose=True)
119
eq(lf.logs[0].revno, 1)
120
eq(lf.logs[0].rev.message, 'empty commit')
122
self.log('log delta: %r' % d)
195
log.show_log(wt.branch, lf, verbose=True)
197
self.assertEqual(1, len(revs))
198
self.assertEqual('1', revs[0].revno)
199
self.assertEqual('empty commit', revs[0].rev.message)
200
self.checkDelta(revs[0].delta)
202
def test_simple_commit(self):
203
wt = self.make_branch_and_tree('.')
204
wt.commit('empty commit')
125
205
self.build_tree(['hello'])
126
b.working_tree().add('hello')
127
b.working_tree().commit('add one file')
130
# log using regular thing
131
show_log(b, LongLogFormatter(lf))
133
for l in lf.readlines():
136
# get log as data structure
207
wt.commit('add one file',
208
committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
209
u'<test@example.com>')
137
210
lf = LogCatcher()
138
show_log(b, lf, verbose=True)
140
self.log('log entries:')
141
for logentry in lf.logs:
142
self.log('%4d %s' % (logentry.revno, logentry.rev.message))
211
log.show_log(wt.branch, lf, verbose=True)
212
self.assertEqual(2, len(lf.revisions))
144
213
# first one is most recent
145
logentry = lf.logs[0]
146
eq(logentry.revno, 2)
147
eq(logentry.rev.message, 'add one file')
149
self.log('log 2 delta: %r' % d)
150
# self.checkDelta(d, added=['hello'])
152
# commit a log message with control characters
153
msg = "All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
154
self.log("original commit message: %r", msg)
155
b.working_tree().commit(msg)
214
log_entry = lf.revisions[0]
215
self.assertEqual('2', log_entry.revno)
216
self.assertEqual('add one file', log_entry.rev.message)
217
self.checkDelta(log_entry.delta, added=['hello'])
219
def test_commit_message_with_control_chars(self):
220
wt = self.make_branch_and_tree('.')
221
msg = u"All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
222
msg = msg.replace(u'\r', u'\n')
156
224
lf = LogCatcher()
157
show_log(b, lf, verbose=True)
158
committed_msg = lf.logs[0].rev.message
159
self.log("escaped commit message: %r", committed_msg)
160
self.assert_(msg != committed_msg)
161
self.assert_(len(committed_msg) > len(msg))
225
log.show_log(wt.branch, lf, verbose=True)
226
committed_msg = lf.revisions[0].rev.message
227
if wt.branch.repository._serializer.squashes_xml_invalid_characters:
228
self.assertNotEqual(msg, committed_msg)
229
self.assertTrue(len(committed_msg) > len(msg))
231
self.assertEqual(msg, committed_msg)
163
# Check that log message with only XML-valid characters isn't
233
def test_commit_message_without_control_chars(self):
234
wt = self.make_branch_and_tree('.')
164
235
# escaped. As ElementTree apparently does some kind of
165
236
# newline conversion, neither LF (\x0A) nor CR (\x0D) are
166
237
# included in the test commit message, even though they are
167
238
# valid XML 1.0 characters.
168
239
msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
169
self.log("original commit message: %r", msg)
170
b.working_tree().commit(msg)
172
show_log(b, lf, verbose=True)
173
committed_msg = lf.logs[0].rev.message
174
self.log("escaped commit message: %r", committed_msg)
175
self.assert_(msg == committed_msg)
242
log.show_log(wt.branch, lf, verbose=True)
243
committed_msg = lf.revisions[0].rev.message
244
self.assertEqual(msg, committed_msg)
246
def test_deltas_in_merge_revisions(self):
247
"""Check deltas created for both mainline and merge revisions"""
248
wt = self.make_branch_and_tree('parent')
249
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
252
wt.commit(message='add file1 and file2')
253
self.run_bzr('branch parent child')
254
os.unlink('child/file1')
255
with file('child/file2', 'wb') as f: f.write('hello\n')
256
self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
259
self.run_bzr('merge ../child')
260
wt.commit('merge child branch')
264
lf.supports_merge_revisions = True
265
log.show_log(b, lf, verbose=True)
268
self.assertEqual(3, len(revs))
271
self.assertEqual('2', logentry.revno)
272
self.assertEqual('merge child branch', logentry.rev.message)
273
self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
276
self.assertEqual('1.1.1', logentry.revno)
277
self.assertEqual('remove file1 and modify file2', logentry.rev.message)
278
self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
281
self.assertEqual('1', logentry.revno)
282
self.assertEqual('add file1 and file2', logentry.rev.message)
283
self.checkDelta(logentry.delta, added=['file1', 'file2'])
286
class TestFormatSignatureValidity(tests.TestCaseWithTransport):
287
class UTFLoopbackGPGStrategy(gpg.LoopbackGPGStrategy):
288
def verify(self, content, testament):
289
return (gpg.SIGNATURE_VALID,
290
u'UTF8 Test \xa1\xb1\xc1\xd1\xe1\xf1 <jrandom@example.com>')
292
def has_signature_for_revision_id(self, revision_id):
295
def get_signature_text(self, revision_id):
298
def test_format_signature_validity_utf(self):
299
"""Check that GPG signatures containing UTF-8 names are formatted
301
# Monkey patch to use our UTF-8 generating GPGStrategy
302
self.overrideAttr(gpg, 'GPGStrategy', self.UTFLoopbackGPGStrategy)
303
wt = self.make_branch_and_tree('.')
304
revid = wt.commit('empty commit')
305
repo = wt.branch.repository
306
# Monkey patch out checking if this rev is actually signed, since we
307
# can't sign it without a heavier TestCase and LoopbackGPGStrategy
308
# doesn't care anyways.
309
self.overrideAttr(repo, 'has_signature_for_revision_id',
310
self.has_signature_for_revision_id)
311
self.overrideAttr(repo, 'get_signature_text', self.get_signature_text)
312
out = log.format_signature_validity(revid, repo)
314
u'valid signature from UTF8 Test \xa1\xb1\xc1\xd1\xe1\xf1 <jrandom@example.com>',
318
class TestShortLogFormatter(TestCaseForLogFormatter):
177
320
def test_trailing_newlines(self):
178
b = Branch.initialize(u'.')
180
wt = b.working_tree()
181
open('a', 'wb').write('hello moto\n')
183
wt.commit('simple log message', rev_id='a1'
184
, timestamp=1132586655.459960938, timezone=-6*3600
185
, committer='Joe Foo <joe@foo.com>')
186
open('b', 'wb').write('goodbye\n')
188
wt.commit('multiline\nlog\nmessage\n', rev_id='a2'
189
, timestamp=1132586842.411175966, timezone=-6*3600
190
, committer='Joe Foo <joe@foo.com>')
192
open('c', 'wb').write('just another manic monday\n')
194
wt.commit('single line with trailing newline\n', rev_id='a3'
195
, timestamp=1132587176.835228920, timezone=-6*3600
196
, committer = 'Joe Foo <joe@foo.com>')
199
lf = ShortLogFormatter(to_file=sio)
201
self.assertEquals(sio.getvalue(), """\
202
3 Joe Foo\t2005-11-21
321
wt = self.make_branch_and_tree('.')
322
b = self.make_commits_with_trailing_newlines(wt)
323
self.assertFormatterResult("""\
324
3 Joe Foo\t2005-11-22
203
325
single line with trailing newline
205
2 Joe Foo\t2005-11-21
327
2 Joe Foo\t2005-11-22
210
1 Joe Foo\t2005-11-21
332
1 Joe Foo\t2005-11-22
211
333
simple log message
216
lf = LongLogFormatter(to_file=sio)
218
self.assertEquals(sio.getvalue(), """\
219
------------------------------------------------------------
221
committer: Joe Foo <joe@foo.com>
223
timestamp: Mon 2005-11-21 09:32:56 -0600
225
single line with trailing newline
226
------------------------------------------------------------
228
committer: Joe Foo <joe@foo.com>
230
timestamp: Mon 2005-11-21 09:27:22 -0600
235
------------------------------------------------------------
237
committer: Joe Foo <joe@foo.com>
239
timestamp: Mon 2005-11-21 09:24:15 -0600
336
b, log.ShortLogFormatter)
338
def test_short_log_with_merges(self):
339
wt = self._prepare_tree_with_merges()
340
self.assertFormatterResult("""\
341
2 Joe Foo\t2005-11-22 [merge]
344
1 Joe Foo\t2005-11-22
348
wt.branch, log.ShortLogFormatter)
350
def test_short_log_with_merges_and_advice(self):
351
wt = self._prepare_tree_with_merges()
352
self.assertFormatterResult("""\
353
2 Joe Foo\t2005-11-22 [merge]
356
1 Joe Foo\t2005-11-22
359
Use --include-merged or -n0 to see merged revisions.
361
wt.branch, log.ShortLogFormatter,
362
formatter_kwargs=dict(show_advice=True))
364
def test_short_log_with_merges_and_range(self):
365
wt = self._prepare_tree_with_merges()
366
self.wt_commit(wt, 'rev-3a', rev_id='rev-3a')
367
wt.branch.set_last_revision_info(2, 'rev-2b')
368
wt.set_parent_ids(['rev-2b', 'rev-3a'])
369
self.wt_commit(wt, 'rev-3b', rev_id='rev-3b')
370
self.assertFormatterResult("""\
371
3 Joe Foo\t2005-11-22 [merge]
374
2 Joe Foo\t2005-11-22 [merge]
378
wt.branch, log.ShortLogFormatter,
379
show_log_kwargs=dict(start_revision=2, end_revision=3))
381
def test_short_log_with_tags(self):
382
wt = self._prepare_tree_with_merges(with_tags=True)
383
self.assertFormatterResult("""\
384
3 Joe Foo\t2005-11-22 {v1.0, v1.0rc1}
387
2 Joe Foo\t2005-11-22 {v0.2} [merge]
390
1 Joe Foo\t2005-11-22
394
wt.branch, log.ShortLogFormatter)
396
def test_short_log_single_merge_revision(self):
397
wt = self._prepare_tree_with_merges()
398
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
399
rev = revspec.in_history(wt.branch)
400
self.assertFormatterResult("""\
401
1.1.1 Joe Foo\t2005-11-22
405
wt.branch, log.ShortLogFormatter,
406
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
408
def test_show_ids(self):
409
wt = self.make_branch_and_tree('parent')
410
self.build_tree(['parent/f1', 'parent/f2'])
412
self.wt_commit(wt, 'first post', rev_id='a')
413
child_wt = wt.bzrdir.sprout('child').open_workingtree()
414
self.wt_commit(child_wt, 'branch 1 changes', rev_id='b')
415
wt.merge_from_branch(child_wt.branch)
416
self.wt_commit(wt, 'merge branch 1', rev_id='c')
417
self.assertFormatterResult("""\
418
2 Joe Foo\t2005-11-22 [merge]
422
1.1.1 Joe Foo\t2005-11-22
426
1 Joe Foo\t2005-11-22
431
wt.branch, log.ShortLogFormatter,
432
formatter_kwargs=dict(levels=0,show_ids=True))
435
class TestShortLogFormatterWithMergeRevisions(TestCaseForLogFormatter):
437
def test_short_merge_revs_log_with_merges(self):
438
wt = self._prepare_tree_with_merges()
439
# Note that the 1.1.1 indenting is in fact correct given that
440
# the revision numbers are right justified within 5 characters
441
# for mainline revnos and 9 characters for dotted revnos.
442
self.assertFormatterResult("""\
443
2 Joe Foo\t2005-11-22 [merge]
446
1.1.1 Joe Foo\t2005-11-22
449
1 Joe Foo\t2005-11-22
453
wt.branch, log.ShortLogFormatter,
454
formatter_kwargs=dict(levels=0))
456
def test_short_merge_revs_log_single_merge_revision(self):
457
wt = self._prepare_tree_with_merges()
458
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
459
rev = revspec.in_history(wt.branch)
460
self.assertFormatterResult("""\
461
1.1.1 Joe Foo\t2005-11-22
465
wt.branch, log.ShortLogFormatter,
466
formatter_kwargs=dict(levels=0),
467
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
470
class TestLongLogFormatter(TestCaseForLogFormatter):
244
472
def test_verbose_log(self):
245
473
"""Verbose log includes changed files
249
b = Branch.initialize(u'.')
250
self.build_tree(['a'])
251
wt = b.working_tree()
253
# XXX: why does a longer nick show up?
254
b.nick = 'test_verbose_log'
255
wt.commit(message='add a',
256
timestamp=1132711707,
258
committer='Lorem Ipsum <test@example.com>')
259
logfile = file('out.tmp', 'w+')
260
formatter = LongLogFormatter(to_file=logfile)
261
show_log(b, formatter, verbose=True)
264
log_contents = logfile.read()
265
self.assertEqualDiff(log_contents, '''\
477
wt = self.make_standard_commit('test_verbose_log', authors=[])
478
self.assertFormatterResult('''\
266
479
------------------------------------------------------------
268
481
committer: Lorem Ipsum <test@example.com>
269
482
branch nick: test_verbose_log
270
timestamp: Wed 2005-11-23 12:08:27 +1000
483
timestamp: Tue 2005-11-22 00:00:00 +0000
489
wt.branch, log.LongLogFormatter,
490
show_log_kwargs=dict(verbose=True))
492
def test_merges_are_indented_by_level(self):
493
wt = self.make_branch_and_tree('parent')
494
self.wt_commit(wt, 'first post')
495
child_wt = wt.bzrdir.sprout('child').open_workingtree()
496
self.wt_commit(child_wt, 'branch 1')
497
smallerchild_wt = wt.bzrdir.sprout('smallerchild').open_workingtree()
498
self.wt_commit(smallerchild_wt, 'branch 2')
499
child_wt.merge_from_branch(smallerchild_wt.branch)
500
self.wt_commit(child_wt, 'merge branch 2')
501
wt.merge_from_branch(child_wt.branch)
502
self.wt_commit(wt, 'merge branch 1')
503
self.assertFormatterResult("""\
504
------------------------------------------------------------
506
committer: Joe Foo <joe@foo.com>
508
timestamp: Tue 2005-11-22 00:00:04 +0000
511
------------------------------------------------------------
513
committer: Joe Foo <joe@foo.com>
515
timestamp: Tue 2005-11-22 00:00:03 +0000
518
------------------------------------------------------------
520
committer: Joe Foo <joe@foo.com>
521
branch nick: smallerchild
522
timestamp: Tue 2005-11-22 00:00:02 +0000
525
------------------------------------------------------------
527
committer: Joe Foo <joe@foo.com>
529
timestamp: Tue 2005-11-22 00:00:01 +0000
532
------------------------------------------------------------
534
committer: Joe Foo <joe@foo.com>
536
timestamp: Tue 2005-11-22 00:00:00 +0000
540
wt.branch, log.LongLogFormatter,
541
formatter_kwargs=dict(levels=0),
542
show_log_kwargs=dict(verbose=True))
544
def test_verbose_merge_revisions_contain_deltas(self):
545
wt = self.make_branch_and_tree('parent')
546
self.build_tree(['parent/f1', 'parent/f2'])
548
self.wt_commit(wt, 'first post')
549
child_wt = wt.bzrdir.sprout('child').open_workingtree()
550
os.unlink('child/f1')
551
self.build_tree_contents([('child/f2', 'hello\n')])
552
self.wt_commit(child_wt, 'removed f1 and modified f2')
553
wt.merge_from_branch(child_wt.branch)
554
self.wt_commit(wt, 'merge branch 1')
555
self.assertFormatterResult("""\
556
------------------------------------------------------------
558
committer: Joe Foo <joe@foo.com>
560
timestamp: Tue 2005-11-22 00:00:02 +0000
567
------------------------------------------------------------
569
committer: Joe Foo <joe@foo.com>
571
timestamp: Tue 2005-11-22 00:00:01 +0000
573
removed f1 and modified f2
578
------------------------------------------------------------
580
committer: Joe Foo <joe@foo.com>
582
timestamp: Tue 2005-11-22 00:00:00 +0000
589
wt.branch, log.LongLogFormatter,
590
formatter_kwargs=dict(levels=0),
591
show_log_kwargs=dict(verbose=True))
593
def test_trailing_newlines(self):
594
wt = self.make_branch_and_tree('.')
595
b = self.make_commits_with_trailing_newlines(wt)
596
self.assertFormatterResult("""\
597
------------------------------------------------------------
599
committer: Joe Foo <joe@foo.com>
601
timestamp: Tue 2005-11-22 00:00:02 +0000
603
single line with trailing newline
604
------------------------------------------------------------
606
committer: Joe Foo <joe@foo.com>
608
timestamp: Tue 2005-11-22 00:00:01 +0000
613
------------------------------------------------------------
615
committer: Joe Foo <joe@foo.com>
617
timestamp: Tue 2005-11-22 00:00:00 +0000
621
b, log.LongLogFormatter)
623
def test_author_in_log(self):
624
"""Log includes the author name if it's set in
625
the revision properties
627
wt = self.make_standard_commit('test_author_log',
628
authors=['John Doe <jdoe@example.com>',
629
'Jane Rey <jrey@example.com>'])
630
self.assertFormatterResult("""\
631
------------------------------------------------------------
633
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
634
committer: Lorem Ipsum <test@example.com>
635
branch nick: test_author_log
636
timestamp: Tue 2005-11-22 00:00:00 +0000
640
wt.branch, log.LongLogFormatter)
642
def test_properties_in_log(self):
643
"""Log includes the custom properties returned by the registered
646
wt = self.make_standard_commit('test_properties_in_log')
647
def trivial_custom_prop_handler(revision):
648
return {'test_prop':'test_value'}
650
# Cleaned up in setUp()
651
log.properties_handler_registry.register(
652
'trivial_custom_prop_handler',
653
trivial_custom_prop_handler)
654
self.assertFormatterResult("""\
655
------------------------------------------------------------
657
test_prop: test_value
658
author: John Doe <jdoe@example.com>
659
committer: Lorem Ipsum <test@example.com>
660
branch nick: test_properties_in_log
661
timestamp: Tue 2005-11-22 00:00:00 +0000
665
wt.branch, log.LongLogFormatter)
667
def test_properties_in_short_log(self):
668
"""Log includes the custom properties returned by the registered
671
wt = self.make_standard_commit('test_properties_in_short_log')
672
def trivial_custom_prop_handler(revision):
673
return {'test_prop':'test_value'}
675
log.properties_handler_registry.register(
676
'trivial_custom_prop_handler',
677
trivial_custom_prop_handler)
678
self.assertFormatterResult("""\
679
1 John Doe\t2005-11-22
680
test_prop: test_value
684
wt.branch, log.ShortLogFormatter)
686
def test_error_in_properties_handler(self):
687
"""Log includes the custom properties returned by the registered
690
wt = self.make_standard_commit('error_in_properties_handler',
691
revprops={'first_prop':'first_value'})
692
sio = self.make_utf8_encoded_stringio()
693
formatter = log.LongLogFormatter(to_file=sio)
694
def trivial_custom_prop_handler(revision):
695
raise StandardError("a test error")
697
log.properties_handler_registry.register(
698
'trivial_custom_prop_handler',
699
trivial_custom_prop_handler)
700
self.assertRaises(StandardError, log.show_log, wt.branch, formatter,)
702
def test_properties_handler_bad_argument(self):
703
wt = self.make_standard_commit('bad_argument',
704
revprops={'a_prop':'test_value'})
705
sio = self.make_utf8_encoded_stringio()
706
formatter = log.LongLogFormatter(to_file=sio)
707
def bad_argument_prop_handler(revision):
708
return {'custom_prop_name':revision.properties['a_prop']}
710
log.properties_handler_registry.register(
711
'bad_argument_prop_handler',
712
bad_argument_prop_handler)
714
self.assertRaises(AttributeError, formatter.show_properties,
717
revision = wt.branch.repository.get_revision(wt.branch.last_revision())
718
formatter.show_properties(revision, '')
719
self.assertEqualDiff('''custom_prop_name: test_value\n''',
722
def test_show_ids(self):
723
wt = self.make_branch_and_tree('parent')
724
self.build_tree(['parent/f1', 'parent/f2'])
726
self.wt_commit(wt, 'first post', rev_id='a')
727
child_wt = wt.bzrdir.sprout('child').open_workingtree()
728
self.wt_commit(child_wt, 'branch 1 changes', rev_id='b')
729
wt.merge_from_branch(child_wt.branch)
730
self.wt_commit(wt, 'merge branch 1', rev_id='c')
731
self.assertFormatterResult("""\
732
------------------------------------------------------------
737
committer: Joe Foo <joe@foo.com>
739
timestamp: Tue 2005-11-22 00:00:02 +0000
742
------------------------------------------------------------
746
committer: Joe Foo <joe@foo.com>
748
timestamp: Tue 2005-11-22 00:00:01 +0000
751
------------------------------------------------------------
754
committer: Joe Foo <joe@foo.com>
756
timestamp: Tue 2005-11-22 00:00:00 +0000
760
wt.branch, log.LongLogFormatter,
761
formatter_kwargs=dict(levels=0,show_ids=True))
764
class TestLongLogFormatterWithoutMergeRevisions(TestCaseForLogFormatter):
766
def test_long_verbose_log(self):
767
"""Verbose log includes changed files
771
wt = self.make_standard_commit('test_long_verbose_log', authors=[])
772
self.assertFormatterResult("""\
773
------------------------------------------------------------
775
committer: Lorem Ipsum <test@example.com>
776
branch nick: test_long_verbose_log
777
timestamp: Tue 2005-11-22 00:00:00 +0000
783
wt.branch, log.LongLogFormatter,
784
formatter_kwargs=dict(levels=1),
785
show_log_kwargs=dict(verbose=True))
787
def test_long_verbose_contain_deltas(self):
788
wt = self.make_branch_and_tree('parent')
789
self.build_tree(['parent/f1', 'parent/f2'])
791
self.wt_commit(wt, 'first post')
792
child_wt = wt.bzrdir.sprout('child').open_workingtree()
793
os.unlink('child/f1')
794
self.build_tree_contents([('child/f2', 'hello\n')])
795
self.wt_commit(child_wt, 'removed f1 and modified f2')
796
wt.merge_from_branch(child_wt.branch)
797
self.wt_commit(wt, 'merge branch 1')
798
self.assertFormatterResult("""\
799
------------------------------------------------------------
801
committer: Joe Foo <joe@foo.com>
803
timestamp: Tue 2005-11-22 00:00:02 +0000
810
------------------------------------------------------------
812
committer: Joe Foo <joe@foo.com>
814
timestamp: Tue 2005-11-22 00:00:00 +0000
821
wt.branch, log.LongLogFormatter,
822
formatter_kwargs=dict(levels=1),
823
show_log_kwargs=dict(verbose=True))
825
def test_long_trailing_newlines(self):
826
wt = self.make_branch_and_tree('.')
827
b = self.make_commits_with_trailing_newlines(wt)
828
self.assertFormatterResult("""\
829
------------------------------------------------------------
831
committer: Joe Foo <joe@foo.com>
833
timestamp: Tue 2005-11-22 00:00:02 +0000
835
single line with trailing newline
836
------------------------------------------------------------
838
committer: Joe Foo <joe@foo.com>
840
timestamp: Tue 2005-11-22 00:00:01 +0000
845
------------------------------------------------------------
847
committer: Joe Foo <joe@foo.com>
849
timestamp: Tue 2005-11-22 00:00:00 +0000
853
b, log.LongLogFormatter,
854
formatter_kwargs=dict(levels=1))
856
def test_long_author_in_log(self):
857
"""Log includes the author name if it's set in
858
the revision properties
860
wt = self.make_standard_commit('test_author_log')
861
self.assertFormatterResult("""\
862
------------------------------------------------------------
864
author: John Doe <jdoe@example.com>
865
committer: Lorem Ipsum <test@example.com>
866
branch nick: test_author_log
867
timestamp: Tue 2005-11-22 00:00:00 +0000
871
wt.branch, log.LongLogFormatter,
872
formatter_kwargs=dict(levels=1))
874
def test_long_properties_in_log(self):
875
"""Log includes the custom properties returned by the registered
878
wt = self.make_standard_commit('test_properties_in_log')
879
def trivial_custom_prop_handler(revision):
880
return {'test_prop':'test_value'}
882
log.properties_handler_registry.register(
883
'trivial_custom_prop_handler',
884
trivial_custom_prop_handler)
885
self.assertFormatterResult("""\
886
------------------------------------------------------------
888
test_prop: test_value
889
author: John Doe <jdoe@example.com>
890
committer: Lorem Ipsum <test@example.com>
891
branch nick: test_properties_in_log
892
timestamp: Tue 2005-11-22 00:00:00 +0000
896
wt.branch, log.LongLogFormatter,
897
formatter_kwargs=dict(levels=1))
900
class TestLineLogFormatter(TestCaseForLogFormatter):
902
def test_line_log(self):
903
"""Line log should show revno
907
wt = self.make_standard_commit('test-line-log',
908
committer='Line-Log-Formatter Tester <test@line.log>',
910
self.assertFormatterResult("""\
911
1: Line-Log-Formatte... 2005-11-22 add a
913
wt.branch, log.LineLogFormatter)
915
def test_trailing_newlines(self):
916
wt = self.make_branch_and_tree('.')
917
b = self.make_commits_with_trailing_newlines(wt)
918
self.assertFormatterResult("""\
919
3: Joe Foo 2005-11-22 single line with trailing newline
920
2: Joe Foo 2005-11-22 multiline
921
1: Joe Foo 2005-11-22 simple log message
923
b, log.LineLogFormatter)
925
def test_line_log_single_merge_revision(self):
926
wt = self._prepare_tree_with_merges()
927
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
928
rev = revspec.in_history(wt.branch)
929
self.assertFormatterResult("""\
930
1.1.1: Joe Foo 2005-11-22 rev-merged
932
wt.branch, log.LineLogFormatter,
933
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
935
def test_line_log_with_tags(self):
936
wt = self._prepare_tree_with_merges(with_tags=True)
937
self.assertFormatterResult("""\
938
3: Joe Foo 2005-11-22 {v1.0, v1.0rc1} rev-3
939
2: Joe Foo 2005-11-22 [merge] {v0.2} rev-2
940
1: Joe Foo 2005-11-22 rev-1
942
wt.branch, log.LineLogFormatter)
945
class TestLineLogFormatterWithMergeRevisions(TestCaseForLogFormatter):
947
def test_line_merge_revs_log(self):
948
"""Line log should show revno
952
wt = self.make_standard_commit('test-line-log',
953
committer='Line-Log-Formatter Tester <test@line.log>',
955
self.assertFormatterResult("""\
956
1: Line-Log-Formatte... 2005-11-22 add a
958
wt.branch, log.LineLogFormatter)
960
def test_line_merge_revs_log_single_merge_revision(self):
961
wt = self._prepare_tree_with_merges()
962
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
963
rev = revspec.in_history(wt.branch)
964
self.assertFormatterResult("""\
965
1.1.1: Joe Foo 2005-11-22 rev-merged
967
wt.branch, log.LineLogFormatter,
968
formatter_kwargs=dict(levels=0),
969
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
971
def test_line_merge_revs_log_with_merges(self):
972
wt = self._prepare_tree_with_merges()
973
self.assertFormatterResult("""\
974
2: Joe Foo 2005-11-22 [merge] rev-2
975
1.1.1: Joe Foo 2005-11-22 rev-merged
976
1: Joe Foo 2005-11-22 rev-1
978
wt.branch, log.LineLogFormatter,
979
formatter_kwargs=dict(levels=0))
982
class TestGnuChangelogFormatter(TestCaseForLogFormatter):
984
def test_gnu_changelog(self):
985
wt = self.make_standard_commit('nicky', authors=[])
986
self.assertFormatterResult('''\
987
2005-11-22 Lorem Ipsum <test@example.com>
992
wt.branch, log.GnuChangelogLogFormatter)
994
def test_with_authors(self):
995
wt = self.make_standard_commit('nicky',
996
authors=['Fooa Fooz <foo@example.com>',
997
'Bari Baro <bar@example.com>'])
998
self.assertFormatterResult('''\
999
2005-11-22 Fooa Fooz <foo@example.com>
1004
wt.branch, log.GnuChangelogLogFormatter)
1006
def test_verbose(self):
1007
wt = self.make_standard_commit('nicky')
1008
self.assertFormatterResult('''\
1009
2005-11-22 John Doe <jdoe@example.com>
1016
wt.branch, log.GnuChangelogLogFormatter,
1017
show_log_kwargs=dict(verbose=True))
1020
class TestShowChangedRevisions(tests.TestCaseWithTransport):
1022
def test_show_changed_revisions_verbose(self):
1023
tree = self.make_branch_and_tree('tree_a')
1024
self.build_tree(['tree_a/foo'])
1026
tree.commit('bar', rev_id='bar-id')
1027
s = self.make_utf8_encoded_stringio()
1028
log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
1029
self.assertContainsRe(s.getvalue(), 'bar')
1030
self.assertNotContainsRe(s.getvalue(), 'foo')
1033
class TestLogFormatter(tests.TestCase):
1036
super(TestLogFormatter, self).setUp()
1037
self.rev = revision.Revision('a-id')
1038
self.lf = log.LogFormatter(None)
1040
def test_short_committer(self):
1041
def assertCommitter(expected, committer):
1042
self.rev.committer = committer
1043
self.assertEqual(expected, self.lf.short_committer(self.rev))
1045
assertCommitter('John Doe', 'John Doe <jdoe@example.com>')
1046
assertCommitter('John Smith', 'John Smith <jsmith@example.com>')
1047
assertCommitter('John Smith', 'John Smith')
1048
assertCommitter('jsmith@example.com', 'jsmith@example.com')
1049
assertCommitter('jsmith@example.com', '<jsmith@example.com>')
1050
assertCommitter('John Smith', 'John Smith jsmith@example.com')
1052
def test_short_author(self):
1053
def assertAuthor(expected, author):
1054
self.rev.properties['author'] = author
1055
self.assertEqual(expected, self.lf.short_author(self.rev))
1057
assertAuthor('John Smith', 'John Smith <jsmith@example.com>')
1058
assertAuthor('John Smith', 'John Smith')
1059
assertAuthor('jsmith@example.com', 'jsmith@example.com')
1060
assertAuthor('jsmith@example.com', '<jsmith@example.com>')
1061
assertAuthor('John Smith', 'John Smith jsmith@example.com')
1063
def test_short_author_from_committer(self):
1064
self.rev.committer = 'John Doe <jdoe@example.com>'
1065
self.assertEqual('John Doe', self.lf.short_author(self.rev))
1067
def test_short_author_from_authors(self):
1068
self.rev.properties['authors'] = ('John Smith <jsmith@example.com>\n'
1069
'Jane Rey <jrey@example.com>')
1070
self.assertEqual('John Smith', self.lf.short_author(self.rev))
1073
class TestReverseByDepth(tests.TestCase):
1074
"""Test reverse_by_depth behavior.
1076
This is used to present revisions in forward (oldest first) order in a nice
1079
The tests use lighter revision description to ease reading.
1082
def assertReversed(self, forward, backward):
1083
# Transform the descriptions to suit the API: tests use (revno, depth),
1084
# while the API expects (revid, revno, depth)
1085
def complete_revisions(l):
1086
"""Transform the description to suit the API.
1088
Tests use (revno, depth) whil the API expects (revid, revno, depth).
1089
Since the revid is arbitrary, we just duplicate revno
1091
return [ (r, r, d) for r, d in l]
1092
forward = complete_revisions(forward)
1093
backward= complete_revisions(backward)
1094
self.assertEqual(forward, log.reverse_by_depth(backward))
1097
def test_mainline_revisions(self):
1098
self.assertReversed([( '1', 0), ('2', 0)],
1099
[('2', 0), ('1', 0)])
1101
def test_merged_revisions(self):
1102
self.assertReversed([('1', 0), ('2', 0), ('2.2', 1), ('2.1', 1),],
1103
[('2', 0), ('2.1', 1), ('2.2', 1), ('1', 0),])
1104
def test_shifted_merged_revisions(self):
1105
"""Test irregular layout.
1107
Requesting revisions touching a file can produce "holes" in the depths.
1109
self.assertReversed([('1', 0), ('2', 0), ('1.1', 2), ('1.2', 2),],
1110
[('2', 0), ('1.2', 2), ('1.1', 2), ('1', 0),])
1112
def test_merged_without_child_revisions(self):
1113
"""Test irregular layout.
1115
Revision ranges can produce "holes" in the depths.
1117
# When a revision of higher depth doesn't follow one of lower depth, we
1118
# assume a lower depth one is virtually there
1119
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1120
[('4', 4), ('3', 3), ('2', 2), ('1', 2),])
1121
# So we get the same order after reversing below even if the original
1122
# revisions are not in the same order.
1123
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1124
[('3', 3), ('4', 4), ('2', 2), ('1', 2),])
1127
class TestHistoryChange(tests.TestCaseWithTransport):
1129
def setup_a_tree(self):
1130
tree = self.make_branch_and_tree('tree')
1132
self.addCleanup(tree.unlock)
1133
tree.commit('1a', rev_id='1a')
1134
tree.commit('2a', rev_id='2a')
1135
tree.commit('3a', rev_id='3a')
1138
def setup_ab_tree(self):
1139
tree = self.setup_a_tree()
1140
tree.set_last_revision('1a')
1141
tree.branch.set_last_revision_info(1, '1a')
1142
tree.commit('2b', rev_id='2b')
1143
tree.commit('3b', rev_id='3b')
1146
def setup_ac_tree(self):
1147
tree = self.setup_a_tree()
1148
tree.set_last_revision(revision.NULL_REVISION)
1149
tree.branch.set_last_revision_info(0, revision.NULL_REVISION)
1150
tree.commit('1c', rev_id='1c')
1151
tree.commit('2c', rev_id='2c')
1152
tree.commit('3c', rev_id='3c')
1155
def test_all_new(self):
1156
tree = self.setup_ab_tree()
1157
old, new = log.get_history_change('1a', '3a', tree.branch.repository)
1158
self.assertEqual([], old)
1159
self.assertEqual(['2a', '3a'], new)
1161
def test_all_old(self):
1162
tree = self.setup_ab_tree()
1163
old, new = log.get_history_change('3a', '1a', tree.branch.repository)
1164
self.assertEqual([], new)
1165
self.assertEqual(['2a', '3a'], old)
1167
def test_null_old(self):
1168
tree = self.setup_ab_tree()
1169
old, new = log.get_history_change(revision.NULL_REVISION,
1170
'3a', tree.branch.repository)
1171
self.assertEqual([], old)
1172
self.assertEqual(['1a', '2a', '3a'], new)
1174
def test_null_new(self):
1175
tree = self.setup_ab_tree()
1176
old, new = log.get_history_change('3a', revision.NULL_REVISION,
1177
tree.branch.repository)
1178
self.assertEqual([], new)
1179
self.assertEqual(['1a', '2a', '3a'], old)
1181
def test_diverged(self):
1182
tree = self.setup_ab_tree()
1183
old, new = log.get_history_change('3a', '3b', tree.branch.repository)
1184
self.assertEqual(old, ['2a', '3a'])
1185
self.assertEqual(new, ['2b', '3b'])
1187
def test_unrelated(self):
1188
tree = self.setup_ac_tree()
1189
old, new = log.get_history_change('3a', '3c', tree.branch.repository)
1190
self.assertEqual(old, ['1a', '2a', '3a'])
1191
self.assertEqual(new, ['1c', '2c', '3c'])
1193
def test_show_branch_change(self):
1194
tree = self.setup_ab_tree()
1196
log.show_branch_change(tree.branch, s, 3, '3a')
1197
self.assertContainsRe(s.getvalue(),
1198
'[*]{60}\nRemoved Revisions:\n(.|\n)*2a(.|\n)*3a(.|\n)*'
1199
'[*]{60}\n\nAdded Revisions:\n(.|\n)*2b(.|\n)*3b')
1201
def test_show_branch_change_no_change(self):
1202
tree = self.setup_ab_tree()
1204
log.show_branch_change(tree.branch, s, 3, '3b')
1205
self.assertEqual(s.getvalue(),
1206
'Nothing seems to have changed\n')
1208
def test_show_branch_change_no_old(self):
1209
tree = self.setup_ab_tree()
1211
log.show_branch_change(tree.branch, s, 2, '2b')
1212
self.assertContainsRe(s.getvalue(), 'Added Revisions:')
1213
self.assertNotContainsRe(s.getvalue(), 'Removed Revisions:')
1215
def test_show_branch_change_no_new(self):
1216
tree = self.setup_ab_tree()
1217
tree.branch.set_last_revision_info(2, '2b')
1219
log.show_branch_change(tree.branch, s, 3, '3b')
1220
self.assertContainsRe(s.getvalue(), 'Removed Revisions:')
1221
self.assertNotContainsRe(s.getvalue(), 'Added Revisions:')
1224
class TestRevisionNotInBranch(TestCaseForLogFormatter):
1226
def setup_a_tree(self):
1227
tree = self.make_branch_and_tree('tree')
1229
self.addCleanup(tree.unlock)
1231
'committer': 'Joe Foo <joe@foo.com>',
1232
'timestamp': 1132617600, # Mon 2005-11-22 00:00:00 +0000
1233
'timezone': 0, # UTC
1235
tree.commit('commit 1a', rev_id='1a', **kwargs)
1236
tree.commit('commit 2a', rev_id='2a', **kwargs)
1237
tree.commit('commit 3a', rev_id='3a', **kwargs)
1240
def setup_ab_tree(self):
1241
tree = self.setup_a_tree()
1242
tree.set_last_revision('1a')
1243
tree.branch.set_last_revision_info(1, '1a')
1245
'committer': 'Joe Foo <joe@foo.com>',
1246
'timestamp': 1132617600, # Mon 2005-11-22 00:00:00 +0000
1247
'timezone': 0, # UTC
1249
tree.commit('commit 2b', rev_id='2b', **kwargs)
1250
tree.commit('commit 3b', rev_id='3b', **kwargs)
1253
def test_one_revision(self):
1254
tree = self.setup_ab_tree()
1256
rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
1257
log.show_log(tree.branch, lf, verbose=True, start_revision=rev,
1259
self.assertEqual(1, len(lf.revisions))
1260
self.assertEqual(None, lf.revisions[0].revno) # Out-of-branch
1261
self.assertEqual('3a', lf.revisions[0].rev.revision_id)
1263
def test_many_revisions(self):
1264
tree = self.setup_ab_tree()
1266
start_rev = revisionspec.RevisionInfo(tree.branch, None, '1a')
1267
end_rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
1268
log.show_log(tree.branch, lf, verbose=True, start_revision=start_rev,
1269
end_revision=end_rev)
1270
self.assertEqual(3, len(lf.revisions))
1271
self.assertEqual(None, lf.revisions[0].revno) # Out-of-branch
1272
self.assertEqual('3a', lf.revisions[0].rev.revision_id)
1273
self.assertEqual(None, lf.revisions[1].revno) # Out-of-branch
1274
self.assertEqual('2a', lf.revisions[1].rev.revision_id)
1275
self.assertEqual('1', lf.revisions[2].revno) # In-branch
1277
def test_long_format(self):
1278
tree = self.setup_ab_tree()
1279
start_rev = revisionspec.RevisionInfo(tree.branch, None, '1a')
1280
end_rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
1281
self.assertFormatterResult("""\
1282
------------------------------------------------------------
1284
committer: Joe Foo <joe@foo.com>
1286
timestamp: Tue 2005-11-22 00:00:00 +0000
1289
------------------------------------------------------------
1291
committer: Joe Foo <joe@foo.com>
1293
timestamp: Tue 2005-11-22 00:00:00 +0000
1296
------------------------------------------------------------
1298
committer: Joe Foo <joe@foo.com>
1300
timestamp: Tue 2005-11-22 00:00:00 +0000
1304
tree.branch, log.LongLogFormatter, show_log_kwargs={
1305
'start_revision': start_rev, 'end_revision': end_rev
1308
def test_short_format(self):
1309
tree = self.setup_ab_tree()
1310
start_rev = revisionspec.RevisionInfo(tree.branch, None, '1a')
1311
end_rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
1312
self.assertFormatterResult("""\
1321
1 Joe Foo\t2005-11-22
1325
tree.branch, log.ShortLogFormatter, show_log_kwargs={
1326
'start_revision': start_rev, 'end_revision': end_rev
1329
def test_line_format(self):
1330
tree = self.setup_ab_tree()
1331
start_rev = revisionspec.RevisionInfo(tree.branch, None, '1a')
1332
end_rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
1333
self.assertFormatterResult("""\
1334
Joe Foo 2005-11-22 commit 3a
1335
Joe Foo 2005-11-22 commit 2a
1336
1: Joe Foo 2005-11-22 commit 1a
1338
tree.branch, log.LineLogFormatter, show_log_kwargs={
1339
'start_revision': start_rev, 'end_revision': end_rev
1343
class TestLogWithBugs(TestCaseForLogFormatter, TestLogMixin):
1346
super(TestLogWithBugs, self).setUp()
1347
log.properties_handler_registry.register(
1348
'bugs_properties_handler',
1349
log._bugs_properties_handler)
1351
def make_commits_with_bugs(self):
1352
"""Helper method for LogFormatter tests"""
1353
tree = self.make_branch_and_tree(u'.')
1354
self.build_tree(['a', 'b'])
1356
self.wt_commit(tree, 'simple log message', rev_id='a1',
1357
revprops={'bugs': 'test://bug/id fixed'})
1359
self.wt_commit(tree, 'multiline\nlog\nmessage\n', rev_id='a2',
1360
authors=['Joe Bar <joe@bar.com>'],
1361
revprops={'bugs': 'test://bug/id fixed\n'
1362
'test://bug/2 fixed'})
1366
def test_long_bugs(self):
1367
tree = self.make_commits_with_bugs()
1368
self.assertFormatterResult("""\
1369
------------------------------------------------------------
1371
fixes bugs: test://bug/id test://bug/2
1372
author: Joe Bar <joe@bar.com>
1373
committer: Joe Foo <joe@foo.com>
1375
timestamp: Tue 2005-11-22 00:00:01 +0000
1380
------------------------------------------------------------
1382
fixes bug: test://bug/id
1383
committer: Joe Foo <joe@foo.com>
1385
timestamp: Tue 2005-11-22 00:00:00 +0000
1389
tree.branch, log.LongLogFormatter)
1391
def test_short_bugs(self):
1392
tree = self.make_commits_with_bugs()
1393
self.assertFormatterResult("""\
1394
2 Joe Bar\t2005-11-22
1395
fixes bugs: test://bug/id test://bug/2
1400
1 Joe Foo\t2005-11-22
1401
fixes bug: test://bug/id
1405
tree.branch, log.ShortLogFormatter)
1407
def test_wrong_bugs_property(self):
1408
tree = self.make_branch_and_tree(u'.')
1409
self.build_tree(['foo'])
1410
self.wt_commit(tree, 'simple log message', rev_id='a1',
1411
revprops={'bugs': 'test://bug/id invalid_value'})
1412
self.assertFormatterResult("""\
1413
1 Joe Foo\t2005-11-22
1417
tree.branch, log.ShortLogFormatter)
1419
def test_bugs_handler_present(self):
1420
self.properties_handler_registry.get('bugs_properties_handler')
1423
class TestLogForAuthors(TestCaseForLogFormatter):
1426
super(TestLogForAuthors, self).setUp()
1427
self.wt = self.make_standard_commit('nicky',
1428
authors=['John Doe <jdoe@example.com>',
1429
'Jane Rey <jrey@example.com>'])
1431
def assertFormatterResult(self, formatter, who, result):
1432
formatter_kwargs = dict()
1434
author_list_handler = log.author_list_registry.get(who)
1435
formatter_kwargs['author_list_handler'] = author_list_handler
1436
TestCaseForLogFormatter.assertFormatterResult(self, result,
1437
self.wt.branch, formatter, formatter_kwargs=formatter_kwargs)
1439
def test_line_default(self):
1440
self.assertFormatterResult(log.LineLogFormatter, None, """\
1441
1: John Doe 2005-11-22 add a
1444
def test_line_committer(self):
1445
self.assertFormatterResult(log.LineLogFormatter, 'committer', """\
1446
1: Lorem Ipsum 2005-11-22 add a
1449
def test_line_first(self):
1450
self.assertFormatterResult(log.LineLogFormatter, 'first', """\
1451
1: John Doe 2005-11-22 add a
1454
def test_line_all(self):
1455
self.assertFormatterResult(log.LineLogFormatter, 'all', """\
1456
1: John Doe, Jane Rey 2005-11-22 add a
1460
def test_short_default(self):
1461
self.assertFormatterResult(log.ShortLogFormatter, None, """\
1462
1 John Doe\t2005-11-22
1467
def test_short_committer(self):
1468
self.assertFormatterResult(log.ShortLogFormatter, 'committer', """\
1469
1 Lorem Ipsum\t2005-11-22
1474
def test_short_first(self):
1475
self.assertFormatterResult(log.ShortLogFormatter, 'first', """\
1476
1 John Doe\t2005-11-22
1481
def test_short_all(self):
1482
self.assertFormatterResult(log.ShortLogFormatter, 'all', """\
1483
1 John Doe, Jane Rey\t2005-11-22
1488
def test_long_default(self):
1489
self.assertFormatterResult(log.LongLogFormatter, None, """\
1490
------------------------------------------------------------
1492
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
1493
committer: Lorem Ipsum <test@example.com>
1495
timestamp: Tue 2005-11-22 00:00:00 +0000
1500
def test_long_committer(self):
1501
self.assertFormatterResult(log.LongLogFormatter, 'committer', """\
1502
------------------------------------------------------------
1504
committer: Lorem Ipsum <test@example.com>
1506
timestamp: Tue 2005-11-22 00:00:00 +0000
1511
def test_long_first(self):
1512
self.assertFormatterResult(log.LongLogFormatter, 'first', """\
1513
------------------------------------------------------------
1515
author: John Doe <jdoe@example.com>
1516
committer: Lorem Ipsum <test@example.com>
1518
timestamp: Tue 2005-11-22 00:00:00 +0000
1523
def test_long_all(self):
1524
self.assertFormatterResult(log.LongLogFormatter, 'all', """\
1525
------------------------------------------------------------
1527
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
1528
committer: Lorem Ipsum <test@example.com>
1530
timestamp: Tue 2005-11-22 00:00:00 +0000
1535
def test_gnu_changelog_default(self):
1536
self.assertFormatterResult(log.GnuChangelogLogFormatter, None, """\
1537
2005-11-22 John Doe <jdoe@example.com>
1543
def test_gnu_changelog_committer(self):
1544
self.assertFormatterResult(log.GnuChangelogLogFormatter, 'committer', """\
1545
2005-11-22 Lorem Ipsum <test@example.com>
1551
def test_gnu_changelog_first(self):
1552
self.assertFormatterResult(log.GnuChangelogLogFormatter, 'first', """\
1553
2005-11-22 John Doe <jdoe@example.com>
1559
def test_gnu_changelog_all(self):
1560
self.assertFormatterResult(log.GnuChangelogLogFormatter, 'all', """\
1561
2005-11-22 John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
1568
class TestLogExcludeAncestry(tests.TestCaseWithTransport):
1570
def make_branch_with_alternate_ancestries(self, relpath='.'):
1571
# See test_merge_sorted_exclude_ancestry below for the difference with
1572
# bt.per_branch.test_iter_merge_sorted_revision.
1573
# TestIterMergeSortedRevisionsBushyGraph.
1574
# make_branch_with_alternate_ancestries
1575
# and test_merge_sorted_exclude_ancestry
1576
# See the FIXME in assertLogRevnos too.
1577
builder = branchbuilder.BranchBuilder(self.get_transport(relpath))
1589
builder.start_series()
1590
builder.build_snapshot('1', None, [
1591
('add', ('', 'TREE_ROOT', 'directory', '')),])
1592
builder.build_snapshot('1.1.1', ['1'], [])
1593
builder.build_snapshot('2', ['1'], [])
1594
builder.build_snapshot('1.2.1', ['1.1.1'], [])
1595
builder.build_snapshot('1.1.2', ['1.1.1', '1.2.1'], [])
1596
builder.build_snapshot('3', ['2', '1.1.2'], [])
1597
builder.finish_series()
1598
br = builder.get_branch()
1600
self.addCleanup(br.unlock)
1603
def assertLogRevnos(self, expected_revnos, b, start, end,
1604
exclude_common_ancestry, generate_merge_revisions=True):
1605
# FIXME: the layering in log makes it hard to test intermediate levels,
1606
# I wish adding filters with their parameters was easier...
1608
iter_revs = log._calc_view_revisions(
1609
b, start, end, direction='reverse',
1610
generate_merge_revisions=generate_merge_revisions,
1611
exclude_common_ancestry=exclude_common_ancestry)
1612
self.assertEqual(expected_revnos,
1613
[revid for revid, revno, depth in iter_revs])
1615
def test_merge_sorted_exclude_ancestry(self):
1616
b = self.make_branch_with_alternate_ancestries()
1617
self.assertLogRevnos(['3', '1.1.2', '1.2.1', '1.1.1', '2', '1'],
1618
b, '1', '3', exclude_common_ancestry=False)
1619
# '2' is part of the '3' ancestry but not part of '1.1.1' ancestry so
1620
# it should be mentioned even if merge_sort order will make it appear
1622
self.assertLogRevnos(['3', '1.1.2', '1.2.1', '2'],
1623
b, '1.1.1', '3', exclude_common_ancestry=True)
1625
def test_merge_sorted_simple_revnos_exclude_ancestry(self):
1626
b = self.make_branch_with_alternate_ancestries()
1627
self.assertLogRevnos(['3', '2'],
1628
b, '1', '3', exclude_common_ancestry=True,
1629
generate_merge_revisions=False)
1630
self.assertLogRevnos(['3', '1.1.2', '1.2.1', '1.1.1', '2'],
1631
b, '1', '3', exclude_common_ancestry=True,
1632
generate_merge_revisions=True)
1635
class TestLogDefaults(TestCaseForLogFormatter):
1636
def test_default_log_level(self):
1638
Test to ensure that specifying 'levels=1' to make_log_request_dict
1639
doesn't get overwritten when using a LogFormatter that supports more
1643
wt = self._prepare_tree_with_merges()
1646
class CustomLogFormatter(log.LogFormatter):
1647
def __init__(self, *args, **kwargs):
1648
super(CustomLogFormatter, self).__init__(*args, **kwargs)
1650
def get_levels(self):
1651
# log formatter supports all levels:
1653
def log_revision(self, revision):
1654
self.revisions.append(revision)
1656
log_formatter = LogCatcher()
1657
# First request we don't specify number of levels, we should get a
1658
# sensible default (whatever the LogFormatter handles - which in this
1659
# case is 0/everything):
1660
request = log.make_log_request_dict(limit=10)
1661
log.Logger(b, request).show(log_formatter)
1662
# should have all three revisions:
1663
self.assertEquals(len(log_formatter.revisions), 3)
1666
log_formatter = LogCatcher()
1667
# now explicitly request mainline revisions only:
1668
request = log.make_log_request_dict(limit=10, levels=1)
1669
log.Logger(b, request).show(log_formatter)
1670
# should now only have 2 revisions:
1671
self.assertEquals(len(log_formatter.revisions), 2)