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
32
class TestLogMixin(object):
34
def wt_commit(self, wt, message, **kwargs):
35
"""Use some mostly fixed values for commits to simplify tests.
37
Tests can use this function to get some commit attributes. The time
38
stamp is incremented at each commit.
40
if getattr(self, 'timestamp', None) is None:
41
self.timestamp = 1132617600 # Mon 2005-11-22 00:00:00 +0000
43
self.timestamp += 1 # 1 second between each commit
44
kwargs.setdefault('timestamp', self.timestamp)
45
kwargs.setdefault('timezone', 0) # UTC
46
kwargs.setdefault('committer', 'Joe Foo <joe@foo.com>')
48
return wt.commit(message, **kwargs)
51
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):
54
super(TestCaseForLogFormatter, self).setUp()
44
super(TestCaseWithoutPropsHandler, self).setUp()
55
45
# keep a reference to the "current" custom prop. handler registry
56
self.properties_handler_registry = log.properties_handler_registry
57
# Use a clean registry for log
46
self.properties_handler_registry = \
47
log.properties_handler_registry
48
# clean up the registry in log
58
49
log.properties_handler_registry = registry.Registry()
61
log.properties_handler_registry = self.properties_handler_registry
62
self.addCleanup(restore)
64
def assertFormatterResult(self, result, branch, formatter_class,
65
formatter_kwargs=None, show_log_kwargs=None):
66
logfile = self.make_utf8_encoded_stringio()
67
if formatter_kwargs is None:
69
formatter = formatter_class(to_file=logfile, **formatter_kwargs)
70
if show_log_kwargs is None:
72
log.show_log(branch, formatter, **show_log_kwargs)
73
self.assertEqualDiff(result, logfile.getvalue())
75
def make_standard_commit(self, branch_nick, **kwargs):
76
wt = self.make_branch_and_tree('.')
78
self.addCleanup(wt.unlock)
79
self.build_tree(['a'])
81
wt.branch.nick = branch_nick
82
kwargs.setdefault('committer', 'Lorem Ipsum <test@example.com>')
83
kwargs.setdefault('authors', ['John Doe <jdoe@example.com>'])
84
self.wt_commit(wt, 'add a', **kwargs)
87
def make_commits_with_trailing_newlines(self, wt):
88
"""Helper method for LogFormatter tests"""
91
self.build_tree_contents([('a', 'hello moto\n')])
92
self.wt_commit(wt, 'simple log message', rev_id='a1')
93
self.build_tree_contents([('b', 'goodbye\n')])
95
self.wt_commit(wt, 'multiline\nlog\nmessage\n', rev_id='a2')
97
self.build_tree_contents([('c', 'just another manic monday\n')])
99
self.wt_commit(wt, 'single line with trailing newline\n', rev_id='a3')
102
def _prepare_tree_with_merges(self, with_tags=False):
103
wt = self.make_branch_and_memory_tree('.')
105
self.addCleanup(wt.unlock)
107
self.wt_commit(wt, 'rev-1', rev_id='rev-1')
108
self.wt_commit(wt, 'rev-merged', rev_id='rev-2a')
109
wt.set_parent_ids(['rev-1', 'rev-2a'])
110
wt.branch.set_last_revision_info(1, 'rev-1')
111
self.wt_commit(wt, 'rev-2', rev_id='rev-2b')
114
branch.tags.set_tag('v0.2', 'rev-2b')
115
self.wt_commit(wt, 'rev-3', rev_id='rev-3')
116
branch.tags.set_tag('v1.0rc1', 'rev-3')
117
branch.tags.set_tag('v1.0', 'rev-3')
121
class LogCatcher(log.LogFormatter):
122
"""Pull log messages into a list rather than displaying them.
124
To simplify testing we save logged revisions here rather than actually
125
formatting anything, so that we can precisely check the result without
126
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.
129
supports_merge_revisions = True
130
68
supports_delta = True
134
def __init__(self, *args, **kwargs):
135
kwargs.update(dict(to_file=None))
136
super(LogCatcher, self).__init__(*args, **kwargs)
71
super(LogCatcher, self).__init__(to_file=None)
139
74
def log_revision(self, revision):
140
self.revisions.append(revision)
143
class TestShowLog(tests.TestCaseWithTransport):
75
self.logs.append(revision)
78
class TestShowLog(TestCaseWithTransport):
145
80
def checkDelta(self, delta, **kw):
146
"""Check the filenames touched by a delta are as expected.
148
Caller only have to pass in the list of files for each part, all
149
unspecified parts are considered empty (and checked as such).
81
"""Check the filenames touched by a delta are as expected."""
151
82
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
152
# By default we expect an empty list
153
83
expected = kw.get(n, [])
154
84
# strip out only the path components
155
85
got = [x[0] for x in getattr(delta, n)]
156
self.assertEqual(expected, got)
158
def assertInvalidRevisonNumber(self, br, start, end):
160
self.assertRaises(errors.InvalidRevisionNumber,
161
log.show_log, br, lf,
162
start_revision=start, end_revision=end)
86
self.assertEquals(expected, got)
164
88
def test_cur_revno(self):
165
89
wt = self.make_branch_and_tree('.')
169
93
wt.commit('empty commit')
170
log.show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
172
# Since there is a single revision in the branch all the combinations
174
self.assertInvalidRevisonNumber(b, 2, 1)
175
self.assertInvalidRevisonNumber(b, 1, 2)
176
self.assertInvalidRevisonNumber(b, 0, 2)
177
self.assertInvalidRevisonNumber(b, 1, 0)
178
self.assertInvalidRevisonNumber(b, -1, 1)
179
self.assertInvalidRevisonNumber(b, 1, -1)
181
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
182
111
wt = self.make_branch_and_tree('.')
184
114
lf = LogCatcher()
185
log.show_log(wt.branch, lf)
187
self.assertEqual([], lf.revisions)
189
def test_empty_commit(self):
190
wt = self.make_branch_and_tree('.')
192
119
wt.commit('empty commit')
193
120
lf = LogCatcher()
194
log.show_log(wt.branch, lf, verbose=True)
196
self.assertEqual(1, len(revs))
197
self.assertEqual('1', revs[0].revno)
198
self.assertEqual('empty commit', revs[0].rev.message)
199
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)
201
def test_simple_commit(self):
202
wt = self.make_branch_and_tree('.')
203
wt.commit('empty commit')
204
129
self.build_tree(['hello'])
206
131
wt.commit('add one file',
207
132
committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
208
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
209
143
lf = LogCatcher()
210
log.show_log(wt.branch, lf, verbose=True)
211
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))
212
150
# first one is most recent
213
log_entry = lf.revisions[0]
214
self.assertEqual('2', log_entry.revno)
215
self.assertEqual('add one file', log_entry.rev.message)
216
self.checkDelta(log_entry.delta, added=['hello'])
218
def test_commit_message_with_control_chars(self):
219
wt = self.make_branch_and_tree('.')
220
msg = u"All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
221
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)
223
162
lf = LogCatcher()
224
log.show_log(wt.branch, lf, verbose=True)
225
committed_msg = lf.revisions[0].rev.message
226
if wt.branch.repository._serializer.squashes_xml_invalid_characters:
227
self.assertNotEqual(msg, committed_msg)
228
self.assertTrue(len(committed_msg) > len(msg))
230
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))
232
def test_commit_message_without_control_chars(self):
233
wt = self.make_branch_and_tree('.')
169
# Check that log message with only XML-valid characters isn't
234
170
# escaped. As ElementTree apparently does some kind of
235
171
# newline conversion, neither LF (\x0A) nor CR (\x0D) are
236
172
# included in the test commit message, even though they are
237
173
# valid XML 1.0 characters.
238
174
msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
175
self.log("original commit message: %r", msg)
240
177
lf = LogCatcher()
241
log.show_log(wt.branch, lf, verbose=True)
242
committed_msg = lf.revisions[0].rev.message
243
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)
245
183
def test_deltas_in_merge_revisions(self):
246
184
"""Check deltas created for both mainline and merge revisions"""
185
eq = self.assertEquals
247
186
wt = self.make_branch_and_tree('parent')
248
187
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
262
201
lf = LogCatcher()
263
202
lf.supports_merge_revisions = True
264
log.show_log(b, lf, verbose=True)
267
self.assertEqual(3, len(revs))
270
self.assertEqual('2', logentry.revno)
271
self.assertEqual('merge child branch', logentry.rev.message)
272
self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
275
self.assertEqual('1.1.1', logentry.revno)
276
self.assertEqual('remove file1 and modify file2', logentry.rev.message)
277
self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
280
self.assertEqual('1', logentry.revno)
281
self.assertEqual('add file1 and file2', logentry.rev.message)
282
self.checkDelta(logentry.delta, added=['file1', 'file2'])
285
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):
287
294
def test_trailing_newlines(self):
288
295
wt = self.make_branch_and_tree('.')
289
b = self.make_commits_with_trailing_newlines(wt)
290
self.assertFormatterResult("""\
291
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
292
302
single line with trailing newline
294
2 Joe Foo\t2005-11-22
304
2 Joe Bar\t2005-11-21
299
1 Joe Foo\t2005-11-22
309
1 Joe Foo\t2005-11-21
300
310
simple log message
303
b, log.ShortLogFormatter)
305
314
def test_short_log_with_merges(self):
306
wt = self._prepare_tree_with_merges()
307
self.assertFormatterResult("""\
308
2 Joe Foo\t2005-11-22 [merge]
311
1 Joe Foo\t2005-11-22
315
wt.branch, log.ShortLogFormatter)
317
def test_short_log_with_merges_and_advice(self):
318
wt = self._prepare_tree_with_merges()
319
self.assertFormatterResult("""\
320
2 Joe Foo\t2005-11-22 [merge]
323
1 Joe Foo\t2005-11-22
326
Use --include-merges or -n0 to see merged revisions.
328
wt.branch, log.ShortLogFormatter,
329
formatter_kwargs=dict(show_advice=True))
331
def test_short_log_with_merges_and_range(self):
332
wt = self._prepare_tree_with_merges()
333
self.wt_commit(wt, 'rev-3a', rev_id='rev-3a')
334
wt.branch.set_last_revision_info(2, 'rev-2b')
335
wt.set_parent_ids(['rev-2b', 'rev-3a'])
336
self.wt_commit(wt, 'rev-3b', rev_id='rev-3b')
337
self.assertFormatterResult("""\
338
3 Joe Foo\t2005-11-22 [merge]
341
2 Joe Foo\t2005-11-22 [merge]
345
wt.branch, log.ShortLogFormatter,
346
show_log_kwargs=dict(start_revision=2, end_revision=3))
348
def test_short_log_with_tags(self):
349
wt = self._prepare_tree_with_merges(with_tags=True)
350
self.assertFormatterResult("""\
351
3 Joe Foo\t2005-11-22 {v1.0, v1.0rc1}
354
2 Joe Foo\t2005-11-22 {v0.2} [merge]
357
1 Joe Foo\t2005-11-22
361
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
363
344
def test_short_log_single_merge_revision(self):
364
wt = self._prepare_tree_with_merges()
365
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
366
rev = revspec.in_history(wt.branch)
367
self.assertFormatterResult("""\
368
1.1.1 Joe Foo\t2005-11-22
372
wt.branch, log.ShortLogFormatter,
373
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
375
def test_show_ids(self):
376
wt = self.make_branch_and_tree('parent')
377
self.build_tree(['parent/f1', 'parent/f2'])
379
self.wt_commit(wt, 'first post', rev_id='a')
380
child_wt = wt.bzrdir.sprout('child').open_workingtree()
381
self.wt_commit(child_wt, 'branch 1 changes', rev_id='b')
382
wt.merge_from_branch(child_wt.branch)
383
self.wt_commit(wt, 'merge branch 1', rev_id='c')
384
self.assertFormatterResult("""\
385
2 Joe Foo\t2005-11-22 [merge]
389
1.1.1 Joe Foo\t2005-11-22
393
1 Joe Foo\t2005-11-22
398
wt.branch, log.ShortLogFormatter,
399
formatter_kwargs=dict(levels=0,show_ids=True))
402
class TestShortLogFormatterWithMergeRevisions(TestCaseForLogFormatter):
404
def test_short_merge_revs_log_with_merges(self):
405
wt = self._prepare_tree_with_merges()
406
# Note that the 1.1.1 indenting is in fact correct given that
407
# the revision numbers are right justified within 5 characters
408
# for mainline revnos and 9 characters for dotted revnos.
409
self.assertFormatterResult("""\
410
2 Joe Foo\t2005-11-22 [merge]
413
1.1.1 Joe Foo\t2005-11-22
416
1 Joe Foo\t2005-11-22
420
wt.branch, log.ShortLogFormatter,
421
formatter_kwargs=dict(levels=0))
423
def test_short_merge_revs_log_single_merge_revision(self):
424
wt = self._prepare_tree_with_merges()
425
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
426
rev = revspec.in_history(wt.branch)
427
self.assertFormatterResult("""\
428
1.1.1 Joe Foo\t2005-11-22
432
wt.branch, log.ShortLogFormatter,
433
formatter_kwargs=dict(levels=0),
434
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
437
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):
439
377
def test_verbose_log(self):
440
378
"""Verbose log includes changed files
444
wt = self.make_standard_commit('test_verbose_log', authors=[])
445
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, '''\
446
399
------------------------------------------------------------
448
401
committer: Lorem Ipsum <test@example.com>
449
402
branch nick: test_verbose_log
450
timestamp: Tue 2005-11-22 00:00:00 +0000
403
timestamp: Wed 2005-11-23 12:08:27 +1000
456
wt.branch, log.LongLogFormatter,
457
show_log_kwargs=dict(verbose=True))
459
410
def test_merges_are_indented_by_level(self):
460
411
wt = self.make_branch_and_tree('parent')
461
self.wt_commit(wt, 'first post')
462
child_wt = wt.bzrdir.sprout('child').open_workingtree()
463
self.wt_commit(child_wt, 'branch 1')
464
smallerchild_wt = wt.bzrdir.sprout('smallerchild').open_workingtree()
465
self.wt_commit(smallerchild_wt, 'branch 2')
466
child_wt.merge_from_branch(smallerchild_wt.branch)
467
self.wt_commit(child_wt, 'merge branch 2')
468
wt.merge_from_branch(child_wt.branch)
469
self.wt_commit(wt, 'merge branch 1')
470
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, """\
471
430
------------------------------------------------------------
473
committer: Joe Foo <joe@foo.com>
432
committer: Lorem Ipsum <test@example.com>
474
433
branch nick: parent
475
timestamp: Tue 2005-11-22 00:00:04 +0000
478
437
------------------------------------------------------------
480
committer: Joe Foo <joe@foo.com>
439
committer: Lorem Ipsum <test@example.com>
481
440
branch nick: child
482
timestamp: Tue 2005-11-22 00:00:03 +0000
485
444
------------------------------------------------------------
487
committer: Joe Foo <joe@foo.com>
446
committer: Lorem Ipsum <test@example.com>
488
447
branch nick: smallerchild
489
timestamp: Tue 2005-11-22 00:00:02 +0000
492
451
------------------------------------------------------------
494
committer: Joe Foo <joe@foo.com>
453
committer: Lorem Ipsum <test@example.com>
495
454
branch nick: child
496
timestamp: Tue 2005-11-22 00:00:01 +0000
499
458
------------------------------------------------------------
501
committer: Joe Foo <joe@foo.com>
460
committer: Lorem Ipsum <test@example.com>
502
461
branch nick: parent
503
timestamp: Tue 2005-11-22 00:00:00 +0000
507
wt.branch, log.LongLogFormatter,
508
formatter_kwargs=dict(levels=0),
509
show_log_kwargs=dict(verbose=True))
511
467
def test_verbose_merge_revisions_contain_deltas(self):
512
468
wt = self.make_branch_and_tree('parent')
513
469
self.build_tree(['parent/f1', 'parent/f2'])
514
470
wt.add(['f1','f2'])
515
self.wt_commit(wt, 'first post')
516
child_wt = wt.bzrdir.sprout('child').open_workingtree()
471
wt.commit('first post')
472
self.run_bzr('branch parent child')
517
473
os.unlink('child/f1')
518
self.build_tree_contents([('child/f2', 'hello\n')])
519
self.wt_commit(child_wt, 'removed f1 and modified f2')
520
wt.merge_from_branch(child_wt.branch)
521
self.wt_commit(wt, 'merge branch 1')
522
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, """\
523
486
------------------------------------------------------------
525
committer: Joe Foo <joe@foo.com>
488
committer: Lorem Ipsum <test@example.com>
526
489
branch nick: parent
527
timestamp: Tue 2005-11-22 00:00:02 +0000
582
546
committer: Joe Foo <joe@foo.com>
583
547
branch nick: test
584
timestamp: Tue 2005-11-22 00:00:00 +0000
548
timestamp: Mon 2005-11-21 09:24:15 -0600
586
550
simple log message
588
b, log.LongLogFormatter)
590
553
def test_author_in_log(self):
591
554
"""Log includes the author name if it's set in
592
555
the revision properties
594
wt = self.make_standard_commit('test_author_log',
595
authors=['John Doe <jdoe@example.com>',
596
'Jane Rey <jrey@example.com>'])
597
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(), '''\
598
571
------------------------------------------------------------
600
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
573
author: John Doe <jdoe@example.com>
601
574
committer: Lorem Ipsum <test@example.com>
602
575
branch nick: test_author_log
603
timestamp: Tue 2005-11-22 00:00:00 +0000
576
timestamp: Wed 2005-11-23 12:08:27 +1000
607
wt.branch, log.LongLogFormatter)
609
581
def test_properties_in_log(self):
610
"""Log includes the custom properties returned by the registered
582
"""Log includes the custom properties returned by the registered
613
wt = self.make_standard_commit('test_properties_in_log')
614
def trivial_custom_prop_handler(revision):
615
return {'test_prop':'test_value'}
617
# Cleaned up in setUp()
618
log.properties_handler_registry.register(
619
'trivial_custom_prop_handler',
620
trivial_custom_prop_handler)
621
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(), '''\
622
609
------------------------------------------------------------
624
611
test_prop: test_value
625
612
author: John Doe <jdoe@example.com>
626
613
committer: Lorem Ipsum <test@example.com>
627
614
branch nick: test_properties_in_log
628
timestamp: Tue 2005-11-22 00:00:00 +0000
615
timestamp: Wed 2005-11-23 12:08:27 +1000
632
wt.branch, log.LongLogFormatter)
634
def test_properties_in_short_log(self):
635
"""Log includes the custom properties returned by the registered
638
wt = self.make_standard_commit('test_properties_in_short_log')
639
def trivial_custom_prop_handler(revision):
640
return {'test_prop':'test_value'}
642
log.properties_handler_registry.register(
643
'trivial_custom_prop_handler',
644
trivial_custom_prop_handler)
645
self.assertFormatterResult("""\
646
1 John Doe\t2005-11-22
647
test_prop: test_value
651
wt.branch, log.ShortLogFormatter)
653
620
def test_error_in_properties_handler(self):
654
"""Log includes the custom properties returned by the registered
621
"""Log includes the custom properties returned by the registered
657
wt = self.make_standard_commit('error_in_properties_handler',
658
revprops={'first_prop':'first_value'})
659
sio = self.make_utf8_encoded_stringio()
660
formatter = log.LongLogFormatter(to_file=sio)
661
def trivial_custom_prop_handler(revision):
662
raise StandardError("a test error")
664
log.properties_handler_registry.register(
665
'trivial_custom_prop_handler',
666
trivial_custom_prop_handler)
667
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')
669
649
def test_properties_handler_bad_argument(self):
670
wt = self.make_standard_commit('bad_argument',
671
revprops={'a_prop':'test_value'})
672
sio = self.make_utf8_encoded_stringio()
673
formatter = log.LongLogFormatter(to_file=sio)
674
def bad_argument_prop_handler(revision):
675
return {'custom_prop_name':revision.properties['a_prop']}
677
log.properties_handler_registry.register(
678
'bad_argument_prop_handler',
679
bad_argument_prop_handler)
681
self.assertRaises(AttributeError, formatter.show_properties,
684
revision = wt.branch.repository.get_revision(wt.branch.last_revision())
685
formatter.show_properties(revision, '')
686
self.assertEqualDiff('''custom_prop_name: test_value\n''',
689
def test_show_ids(self):
690
wt = self.make_branch_and_tree('parent')
691
self.build_tree(['parent/f1', 'parent/f2'])
693
self.wt_commit(wt, 'first post', rev_id='a')
694
child_wt = wt.bzrdir.sprout('child').open_workingtree()
695
self.wt_commit(child_wt, 'branch 1 changes', rev_id='b')
696
wt.merge_from_branch(child_wt.branch)
697
self.wt_commit(wt, 'merge branch 1', rev_id='c')
698
self.assertFormatterResult("""\
699
------------------------------------------------------------
704
committer: Joe Foo <joe@foo.com>
706
timestamp: Tue 2005-11-22 00:00:02 +0000
709
------------------------------------------------------------
713
committer: Joe Foo <joe@foo.com>
715
timestamp: Tue 2005-11-22 00:00:01 +0000
718
------------------------------------------------------------
721
committer: Joe Foo <joe@foo.com>
723
timestamp: Tue 2005-11-22 00:00:00 +0000
727
wt.branch, log.LongLogFormatter,
728
formatter_kwargs=dict(levels=0,show_ids=True))
731
class TestLongLogFormatterWithoutMergeRevisions(TestCaseForLogFormatter):
733
def test_long_verbose_log(self):
734
"""Verbose log includes changed files
738
wt = self.make_standard_commit('test_long_verbose_log', authors=[])
739
self.assertFormatterResult("""\
740
------------------------------------------------------------
742
committer: Lorem Ipsum <test@example.com>
743
branch nick: test_long_verbose_log
744
timestamp: Tue 2005-11-22 00:00:00 +0000
750
wt.branch, log.LongLogFormatter,
751
formatter_kwargs=dict(levels=1),
752
show_log_kwargs=dict(verbose=True))
754
def test_long_verbose_contain_deltas(self):
755
wt = self.make_branch_and_tree('parent')
756
self.build_tree(['parent/f1', 'parent/f2'])
758
self.wt_commit(wt, 'first post')
759
child_wt = wt.bzrdir.sprout('child').open_workingtree()
760
os.unlink('child/f1')
761
self.build_tree_contents([('child/f2', 'hello\n')])
762
self.wt_commit(child_wt, 'removed f1 and modified f2')
763
wt.merge_from_branch(child_wt.branch)
764
self.wt_commit(wt, 'merge branch 1')
765
self.assertFormatterResult("""\
766
------------------------------------------------------------
768
committer: Joe Foo <joe@foo.com>
770
timestamp: Tue 2005-11-22 00:00:02 +0000
777
------------------------------------------------------------
779
committer: Joe Foo <joe@foo.com>
781
timestamp: Tue 2005-11-22 00:00:00 +0000
788
wt.branch, log.LongLogFormatter,
789
formatter_kwargs=dict(levels=1),
790
show_log_kwargs=dict(verbose=True))
792
def test_long_trailing_newlines(self):
793
650
wt = self.make_branch_and_tree('.')
794
b = self.make_commits_with_trailing_newlines(wt)
795
self.assertFormatterResult("""\
796
------------------------------------------------------------
798
committer: Joe Foo <joe@foo.com>
800
timestamp: Tue 2005-11-22 00:00:02 +0000
802
single line with trailing newline
803
------------------------------------------------------------
805
committer: Joe Foo <joe@foo.com>
807
timestamp: Tue 2005-11-22 00:00:01 +0000
812
------------------------------------------------------------
814
committer: Joe Foo <joe@foo.com>
816
timestamp: Tue 2005-11-22 00:00:00 +0000
820
b, log.LongLogFormatter,
821
formatter_kwargs=dict(levels=1))
823
def test_long_author_in_log(self):
824
"""Log includes the author name if it's set in
825
the revision properties
827
wt = self.make_standard_commit('test_author_log')
828
self.assertFormatterResult("""\
829
------------------------------------------------------------
831
author: John Doe <jdoe@example.com>
832
committer: Lorem Ipsum <test@example.com>
833
branch nick: test_author_log
834
timestamp: Tue 2005-11-22 00:00:00 +0000
838
wt.branch, log.LongLogFormatter,
839
formatter_kwargs=dict(levels=1))
841
def test_long_properties_in_log(self):
842
"""Log includes the custom properties returned by the registered
845
wt = self.make_standard_commit('test_properties_in_log')
846
def trivial_custom_prop_handler(revision):
847
return {'test_prop':'test_value'}
849
log.properties_handler_registry.register(
850
'trivial_custom_prop_handler',
851
trivial_custom_prop_handler)
852
self.assertFormatterResult("""\
853
------------------------------------------------------------
855
test_prop: test_value
856
author: John Doe <jdoe@example.com>
857
committer: Lorem Ipsum <test@example.com>
858
branch nick: test_properties_in_log
859
timestamp: Tue 2005-11-22 00:00:00 +0000
863
wt.branch, log.LongLogFormatter,
864
formatter_kwargs=dict(levels=1))
867
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):
869
685
def test_line_log(self):
870
686
"""Line log should show revno
874
wt = self.make_standard_commit('test-line-log',
875
committer='Line-Log-Formatter Tester <test@line.log>',
877
self.assertFormatterResult("""\
878
1: Line-Log-Formatte... 2005-11-22 add a
880
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')
882
708
def test_trailing_newlines(self):
883
709
wt = self.make_branch_and_tree('.')
884
b = self.make_commits_with_trailing_newlines(wt)
885
self.assertFormatterResult("""\
886
3: Joe Foo 2005-11-22 single line with trailing newline
887
2: Joe Foo 2005-11-22 multiline
888
1: Joe Foo 2005-11-22 simple log message
890
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
892
720
def test_line_log_single_merge_revision(self):
893
wt = self._prepare_tree_with_merges()
894
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
895
rev = revspec.in_history(wt.branch)
896
self.assertFormatterResult("""\
897
1.1.1: Joe Foo 2005-11-22 rev-merged
899
wt.branch, log.LineLogFormatter,
900
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
902
def test_line_log_with_tags(self):
903
wt = self._prepare_tree_with_merges(with_tags=True)
904
self.assertFormatterResult("""\
905
3: Joe Foo 2005-11-22 {v1.0, v1.0rc1} rev-3
906
2: Joe Foo 2005-11-22 [merge] {v0.2} rev-2
907
1: Joe Foo 2005-11-22 rev-1
909
wt.branch, log.LineLogFormatter)
912
class TestLineLogFormatterWithMergeRevisions(TestCaseForLogFormatter):
914
def test_line_merge_revs_log(self):
915
"""Line log should show revno
919
wt = self.make_standard_commit('test-line-log',
920
committer='Line-Log-Formatter Tester <test@line.log>',
922
self.assertFormatterResult("""\
923
1: Line-Log-Formatte... 2005-11-22 add a
925
wt.branch, log.LineLogFormatter)
927
def test_line_merge_revs_log_single_merge_revision(self):
928
wt = self._prepare_tree_with_merges()
929
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
930
rev = revspec.in_history(wt.branch)
931
self.assertFormatterResult("""\
932
1.1.1: Joe Foo 2005-11-22 rev-merged
934
wt.branch, log.LineLogFormatter,
935
formatter_kwargs=dict(levels=0),
936
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
938
def test_line_merge_revs_log_with_merges(self):
939
wt = self._prepare_tree_with_merges()
940
self.assertFormatterResult("""\
941
2: Joe Foo 2005-11-22 [merge] rev-2
942
1.1.1: Joe Foo 2005-11-22 rev-merged
943
1: Joe Foo 2005-11-22 rev-1
945
wt.branch, log.LineLogFormatter,
946
formatter_kwargs=dict(levels=0))
949
class TestGnuChangelogFormatter(TestCaseForLogFormatter):
951
def test_gnu_changelog(self):
952
wt = self.make_standard_commit('nicky', authors=[])
953
self.assertFormatterResult('''\
954
2005-11-22 Lorem Ipsum <test@example.com>
959
wt.branch, log.GnuChangelogLogFormatter)
961
def test_with_authors(self):
962
wt = self.make_standard_commit('nicky',
963
authors=['Fooa Fooz <foo@example.com>',
964
'Bari Baro <bar@example.com>'])
965
self.assertFormatterResult('''\
966
2005-11-22 Fooa Fooz <foo@example.com>
971
wt.branch, log.GnuChangelogLogFormatter)
973
def test_verbose(self):
974
wt = self.make_standard_commit('nicky')
975
self.assertFormatterResult('''\
976
2005-11-22 John Doe <jdoe@example.com>
983
wt.branch, log.GnuChangelogLogFormatter,
984
show_log_kwargs=dict(verbose=True))
987
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):
989
1028
def test_show_changed_revisions_verbose(self):
990
1029
tree = self.make_branch_and_tree('tree_a')
997
1036
self.assertNotContainsRe(s.getvalue(), 'foo')
1000
class TestLogFormatter(tests.TestCase):
1003
super(TestLogFormatter, self).setUp()
1004
self.rev = revision.Revision('a-id')
1005
self.lf = log.LogFormatter(None)
1039
class TestLogFormatter(TestCase):
1007
1041
def test_short_committer(self):
1008
def assertCommitter(expected, committer):
1009
self.rev.committer = committer
1010
self.assertEqual(expected, self.lf.short_committer(self.rev))
1012
assertCommitter('John Doe', 'John Doe <jdoe@example.com>')
1013
assertCommitter('John Smith', 'John Smith <jsmith@example.com>')
1014
assertCommitter('John Smith', 'John Smith')
1015
assertCommitter('jsmith@example.com', 'jsmith@example.com')
1016
assertCommitter('jsmith@example.com', '<jsmith@example.com>')
1017
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))
1019
1057
def test_short_author(self):
1020
def assertAuthor(expected, author):
1021
self.rev.properties['author'] = author
1022
self.assertEqual(expected, self.lf.short_author(self.rev))
1024
assertAuthor('John Smith', 'John Smith <jsmith@example.com>')
1025
assertAuthor('John Smith', 'John Smith')
1026
assertAuthor('jsmith@example.com', 'jsmith@example.com')
1027
assertAuthor('jsmith@example.com', '<jsmith@example.com>')
1028
assertAuthor('John Smith', 'John Smith jsmith@example.com')
1030
def test_short_author_from_committer(self):
1031
self.rev.committer = 'John Doe <jdoe@example.com>'
1032
self.assertEqual('John Doe', self.lf.short_author(self.rev))
1034
def test_short_author_from_authors(self):
1035
self.rev.properties['authors'] = ('John Smith <jsmith@example.com>\n'
1036
'Jane Rey <jrey@example.com>')
1037
self.assertEqual('John Smith', self.lf.short_author(self.rev))
1040
class TestReverseByDepth(tests.TestCase):
1041
"""Test reverse_by_depth behavior.
1043
This is used to present revisions in forward (oldest first) order in a nice
1046
The tests use lighter revision description to ease reading.
1049
def assertReversed(self, forward, backward):
1050
# Transform the descriptions to suit the API: tests use (revno, depth),
1051
# while the API expects (revid, revno, depth)
1052
def complete_revisions(l):
1053
"""Transform the description to suit the API.
1055
Tests use (revno, depth) whil the API expects (revid, revno, depth).
1056
Since the revid is arbitrary, we just duplicate revno
1058
return [ (r, r, d) for r, d in l]
1059
forward = complete_revisions(forward)
1060
backward= complete_revisions(backward)
1061
self.assertEqual(forward, log.reverse_by_depth(backward))
1064
def test_mainline_revisions(self):
1065
self.assertReversed([( '1', 0), ('2', 0)],
1066
[('2', 0), ('1', 0)])
1068
def test_merged_revisions(self):
1069
self.assertReversed([('1', 0), ('2', 0), ('2.2', 1), ('2.1', 1),],
1070
[('2', 0), ('2.1', 1), ('2.2', 1), ('1', 0),])
1071
def test_shifted_merged_revisions(self):
1072
"""Test irregular layout.
1074
Requesting revisions touching a file can produce "holes" in the depths.
1076
self.assertReversed([('1', 0), ('2', 0), ('1.1', 2), ('1.2', 2),],
1077
[('2', 0), ('1.2', 2), ('1.1', 2), ('1', 0),])
1079
def test_merged_without_child_revisions(self):
1080
"""Test irregular layout.
1082
Revision ranges can produce "holes" in the depths.
1084
# When a revision of higher depth doesn't follow one of lower depth, we
1085
# assume a lower depth one is virtually there
1086
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1087
[('4', 4), ('3', 3), ('2', 2), ('1', 2),])
1088
# So we get the same order after reversing below even if the original
1089
# revisions are not in the same order.
1090
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1091
[('3', 3), ('4', 4), ('2', 2), ('1', 2),])
1094
class TestHistoryChange(tests.TestCaseWithTransport):
1096
def setup_a_tree(self):
1097
tree = self.make_branch_and_tree('tree')
1099
self.addCleanup(tree.unlock)
1100
tree.commit('1a', rev_id='1a')
1101
tree.commit('2a', rev_id='2a')
1102
tree.commit('3a', rev_id='3a')
1105
def setup_ab_tree(self):
1106
tree = self.setup_a_tree()
1107
tree.set_last_revision('1a')
1108
tree.branch.set_last_revision_info(1, '1a')
1109
tree.commit('2b', rev_id='2b')
1110
tree.commit('3b', rev_id='3b')
1113
def setup_ac_tree(self):
1114
tree = self.setup_a_tree()
1115
tree.set_last_revision(revision.NULL_REVISION)
1116
tree.branch.set_last_revision_info(0, revision.NULL_REVISION)
1117
tree.commit('1c', rev_id='1c')
1118
tree.commit('2c', rev_id='2c')
1119
tree.commit('3c', rev_id='3c')
1122
def test_all_new(self):
1123
tree = self.setup_ab_tree()
1124
old, new = log.get_history_change('1a', '3a', tree.branch.repository)
1125
self.assertEqual([], old)
1126
self.assertEqual(['2a', '3a'], new)
1128
def test_all_old(self):
1129
tree = self.setup_ab_tree()
1130
old, new = log.get_history_change('3a', '1a', tree.branch.repository)
1131
self.assertEqual([], new)
1132
self.assertEqual(['2a', '3a'], old)
1134
def test_null_old(self):
1135
tree = self.setup_ab_tree()
1136
old, new = log.get_history_change(revision.NULL_REVISION,
1137
'3a', tree.branch.repository)
1138
self.assertEqual([], old)
1139
self.assertEqual(['1a', '2a', '3a'], new)
1141
def test_null_new(self):
1142
tree = self.setup_ab_tree()
1143
old, new = log.get_history_change('3a', revision.NULL_REVISION,
1144
tree.branch.repository)
1145
self.assertEqual([], new)
1146
self.assertEqual(['1a', '2a', '3a'], old)
1148
def test_diverged(self):
1149
tree = self.setup_ab_tree()
1150
old, new = log.get_history_change('3a', '3b', tree.branch.repository)
1151
self.assertEqual(old, ['2a', '3a'])
1152
self.assertEqual(new, ['2b', '3b'])
1154
def test_unrelated(self):
1155
tree = self.setup_ac_tree()
1156
old, new = log.get_history_change('3a', '3c', tree.branch.repository)
1157
self.assertEqual(old, ['1a', '2a', '3a'])
1158
self.assertEqual(new, ['1c', '2c', '3c'])
1160
def test_show_branch_change(self):
1161
tree = self.setup_ab_tree()
1163
log.show_branch_change(tree.branch, s, 3, '3a')
1164
self.assertContainsRe(s.getvalue(),
1165
'[*]{60}\nRemoved Revisions:\n(.|\n)*2a(.|\n)*3a(.|\n)*'
1166
'[*]{60}\n\nAdded Revisions:\n(.|\n)*2b(.|\n)*3b')
1168
def test_show_branch_change_no_change(self):
1169
tree = self.setup_ab_tree()
1171
log.show_branch_change(tree.branch, s, 3, '3b')
1172
self.assertEqual(s.getvalue(),
1173
'Nothing seems to have changed\n')
1175
def test_show_branch_change_no_old(self):
1176
tree = self.setup_ab_tree()
1178
log.show_branch_change(tree.branch, s, 2, '2b')
1179
self.assertContainsRe(s.getvalue(), 'Added Revisions:')
1180
self.assertNotContainsRe(s.getvalue(), 'Removed Revisions:')
1182
def test_show_branch_change_no_new(self):
1183
tree = self.setup_ab_tree()
1184
tree.branch.set_last_revision_info(2, '2b')
1186
log.show_branch_change(tree.branch, s, 3, '3b')
1187
self.assertContainsRe(s.getvalue(), 'Removed Revisions:')
1188
self.assertNotContainsRe(s.getvalue(), 'Added Revisions:')
1191
class TestRevisionNotInBranch(TestCaseForLogFormatter):
1193
def setup_a_tree(self):
1194
tree = self.make_branch_and_tree('tree')
1196
self.addCleanup(tree.unlock)
1198
'committer': 'Joe Foo <joe@foo.com>',
1199
'timestamp': 1132617600, # Mon 2005-11-22 00:00:00 +0000
1200
'timezone': 0, # UTC
1202
tree.commit('commit 1a', rev_id='1a', **kwargs)
1203
tree.commit('commit 2a', rev_id='2a', **kwargs)
1204
tree.commit('commit 3a', rev_id='3a', **kwargs)
1207
def setup_ab_tree(self):
1208
tree = self.setup_a_tree()
1209
tree.set_last_revision('1a')
1210
tree.branch.set_last_revision_info(1, '1a')
1212
'committer': 'Joe Foo <joe@foo.com>',
1213
'timestamp': 1132617600, # Mon 2005-11-22 00:00:00 +0000
1214
'timezone': 0, # UTC
1216
tree.commit('commit 2b', rev_id='2b', **kwargs)
1217
tree.commit('commit 3b', rev_id='3b', **kwargs)
1220
def test_one_revision(self):
1221
tree = self.setup_ab_tree()
1223
rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
1224
log.show_log(tree.branch, lf, verbose=True, start_revision=rev,
1226
self.assertEqual(1, len(lf.revisions))
1227
self.assertEqual(None, lf.revisions[0].revno) # Out-of-branch
1228
self.assertEqual('3a', lf.revisions[0].rev.revision_id)
1230
def test_many_revisions(self):
1231
tree = self.setup_ab_tree()
1233
start_rev = revisionspec.RevisionInfo(tree.branch, None, '1a')
1234
end_rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
1235
log.show_log(tree.branch, lf, verbose=True, start_revision=start_rev,
1236
end_revision=end_rev)
1237
self.assertEqual(3, len(lf.revisions))
1238
self.assertEqual(None, lf.revisions[0].revno) # Out-of-branch
1239
self.assertEqual('3a', lf.revisions[0].rev.revision_id)
1240
self.assertEqual(None, lf.revisions[1].revno) # Out-of-branch
1241
self.assertEqual('2a', lf.revisions[1].rev.revision_id)
1242
self.assertEqual('1', lf.revisions[2].revno) # In-branch
1244
def test_long_format(self):
1245
tree = self.setup_ab_tree()
1246
start_rev = revisionspec.RevisionInfo(tree.branch, None, '1a')
1247
end_rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
1248
self.assertFormatterResult("""\
1249
------------------------------------------------------------
1251
committer: Joe Foo <joe@foo.com>
1253
timestamp: Tue 2005-11-22 00:00:00 +0000
1256
------------------------------------------------------------
1258
committer: Joe Foo <joe@foo.com>
1260
timestamp: Tue 2005-11-22 00:00:00 +0000
1263
------------------------------------------------------------
1265
committer: Joe Foo <joe@foo.com>
1267
timestamp: Tue 2005-11-22 00:00:00 +0000
1271
tree.branch, log.LongLogFormatter, show_log_kwargs={
1272
'start_revision': start_rev, 'end_revision': end_rev
1275
def test_short_format(self):
1276
tree = self.setup_ab_tree()
1277
start_rev = revisionspec.RevisionInfo(tree.branch, None, '1a')
1278
end_rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
1279
self.assertFormatterResult("""\
1288
1 Joe Foo\t2005-11-22
1292
tree.branch, log.ShortLogFormatter, show_log_kwargs={
1293
'start_revision': start_rev, 'end_revision': end_rev
1296
def test_line_format(self):
1297
tree = self.setup_ab_tree()
1298
start_rev = revisionspec.RevisionInfo(tree.branch, None, '1a')
1299
end_rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
1300
self.assertFormatterResult("""\
1301
Joe Foo 2005-11-22 commit 3a
1302
Joe Foo 2005-11-22 commit 2a
1303
1: Joe Foo 2005-11-22 commit 1a
1305
tree.branch, log.LineLogFormatter, show_log_kwargs={
1306
'start_revision': start_rev, 'end_revision': end_rev
1310
class TestLogWithBugs(TestCaseForLogFormatter, TestLogMixin):
1313
TestCaseForLogFormatter.setUp(self)
1314
log.properties_handler_registry.register(
1315
'bugs_properties_handler',
1316
log._bugs_properties_handler)
1318
def make_commits_with_bugs(self):
1319
"""Helper method for LogFormatter tests"""
1320
tree = self.make_branch_and_tree(u'.')
1321
self.build_tree(['a', 'b'])
1323
self.wt_commit(tree, 'simple log message', rev_id='a1',
1324
revprops={'bugs': 'test://bug/id fixed'})
1326
self.wt_commit(tree, 'multiline\nlog\nmessage\n', rev_id='a2',
1327
authors=['Joe Bar <joe@bar.com>'],
1328
revprops={'bugs': 'test://bug/id fixed\n'
1329
'test://bug/2 fixed'})
1333
def test_long_bugs(self):
1334
tree = self.make_commits_with_bugs()
1335
self.assertFormatterResult("""\
1336
------------------------------------------------------------
1338
fixes bug(s): test://bug/id test://bug/2
1339
author: Joe Bar <joe@bar.com>
1340
committer: Joe Foo <joe@foo.com>
1342
timestamp: Tue 2005-11-22 00:00:01 +0000
1347
------------------------------------------------------------
1349
fixes bug(s): test://bug/id
1350
committer: Joe Foo <joe@foo.com>
1352
timestamp: Tue 2005-11-22 00:00:00 +0000
1356
tree.branch, log.LongLogFormatter)
1358
def test_short_bugs(self):
1359
tree = self.make_commits_with_bugs()
1360
self.assertFormatterResult("""\
1361
2 Joe Bar\t2005-11-22
1362
fixes bug(s): test://bug/id test://bug/2
1367
1 Joe Foo\t2005-11-22
1368
fixes bug(s): test://bug/id
1372
tree.branch, log.ShortLogFormatter)
1374
def test_wrong_bugs_property(self):
1375
tree = self.make_branch_and_tree(u'.')
1376
self.build_tree(['foo'])
1377
self.wt_commit(tree, 'simple log message', rev_id='a1',
1378
revprops={'bugs': 'test://bug/id invalid_value'})
1379
self.assertFormatterResult("""\
1380
1 Joe Foo\t2005-11-22
1384
tree.branch, log.ShortLogFormatter)
1386
def test_bugs_handler_present(self):
1387
self.properties_handler_registry.get('bugs_properties_handler')
1390
class TestLogForAuthors(TestCaseForLogFormatter):
1393
TestCaseForLogFormatter.setUp(self)
1394
self.wt = self.make_standard_commit('nicky',
1395
authors=['John Doe <jdoe@example.com>',
1396
'Jane Rey <jrey@example.com>'])
1398
def assertFormatterResult(self, formatter, who, result):
1399
formatter_kwargs = dict()
1401
author_list_handler = log.author_list_registry.get(who)
1402
formatter_kwargs['author_list_handler'] = author_list_handler
1403
TestCaseForLogFormatter.assertFormatterResult(self, result,
1404
self.wt.branch, formatter, formatter_kwargs=formatter_kwargs)
1406
def test_line_default(self):
1407
self.assertFormatterResult(log.LineLogFormatter, None, """\
1408
1: John Doe 2005-11-22 add a
1411
def test_line_committer(self):
1412
self.assertFormatterResult(log.LineLogFormatter, 'committer', """\
1413
1: Lorem Ipsum 2005-11-22 add a
1416
def test_line_first(self):
1417
self.assertFormatterResult(log.LineLogFormatter, 'first', """\
1418
1: John Doe 2005-11-22 add a
1421
def test_line_all(self):
1422
self.assertFormatterResult(log.LineLogFormatter, 'all', """\
1423
1: John Doe, Jane Rey 2005-11-22 add a
1427
def test_short_default(self):
1428
self.assertFormatterResult(log.ShortLogFormatter, None, """\
1429
1 John Doe\t2005-11-22
1434
def test_short_committer(self):
1435
self.assertFormatterResult(log.ShortLogFormatter, 'committer', """\
1436
1 Lorem Ipsum\t2005-11-22
1441
def test_short_first(self):
1442
self.assertFormatterResult(log.ShortLogFormatter, 'first', """\
1443
1 John Doe\t2005-11-22
1448
def test_short_all(self):
1449
self.assertFormatterResult(log.ShortLogFormatter, 'all', """\
1450
1 John Doe, Jane Rey\t2005-11-22
1455
def test_long_default(self):
1456
self.assertFormatterResult(log.LongLogFormatter, None, """\
1457
------------------------------------------------------------
1459
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
1460
committer: Lorem Ipsum <test@example.com>
1462
timestamp: Tue 2005-11-22 00:00:00 +0000
1467
def test_long_committer(self):
1468
self.assertFormatterResult(log.LongLogFormatter, 'committer', """\
1469
------------------------------------------------------------
1471
committer: Lorem Ipsum <test@example.com>
1473
timestamp: Tue 2005-11-22 00:00:00 +0000
1478
def test_long_first(self):
1479
self.assertFormatterResult(log.LongLogFormatter, 'first', """\
1480
------------------------------------------------------------
1482
author: John Doe <jdoe@example.com>
1483
committer: Lorem Ipsum <test@example.com>
1485
timestamp: Tue 2005-11-22 00:00:00 +0000
1490
def test_long_all(self):
1491
self.assertFormatterResult(log.LongLogFormatter, 'all', """\
1492
------------------------------------------------------------
1494
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
1495
committer: Lorem Ipsum <test@example.com>
1497
timestamp: Tue 2005-11-22 00:00:00 +0000
1502
def test_gnu_changelog_default(self):
1503
self.assertFormatterResult(log.GnuChangelogLogFormatter, None, """\
1504
2005-11-22 John Doe <jdoe@example.com>
1510
def test_gnu_changelog_committer(self):
1511
self.assertFormatterResult(log.GnuChangelogLogFormatter, 'committer', """\
1512
2005-11-22 Lorem Ipsum <test@example.com>
1518
def test_gnu_changelog_first(self):
1519
self.assertFormatterResult(log.GnuChangelogLogFormatter, 'first', """\
1520
2005-11-22 John Doe <jdoe@example.com>
1526
def test_gnu_changelog_all(self):
1527
self.assertFormatterResult(log.GnuChangelogLogFormatter, 'all', """\
1528
2005-11-22 John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
1535
class TestLogExcludeAncestry(tests.TestCaseWithTransport):
1537
def make_branch_with_alternate_ancestries(self, relpath='.'):
1538
# See test_merge_sorted_exclude_ancestry below for the difference with
1539
# bt.per_branch.test_iter_merge_sorted_revision.
1540
# TestIterMergeSortedRevisionsBushyGraph.
1541
# make_branch_with_alternate_ancestries
1542
# and test_merge_sorted_exclude_ancestry
1543
# See the FIXME in assertLogRevnos too.
1544
builder = branchbuilder.BranchBuilder(self.get_transport(relpath))
1556
builder.start_series()
1557
builder.build_snapshot('1', None, [
1558
('add', ('', 'TREE_ROOT', 'directory', '')),])
1559
builder.build_snapshot('1.1.1', ['1'], [])
1560
builder.build_snapshot('2', ['1'], [])
1561
builder.build_snapshot('1.2.1', ['1.1.1'], [])
1562
builder.build_snapshot('1.1.2', ['1.1.1', '1.2.1'], [])
1563
builder.build_snapshot('3', ['2', '1.1.2'], [])
1564
builder.finish_series()
1565
br = builder.get_branch()
1567
self.addCleanup(br.unlock)
1570
def assertLogRevnos(self, expected_revnos, b, start, end,
1571
exclude_common_ancestry, generate_merge_revisions=True):
1572
# FIXME: the layering in log makes it hard to test intermediate levels,
1573
# I wish adding filters with their parameters were easier...
1575
iter_revs = log._calc_view_revisions(
1576
b, start, end, direction='reverse',
1577
generate_merge_revisions=generate_merge_revisions,
1578
exclude_common_ancestry=exclude_common_ancestry)
1579
self.assertEqual(expected_revnos,
1580
[revid for revid, revno, depth in iter_revs])
1582
def test_merge_sorted_exclude_ancestry(self):
1583
b = self.make_branch_with_alternate_ancestries()
1584
self.assertLogRevnos(['3', '1.1.2', '1.2.1', '1.1.1', '2', '1'],
1585
b, '1', '3', exclude_common_ancestry=False)
1586
# '2' is part of the '3' ancestry but not part of '1.1.1' ancestry so
1587
# it should be mentioned even if merge_sort order will make it appear
1589
self.assertLogRevnos(['3', '1.1.2', '1.2.1', '2'],
1590
b, '1.1.1', '3', exclude_common_ancestry=True)
1592
def test_merge_sorted_simple_revnos_exclude_ancestry(self):
1593
b = self.make_branch_with_alternate_ancestries()
1594
self.assertLogRevnos(['3', '2'],
1595
b, '1', '3', exclude_common_ancestry=True,
1596
generate_merge_revisions=False)
1597
self.assertLogRevnos(['3', '1.1.2', '1.2.1', '1.1.1', '2'],
1598
b, '1', '3', exclude_common_ancestry=True,
1599
generate_merge_revisions=True)
1602
class TestLogDefaults(TestCaseForLogFormatter):
1603
def test_default_log_level(self):
1605
Test to ensure that specifying 'levels=1' to make_log_request_dict
1606
doesn't get overwritten when using a LogFormatter that supports more
1610
wt = self._prepare_tree_with_merges()
1613
class CustomLogFormatter(log.LogFormatter):
1614
def __init__(self, *args, **kwargs):
1615
super(CustomLogFormatter, self).__init__(*args, **kwargs)
1617
def get_levels(self):
1618
# log formatter supports all levels:
1620
def log_revision(self, revision):
1621
self.revisions.append(revision)
1623
log_formatter = LogCatcher()
1624
# First request we don't specify number of levels, we should get a
1625
# sensible default (whatever the LogFormatter handles - which in this
1626
# case is 0/everything):
1627
request = log.make_log_request_dict(limit=10)
1628
log.Logger(b, request).show(log_formatter)
1629
# should have all three revisions:
1630
self.assertEquals(len(log_formatter.revisions), 3)
1633
log_formatter = LogCatcher()
1634
# now explicitly request mainline revisions only:
1635
request = log.make_log_request_dict(limit=10, levels=1)
1636
log.Logger(b, request).show(log_formatter)
1637
# should now only have 2 revisions:
1638
self.assertEquals(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))