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 TestCaseForLogFormatter(tests.TestCaseWithTransport):
33
super(TestCaseForLogFormatter, 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)
43
def assertFormatterResult(self, result, branch, formatter_class,
44
formatter_kwargs=None, show_log_kwargs=None,
46
logfile = self.make_utf8_encoded_stringio()
47
if formatter_kwargs is None:
49
formatter = formatter_class(to_file=logfile, **formatter_kwargs)
50
if show_log_kwargs is None:
52
log.show_log(branch, formatter, **show_log_kwargs)
53
log_content = logfile.getvalue()
55
log_content = normalize_log(log_content)
56
self.assertEqualDiff(result, log_content)
58
def make_standard_commit(self, branch_nick, **kwargs):
59
wt = self.make_branch_and_tree('.')
61
self.addCleanup(wt.unlock)
62
self.build_tree(['a'])
64
wt.branch.nick = branch_nick
66
kwargs.setdefault('message', 'add a')
67
kwargs.setdefault('timestamp', 1132711707)
68
kwargs.setdefault('timezone', 36000)
69
kwargs.setdefault('committer', 'Lorem Ipsum <test@example.com>')
70
kwargs.setdefault('authors', ['John Doe <jdoe@example.com>'])
74
def _prepare_tree_with_merges(self, with_tags=False):
75
wt = self.make_branch_and_memory_tree('.')
77
self.addCleanup(wt.unlock)
79
wt.commit('rev-1', rev_id='rev-1',
80
timestamp=1132586655, timezone=36000,
81
committer='Joe Foo <joe@foo.com>')
82
wt.commit('rev-merged', rev_id='rev-2a',
83
timestamp=1132586700, timezone=36000,
84
committer='Joe Foo <joe@foo.com>')
85
wt.set_parent_ids(['rev-1', 'rev-2a'])
86
wt.branch.set_last_revision_info(1, 'rev-1')
87
wt.commit('rev-2', rev_id='rev-2b',
88
timestamp=1132586800, timezone=36000,
89
committer='Joe Foo <joe@foo.com>')
92
branch.tags.set_tag('v0.2', 'rev-2b')
93
wt.commit('rev-3', rev_id='rev-3',
94
timestamp=1132586900, timezone=36000,
95
committer='Jane Foo <jane@foo.com>')
96
branch.tags.set_tag('v1.0rc1', 'rev-3')
97
branch.tags.set_tag('v1.0', 'rev-3')
103
class LogCatcher(log.LogFormatter):
104
"""Pull log messages into a list rather than displaying them.
106
To simplify testing we save logged revisions here rather than actually
107
formatting anything, so that we can precisely check the result without
108
being dependent on the formatting.
44
111
supports_delta = True
46
113
def __init__(self):
47
114
super(LogCatcher, self).__init__(to_file=None)
50
117
def log_revision(self, revision):
51
self.logs.append(revision)
54
class TestShowLog(TestCaseWithTransport):
118
self.revisions.append(revision)
121
class TestShowLog(tests.TestCaseWithTransport):
56
123
def checkDelta(self, delta, **kw):
57
"""Check the filenames touched by a delta are as expected."""
124
"""Check the filenames touched by a delta are as expected.
126
Caller only have to pass in the list of files for each part, all
127
unspecified parts are considered empty (and checked as such).
58
129
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
130
# By default we expect an empty list
59
131
expected = kw.get(n, [])
60
132
# strip out only the path components
61
133
got = [x[0] for x in getattr(delta, n)]
62
self.assertEquals(expected, got)
134
self.assertEqual(expected, got)
136
def assertInvalidRevisonNumber(self, br, start, end):
138
self.assertRaises(errors.InvalidRevisionNumber,
139
log.show_log, br, lf,
140
start_revision=start, end_revision=end)
64
142
def test_cur_revno(self):
65
143
wt = self.make_branch_and_tree('.')
69
147
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
148
log.show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
150
# Since there is a single revision in the branch all the combinations
152
self.assertInvalidRevisonNumber(b, 2, 1)
153
self.assertInvalidRevisonNumber(b, 1, 2)
154
self.assertInvalidRevisonNumber(b, 0, 2)
155
self.assertInvalidRevisonNumber(b, 1, 0)
156
self.assertInvalidRevisonNumber(b, -1, 1)
157
self.assertInvalidRevisonNumber(b, 1, -1)
159
def test_empty_branch(self):
87
160
wt = self.make_branch_and_tree('.')
163
log.show_log(wt.branch, lf)
165
self.assertEqual([], lf.revisions)
167
def test_empty_commit(self):
168
wt = self.make_branch_and_tree('.')
95
170
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)
172
log.show_log(wt.branch, lf, verbose=True)
174
self.assertEqual(1, len(revs))
175
self.assertEqual('1', revs[0].revno)
176
self.assertEqual('empty commit', revs[0].rev.message)
177
self.checkDelta(revs[0].delta)
179
def test_simple_commit(self):
180
wt = self.make_branch_and_tree('.')
181
wt.commit('empty commit')
105
182
self.build_tree(['hello'])
107
184
wt.commit('add one file',
108
185
committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
109
186
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
187
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))
188
log.show_log(wt.branch, lf, verbose=True)
189
self.assertEqual(2, len(lf.revisions))
126
190
# 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)
191
log_entry = lf.revisions[0]
192
self.assertEqual('2', log_entry.revno)
193
self.assertEqual('add one file', log_entry.rev.message)
194
self.checkDelta(log_entry.delta, added=['hello'])
196
def test_commit_message_with_control_chars(self):
197
wt = self.make_branch_and_tree('.')
198
msg = u"All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
199
msg = msg.replace(u'\r', u'\n')
138
201
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))
202
log.show_log(wt.branch, lf, verbose=True)
203
committed_msg = lf.revisions[0].rev.message
204
if wt.branch.repository._serializer.squashes_xml_invalid_characters:
205
self.assertNotEqual(msg, committed_msg)
206
self.assertTrue(len(committed_msg) > len(msg))
208
self.assertEqual(msg, committed_msg)
145
# Check that log message with only XML-valid characters isn't
210
def test_commit_message_without_control_chars(self):
211
wt = self.make_branch_and_tree('.')
146
212
# escaped. As ElementTree apparently does some kind of
147
213
# newline conversion, neither LF (\x0A) nor CR (\x0D) are
148
214
# included in the test commit message, even though they are
149
215
# valid XML 1.0 characters.
150
216
msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
151
self.log("original commit message: %r", msg)
153
218
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)
219
log.show_log(wt.branch, lf, verbose=True)
220
committed_msg = lf.revisions[0].rev.message
221
self.assertEqual(msg, committed_msg)
159
223
def test_deltas_in_merge_revisions(self):
160
224
"""Check deltas created for both mainline and merge revisions"""
161
eq = self.assertEquals
162
225
wt = self.make_branch_and_tree('parent')
163
226
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
239
318
1 Joe Foo\t2005-11-21
240
319
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)
322
b, log.ShortLogFormatter)
324
def test_short_log_with_merges(self):
325
wt = self._prepare_tree_with_merges()
326
self.assertFormatterResult("""\
327
2 Joe Foo\t2005-11-22 [merge]
330
1 Joe Foo\t2005-11-22
334
wt.branch, log.ShortLogFormatter)
336
def test_short_log_with_merges_and_advice(self):
337
wt = self._prepare_tree_with_merges()
338
self.assertFormatterResult("""\
339
2 Joe Foo\t2005-11-22 [merge]
342
1 Joe Foo\t2005-11-22
345
Use --include-merges or -n0 to see merged revisions.
347
wt.branch, log.ShortLogFormatter,
348
formatter_kwargs=dict(show_advice=True))
350
def test_short_log_with_merges_and_range(self):
351
wt = self.make_branch_and_memory_tree('.')
353
self.addCleanup(wt.unlock)
355
wt.commit('rev-1', rev_id='rev-1',
356
timestamp=1132586655, timezone=36000,
357
committer='Joe Foo <joe@foo.com>')
358
wt.commit('rev-merged', rev_id='rev-2a',
359
timestamp=1132586700, timezone=36000,
360
committer='Joe Foo <joe@foo.com>')
361
wt.branch.set_last_revision_info(1, 'rev-1')
362
wt.set_parent_ids(['rev-1', 'rev-2a'])
363
wt.commit('rev-2b', rev_id='rev-2b',
364
timestamp=1132586800, timezone=36000,
365
committer='Joe Foo <joe@foo.com>')
366
wt.commit('rev-3a', rev_id='rev-3a',
367
timestamp=1132586800, timezone=36000,
368
committer='Joe Foo <joe@foo.com>')
369
wt.branch.set_last_revision_info(2, 'rev-2b')
370
wt.set_parent_ids(['rev-2b', 'rev-3a'])
371
wt.commit('rev-3b', rev_id='rev-3b',
372
timestamp=1132586800, timezone=36000,
373
committer='Joe Foo <joe@foo.com>')
374
self.assertFormatterResult("""\
375
3 Joe Foo\t2005-11-22 [merge]
378
2 Joe Foo\t2005-11-22 [merge]
382
wt.branch, log.ShortLogFormatter,
383
show_log_kwargs=dict(start_revision=2, end_revision=3))
385
def test_short_log_with_tags(self):
386
wt = self._prepare_tree_with_merges(with_tags=True)
387
self.assertFormatterResult("""\
388
3 Jane Foo\t2005-11-22 {v1.0, v1.0rc1}
391
2 Joe Foo\t2005-11-22 {v0.2} [merge]
394
1 Joe Foo\t2005-11-22
398
wt.branch, log.ShortLogFormatter)
400
def test_short_log_single_merge_revision(self):
401
wt = self.make_branch_and_memory_tree('.')
403
self.addCleanup(wt.unlock)
405
wt.commit('rev-1', rev_id='rev-1',
406
timestamp=1132586655, timezone=36000,
407
committer='Joe Foo <joe@foo.com>')
408
wt.commit('rev-merged', rev_id='rev-2a',
409
timestamp=1132586700, timezone=36000,
410
committer='Joe Foo <joe@foo.com>')
411
wt.set_parent_ids(['rev-1', 'rev-2a'])
412
wt.branch.set_last_revision_info(1, 'rev-1')
413
wt.commit('rev-2', rev_id='rev-2b',
414
timestamp=1132586800, timezone=36000,
415
committer='Joe Foo <joe@foo.com>')
416
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
417
rev = revspec.in_history(wt.branch)
418
self.assertFormatterResult("""\
419
1.1.1 Joe Foo\t2005-11-22
423
wt.branch, log.ShortLogFormatter,
424
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
427
class TestShortLogFormatterWithMergeRevisions(TestCaseForLogFormatter):
429
def test_short_merge_revs_log_with_merges(self):
430
wt = self.make_branch_and_memory_tree('.')
432
self.addCleanup(wt.unlock)
434
wt.commit('rev-1', rev_id='rev-1',
435
timestamp=1132586655, timezone=36000,
436
committer='Joe Foo <joe@foo.com>')
437
wt.commit('rev-merged', rev_id='rev-2a',
438
timestamp=1132586700, timezone=36000,
439
committer='Joe Foo <joe@foo.com>')
440
wt.set_parent_ids(['rev-1', 'rev-2a'])
441
wt.branch.set_last_revision_info(1, 'rev-1')
442
wt.commit('rev-2', rev_id='rev-2b',
443
timestamp=1132586800, timezone=36000,
444
committer='Joe Foo <joe@foo.com>')
445
# Note that the 1.1.1 indenting is in fact correct given that
446
# the revision numbers are right justified within 5 characters
447
# for mainline revnos and 9 characters for dotted revnos.
448
self.assertFormatterResult("""\
449
2 Joe Foo\t2005-11-22 [merge]
452
1.1.1 Joe Foo\t2005-11-22
455
1 Joe Foo\t2005-11-22
459
wt.branch, log.ShortLogFormatter,
460
formatter_kwargs=dict(levels=0))
462
def test_short_merge_revs_log_single_merge_revision(self):
463
wt = self.make_branch_and_memory_tree('.')
465
self.addCleanup(wt.unlock)
467
wt.commit('rev-1', rev_id='rev-1',
468
timestamp=1132586655, timezone=36000,
469
committer='Joe Foo <joe@foo.com>')
470
wt.commit('rev-merged', rev_id='rev-2a',
471
timestamp=1132586700, timezone=36000,
472
committer='Joe Foo <joe@foo.com>')
473
wt.set_parent_ids(['rev-1', 'rev-2a'])
474
wt.branch.set_last_revision_info(1, 'rev-1')
475
wt.commit('rev-2', rev_id='rev-2b',
476
timestamp=1132586800, timezone=36000,
477
committer='Joe Foo <joe@foo.com>')
478
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
479
rev = revspec.in_history(wt.branch)
480
self.assertFormatterResult("""\
481
1.1.1 Joe Foo\t2005-11-22
485
wt.branch, log.ShortLogFormatter,
486
formatter_kwargs=dict(levels=0),
487
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
490
class TestLongLogFormatter(TestCaseForLogFormatter):
263
492
def test_verbose_log(self):
264
493
"""Verbose log includes changed files
268
wt = self.make_branch_and_tree('.')
270
self.build_tree(['a'])
272
# XXX: why does a longer nick show up?
273
b.nick = 'test_verbose_log'
274
wt.commit(message='add a',
275
timestamp=1132711707,
277
committer='Lorem Ipsum <test@example.com>')
278
logfile = file('out.tmp', 'w+')
279
formatter = LongLogFormatter(to_file=logfile)
280
show_log(b, formatter, verbose=True)
283
log_contents = logfile.read()
284
self.assertEqualDiff(log_contents, '''\
497
wt = self.make_standard_commit('test_verbose_log', authors=[])
498
self.assertFormatterResult('''\
285
499
------------------------------------------------------------
287
501
committer: Lorem Ipsum <test@example.com>
434
645
timestamp: Mon 2005-11-21 09:24:15 -0600
436
647
simple log message
649
b, log.LongLogFormatter)
439
651
def test_author_in_log(self):
440
652
"""Log includes the author name if it's set in
441
653
the revision properties
655
wt = self.make_standard_commit('test_author_log',
656
authors=['John Doe <jdoe@example.com>',
657
'Jane Rey <jrey@example.com>'])
658
self.assertFormatterResult("""\
659
------------------------------------------------------------
661
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
662
committer: Lorem Ipsum <test@example.com>
663
branch nick: test_author_log
664
timestamp: Wed 2005-11-23 12:08:27 +1000
668
wt.branch, log.LongLogFormatter)
670
def test_properties_in_log(self):
671
"""Log includes the custom properties returned by the registered
674
wt = self.make_standard_commit('test_properties_in_log')
675
def trivial_custom_prop_handler(revision):
676
return {'test_prop':'test_value'}
678
# Cleaned up in setUp()
679
log.properties_handler_registry.register(
680
'trivial_custom_prop_handler',
681
trivial_custom_prop_handler)
682
self.assertFormatterResult("""\
683
------------------------------------------------------------
685
test_prop: test_value
686
author: John Doe <jdoe@example.com>
687
committer: Lorem Ipsum <test@example.com>
688
branch nick: test_properties_in_log
689
timestamp: Wed 2005-11-23 12:08:27 +1000
693
wt.branch, log.LongLogFormatter)
695
def test_properties_in_short_log(self):
696
"""Log includes the custom properties returned by the registered
699
wt = self.make_standard_commit('test_properties_in_short_log')
700
def trivial_custom_prop_handler(revision):
701
return {'test_prop':'test_value'}
703
log.properties_handler_registry.register(
704
'trivial_custom_prop_handler',
705
trivial_custom_prop_handler)
706
self.assertFormatterResult("""\
707
1 John Doe\t2005-11-23
708
test_prop: test_value
712
wt.branch, log.ShortLogFormatter)
714
def test_error_in_properties_handler(self):
715
"""Log includes the custom properties returned by the registered
718
wt = self.make_standard_commit('error_in_properties_handler',
719
revprops={'first_prop':'first_value'})
720
sio = self.make_utf8_encoded_stringio()
721
formatter = log.LongLogFormatter(to_file=sio)
722
def trivial_custom_prop_handler(revision):
723
raise StandardError("a test error")
725
log.properties_handler_registry.register(
726
'trivial_custom_prop_handler',
727
trivial_custom_prop_handler)
728
self.assertRaises(StandardError, log.show_log, wt.branch, formatter,)
730
def test_properties_handler_bad_argument(self):
731
wt = self.make_standard_commit('bad_argument',
732
revprops={'a_prop':'test_value'})
733
sio = self.make_utf8_encoded_stringio()
734
formatter = log.LongLogFormatter(to_file=sio)
735
def bad_argument_prop_handler(revision):
736
return {'custom_prop_name':revision.properties['a_prop']}
738
log.properties_handler_registry.register(
739
'bad_argument_prop_handler',
740
bad_argument_prop_handler)
742
self.assertRaises(AttributeError, formatter.show_properties,
745
revision = wt.branch.repository.get_revision(wt.branch.last_revision())
746
formatter.show_properties(revision, '')
747
self.assertEqualDiff('''custom_prop_name: test_value\n''',
751
class TestLongLogFormatterWithoutMergeRevisions(TestCaseForLogFormatter):
753
def test_long_verbose_log(self):
754
"""Verbose log includes changed files
758
wt = self.make_standard_commit('test_long_verbose_log', authors=[])
759
self.assertFormatterResult("""\
760
------------------------------------------------------------
762
committer: Lorem Ipsum <test@example.com>
763
branch nick: test_long_verbose_log
764
timestamp: Wed 2005-11-23 12:08:27 +1000
770
wt.branch, log.LongLogFormatter,
771
formatter_kwargs=dict(levels=1),
772
show_log_kwargs=dict(verbose=True))
774
def test_long_verbose_contain_deltas(self):
775
wt = self.make_branch_and_tree('parent')
776
self.build_tree(['parent/f1', 'parent/f2'])
778
wt.commit('first post')
779
child_wt = wt.bzrdir.sprout('child').open_workingtree()
780
os.unlink('child/f1')
781
self.build_tree_contents([('child/f2', 'hello\n')])
782
child_wt.commit('removed f1 and modified f2')
783
wt.merge_from_branch(child_wt.branch)
784
wt.commit('merge branch 1')
785
self.assertFormatterResult("""\
786
------------------------------------------------------------
788
committer: Lorem Ipsum <test@example.com>
797
------------------------------------------------------------
799
committer: Lorem Ipsum <test@example.com>
808
wt.branch, log.LongLogFormatter,
809
formatter_kwargs=dict(levels=1),
810
show_log_kwargs=dict(verbose=True),
813
def test_long_trailing_newlines(self):
443
814
wt = self.make_branch_and_tree('.')
445
self.build_tree(['a'])
447
b.nick = 'test_author_log'
448
wt.commit(message='add a',
449
timestamp=1132711707,
451
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(), '''\
815
b = make_commits_with_trailing_newlines(wt)
816
self.assertFormatterResult("""\
817
------------------------------------------------------------
819
committer: Joe Foo <joe@foo.com>
821
timestamp: Mon 2005-11-21 09:32:56 -0600
823
single line with trailing newline
824
------------------------------------------------------------
826
author: Joe Bar <joe@bar.com>
827
committer: Joe Foo <joe@foo.com>
829
timestamp: Mon 2005-11-21 09:27:22 -0600
834
------------------------------------------------------------
836
committer: Joe Foo <joe@foo.com>
838
timestamp: Mon 2005-11-21 09:24:15 -0600
842
b, log.LongLogFormatter,
843
formatter_kwargs=dict(levels=1))
845
def test_long_author_in_log(self):
846
"""Log includes the author name if it's set in
847
the revision properties
849
wt = self.make_standard_commit('test_author_log')
850
self.assertFormatterResult("""\
457
851
------------------------------------------------------------
459
853
author: John Doe <jdoe@example.com>
462
856
timestamp: Wed 2005-11-23 12:08:27 +1000
469
class TestLineLogFormatter(TestCaseWithTransport):
860
wt.branch, log.LongLogFormatter,
861
formatter_kwargs=dict(levels=1))
863
def test_long_properties_in_log(self):
864
"""Log includes the custom properties returned by the registered
867
wt = self.make_standard_commit('test_properties_in_log')
868
def trivial_custom_prop_handler(revision):
869
return {'test_prop':'test_value'}
871
log.properties_handler_registry.register(
872
'trivial_custom_prop_handler',
873
trivial_custom_prop_handler)
874
self.assertFormatterResult("""\
875
------------------------------------------------------------
877
test_prop: test_value
878
author: John Doe <jdoe@example.com>
879
committer: Lorem Ipsum <test@example.com>
880
branch nick: test_properties_in_log
881
timestamp: Wed 2005-11-23 12:08:27 +1000
885
wt.branch, log.LongLogFormatter,
886
formatter_kwargs=dict(levels=1))
889
class TestLineLogFormatter(TestCaseForLogFormatter):
471
891
def test_line_log(self):
472
892
"""Line log should show revno
476
wt = self.make_branch_and_tree('.')
478
self.build_tree(['a'])
480
b.nick = 'test-line-log'
481
wt.commit(message='add a',
482
timestamp=1132711707,
484
committer='Line-Log-Formatter Tester <test@line.log>')
485
logfile = file('out.tmp', 'w+')
486
formatter = LineLogFormatter(to_file=logfile)
487
show_log(b, formatter)
490
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())
896
wt = self.make_standard_commit('test-line-log',
897
committer='Line-Log-Formatter Tester <test@line.log>',
899
self.assertFormatterResult("""\
900
1: Line-Log-Formatte... 2005-11-23 add a
902
wt.branch, log.LineLogFormatter)
524
904
def test_trailing_newlines(self):
525
905
wt = self.make_branch_and_tree('.')
526
906
b = make_commits_with_trailing_newlines(wt)
527
sio = self.make_utf8_encoded_stringio()
528
lf = LineLogFormatter(to_file=sio)
530
self.assertEqualDiff(sio.getvalue(), """\
907
self.assertFormatterResult("""\
531
908
3: Joe Foo 2005-11-21 single line with trailing newline
532
909
2: Joe Bar 2005-11-21 multiline
533
910
1: Joe Foo 2005-11-21 simple log message
537
class TestGetViewRevisions(TestCaseWithTransport):
912
b, log.LineLogFormatter)
914
def test_line_log_single_merge_revision(self):
915
wt = self._prepare_tree_with_merges()
916
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
917
rev = revspec.in_history(wt.branch)
918
self.assertFormatterResult("""\
919
1.1.1: Joe Foo 2005-11-22 rev-merged
921
wt.branch, log.LineLogFormatter,
922
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
924
def test_line_log_with_tags(self):
925
wt = self._prepare_tree_with_merges(with_tags=True)
926
self.assertFormatterResult("""\
927
3: Jane Foo 2005-11-22 {v1.0, v1.0rc1} rev-3
928
2: Joe Foo 2005-11-22 [merge] {v0.2} rev-2
929
1: Joe Foo 2005-11-22 rev-1
931
wt.branch, log.LineLogFormatter)
934
class TestLineLogFormatterWithMergeRevisions(TestCaseForLogFormatter):
936
def test_line_merge_revs_log(self):
937
"""Line log should show revno
941
wt = self.make_standard_commit('test-line-log',
942
committer='Line-Log-Formatter Tester <test@line.log>',
944
self.assertFormatterResult("""\
945
1: Line-Log-Formatte... 2005-11-23 add a
947
wt.branch, log.LineLogFormatter)
949
def test_line_merge_revs_log_single_merge_revision(self):
950
wt = self.make_branch_and_memory_tree('.')
952
self.addCleanup(wt.unlock)
954
wt.commit('rev-1', rev_id='rev-1',
955
timestamp=1132586655, timezone=36000,
956
committer='Joe Foo <joe@foo.com>')
957
wt.commit('rev-merged', rev_id='rev-2a',
958
timestamp=1132586700, timezone=36000,
959
committer='Joe Foo <joe@foo.com>')
960
wt.set_parent_ids(['rev-1', 'rev-2a'])
961
wt.branch.set_last_revision_info(1, 'rev-1')
962
wt.commit('rev-2', rev_id='rev-2b',
963
timestamp=1132586800, timezone=36000,
964
committer='Joe Foo <joe@foo.com>')
965
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
966
rev = revspec.in_history(wt.branch)
967
self.assertFormatterResult("""\
968
1.1.1: Joe Foo 2005-11-22 rev-merged
970
wt.branch, log.LineLogFormatter,
971
formatter_kwargs=dict(levels=0),
972
show_log_kwargs=dict(start_revision=rev, end_revision=rev))
974
def test_line_merge_revs_log_with_merges(self):
975
wt = self.make_branch_and_memory_tree('.')
977
self.addCleanup(wt.unlock)
979
wt.commit('rev-1', rev_id='rev-1',
980
timestamp=1132586655, timezone=36000,
981
committer='Joe Foo <joe@foo.com>')
982
wt.commit('rev-merged', rev_id='rev-2a',
983
timestamp=1132586700, timezone=36000,
984
committer='Joe Foo <joe@foo.com>')
985
wt.set_parent_ids(['rev-1', 'rev-2a'])
986
wt.branch.set_last_revision_info(1, 'rev-1')
987
wt.commit('rev-2', rev_id='rev-2b',
988
timestamp=1132586800, timezone=36000,
989
committer='Joe Foo <joe@foo.com>')
990
self.assertFormatterResult("""\
991
2: Joe Foo 2005-11-22 [merge] rev-2
992
1.1.1: Joe Foo 2005-11-22 rev-merged
993
1: Joe Foo 2005-11-22 rev-1
995
wt.branch, log.LineLogFormatter,
996
formatter_kwargs=dict(levels=0))
999
class TestGetViewRevisions(tests.TestCaseWithTransport):
539
1001
def make_tree_with_commits(self):
540
1002
"""Create a tree with well-known revision ids"""
559
1021
return mainline_revs, rev_nos, wt
561
def make_tree_with_many_merges(self):
1023
def make_branch_with_many_merges(self):
562
1024
"""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')
1025
builder = self.make_branch_builder('tree1')
1026
builder.start_series()
1027
builder.build_snapshot('1', None, [
1028
('add', ('', 'TREE_ROOT', 'directory', '')),
1029
('add', ('f', 'f-id', 'file', '1\n'))])
1030
builder.build_snapshot('2', ['1'], [])
1031
builder.build_snapshot('3a', ['2'], [
1032
('modify', ('f-id', '1\n2\n3a\n'))])
1033
builder.build_snapshot('3b', ['2', '3a'], [
1034
('modify', ('f-id', '1\n2\n3a\n'))])
1035
builder.build_snapshot('3c', ['2', '3b'], [
1036
('modify', ('f-id', '1\n2\n3a\n'))])
1037
builder.build_snapshot('4a', ['3b'], [])
1038
builder.build_snapshot('4b', ['3c', '4a'], [])
1039
builder.finish_series()
576
1053
mainline_revs = [None, '1', '2', '3c', '4b']
577
1054
rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
578
1055
full_rev_nos_for_reference = {
581
'3a': '2.2.1', #first commit tree 3
582
'3b': '2.1.1', # first commit tree 2
1058
'3a': '2.1.1', #first commit tree 3
1059
'3b': '2.2.1', # first commit tree 2
583
1060
'3c': '3', #merges 3b to main
584
'4a': '2.1.2', # second commit tree 2
1061
'4a': '2.2.2', # second commit tree 2
585
1062
'4b': '4', # merges 4a to main
587
return mainline_revs, rev_nos, wt
1064
return mainline_revs, rev_nos, builder.get_branch()
589
1066
def test_get_view_revisions_forward(self):
590
1067
"""Test the get_view_revisions method"""
591
1068
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
592
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
1070
self.addCleanup(wt.unlock)
1071
revisions = list(log.get_view_revisions(
1072
mainline_revs, rev_nos, wt.branch, 'forward'))
594
1073
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))
1075
revisions2 = list(log.get_view_revisions(
1076
mainline_revs, rev_nos, wt.branch, 'forward',
1077
include_merges=False))
598
1078
self.assertEqual(revisions, revisions2)
600
1080
def test_get_view_revisions_reverse(self):
601
1081
"""Test the get_view_revisions with reverse"""
602
1082
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
603
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
1084
self.addCleanup(wt.unlock)
1085
revisions = list(log.get_view_revisions(
1086
mainline_revs, rev_nos, wt.branch, 'reverse'))
605
1087
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))
1089
revisions2 = list(log.get_view_revisions(
1090
mainline_revs, rev_nos, wt.branch, 'reverse',
1091
include_merges=False))
609
1092
self.assertEqual(revisions, revisions2)
611
1094
def test_get_view_revisions_merge(self):
612
1095
"""Test get_view_revisions when there are merges"""
613
1096
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),
1098
self.addCleanup(wt.unlock)
1099
revisions = list(log.get_view_revisions(
1100
mainline_revs, rev_nos, wt.branch, 'forward'))
1101
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
1102
('4b', '4', 0), ('4a', '3.1.1', 1)],
1104
revisions = list(log.get_view_revisions(
1105
mainline_revs, rev_nos, wt.branch, 'forward',
1106
include_merges=False))
1107
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
625
1111
def test_get_view_revisions_merge_reverse(self):
626
1112
"""Test get_view_revisions in reverse when there are merges"""
627
1113
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
628
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
1115
self.addCleanup(wt.unlock)
1116
revisions = list(log.get_view_revisions(
1117
mainline_revs, rev_nos, wt.branch, 'reverse'))
630
1118
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))
1119
('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
1121
revisions = list(log.get_view_revisions(
1122
mainline_revs, rev_nos, wt.branch, 'reverse',
1123
include_merges=False))
635
1124
self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
639
1128
def test_get_view_revisions_merge2(self):
640
1129
"""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,
1130
mainline_revs, rev_nos, b = self.make_branch_with_many_merges()
1132
self.addCleanup(b.unlock)
1133
revisions = list(log.get_view_revisions(
1134
mainline_revs, rev_nos, b, 'forward'))
644
1135
expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
645
('3a', '2.2.1', 1), ('3b', '2.1.1', 1), ('4b', '4', 0),
1136
('3b', '2.2.1', 1), ('3a', '2.1.1', 2), ('4b', '4', 0),
647
1138
self.assertEqual(expected, revisions)
648
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
649
'forward', include_merges=False))
1139
revisions = list(log.get_view_revisions(
1140
mainline_revs, rev_nos, b, 'forward',
1141
include_merges=False))
650
1142
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
655
class TestGetRevisionsTouchingFileID(TestCaseWithTransport):
1147
def test_file_id_for_range(self):
1148
mainline_revs, rev_nos, b = self.make_branch_with_many_merges()
1150
self.addCleanup(b.unlock)
1152
def rev_from_rev_id(revid, branch):
1153
revspec = revisionspec.RevisionSpec.from_string('revid:%s' % revid)
1154
return revspec.in_history(branch)
1156
def view_revs(start_rev, end_rev, file_id, direction):
1157
revs = log.calculate_view_revisions(
1159
start_rev, # start_revision
1160
end_rev, # end_revision
1161
direction, # direction
1162
file_id, # specific_fileid
1163
True, # generate_merge_revisions
1167
rev_3a = rev_from_rev_id('3a', b)
1168
rev_4b = rev_from_rev_id('4b', b)
1169
self.assertEqual([('3c', '3', 0), ('3b', '2.2.1', 1), ('3a', '2.1.1', 2)],
1170
view_revs(rev_3a, rev_4b, 'f-id', 'reverse'))
1171
# Note: 3c still appears before 3a here because of depth-based sorting
1172
self.assertEqual([('3c', '3', 0), ('3b', '2.2.1', 1), ('3a', '2.1.1', 2)],
1173
view_revs(rev_3a, rev_4b, 'f-id', 'forward'))
1176
class TestGetRevisionsTouchingFileID(tests.TestCaseWithTransport):
657
1178
def create_tree_with_single_merge(self):
658
1179
"""Create a branch with a moderate layout.
708
1229
tree.commit('D', rev_id='D')
710
1231
# Switch to a read lock for this tree.
711
# We still have addCleanup(unlock)
1232
# We still have an addCleanup(tree.unlock) pending
713
1234
tree.lock_read()
1237
def check_delta(self, delta, **kw):
1238
"""Check the filenames touched by a delta are as expected.
1240
Caller only have to pass in the list of files for each part, all
1241
unspecified parts are considered empty (and checked as such).
1243
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
1244
# By default we expect an empty list
1245
expected = kw.get(n, [])
1246
# strip out only the path components
1247
got = [x[0] for x in getattr(delta, n)]
1248
self.assertEqual(expected, got)
716
1250
def test_tree_with_single_merge(self):
717
1251
"""Make sure the tree layout is correct."""
718
1252
tree = self.create_tree_with_single_merge()
719
1253
rev_A_tree = tree.branch.repository.revision_tree('A')
720
1254
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
1255
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
1256
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)
1258
self.check_delta(rev_B_tree.changes_from(rev_A_tree),
1259
modified=['f1', 'f3'])
1261
self.check_delta(rev_C_tree.changes_from(rev_A_tree),
1262
modified=['f2', 'f3'])
1264
self.check_delta(rev_D_tree.changes_from(rev_B_tree),
1265
modified=['f2', 'f3'])
1267
self.check_delta(rev_D_tree.changes_from(rev_C_tree),
1268
modified=['f1', 'f3'])
752
1270
def assertAllRevisionsForFileID(self, tree, file_id, revisions):
753
"""Make sure _filter_revisions_touching_file_id returns the right values.
1271
"""Ensure _filter_revisions_touching_file_id returns the right values.
755
1273
Get the return value from _filter_revisions_touching_file_id and make
756
1274
sure they are correct.
758
# The api for _get_revisions_touching_file_id is a little crazy,
1276
# The api for _filter_revisions_touching_file_id is a little crazy.
759
1277
# So we do the setup here.
760
1278
mainline = tree.branch.revision_history()
761
1279
mainline.insert(0, None)
799
1339
self.assertNotContainsRe(s.getvalue(), 'foo')
802
class TestLogFormatter(TestCase):
1342
class TestLogFormatter(tests.TestCase):
804
1344
def test_short_committer(self):
805
rev = Revision('a-id')
1345
rev = revision.Revision('a-id')
806
1346
rev.committer = 'John Doe <jdoe@example.com>'
807
lf = LogFormatter(None)
1347
lf = log.LogFormatter(None)
808
1348
self.assertEqual('John Doe', lf.short_committer(rev))
1349
rev.committer = 'John Smith <jsmith@example.com>'
1350
self.assertEqual('John Smith', lf.short_committer(rev))
1351
rev.committer = 'John Smith'
1352
self.assertEqual('John Smith', lf.short_committer(rev))
1353
rev.committer = 'jsmith@example.com'
1354
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1355
rev.committer = '<jsmith@example.com>'
1356
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1357
rev.committer = 'John Smith jsmith@example.com'
1358
self.assertEqual('John Smith', lf.short_committer(rev))
810
1360
def test_short_author(self):
811
rev = Revision('a-id')
1361
rev = revision.Revision('a-id')
812
1362
rev.committer = 'John Doe <jdoe@example.com>'
813
lf = LogFormatter(None)
1363
lf = log.LogFormatter(None)
814
1364
self.assertEqual('John Doe', lf.short_author(rev))
815
1365
rev.properties['author'] = 'John Smith <jsmith@example.com>'
816
1366
self.assertEqual('John Smith', lf.short_author(rev))
1367
rev.properties['author'] = 'John Smith'
1368
self.assertEqual('John Smith', lf.short_author(rev))
1369
rev.properties['author'] = 'jsmith@example.com'
1370
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1371
rev.properties['author'] = '<jsmith@example.com>'
1372
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1373
rev.properties['author'] = 'John Smith jsmith@example.com'
1374
self.assertEqual('John Smith', lf.short_author(rev))
1375
del rev.properties['author']
1376
rev.properties['authors'] = ('John Smith <jsmith@example.com>\n'
1377
'Jane Rey <jrey@example.com>')
1378
self.assertEqual('John Smith', lf.short_author(rev))
1381
class TestReverseByDepth(tests.TestCase):
1382
"""Test reverse_by_depth behavior.
1384
This is used to present revisions in forward (oldest first) order in a nice
1387
The tests use lighter revision description to ease reading.
1390
def assertReversed(self, forward, backward):
1391
# Transform the descriptions to suit the API: tests use (revno, depth),
1392
# while the API expects (revid, revno, depth)
1393
def complete_revisions(l):
1394
"""Transform the description to suit the API.
1396
Tests use (revno, depth) whil the API expects (revid, revno, depth).
1397
Since the revid is arbitrary, we just duplicate revno
1399
return [ (r, r, d) for r, d in l]
1400
forward = complete_revisions(forward)
1401
backward= complete_revisions(backward)
1402
self.assertEqual(forward, log.reverse_by_depth(backward))
1405
def test_mainline_revisions(self):
1406
self.assertReversed([( '1', 0), ('2', 0)],
1407
[('2', 0), ('1', 0)])
1409
def test_merged_revisions(self):
1410
self.assertReversed([('1', 0), ('2', 0), ('2.2', 1), ('2.1', 1),],
1411
[('2', 0), ('2.1', 1), ('2.2', 1), ('1', 0),])
1412
def test_shifted_merged_revisions(self):
1413
"""Test irregular layout.
1415
Requesting revisions touching a file can produce "holes" in the depths.
1417
self.assertReversed([('1', 0), ('2', 0), ('1.1', 2), ('1.2', 2),],
1418
[('2', 0), ('1.2', 2), ('1.1', 2), ('1', 0),])
1420
def test_merged_without_child_revisions(self):
1421
"""Test irregular layout.
1423
Revision ranges can produce "holes" in the depths.
1425
# When a revision of higher depth doesn't follow one of lower depth, we
1426
# assume a lower depth one is virtually there
1427
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1428
[('4', 4), ('3', 3), ('2', 2), ('1', 2),])
1429
# So we get the same order after reversing below even if the original
1430
# revisions are not in the same order.
1431
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1432
[('3', 3), ('4', 4), ('2', 2), ('1', 2),])
1435
class TestHistoryChange(tests.TestCaseWithTransport):
1437
def setup_a_tree(self):
1438
tree = self.make_branch_and_tree('tree')
1440
self.addCleanup(tree.unlock)
1441
tree.commit('1a', rev_id='1a')
1442
tree.commit('2a', rev_id='2a')
1443
tree.commit('3a', rev_id='3a')
1446
def setup_ab_tree(self):
1447
tree = self.setup_a_tree()
1448
tree.set_last_revision('1a')
1449
tree.branch.set_last_revision_info(1, '1a')
1450
tree.commit('2b', rev_id='2b')
1451
tree.commit('3b', rev_id='3b')
1454
def setup_ac_tree(self):
1455
tree = self.setup_a_tree()
1456
tree.set_last_revision(revision.NULL_REVISION)
1457
tree.branch.set_last_revision_info(0, revision.NULL_REVISION)
1458
tree.commit('1c', rev_id='1c')
1459
tree.commit('2c', rev_id='2c')
1460
tree.commit('3c', rev_id='3c')
1463
def test_all_new(self):
1464
tree = self.setup_ab_tree()
1465
old, new = log.get_history_change('1a', '3a', tree.branch.repository)
1466
self.assertEqual([], old)
1467
self.assertEqual(['2a', '3a'], new)
1469
def test_all_old(self):
1470
tree = self.setup_ab_tree()
1471
old, new = log.get_history_change('3a', '1a', tree.branch.repository)
1472
self.assertEqual([], new)
1473
self.assertEqual(['2a', '3a'], old)
1475
def test_null_old(self):
1476
tree = self.setup_ab_tree()
1477
old, new = log.get_history_change(revision.NULL_REVISION,
1478
'3a', tree.branch.repository)
1479
self.assertEqual([], old)
1480
self.assertEqual(['1a', '2a', '3a'], new)
1482
def test_null_new(self):
1483
tree = self.setup_ab_tree()
1484
old, new = log.get_history_change('3a', revision.NULL_REVISION,
1485
tree.branch.repository)
1486
self.assertEqual([], new)
1487
self.assertEqual(['1a', '2a', '3a'], old)
1489
def test_diverged(self):
1490
tree = self.setup_ab_tree()
1491
old, new = log.get_history_change('3a', '3b', tree.branch.repository)
1492
self.assertEqual(old, ['2a', '3a'])
1493
self.assertEqual(new, ['2b', '3b'])
1495
def test_unrelated(self):
1496
tree = self.setup_ac_tree()
1497
old, new = log.get_history_change('3a', '3c', tree.branch.repository)
1498
self.assertEqual(old, ['1a', '2a', '3a'])
1499
self.assertEqual(new, ['1c', '2c', '3c'])
1501
def test_show_branch_change(self):
1502
tree = self.setup_ab_tree()
1504
log.show_branch_change(tree.branch, s, 3, '3a')
1505
self.assertContainsRe(s.getvalue(),
1506
'[*]{60}\nRemoved Revisions:\n(.|\n)*2a(.|\n)*3a(.|\n)*'
1507
'[*]{60}\n\nAdded Revisions:\n(.|\n)*2b(.|\n)*3b')
1509
def test_show_branch_change_no_change(self):
1510
tree = self.setup_ab_tree()
1512
log.show_branch_change(tree.branch, s, 3, '3b')
1513
self.assertEqual(s.getvalue(),
1514
'Nothing seems to have changed\n')
1516
def test_show_branch_change_no_old(self):
1517
tree = self.setup_ab_tree()
1519
log.show_branch_change(tree.branch, s, 2, '2b')
1520
self.assertContainsRe(s.getvalue(), 'Added Revisions:')
1521
self.assertNotContainsRe(s.getvalue(), 'Removed Revisions:')
1523
def test_show_branch_change_no_new(self):
1524
tree = self.setup_ab_tree()
1525
tree.branch.set_last_revision_info(2, '2b')
1527
log.show_branch_change(tree.branch, s, 3, '3b')
1528
self.assertContainsRe(s.getvalue(), 'Removed Revisions:')
1529
self.assertNotContainsRe(s.getvalue(), 'Added Revisions:')
1533
class TestLogWithBugs(TestCaseForLogFormatter):
1536
TestCaseForLogFormatter.setUp(self)
1537
log.properties_handler_registry.register(
1538
'bugs_properties_handler',
1539
log._bugs_properties_handler)
1541
def make_commits_with_bugs(self):
1542
"""Helper method for LogFormatter tests"""
1543
tree = self.make_branch_and_tree(u'.')
1544
self.build_tree(['a', 'b'])
1546
tree.commit('simple log message', rev_id='a1',
1547
timestamp=1132586655.459960938, timezone=-6*3600,
1548
committer='Joe Foo <joe@foo.com>',
1549
revprops={'bugs': 'test://bug/id fixed'})
1551
tree.commit('multiline\nlog\nmessage\n', rev_id='a2',
1552
timestamp=1132586842.411175966, timezone=-6*3600,
1553
committer='Joe Foo <joe@foo.com>',
1554
authors=['Joe Bar <joe@bar.com>'],
1555
revprops={'bugs': 'test://bug/id fixed\n'
1556
'test://bug/2 fixed'})
1560
def test_long_bugs(self):
1561
tree = self.make_commits_with_bugs()
1562
self.assertFormatterResult("""\
1563
------------------------------------------------------------
1565
fixes bug(s): test://bug/id test://bug/2
1566
author: Joe Bar <joe@bar.com>
1567
committer: Joe Foo <joe@foo.com>
1569
timestamp: Mon 2005-11-21 09:27:22 -0600
1574
------------------------------------------------------------
1576
fixes bug(s): test://bug/id
1577
committer: Joe Foo <joe@foo.com>
1579
timestamp: Mon 2005-11-21 09:24:15 -0600
1583
tree.branch, log.LongLogFormatter)
1585
def test_short_bugs(self):
1586
tree = self.make_commits_with_bugs()
1587
self.assertFormatterResult("""\
1588
2 Joe Bar\t2005-11-21
1589
fixes bug(s): test://bug/id test://bug/2
1594
1 Joe Foo\t2005-11-21
1595
fixes bug(s): test://bug/id
1599
tree.branch, log.ShortLogFormatter)
1601
def test_wrong_bugs_property(self):
1602
tree = self.make_branch_and_tree(u'.')
1603
self.build_tree(['foo'])
1604
tree.commit('simple log message', rev_id='a1',
1605
timestamp=1132586655.459960938, timezone=-6*3600,
1606
committer='Joe Foo <joe@foo.com>',
1607
revprops={'bugs': 'test://bug/id invalid_value'})
1608
self.assertFormatterResult("""\
1609
1 Joe Foo\t2005-11-21
1613
tree.branch, log.ShortLogFormatter)
1615
def test_bugs_handler_present(self):
1616
self.properties_handler_registry.get('bugs_properties_handler')