13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
18
from cStringIO import StringIO
20
from bzrlib import log
21
from bzrlib.tests import TestCaseWithTransport
22
from bzrlib.log import (show_log,
29
from bzrlib.branch import Branch
30
from bzrlib.errors import InvalidRevisionNumber
33
class LogCatcher(LogFormatter):
34
"""Pull log messages into list rather than displaying them.
36
For ease of testing we save log messages here rather than actually
37
formatting them, so that we can precisely check the result without
38
being too dependent on the exact formatting.
40
We should also test the LogFormatter.
30
class TestCaseWithoutPropsHandler(tests.TestCaseWithTransport):
33
super(TestCaseWithoutPropsHandler, self).setUp()
34
# keep a reference to the "current" custom prop. handler registry
35
self.properties_handler_registry = log.properties_handler_registry
36
# Use a clean registry for log
37
log.properties_handler_registry = registry.Registry()
40
log.properties_handler_registry = self.properties_handler_registry
41
self.addCleanup(restore)
44
class LogCatcher(log.LogFormatter):
45
"""Pull log messages into a list rather than displaying them.
47
To simplify testing we save logged revisions here rather than actually
48
formatting anything, so that we can precisely check the result without
49
being dependent on the formatting.
43
52
supports_delta = True
45
54
def __init__(self):
46
55
super(LogCatcher, self).__init__(to_file=None)
49
58
def log_revision(self, revision):
50
self.logs.append(revision)
53
class TestShowLog(TestCaseWithTransport):
59
self.revisions.append(revision)
62
class TestShowLog(tests.TestCaseWithTransport):
55
64
def checkDelta(self, delta, **kw):
56
"""Check the filenames touched by a delta are as expected."""
65
"""Check the filenames touched by a delta are as expected.
67
Caller only have to pass in the list of files for each part, all
68
unspecified parts are considered empty (and checked as such).
57
70
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
71
# By default we expect an empty list
58
72
expected = kw.get(n, [])
59
73
# strip out only the path components
60
74
got = [x[0] for x in getattr(delta, n)]
61
self.assertEquals(expected, got)
75
self.assertEqual(expected, got)
77
def assertInvalidRevisonNumber(self, br, start, end):
79
self.assertRaises(errors.InvalidRevisionNumber,
81
start_revision=start, end_revision=end)
63
83
def test_cur_revno(self):
64
84
wt = self.make_branch_and_tree('.')
68
88
wt.commit('empty commit')
69
show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
70
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
71
start_revision=2, end_revision=1)
72
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
73
start_revision=1, end_revision=2)
74
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
75
start_revision=0, end_revision=2)
76
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
77
start_revision=1, end_revision=0)
78
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
79
start_revision=-1, end_revision=1)
80
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
81
start_revision=1, end_revision=-1)
83
def test_simple_log(self):
84
eq = self.assertEquals
89
log.show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
91
# Since there is a single revision in the branch all the combinations
93
self.assertInvalidRevisonNumber(b, 2, 1)
94
self.assertInvalidRevisonNumber(b, 1, 2)
95
self.assertInvalidRevisonNumber(b, 0, 2)
96
self.assertInvalidRevisonNumber(b, 1, 0)
97
self.assertInvalidRevisonNumber(b, -1, 1)
98
self.assertInvalidRevisonNumber(b, 1, -1)
100
def test_empty_branch(self):
86
101
wt = self.make_branch_and_tree('.')
104
log.show_log(wt.branch, lf)
106
self.assertEqual([], lf.revisions)
108
def test_empty_commit(self):
109
wt = self.make_branch_and_tree('.')
94
111
wt.commit('empty commit')
96
show_log(b, lf, verbose=True)
98
eq(lf.logs[0].revno, '1')
99
eq(lf.logs[0].rev.message, 'empty commit')
101
self.log('log delta: %r' % d)
113
log.show_log(wt.branch, lf, verbose=True)
115
self.assertEqual(1, len(revs))
116
self.assertEqual('1', revs[0].revno)
117
self.assertEqual('empty commit', revs[0].rev.message)
118
self.checkDelta(revs[0].delta)
120
def test_simple_commit(self):
121
wt = self.make_branch_and_tree('.')
122
wt.commit('empty commit')
104
123
self.build_tree(['hello'])
106
wt.commit('add one file')
109
# log using regular thing
110
show_log(b, LongLogFormatter(lf))
112
for l in lf.readlines():
115
# get log as data structure
125
wt.commit('add one file',
126
committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
127
u'<test@example.com>')
116
128
lf = LogCatcher()
117
show_log(b, lf, verbose=True)
119
self.log('log entries:')
120
for logentry in lf.logs:
121
self.log('%4s %s' % (logentry.revno, logentry.rev.message))
129
log.show_log(wt.branch, lf, verbose=True)
130
self.assertEqual(2, len(lf.revisions))
123
131
# first one is most recent
124
logentry = lf.logs[0]
125
eq(logentry.revno, '2')
126
eq(logentry.rev.message, 'add one file')
128
self.log('log 2 delta: %r' % d)
129
self.checkDelta(d, added=['hello'])
131
# commit a log message with control characters
132
msg = "All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
133
self.log("original commit message: %r", msg)
132
log_entry = lf.revisions[0]
133
self.assertEqual('2', log_entry.revno)
134
self.assertEqual('add one file', log_entry.rev.message)
135
self.checkDelta(log_entry.delta, added=['hello'])
137
def test_commit_message_with_control_chars(self):
138
wt = self.make_branch_and_tree('.')
139
msg = u"All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
140
msg = msg.replace(u'\r', u'\n')
135
142
lf = LogCatcher()
136
show_log(b, lf, verbose=True)
137
committed_msg = lf.logs[0].rev.message
138
self.log("escaped commit message: %r", committed_msg)
139
self.assert_(msg != committed_msg)
140
self.assert_(len(committed_msg) > len(msg))
143
log.show_log(wt.branch, lf, verbose=True)
144
committed_msg = lf.revisions[0].rev.message
145
self.assertNotEqual(msg, committed_msg)
146
self.assertTrue(len(committed_msg) > len(msg))
142
# Check that log message with only XML-valid characters isn't
148
def test_commit_message_without_control_chars(self):
149
wt = self.make_branch_and_tree('.')
143
150
# escaped. As ElementTree apparently does some kind of
144
151
# newline conversion, neither LF (\x0A) nor CR (\x0D) are
145
152
# included in the test commit message, even though they are
146
153
# valid XML 1.0 characters.
147
154
msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
148
self.log("original commit message: %r", msg)
150
156
lf = LogCatcher()
151
show_log(b, lf, verbose=True)
152
committed_msg = lf.logs[0].rev.message
153
self.log("escaped commit message: %r", committed_msg)
154
self.assert_(msg == committed_msg)
157
log.show_log(wt.branch, lf, verbose=True)
158
committed_msg = lf.revisions[0].rev.message
159
self.assertEqual(msg, committed_msg)
156
161
def test_deltas_in_merge_revisions(self):
157
162
"""Check deltas created for both mainline and merge revisions"""
158
eq = self.assertEquals
159
163
wt = self.make_branch_and_tree('parent')
160
164
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
174
178
lf = LogCatcher()
175
179
lf.supports_merge_revisions = True
176
show_log(b, lf, verbose=True)
178
logentry = lf.logs[0]
179
eq(logentry.revno, '2')
180
eq(logentry.rev.message, 'merge child branch')
182
self.checkDelta(d, removed=['file1'], modified=['file2'])
183
logentry = lf.logs[1]
184
eq(logentry.revno, '1.1.1')
185
eq(logentry.rev.message, 'remove file1 and modify file2')
187
self.checkDelta(d, removed=['file1'], modified=['file2'])
188
logentry = lf.logs[2]
189
eq(logentry.revno, '1')
190
eq(logentry.rev.message, 'add file1 and file2')
192
self.checkDelta(d, added=['file1', 'file2'])
180
log.show_log(b, lf, verbose=True)
183
self.assertEqual(3, len(revs))
186
self.assertEqual('2', logentry.revno)
187
self.assertEqual('merge child branch', logentry.rev.message)
188
self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
191
self.assertEqual('1.1.1', logentry.revno)
192
self.assertEqual('remove file1 and modify file2', logentry.rev.message)
193
self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
196
self.assertEqual('1', logentry.revno)
197
self.assertEqual('add file1 and file2', logentry.rev.message)
198
self.checkDelta(logentry.delta, added=['file1', 'file2'])
195
201
def make_commits_with_trailing_newlines(wt):
196
"""Helper method for LogFormatter tests"""
202
"""Helper method for LogFormatter tests"""
199
205
open('a', 'wb').write('hello moto\n')
201
wt.commit('simple log message', rev_id='a1'
202
, timestamp=1132586655.459960938, timezone=-6*3600
203
, committer='Joe Foo <joe@foo.com>')
207
wt.commit('simple log message', rev_id='a1',
208
timestamp=1132586655.459960938, timezone=-6*3600,
209
committer='Joe Foo <joe@foo.com>')
204
210
open('b', 'wb').write('goodbye\n')
206
wt.commit('multiline\nlog\nmessage\n', rev_id='a2'
207
, timestamp=1132586842.411175966, timezone=-6*3600
208
, committer='Joe Foo <joe@foo.com>')
212
wt.commit('multiline\nlog\nmessage\n', rev_id='a2',
213
timestamp=1132586842.411175966, timezone=-6*3600,
214
committer='Joe Foo <joe@foo.com>',
215
authors=['Joe Bar <joe@bar.com>'])
210
217
open('c', 'wb').write('just another manic monday\n')
212
wt.commit('single line with trailing newline\n', rev_id='a3'
213
, timestamp=1132587176.835228920, timezone=-6*3600
214
, committer = 'Joe Foo <joe@foo.com>')
219
wt.commit('single line with trailing newline\n', rev_id='a3',
220
timestamp=1132587176.835228920, timezone=-6*3600,
221
committer = 'Joe Foo <joe@foo.com>')
218
class TestShortLogFormatter(TestCaseWithTransport):
225
def normalize_log(log):
226
"""Replaces the variable lines of logs with fixed lines"""
227
author = 'author: Dolor Sit <test@example.com>'
228
committer = 'committer: Lorem Ipsum <test@example.com>'
229
lines = log.splitlines(True)
230
for idx,line in enumerate(lines):
231
stripped_line = line.lstrip()
232
indent = ' ' * (len(line) - len(stripped_line))
233
if stripped_line.startswith('author:'):
234
lines[idx] = indent + author + '\n'
235
elif stripped_line.startswith('committer:'):
236
lines[idx] = indent + committer + '\n'
237
elif stripped_line.startswith('timestamp:'):
238
lines[idx] = indent + 'timestamp: Just now\n'
239
return ''.join(lines)
242
class TestShortLogFormatter(tests.TestCaseWithTransport):
220
244
def test_trailing_newlines(self):
221
245
wt = self.make_branch_and_tree('.')
222
246
b = make_commits_with_trailing_newlines(wt)
224
lf = ShortLogFormatter(to_file=sio)
226
self.assertEquals(sio.getvalue(), """\
247
sio = self.make_utf8_encoded_stringio()
248
lf = log.ShortLogFormatter(to_file=sio)
250
self.assertEqualDiff("""\
227
251
3 Joe Foo\t2005-11-21
228
252
single line with trailing newline
230
2 Joe Foo\t2005-11-21
254
2 Joe Bar\t2005-11-21
235
259
1 Joe Foo\t2005-11-21
236
260
simple log message
241
class TestLongLogFormatter(TestCaseWithTransport):
243
def normalize_log(self,log):
244
"""Replaces the variable lines of logs with fixed lines"""
245
committer = 'committer: Lorem Ipsum <test@example.com>'
246
lines = log.splitlines(True)
247
for idx,line in enumerate(lines):
248
stripped_line = line.lstrip()
249
indent = ' ' * (len(line) - len(stripped_line))
250
if stripped_line.startswith('committer:'):
251
lines[idx] = indent + committer + '\n'
252
if stripped_line.startswith('timestamp:'):
253
lines[idx] = indent + 'timestamp: Just now\n'
254
return ''.join(lines)
265
def _prepare_tree_with_merges(self, with_tags=False):
266
wt = self.make_branch_and_memory_tree('.')
268
self.addCleanup(wt.unlock)
270
wt.commit('rev-1', rev_id='rev-1',
271
timestamp=1132586655, timezone=36000,
272
committer='Joe Foo <joe@foo.com>')
273
wt.commit('rev-merged', rev_id='rev-2a',
274
timestamp=1132586700, timezone=36000,
275
committer='Joe Foo <joe@foo.com>')
276
wt.set_parent_ids(['rev-1', 'rev-2a'])
277
wt.branch.set_last_revision_info(1, 'rev-1')
278
wt.commit('rev-2', rev_id='rev-2b',
279
timestamp=1132586800, timezone=36000,
280
committer='Joe Foo <joe@foo.com>')
283
branch.tags.set_tag('v0.2', 'rev-2b')
284
wt.commit('rev-3', rev_id='rev-3',
285
timestamp=1132586900, timezone=36000,
286
committer='Jane Foo <jane@foo.com>')
287
branch.tags.set_tag('v1.0rc1', 'rev-3')
288
branch.tags.set_tag('v1.0', 'rev-3')
291
def test_short_log_with_merges(self):
292
wt = self._prepare_tree_with_merges()
293
logfile = self.make_utf8_encoded_stringio()
294
formatter = log.ShortLogFormatter(to_file=logfile)
295
log.show_log(wt.branch, formatter)
296
self.assertEqualDiff("""\
297
2 Joe Foo\t2005-11-22 [merge]
300
1 Joe Foo\t2005-11-22
306
def test_short_log_with_merges_and_advice(self):
307
wt = self._prepare_tree_with_merges()
308
logfile = self.make_utf8_encoded_stringio()
309
formatter = log.ShortLogFormatter(to_file=logfile,
311
log.show_log(wt.branch, formatter)
312
self.assertEqualDiff("""\
313
2 Joe Foo\t2005-11-22 [merge]
316
1 Joe Foo\t2005-11-22
319
Use --include-merges or -n0 to see merged revisions.
323
def test_short_log_with_merges_and_range(self):
324
wt = self.make_branch_and_memory_tree('.')
326
self.addCleanup(wt.unlock)
328
wt.commit('rev-1', rev_id='rev-1',
329
timestamp=1132586655, timezone=36000,
330
committer='Joe Foo <joe@foo.com>')
331
wt.commit('rev-merged', rev_id='rev-2a',
332
timestamp=1132586700, timezone=36000,
333
committer='Joe Foo <joe@foo.com>')
334
wt.branch.set_last_revision_info(1, 'rev-1')
335
wt.set_parent_ids(['rev-1', 'rev-2a'])
336
wt.commit('rev-2b', rev_id='rev-2b',
337
timestamp=1132586800, timezone=36000,
338
committer='Joe Foo <joe@foo.com>')
339
wt.commit('rev-3a', rev_id='rev-3a',
340
timestamp=1132586800, timezone=36000,
341
committer='Joe Foo <joe@foo.com>')
342
wt.branch.set_last_revision_info(2, 'rev-2b')
343
wt.set_parent_ids(['rev-2b', 'rev-3a'])
344
wt.commit('rev-3b', rev_id='rev-3b',
345
timestamp=1132586800, timezone=36000,
346
committer='Joe Foo <joe@foo.com>')
347
logfile = self.make_utf8_encoded_stringio()
348
formatter = log.ShortLogFormatter(to_file=logfile)
349
log.show_log(wt.branch, formatter,
350
start_revision=2, end_revision=3)
351
self.assertEqualDiff("""\
352
3 Joe Foo\t2005-11-22 [merge]
355
2 Joe Foo\t2005-11-22 [merge]
361
def test_short_log_with_tags(self):
362
wt = self._prepare_tree_with_merges(with_tags=True)
363
logfile = self.make_utf8_encoded_stringio()
364
formatter = log.ShortLogFormatter(to_file=logfile)
365
log.show_log(wt.branch, formatter)
366
self.assertEqualDiff("""\
367
3 Jane Foo\t2005-11-22 {v1.0, v1.0rc1}
370
2 Joe Foo\t2005-11-22 {v0.2} [merge]
373
1 Joe Foo\t2005-11-22
379
def test_short_log_single_merge_revision(self):
380
wt = self.make_branch_and_memory_tree('.')
382
self.addCleanup(wt.unlock)
384
wt.commit('rev-1', rev_id='rev-1',
385
timestamp=1132586655, timezone=36000,
386
committer='Joe Foo <joe@foo.com>')
387
wt.commit('rev-merged', rev_id='rev-2a',
388
timestamp=1132586700, timezone=36000,
389
committer='Joe Foo <joe@foo.com>')
390
wt.set_parent_ids(['rev-1', 'rev-2a'])
391
wt.branch.set_last_revision_info(1, 'rev-1')
392
wt.commit('rev-2', rev_id='rev-2b',
393
timestamp=1132586800, timezone=36000,
394
committer='Joe Foo <joe@foo.com>')
395
logfile = self.make_utf8_encoded_stringio()
396
formatter = log.ShortLogFormatter(to_file=logfile)
397
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
399
rev = revspec.in_history(wtb)
400
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
401
self.assertEqualDiff("""\
402
1.1.1 Joe Foo\t2005-11-22
409
class TestShortLogFormatterWithMergeRevisions(tests.TestCaseWithTransport):
411
def test_short_merge_revs_log_with_merges(self):
412
wt = self.make_branch_and_memory_tree('.')
414
self.addCleanup(wt.unlock)
416
wt.commit('rev-1', rev_id='rev-1',
417
timestamp=1132586655, timezone=36000,
418
committer='Joe Foo <joe@foo.com>')
419
wt.commit('rev-merged', rev_id='rev-2a',
420
timestamp=1132586700, timezone=36000,
421
committer='Joe Foo <joe@foo.com>')
422
wt.set_parent_ids(['rev-1', 'rev-2a'])
423
wt.branch.set_last_revision_info(1, 'rev-1')
424
wt.commit('rev-2', rev_id='rev-2b',
425
timestamp=1132586800, timezone=36000,
426
committer='Joe Foo <joe@foo.com>')
427
logfile = self.make_utf8_encoded_stringio()
428
formatter = log.ShortLogFormatter(to_file=logfile, levels=0)
429
log.show_log(wt.branch, formatter)
430
# Note that the 1.1.1 indenting is in fact correct given that
431
# the revision numbers are right justified within 5 characters
432
# for mainline revnos and 9 characters for dotted revnos.
433
self.assertEqualDiff("""\
434
2 Joe Foo\t2005-11-22 [merge]
437
1.1.1 Joe Foo\t2005-11-22
440
1 Joe Foo\t2005-11-22
446
def test_short_merge_revs_log_single_merge_revision(self):
447
wt = self.make_branch_and_memory_tree('.')
449
self.addCleanup(wt.unlock)
451
wt.commit('rev-1', rev_id='rev-1',
452
timestamp=1132586655, timezone=36000,
453
committer='Joe Foo <joe@foo.com>')
454
wt.commit('rev-merged', rev_id='rev-2a',
455
timestamp=1132586700, timezone=36000,
456
committer='Joe Foo <joe@foo.com>')
457
wt.set_parent_ids(['rev-1', 'rev-2a'])
458
wt.branch.set_last_revision_info(1, 'rev-1')
459
wt.commit('rev-2', rev_id='rev-2b',
460
timestamp=1132586800, timezone=36000,
461
committer='Joe Foo <joe@foo.com>')
462
logfile = self.make_utf8_encoded_stringio()
463
formatter = log.ShortLogFormatter(to_file=logfile, levels=0)
464
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
466
rev = revspec.in_history(wtb)
467
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
468
self.assertEqualDiff("""\
469
1.1.1 Joe Foo\t2005-11-22
476
class TestLongLogFormatter(TestCaseWithoutPropsHandler):
256
478
def test_verbose_log(self):
257
479
"""Verbose log includes changed files
261
483
wt = self.make_branch_and_tree('.')
399
624
def test_trailing_newlines(self):
400
625
wt = self.make_branch_and_tree('.')
401
626
b = make_commits_with_trailing_newlines(wt)
403
lf = LongLogFormatter(to_file=sio)
405
self.assertEqualDiff(sio.getvalue(), """\
406
------------------------------------------------------------
408
committer: Joe Foo <joe@foo.com>
410
timestamp: Mon 2005-11-21 09:32:56 -0600
412
single line with trailing newline
413
------------------------------------------------------------
415
committer: Joe Foo <joe@foo.com>
417
timestamp: Mon 2005-11-21 09:27:22 -0600
422
------------------------------------------------------------
424
committer: Joe Foo <joe@foo.com>
426
timestamp: Mon 2005-11-21 09:24:15 -0600
432
class TestLineLogFormatter(TestCaseWithTransport):
627
sio = self.make_utf8_encoded_stringio()
628
lf = log.LongLogFormatter(to_file=sio)
630
self.assertEqualDiff("""\
631
------------------------------------------------------------
633
committer: Joe Foo <joe@foo.com>
635
timestamp: Mon 2005-11-21 09:32:56 -0600
637
single line with trailing newline
638
------------------------------------------------------------
640
author: Joe Bar <joe@bar.com>
641
committer: Joe Foo <joe@foo.com>
643
timestamp: Mon 2005-11-21 09:27:22 -0600
648
------------------------------------------------------------
650
committer: Joe Foo <joe@foo.com>
652
timestamp: Mon 2005-11-21 09:24:15 -0600
658
def test_author_in_log(self):
659
"""Log includes the author name if it's set in
660
the revision properties
662
wt = self.make_branch_and_tree('.')
664
self.build_tree(['a'])
666
b.nick = 'test_author_log'
667
wt.commit(message='add a',
668
timestamp=1132711707,
670
committer='Lorem Ipsum <test@example.com>',
671
authors=['John Doe <jdoe@example.com>',
672
'Jane Rey <jrey@example.com>'])
674
formatter = log.LongLogFormatter(to_file=sio)
675
log.show_log(b, formatter)
676
self.assertEqualDiff('''\
677
------------------------------------------------------------
679
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
680
committer: Lorem Ipsum <test@example.com>
681
branch nick: test_author_log
682
timestamp: Wed 2005-11-23 12:08:27 +1000
688
def test_properties_in_log(self):
689
"""Log includes the custom properties returned by the registered
692
wt = self.make_branch_and_tree('.')
694
self.build_tree(['a'])
696
b.nick = 'test_properties_in_log'
697
wt.commit(message='add a',
698
timestamp=1132711707,
700
committer='Lorem Ipsum <test@example.com>',
701
authors=['John Doe <jdoe@example.com>'])
703
formatter = log.LongLogFormatter(to_file=sio)
705
def trivial_custom_prop_handler(revision):
706
return {'test_prop':'test_value'}
708
log.properties_handler_registry.register(
709
'trivial_custom_prop_handler',
710
trivial_custom_prop_handler)
711
log.show_log(b, formatter)
713
log.properties_handler_registry.remove(
714
'trivial_custom_prop_handler')
715
self.assertEqualDiff('''\
716
------------------------------------------------------------
718
test_prop: test_value
719
author: John Doe <jdoe@example.com>
720
committer: Lorem Ipsum <test@example.com>
721
branch nick: test_properties_in_log
722
timestamp: Wed 2005-11-23 12:08:27 +1000
728
def test_properties_in_short_log(self):
729
"""Log includes the custom properties returned by the registered
732
wt = self.make_branch_and_tree('.')
734
self.build_tree(['a'])
736
b.nick = 'test_properties_in_short_log'
737
wt.commit(message='add a',
738
timestamp=1132711707,
740
committer='Lorem Ipsum <test@example.com>',
741
authors=['John Doe <jdoe@example.com>'])
743
formatter = log.ShortLogFormatter(to_file=sio)
745
def trivial_custom_prop_handler(revision):
746
return {'test_prop':'test_value'}
748
log.properties_handler_registry.register(
749
'trivial_custom_prop_handler',
750
trivial_custom_prop_handler)
751
log.show_log(b, formatter)
753
log.properties_handler_registry.remove(
754
'trivial_custom_prop_handler')
755
self.assertEqualDiff('''\
756
1 John Doe\t2005-11-23
757
test_prop: test_value
763
def test_error_in_properties_handler(self):
764
"""Log includes the custom properties returned by the registered
767
wt = self.make_branch_and_tree('.')
769
self.build_tree(['a'])
771
b.nick = 'test_author_log'
772
wt.commit(message='add a',
773
timestamp=1132711707,
775
committer='Lorem Ipsum <test@example.com>',
776
authors=['John Doe <jdoe@example.com>'],
777
revprops={'first_prop':'first_value'})
779
formatter = log.LongLogFormatter(to_file=sio)
781
def trivial_custom_prop_handler(revision):
782
raise StandardError("a test error")
784
log.properties_handler_registry.register(
785
'trivial_custom_prop_handler',
786
trivial_custom_prop_handler)
787
self.assertRaises(StandardError, log.show_log, b, formatter,)
789
log.properties_handler_registry.remove(
790
'trivial_custom_prop_handler')
792
def test_properties_handler_bad_argument(self):
793
wt = self.make_branch_and_tree('.')
795
self.build_tree(['a'])
797
b.nick = 'test_author_log'
798
wt.commit(message='add a',
799
timestamp=1132711707,
801
committer='Lorem Ipsum <test@example.com>',
802
authors=['John Doe <jdoe@example.com>'],
803
revprops={'a_prop':'test_value'})
805
formatter = log.LongLogFormatter(to_file=sio)
807
def bad_argument_prop_handler(revision):
808
return {'custom_prop_name':revision.properties['a_prop']}
810
log.properties_handler_registry.register(
811
'bad_argument_prop_handler',
812
bad_argument_prop_handler)
814
self.assertRaises(AttributeError, formatter.show_properties,
817
revision = b.repository.get_revision(b.last_revision())
818
formatter.show_properties(revision, '')
819
self.assertEqualDiff('''custom_prop_name: test_value\n''',
822
log.properties_handler_registry.remove(
823
'bad_argument_prop_handler')
826
class TestLongLogFormatterWithoutMergeRevisions(TestCaseWithoutPropsHandler):
828
def test_long_verbose_log(self):
829
"""Verbose log includes changed files
833
wt = self.make_branch_and_tree('.')
835
self.build_tree(['a'])
837
# XXX: why does a longer nick show up?
838
b.nick = 'test_verbose_log'
839
wt.commit(message='add a',
840
timestamp=1132711707,
842
committer='Lorem Ipsum <test@example.com>')
843
logfile = file('out.tmp', 'w+')
844
formatter = log.LongLogFormatter(to_file=logfile, levels=1)
845
log.show_log(b, formatter, verbose=True)
848
log_contents = logfile.read()
849
self.assertEqualDiff('''\
850
------------------------------------------------------------
852
committer: Lorem Ipsum <test@example.com>
853
branch nick: test_verbose_log
854
timestamp: Wed 2005-11-23 12:08:27 +1000
862
def test_long_verbose_contain_deltas(self):
863
wt = self.make_branch_and_tree('parent')
864
self.build_tree(['parent/f1', 'parent/f2'])
866
wt.commit('first post')
867
self.run_bzr('branch parent child')
868
os.unlink('child/f1')
869
file('child/f2', 'wb').write('hello\n')
870
self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
873
self.run_bzr('merge ../child')
874
wt.commit('merge branch 1')
876
sio = self.make_utf8_encoded_stringio()
877
lf = log.LongLogFormatter(to_file=sio, levels=1)
878
log.show_log(b, lf, verbose=True)
879
the_log = normalize_log(sio.getvalue())
880
self.assertEqualDiff("""\
881
------------------------------------------------------------
883
committer: Lorem Ipsum <test@example.com>
892
------------------------------------------------------------
894
committer: Lorem Ipsum <test@example.com>
905
def test_long_trailing_newlines(self):
906
wt = self.make_branch_and_tree('.')
907
b = make_commits_with_trailing_newlines(wt)
908
sio = self.make_utf8_encoded_stringio()
909
lf = log.LongLogFormatter(to_file=sio, levels=1)
911
self.assertEqualDiff("""\
912
------------------------------------------------------------
914
committer: Joe Foo <joe@foo.com>
916
timestamp: Mon 2005-11-21 09:32:56 -0600
918
single line with trailing newline
919
------------------------------------------------------------
921
author: Joe Bar <joe@bar.com>
922
committer: Joe Foo <joe@foo.com>
924
timestamp: Mon 2005-11-21 09:27:22 -0600
929
------------------------------------------------------------
931
committer: Joe Foo <joe@foo.com>
933
timestamp: Mon 2005-11-21 09:24:15 -0600
939
def test_long_author_in_log(self):
940
"""Log includes the author name if it's set in
941
the revision properties
943
wt = self.make_branch_and_tree('.')
945
self.build_tree(['a'])
947
b.nick = 'test_author_log'
948
wt.commit(message='add a',
949
timestamp=1132711707,
951
committer='Lorem Ipsum <test@example.com>',
952
authors=['John Doe <jdoe@example.com>'])
954
formatter = log.LongLogFormatter(to_file=sio, levels=1)
955
log.show_log(b, formatter)
956
self.assertEqualDiff('''\
957
------------------------------------------------------------
959
author: John Doe <jdoe@example.com>
960
committer: Lorem Ipsum <test@example.com>
961
branch nick: test_author_log
962
timestamp: Wed 2005-11-23 12:08:27 +1000
968
def test_long_properties_in_log(self):
969
"""Log includes the custom properties returned by the registered
972
wt = self.make_branch_and_tree('.')
974
self.build_tree(['a'])
976
b.nick = 'test_properties_in_log'
977
wt.commit(message='add a',
978
timestamp=1132711707,
980
committer='Lorem Ipsum <test@example.com>',
981
authors=['John Doe <jdoe@example.com>'])
983
formatter = log.LongLogFormatter(to_file=sio, levels=1)
985
def trivial_custom_prop_handler(revision):
986
return {'test_prop':'test_value'}
988
log.properties_handler_registry.register(
989
'trivial_custom_prop_handler',
990
trivial_custom_prop_handler)
991
log.show_log(b, formatter)
993
log.properties_handler_registry.remove(
994
'trivial_custom_prop_handler')
995
self.assertEqualDiff('''\
996
------------------------------------------------------------
998
test_prop: test_value
999
author: John Doe <jdoe@example.com>
1000
committer: Lorem Ipsum <test@example.com>
1001
branch nick: test_properties_in_log
1002
timestamp: Wed 2005-11-23 12:08:27 +1000
1009
class TestLineLogFormatter(tests.TestCaseWithTransport):
434
1011
def test_line_log(self):
435
1012
"""Line log should show revno
439
1016
wt = self.make_branch_and_tree('.')
441
1018
self.build_tree(['a'])
443
1020
b.nick = 'test-line-log'
444
wt.commit(message='add a',
445
timestamp=1132711707,
1021
wt.commit(message='add a',
1022
timestamp=1132711707,
447
1024
committer='Line-Log-Formatter Tester <test@line.log>')
448
1025
logfile = file('out.tmp', 'w+')
449
formatter = LineLogFormatter(to_file=logfile)
450
show_log(b, formatter)
1026
formatter = log.LineLogFormatter(to_file=logfile)
1027
log.show_log(b, formatter)
453
1030
log_contents = logfile.read()
454
self.assertEqualDiff(log_contents, '1: Line-Log-Formatte... 2005-11-23 add a\n')
456
def test_short_log_with_merges(self):
457
wt = self.make_branch_and_memory_tree('.')
461
wt.commit('rev-1', rev_id='rev-1',
462
timestamp=1132586655, timezone=36000,
463
committer='Joe Foo <joe@foo.com>')
464
wt.commit('rev-merged', rev_id='rev-2a',
465
timestamp=1132586700, timezone=36000,
466
committer='Joe Foo <joe@foo.com>')
467
wt.set_parent_ids(['rev-1', 'rev-2a'])
468
wt.branch.set_last_revision_info(1, 'rev-1')
469
wt.commit('rev-2', rev_id='rev-2b',
470
timestamp=1132586800, timezone=36000,
471
committer='Joe Foo <joe@foo.com>')
473
formatter = ShortLogFormatter(to_file=logfile)
474
show_log(wt.branch, formatter)
476
self.assertEqualDiff("""\
477
2 Joe Foo\t2005-11-22 [merge]
480
1 Joe Foo\t2005-11-22
483
""", logfile.getvalue())
1031
self.assertEqualDiff('1: Line-Log-Formatte... 2005-11-23 add a\n',
487
1034
def test_trailing_newlines(self):
488
1035
wt = self.make_branch_and_tree('.')
489
1036
b = make_commits_with_trailing_newlines(wt)
491
lf = LineLogFormatter(to_file=sio)
493
self.assertEqualDiff(sio.getvalue(), """\
1037
sio = self.make_utf8_encoded_stringio()
1038
lf = log.LineLogFormatter(to_file=sio)
1040
self.assertEqualDiff("""\
494
1041
3: Joe Foo 2005-11-21 single line with trailing newline
495
2: Joe Foo 2005-11-21 multiline
1042
2: Joe Bar 2005-11-21 multiline
496
1043
1: Joe Foo 2005-11-21 simple log message
500
class TestGetViewRevisions(TestCaseWithTransport):
1047
def _prepare_tree_with_merges(self, with_tags=False):
1048
wt = self.make_branch_and_memory_tree('.')
1050
self.addCleanup(wt.unlock)
1052
wt.commit('rev-1', rev_id='rev-1',
1053
timestamp=1132586655, timezone=36000,
1054
committer='Joe Foo <joe@foo.com>')
1055
wt.commit('rev-merged', rev_id='rev-2a',
1056
timestamp=1132586700, timezone=36000,
1057
committer='Joe Foo <joe@foo.com>')
1058
wt.set_parent_ids(['rev-1', 'rev-2a'])
1059
wt.branch.set_last_revision_info(1, 'rev-1')
1060
wt.commit('rev-2', rev_id='rev-2b',
1061
timestamp=1132586800, timezone=36000,
1062
committer='Joe Foo <joe@foo.com>')
1065
branch.tags.set_tag('v0.2', 'rev-2b')
1066
wt.commit('rev-3', rev_id='rev-3',
1067
timestamp=1132586900, timezone=36000,
1068
committer='Jane Foo <jane@foo.com>')
1069
branch.tags.set_tag('v1.0rc1', 'rev-3')
1070
branch.tags.set_tag('v1.0', 'rev-3')
1073
def test_line_log_single_merge_revision(self):
1074
wt = self._prepare_tree_with_merges()
1075
logfile = self.make_utf8_encoded_stringio()
1076
formatter = log.LineLogFormatter(to_file=logfile)
1077
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
1079
rev = revspec.in_history(wtb)
1080
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
1081
self.assertEqualDiff("""\
1082
1.1.1: Joe Foo 2005-11-22 rev-merged
1086
def test_line_log_with_tags(self):
1087
wt = self._prepare_tree_with_merges(with_tags=True)
1088
logfile = self.make_utf8_encoded_stringio()
1089
formatter = log.LineLogFormatter(to_file=logfile)
1090
log.show_log(wt.branch, formatter)
1091
self.assertEqualDiff("""\
1092
3: Jane Foo 2005-11-22 {v1.0, v1.0rc1} rev-3
1093
2: Joe Foo 2005-11-22 [merge] {v0.2} rev-2
1094
1: Joe Foo 2005-11-22 rev-1
1098
class TestLineLogFormatterWithMergeRevisions(tests.TestCaseWithTransport):
1100
def test_line_merge_revs_log(self):
1101
"""Line log should show revno
1105
wt = self.make_branch_and_tree('.')
1107
self.build_tree(['a'])
1109
b.nick = 'test-line-log'
1110
wt.commit(message='add a',
1111
timestamp=1132711707,
1113
committer='Line-Log-Formatter Tester <test@line.log>')
1114
logfile = file('out.tmp', 'w+')
1115
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1116
log.show_log(b, formatter)
1119
log_contents = logfile.read()
1120
self.assertEqualDiff('1: Line-Log-Formatte... 2005-11-23 add a\n',
1123
def test_line_merge_revs_log_single_merge_revision(self):
1124
wt = self.make_branch_and_memory_tree('.')
1126
self.addCleanup(wt.unlock)
1128
wt.commit('rev-1', rev_id='rev-1',
1129
timestamp=1132586655, timezone=36000,
1130
committer='Joe Foo <joe@foo.com>')
1131
wt.commit('rev-merged', rev_id='rev-2a',
1132
timestamp=1132586700, timezone=36000,
1133
committer='Joe Foo <joe@foo.com>')
1134
wt.set_parent_ids(['rev-1', 'rev-2a'])
1135
wt.branch.set_last_revision_info(1, 'rev-1')
1136
wt.commit('rev-2', rev_id='rev-2b',
1137
timestamp=1132586800, timezone=36000,
1138
committer='Joe Foo <joe@foo.com>')
1139
logfile = self.make_utf8_encoded_stringio()
1140
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1141
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
1143
rev = revspec.in_history(wtb)
1144
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
1145
self.assertEqualDiff("""\
1146
1.1.1: Joe Foo 2005-11-22 rev-merged
1150
def test_line_merge_revs_log_with_merges(self):
1151
wt = self.make_branch_and_memory_tree('.')
1153
self.addCleanup(wt.unlock)
1155
wt.commit('rev-1', rev_id='rev-1',
1156
timestamp=1132586655, timezone=36000,
1157
committer='Joe Foo <joe@foo.com>')
1158
wt.commit('rev-merged', rev_id='rev-2a',
1159
timestamp=1132586700, timezone=36000,
1160
committer='Joe Foo <joe@foo.com>')
1161
wt.set_parent_ids(['rev-1', 'rev-2a'])
1162
wt.branch.set_last_revision_info(1, 'rev-1')
1163
wt.commit('rev-2', rev_id='rev-2b',
1164
timestamp=1132586800, timezone=36000,
1165
committer='Joe Foo <joe@foo.com>')
1166
logfile = self.make_utf8_encoded_stringio()
1167
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1168
log.show_log(wt.branch, formatter)
1169
self.assertEqualDiff("""\
1170
2: Joe Foo 2005-11-22 [merge] rev-2
1171
1.1.1: Joe Foo 2005-11-22 rev-merged
1172
1: Joe Foo 2005-11-22 rev-1
1176
class TestGetViewRevisions(tests.TestCaseWithTransport):
502
1178
def make_tree_with_commits(self):
503
1179
"""Create a tree with well-known revision ids"""
552
1236
def test_get_view_revisions_forward(self):
553
1237
"""Test the get_view_revisions method"""
554
1238
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
555
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
1240
self.addCleanup(wt.unlock)
1241
revisions = list(log.get_view_revisions(
1242
mainline_revs, rev_nos, wt.branch, 'forward'))
557
1243
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
559
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
560
'forward', include_merges=False))
1245
revisions2 = list(log.get_view_revisions(
1246
mainline_revs, rev_nos, wt.branch, 'forward',
1247
include_merges=False))
561
1248
self.assertEqual(revisions, revisions2)
563
1250
def test_get_view_revisions_reverse(self):
564
1251
"""Test the get_view_revisions with reverse"""
565
1252
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
566
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
1254
self.addCleanup(wt.unlock)
1255
revisions = list(log.get_view_revisions(
1256
mainline_revs, rev_nos, wt.branch, 'reverse'))
568
1257
self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
570
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
571
'reverse', include_merges=False))
1259
revisions2 = list(log.get_view_revisions(
1260
mainline_revs, rev_nos, wt.branch, 'reverse',
1261
include_merges=False))
572
1262
self.assertEqual(revisions, revisions2)
574
1264
def test_get_view_revisions_merge(self):
575
1265
"""Test get_view_revisions when there are merges"""
576
1266
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
577
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
579
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
580
('4b', '4', 0), ('4a', '3.1.1', 1)],
582
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
583
'forward', include_merges=False))
584
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
1268
self.addCleanup(wt.unlock)
1269
revisions = list(log.get_view_revisions(
1270
mainline_revs, rev_nos, wt.branch, 'forward'))
1271
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
1272
('4b', '4', 0), ('4a', '3.1.1', 1)],
1274
revisions = list(log.get_view_revisions(
1275
mainline_revs, rev_nos, wt.branch, 'forward',
1276
include_merges=False))
1277
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
588
1281
def test_get_view_revisions_merge_reverse(self):
589
1282
"""Test get_view_revisions in reverse when there are merges"""
590
1283
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
591
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
1285
self.addCleanup(wt.unlock)
1286
revisions = list(log.get_view_revisions(
1287
mainline_revs, rev_nos, wt.branch, 'reverse'))
593
1288
self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
594
('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
596
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
597
'reverse', include_merges=False))
1289
('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
1291
revisions = list(log.get_view_revisions(
1292
mainline_revs, rev_nos, wt.branch, 'reverse',
1293
include_merges=False))
598
1294
self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
602
1298
def test_get_view_revisions_merge2(self):
603
1299
"""Test get_view_revisions when there are merges"""
604
1300
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
605
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
1302
self.addCleanup(wt.unlock)
1303
revisions = list(log.get_view_revisions(
1304
mainline_revs, rev_nos, wt.branch, 'forward'))
607
1305
expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
608
('3a', '2.2.1', 1), ('3b', '2.1.1', 1), ('4b', '4', 0),
1306
('3a', '2.1.1', 1), ('3b', '2.2.1', 1), ('4b', '4', 0),
610
1308
self.assertEqual(expected, revisions)
611
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
612
'forward', include_merges=False))
1309
revisions = list(log.get_view_revisions(
1310
mainline_revs, rev_nos, wt.branch, 'forward',
1311
include_merges=False))
613
1312
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
618
class TestGetRevisionsTouchingFileID(TestCaseWithTransport):
1317
def test_file_id_for_range(self):
1318
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
1320
self.addCleanup(wt.unlock)
1322
def rev_from_rev_id(revid, branch):
1323
revspec = revisionspec.RevisionSpec.from_string('revid:%s' % revid)
1324
return revspec.in_history(branch)
1326
def view_revs(start_rev, end_rev, file_id, direction):
1327
revs = log.calculate_view_revisions(
1329
start_rev, # start_revision
1330
end_rev, # end_revision
1331
direction, # direction
1332
file_id, # specific_fileid
1333
True, # generate_merge_revisions
1337
rev_3a = rev_from_rev_id('3a', wt.branch)
1338
rev_4b = rev_from_rev_id('4b', wt.branch)
1339
self.assertEqual([('3c', '3', 0), ('3a', '2.1.1', 1)],
1340
view_revs(rev_3a, rev_4b, 'f-id', 'reverse'))
1341
# Note: 3c still appears before 3a here because of depth-based sorting
1342
self.assertEqual([('3c', '3', 0), ('3a', '2.1.1', 1)],
1343
view_revs(rev_3a, rev_4b, 'f-id', 'forward'))
1346
class TestGetRevisionsTouchingFileID(tests.TestCaseWithTransport):
620
1348
def create_tree_with_single_merge(self):
621
1349
"""Create a branch with a moderate layout.
671
1399
tree.commit('D', rev_id='D')
673
1401
# Switch to a read lock for this tree.
674
# We still have addCleanup(unlock)
1402
# We still have an addCleanup(tree.unlock) pending
676
1404
tree.lock_read()
1407
def check_delta(self, delta, **kw):
1408
"""Check the filenames touched by a delta are as expected.
1410
Caller only have to pass in the list of files for each part, all
1411
unspecified parts are considered empty (and checked as such).
1413
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
1414
# By default we expect an empty list
1415
expected = kw.get(n, [])
1416
# strip out only the path components
1417
got = [x[0] for x in getattr(delta, n)]
1418
self.assertEqual(expected, got)
679
1420
def test_tree_with_single_merge(self):
680
1421
"""Make sure the tree layout is correct."""
681
1422
tree = self.create_tree_with_single_merge()
682
1423
rev_A_tree = tree.branch.repository.revision_tree('A')
683
1424
rev_B_tree = tree.branch.repository.revision_tree('B')
685
f1_changed = (u'f1', 'f1-id', 'file', True, False)
686
f2_changed = (u'f2', 'f2-id', 'file', True, False)
687
f3_changed = (u'f3', 'f3-id', 'file', True, False)
689
delta = rev_B_tree.changes_from(rev_A_tree)
690
self.assertEqual([f1_changed, f3_changed], delta.modified)
691
self.assertEqual([], delta.renamed)
692
self.assertEqual([], delta.added)
693
self.assertEqual([], delta.removed)
695
1425
rev_C_tree = tree.branch.repository.revision_tree('C')
696
delta = rev_C_tree.changes_from(rev_A_tree)
697
self.assertEqual([f2_changed, f3_changed], delta.modified)
698
self.assertEqual([], delta.renamed)
699
self.assertEqual([], delta.added)
700
self.assertEqual([], delta.removed)
702
1426
rev_D_tree = tree.branch.repository.revision_tree('D')
703
delta = rev_D_tree.changes_from(rev_B_tree)
704
self.assertEqual([f2_changed, f3_changed], delta.modified)
705
self.assertEqual([], delta.renamed)
706
self.assertEqual([], delta.added)
707
self.assertEqual([], delta.removed)
709
delta = rev_D_tree.changes_from(rev_C_tree)
710
self.assertEqual([f1_changed, f3_changed], delta.modified)
711
self.assertEqual([], delta.renamed)
712
self.assertEqual([], delta.added)
713
self.assertEqual([], delta.removed)
1428
self.check_delta(rev_B_tree.changes_from(rev_A_tree),
1429
modified=['f1', 'f3'])
1431
self.check_delta(rev_C_tree.changes_from(rev_A_tree),
1432
modified=['f2', 'f3'])
1434
self.check_delta(rev_D_tree.changes_from(rev_B_tree),
1435
modified=['f2', 'f3'])
1437
self.check_delta(rev_D_tree.changes_from(rev_C_tree),
1438
modified=['f1', 'f3'])
715
1440
def assertAllRevisionsForFileID(self, tree, file_id, revisions):
716
"""Make sure _filter_revisions_touching_file_id returns the right values.
1441
"""Ensure _filter_revisions_touching_file_id returns the right values.
718
1443
Get the return value from _filter_revisions_touching_file_id and make
719
1444
sure they are correct.
721
# The api for _get_revisions_touching_file_id is a little crazy,
1446
# The api for _filter_revisions_touching_file_id is a little crazy.
722
1447
# So we do the setup here.
723
1448
mainline = tree.branch.revision_history()
724
1449
mainline.insert(0, None)
746
1470
def test_file_id_f3(self):
747
1471
tree = self.create_tree_with_single_merge()
748
1472
# f3 should be marked as modified by revisions A, B, C, and D
1473
self.assertAllRevisionsForFileID(tree, 'f3-id', ['D', 'C', 'B', 'A'])
1475
def test_file_id_with_ghosts(self):
1476
# This is testing bug #209948, where having a ghost would cause
1477
# _filter_revisions_touching_file_id() to fail.
1478
tree = self.create_tree_with_single_merge()
1479
# We need to add a revision, so switch back to a write-locked tree
1480
# (still a single addCleanup(tree.unlock) pending).
1483
first_parent = tree.last_revision()
1484
tree.set_parent_ids([first_parent, 'ghost-revision-id'])
1485
self.build_tree_contents([('tree/f1', 'A\nB\nXX\n')])
1486
tree.commit('commit with a ghost', rev_id='XX')
1487
self.assertAllRevisionsForFileID(tree, 'f1-id', ['XX', 'B', 'A'])
749
1488
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1490
def test_unknown_file_id(self):
1491
tree = self.create_tree_with_single_merge()
1492
self.assertAllRevisionsForFileID(tree, 'unknown', [])
1494
def test_empty_branch_unknown_file_id(self):
1495
tree = self.make_branch_and_tree('tree')
1496
self.assertAllRevisionsForFileID(tree, 'unknown', [])
1499
class TestShowChangedRevisions(tests.TestCaseWithTransport):
1501
def test_show_changed_revisions_verbose(self):
1502
tree = self.make_branch_and_tree('tree_a')
1503
self.build_tree(['tree_a/foo'])
1505
tree.commit('bar', rev_id='bar-id')
1506
s = self.make_utf8_encoded_stringio()
1507
log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
1508
self.assertContainsRe(s.getvalue(), 'bar')
1509
self.assertNotContainsRe(s.getvalue(), 'foo')
1512
class TestLogFormatter(tests.TestCase):
1514
def test_short_committer(self):
1515
rev = revision.Revision('a-id')
1516
rev.committer = 'John Doe <jdoe@example.com>'
1517
lf = log.LogFormatter(None)
1518
self.assertEqual('John Doe', lf.short_committer(rev))
1519
rev.committer = 'John Smith <jsmith@example.com>'
1520
self.assertEqual('John Smith', lf.short_committer(rev))
1521
rev.committer = 'John Smith'
1522
self.assertEqual('John Smith', lf.short_committer(rev))
1523
rev.committer = 'jsmith@example.com'
1524
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1525
rev.committer = '<jsmith@example.com>'
1526
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1527
rev.committer = 'John Smith jsmith@example.com'
1528
self.assertEqual('John Smith', lf.short_committer(rev))
1530
def test_short_author(self):
1531
rev = revision.Revision('a-id')
1532
rev.committer = 'John Doe <jdoe@example.com>'
1533
lf = log.LogFormatter(None)
1534
self.assertEqual('John Doe', lf.short_author(rev))
1535
rev.properties['author'] = 'John Smith <jsmith@example.com>'
1536
self.assertEqual('John Smith', lf.short_author(rev))
1537
rev.properties['author'] = 'John Smith'
1538
self.assertEqual('John Smith', lf.short_author(rev))
1539
rev.properties['author'] = 'jsmith@example.com'
1540
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1541
rev.properties['author'] = '<jsmith@example.com>'
1542
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1543
rev.properties['author'] = 'John Smith jsmith@example.com'
1544
self.assertEqual('John Smith', lf.short_author(rev))
1545
del rev.properties['author']
1546
rev.properties['authors'] = ('John Smith <jsmith@example.com>\n'
1547
'Jane Rey <jrey@example.com>')
1548
self.assertEqual('John Smith', lf.short_author(rev))
1551
class TestReverseByDepth(tests.TestCase):
1552
"""Test reverse_by_depth behavior.
1554
This is used to present revisions in forward (oldest first) order in a nice
1557
The tests use lighter revision description to ease reading.
1560
def assertReversed(self, forward, backward):
1561
# Transform the descriptions to suit the API: tests use (revno, depth),
1562
# while the API expects (revid, revno, depth)
1563
def complete_revisions(l):
1564
"""Transform the description to suit the API.
1566
Tests use (revno, depth) whil the API expects (revid, revno, depth).
1567
Since the revid is arbitrary, we just duplicate revno
1569
return [ (r, r, d) for r, d in l]
1570
forward = complete_revisions(forward)
1571
backward= complete_revisions(backward)
1572
self.assertEqual(forward, log.reverse_by_depth(backward))
1575
def test_mainline_revisions(self):
1576
self.assertReversed([( '1', 0), ('2', 0)],
1577
[('2', 0), ('1', 0)])
1579
def test_merged_revisions(self):
1580
self.assertReversed([('1', 0), ('2', 0), ('2.2', 1), ('2.1', 1),],
1581
[('2', 0), ('2.1', 1), ('2.2', 1), ('1', 0),])
1582
def test_shifted_merged_revisions(self):
1583
"""Test irregular layout.
1585
Requesting revisions touching a file can produce "holes" in the depths.
1587
self.assertReversed([('1', 0), ('2', 0), ('1.1', 2), ('1.2', 2),],
1588
[('2', 0), ('1.2', 2), ('1.1', 2), ('1', 0),])
1590
def test_merged_without_child_revisions(self):
1591
"""Test irregular layout.
1593
Revision ranges can produce "holes" in the depths.
1595
# When a revision of higher depth doesn't follow one of lower depth, we
1596
# assume a lower depth one is virtually there
1597
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1598
[('4', 4), ('3', 3), ('2', 2), ('1', 2),])
1599
# So we get the same order after reversing below even if the original
1600
# revisions are not in the same order.
1601
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1602
[('3', 3), ('4', 4), ('2', 2), ('1', 2),])
1605
class TestHistoryChange(tests.TestCaseWithTransport):
1607
def setup_a_tree(self):
1608
tree = self.make_branch_and_tree('tree')
1610
self.addCleanup(tree.unlock)
1611
tree.commit('1a', rev_id='1a')
1612
tree.commit('2a', rev_id='2a')
1613
tree.commit('3a', rev_id='3a')
1616
def setup_ab_tree(self):
1617
tree = self.setup_a_tree()
1618
tree.set_last_revision('1a')
1619
tree.branch.set_last_revision_info(1, '1a')
1620
tree.commit('2b', rev_id='2b')
1621
tree.commit('3b', rev_id='3b')
1624
def setup_ac_tree(self):
1625
tree = self.setup_a_tree()
1626
tree.set_last_revision(revision.NULL_REVISION)
1627
tree.branch.set_last_revision_info(0, revision.NULL_REVISION)
1628
tree.commit('1c', rev_id='1c')
1629
tree.commit('2c', rev_id='2c')
1630
tree.commit('3c', rev_id='3c')
1633
def test_all_new(self):
1634
tree = self.setup_ab_tree()
1635
old, new = log.get_history_change('1a', '3a', tree.branch.repository)
1636
self.assertEqual([], old)
1637
self.assertEqual(['2a', '3a'], new)
1639
def test_all_old(self):
1640
tree = self.setup_ab_tree()
1641
old, new = log.get_history_change('3a', '1a', tree.branch.repository)
1642
self.assertEqual([], new)
1643
self.assertEqual(['2a', '3a'], old)
1645
def test_null_old(self):
1646
tree = self.setup_ab_tree()
1647
old, new = log.get_history_change(revision.NULL_REVISION,
1648
'3a', tree.branch.repository)
1649
self.assertEqual([], old)
1650
self.assertEqual(['1a', '2a', '3a'], new)
1652
def test_null_new(self):
1653
tree = self.setup_ab_tree()
1654
old, new = log.get_history_change('3a', revision.NULL_REVISION,
1655
tree.branch.repository)
1656
self.assertEqual([], new)
1657
self.assertEqual(['1a', '2a', '3a'], old)
1659
def test_diverged(self):
1660
tree = self.setup_ab_tree()
1661
old, new = log.get_history_change('3a', '3b', tree.branch.repository)
1662
self.assertEqual(old, ['2a', '3a'])
1663
self.assertEqual(new, ['2b', '3b'])
1665
def test_unrelated(self):
1666
tree = self.setup_ac_tree()
1667
old, new = log.get_history_change('3a', '3c', tree.branch.repository)
1668
self.assertEqual(old, ['1a', '2a', '3a'])
1669
self.assertEqual(new, ['1c', '2c', '3c'])
1671
def test_show_branch_change(self):
1672
tree = self.setup_ab_tree()
1674
log.show_branch_change(tree.branch, s, 3, '3a')
1675
self.assertContainsRe(s.getvalue(),
1676
'[*]{60}\nRemoved Revisions:\n(.|\n)*2a(.|\n)*3a(.|\n)*'
1677
'[*]{60}\n\nAdded Revisions:\n(.|\n)*2b(.|\n)*3b')
1679
def test_show_branch_change_no_change(self):
1680
tree = self.setup_ab_tree()
1682
log.show_branch_change(tree.branch, s, 3, '3b')
1683
self.assertEqual(s.getvalue(),
1684
'Nothing seems to have changed\n')
1686
def test_show_branch_change_no_old(self):
1687
tree = self.setup_ab_tree()
1689
log.show_branch_change(tree.branch, s, 2, '2b')
1690
self.assertContainsRe(s.getvalue(), 'Added Revisions:')
1691
self.assertNotContainsRe(s.getvalue(), 'Removed Revisions:')
1693
def test_show_branch_change_no_new(self):
1694
tree = self.setup_ab_tree()
1695
tree.branch.set_last_revision_info(2, '2b')
1697
log.show_branch_change(tree.branch, s, 3, '3b')
1698
self.assertContainsRe(s.getvalue(), 'Removed Revisions:')
1699
self.assertNotContainsRe(s.getvalue(), 'Added Revisions:')