167
149
# valid XML 1.0 characters.
168
150
msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
169
151
self.log("original commit message: %r", msg)
170
b.working_tree().commit(msg)
171
153
lf = LogCatcher()
172
154
show_log(b, lf, verbose=True)
173
155
committed_msg = lf.logs[0].rev.message
174
156
self.log("escaped commit message: %r", committed_msg)
175
157
self.assert_(msg == committed_msg)
159
def test_deltas_in_merge_revisions(self):
160
"""Check deltas created for both mainline and merge revisions"""
161
eq = self.assertEquals
162
wt = self.make_branch_and_tree('parent')
163
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
166
wt.commit(message='add file1 and file2')
167
self.run_bzr('branch parent child')
168
os.unlink('child/file1')
169
file('child/file2', 'wb').write('hello\n')
170
self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
173
self.run_bzr('merge ../child')
174
wt.commit('merge child branch')
178
lf.supports_merge_revisions = True
179
show_log(b, lf, verbose=True)
181
logentry = lf.logs[0]
182
eq(logentry.revno, '2')
183
eq(logentry.rev.message, 'merge child branch')
185
self.checkDelta(d, removed=['file1'], modified=['file2'])
186
logentry = lf.logs[1]
187
eq(logentry.revno, '1.1.1')
188
eq(logentry.rev.message, 'remove file1 and modify file2')
190
self.checkDelta(d, removed=['file1'], modified=['file2'])
191
logentry = lf.logs[2]
192
eq(logentry.revno, '1')
193
eq(logentry.rev.message, 'add file1 and file2')
195
self.checkDelta(d, added=['file1', 'file2'])
198
def make_commits_with_trailing_newlines(wt):
199
"""Helper method for LogFormatter tests"""
202
open('a', 'wb').write('hello moto\n')
204
wt.commit('simple log message', rev_id='a1',
205
timestamp=1132586655.459960938, timezone=-6*3600,
206
committer='Joe Foo <joe@foo.com>')
207
open('b', 'wb').write('goodbye\n')
209
wt.commit('multiline\nlog\nmessage\n', rev_id='a2',
210
timestamp=1132586842.411175966, timezone=-6*3600,
211
committer='Joe Foo <joe@foo.com>',
212
author='Joe Bar <joe@bar.com>')
214
open('c', 'wb').write('just another manic monday\n')
216
wt.commit('single line with trailing newline\n', rev_id='a3',
217
timestamp=1132587176.835228920, timezone=-6*3600,
218
committer = 'Joe Foo <joe@foo.com>')
222
class TestShortLogFormatter(TestCaseWithTransport):
177
224
def test_trailing_newlines(self):
178
b = Branch.initialize(u'.')
180
wt = b.working_tree()
181
open('a', 'wb').write('hello moto\n')
183
wt.commit('simple log message', rev_id='a1'
184
, timestamp=1132586655.459960938, timezone=-6*3600
185
, committer='Joe Foo <joe@foo.com>')
186
open('b', 'wb').write('goodbye\n')
188
wt.commit('multiline\nlog\nmessage\n', rev_id='a2'
189
, timestamp=1132586842.411175966, timezone=-6*3600
190
, committer='Joe Foo <joe@foo.com>')
192
open('c', 'wb').write('just another manic monday\n')
194
wt.commit('single line with trailing newline\n', rev_id='a3'
195
, timestamp=1132587176.835228920, timezone=-6*3600
196
, committer = 'Joe Foo <joe@foo.com>')
225
wt = self.make_branch_and_tree('.')
226
b = make_commits_with_trailing_newlines(wt)
227
sio = self.make_utf8_encoded_stringio()
199
228
lf = ShortLogFormatter(to_file=sio)
201
230
self.assertEquals(sio.getvalue(), """\
202
231
3 Joe Foo\t2005-11-21
203
232
single line with trailing newline
205
2 Joe Foo\t2005-11-21
234
2 Joe Bar\t2005-11-21
296
def test_merges_are_indented_by_level(self):
297
wt = self.make_branch_and_tree('parent')
298
wt.commit('first post')
299
self.run_bzr('branch parent child')
300
self.run_bzr(['commit', '-m', 'branch 1', '--unchanged', 'child'])
301
self.run_bzr('branch child smallerchild')
302
self.run_bzr(['commit', '-m', 'branch 2', '--unchanged',
305
self.run_bzr('merge ../smallerchild')
306
self.run_bzr(['commit', '-m', 'merge branch 2'])
307
os.chdir('../parent')
308
self.run_bzr('merge ../child')
309
wt.commit('merge branch 1')
311
sio = self.make_utf8_encoded_stringio()
312
lf = LongLogFormatter(to_file=sio)
313
show_log(b, lf, verbose=True)
314
log = self.normalize_log(sio.getvalue())
315
self.assertEqualDiff("""\
316
------------------------------------------------------------
318
committer: Lorem Ipsum <test@example.com>
323
------------------------------------------------------------
325
committer: Lorem Ipsum <test@example.com>
330
------------------------------------------------------------
332
committer: Lorem Ipsum <test@example.com>
333
branch nick: smallerchild
337
------------------------------------------------------------
339
committer: Lorem Ipsum <test@example.com>
344
------------------------------------------------------------
346
committer: Lorem Ipsum <test@example.com>
353
def test_verbose_merge_revisions_contain_deltas(self):
354
wt = self.make_branch_and_tree('parent')
355
self.build_tree(['parent/f1', 'parent/f2'])
357
wt.commit('first post')
358
self.run_bzr('branch parent child')
359
os.unlink('child/f1')
360
file('child/f2', 'wb').write('hello\n')
361
self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
364
self.run_bzr('merge ../child')
365
wt.commit('merge branch 1')
367
sio = self.make_utf8_encoded_stringio()
368
lf = LongLogFormatter(to_file=sio)
369
show_log(b, lf, verbose=True)
370
log = self.normalize_log(sio.getvalue())
371
self.assertEqualDiff("""\
372
------------------------------------------------------------
374
committer: Lorem Ipsum <test@example.com>
383
------------------------------------------------------------
385
committer: Lorem Ipsum <test@example.com>
389
removed f1 and modified f2
394
------------------------------------------------------------
396
committer: Lorem Ipsum <test@example.com>
406
def test_trailing_newlines(self):
407
wt = self.make_branch_and_tree('.')
408
b = make_commits_with_trailing_newlines(wt)
409
sio = self.make_utf8_encoded_stringio()
410
lf = LongLogFormatter(to_file=sio)
412
self.assertEqualDiff(sio.getvalue(), """\
413
------------------------------------------------------------
415
committer: Joe Foo <joe@foo.com>
417
timestamp: Mon 2005-11-21 09:32:56 -0600
419
single line with trailing newline
420
------------------------------------------------------------
422
author: Joe Bar <joe@bar.com>
423
committer: Joe Foo <joe@foo.com>
425
timestamp: Mon 2005-11-21 09:27:22 -0600
430
------------------------------------------------------------
432
committer: Joe Foo <joe@foo.com>
434
timestamp: Mon 2005-11-21 09:24:15 -0600
439
def test_author_in_log(self):
440
"""Log includes the author name if it's set in
441
the revision properties
443
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(), '''\
457
------------------------------------------------------------
459
author: John Doe <jdoe@example.com>
460
committer: Lorem Ipsum <test@example.com>
461
branch nick: test_author_log
462
timestamp: Wed 2005-11-23 12:08:27 +1000
469
class TestLineLogFormatter(TestCaseWithTransport):
471
def test_line_log(self):
472
"""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())
524
def test_trailing_newlines(self):
525
wt = self.make_branch_and_tree('.')
526
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(), """\
531
3: Joe Foo 2005-11-21 single line with trailing newline
532
2: Joe Bar 2005-11-21 multiline
533
1: Joe Foo 2005-11-21 simple log message
537
class TestGetViewRevisions(TestCaseWithTransport):
539
def make_tree_with_commits(self):
540
"""Create a tree with well-known revision ids"""
541
wt = self.make_branch_and_tree('tree1')
542
wt.commit('commit one', rev_id='1')
543
wt.commit('commit two', rev_id='2')
544
wt.commit('commit three', rev_id='3')
545
mainline_revs = [None, '1', '2', '3']
546
rev_nos = {'1': 1, '2': 2, '3': 3}
547
return mainline_revs, rev_nos, wt
549
def make_tree_with_merges(self):
550
"""Create a tree with well-known revision ids and a merge"""
551
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
552
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
553
tree2.commit('four-a', rev_id='4a')
554
wt.merge_from_branch(tree2.branch)
555
wt.commit('four-b', rev_id='4b')
556
mainline_revs.append('4b')
559
return mainline_revs, rev_nos, wt
561
def make_tree_with_many_merges(self):
562
"""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')
576
mainline_revs = [None, '1', '2', '3c', '4b']
577
rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
578
full_rev_nos_for_reference = {
581
'3a': '2.2.1', #first commit tree 3
582
'3b': '2.1.1', # first commit tree 2
583
'3c': '3', #merges 3b to main
584
'4a': '2.1.2', # second commit tree 2
585
'4b': '4', # merges 4a to main
587
return mainline_revs, rev_nos, wt
589
def test_get_view_revisions_forward(self):
590
"""Test the get_view_revisions method"""
591
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
592
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
594
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))
598
self.assertEqual(revisions, revisions2)
600
def test_get_view_revisions_reverse(self):
601
"""Test the get_view_revisions with reverse"""
602
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
603
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
605
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))
609
self.assertEqual(revisions, revisions2)
611
def test_get_view_revisions_merge(self):
612
"""Test get_view_revisions when there are merges"""
613
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),
625
def test_get_view_revisions_merge_reverse(self):
626
"""Test get_view_revisions in reverse when there are merges"""
627
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
628
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
630
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))
635
self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
639
def test_get_view_revisions_merge2(self):
640
"""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,
644
expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
645
('3a', '2.2.1', 1), ('3b', '2.1.1', 1), ('4b', '4', 0),
647
self.assertEqual(expected, revisions)
648
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
649
'forward', include_merges=False))
650
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
655
class TestGetRevisionsTouchingFileID(TestCaseWithTransport):
657
def create_tree_with_single_merge(self):
658
"""Create a branch with a moderate layout.
660
The revision graph looks like:
668
In this graph, A introduced files f1 and f2 and f3.
669
B modifies f1 and f3, and C modifies f2 and f3.
670
D merges the changes from B and C and resolves the conflict for f3.
672
# TODO: jam 20070218 This seems like it could really be done
673
# with make_branch_and_memory_tree() if we could just
674
# create the content of those files.
675
# TODO: jam 20070218 Another alternative is that we would really
676
# like to only create this tree 1 time for all tests that
677
# use it. Since 'log' only uses the tree in a readonly
678
# fashion, it seems a shame to regenerate an identical
679
# tree for each test.
680
tree = self.make_branch_and_tree('tree')
682
self.addCleanup(tree.unlock)
684
self.build_tree_contents([('tree/f1', 'A\n'),
688
tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
689
tree.commit('A', rev_id='A')
691
self.build_tree_contents([('tree/f2', 'A\nC\n'),
692
('tree/f3', 'A\nC\n'),
694
tree.commit('C', rev_id='C')
695
# Revert back to A to build the other history.
696
tree.set_last_revision('A')
697
tree.branch.set_last_revision_info(1, 'A')
698
self.build_tree_contents([('tree/f1', 'A\nB\n'),
700
('tree/f3', 'A\nB\n'),
702
tree.commit('B', rev_id='B')
703
tree.set_parent_ids(['B', 'C'])
704
self.build_tree_contents([('tree/f1', 'A\nB\n'),
705
('tree/f2', 'A\nC\n'),
706
('tree/f3', 'A\nB\nC\n'),
708
tree.commit('D', rev_id='D')
710
# Switch to a read lock for this tree.
711
# We still have addCleanup(unlock)
716
def test_tree_with_single_merge(self):
717
"""Make sure the tree layout is correct."""
718
tree = self.create_tree_with_single_merge()
719
rev_A_tree = tree.branch.repository.revision_tree('A')
720
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
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
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)
752
def assertAllRevisionsForFileID(self, tree, file_id, revisions):
753
"""Make sure _filter_revisions_touching_file_id returns the right values.
755
Get the return value from _filter_revisions_touching_file_id and make
756
sure they are correct.
758
# The api for _get_revisions_touching_file_id is a little crazy,
759
# So we do the setup here.
760
mainline = tree.branch.revision_history()
761
mainline.insert(0, None)
762
revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
763
view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
765
actual_revs = log._filter_revisions_touching_file_id(
769
list(view_revs_iter))
770
self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
772
def test_file_id_f1(self):
773
tree = self.create_tree_with_single_merge()
774
# f1 should be marked as modified by revisions A and B
775
self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
777
def test_file_id_f2(self):
778
tree = self.create_tree_with_single_merge()
779
# f2 should be marked as modified by revisions A, C, and D
780
# because D merged the changes from C.
781
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
783
def test_file_id_f3(self):
784
tree = self.create_tree_with_single_merge()
785
# f3 should be marked as modified by revisions A, B, C, and D
786
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
789
class TestShowChangedRevisions(TestCaseWithTransport):
791
def test_show_changed_revisions_verbose(self):
792
tree = self.make_branch_and_tree('tree_a')
793
self.build_tree(['tree_a/foo'])
795
tree.commit('bar', rev_id='bar-id')
796
s = self.make_utf8_encoded_stringio()
797
log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
798
self.assertContainsRe(s.getvalue(), 'bar')
799
self.assertNotContainsRe(s.getvalue(), 'foo')
802
class TestLogFormatter(TestCase):
804
def test_short_committer(self):
805
rev = Revision('a-id')
806
rev.committer = 'John Doe <jdoe@example.com>'
807
lf = LogFormatter(None)
808
self.assertEqual('John Doe', lf.short_committer(rev))
810
def test_short_author(self):
811
rev = Revision('a-id')
812
rev.committer = 'John Doe <jdoe@example.com>'
813
lf = LogFormatter(None)
814
self.assertEqual('John Doe', lf.short_author(rev))
815
rev.properties['author'] = 'John Smith <jsmith@example.com>'
816
self.assertEqual('John Smith', lf.short_author(rev))