145
102
eq(len(lf.logs), 2)
146
103
self.log('log entries:')
147
104
for logentry in lf.logs:
148
self.log('%4s %s' % (logentry.revno, logentry.rev.message))
105
self.log('%4d %s' % (logentry.revno, logentry.rev.message))
150
107
# first one is most recent
151
108
logentry = lf.logs[0]
152
eq(logentry.revno, '2')
109
eq(logentry.revno, 2)
153
110
eq(logentry.rev.message, 'add one file')
154
111
d = logentry.delta
155
112
self.log('log 2 delta: %r' % d)
156
self.checkDelta(d, added=['hello'])
158
# commit a log message with control characters
159
msg = "All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
160
self.log("original commit message: %r", msg)
163
show_log(b, lf, verbose=True)
164
committed_msg = lf.logs[0].rev.message
165
self.log("escaped commit message: %r", committed_msg)
166
self.assert_(msg != committed_msg)
167
self.assert_(len(committed_msg) > len(msg))
169
# Check that log message with only XML-valid characters isn't
170
# escaped. As ElementTree apparently does some kind of
171
# newline conversion, neither LF (\x0A) nor CR (\x0D) are
172
# included in the test commit message, even though they are
173
# valid XML 1.0 characters.
174
msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
175
self.log("original commit message: %r", msg)
178
show_log(b, lf, verbose=True)
179
committed_msg = lf.logs[0].rev.message
180
self.log("escaped commit message: %r", committed_msg)
181
self.assert_(msg == committed_msg)
183
def test_deltas_in_merge_revisions(self):
184
"""Check deltas created for both mainline and merge revisions"""
185
eq = self.assertEquals
186
wt = self.make_branch_and_tree('parent')
187
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
190
wt.commit(message='add file1 and file2')
191
self.run_bzr('branch parent child')
192
os.unlink('child/file1')
193
file('child/file2', 'wb').write('hello\n')
194
self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
197
self.run_bzr('merge ../child')
198
wt.commit('merge child branch')
202
lf.supports_merge_revisions = True
203
show_log(b, lf, verbose=True)
205
logentry = lf.logs[0]
206
eq(logentry.revno, '2')
207
eq(logentry.rev.message, 'merge child branch')
209
self.checkDelta(d, removed=['file1'], modified=['file2'])
210
logentry = lf.logs[1]
211
eq(logentry.revno, '1.1.1')
212
eq(logentry.rev.message, 'remove file1 and modify file2')
214
self.checkDelta(d, removed=['file1'], modified=['file2'])
215
logentry = lf.logs[2]
216
eq(logentry.revno, '1')
217
eq(logentry.rev.message, 'add file1 and file2')
219
self.checkDelta(d, added=['file1', 'file2'])
221
def test_merges_nonsupporting_formatter(self):
222
"""Tests that show_log will raise if the formatter doesn't
223
support merge revisions."""
224
wt = self.make_branch_and_memory_tree('.')
228
wt.commit('rev-1', rev_id='rev-1',
229
timestamp=1132586655, timezone=36000,
230
committer='Joe Foo <joe@foo.com>')
231
wt.commit('rev-merged', rev_id='rev-2a',
232
timestamp=1132586700, timezone=36000,
233
committer='Joe Foo <joe@foo.com>')
234
wt.set_parent_ids(['rev-1', 'rev-2a'])
235
wt.branch.set_last_revision_info(1, 'rev-1')
236
wt.commit('rev-2', rev_id='rev-2b',
237
timestamp=1132586800, timezone=36000,
238
committer='Joe Foo <joe@foo.com>')
239
logfile = self.make_utf8_encoded_stringio()
240
formatter = ShortLogFormatter(to_file=logfile)
243
revspec = RevisionSpec.from_string('1.1.1')
244
rev = revspec.in_history(wtb)
245
self.assertRaises(BzrCommandError, show_log, wtb, lf,
246
start_revision=rev, end_revision=rev)
251
def make_commits_with_trailing_newlines(wt):
252
"""Helper method for LogFormatter tests"""
255
open('a', 'wb').write('hello moto\n')
257
wt.commit('simple log message', rev_id='a1',
258
timestamp=1132586655.459960938, timezone=-6*3600,
259
committer='Joe Foo <joe@foo.com>')
260
open('b', 'wb').write('goodbye\n')
262
wt.commit('multiline\nlog\nmessage\n', rev_id='a2',
263
timestamp=1132586842.411175966, timezone=-6*3600,
264
committer='Joe Foo <joe@foo.com>',
265
author='Joe Bar <joe@bar.com>')
267
open('c', 'wb').write('just another manic monday\n')
269
wt.commit('single line with trailing newline\n', rev_id='a3',
270
timestamp=1132587176.835228920, timezone=-6*3600,
271
committer = 'Joe Foo <joe@foo.com>')
275
def normalize_log(log):
276
"""Replaces the variable lines of logs with fixed lines"""
277
author = 'author: Dolor Sit <test@example.com>'
278
committer = 'committer: Lorem Ipsum <test@example.com>'
279
lines = log.splitlines(True)
280
for idx,line in enumerate(lines):
281
stripped_line = line.lstrip()
282
indent = ' ' * (len(line) - len(stripped_line))
283
if stripped_line.startswith('author:'):
284
lines[idx] = indent + author + '\n'
285
elif stripped_line.startswith('committer:'):
286
lines[idx] = indent + committer + '\n'
287
elif stripped_line.startswith('timestamp:'):
288
lines[idx] = indent + 'timestamp: Just now\n'
289
return ''.join(lines)
292
class TestShortLogFormatter(TestCaseWithTransport):
294
def test_trailing_newlines(self):
295
wt = self.make_branch_and_tree('.')
296
b = make_commits_with_trailing_newlines(wt)
297
sio = self.make_utf8_encoded_stringio()
298
lf = ShortLogFormatter(to_file=sio)
300
self.assertEqualDiff(sio.getvalue(), """\
301
3 Joe Foo\t2005-11-21
302
single line with trailing newline
304
2 Joe Bar\t2005-11-21
309
1 Joe Foo\t2005-11-21
314
def test_short_log_with_merges(self):
315
wt = self.make_branch_and_memory_tree('.')
319
wt.commit('rev-1', rev_id='rev-1',
320
timestamp=1132586655, timezone=36000,
321
committer='Joe Foo <joe@foo.com>')
322
wt.commit('rev-merged', rev_id='rev-2a',
323
timestamp=1132586700, timezone=36000,
324
committer='Joe Foo <joe@foo.com>')
325
wt.set_parent_ids(['rev-1', 'rev-2a'])
326
wt.branch.set_last_revision_info(1, 'rev-1')
327
wt.commit('rev-2', rev_id='rev-2b',
328
timestamp=1132586800, timezone=36000,
329
committer='Joe Foo <joe@foo.com>')
330
logfile = self.make_utf8_encoded_stringio()
331
formatter = ShortLogFormatter(to_file=logfile)
332
show_log(wt.branch, formatter)
333
self.assertEqualDiff(logfile.getvalue(), """\
334
2 Joe Foo\t2005-11-22 [merge]
337
1 Joe Foo\t2005-11-22
344
def test_short_log_single_merge_revision(self):
345
wt = self.make_branch_and_memory_tree('.')
349
wt.commit('rev-1', rev_id='rev-1',
350
timestamp=1132586655, timezone=36000,
351
committer='Joe Foo <joe@foo.com>')
352
wt.commit('rev-merged', rev_id='rev-2a',
353
timestamp=1132586700, timezone=36000,
354
committer='Joe Foo <joe@foo.com>')
355
wt.set_parent_ids(['rev-1', 'rev-2a'])
356
wt.branch.set_last_revision_info(1, 'rev-1')
357
wt.commit('rev-2', rev_id='rev-2b',
358
timestamp=1132586800, timezone=36000,
359
committer='Joe Foo <joe@foo.com>')
360
logfile = self.make_utf8_encoded_stringio()
361
formatter = ShortLogFormatter(to_file=logfile)
362
revspec = RevisionSpec.from_string('1.1.1')
364
rev = revspec.in_history(wtb)
365
show_log(wtb, formatter, start_revision=rev, end_revision=rev)
366
self.assertEqualDiff(logfile.getvalue(), """\
367
1.1.1 Joe Foo\t2005-11-22
375
class TestLongLogFormatter(TestCaseWithoutPropsHandler):
377
def test_verbose_log(self):
378
"""Verbose log includes changed files
382
wt = self.make_branch_and_tree('.')
384
self.build_tree(['a'])
386
# XXX: why does a longer nick show up?
387
b.nick = 'test_verbose_log'
388
wt.commit(message='add a',
389
timestamp=1132711707,
391
committer='Lorem Ipsum <test@example.com>')
392
logfile = file('out.tmp', 'w+')
393
formatter = LongLogFormatter(to_file=logfile)
394
show_log(b, formatter, verbose=True)
397
log_contents = logfile.read()
398
self.assertEqualDiff(log_contents, '''\
399
------------------------------------------------------------
401
committer: Lorem Ipsum <test@example.com>
402
branch nick: test_verbose_log
403
timestamp: Wed 2005-11-23 12:08:27 +1000
410
def test_merges_are_indented_by_level(self):
411
wt = self.make_branch_and_tree('parent')
412
wt.commit('first post')
413
self.run_bzr('branch parent child')
414
self.run_bzr(['commit', '-m', 'branch 1', '--unchanged', 'child'])
415
self.run_bzr('branch child smallerchild')
416
self.run_bzr(['commit', '-m', 'branch 2', '--unchanged',
419
self.run_bzr('merge ../smallerchild')
420
self.run_bzr(['commit', '-m', 'merge branch 2'])
421
os.chdir('../parent')
422
self.run_bzr('merge ../child')
423
wt.commit('merge branch 1')
425
sio = self.make_utf8_encoded_stringio()
426
lf = LongLogFormatter(to_file=sio)
427
show_log(b, lf, verbose=True)
428
log = normalize_log(sio.getvalue())
429
self.assertEqualDiff(log, """\
430
------------------------------------------------------------
432
committer: Lorem Ipsum <test@example.com>
437
------------------------------------------------------------
439
committer: Lorem Ipsum <test@example.com>
444
------------------------------------------------------------
446
committer: Lorem Ipsum <test@example.com>
447
branch nick: smallerchild
451
------------------------------------------------------------
453
committer: Lorem Ipsum <test@example.com>
458
------------------------------------------------------------
460
committer: Lorem Ipsum <test@example.com>
467
def test_verbose_merge_revisions_contain_deltas(self):
468
wt = self.make_branch_and_tree('parent')
469
self.build_tree(['parent/f1', 'parent/f2'])
471
wt.commit('first post')
472
self.run_bzr('branch parent child')
473
os.unlink('child/f1')
474
file('child/f2', 'wb').write('hello\n')
475
self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
478
self.run_bzr('merge ../child')
479
wt.commit('merge branch 1')
481
sio = self.make_utf8_encoded_stringio()
482
lf = LongLogFormatter(to_file=sio)
483
show_log(b, lf, verbose=True)
484
log = normalize_log(sio.getvalue())
485
self.assertEqualDiff(log, """\
486
------------------------------------------------------------
488
committer: Lorem Ipsum <test@example.com>
497
------------------------------------------------------------
499
committer: Lorem Ipsum <test@example.com>
503
removed f1 and modified f2
508
------------------------------------------------------------
510
committer: Lorem Ipsum <test@example.com>
520
def test_trailing_newlines(self):
521
wt = self.make_branch_and_tree('.')
522
b = make_commits_with_trailing_newlines(wt)
523
sio = self.make_utf8_encoded_stringio()
524
lf = LongLogFormatter(to_file=sio)
526
self.assertEqualDiff(sio.getvalue(), """\
527
------------------------------------------------------------
529
committer: Joe Foo <joe@foo.com>
531
timestamp: Mon 2005-11-21 09:32:56 -0600
533
single line with trailing newline
534
------------------------------------------------------------
536
author: Joe Bar <joe@bar.com>
537
committer: Joe Foo <joe@foo.com>
539
timestamp: Mon 2005-11-21 09:27:22 -0600
544
------------------------------------------------------------
546
committer: Joe Foo <joe@foo.com>
548
timestamp: Mon 2005-11-21 09:24:15 -0600
553
def test_author_in_log(self):
554
"""Log includes the author name if it's set in
555
the revision properties
557
wt = self.make_branch_and_tree('.')
559
self.build_tree(['a'])
561
b.nick = 'test_author_log'
562
wt.commit(message='add a',
563
timestamp=1132711707,
565
committer='Lorem Ipsum <test@example.com>',
566
author='John Doe <jdoe@example.com>')
568
formatter = LongLogFormatter(to_file=sio)
569
show_log(b, formatter)
570
self.assertEqualDiff(sio.getvalue(), '''\
571
------------------------------------------------------------
573
author: John Doe <jdoe@example.com>
574
committer: Lorem Ipsum <test@example.com>
575
branch nick: test_author_log
576
timestamp: Wed 2005-11-23 12:08:27 +1000
581
def test_properties_in_log(self):
582
"""Log includes the custom properties returned by the registered
585
wt = self.make_branch_and_tree('.')
587
self.build_tree(['a'])
589
b.nick = 'test_properties_in_log'
590
wt.commit(message='add a',
591
timestamp=1132711707,
593
committer='Lorem Ipsum <test@example.com>',
594
author='John Doe <jdoe@example.com>')
596
formatter = LongLogFormatter(to_file=sio)
598
def trivial_custom_prop_handler(revision):
599
return {'test_prop':'test_value'}
601
log.properties_handler_registry.register(
602
'trivial_custom_prop_handler',
603
trivial_custom_prop_handler)
604
show_log(b, formatter)
606
log.properties_handler_registry.remove(
607
'trivial_custom_prop_handler')
608
self.assertEqualDiff(sio.getvalue(), '''\
609
------------------------------------------------------------
611
test_prop: test_value
612
author: John Doe <jdoe@example.com>
613
committer: Lorem Ipsum <test@example.com>
614
branch nick: test_properties_in_log
615
timestamp: Wed 2005-11-23 12:08:27 +1000
620
def test_error_in_properties_handler(self):
621
"""Log includes the custom properties returned by the registered
624
wt = self.make_branch_and_tree('.')
626
self.build_tree(['a'])
628
b.nick = 'test_author_log'
629
wt.commit(message='add a',
630
timestamp=1132711707,
632
committer='Lorem Ipsum <test@example.com>',
633
author='John Doe <jdoe@example.com>',
634
revprops={'first_prop':'first_value'})
636
formatter = LongLogFormatter(to_file=sio)
638
def trivial_custom_prop_handler(revision):
639
raise StandardError("a test error")
641
log.properties_handler_registry.register(
642
'trivial_custom_prop_handler',
643
trivial_custom_prop_handler)
644
self.assertRaises(StandardError, show_log, b, formatter,)
646
log.properties_handler_registry.remove(
647
'trivial_custom_prop_handler')
649
def test_properties_handler_bad_argument(self):
650
wt = self.make_branch_and_tree('.')
652
self.build_tree(['a'])
654
b.nick = 'test_author_log'
655
wt.commit(message='add a',
656
timestamp=1132711707,
658
committer='Lorem Ipsum <test@example.com>',
659
author='John Doe <jdoe@example.com>',
660
revprops={'a_prop':'test_value'})
662
formatter = LongLogFormatter(to_file=sio)
664
def bad_argument_prop_handler(revision):
665
return {'custom_prop_name':revision.properties['a_prop']}
667
log.properties_handler_registry.register(
668
'bad_argument_prop_handler',
669
bad_argument_prop_handler)
671
self.assertRaises(AttributeError, formatter.show_properties,
674
revision = b.repository.get_revision(b.last_revision())
675
formatter.show_properties(revision, '')
676
self.assertEqualDiff(sio.getvalue(),
677
'''custom_prop_name: test_value\n''')
679
log.properties_handler_registry.remove(
680
'bad_argument_prop_handler')
683
class TestLineLogFormatter(TestCaseWithTransport):
685
def test_line_log(self):
686
"""Line log should show revno
690
wt = self.make_branch_and_tree('.')
692
self.build_tree(['a'])
694
b.nick = 'test-line-log'
695
wt.commit(message='add a',
696
timestamp=1132711707,
698
committer='Line-Log-Formatter Tester <test@line.log>')
699
logfile = file('out.tmp', 'w+')
700
formatter = LineLogFormatter(to_file=logfile)
701
show_log(b, formatter)
704
log_contents = logfile.read()
705
self.assertEqualDiff(log_contents,
706
'1: Line-Log-Formatte... 2005-11-23 add a\n')
708
def test_trailing_newlines(self):
709
wt = self.make_branch_and_tree('.')
710
b = make_commits_with_trailing_newlines(wt)
711
sio = self.make_utf8_encoded_stringio()
712
lf = LineLogFormatter(to_file=sio)
714
self.assertEqualDiff(sio.getvalue(), """\
715
3: Joe Foo 2005-11-21 single line with trailing newline
716
2: Joe Bar 2005-11-21 multiline
717
1: Joe Foo 2005-11-21 simple log message
720
def test_line_log_single_merge_revision(self):
721
wt = self.make_branch_and_memory_tree('.')
725
wt.commit('rev-1', rev_id='rev-1',
726
timestamp=1132586655, timezone=36000,
727
committer='Joe Foo <joe@foo.com>')
728
wt.commit('rev-merged', rev_id='rev-2a',
729
timestamp=1132586700, timezone=36000,
730
committer='Joe Foo <joe@foo.com>')
731
wt.set_parent_ids(['rev-1', 'rev-2a'])
732
wt.branch.set_last_revision_info(1, 'rev-1')
733
wt.commit('rev-2', rev_id='rev-2b',
734
timestamp=1132586800, timezone=36000,
735
committer='Joe Foo <joe@foo.com>')
736
logfile = self.make_utf8_encoded_stringio()
737
formatter = LineLogFormatter(to_file=logfile)
738
revspec = RevisionSpec.from_string('1.1.1')
740
rev = revspec.in_history(wtb)
741
show_log(wtb, formatter, start_revision=rev, end_revision=rev)
742
self.assertEqualDiff(logfile.getvalue(), """\
743
1.1.1: Joe Foo 2005-11-22 rev-merged
750
class TestGetViewRevisions(TestCaseWithTransport):
752
def make_tree_with_commits(self):
753
"""Create a tree with well-known revision ids"""
754
wt = self.make_branch_and_tree('tree1')
755
wt.commit('commit one', rev_id='1')
756
wt.commit('commit two', rev_id='2')
757
wt.commit('commit three', rev_id='3')
758
mainline_revs = [None, '1', '2', '3']
759
rev_nos = {'1': 1, '2': 2, '3': 3}
760
return mainline_revs, rev_nos, wt
762
def make_tree_with_merges(self):
763
"""Create a tree with well-known revision ids and a merge"""
764
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
765
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
766
tree2.commit('four-a', rev_id='4a')
767
wt.merge_from_branch(tree2.branch)
768
wt.commit('four-b', rev_id='4b')
769
mainline_revs.append('4b')
772
return mainline_revs, rev_nos, wt
774
def make_tree_with_many_merges(self):
775
"""Create a tree with well-known revision ids"""
776
wt = self.make_branch_and_tree('tree1')
777
wt.commit('commit one', rev_id='1')
778
wt.commit('commit two', rev_id='2')
779
tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
780
tree3.commit('commit three a', rev_id='3a')
781
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
782
tree2.merge_from_branch(tree3.branch)
783
tree2.commit('commit three b', rev_id='3b')
784
wt.merge_from_branch(tree2.branch)
785
wt.commit('commit three c', rev_id='3c')
786
tree2.commit('four-a', rev_id='4a')
787
wt.merge_from_branch(tree2.branch)
788
wt.commit('four-b', rev_id='4b')
789
mainline_revs = [None, '1', '2', '3c', '4b']
790
rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
791
full_rev_nos_for_reference = {
794
'3a': '2.1.1', #first commit tree 3
795
'3b': '2.2.1', # first commit tree 2
796
'3c': '3', #merges 3b to main
797
'4a': '2.2.2', # second commit tree 2
798
'4b': '4', # merges 4a to main
800
return mainline_revs, rev_nos, wt
802
def test_get_view_revisions_forward(self):
803
"""Test the get_view_revisions method"""
804
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
806
self.addCleanup(wt.unlock)
807
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
809
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
811
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
812
'forward', include_merges=False))
813
self.assertEqual(revisions, revisions2)
815
def test_get_view_revisions_reverse(self):
816
"""Test the get_view_revisions with reverse"""
817
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
819
self.addCleanup(wt.unlock)
820
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
822
self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
824
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
825
'reverse', include_merges=False))
826
self.assertEqual(revisions, revisions2)
828
def test_get_view_revisions_merge(self):
829
"""Test get_view_revisions when there are merges"""
830
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
832
self.addCleanup(wt.unlock)
833
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
835
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
836
('4b', '4', 0), ('4a', '3.1.1', 1)],
838
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
839
'forward', include_merges=False))
840
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
844
def test_get_view_revisions_merge_reverse(self):
845
"""Test get_view_revisions in reverse when there are merges"""
846
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
848
self.addCleanup(wt.unlock)
849
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
851
self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
852
('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
854
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
855
'reverse', include_merges=False))
856
self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
860
def test_get_view_revisions_merge2(self):
861
"""Test get_view_revisions when there are merges"""
862
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
864
self.addCleanup(wt.unlock)
865
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
867
expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
868
('3a', '2.1.1', 1), ('3b', '2.2.1', 1), ('4b', '4', 0),
870
self.assertEqual(expected, revisions)
871
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
872
'forward', include_merges=False))
873
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
878
class TestGetRevisionsTouchingFileID(TestCaseWithTransport):
880
def create_tree_with_single_merge(self):
881
"""Create a branch with a moderate layout.
883
The revision graph looks like:
891
In this graph, A introduced files f1 and f2 and f3.
892
B modifies f1 and f3, and C modifies f2 and f3.
893
D merges the changes from B and C and resolves the conflict for f3.
895
# TODO: jam 20070218 This seems like it could really be done
896
# with make_branch_and_memory_tree() if we could just
897
# create the content of those files.
898
# TODO: jam 20070218 Another alternative is that we would really
899
# like to only create this tree 1 time for all tests that
900
# use it. Since 'log' only uses the tree in a readonly
901
# fashion, it seems a shame to regenerate an identical
902
# tree for each test.
903
tree = self.make_branch_and_tree('tree')
905
self.addCleanup(tree.unlock)
907
self.build_tree_contents([('tree/f1', 'A\n'),
911
tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
912
tree.commit('A', rev_id='A')
914
self.build_tree_contents([('tree/f2', 'A\nC\n'),
915
('tree/f3', 'A\nC\n'),
917
tree.commit('C', rev_id='C')
918
# Revert back to A to build the other history.
919
tree.set_last_revision('A')
920
tree.branch.set_last_revision_info(1, 'A')
921
self.build_tree_contents([('tree/f1', 'A\nB\n'),
923
('tree/f3', 'A\nB\n'),
925
tree.commit('B', rev_id='B')
926
tree.set_parent_ids(['B', 'C'])
927
self.build_tree_contents([('tree/f1', 'A\nB\n'),
928
('tree/f2', 'A\nC\n'),
929
('tree/f3', 'A\nB\nC\n'),
931
tree.commit('D', rev_id='D')
933
# Switch to a read lock for this tree.
934
# We still have addCleanup(unlock)
939
def test_tree_with_single_merge(self):
940
"""Make sure the tree layout is correct."""
941
tree = self.create_tree_with_single_merge()
942
rev_A_tree = tree.branch.repository.revision_tree('A')
943
rev_B_tree = tree.branch.repository.revision_tree('B')
945
f1_changed = (u'f1', 'f1-id', 'file', True, False)
946
f2_changed = (u'f2', 'f2-id', 'file', True, False)
947
f3_changed = (u'f3', 'f3-id', 'file', True, False)
949
delta = rev_B_tree.changes_from(rev_A_tree)
950
self.assertEqual([f1_changed, f3_changed], delta.modified)
951
self.assertEqual([], delta.renamed)
952
self.assertEqual([], delta.added)
953
self.assertEqual([], delta.removed)
955
rev_C_tree = tree.branch.repository.revision_tree('C')
956
delta = rev_C_tree.changes_from(rev_A_tree)
957
self.assertEqual([f2_changed, f3_changed], delta.modified)
958
self.assertEqual([], delta.renamed)
959
self.assertEqual([], delta.added)
960
self.assertEqual([], delta.removed)
962
rev_D_tree = tree.branch.repository.revision_tree('D')
963
delta = rev_D_tree.changes_from(rev_B_tree)
964
self.assertEqual([f2_changed, f3_changed], delta.modified)
965
self.assertEqual([], delta.renamed)
966
self.assertEqual([], delta.added)
967
self.assertEqual([], delta.removed)
969
delta = rev_D_tree.changes_from(rev_C_tree)
970
self.assertEqual([f1_changed, f3_changed], delta.modified)
971
self.assertEqual([], delta.renamed)
972
self.assertEqual([], delta.added)
973
self.assertEqual([], delta.removed)
975
def assertAllRevisionsForFileID(self, tree, file_id, revisions):
976
"""Make sure _filter_revisions_touching_file_id returns the right values.
978
Get the return value from _filter_revisions_touching_file_id and make
979
sure they are correct.
981
# The api for _get_revisions_touching_file_id is a little crazy,
982
# So we do the setup here.
983
mainline = tree.branch.revision_history()
984
mainline.insert(0, None)
985
revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
986
view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
988
actual_revs = log._filter_revisions_touching_file_id(
991
list(view_revs_iter),
993
self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
995
def test_file_id_f1(self):
996
tree = self.create_tree_with_single_merge()
997
# f1 should be marked as modified by revisions A and B
998
self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
1000
def test_file_id_f2(self):
1001
tree = self.create_tree_with_single_merge()
1002
# f2 should be marked as modified by revisions A, C, and D
1003
# because D merged the changes from C.
1004
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1006
def test_file_id_f3(self):
1007
tree = self.create_tree_with_single_merge()
1008
# f3 should be marked as modified by revisions A, B, C, and D
1009
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1011
def test_file_id_with_ghosts(self):
1012
# This is testing bug #209948, where having a ghost would cause
1013
# _filter_revisions_touching_file_id() to fail.
1014
tree = self.create_tree_with_single_merge()
1015
# We need to add a revision, so switch back to a write-locked tree
1018
first_parent = tree.last_revision()
1019
tree.set_parent_ids([first_parent, 'ghost-revision-id'])
1020
self.build_tree_contents([('tree/f1', 'A\nB\nXX\n')])
1021
tree.commit('commit with a ghost', rev_id='XX')
1022
self.assertAllRevisionsForFileID(tree, 'f1-id', ['XX', 'B', 'A'])
1023
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1026
class TestShowChangedRevisions(TestCaseWithTransport):
1028
def test_show_changed_revisions_verbose(self):
1029
tree = self.make_branch_and_tree('tree_a')
1030
self.build_tree(['tree_a/foo'])
1032
tree.commit('bar', rev_id='bar-id')
1033
s = self.make_utf8_encoded_stringio()
1034
log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
1035
self.assertContainsRe(s.getvalue(), 'bar')
1036
self.assertNotContainsRe(s.getvalue(), 'foo')
1039
class TestLogFormatter(TestCase):
1041
def test_short_committer(self):
1042
rev = Revision('a-id')
1043
rev.committer = 'John Doe <jdoe@example.com>'
1044
lf = LogFormatter(None)
1045
self.assertEqual('John Doe', lf.short_committer(rev))
1046
rev.committer = 'John Smith <jsmith@example.com>'
1047
self.assertEqual('John Smith', lf.short_committer(rev))
1048
rev.committer = 'John Smith'
1049
self.assertEqual('John Smith', lf.short_committer(rev))
1050
rev.committer = 'jsmith@example.com'
1051
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1052
rev.committer = '<jsmith@example.com>'
1053
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1054
rev.committer = 'John Smith jsmith@example.com'
1055
self.assertEqual('John Smith', lf.short_committer(rev))
1057
def test_short_author(self):
1058
rev = Revision('a-id')
1059
rev.committer = 'John Doe <jdoe@example.com>'
1060
lf = LogFormatter(None)
1061
self.assertEqual('John Doe', lf.short_author(rev))
1062
rev.properties['author'] = 'John Smith <jsmith@example.com>'
1063
self.assertEqual('John Smith', lf.short_author(rev))
1064
rev.properties['author'] = 'John Smith'
1065
self.assertEqual('John Smith', lf.short_author(rev))
1066
rev.properties['author'] = 'jsmith@example.com'
1067
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1068
rev.properties['author'] = '<jsmith@example.com>'
1069
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1070
rev.properties['author'] = 'John Smith jsmith@example.com'
1071
self.assertEqual('John Smith', lf.short_author(rev))
113
# self.checkDelta(d, added=['hello'])