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 TestCase, TestCaseWithTransport
22
from bzrlib.log import (show_log,
29
from bzrlib.branch import Branch
30
from bzrlib.errors import InvalidRevisionNumber
31
from bzrlib.revision import Revision
34
class LogCatcher(LogFormatter):
35
"""Pull log messages into list rather than displaying them.
37
For ease of testing we save log messages here rather than actually
38
formatting them, so that we can precisely check the result without
39
being too dependent on the exact formatting.
41
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.
44
52
supports_delta = True
46
54
def __init__(self):
47
55
super(LogCatcher, self).__init__(to_file=None)
50
58
def log_revision(self, revision):
51
self.logs.append(revision)
54
class TestShowLog(TestCaseWithTransport):
59
self.revisions.append(revision)
62
class TestShowLog(tests.TestCaseWithTransport):
56
64
def checkDelta(self, delta, **kw):
57
"""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).
58
70
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
71
# By default we expect an empty list
59
72
expected = kw.get(n, [])
60
73
# strip out only the path components
61
74
got = [x[0] for x in getattr(delta, n)]
62
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)
64
83
def test_cur_revno(self):
65
84
wt = self.make_branch_and_tree('.')
69
88
wt.commit('empty commit')
70
show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
71
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
72
start_revision=2, end_revision=1)
73
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
74
start_revision=1, end_revision=2)
75
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
76
start_revision=0, end_revision=2)
77
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
78
start_revision=1, end_revision=0)
79
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
80
start_revision=-1, end_revision=1)
81
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
82
start_revision=1, end_revision=-1)
84
def test_simple_log(self):
85
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):
87
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('.')
95
111
wt.commit('empty commit')
97
show_log(b, lf, verbose=True)
99
eq(lf.logs[0].revno, '1')
100
eq(lf.logs[0].rev.message, 'empty commit')
102
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')
105
123
self.build_tree(['hello'])
107
125
wt.commit('add one file',
108
126
committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
109
127
u'<test@example.com>')
111
lf = self.make_utf8_encoded_stringio()
112
# log using regular thing
113
show_log(b, LongLogFormatter(lf))
115
for l in lf.readlines():
118
# get log as data structure
119
128
lf = LogCatcher()
120
show_log(b, lf, verbose=True)
122
self.log('log entries:')
123
for logentry in lf.logs:
124
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))
126
131
# first one is most recent
127
logentry = lf.logs[0]
128
eq(logentry.revno, '2')
129
eq(logentry.rev.message, 'add one file')
131
self.log('log 2 delta: %r' % d)
132
self.checkDelta(d, added=['hello'])
134
# commit a log message with control characters
135
msg = "All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
136
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')
138
142
lf = LogCatcher()
139
show_log(b, lf, verbose=True)
140
committed_msg = lf.logs[0].rev.message
141
self.log("escaped commit message: %r", committed_msg)
142
self.assert_(msg != committed_msg)
143
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
if wt.branch.repository._serializer.squashes_xml_invalid_characters:
146
self.assertNotEqual(msg, committed_msg)
147
self.assertTrue(len(committed_msg) > len(msg))
149
self.assertEqual(msg, committed_msg)
145
# Check that log message with only XML-valid characters isn't
151
def test_commit_message_without_control_chars(self):
152
wt = self.make_branch_and_tree('.')
146
153
# escaped. As ElementTree apparently does some kind of
147
154
# newline conversion, neither LF (\x0A) nor CR (\x0D) are
148
155
# included in the test commit message, even though they are
149
156
# valid XML 1.0 characters.
150
157
msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
151
self.log("original commit message: %r", msg)
153
159
lf = LogCatcher()
154
show_log(b, lf, verbose=True)
155
committed_msg = lf.logs[0].rev.message
156
self.log("escaped commit message: %r", committed_msg)
157
self.assert_(msg == committed_msg)
160
log.show_log(wt.branch, lf, verbose=True)
161
committed_msg = lf.revisions[0].rev.message
162
self.assertEqual(msg, committed_msg)
159
164
def test_deltas_in_merge_revisions(self):
160
165
"""Check deltas created for both mainline and merge revisions"""
161
eq = self.assertEquals
162
166
wt = self.make_branch_and_tree('parent')
163
167
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
239
262
1 Joe Foo\t2005-11-21
240
263
simple log message
245
class TestLongLogFormatter(TestCaseWithTransport):
247
def normalize_log(self,log):
248
"""Replaces the variable lines of logs with fixed lines"""
249
author = 'author: Dolor Sit <test@example.com>'
250
committer = 'committer: Lorem Ipsum <test@example.com>'
251
lines = log.splitlines(True)
252
for idx,line in enumerate(lines):
253
stripped_line = line.lstrip()
254
indent = ' ' * (len(line) - len(stripped_line))
255
if stripped_line.startswith('author:'):
256
lines[idx] = indent + author + '\n'
257
elif stripped_line.startswith('committer:'):
258
lines[idx] = indent + committer + '\n'
259
elif stripped_line.startswith('timestamp:'):
260
lines[idx] = indent + 'timestamp: Just now\n'
261
return ''.join(lines)
268
def _prepare_tree_with_merges(self, with_tags=False):
269
wt = self.make_branch_and_memory_tree('.')
271
self.addCleanup(wt.unlock)
273
wt.commit('rev-1', rev_id='rev-1',
274
timestamp=1132586655, timezone=36000,
275
committer='Joe Foo <joe@foo.com>')
276
wt.commit('rev-merged', rev_id='rev-2a',
277
timestamp=1132586700, timezone=36000,
278
committer='Joe Foo <joe@foo.com>')
279
wt.set_parent_ids(['rev-1', 'rev-2a'])
280
wt.branch.set_last_revision_info(1, 'rev-1')
281
wt.commit('rev-2', rev_id='rev-2b',
282
timestamp=1132586800, timezone=36000,
283
committer='Joe Foo <joe@foo.com>')
286
branch.tags.set_tag('v0.2', 'rev-2b')
287
wt.commit('rev-3', rev_id='rev-3',
288
timestamp=1132586900, timezone=36000,
289
committer='Jane Foo <jane@foo.com>')
290
branch.tags.set_tag('v1.0rc1', 'rev-3')
291
branch.tags.set_tag('v1.0', 'rev-3')
294
def test_short_log_with_merges(self):
295
wt = self._prepare_tree_with_merges()
296
logfile = self.make_utf8_encoded_stringio()
297
formatter = log.ShortLogFormatter(to_file=logfile)
298
log.show_log(wt.branch, formatter)
299
self.assertEqualDiff("""\
300
2 Joe Foo\t2005-11-22 [merge]
303
1 Joe Foo\t2005-11-22
309
def test_short_log_with_merges_and_advice(self):
310
wt = self._prepare_tree_with_merges()
311
logfile = self.make_utf8_encoded_stringio()
312
formatter = log.ShortLogFormatter(to_file=logfile,
314
log.show_log(wt.branch, formatter)
315
self.assertEqualDiff("""\
316
2 Joe Foo\t2005-11-22 [merge]
319
1 Joe Foo\t2005-11-22
322
Use --include-merges or -n0 to see merged revisions.
326
def test_short_log_with_merges_and_range(self):
327
wt = self.make_branch_and_memory_tree('.')
329
self.addCleanup(wt.unlock)
331
wt.commit('rev-1', rev_id='rev-1',
332
timestamp=1132586655, timezone=36000,
333
committer='Joe Foo <joe@foo.com>')
334
wt.commit('rev-merged', rev_id='rev-2a',
335
timestamp=1132586700, timezone=36000,
336
committer='Joe Foo <joe@foo.com>')
337
wt.branch.set_last_revision_info(1, 'rev-1')
338
wt.set_parent_ids(['rev-1', 'rev-2a'])
339
wt.commit('rev-2b', rev_id='rev-2b',
340
timestamp=1132586800, timezone=36000,
341
committer='Joe Foo <joe@foo.com>')
342
wt.commit('rev-3a', rev_id='rev-3a',
343
timestamp=1132586800, timezone=36000,
344
committer='Joe Foo <joe@foo.com>')
345
wt.branch.set_last_revision_info(2, 'rev-2b')
346
wt.set_parent_ids(['rev-2b', 'rev-3a'])
347
wt.commit('rev-3b', rev_id='rev-3b',
348
timestamp=1132586800, timezone=36000,
349
committer='Joe Foo <joe@foo.com>')
350
logfile = self.make_utf8_encoded_stringio()
351
formatter = log.ShortLogFormatter(to_file=logfile)
352
log.show_log(wt.branch, formatter,
353
start_revision=2, end_revision=3)
354
self.assertEqualDiff("""\
355
3 Joe Foo\t2005-11-22 [merge]
358
2 Joe Foo\t2005-11-22 [merge]
364
def test_short_log_with_tags(self):
365
wt = self._prepare_tree_with_merges(with_tags=True)
366
logfile = self.make_utf8_encoded_stringio()
367
formatter = log.ShortLogFormatter(to_file=logfile)
368
log.show_log(wt.branch, formatter)
369
self.assertEqualDiff("""\
370
3 Jane Foo\t2005-11-22 {v1.0, v1.0rc1}
373
2 Joe Foo\t2005-11-22 {v0.2} [merge]
376
1 Joe Foo\t2005-11-22
382
def test_short_log_single_merge_revision(self):
383
wt = self.make_branch_and_memory_tree('.')
385
self.addCleanup(wt.unlock)
387
wt.commit('rev-1', rev_id='rev-1',
388
timestamp=1132586655, timezone=36000,
389
committer='Joe Foo <joe@foo.com>')
390
wt.commit('rev-merged', rev_id='rev-2a',
391
timestamp=1132586700, timezone=36000,
392
committer='Joe Foo <joe@foo.com>')
393
wt.set_parent_ids(['rev-1', 'rev-2a'])
394
wt.branch.set_last_revision_info(1, 'rev-1')
395
wt.commit('rev-2', rev_id='rev-2b',
396
timestamp=1132586800, timezone=36000,
397
committer='Joe Foo <joe@foo.com>')
398
logfile = self.make_utf8_encoded_stringio()
399
formatter = log.ShortLogFormatter(to_file=logfile)
400
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
402
rev = revspec.in_history(wtb)
403
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
404
self.assertEqualDiff("""\
405
1.1.1 Joe Foo\t2005-11-22
412
class TestShortLogFormatterWithMergeRevisions(tests.TestCaseWithTransport):
414
def test_short_merge_revs_log_with_merges(self):
415
wt = self.make_branch_and_memory_tree('.')
417
self.addCleanup(wt.unlock)
419
wt.commit('rev-1', rev_id='rev-1',
420
timestamp=1132586655, timezone=36000,
421
committer='Joe Foo <joe@foo.com>')
422
wt.commit('rev-merged', rev_id='rev-2a',
423
timestamp=1132586700, timezone=36000,
424
committer='Joe Foo <joe@foo.com>')
425
wt.set_parent_ids(['rev-1', 'rev-2a'])
426
wt.branch.set_last_revision_info(1, 'rev-1')
427
wt.commit('rev-2', rev_id='rev-2b',
428
timestamp=1132586800, timezone=36000,
429
committer='Joe Foo <joe@foo.com>')
430
logfile = self.make_utf8_encoded_stringio()
431
formatter = log.ShortLogFormatter(to_file=logfile, levels=0)
432
log.show_log(wt.branch, formatter)
433
# Note that the 1.1.1 indenting is in fact correct given that
434
# the revision numbers are right justified within 5 characters
435
# for mainline revnos and 9 characters for dotted revnos.
436
self.assertEqualDiff("""\
437
2 Joe Foo\t2005-11-22 [merge]
440
1.1.1 Joe Foo\t2005-11-22
443
1 Joe Foo\t2005-11-22
449
def test_short_merge_revs_log_single_merge_revision(self):
450
wt = self.make_branch_and_memory_tree('.')
452
self.addCleanup(wt.unlock)
454
wt.commit('rev-1', rev_id='rev-1',
455
timestamp=1132586655, timezone=36000,
456
committer='Joe Foo <joe@foo.com>')
457
wt.commit('rev-merged', rev_id='rev-2a',
458
timestamp=1132586700, timezone=36000,
459
committer='Joe Foo <joe@foo.com>')
460
wt.set_parent_ids(['rev-1', 'rev-2a'])
461
wt.branch.set_last_revision_info(1, 'rev-1')
462
wt.commit('rev-2', rev_id='rev-2b',
463
timestamp=1132586800, timezone=36000,
464
committer='Joe Foo <joe@foo.com>')
465
logfile = self.make_utf8_encoded_stringio()
466
formatter = log.ShortLogFormatter(to_file=logfile, levels=0)
467
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
469
rev = revspec.in_history(wtb)
470
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
471
self.assertEqualDiff("""\
472
1.1.1 Joe Foo\t2005-11-22
479
class TestLongLogFormatter(TestCaseWithoutPropsHandler):
263
481
def test_verbose_log(self):
264
482
"""Verbose log includes changed files
268
486
wt = self.make_branch_and_tree('.')
449
671
timestamp=1132711707,
451
673
committer='Lorem Ipsum <test@example.com>',
452
author='John Doe <jdoe@example.com>')
454
formatter = LongLogFormatter(to_file=sio)
455
show_log(b, formatter)
456
self.assertEqualDiff(sio.getvalue(), '''\
457
------------------------------------------------------------
459
author: John Doe <jdoe@example.com>
460
committer: Lorem Ipsum <test@example.com>
461
branch nick: test_author_log
462
timestamp: Wed 2005-11-23 12:08:27 +1000
469
class TestLineLogFormatter(TestCaseWithTransport):
674
authors=['John Doe <jdoe@example.com>',
675
'Jane Rey <jrey@example.com>'])
677
formatter = log.LongLogFormatter(to_file=sio)
678
log.show_log(b, formatter)
679
self.assertEqualDiff('''\
680
------------------------------------------------------------
682
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
683
committer: Lorem Ipsum <test@example.com>
684
branch nick: test_author_log
685
timestamp: Wed 2005-11-23 12:08:27 +1000
691
def test_properties_in_log(self):
692
"""Log includes the custom properties returned by the registered
695
wt = self.make_branch_and_tree('.')
697
self.build_tree(['a'])
699
b.nick = 'test_properties_in_log'
700
wt.commit(message='add a',
701
timestamp=1132711707,
703
committer='Lorem Ipsum <test@example.com>',
704
authors=['John Doe <jdoe@example.com>'])
706
formatter = log.LongLogFormatter(to_file=sio)
708
def trivial_custom_prop_handler(revision):
709
return {'test_prop':'test_value'}
711
log.properties_handler_registry.register(
712
'trivial_custom_prop_handler',
713
trivial_custom_prop_handler)
714
log.show_log(b, formatter)
716
log.properties_handler_registry.remove(
717
'trivial_custom_prop_handler')
718
self.assertEqualDiff('''\
719
------------------------------------------------------------
721
test_prop: test_value
722
author: John Doe <jdoe@example.com>
723
committer: Lorem Ipsum <test@example.com>
724
branch nick: test_properties_in_log
725
timestamp: Wed 2005-11-23 12:08:27 +1000
731
def test_properties_in_short_log(self):
732
"""Log includes the custom properties returned by the registered
735
wt = self.make_branch_and_tree('.')
737
self.build_tree(['a'])
739
b.nick = 'test_properties_in_short_log'
740
wt.commit(message='add a',
741
timestamp=1132711707,
743
committer='Lorem Ipsum <test@example.com>',
744
authors=['John Doe <jdoe@example.com>'])
746
formatter = log.ShortLogFormatter(to_file=sio)
748
def trivial_custom_prop_handler(revision):
749
return {'test_prop':'test_value'}
751
log.properties_handler_registry.register(
752
'trivial_custom_prop_handler',
753
trivial_custom_prop_handler)
754
log.show_log(b, formatter)
756
log.properties_handler_registry.remove(
757
'trivial_custom_prop_handler')
758
self.assertEqualDiff('''\
759
1 John Doe\t2005-11-23
760
test_prop: test_value
766
def test_error_in_properties_handler(self):
767
"""Log includes the custom properties returned by the registered
770
wt = self.make_branch_and_tree('.')
772
self.build_tree(['a'])
774
b.nick = 'test_author_log'
775
wt.commit(message='add a',
776
timestamp=1132711707,
778
committer='Lorem Ipsum <test@example.com>',
779
authors=['John Doe <jdoe@example.com>'],
780
revprops={'first_prop':'first_value'})
782
formatter = log.LongLogFormatter(to_file=sio)
784
def trivial_custom_prop_handler(revision):
785
raise StandardError("a test error")
787
log.properties_handler_registry.register(
788
'trivial_custom_prop_handler',
789
trivial_custom_prop_handler)
790
self.assertRaises(StandardError, log.show_log, b, formatter,)
792
log.properties_handler_registry.remove(
793
'trivial_custom_prop_handler')
795
def test_properties_handler_bad_argument(self):
796
wt = self.make_branch_and_tree('.')
798
self.build_tree(['a'])
800
b.nick = 'test_author_log'
801
wt.commit(message='add a',
802
timestamp=1132711707,
804
committer='Lorem Ipsum <test@example.com>',
805
authors=['John Doe <jdoe@example.com>'],
806
revprops={'a_prop':'test_value'})
808
formatter = log.LongLogFormatter(to_file=sio)
810
def bad_argument_prop_handler(revision):
811
return {'custom_prop_name':revision.properties['a_prop']}
813
log.properties_handler_registry.register(
814
'bad_argument_prop_handler',
815
bad_argument_prop_handler)
817
self.assertRaises(AttributeError, formatter.show_properties,
820
revision = b.repository.get_revision(b.last_revision())
821
formatter.show_properties(revision, '')
822
self.assertEqualDiff('''custom_prop_name: test_value\n''',
825
log.properties_handler_registry.remove(
826
'bad_argument_prop_handler')
829
class TestLongLogFormatterWithoutMergeRevisions(TestCaseWithoutPropsHandler):
831
def test_long_verbose_log(self):
832
"""Verbose log includes changed files
836
wt = self.make_branch_and_tree('.')
838
self.build_tree(['a'])
840
# XXX: why does a longer nick show up?
841
b.nick = 'test_verbose_log'
842
wt.commit(message='add a',
843
timestamp=1132711707,
845
committer='Lorem Ipsum <test@example.com>')
846
logfile = file('out.tmp', 'w+')
847
formatter = log.LongLogFormatter(to_file=logfile, levels=1)
848
log.show_log(b, formatter, verbose=True)
851
log_contents = logfile.read()
852
self.assertEqualDiff('''\
853
------------------------------------------------------------
855
committer: Lorem Ipsum <test@example.com>
856
branch nick: test_verbose_log
857
timestamp: Wed 2005-11-23 12:08:27 +1000
865
def test_long_verbose_contain_deltas(self):
866
wt = self.make_branch_and_tree('parent')
867
self.build_tree(['parent/f1', 'parent/f2'])
869
wt.commit('first post')
870
self.run_bzr('branch parent child')
871
os.unlink('child/f1')
872
file('child/f2', 'wb').write('hello\n')
873
self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
876
self.run_bzr('merge ../child')
877
wt.commit('merge branch 1')
879
sio = self.make_utf8_encoded_stringio()
880
lf = log.LongLogFormatter(to_file=sio, levels=1)
881
log.show_log(b, lf, verbose=True)
882
the_log = normalize_log(sio.getvalue())
883
self.assertEqualDiff("""\
884
------------------------------------------------------------
886
committer: Lorem Ipsum <test@example.com>
895
------------------------------------------------------------
897
committer: Lorem Ipsum <test@example.com>
908
def test_long_trailing_newlines(self):
909
wt = self.make_branch_and_tree('.')
910
b = make_commits_with_trailing_newlines(wt)
911
sio = self.make_utf8_encoded_stringio()
912
lf = log.LongLogFormatter(to_file=sio, levels=1)
914
self.assertEqualDiff("""\
915
------------------------------------------------------------
917
committer: Joe Foo <joe@foo.com>
919
timestamp: Mon 2005-11-21 09:32:56 -0600
921
single line with trailing newline
922
------------------------------------------------------------
924
author: Joe Bar <joe@bar.com>
925
committer: Joe Foo <joe@foo.com>
927
timestamp: Mon 2005-11-21 09:27:22 -0600
932
------------------------------------------------------------
934
committer: Joe Foo <joe@foo.com>
936
timestamp: Mon 2005-11-21 09:24:15 -0600
942
def test_long_author_in_log(self):
943
"""Log includes the author name if it's set in
944
the revision properties
946
wt = self.make_branch_and_tree('.')
948
self.build_tree(['a'])
950
b.nick = 'test_author_log'
951
wt.commit(message='add a',
952
timestamp=1132711707,
954
committer='Lorem Ipsum <test@example.com>',
955
authors=['John Doe <jdoe@example.com>'])
957
formatter = log.LongLogFormatter(to_file=sio, levels=1)
958
log.show_log(b, formatter)
959
self.assertEqualDiff('''\
960
------------------------------------------------------------
962
author: John Doe <jdoe@example.com>
963
committer: Lorem Ipsum <test@example.com>
964
branch nick: test_author_log
965
timestamp: Wed 2005-11-23 12:08:27 +1000
971
def test_long_properties_in_log(self):
972
"""Log includes the custom properties returned by the registered
975
wt = self.make_branch_and_tree('.')
977
self.build_tree(['a'])
979
b.nick = 'test_properties_in_log'
980
wt.commit(message='add a',
981
timestamp=1132711707,
983
committer='Lorem Ipsum <test@example.com>',
984
authors=['John Doe <jdoe@example.com>'])
986
formatter = log.LongLogFormatter(to_file=sio, levels=1)
988
def trivial_custom_prop_handler(revision):
989
return {'test_prop':'test_value'}
991
log.properties_handler_registry.register(
992
'trivial_custom_prop_handler',
993
trivial_custom_prop_handler)
994
log.show_log(b, formatter)
996
log.properties_handler_registry.remove(
997
'trivial_custom_prop_handler')
998
self.assertEqualDiff('''\
999
------------------------------------------------------------
1001
test_prop: test_value
1002
author: John Doe <jdoe@example.com>
1003
committer: Lorem Ipsum <test@example.com>
1004
branch nick: test_properties_in_log
1005
timestamp: Wed 2005-11-23 12:08:27 +1000
1012
class TestLineLogFormatter(tests.TestCaseWithTransport):
471
1014
def test_line_log(self):
472
1015
"""Line log should show revno
476
1019
wt = self.make_branch_and_tree('.')
478
1021
self.build_tree(['a'])
480
1023
b.nick = 'test-line-log'
481
wt.commit(message='add a',
482
timestamp=1132711707,
1024
wt.commit(message='add a',
1025
timestamp=1132711707,
484
1027
committer='Line-Log-Formatter Tester <test@line.log>')
485
1028
logfile = file('out.tmp', 'w+')
486
formatter = LineLogFormatter(to_file=logfile)
487
show_log(b, formatter)
1029
formatter = log.LineLogFormatter(to_file=logfile)
1030
log.show_log(b, formatter)
490
1033
log_contents = logfile.read()
491
self.assertEqualDiff(log_contents, '1: Line-Log-Formatte... 2005-11-23 add a\n')
493
def test_short_log_with_merges(self):
494
wt = self.make_branch_and_memory_tree('.')
498
wt.commit('rev-1', rev_id='rev-1',
499
timestamp=1132586655, timezone=36000,
500
committer='Joe Foo <joe@foo.com>')
501
wt.commit('rev-merged', rev_id='rev-2a',
502
timestamp=1132586700, timezone=36000,
503
committer='Joe Foo <joe@foo.com>')
504
wt.set_parent_ids(['rev-1', 'rev-2a'])
505
wt.branch.set_last_revision_info(1, 'rev-1')
506
wt.commit('rev-2', rev_id='rev-2b',
507
timestamp=1132586800, timezone=36000,
508
committer='Joe Foo <joe@foo.com>')
509
logfile = self.make_utf8_encoded_stringio()
510
formatter = ShortLogFormatter(to_file=logfile)
511
show_log(wt.branch, formatter)
513
self.assertEqualDiff("""\
514
2 Joe Foo\t2005-11-22 [merge]
517
1 Joe Foo\t2005-11-22
520
""", logfile.getvalue())
1034
self.assertEqualDiff('1: Line-Log-Formatte... 2005-11-23 add a\n',
524
1037
def test_trailing_newlines(self):
525
1038
wt = self.make_branch_and_tree('.')
526
1039
b = make_commits_with_trailing_newlines(wt)
527
1040
sio = self.make_utf8_encoded_stringio()
528
lf = LineLogFormatter(to_file=sio)
530
self.assertEqualDiff(sio.getvalue(), """\
1041
lf = log.LineLogFormatter(to_file=sio)
1043
self.assertEqualDiff("""\
531
1044
3: Joe Foo 2005-11-21 single line with trailing newline
532
1045
2: Joe Bar 2005-11-21 multiline
533
1046
1: Joe Foo 2005-11-21 simple log message
537
class TestGetViewRevisions(TestCaseWithTransport):
1050
def _prepare_tree_with_merges(self, with_tags=False):
1051
wt = self.make_branch_and_memory_tree('.')
1053
self.addCleanup(wt.unlock)
1055
wt.commit('rev-1', rev_id='rev-1',
1056
timestamp=1132586655, timezone=36000,
1057
committer='Joe Foo <joe@foo.com>')
1058
wt.commit('rev-merged', rev_id='rev-2a',
1059
timestamp=1132586700, timezone=36000,
1060
committer='Joe Foo <joe@foo.com>')
1061
wt.set_parent_ids(['rev-1', 'rev-2a'])
1062
wt.branch.set_last_revision_info(1, 'rev-1')
1063
wt.commit('rev-2', rev_id='rev-2b',
1064
timestamp=1132586800, timezone=36000,
1065
committer='Joe Foo <joe@foo.com>')
1068
branch.tags.set_tag('v0.2', 'rev-2b')
1069
wt.commit('rev-3', rev_id='rev-3',
1070
timestamp=1132586900, timezone=36000,
1071
committer='Jane Foo <jane@foo.com>')
1072
branch.tags.set_tag('v1.0rc1', 'rev-3')
1073
branch.tags.set_tag('v1.0', 'rev-3')
1076
def test_line_log_single_merge_revision(self):
1077
wt = self._prepare_tree_with_merges()
1078
logfile = self.make_utf8_encoded_stringio()
1079
formatter = log.LineLogFormatter(to_file=logfile)
1080
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
1082
rev = revspec.in_history(wtb)
1083
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
1084
self.assertEqualDiff("""\
1085
1.1.1: Joe Foo 2005-11-22 rev-merged
1089
def test_line_log_with_tags(self):
1090
wt = self._prepare_tree_with_merges(with_tags=True)
1091
logfile = self.make_utf8_encoded_stringio()
1092
formatter = log.LineLogFormatter(to_file=logfile)
1093
log.show_log(wt.branch, formatter)
1094
self.assertEqualDiff("""\
1095
3: Jane Foo 2005-11-22 {v1.0, v1.0rc1} rev-3
1096
2: Joe Foo 2005-11-22 [merge] {v0.2} rev-2
1097
1: Joe Foo 2005-11-22 rev-1
1101
class TestLineLogFormatterWithMergeRevisions(tests.TestCaseWithTransport):
1103
def test_line_merge_revs_log(self):
1104
"""Line log should show revno
1108
wt = self.make_branch_and_tree('.')
1110
self.build_tree(['a'])
1112
b.nick = 'test-line-log'
1113
wt.commit(message='add a',
1114
timestamp=1132711707,
1116
committer='Line-Log-Formatter Tester <test@line.log>')
1117
logfile = file('out.tmp', 'w+')
1118
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1119
log.show_log(b, formatter)
1122
log_contents = logfile.read()
1123
self.assertEqualDiff('1: Line-Log-Formatte... 2005-11-23 add a\n',
1126
def test_line_merge_revs_log_single_merge_revision(self):
1127
wt = self.make_branch_and_memory_tree('.')
1129
self.addCleanup(wt.unlock)
1131
wt.commit('rev-1', rev_id='rev-1',
1132
timestamp=1132586655, timezone=36000,
1133
committer='Joe Foo <joe@foo.com>')
1134
wt.commit('rev-merged', rev_id='rev-2a',
1135
timestamp=1132586700, timezone=36000,
1136
committer='Joe Foo <joe@foo.com>')
1137
wt.set_parent_ids(['rev-1', 'rev-2a'])
1138
wt.branch.set_last_revision_info(1, 'rev-1')
1139
wt.commit('rev-2', rev_id='rev-2b',
1140
timestamp=1132586800, timezone=36000,
1141
committer='Joe Foo <joe@foo.com>')
1142
logfile = self.make_utf8_encoded_stringio()
1143
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1144
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
1146
rev = revspec.in_history(wtb)
1147
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
1148
self.assertEqualDiff("""\
1149
1.1.1: Joe Foo 2005-11-22 rev-merged
1153
def test_line_merge_revs_log_with_merges(self):
1154
wt = self.make_branch_and_memory_tree('.')
1156
self.addCleanup(wt.unlock)
1158
wt.commit('rev-1', rev_id='rev-1',
1159
timestamp=1132586655, timezone=36000,
1160
committer='Joe Foo <joe@foo.com>')
1161
wt.commit('rev-merged', rev_id='rev-2a',
1162
timestamp=1132586700, timezone=36000,
1163
committer='Joe Foo <joe@foo.com>')
1164
wt.set_parent_ids(['rev-1', 'rev-2a'])
1165
wt.branch.set_last_revision_info(1, 'rev-1')
1166
wt.commit('rev-2', rev_id='rev-2b',
1167
timestamp=1132586800, timezone=36000,
1168
committer='Joe Foo <joe@foo.com>')
1169
logfile = self.make_utf8_encoded_stringio()
1170
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1171
log.show_log(wt.branch, formatter)
1172
self.assertEqualDiff("""\
1173
2: Joe Foo 2005-11-22 [merge] rev-2
1174
1.1.1: Joe Foo 2005-11-22 rev-merged
1175
1: Joe Foo 2005-11-22 rev-1
1179
class TestGetViewRevisions(tests.TestCaseWithTransport):
539
1181
def make_tree_with_commits(self):
540
1182
"""Create a tree with well-known revision ids"""
559
1201
return mainline_revs, rev_nos, wt
561
def make_tree_with_many_merges(self):
1203
def make_branch_with_many_merges(self):
562
1204
"""Create a tree with well-known revision ids"""
563
wt = self.make_branch_and_tree('tree1')
564
wt.commit('commit one', rev_id='1')
565
wt.commit('commit two', rev_id='2')
566
tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
567
tree3.commit('commit three a', rev_id='3a')
568
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
569
tree2.merge_from_branch(tree3.branch)
570
tree2.commit('commit three b', rev_id='3b')
571
wt.merge_from_branch(tree2.branch)
572
wt.commit('commit three c', rev_id='3c')
573
tree2.commit('four-a', rev_id='4a')
574
wt.merge_from_branch(tree2.branch)
575
wt.commit('four-b', rev_id='4b')
1205
builder = self.make_branch_builder('tree1')
1206
builder.start_series()
1207
builder.build_snapshot('1', None, [
1208
('add', ('', 'TREE_ROOT', 'directory', '')),
1209
('add', ('f', 'f-id', 'file', '1\n'))])
1210
builder.build_snapshot('2', ['1'], [])
1211
builder.build_snapshot('3a', ['2'], [
1212
('modify', ('f-id', '1\n2\n3a\n'))])
1213
builder.build_snapshot('3b', ['2', '3a'], [
1214
('modify', ('f-id', '1\n2\n3a\n'))])
1215
builder.build_snapshot('3c', ['2', '3b'], [
1216
('modify', ('f-id', '1\n2\n3a\n'))])
1217
builder.build_snapshot('4a', ['3b'], [])
1218
builder.build_snapshot('4b', ['3c', '4a'], [])
1219
builder.finish_series()
576
1233
mainline_revs = [None, '1', '2', '3c', '4b']
577
1234
rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
578
1235
full_rev_nos_for_reference = {
581
'3a': '2.2.1', #first commit tree 3
582
'3b': '2.1.1', # first commit tree 2
1238
'3a': '2.1.1', #first commit tree 3
1239
'3b': '2.2.1', # first commit tree 2
583
1240
'3c': '3', #merges 3b to main
584
'4a': '2.1.2', # second commit tree 2
1241
'4a': '2.2.2', # second commit tree 2
585
1242
'4b': '4', # merges 4a to main
587
return mainline_revs, rev_nos, wt
1244
return mainline_revs, rev_nos, builder.get_branch()
589
1246
def test_get_view_revisions_forward(self):
590
1247
"""Test the get_view_revisions method"""
591
1248
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
592
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
1250
self.addCleanup(wt.unlock)
1251
revisions = list(log.get_view_revisions(
1252
mainline_revs, rev_nos, wt.branch, 'forward'))
594
1253
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
596
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
597
'forward', include_merges=False))
1255
revisions2 = list(log.get_view_revisions(
1256
mainline_revs, rev_nos, wt.branch, 'forward',
1257
include_merges=False))
598
1258
self.assertEqual(revisions, revisions2)
600
1260
def test_get_view_revisions_reverse(self):
601
1261
"""Test the get_view_revisions with reverse"""
602
1262
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
603
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
1264
self.addCleanup(wt.unlock)
1265
revisions = list(log.get_view_revisions(
1266
mainline_revs, rev_nos, wt.branch, 'reverse'))
605
1267
self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
607
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
608
'reverse', include_merges=False))
1269
revisions2 = list(log.get_view_revisions(
1270
mainline_revs, rev_nos, wt.branch, 'reverse',
1271
include_merges=False))
609
1272
self.assertEqual(revisions, revisions2)
611
1274
def test_get_view_revisions_merge(self):
612
1275
"""Test get_view_revisions when there are merges"""
613
1276
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
614
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
616
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
617
('4b', '4', 0), ('4a', '3.1.1', 1)],
619
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
620
'forward', include_merges=False))
621
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
1278
self.addCleanup(wt.unlock)
1279
revisions = list(log.get_view_revisions(
1280
mainline_revs, rev_nos, wt.branch, 'forward'))
1281
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
1282
('4b', '4', 0), ('4a', '3.1.1', 1)],
1284
revisions = list(log.get_view_revisions(
1285
mainline_revs, rev_nos, wt.branch, 'forward',
1286
include_merges=False))
1287
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
625
1291
def test_get_view_revisions_merge_reverse(self):
626
1292
"""Test get_view_revisions in reverse when there are merges"""
627
1293
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
628
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
1295
self.addCleanup(wt.unlock)
1296
revisions = list(log.get_view_revisions(
1297
mainline_revs, rev_nos, wt.branch, 'reverse'))
630
1298
self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
631
('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
633
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
634
'reverse', include_merges=False))
1299
('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
1301
revisions = list(log.get_view_revisions(
1302
mainline_revs, rev_nos, wt.branch, 'reverse',
1303
include_merges=False))
635
1304
self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
639
1308
def test_get_view_revisions_merge2(self):
640
1309
"""Test get_view_revisions when there are merges"""
641
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
642
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
1310
mainline_revs, rev_nos, b = self.make_branch_with_many_merges()
1312
self.addCleanup(b.unlock)
1313
revisions = list(log.get_view_revisions(
1314
mainline_revs, rev_nos, b, 'forward'))
644
1315
expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
645
('3a', '2.2.1', 1), ('3b', '2.1.1', 1), ('4b', '4', 0),
1316
('3b', '2.2.1', 1), ('3a', '2.1.1', 2), ('4b', '4', 0),
647
1318
self.assertEqual(expected, revisions)
648
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
649
'forward', include_merges=False))
1319
revisions = list(log.get_view_revisions(
1320
mainline_revs, rev_nos, b, 'forward',
1321
include_merges=False))
650
1322
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
655
class TestGetRevisionsTouchingFileID(TestCaseWithTransport):
1327
def test_file_id_for_range(self):
1328
mainline_revs, rev_nos, b = self.make_branch_with_many_merges()
1330
self.addCleanup(b.unlock)
1332
def rev_from_rev_id(revid, branch):
1333
revspec = revisionspec.RevisionSpec.from_string('revid:%s' % revid)
1334
return revspec.in_history(branch)
1336
def view_revs(start_rev, end_rev, file_id, direction):
1337
revs = log.calculate_view_revisions(
1339
start_rev, # start_revision
1340
end_rev, # end_revision
1341
direction, # direction
1342
file_id, # specific_fileid
1343
True, # generate_merge_revisions
1347
rev_3a = rev_from_rev_id('3a', b)
1348
rev_4b = rev_from_rev_id('4b', b)
1349
self.assertEqual([('3c', '3', 0), ('3b', '2.2.1', 1), ('3a', '2.1.1', 2)],
1350
view_revs(rev_3a, rev_4b, 'f-id', 'reverse'))
1351
# Note: 3c still appears before 3a here because of depth-based sorting
1352
self.assertEqual([('3c', '3', 0), ('3b', '2.2.1', 1), ('3a', '2.1.1', 2)],
1353
view_revs(rev_3a, rev_4b, 'f-id', 'forward'))
1356
class TestGetRevisionsTouchingFileID(tests.TestCaseWithTransport):
657
1358
def create_tree_with_single_merge(self):
658
1359
"""Create a branch with a moderate layout.
708
1409
tree.commit('D', rev_id='D')
710
1411
# Switch to a read lock for this tree.
711
# We still have addCleanup(unlock)
1412
# We still have an addCleanup(tree.unlock) pending
713
1414
tree.lock_read()
1417
def check_delta(self, delta, **kw):
1418
"""Check the filenames touched by a delta are as expected.
1420
Caller only have to pass in the list of files for each part, all
1421
unspecified parts are considered empty (and checked as such).
1423
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
1424
# By default we expect an empty list
1425
expected = kw.get(n, [])
1426
# strip out only the path components
1427
got = [x[0] for x in getattr(delta, n)]
1428
self.assertEqual(expected, got)
716
1430
def test_tree_with_single_merge(self):
717
1431
"""Make sure the tree layout is correct."""
718
1432
tree = self.create_tree_with_single_merge()
719
1433
rev_A_tree = tree.branch.repository.revision_tree('A')
720
1434
rev_B_tree = tree.branch.repository.revision_tree('B')
722
f1_changed = (u'f1', 'f1-id', 'file', True, False)
723
f2_changed = (u'f2', 'f2-id', 'file', True, False)
724
f3_changed = (u'f3', 'f3-id', 'file', True, False)
726
delta = rev_B_tree.changes_from(rev_A_tree)
727
self.assertEqual([f1_changed, f3_changed], delta.modified)
728
self.assertEqual([], delta.renamed)
729
self.assertEqual([], delta.added)
730
self.assertEqual([], delta.removed)
732
1435
rev_C_tree = tree.branch.repository.revision_tree('C')
733
delta = rev_C_tree.changes_from(rev_A_tree)
734
self.assertEqual([f2_changed, f3_changed], delta.modified)
735
self.assertEqual([], delta.renamed)
736
self.assertEqual([], delta.added)
737
self.assertEqual([], delta.removed)
739
1436
rev_D_tree = tree.branch.repository.revision_tree('D')
740
delta = rev_D_tree.changes_from(rev_B_tree)
741
self.assertEqual([f2_changed, f3_changed], delta.modified)
742
self.assertEqual([], delta.renamed)
743
self.assertEqual([], delta.added)
744
self.assertEqual([], delta.removed)
746
delta = rev_D_tree.changes_from(rev_C_tree)
747
self.assertEqual([f1_changed, f3_changed], delta.modified)
748
self.assertEqual([], delta.renamed)
749
self.assertEqual([], delta.added)
750
self.assertEqual([], delta.removed)
1438
self.check_delta(rev_B_tree.changes_from(rev_A_tree),
1439
modified=['f1', 'f3'])
1441
self.check_delta(rev_C_tree.changes_from(rev_A_tree),
1442
modified=['f2', 'f3'])
1444
self.check_delta(rev_D_tree.changes_from(rev_B_tree),
1445
modified=['f2', 'f3'])
1447
self.check_delta(rev_D_tree.changes_from(rev_C_tree),
1448
modified=['f1', 'f3'])
752
1450
def assertAllRevisionsForFileID(self, tree, file_id, revisions):
753
"""Make sure _filter_revisions_touching_file_id returns the right values.
1451
"""Ensure _filter_revisions_touching_file_id returns the right values.
755
1453
Get the return value from _filter_revisions_touching_file_id and make
756
1454
sure they are correct.
758
# The api for _get_revisions_touching_file_id is a little crazy,
1456
# The api for _filter_revisions_touching_file_id is a little crazy.
759
1457
# So we do the setup here.
760
1458
mainline = tree.branch.revision_history()
761
1459
mainline.insert(0, None)
799
1519
self.assertNotContainsRe(s.getvalue(), 'foo')
802
class TestLogFormatter(TestCase):
1522
class TestLogFormatter(tests.TestCase):
804
1524
def test_short_committer(self):
805
rev = Revision('a-id')
1525
rev = revision.Revision('a-id')
806
1526
rev.committer = 'John Doe <jdoe@example.com>'
807
lf = LogFormatter(None)
1527
lf = log.LogFormatter(None)
808
1528
self.assertEqual('John Doe', lf.short_committer(rev))
1529
rev.committer = 'John Smith <jsmith@example.com>'
1530
self.assertEqual('John Smith', lf.short_committer(rev))
1531
rev.committer = 'John Smith'
1532
self.assertEqual('John Smith', lf.short_committer(rev))
1533
rev.committer = 'jsmith@example.com'
1534
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1535
rev.committer = '<jsmith@example.com>'
1536
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1537
rev.committer = 'John Smith jsmith@example.com'
1538
self.assertEqual('John Smith', lf.short_committer(rev))
810
1540
def test_short_author(self):
811
rev = Revision('a-id')
1541
rev = revision.Revision('a-id')
812
1542
rev.committer = 'John Doe <jdoe@example.com>'
813
lf = LogFormatter(None)
1543
lf = log.LogFormatter(None)
814
1544
self.assertEqual('John Doe', lf.short_author(rev))
815
1545
rev.properties['author'] = 'John Smith <jsmith@example.com>'
816
1546
self.assertEqual('John Smith', lf.short_author(rev))
1547
rev.properties['author'] = 'John Smith'
1548
self.assertEqual('John Smith', lf.short_author(rev))
1549
rev.properties['author'] = 'jsmith@example.com'
1550
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1551
rev.properties['author'] = '<jsmith@example.com>'
1552
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1553
rev.properties['author'] = 'John Smith jsmith@example.com'
1554
self.assertEqual('John Smith', lf.short_author(rev))
1555
del rev.properties['author']
1556
rev.properties['authors'] = ('John Smith <jsmith@example.com>\n'
1557
'Jane Rey <jrey@example.com>')
1558
self.assertEqual('John Smith', lf.short_author(rev))
1561
class TestReverseByDepth(tests.TestCase):
1562
"""Test reverse_by_depth behavior.
1564
This is used to present revisions in forward (oldest first) order in a nice
1567
The tests use lighter revision description to ease reading.
1570
def assertReversed(self, forward, backward):
1571
# Transform the descriptions to suit the API: tests use (revno, depth),
1572
# while the API expects (revid, revno, depth)
1573
def complete_revisions(l):
1574
"""Transform the description to suit the API.
1576
Tests use (revno, depth) whil the API expects (revid, revno, depth).
1577
Since the revid is arbitrary, we just duplicate revno
1579
return [ (r, r, d) for r, d in l]
1580
forward = complete_revisions(forward)
1581
backward= complete_revisions(backward)
1582
self.assertEqual(forward, log.reverse_by_depth(backward))
1585
def test_mainline_revisions(self):
1586
self.assertReversed([( '1', 0), ('2', 0)],
1587
[('2', 0), ('1', 0)])
1589
def test_merged_revisions(self):
1590
self.assertReversed([('1', 0), ('2', 0), ('2.2', 1), ('2.1', 1),],
1591
[('2', 0), ('2.1', 1), ('2.2', 1), ('1', 0),])
1592
def test_shifted_merged_revisions(self):
1593
"""Test irregular layout.
1595
Requesting revisions touching a file can produce "holes" in the depths.
1597
self.assertReversed([('1', 0), ('2', 0), ('1.1', 2), ('1.2', 2),],
1598
[('2', 0), ('1.2', 2), ('1.1', 2), ('1', 0),])
1600
def test_merged_without_child_revisions(self):
1601
"""Test irregular layout.
1603
Revision ranges can produce "holes" in the depths.
1605
# When a revision of higher depth doesn't follow one of lower depth, we
1606
# assume a lower depth one is virtually there
1607
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1608
[('4', 4), ('3', 3), ('2', 2), ('1', 2),])
1609
# So we get the same order after reversing below even if the original
1610
# revisions are not in the same order.
1611
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1612
[('3', 3), ('4', 4), ('2', 2), ('1', 2),])
1615
class TestHistoryChange(tests.TestCaseWithTransport):
1617
def setup_a_tree(self):
1618
tree = self.make_branch_and_tree('tree')
1620
self.addCleanup(tree.unlock)
1621
tree.commit('1a', rev_id='1a')
1622
tree.commit('2a', rev_id='2a')
1623
tree.commit('3a', rev_id='3a')
1626
def setup_ab_tree(self):
1627
tree = self.setup_a_tree()
1628
tree.set_last_revision('1a')
1629
tree.branch.set_last_revision_info(1, '1a')
1630
tree.commit('2b', rev_id='2b')
1631
tree.commit('3b', rev_id='3b')
1634
def setup_ac_tree(self):
1635
tree = self.setup_a_tree()
1636
tree.set_last_revision(revision.NULL_REVISION)
1637
tree.branch.set_last_revision_info(0, revision.NULL_REVISION)
1638
tree.commit('1c', rev_id='1c')
1639
tree.commit('2c', rev_id='2c')
1640
tree.commit('3c', rev_id='3c')
1643
def test_all_new(self):
1644
tree = self.setup_ab_tree()
1645
old, new = log.get_history_change('1a', '3a', tree.branch.repository)
1646
self.assertEqual([], old)
1647
self.assertEqual(['2a', '3a'], new)
1649
def test_all_old(self):
1650
tree = self.setup_ab_tree()
1651
old, new = log.get_history_change('3a', '1a', tree.branch.repository)
1652
self.assertEqual([], new)
1653
self.assertEqual(['2a', '3a'], old)
1655
def test_null_old(self):
1656
tree = self.setup_ab_tree()
1657
old, new = log.get_history_change(revision.NULL_REVISION,
1658
'3a', tree.branch.repository)
1659
self.assertEqual([], old)
1660
self.assertEqual(['1a', '2a', '3a'], new)
1662
def test_null_new(self):
1663
tree = self.setup_ab_tree()
1664
old, new = log.get_history_change('3a', revision.NULL_REVISION,
1665
tree.branch.repository)
1666
self.assertEqual([], new)
1667
self.assertEqual(['1a', '2a', '3a'], old)
1669
def test_diverged(self):
1670
tree = self.setup_ab_tree()
1671
old, new = log.get_history_change('3a', '3b', tree.branch.repository)
1672
self.assertEqual(old, ['2a', '3a'])
1673
self.assertEqual(new, ['2b', '3b'])
1675
def test_unrelated(self):
1676
tree = self.setup_ac_tree()
1677
old, new = log.get_history_change('3a', '3c', tree.branch.repository)
1678
self.assertEqual(old, ['1a', '2a', '3a'])
1679
self.assertEqual(new, ['1c', '2c', '3c'])
1681
def test_show_branch_change(self):
1682
tree = self.setup_ab_tree()
1684
log.show_branch_change(tree.branch, s, 3, '3a')
1685
self.assertContainsRe(s.getvalue(),
1686
'[*]{60}\nRemoved Revisions:\n(.|\n)*2a(.|\n)*3a(.|\n)*'
1687
'[*]{60}\n\nAdded Revisions:\n(.|\n)*2b(.|\n)*3b')
1689
def test_show_branch_change_no_change(self):
1690
tree = self.setup_ab_tree()
1692
log.show_branch_change(tree.branch, s, 3, '3b')
1693
self.assertEqual(s.getvalue(),
1694
'Nothing seems to have changed\n')
1696
def test_show_branch_change_no_old(self):
1697
tree = self.setup_ab_tree()
1699
log.show_branch_change(tree.branch, s, 2, '2b')
1700
self.assertContainsRe(s.getvalue(), 'Added Revisions:')
1701
self.assertNotContainsRe(s.getvalue(), 'Removed Revisions:')
1703
def test_show_branch_change_no_new(self):
1704
tree = self.setup_ab_tree()
1705
tree.branch.set_last_revision_info(2, '2b')
1707
log.show_branch_change(tree.branch, s, 3, '3b')
1708
self.assertContainsRe(s.getvalue(), 'Removed Revisions:')
1709
self.assertNotContainsRe(s.getvalue(), 'Added Revisions:')