177
163
self.log("escaped commit message: %r", committed_msg)
178
164
self.assert_(msg == committed_msg)
166
def test_deltas_in_merge_revisions(self):
167
"""Check deltas created for both mainline and merge revisions"""
168
eq = self.assertEquals
169
wt = self.make_branch_and_tree('parent')
170
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
173
wt.commit(message='add file1 and file2')
174
self.run_bzr('branch parent child')
175
os.unlink('child/file1')
176
file('child/file2', 'wb').write('hello\n')
177
self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
180
self.run_bzr('merge ../child')
181
wt.commit('merge child branch')
185
lf.supports_merge_revisions = True
186
show_log(b, lf, verbose=True)
188
logentry = lf.logs[0]
189
eq(logentry.revno, '2')
190
eq(logentry.rev.message, 'merge child branch')
192
self.checkDelta(d, removed=['file1'], modified=['file2'])
193
logentry = lf.logs[1]
194
eq(logentry.revno, '1.1.1')
195
eq(logentry.rev.message, 'remove file1 and modify file2')
197
self.checkDelta(d, removed=['file1'], modified=['file2'])
198
logentry = lf.logs[2]
199
eq(logentry.revno, '1')
200
eq(logentry.rev.message, 'add file1 and file2')
202
self.checkDelta(d, added=['file1', 'file2'])
204
def test_merges_nonsupporting_formatter(self):
205
"""Tests that show_log will raise if the formatter doesn't
206
support merge revisions."""
207
wt = self.make_branch_and_memory_tree('.')
211
wt.commit('rev-1', rev_id='rev-1',
212
timestamp=1132586655, timezone=36000,
213
committer='Joe Foo <joe@foo.com>')
214
wt.commit('rev-merged', rev_id='rev-2a',
215
timestamp=1132586700, timezone=36000,
216
committer='Joe Foo <joe@foo.com>')
217
wt.set_parent_ids(['rev-1', 'rev-2a'])
218
wt.branch.set_last_revision_info(1, 'rev-1')
219
wt.commit('rev-2', rev_id='rev-2b',
220
timestamp=1132586800, timezone=36000,
221
committer='Joe Foo <joe@foo.com>')
222
logfile = self.make_utf8_encoded_stringio()
223
formatter = ShortLogFormatter(to_file=logfile)
226
revspec = RevisionSpec.from_string('1.1.1')
227
rev = revspec.in_history(wtb)
228
self.assertRaises(BzrCommandError, show_log, wtb, lf,
229
start_revision=rev, end_revision=rev)
234
def make_commits_with_trailing_newlines(wt):
235
"""Helper method for LogFormatter tests"""
238
open('a', 'wb').write('hello moto\n')
240
wt.commit('simple log message', rev_id='a1',
241
timestamp=1132586655.459960938, timezone=-6*3600,
242
committer='Joe Foo <joe@foo.com>')
243
open('b', 'wb').write('goodbye\n')
245
wt.commit('multiline\nlog\nmessage\n', rev_id='a2',
246
timestamp=1132586842.411175966, timezone=-6*3600,
247
committer='Joe Foo <joe@foo.com>',
248
author='Joe Bar <joe@bar.com>')
250
open('c', 'wb').write('just another manic monday\n')
252
wt.commit('single line with trailing newline\n', rev_id='a3',
253
timestamp=1132587176.835228920, timezone=-6*3600,
254
committer = 'Joe Foo <joe@foo.com>')
258
def normalize_log(log):
259
"""Replaces the variable lines of logs with fixed lines"""
260
author = 'author: Dolor Sit <test@example.com>'
261
committer = 'committer: Lorem Ipsum <test@example.com>'
262
lines = log.splitlines(True)
263
for idx,line in enumerate(lines):
264
stripped_line = line.lstrip()
265
indent = ' ' * (len(line) - len(stripped_line))
266
if stripped_line.startswith('author:'):
267
lines[idx] = indent + author + '\n'
268
elif stripped_line.startswith('committer:'):
269
lines[idx] = indent + committer + '\n'
270
elif stripped_line.startswith('timestamp:'):
271
lines[idx] = indent + 'timestamp: Just now\n'
272
return ''.join(lines)
275
class TestShortLogFormatter(TestCaseWithTransport):
180
277
def test_trailing_newlines(self):
181
278
wt = self.make_branch_and_tree('.')
184
open('a', 'wb').write('hello moto\n')
186
wt.commit('simple log message', rev_id='a1'
187
, timestamp=1132586655.459960938, timezone=-6*3600
188
, committer='Joe Foo <joe@foo.com>')
189
open('b', 'wb').write('goodbye\n')
191
wt.commit('multiline\nlog\nmessage\n', rev_id='a2'
192
, timestamp=1132586842.411175966, timezone=-6*3600
193
, committer='Joe Foo <joe@foo.com>')
195
open('c', 'wb').write('just another manic monday\n')
197
wt.commit('single line with trailing newline\n', rev_id='a3'
198
, timestamp=1132587176.835228920, timezone=-6*3600
199
, committer = 'Joe Foo <joe@foo.com>')
279
b = make_commits_with_trailing_newlines(wt)
280
sio = self.make_utf8_encoded_stringio()
202
281
lf = ShortLogFormatter(to_file=sio)
204
self.assertEquals(sio.getvalue(), """\
283
self.assertEqualDiff(sio.getvalue(), """\
205
284
3 Joe Foo\t2005-11-21
206
285
single line with trailing newline
208
2 Joe Foo\t2005-11-21
287
2 Joe Bar\t2005-11-21
393
def test_merges_are_indented_by_level(self):
394
wt = self.make_branch_and_tree('parent')
395
wt.commit('first post')
396
self.run_bzr('branch parent child')
397
self.run_bzr(['commit', '-m', 'branch 1', '--unchanged', 'child'])
398
self.run_bzr('branch child smallerchild')
399
self.run_bzr(['commit', '-m', 'branch 2', '--unchanged',
402
self.run_bzr('merge ../smallerchild')
403
self.run_bzr(['commit', '-m', 'merge branch 2'])
404
os.chdir('../parent')
405
self.run_bzr('merge ../child')
406
wt.commit('merge branch 1')
408
sio = self.make_utf8_encoded_stringio()
409
lf = LongLogFormatter(to_file=sio)
410
show_log(b, lf, verbose=True)
411
log = normalize_log(sio.getvalue())
412
self.assertEqualDiff(log, """\
413
------------------------------------------------------------
415
committer: Lorem Ipsum <test@example.com>
420
------------------------------------------------------------
422
committer: Lorem Ipsum <test@example.com>
427
------------------------------------------------------------
429
committer: Lorem Ipsum <test@example.com>
430
branch nick: smallerchild
434
------------------------------------------------------------
436
committer: Lorem Ipsum <test@example.com>
441
------------------------------------------------------------
443
committer: Lorem Ipsum <test@example.com>
450
def test_verbose_merge_revisions_contain_deltas(self):
451
wt = self.make_branch_and_tree('parent')
452
self.build_tree(['parent/f1', 'parent/f2'])
454
wt.commit('first post')
455
self.run_bzr('branch parent child')
456
os.unlink('child/f1')
457
file('child/f2', 'wb').write('hello\n')
458
self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
461
self.run_bzr('merge ../child')
462
wt.commit('merge branch 1')
464
sio = self.make_utf8_encoded_stringio()
465
lf = LongLogFormatter(to_file=sio)
466
show_log(b, lf, verbose=True)
467
log = normalize_log(sio.getvalue())
468
self.assertEqualDiff(log, """\
469
------------------------------------------------------------
471
committer: Lorem Ipsum <test@example.com>
480
------------------------------------------------------------
482
committer: Lorem Ipsum <test@example.com>
486
removed f1 and modified f2
491
------------------------------------------------------------
493
committer: Lorem Ipsum <test@example.com>
503
def test_trailing_newlines(self):
504
wt = self.make_branch_and_tree('.')
505
b = make_commits_with_trailing_newlines(wt)
506
sio = self.make_utf8_encoded_stringio()
507
lf = LongLogFormatter(to_file=sio)
509
self.assertEqualDiff(sio.getvalue(), """\
510
------------------------------------------------------------
512
committer: Joe Foo <joe@foo.com>
514
timestamp: Mon 2005-11-21 09:32:56 -0600
516
single line with trailing newline
517
------------------------------------------------------------
519
author: Joe Bar <joe@bar.com>
520
committer: Joe Foo <joe@foo.com>
522
timestamp: Mon 2005-11-21 09:27:22 -0600
527
------------------------------------------------------------
529
committer: Joe Foo <joe@foo.com>
531
timestamp: Mon 2005-11-21 09:24:15 -0600
536
def test_author_in_log(self):
537
"""Log includes the author name if it's set in
538
the revision properties
540
wt = self.make_branch_and_tree('.')
542
self.build_tree(['a'])
544
b.nick = 'test_author_log'
545
wt.commit(message='add a',
546
timestamp=1132711707,
548
committer='Lorem Ipsum <test@example.com>',
549
author='John Doe <jdoe@example.com>')
551
formatter = LongLogFormatter(to_file=sio)
552
show_log(b, formatter)
553
self.assertEqualDiff(sio.getvalue(), '''\
554
------------------------------------------------------------
556
author: John Doe <jdoe@example.com>
557
committer: Lorem Ipsum <test@example.com>
558
branch nick: test_author_log
559
timestamp: Wed 2005-11-23 12:08:27 +1000
566
class TestLineLogFormatter(TestCaseWithTransport):
280
568
def test_line_log(self):
281
569
"""Line log should show revno
299
587
log_contents = logfile.read()
300
self.assertEqualDiff(log_contents, '1: Line-Log-Formatte... 2005-11-23 add a\n')
588
self.assertEqualDiff(log_contents,
589
'1: Line-Log-Formatte... 2005-11-23 add a\n')
591
def test_trailing_newlines(self):
592
wt = self.make_branch_and_tree('.')
593
b = make_commits_with_trailing_newlines(wt)
594
sio = self.make_utf8_encoded_stringio()
595
lf = LineLogFormatter(to_file=sio)
597
self.assertEqualDiff(sio.getvalue(), """\
598
3: Joe Foo 2005-11-21 single line with trailing newline
599
2: Joe Bar 2005-11-21 multiline
600
1: Joe Foo 2005-11-21 simple log message
603
def test_line_log_single_merge_revision(self):
604
wt = self.make_branch_and_memory_tree('.')
608
wt.commit('rev-1', rev_id='rev-1',
609
timestamp=1132586655, timezone=36000,
610
committer='Joe Foo <joe@foo.com>')
611
wt.commit('rev-merged', rev_id='rev-2a',
612
timestamp=1132586700, timezone=36000,
613
committer='Joe Foo <joe@foo.com>')
614
wt.set_parent_ids(['rev-1', 'rev-2a'])
615
wt.branch.set_last_revision_info(1, 'rev-1')
616
wt.commit('rev-2', rev_id='rev-2b',
617
timestamp=1132586800, timezone=36000,
618
committer='Joe Foo <joe@foo.com>')
619
logfile = self.make_utf8_encoded_stringio()
620
formatter = LineLogFormatter(to_file=logfile)
621
revspec = RevisionSpec.from_string('1.1.1')
623
rev = revspec.in_history(wtb)
624
show_log(wtb, formatter, start_revision=rev, end_revision=rev)
625
self.assertEqualDiff(logfile.getvalue(), """\
626
1.1.1: Joe Foo 2005-11-22 rev-merged
633
class TestGetViewRevisions(TestCaseWithTransport):
635
def make_tree_with_commits(self):
636
"""Create a tree with well-known revision ids"""
637
wt = self.make_branch_and_tree('tree1')
638
wt.commit('commit one', rev_id='1')
639
wt.commit('commit two', rev_id='2')
640
wt.commit('commit three', rev_id='3')
641
mainline_revs = [None, '1', '2', '3']
642
rev_nos = {'1': 1, '2': 2, '3': 3}
643
return mainline_revs, rev_nos, wt
645
def make_tree_with_merges(self):
646
"""Create a tree with well-known revision ids and a merge"""
647
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
648
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
649
tree2.commit('four-a', rev_id='4a')
650
wt.merge_from_branch(tree2.branch)
651
wt.commit('four-b', rev_id='4b')
652
mainline_revs.append('4b')
655
return mainline_revs, rev_nos, wt
657
def make_tree_with_many_merges(self):
658
"""Create a tree with well-known revision ids"""
659
wt = self.make_branch_and_tree('tree1')
660
wt.commit('commit one', rev_id='1')
661
wt.commit('commit two', rev_id='2')
662
tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
663
tree3.commit('commit three a', rev_id='3a')
664
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
665
tree2.merge_from_branch(tree3.branch)
666
tree2.commit('commit three b', rev_id='3b')
667
wt.merge_from_branch(tree2.branch)
668
wt.commit('commit three c', rev_id='3c')
669
tree2.commit('four-a', rev_id='4a')
670
wt.merge_from_branch(tree2.branch)
671
wt.commit('four-b', rev_id='4b')
672
mainline_revs = [None, '1', '2', '3c', '4b']
673
rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
674
full_rev_nos_for_reference = {
677
'3a': '2.1.1', #first commit tree 3
678
'3b': '2.2.1', # first commit tree 2
679
'3c': '3', #merges 3b to main
680
'4a': '2.2.2', # second commit tree 2
681
'4b': '4', # merges 4a to main
683
return mainline_revs, rev_nos, wt
685
def test_get_view_revisions_forward(self):
686
"""Test the get_view_revisions method"""
687
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
688
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
690
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
692
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
693
'forward', include_merges=False))
694
self.assertEqual(revisions, revisions2)
696
def test_get_view_revisions_reverse(self):
697
"""Test the get_view_revisions with reverse"""
698
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
699
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
701
self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
703
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
704
'reverse', include_merges=False))
705
self.assertEqual(revisions, revisions2)
707
def test_get_view_revisions_merge(self):
708
"""Test get_view_revisions when there are merges"""
709
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
710
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
712
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
713
('4b', '4', 0), ('4a', '3.1.1', 1)],
715
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
716
'forward', include_merges=False))
717
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
721
def test_get_view_revisions_merge_reverse(self):
722
"""Test get_view_revisions in reverse when there are merges"""
723
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
724
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
726
self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
727
('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
729
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
730
'reverse', include_merges=False))
731
self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
735
def test_get_view_revisions_merge2(self):
736
"""Test get_view_revisions when there are merges"""
737
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
738
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
740
expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
741
('3a', '2.1.1', 1), ('3b', '2.2.1', 1), ('4b', '4', 0),
743
self.assertEqual(expected, revisions)
744
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
745
'forward', include_merges=False))
746
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
751
class TestGetRevisionsTouchingFileID(TestCaseWithTransport):
753
def create_tree_with_single_merge(self):
754
"""Create a branch with a moderate layout.
756
The revision graph looks like:
764
In this graph, A introduced files f1 and f2 and f3.
765
B modifies f1 and f3, and C modifies f2 and f3.
766
D merges the changes from B and C and resolves the conflict for f3.
768
# TODO: jam 20070218 This seems like it could really be done
769
# with make_branch_and_memory_tree() if we could just
770
# create the content of those files.
771
# TODO: jam 20070218 Another alternative is that we would really
772
# like to only create this tree 1 time for all tests that
773
# use it. Since 'log' only uses the tree in a readonly
774
# fashion, it seems a shame to regenerate an identical
775
# tree for each test.
776
tree = self.make_branch_and_tree('tree')
778
self.addCleanup(tree.unlock)
780
self.build_tree_contents([('tree/f1', 'A\n'),
784
tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
785
tree.commit('A', rev_id='A')
787
self.build_tree_contents([('tree/f2', 'A\nC\n'),
788
('tree/f3', 'A\nC\n'),
790
tree.commit('C', rev_id='C')
791
# Revert back to A to build the other history.
792
tree.set_last_revision('A')
793
tree.branch.set_last_revision_info(1, 'A')
794
self.build_tree_contents([('tree/f1', 'A\nB\n'),
796
('tree/f3', 'A\nB\n'),
798
tree.commit('B', rev_id='B')
799
tree.set_parent_ids(['B', 'C'])
800
self.build_tree_contents([('tree/f1', 'A\nB\n'),
801
('tree/f2', 'A\nC\n'),
802
('tree/f3', 'A\nB\nC\n'),
804
tree.commit('D', rev_id='D')
806
# Switch to a read lock for this tree.
807
# We still have addCleanup(unlock)
812
def test_tree_with_single_merge(self):
813
"""Make sure the tree layout is correct."""
814
tree = self.create_tree_with_single_merge()
815
rev_A_tree = tree.branch.repository.revision_tree('A')
816
rev_B_tree = tree.branch.repository.revision_tree('B')
818
f1_changed = (u'f1', 'f1-id', 'file', True, False)
819
f2_changed = (u'f2', 'f2-id', 'file', True, False)
820
f3_changed = (u'f3', 'f3-id', 'file', True, False)
822
delta = rev_B_tree.changes_from(rev_A_tree)
823
self.assertEqual([f1_changed, f3_changed], delta.modified)
824
self.assertEqual([], delta.renamed)
825
self.assertEqual([], delta.added)
826
self.assertEqual([], delta.removed)
828
rev_C_tree = tree.branch.repository.revision_tree('C')
829
delta = rev_C_tree.changes_from(rev_A_tree)
830
self.assertEqual([f2_changed, f3_changed], delta.modified)
831
self.assertEqual([], delta.renamed)
832
self.assertEqual([], delta.added)
833
self.assertEqual([], delta.removed)
835
rev_D_tree = tree.branch.repository.revision_tree('D')
836
delta = rev_D_tree.changes_from(rev_B_tree)
837
self.assertEqual([f2_changed, f3_changed], delta.modified)
838
self.assertEqual([], delta.renamed)
839
self.assertEqual([], delta.added)
840
self.assertEqual([], delta.removed)
842
delta = rev_D_tree.changes_from(rev_C_tree)
843
self.assertEqual([f1_changed, f3_changed], delta.modified)
844
self.assertEqual([], delta.renamed)
845
self.assertEqual([], delta.added)
846
self.assertEqual([], delta.removed)
848
def assertAllRevisionsForFileID(self, tree, file_id, revisions):
849
"""Make sure _filter_revisions_touching_file_id returns the right values.
851
Get the return value from _filter_revisions_touching_file_id and make
852
sure they are correct.
854
# The api for _get_revisions_touching_file_id is a little crazy,
855
# So we do the setup here.
856
mainline = tree.branch.revision_history()
857
mainline.insert(0, None)
858
revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
859
view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
861
actual_revs = log._filter_revisions_touching_file_id(
865
list(view_revs_iter))
866
self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
868
def test_file_id_f1(self):
869
tree = self.create_tree_with_single_merge()
870
# f1 should be marked as modified by revisions A and B
871
self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
873
def test_file_id_f2(self):
874
tree = self.create_tree_with_single_merge()
875
# f2 should be marked as modified by revisions A, C, and D
876
# because D merged the changes from C.
877
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
879
def test_file_id_f3(self):
880
tree = self.create_tree_with_single_merge()
881
# f3 should be marked as modified by revisions A, B, C, and D
882
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
885
class TestShowChangedRevisions(TestCaseWithTransport):
887
def test_show_changed_revisions_verbose(self):
888
tree = self.make_branch_and_tree('tree_a')
889
self.build_tree(['tree_a/foo'])
891
tree.commit('bar', rev_id='bar-id')
892
s = self.make_utf8_encoded_stringio()
893
log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
894
self.assertContainsRe(s.getvalue(), 'bar')
895
self.assertNotContainsRe(s.getvalue(), 'foo')
898
class TestLogFormatter(TestCase):
900
def test_short_committer(self):
901
rev = Revision('a-id')
902
rev.committer = 'John Doe <jdoe@example.com>'
903
lf = LogFormatter(None)
904
self.assertEqual('John Doe', lf.short_committer(rev))
905
rev.committer = 'John Smith <jsmith@example.com>'
906
self.assertEqual('John Smith', lf.short_committer(rev))
907
rev.committer = 'John Smith'
908
self.assertEqual('John Smith', lf.short_committer(rev))
909
rev.committer = 'jsmith@example.com'
910
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
911
rev.committer = '<jsmith@example.com>'
912
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
913
rev.committer = 'John Smith jsmith@example.com'
914
self.assertEqual('John Smith', lf.short_committer(rev))
916
def test_short_author(self):
917
rev = Revision('a-id')
918
rev.committer = 'John Doe <jdoe@example.com>'
919
lf = LogFormatter(None)
920
self.assertEqual('John Doe', lf.short_author(rev))
921
rev.properties['author'] = 'John Smith <jsmith@example.com>'
922
self.assertEqual('John Smith', lf.short_author(rev))
923
rev.properties['author'] = 'John Smith'
924
self.assertEqual('John Smith', lf.short_author(rev))
925
rev.properties['author'] = 'jsmith@example.com'
926
self.assertEqual('jsmith@example.com', lf.short_author(rev))
927
rev.properties['author'] = '<jsmith@example.com>'
928
self.assertEqual('jsmith@example.com', lf.short_author(rev))
929
rev.properties['author'] = 'John Smith jsmith@example.com'
930
self.assertEqual('John Smith', lf.short_author(rev))