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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
18
from cStringIO import StringIO
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):
20
from bzrlib import log, registry
21
from bzrlib.tests import TestCase, TestCaseWithTransport
22
from bzrlib.log import (show_log,
29
from bzrlib.branch import Branch
30
from bzrlib.errors import (
32
InvalidRevisionNumber,
34
from bzrlib.revision import Revision
35
from bzrlib.revisionspec import (
41
class TestCaseWithoutPropsHandler(TestCaseWithTransport):
55
super(TestCaseForLogFormatter, self).setUp()
44
super(TestCaseWithoutPropsHandler, self).setUp()
56
45
# 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
46
self.properties_handler_registry = \
47
log.properties_handler_registry
48
# clean up the registry in log
59
49
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.
52
super(TestCaseWithoutPropsHandler, self)._cleanup()
53
# restore the custom properties handler registry
54
log.properties_handler_registry = \
55
self.properties_handler_registry
58
class LogCatcher(LogFormatter):
59
"""Pull log messages into list rather than displaying them.
61
For ease of testing we save log messages here rather than actually
62
formatting them, so that we can precisely check the result without
63
being too dependent on the exact formatting.
65
We should also test the LogFormatter.
130
supports_merge_revisions = True
131
68
supports_delta = True
135
def __init__(self, *args, **kwargs):
136
kwargs.update(dict(to_file=None))
137
super(LogCatcher, self).__init__(*args, **kwargs)
71
super(LogCatcher, self).__init__(to_file=None)
140
74
def log_revision(self, revision):
141
self.revisions.append(revision)
144
class TestShowLog(tests.TestCaseWithTransport):
75
self.logs.append(revision)
78
class TestShowLog(TestCaseWithTransport):
146
80
def checkDelta(self, delta, **kw):
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).
81
"""Check the filenames touched by a delta are as expected."""
152
82
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
153
# By default we expect an empty list
154
83
expected = kw.get(n, [])
155
84
# strip out only the path components
156
85
got = [x[0] for x in getattr(delta, n)]
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)
86
self.assertEquals(expected, got)
165
88
def test_cur_revno(self):
166
89
wt = self.make_branch_and_tree('.')
170
93
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):
94
show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
95
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
96
start_revision=2, end_revision=1)
97
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
98
start_revision=1, end_revision=2)
99
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
100
start_revision=0, end_revision=2)
101
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
102
start_revision=1, end_revision=0)
103
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
104
start_revision=-1, end_revision=1)
105
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
106
start_revision=1, end_revision=-1)
108
def test_simple_log(self):
109
eq = self.assertEquals
183
111
wt = self.make_branch_and_tree('.')
185
114
lf = LogCatcher()
186
log.show_log(wt.branch, lf)
188
self.assertEqual([], lf.revisions)
190
def test_empty_commit(self):
191
wt = self.make_branch_and_tree('.')
193
119
wt.commit('empty commit')
194
120
lf = LogCatcher()
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)
121
show_log(b, lf, verbose=True)
123
eq(lf.logs[0].revno, '1')
124
eq(lf.logs[0].rev.message, 'empty commit')
126
self.log('log delta: %r' % d)
202
def test_simple_commit(self):
203
wt = self.make_branch_and_tree('.')
204
wt.commit('empty commit')
205
129
self.build_tree(['hello'])
207
131
wt.commit('add one file',
208
132
committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
209
133
u'<test@example.com>')
135
lf = self.make_utf8_encoded_stringio()
136
# log using regular thing
137
show_log(b, LongLogFormatter(lf))
139
for l in lf.readlines():
142
# get log as data structure
210
143
lf = LogCatcher()
211
log.show_log(wt.branch, lf, verbose=True)
212
self.assertEqual(2, len(lf.revisions))
144
show_log(b, lf, verbose=True)
146
self.log('log entries:')
147
for logentry in lf.logs:
148
self.log('%4s %s' % (logentry.revno, logentry.rev.message))
213
150
# first one is most recent
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')
151
logentry = lf.logs[0]
152
eq(logentry.revno, '2')
153
eq(logentry.rev.message, 'add one file')
155
self.log('log 2 delta: %r' % d)
156
self.checkDelta(d, added=['hello'])
158
# commit a log message with control characters
159
msg = "All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
160
self.log("original commit message: %r", msg)
224
162
lf = LogCatcher()
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
show_log(b, lf, verbose=True)
164
committed_msg = lf.logs[0].rev.message
165
self.log("escaped commit message: %r", committed_msg)
166
self.assert_(msg != committed_msg)
167
self.assert_(len(committed_msg) > len(msg))
233
def test_commit_message_without_control_chars(self):
234
wt = self.make_branch_and_tree('.')
169
# Check that log message with only XML-valid characters isn't
235
170
# escaped. As ElementTree apparently does some kind of
236
171
# newline conversion, neither LF (\x0A) nor CR (\x0D) are
237
172
# included in the test commit message, even though they are
238
173
# valid XML 1.0 characters.
239
174
msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
175
self.log("original commit message: %r", msg)
241
177
lf = LogCatcher()
242
log.show_log(wt.branch, lf, verbose=True)
243
committed_msg = lf.revisions[0].rev.message
244
self.assertEqual(msg, committed_msg)
178
show_log(b, lf, verbose=True)
179
committed_msg = lf.logs[0].rev.message
180
self.log("escaped commit message: %r", committed_msg)
181
self.assert_(msg == committed_msg)
246
183
def test_deltas_in_merge_revisions(self):
247
184
"""Check deltas created for both mainline and merge revisions"""
185
eq = self.assertEquals
248
186
wt = self.make_branch_and_tree('parent')
249
187
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
263
201
lf = LogCatcher()
264
202
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):
203
show_log(b, lf, verbose=True)
205
logentry = lf.logs[0]
206
eq(logentry.revno, '2')
207
eq(logentry.rev.message, 'merge child branch')
209
self.checkDelta(d, removed=['file1'], modified=['file2'])
210
logentry = lf.logs[1]
211
eq(logentry.revno, '1.1.1')
212
eq(logentry.rev.message, 'remove file1 and modify file2')
214
self.checkDelta(d, removed=['file1'], modified=['file2'])
215
logentry = lf.logs[2]
216
eq(logentry.revno, '1')
217
eq(logentry.rev.message, 'add file1 and file2')
219
self.checkDelta(d, added=['file1', 'file2'])
221
def test_merges_nonsupporting_formatter(self):
222
"""Tests that show_log will raise if the formatter doesn't
223
support merge revisions."""
224
wt = self.make_branch_and_memory_tree('.')
228
wt.commit('rev-1', rev_id='rev-1',
229
timestamp=1132586655, timezone=36000,
230
committer='Joe Foo <joe@foo.com>')
231
wt.commit('rev-merged', rev_id='rev-2a',
232
timestamp=1132586700, timezone=36000,
233
committer='Joe Foo <joe@foo.com>')
234
wt.set_parent_ids(['rev-1', 'rev-2a'])
235
wt.branch.set_last_revision_info(1, 'rev-1')
236
wt.commit('rev-2', rev_id='rev-2b',
237
timestamp=1132586800, timezone=36000,
238
committer='Joe Foo <joe@foo.com>')
239
logfile = self.make_utf8_encoded_stringio()
240
formatter = ShortLogFormatter(to_file=logfile)
243
revspec = RevisionSpec.from_string('1.1.1')
244
rev = revspec.in_history(wtb)
245
self.assertRaises(BzrCommandError, show_log, wtb, lf,
246
start_revision=rev, end_revision=rev)
251
def make_commits_with_trailing_newlines(wt):
252
"""Helper method for LogFormatter tests"""
255
open('a', 'wb').write('hello moto\n')
257
wt.commit('simple log message', rev_id='a1',
258
timestamp=1132586655.459960938, timezone=-6*3600,
259
committer='Joe Foo <joe@foo.com>')
260
open('b', 'wb').write('goodbye\n')
262
wt.commit('multiline\nlog\nmessage\n', rev_id='a2',
263
timestamp=1132586842.411175966, timezone=-6*3600,
264
committer='Joe Foo <joe@foo.com>',
265
author='Joe Bar <joe@bar.com>')
267
open('c', 'wb').write('just another manic monday\n')
269
wt.commit('single line with trailing newline\n', rev_id='a3',
270
timestamp=1132587176.835228920, timezone=-6*3600,
271
committer = 'Joe Foo <joe@foo.com>')
275
def normalize_log(log):
276
"""Replaces the variable lines of logs with fixed lines"""
277
author = 'author: Dolor Sit <test@example.com>'
278
committer = 'committer: Lorem Ipsum <test@example.com>'
279
lines = log.splitlines(True)
280
for idx,line in enumerate(lines):
281
stripped_line = line.lstrip()
282
indent = ' ' * (len(line) - len(stripped_line))
283
if stripped_line.startswith('author:'):
284
lines[idx] = indent + author + '\n'
285
elif stripped_line.startswith('committer:'):
286
lines[idx] = indent + committer + '\n'
287
elif stripped_line.startswith('timestamp:'):
288
lines[idx] = indent + 'timestamp: Just now\n'
289
return ''.join(lines)
292
class TestShortLogFormatter(TestCaseWithTransport):
320
294
def test_trailing_newlines(self):
321
295
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
296
b = make_commits_with_trailing_newlines(wt)
297
sio = self.make_utf8_encoded_stringio()
298
lf = ShortLogFormatter(to_file=sio)
300
self.assertEqualDiff(sio.getvalue(), """\
301
3 Joe Foo\t2005-11-21
325
302
single line with trailing newline
327
2 Joe Foo\t2005-11-22
304
2 Joe Bar\t2005-11-21
332
1 Joe Foo\t2005-11-22
309
1 Joe Foo\t2005-11-21
333
310
simple log message
336
b, log.ShortLogFormatter)
338
314
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)
315
wt = self.make_branch_and_memory_tree('.')
319
wt.commit('rev-1', rev_id='rev-1',
320
timestamp=1132586655, timezone=36000,
321
committer='Joe Foo <joe@foo.com>')
322
wt.commit('rev-merged', rev_id='rev-2a',
323
timestamp=1132586700, timezone=36000,
324
committer='Joe Foo <joe@foo.com>')
325
wt.set_parent_ids(['rev-1', 'rev-2a'])
326
wt.branch.set_last_revision_info(1, 'rev-1')
327
wt.commit('rev-2', rev_id='rev-2b',
328
timestamp=1132586800, timezone=36000,
329
committer='Joe Foo <joe@foo.com>')
330
logfile = self.make_utf8_encoded_stringio()
331
formatter = ShortLogFormatter(to_file=logfile)
332
show_log(wt.branch, formatter)
333
self.assertEqualDiff(logfile.getvalue(), """\
334
2 Joe Foo\t2005-11-22 [merge]
337
1 Joe Foo\t2005-11-22
396
344
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):
345
wt = self.make_branch_and_memory_tree('.')
349
wt.commit('rev-1', rev_id='rev-1',
350
timestamp=1132586655, timezone=36000,
351
committer='Joe Foo <joe@foo.com>')
352
wt.commit('rev-merged', rev_id='rev-2a',
353
timestamp=1132586700, timezone=36000,
354
committer='Joe Foo <joe@foo.com>')
355
wt.set_parent_ids(['rev-1', 'rev-2a'])
356
wt.branch.set_last_revision_info(1, 'rev-1')
357
wt.commit('rev-2', rev_id='rev-2b',
358
timestamp=1132586800, timezone=36000,
359
committer='Joe Foo <joe@foo.com>')
360
logfile = self.make_utf8_encoded_stringio()
361
formatter = ShortLogFormatter(to_file=logfile)
362
revspec = RevisionSpec.from_string('1.1.1')
364
rev = revspec.in_history(wtb)
365
show_log(wtb, formatter, start_revision=rev, end_revision=rev)
366
self.assertEqualDiff(logfile.getvalue(), """\
367
1.1.1 Joe Foo\t2005-11-22
375
class TestLongLogFormatter(TestCaseWithoutPropsHandler):
472
377
def test_verbose_log(self):
473
378
"""Verbose log includes changed files
477
wt = self.make_standard_commit('test_verbose_log', authors=[])
478
self.assertFormatterResult('''\
382
wt = self.make_branch_and_tree('.')
384
self.build_tree(['a'])
386
# XXX: why does a longer nick show up?
387
b.nick = 'test_verbose_log'
388
wt.commit(message='add a',
389
timestamp=1132711707,
391
committer='Lorem Ipsum <test@example.com>')
392
logfile = file('out.tmp', 'w+')
393
formatter = LongLogFormatter(to_file=logfile)
394
show_log(b, formatter, verbose=True)
397
log_contents = logfile.read()
398
self.assertEqualDiff(log_contents, '''\
479
399
------------------------------------------------------------
481
401
committer: Lorem Ipsum <test@example.com>
482
402
branch nick: test_verbose_log
483
timestamp: Tue 2005-11-22 00:00:00 +0000
403
timestamp: Wed 2005-11-23 12:08:27 +1000
489
wt.branch, log.LongLogFormatter,
490
show_log_kwargs=dict(verbose=True))
492
410
def test_merges_are_indented_by_level(self):
493
411
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("""\
412
wt.commit('first post')
413
self.run_bzr('branch parent child')
414
self.run_bzr(['commit', '-m', 'branch 1', '--unchanged', 'child'])
415
self.run_bzr('branch child smallerchild')
416
self.run_bzr(['commit', '-m', 'branch 2', '--unchanged',
419
self.run_bzr('merge ../smallerchild')
420
self.run_bzr(['commit', '-m', 'merge branch 2'])
421
os.chdir('../parent')
422
self.run_bzr('merge ../child')
423
wt.commit('merge branch 1')
425
sio = self.make_utf8_encoded_stringio()
426
lf = LongLogFormatter(to_file=sio)
427
show_log(b, lf, verbose=True)
428
log = normalize_log(sio.getvalue())
429
self.assertEqualDiff(log, """\
504
430
------------------------------------------------------------
506
committer: Joe Foo <joe@foo.com>
432
committer: Lorem Ipsum <test@example.com>
507
433
branch nick: parent
508
timestamp: Tue 2005-11-22 00:00:04 +0000
511
437
------------------------------------------------------------
513
committer: Joe Foo <joe@foo.com>
439
committer: Lorem Ipsum <test@example.com>
514
440
branch nick: child
515
timestamp: Tue 2005-11-22 00:00:03 +0000
518
444
------------------------------------------------------------
520
committer: Joe Foo <joe@foo.com>
446
committer: Lorem Ipsum <test@example.com>
521
447
branch nick: smallerchild
522
timestamp: Tue 2005-11-22 00:00:02 +0000
525
451
------------------------------------------------------------
527
committer: Joe Foo <joe@foo.com>
453
committer: Lorem Ipsum <test@example.com>
528
454
branch nick: child
529
timestamp: Tue 2005-11-22 00:00:01 +0000
532
458
------------------------------------------------------------
534
committer: Joe Foo <joe@foo.com>
460
committer: Lorem Ipsum <test@example.com>
535
461
branch nick: parent
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
467
def test_verbose_merge_revisions_contain_deltas(self):
545
468
wt = self.make_branch_and_tree('parent')
546
469
self.build_tree(['parent/f1', 'parent/f2'])
547
470
wt.add(['f1','f2'])
548
self.wt_commit(wt, 'first post')
549
child_wt = wt.bzrdir.sprout('child').open_workingtree()
471
wt.commit('first post')
472
self.run_bzr('branch parent child')
550
473
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("""\
474
file('child/f2', 'wb').write('hello\n')
475
self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
478
self.run_bzr('merge ../child')
479
wt.commit('merge branch 1')
481
sio = self.make_utf8_encoded_stringio()
482
lf = LongLogFormatter(to_file=sio)
483
show_log(b, lf, verbose=True)
484
log = normalize_log(sio.getvalue())
485
self.assertEqualDiff(log, """\
556
486
------------------------------------------------------------
558
committer: Joe Foo <joe@foo.com>
488
committer: Lorem Ipsum <test@example.com>
559
489
branch nick: parent
560
timestamp: Tue 2005-11-22 00:00:02 +0000
615
546
committer: Joe Foo <joe@foo.com>
616
547
branch nick: test
617
timestamp: Tue 2005-11-22 00:00:00 +0000
548
timestamp: Mon 2005-11-21 09:24:15 -0600
619
550
simple log message
621
b, log.LongLogFormatter)
623
553
def test_author_in_log(self):
624
554
"""Log includes the author name if it's set in
625
555
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("""\
557
wt = self.make_branch_and_tree('.')
559
self.build_tree(['a'])
561
b.nick = 'test_author_log'
562
wt.commit(message='add a',
563
timestamp=1132711707,
565
committer='Lorem Ipsum <test@example.com>',
566
author='John Doe <jdoe@example.com>')
568
formatter = LongLogFormatter(to_file=sio)
569
show_log(b, formatter)
570
self.assertEqualDiff(sio.getvalue(), '''\
631
571
------------------------------------------------------------
633
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
573
author: John Doe <jdoe@example.com>
634
574
committer: Lorem Ipsum <test@example.com>
635
575
branch nick: test_author_log
636
timestamp: Tue 2005-11-22 00:00:00 +0000
576
timestamp: Wed 2005-11-23 12:08:27 +1000
640
wt.branch, log.LongLogFormatter)
642
581
def test_properties_in_log(self):
643
"""Log includes the custom properties returned by the registered
582
"""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("""\
585
wt = self.make_branch_and_tree('.')
587
self.build_tree(['a'])
589
b.nick = 'test_properties_in_log'
590
wt.commit(message='add a',
591
timestamp=1132711707,
593
committer='Lorem Ipsum <test@example.com>',
594
author='John Doe <jdoe@example.com>')
596
formatter = LongLogFormatter(to_file=sio)
598
def trivial_custom_prop_handler(revision):
599
return {'test_prop':'test_value'}
601
log.properties_handler_registry.register(
602
'trivial_custom_prop_handler',
603
trivial_custom_prop_handler)
604
show_log(b, formatter)
606
log.properties_handler_registry.remove(
607
'trivial_custom_prop_handler')
608
self.assertEqualDiff(sio.getvalue(), '''\
655
609
------------------------------------------------------------
657
611
test_prop: test_value
658
612
author: John Doe <jdoe@example.com>
659
613
committer: Lorem Ipsum <test@example.com>
660
614
branch nick: test_properties_in_log
661
timestamp: Tue 2005-11-22 00:00:00 +0000
615
timestamp: Wed 2005-11-23 12:08:27 +1000
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
620
def test_error_in_properties_handler(self):
687
"""Log includes the custom properties returned by the registered
621
"""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,)
624
wt = self.make_branch_and_tree('.')
626
self.build_tree(['a'])
628
b.nick = 'test_author_log'
629
wt.commit(message='add a',
630
timestamp=1132711707,
632
committer='Lorem Ipsum <test@example.com>',
633
author='John Doe <jdoe@example.com>',
634
revprops={'first_prop':'first_value'})
636
formatter = LongLogFormatter(to_file=sio)
638
def trivial_custom_prop_handler(revision):
639
raise StandardError("a test error")
641
log.properties_handler_registry.register(
642
'trivial_custom_prop_handler',
643
trivial_custom_prop_handler)
644
self.assertRaises(StandardError, show_log, b, formatter,)
646
log.properties_handler_registry.remove(
647
'trivial_custom_prop_handler')
702
649
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
650
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):
652
self.build_tree(['a'])
654
b.nick = 'test_author_log'
655
wt.commit(message='add a',
656
timestamp=1132711707,
658
committer='Lorem Ipsum <test@example.com>',
659
author='John Doe <jdoe@example.com>',
660
revprops={'a_prop':'test_value'})
662
formatter = LongLogFormatter(to_file=sio)
664
def bad_argument_prop_handler(revision):
665
return {'custom_prop_name':revision.properties['a_prop']}
667
log.properties_handler_registry.register(
668
'bad_argument_prop_handler',
669
bad_argument_prop_handler)
671
self.assertRaises(AttributeError, formatter.show_properties,
674
revision = b.repository.get_revision(b.last_revision())
675
formatter.show_properties(revision, '')
676
self.assertEqualDiff(sio.getvalue(),
677
'''custom_prop_name: test_value\n''')
679
log.properties_handler_registry.remove(
680
'bad_argument_prop_handler')
683
class TestLineLogFormatter(TestCaseWithTransport):
902
685
def test_line_log(self):
903
686
"""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)
690
wt = self.make_branch_and_tree('.')
692
self.build_tree(['a'])
694
b.nick = 'test-line-log'
695
wt.commit(message='add a',
696
timestamp=1132711707,
698
committer='Line-Log-Formatter Tester <test@line.log>')
699
logfile = file('out.tmp', 'w+')
700
formatter = LineLogFormatter(to_file=logfile)
701
show_log(b, formatter)
704
log_contents = logfile.read()
705
self.assertEqualDiff(log_contents,
706
'1: Line-Log-Formatte... 2005-11-23 add a\n')
915
708
def test_trailing_newlines(self):
916
709
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)
710
b = make_commits_with_trailing_newlines(wt)
711
sio = self.make_utf8_encoded_stringio()
712
lf = LineLogFormatter(to_file=sio)
714
self.assertEqualDiff(sio.getvalue(), """\
715
3: Joe Foo 2005-11-21 single line with trailing newline
716
2: Joe Bar 2005-11-21 multiline
717
1: Joe Foo 2005-11-21 simple log message
925
720
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):
721
wt = self.make_branch_and_memory_tree('.')
725
wt.commit('rev-1', rev_id='rev-1',
726
timestamp=1132586655, timezone=36000,
727
committer='Joe Foo <joe@foo.com>')
728
wt.commit('rev-merged', rev_id='rev-2a',
729
timestamp=1132586700, timezone=36000,
730
committer='Joe Foo <joe@foo.com>')
731
wt.set_parent_ids(['rev-1', 'rev-2a'])
732
wt.branch.set_last_revision_info(1, 'rev-1')
733
wt.commit('rev-2', rev_id='rev-2b',
734
timestamp=1132586800, timezone=36000,
735
committer='Joe Foo <joe@foo.com>')
736
logfile = self.make_utf8_encoded_stringio()
737
formatter = LineLogFormatter(to_file=logfile)
738
revspec = RevisionSpec.from_string('1.1.1')
740
rev = revspec.in_history(wtb)
741
show_log(wtb, formatter, start_revision=rev, end_revision=rev)
742
self.assertEqualDiff(logfile.getvalue(), """\
743
1.1.1: Joe Foo 2005-11-22 rev-merged
750
class TestGetViewRevisions(TestCaseWithTransport):
752
def make_tree_with_commits(self):
753
"""Create a tree with well-known revision ids"""
754
wt = self.make_branch_and_tree('tree1')
755
wt.commit('commit one', rev_id='1')
756
wt.commit('commit two', rev_id='2')
757
wt.commit('commit three', rev_id='3')
758
mainline_revs = [None, '1', '2', '3']
759
rev_nos = {'1': 1, '2': 2, '3': 3}
760
return mainline_revs, rev_nos, wt
762
def make_tree_with_merges(self):
763
"""Create a tree with well-known revision ids and a merge"""
764
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
765
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
766
tree2.commit('four-a', rev_id='4a')
767
wt.merge_from_branch(tree2.branch)
768
wt.commit('four-b', rev_id='4b')
769
mainline_revs.append('4b')
772
return mainline_revs, rev_nos, wt
774
def make_tree_with_many_merges(self):
775
"""Create a tree with well-known revision ids"""
776
wt = self.make_branch_and_tree('tree1')
777
wt.commit('commit one', rev_id='1')
778
wt.commit('commit two', rev_id='2')
779
tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
780
tree3.commit('commit three a', rev_id='3a')
781
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
782
tree2.merge_from_branch(tree3.branch)
783
tree2.commit('commit three b', rev_id='3b')
784
wt.merge_from_branch(tree2.branch)
785
wt.commit('commit three c', rev_id='3c')
786
tree2.commit('four-a', rev_id='4a')
787
wt.merge_from_branch(tree2.branch)
788
wt.commit('four-b', rev_id='4b')
789
mainline_revs = [None, '1', '2', '3c', '4b']
790
rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
791
full_rev_nos_for_reference = {
794
'3a': '2.1.1', #first commit tree 3
795
'3b': '2.2.1', # first commit tree 2
796
'3c': '3', #merges 3b to main
797
'4a': '2.2.2', # second commit tree 2
798
'4b': '4', # merges 4a to main
800
return mainline_revs, rev_nos, wt
802
def test_get_view_revisions_forward(self):
803
"""Test the get_view_revisions method"""
804
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
806
self.addCleanup(wt.unlock)
807
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
809
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
811
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
812
'forward', include_merges=False))
813
self.assertEqual(revisions, revisions2)
815
def test_get_view_revisions_reverse(self):
816
"""Test the get_view_revisions with reverse"""
817
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
819
self.addCleanup(wt.unlock)
820
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
822
self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
824
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
825
'reverse', include_merges=False))
826
self.assertEqual(revisions, revisions2)
828
def test_get_view_revisions_merge(self):
829
"""Test get_view_revisions when there are merges"""
830
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
832
self.addCleanup(wt.unlock)
833
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
835
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
836
('4b', '4', 0), ('4a', '3.1.1', 1)],
838
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
839
'forward', include_merges=False))
840
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
844
def test_get_view_revisions_merge_reverse(self):
845
"""Test get_view_revisions in reverse when there are merges"""
846
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
848
self.addCleanup(wt.unlock)
849
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
851
self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
852
('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
854
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
855
'reverse', include_merges=False))
856
self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
860
def test_get_view_revisions_merge2(self):
861
"""Test get_view_revisions when there are merges"""
862
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
864
self.addCleanup(wt.unlock)
865
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
867
expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
868
('3a', '2.1.1', 1), ('3b', '2.2.1', 1), ('4b', '4', 0),
870
self.assertEqual(expected, revisions)
871
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
872
'forward', include_merges=False))
873
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
878
class TestGetRevisionsTouchingFileID(TestCaseWithTransport):
880
def create_tree_with_single_merge(self):
881
"""Create a branch with a moderate layout.
883
The revision graph looks like:
891
In this graph, A introduced files f1 and f2 and f3.
892
B modifies f1 and f3, and C modifies f2 and f3.
893
D merges the changes from B and C and resolves the conflict for f3.
895
# TODO: jam 20070218 This seems like it could really be done
896
# with make_branch_and_memory_tree() if we could just
897
# create the content of those files.
898
# TODO: jam 20070218 Another alternative is that we would really
899
# like to only create this tree 1 time for all tests that
900
# use it. Since 'log' only uses the tree in a readonly
901
# fashion, it seems a shame to regenerate an identical
902
# tree for each test.
903
tree = self.make_branch_and_tree('tree')
905
self.addCleanup(tree.unlock)
907
self.build_tree_contents([('tree/f1', 'A\n'),
911
tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
912
tree.commit('A', rev_id='A')
914
self.build_tree_contents([('tree/f2', 'A\nC\n'),
915
('tree/f3', 'A\nC\n'),
917
tree.commit('C', rev_id='C')
918
# Revert back to A to build the other history.
919
tree.set_last_revision('A')
920
tree.branch.set_last_revision_info(1, 'A')
921
self.build_tree_contents([('tree/f1', 'A\nB\n'),
923
('tree/f3', 'A\nB\n'),
925
tree.commit('B', rev_id='B')
926
tree.set_parent_ids(['B', 'C'])
927
self.build_tree_contents([('tree/f1', 'A\nB\n'),
928
('tree/f2', 'A\nC\n'),
929
('tree/f3', 'A\nB\nC\n'),
931
tree.commit('D', rev_id='D')
933
# Switch to a read lock for this tree.
934
# We still have addCleanup(unlock)
939
def test_tree_with_single_merge(self):
940
"""Make sure the tree layout is correct."""
941
tree = self.create_tree_with_single_merge()
942
rev_A_tree = tree.branch.repository.revision_tree('A')
943
rev_B_tree = tree.branch.repository.revision_tree('B')
945
f1_changed = (u'f1', 'f1-id', 'file', True, False)
946
f2_changed = (u'f2', 'f2-id', 'file', True, False)
947
f3_changed = (u'f3', 'f3-id', 'file', True, False)
949
delta = rev_B_tree.changes_from(rev_A_tree)
950
self.assertEqual([f1_changed, f3_changed], delta.modified)
951
self.assertEqual([], delta.renamed)
952
self.assertEqual([], delta.added)
953
self.assertEqual([], delta.removed)
955
rev_C_tree = tree.branch.repository.revision_tree('C')
956
delta = rev_C_tree.changes_from(rev_A_tree)
957
self.assertEqual([f2_changed, f3_changed], delta.modified)
958
self.assertEqual([], delta.renamed)
959
self.assertEqual([], delta.added)
960
self.assertEqual([], delta.removed)
962
rev_D_tree = tree.branch.repository.revision_tree('D')
963
delta = rev_D_tree.changes_from(rev_B_tree)
964
self.assertEqual([f2_changed, f3_changed], delta.modified)
965
self.assertEqual([], delta.renamed)
966
self.assertEqual([], delta.added)
967
self.assertEqual([], delta.removed)
969
delta = rev_D_tree.changes_from(rev_C_tree)
970
self.assertEqual([f1_changed, f3_changed], delta.modified)
971
self.assertEqual([], delta.renamed)
972
self.assertEqual([], delta.added)
973
self.assertEqual([], delta.removed)
975
def assertAllRevisionsForFileID(self, tree, file_id, revisions):
976
"""Make sure _filter_revisions_touching_file_id returns the right values.
978
Get the return value from _filter_revisions_touching_file_id and make
979
sure they are correct.
981
# The api for _get_revisions_touching_file_id is a little crazy,
982
# So we do the setup here.
983
mainline = tree.branch.revision_history()
984
mainline.insert(0, None)
985
revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
986
view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
988
actual_revs = log._filter_revisions_touching_file_id(
992
list(view_revs_iter))
993
self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
995
def test_file_id_f1(self):
996
tree = self.create_tree_with_single_merge()
997
# f1 should be marked as modified by revisions A and B
998
self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
1000
def test_file_id_f2(self):
1001
tree = self.create_tree_with_single_merge()
1002
# f2 should be marked as modified by revisions A, C, and D
1003
# because D merged the changes from C.
1004
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1006
def test_file_id_f3(self):
1007
tree = self.create_tree_with_single_merge()
1008
# f3 should be marked as modified by revisions A, B, C, and D
1009
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1011
def test_file_id_with_ghosts(self):
1012
# This is testing bug #209948, where having a ghost would cause
1013
# _filter_revisions_touching_file_id() to fail.
1014
tree = self.create_tree_with_single_merge()
1015
# We need to add a revision, so switch back to a write-locked tree
1018
first_parent = tree.last_revision()
1019
tree.set_parent_ids([first_parent, 'ghost-revision-id'])
1020
self.build_tree_contents([('tree/f1', 'A\nB\nXX\n')])
1021
tree.commit('commit with a ghost', rev_id='XX')
1022
self.assertAllRevisionsForFileID(tree, 'f1-id', ['XX', 'B', 'A'])
1023
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1026
class TestShowChangedRevisions(TestCaseWithTransport):
1022
1028
def test_show_changed_revisions_verbose(self):
1023
1029
tree = self.make_branch_and_tree('tree_a')
1030
1036
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)
1039
class TestLogFormatter(TestCase):
1040
1041
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')
1042
rev = Revision('a-id')
1043
rev.committer = 'John Doe <jdoe@example.com>'
1044
lf = LogFormatter(None)
1045
self.assertEqual('John Doe', lf.short_committer(rev))
1046
rev.committer = 'John Smith <jsmith@example.com>'
1047
self.assertEqual('John Smith', lf.short_committer(rev))
1048
rev.committer = 'John Smith'
1049
self.assertEqual('John Smith', lf.short_committer(rev))
1050
rev.committer = 'jsmith@example.com'
1051
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1052
rev.committer = '<jsmith@example.com>'
1053
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1054
rev.committer = 'John Smith jsmith@example.com'
1055
self.assertEqual('John Smith', lf.short_committer(rev))
1052
1057
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.assertEqual(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.assertEqual(len(log_formatter.revisions), 2)
1058
rev = Revision('a-id')
1059
rev.committer = 'John Doe <jdoe@example.com>'
1060
lf = LogFormatter(None)
1061
self.assertEqual('John Doe', lf.short_author(rev))
1062
rev.properties['author'] = 'John Smith <jsmith@example.com>'
1063
self.assertEqual('John Smith', lf.short_author(rev))
1064
rev.properties['author'] = 'John Smith'
1065
self.assertEqual('John Smith', lf.short_author(rev))
1066
rev.properties['author'] = 'jsmith@example.com'
1067
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1068
rev.properties['author'] = '<jsmith@example.com>'
1069
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1070
rev.properties['author'] = 'John Smith jsmith@example.com'
1071
self.assertEqual('John Smith', lf.short_author(rev))