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):
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.
20
from bzrlib import log
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 LogCatcher(LogFormatter):
42
"""Pull log messages into list rather than displaying them.
44
For ease of testing we save log messages here rather than actually
45
formatting them, so that we can precisely check the result without
46
being too dependent on the exact formatting.
48
We should also test the LogFormatter.
130
supports_merge_revisions = True
131
51
supports_delta = True
135
def __init__(self, *args, **kwargs):
136
kwargs.update(dict(to_file=None))
137
super(LogCatcher, self).__init__(*args, **kwargs)
54
super(LogCatcher, self).__init__(to_file=None)
140
57
def log_revision(self, revision):
141
self.revisions.append(revision)
144
class TestShowLog(tests.TestCaseWithTransport):
58
self.logs.append(revision)
61
class TestShowLog(TestCaseWithTransport):
146
63
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).
64
"""Check the filenames touched by a delta are as expected."""
152
65
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
153
# By default we expect an empty list
154
66
expected = kw.get(n, [])
155
67
# strip out only the path components
156
68
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)
69
self.assertEquals(expected, got)
165
71
def test_cur_revno(self):
166
72
wt = self.make_branch_and_tree('.')
170
76
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):
77
show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
78
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
79
start_revision=2, end_revision=1)
80
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
81
start_revision=1, end_revision=2)
82
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
83
start_revision=0, end_revision=2)
84
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
85
start_revision=1, end_revision=0)
86
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
87
start_revision=-1, end_revision=1)
88
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
89
start_revision=1, end_revision=-1)
91
def test_simple_log(self):
92
eq = self.assertEquals
183
94
wt = self.make_branch_and_tree('.')
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
102
wt.commit('empty commit')
194
103
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)
104
show_log(b, lf, verbose=True)
106
eq(lf.logs[0].revno, '1')
107
eq(lf.logs[0].rev.message, 'empty commit')
109
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
112
self.build_tree(['hello'])
207
114
wt.commit('add one file',
208
115
committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
209
116
u'<test@example.com>')
118
lf = self.make_utf8_encoded_stringio()
119
# log using regular thing
120
show_log(b, LongLogFormatter(lf))
122
for l in lf.readlines():
125
# get log as data structure
210
126
lf = LogCatcher()
211
log.show_log(wt.branch, lf, verbose=True)
212
self.assertEqual(2, len(lf.revisions))
127
show_log(b, lf, verbose=True)
129
self.log('log entries:')
130
for logentry in lf.logs:
131
self.log('%4s %s' % (logentry.revno, logentry.rev.message))
213
133
# 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')
134
logentry = lf.logs[0]
135
eq(logentry.revno, '2')
136
eq(logentry.rev.message, 'add one file')
138
self.log('log 2 delta: %r' % d)
139
self.checkDelta(d, added=['hello'])
141
# commit a log message with control characters
142
msg = "All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
143
self.log("original commit message: %r", msg)
224
145
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)
146
show_log(b, lf, verbose=True)
147
committed_msg = lf.logs[0].rev.message
148
self.log("escaped commit message: %r", committed_msg)
149
self.assert_(msg != committed_msg)
150
self.assert_(len(committed_msg) > len(msg))
233
def test_commit_message_without_control_chars(self):
234
wt = self.make_branch_and_tree('.')
152
# Check that log message with only XML-valid characters isn't
235
153
# escaped. As ElementTree apparently does some kind of
236
154
# newline conversion, neither LF (\x0A) nor CR (\x0D) are
237
155
# included in the test commit message, even though they are
238
156
# valid XML 1.0 characters.
239
157
msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
158
self.log("original commit message: %r", msg)
241
160
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)
161
show_log(b, lf, verbose=True)
162
committed_msg = lf.logs[0].rev.message
163
self.log("escaped commit message: %r", committed_msg)
164
self.assert_(msg == committed_msg)
246
166
def test_deltas_in_merge_revisions(self):
247
167
"""Check deltas created for both mainline and merge revisions"""
168
eq = self.assertEquals
248
169
wt = self.make_branch_and_tree('parent')
249
170
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
263
184
lf = LogCatcher()
264
185
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):
186
show_log(b, lf, verbose=True)
188
logentry = lf.logs[0]
189
eq(logentry.revno, '2')
190
eq(logentry.rev.message, 'merge child branch')
192
self.checkDelta(d, removed=['file1'], modified=['file2'])
193
logentry = lf.logs[1]
194
eq(logentry.revno, '1.1.1')
195
eq(logentry.rev.message, 'remove file1 and modify file2')
197
self.checkDelta(d, removed=['file1'], modified=['file2'])
198
logentry = lf.logs[2]
199
eq(logentry.revno, '1')
200
eq(logentry.rev.message, 'add file1 and file2')
202
self.checkDelta(d, added=['file1', 'file2'])
204
def test_merges_nonsupporting_formatter(self):
205
"""Tests that show_log will raise if the formatter doesn't
206
support merge revisions."""
207
wt = self.make_branch_and_memory_tree('.')
211
wt.commit('rev-1', rev_id='rev-1',
212
timestamp=1132586655, timezone=36000,
213
committer='Joe Foo <joe@foo.com>')
214
wt.commit('rev-merged', rev_id='rev-2a',
215
timestamp=1132586700, timezone=36000,
216
committer='Joe Foo <joe@foo.com>')
217
wt.set_parent_ids(['rev-1', 'rev-2a'])
218
wt.branch.set_last_revision_info(1, 'rev-1')
219
wt.commit('rev-2', rev_id='rev-2b',
220
timestamp=1132586800, timezone=36000,
221
committer='Joe Foo <joe@foo.com>')
222
logfile = self.make_utf8_encoded_stringio()
223
formatter = ShortLogFormatter(to_file=logfile)
226
revspec = RevisionSpec.from_string('1.1.1')
227
rev = revspec.in_history(wtb)
228
self.assertRaises(BzrCommandError, show_log, wtb, lf,
229
start_revision=rev, end_revision=rev)
234
def make_commits_with_trailing_newlines(wt):
235
"""Helper method for LogFormatter tests"""
238
open('a', 'wb').write('hello moto\n')
240
wt.commit('simple log message', rev_id='a1',
241
timestamp=1132586655.459960938, timezone=-6*3600,
242
committer='Joe Foo <joe@foo.com>')
243
open('b', 'wb').write('goodbye\n')
245
wt.commit('multiline\nlog\nmessage\n', rev_id='a2',
246
timestamp=1132586842.411175966, timezone=-6*3600,
247
committer='Joe Foo <joe@foo.com>',
248
author='Joe Bar <joe@bar.com>')
250
open('c', 'wb').write('just another manic monday\n')
252
wt.commit('single line with trailing newline\n', rev_id='a3',
253
timestamp=1132587176.835228920, timezone=-6*3600,
254
committer = 'Joe Foo <joe@foo.com>')
258
def normalize_log(log):
259
"""Replaces the variable lines of logs with fixed lines"""
260
author = 'author: Dolor Sit <test@example.com>'
261
committer = 'committer: Lorem Ipsum <test@example.com>'
262
lines = log.splitlines(True)
263
for idx,line in enumerate(lines):
264
stripped_line = line.lstrip()
265
indent = ' ' * (len(line) - len(stripped_line))
266
if stripped_line.startswith('author:'):
267
lines[idx] = indent + author + '\n'
268
elif stripped_line.startswith('committer:'):
269
lines[idx] = indent + committer + '\n'
270
elif stripped_line.startswith('timestamp:'):
271
lines[idx] = indent + 'timestamp: Just now\n'
272
return ''.join(lines)
275
class TestShortLogFormatter(TestCaseWithTransport):
320
277
def test_trailing_newlines(self):
321
278
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
279
b = make_commits_with_trailing_newlines(wt)
280
sio = self.make_utf8_encoded_stringio()
281
lf = ShortLogFormatter(to_file=sio)
283
self.assertEqualDiff(sio.getvalue(), """\
284
3 Joe Foo\t2005-11-21
325
285
single line with trailing newline
327
2 Joe Foo\t2005-11-22
287
2 Joe Bar\t2005-11-21
332
1 Joe Foo\t2005-11-22
292
1 Joe Foo\t2005-11-21
333
293
simple log message
336
b, log.ShortLogFormatter)
338
297
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)
298
wt = self.make_branch_and_memory_tree('.')
302
wt.commit('rev-1', rev_id='rev-1',
303
timestamp=1132586655, timezone=36000,
304
committer='Joe Foo <joe@foo.com>')
305
wt.commit('rev-merged', rev_id='rev-2a',
306
timestamp=1132586700, timezone=36000,
307
committer='Joe Foo <joe@foo.com>')
308
wt.set_parent_ids(['rev-1', 'rev-2a'])
309
wt.branch.set_last_revision_info(1, 'rev-1')
310
wt.commit('rev-2', rev_id='rev-2b',
311
timestamp=1132586800, timezone=36000,
312
committer='Joe Foo <joe@foo.com>')
313
logfile = self.make_utf8_encoded_stringio()
314
formatter = ShortLogFormatter(to_file=logfile)
315
show_log(wt.branch, formatter)
316
self.assertEqualDiff(logfile.getvalue(), """\
317
2 Joe Foo\t2005-11-22 [merge]
320
1 Joe Foo\t2005-11-22
396
327
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):
328
wt = self.make_branch_and_memory_tree('.')
332
wt.commit('rev-1', rev_id='rev-1',
333
timestamp=1132586655, timezone=36000,
334
committer='Joe Foo <joe@foo.com>')
335
wt.commit('rev-merged', rev_id='rev-2a',
336
timestamp=1132586700, timezone=36000,
337
committer='Joe Foo <joe@foo.com>')
338
wt.set_parent_ids(['rev-1', 'rev-2a'])
339
wt.branch.set_last_revision_info(1, 'rev-1')
340
wt.commit('rev-2', rev_id='rev-2b',
341
timestamp=1132586800, timezone=36000,
342
committer='Joe Foo <joe@foo.com>')
343
logfile = self.make_utf8_encoded_stringio()
344
formatter = ShortLogFormatter(to_file=logfile)
345
revspec = RevisionSpec.from_string('1.1.1')
347
rev = revspec.in_history(wtb)
348
show_log(wtb, formatter, start_revision=rev, end_revision=rev)
349
self.assertEqualDiff(logfile.getvalue(), """\
350
1.1.1 Joe Foo\t2005-11-22
358
class TestLongLogFormatter(TestCaseWithTransport):
472
360
def test_verbose_log(self):
473
361
"""Verbose log includes changed files
477
wt = self.make_standard_commit('test_verbose_log', authors=[])
478
self.assertFormatterResult('''\
365
wt = self.make_branch_and_tree('.')
367
self.build_tree(['a'])
369
# XXX: why does a longer nick show up?
370
b.nick = 'test_verbose_log'
371
wt.commit(message='add a',
372
timestamp=1132711707,
374
committer='Lorem Ipsum <test@example.com>')
375
logfile = file('out.tmp', 'w+')
376
formatter = LongLogFormatter(to_file=logfile)
377
show_log(b, formatter, verbose=True)
380
log_contents = logfile.read()
381
self.assertEqualDiff(log_contents, '''\
479
382
------------------------------------------------------------
481
384
committer: Lorem Ipsum <test@example.com>
482
385
branch nick: test_verbose_log
483
timestamp: Tue 2005-11-22 00:00:00 +0000
386
timestamp: Wed 2005-11-23 12:08:27 +1000
489
wt.branch, log.LongLogFormatter,
490
show_log_kwargs=dict(verbose=True))
492
393
def test_merges_are_indented_by_level(self):
493
394
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("""\
395
wt.commit('first post')
396
self.run_bzr('branch parent child')
397
self.run_bzr(['commit', '-m', 'branch 1', '--unchanged', 'child'])
398
self.run_bzr('branch child smallerchild')
399
self.run_bzr(['commit', '-m', 'branch 2', '--unchanged',
402
self.run_bzr('merge ../smallerchild')
403
self.run_bzr(['commit', '-m', 'merge branch 2'])
404
os.chdir('../parent')
405
self.run_bzr('merge ../child')
406
wt.commit('merge branch 1')
408
sio = self.make_utf8_encoded_stringio()
409
lf = LongLogFormatter(to_file=sio)
410
show_log(b, lf, verbose=True)
411
log = normalize_log(sio.getvalue())
412
self.assertEqualDiff(log, """\
504
413
------------------------------------------------------------
506
committer: Joe Foo <joe@foo.com>
415
committer: Lorem Ipsum <test@example.com>
507
416
branch nick: parent
508
timestamp: Tue 2005-11-22 00:00:04 +0000
511
420
------------------------------------------------------------
513
committer: Joe Foo <joe@foo.com>
422
committer: Lorem Ipsum <test@example.com>
514
423
branch nick: child
515
timestamp: Tue 2005-11-22 00:00:03 +0000
518
427
------------------------------------------------------------
520
committer: Joe Foo <joe@foo.com>
429
committer: Lorem Ipsum <test@example.com>
521
430
branch nick: smallerchild
522
timestamp: Tue 2005-11-22 00:00:02 +0000
525
434
------------------------------------------------------------
527
committer: Joe Foo <joe@foo.com>
436
committer: Lorem Ipsum <test@example.com>
528
437
branch nick: child
529
timestamp: Tue 2005-11-22 00:00:01 +0000
532
441
------------------------------------------------------------
534
committer: Joe Foo <joe@foo.com>
443
committer: Lorem Ipsum <test@example.com>
535
444
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
450
def test_verbose_merge_revisions_contain_deltas(self):
545
451
wt = self.make_branch_and_tree('parent')
546
452
self.build_tree(['parent/f1', 'parent/f2'])
547
453
wt.add(['f1','f2'])
548
self.wt_commit(wt, 'first post')
549
child_wt = wt.bzrdir.sprout('child').open_workingtree()
454
wt.commit('first post')
455
self.run_bzr('branch parent child')
550
456
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("""\
457
file('child/f2', 'wb').write('hello\n')
458
self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
461
self.run_bzr('merge ../child')
462
wt.commit('merge branch 1')
464
sio = self.make_utf8_encoded_stringio()
465
lf = LongLogFormatter(to_file=sio)
466
show_log(b, lf, verbose=True)
467
log = normalize_log(sio.getvalue())
468
self.assertEqualDiff(log, """\
556
469
------------------------------------------------------------
558
committer: Joe Foo <joe@foo.com>
471
committer: Lorem Ipsum <test@example.com>
559
472
branch nick: parent
560
timestamp: Tue 2005-11-22 00:00:02 +0000
615
529
committer: Joe Foo <joe@foo.com>
616
530
branch nick: test
617
timestamp: Tue 2005-11-22 00:00:00 +0000
531
timestamp: Mon 2005-11-21 09:24:15 -0600
619
533
simple log message
621
b, log.LongLogFormatter)
623
536
def test_author_in_log(self):
624
537
"""Log includes the author name if it's set in
625
538
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
540
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("""\
542
self.build_tree(['a'])
544
b.nick = 'test_author_log'
545
wt.commit(message='add a',
546
timestamp=1132711707,
548
committer='Lorem Ipsum <test@example.com>',
549
author='John Doe <jdoe@example.com>')
551
formatter = LongLogFormatter(to_file=sio)
552
show_log(b, formatter)
553
self.assertEqualDiff(sio.getvalue(), '''\
862
554
------------------------------------------------------------
864
556
author: John Doe <jdoe@example.com>
865
557
committer: Lorem Ipsum <test@example.com>
866
558
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):
559
timestamp: Wed 2005-11-23 12:08:27 +1000
566
class TestLineLogFormatter(TestCaseWithTransport):
902
568
def test_line_log(self):
903
569
"""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)
573
wt = self.make_branch_and_tree('.')
575
self.build_tree(['a'])
577
b.nick = 'test-line-log'
578
wt.commit(message='add a',
579
timestamp=1132711707,
581
committer='Line-Log-Formatter Tester <test@line.log>')
582
logfile = file('out.tmp', 'w+')
583
formatter = LineLogFormatter(to_file=logfile)
584
show_log(b, formatter)
587
log_contents = logfile.read()
588
self.assertEqualDiff(log_contents,
589
'1: Line-Log-Formatte... 2005-11-23 add a\n')
915
591
def test_trailing_newlines(self):
916
592
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)
593
b = make_commits_with_trailing_newlines(wt)
594
sio = self.make_utf8_encoded_stringio()
595
lf = LineLogFormatter(to_file=sio)
597
self.assertEqualDiff(sio.getvalue(), """\
598
3: Joe Foo 2005-11-21 single line with trailing newline
599
2: Joe Bar 2005-11-21 multiline
600
1: Joe Foo 2005-11-21 simple log message
925
603
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):
604
wt = self.make_branch_and_memory_tree('.')
608
wt.commit('rev-1', rev_id='rev-1',
609
timestamp=1132586655, timezone=36000,
610
committer='Joe Foo <joe@foo.com>')
611
wt.commit('rev-merged', rev_id='rev-2a',
612
timestamp=1132586700, timezone=36000,
613
committer='Joe Foo <joe@foo.com>')
614
wt.set_parent_ids(['rev-1', 'rev-2a'])
615
wt.branch.set_last_revision_info(1, 'rev-1')
616
wt.commit('rev-2', rev_id='rev-2b',
617
timestamp=1132586800, timezone=36000,
618
committer='Joe Foo <joe@foo.com>')
619
logfile = self.make_utf8_encoded_stringio()
620
formatter = LineLogFormatter(to_file=logfile)
621
revspec = RevisionSpec.from_string('1.1.1')
623
rev = revspec.in_history(wtb)
624
show_log(wtb, formatter, start_revision=rev, end_revision=rev)
625
self.assertEqualDiff(logfile.getvalue(), """\
626
1.1.1: Joe Foo 2005-11-22 rev-merged
633
class TestGetViewRevisions(TestCaseWithTransport):
635
def make_tree_with_commits(self):
636
"""Create a tree with well-known revision ids"""
637
wt = self.make_branch_and_tree('tree1')
638
wt.commit('commit one', rev_id='1')
639
wt.commit('commit two', rev_id='2')
640
wt.commit('commit three', rev_id='3')
641
mainline_revs = [None, '1', '2', '3']
642
rev_nos = {'1': 1, '2': 2, '3': 3}
643
return mainline_revs, rev_nos, wt
645
def make_tree_with_merges(self):
646
"""Create a tree with well-known revision ids and a merge"""
647
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
648
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
649
tree2.commit('four-a', rev_id='4a')
650
wt.merge_from_branch(tree2.branch)
651
wt.commit('four-b', rev_id='4b')
652
mainline_revs.append('4b')
655
return mainline_revs, rev_nos, wt
657
def make_tree_with_many_merges(self):
658
"""Create a tree with well-known revision ids"""
659
wt = self.make_branch_and_tree('tree1')
660
wt.commit('commit one', rev_id='1')
661
wt.commit('commit two', rev_id='2')
662
tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
663
tree3.commit('commit three a', rev_id='3a')
664
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
665
tree2.merge_from_branch(tree3.branch)
666
tree2.commit('commit three b', rev_id='3b')
667
wt.merge_from_branch(tree2.branch)
668
wt.commit('commit three c', rev_id='3c')
669
tree2.commit('four-a', rev_id='4a')
670
wt.merge_from_branch(tree2.branch)
671
wt.commit('four-b', rev_id='4b')
672
mainline_revs = [None, '1', '2', '3c', '4b']
673
rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
674
full_rev_nos_for_reference = {
677
'3a': '2.1.1', #first commit tree 3
678
'3b': '2.2.1', # first commit tree 2
679
'3c': '3', #merges 3b to main
680
'4a': '2.2.2', # second commit tree 2
681
'4b': '4', # merges 4a to main
683
return mainline_revs, rev_nos, wt
685
def test_get_view_revisions_forward(self):
686
"""Test the get_view_revisions method"""
687
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
689
self.addCleanup(wt.unlock)
690
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
692
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
694
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
695
'forward', include_merges=False))
696
self.assertEqual(revisions, revisions2)
698
def test_get_view_revisions_reverse(self):
699
"""Test the get_view_revisions with reverse"""
700
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
702
self.addCleanup(wt.unlock)
703
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
705
self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
707
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
708
'reverse', include_merges=False))
709
self.assertEqual(revisions, revisions2)
711
def test_get_view_revisions_merge(self):
712
"""Test get_view_revisions when there are merges"""
713
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
715
self.addCleanup(wt.unlock)
716
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
718
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
719
('4b', '4', 0), ('4a', '3.1.1', 1)],
721
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
722
'forward', include_merges=False))
723
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
727
def test_get_view_revisions_merge_reverse(self):
728
"""Test get_view_revisions in reverse when there are merges"""
729
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
731
self.addCleanup(wt.unlock)
732
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
734
self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
735
('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
737
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
738
'reverse', include_merges=False))
739
self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
743
def test_get_view_revisions_merge2(self):
744
"""Test get_view_revisions when there are merges"""
745
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
747
self.addCleanup(wt.unlock)
748
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
750
expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
751
('3a', '2.1.1', 1), ('3b', '2.2.1', 1), ('4b', '4', 0),
753
self.assertEqual(expected, revisions)
754
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
755
'forward', include_merges=False))
756
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
761
class TestGetRevisionsTouchingFileID(TestCaseWithTransport):
763
def create_tree_with_single_merge(self):
764
"""Create a branch with a moderate layout.
766
The revision graph looks like:
774
In this graph, A introduced files f1 and f2 and f3.
775
B modifies f1 and f3, and C modifies f2 and f3.
776
D merges the changes from B and C and resolves the conflict for f3.
778
# TODO: jam 20070218 This seems like it could really be done
779
# with make_branch_and_memory_tree() if we could just
780
# create the content of those files.
781
# TODO: jam 20070218 Another alternative is that we would really
782
# like to only create this tree 1 time for all tests that
783
# use it. Since 'log' only uses the tree in a readonly
784
# fashion, it seems a shame to regenerate an identical
785
# tree for each test.
786
tree = self.make_branch_and_tree('tree')
788
self.addCleanup(tree.unlock)
790
self.build_tree_contents([('tree/f1', 'A\n'),
794
tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
795
tree.commit('A', rev_id='A')
797
self.build_tree_contents([('tree/f2', 'A\nC\n'),
798
('tree/f3', 'A\nC\n'),
800
tree.commit('C', rev_id='C')
801
# Revert back to A to build the other history.
802
tree.set_last_revision('A')
803
tree.branch.set_last_revision_info(1, 'A')
804
self.build_tree_contents([('tree/f1', 'A\nB\n'),
806
('tree/f3', 'A\nB\n'),
808
tree.commit('B', rev_id='B')
809
tree.set_parent_ids(['B', 'C'])
810
self.build_tree_contents([('tree/f1', 'A\nB\n'),
811
('tree/f2', 'A\nC\n'),
812
('tree/f3', 'A\nB\nC\n'),
814
tree.commit('D', rev_id='D')
816
# Switch to a read lock for this tree.
817
# We still have addCleanup(unlock)
822
def test_tree_with_single_merge(self):
823
"""Make sure the tree layout is correct."""
824
tree = self.create_tree_with_single_merge()
825
rev_A_tree = tree.branch.repository.revision_tree('A')
826
rev_B_tree = tree.branch.repository.revision_tree('B')
828
f1_changed = (u'f1', 'f1-id', 'file', True, False)
829
f2_changed = (u'f2', 'f2-id', 'file', True, False)
830
f3_changed = (u'f3', 'f3-id', 'file', True, False)
832
delta = rev_B_tree.changes_from(rev_A_tree)
833
self.assertEqual([f1_changed, f3_changed], delta.modified)
834
self.assertEqual([], delta.renamed)
835
self.assertEqual([], delta.added)
836
self.assertEqual([], delta.removed)
838
rev_C_tree = tree.branch.repository.revision_tree('C')
839
delta = rev_C_tree.changes_from(rev_A_tree)
840
self.assertEqual([f2_changed, f3_changed], delta.modified)
841
self.assertEqual([], delta.renamed)
842
self.assertEqual([], delta.added)
843
self.assertEqual([], delta.removed)
845
rev_D_tree = tree.branch.repository.revision_tree('D')
846
delta = rev_D_tree.changes_from(rev_B_tree)
847
self.assertEqual([f2_changed, f3_changed], delta.modified)
848
self.assertEqual([], delta.renamed)
849
self.assertEqual([], delta.added)
850
self.assertEqual([], delta.removed)
852
delta = rev_D_tree.changes_from(rev_C_tree)
853
self.assertEqual([f1_changed, f3_changed], delta.modified)
854
self.assertEqual([], delta.renamed)
855
self.assertEqual([], delta.added)
856
self.assertEqual([], delta.removed)
858
def assertAllRevisionsForFileID(self, tree, file_id, revisions):
859
"""Make sure _filter_revisions_touching_file_id returns the right values.
861
Get the return value from _filter_revisions_touching_file_id and make
862
sure they are correct.
864
# The api for _get_revisions_touching_file_id is a little crazy,
865
# So we do the setup here.
866
mainline = tree.branch.revision_history()
867
mainline.insert(0, None)
868
revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
869
view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
871
actual_revs = log._filter_revisions_touching_file_id(
875
list(view_revs_iter))
876
self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
878
def test_file_id_f1(self):
879
tree = self.create_tree_with_single_merge()
880
# f1 should be marked as modified by revisions A and B
881
self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
883
def test_file_id_f2(self):
884
tree = self.create_tree_with_single_merge()
885
# f2 should be marked as modified by revisions A, C, and D
886
# because D merged the changes from C.
887
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
889
def test_file_id_f3(self):
890
tree = self.create_tree_with_single_merge()
891
# f3 should be marked as modified by revisions A, B, C, and D
892
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
894
def test_file_id_with_ghosts(self):
895
# This is testing bug #209948, where having a ghost would cause
896
# _filter_revisions_touching_file_id() to fail.
897
tree = self.create_tree_with_single_merge()
898
# We need to add a revision, so switch back to a write-locked tree
901
first_parent = tree.last_revision()
902
tree.set_parent_ids([first_parent, 'ghost-revision-id'])
903
self.build_tree_contents([('tree/f1', 'A\nB\nXX\n')])
904
tree.commit('commit with a ghost', rev_id='XX')
905
self.assertAllRevisionsForFileID(tree, 'f1-id', ['XX', 'B', 'A'])
906
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
909
class TestShowChangedRevisions(TestCaseWithTransport):
1022
911
def test_show_changed_revisions_verbose(self):
1023
912
tree = self.make_branch_and_tree('tree_a')
1030
919
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)
922
class TestLogFormatter(TestCase):
1040
924
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')
925
rev = Revision('a-id')
926
rev.committer = 'John Doe <jdoe@example.com>'
927
lf = LogFormatter(None)
928
self.assertEqual('John Doe', lf.short_committer(rev))
929
rev.committer = 'John Smith <jsmith@example.com>'
930
self.assertEqual('John Smith', lf.short_committer(rev))
931
rev.committer = 'John Smith'
932
self.assertEqual('John Smith', lf.short_committer(rev))
933
rev.committer = 'jsmith@example.com'
934
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
935
rev.committer = '<jsmith@example.com>'
936
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
937
rev.committer = 'John Smith jsmith@example.com'
938
self.assertEqual('John Smith', lf.short_committer(rev))
1052
940
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)
941
rev = Revision('a-id')
942
rev.committer = 'John Doe <jdoe@example.com>'
943
lf = LogFormatter(None)
944
self.assertEqual('John Doe', lf.short_author(rev))
945
rev.properties['author'] = 'John Smith <jsmith@example.com>'
946
self.assertEqual('John Smith', lf.short_author(rev))
947
rev.properties['author'] = 'John Smith'
948
self.assertEqual('John Smith', lf.short_author(rev))
949
rev.properties['author'] = 'jsmith@example.com'
950
self.assertEqual('jsmith@example.com', lf.short_author(rev))
951
rev.properties['author'] = '<jsmith@example.com>'
952
self.assertEqual('jsmith@example.com', lf.short_author(rev))
953
rev.properties['author'] = 'John Smith jsmith@example.com'
954
self.assertEqual('John Smith', lf.short_author(rev))