97
118
eq(len(lf.logs), 2)
98
119
self.log('log entries:')
99
120
for logentry in lf.logs:
100
self.log('%4d %s' % (logentry.revno, logentry.rev.message))
121
self.log('%4s %s' % (logentry.revno, logentry.rev.message))
102
123
# first one is most recent
103
124
logentry = lf.logs[0]
104
eq(logentry.revno, 2)
125
eq(logentry.revno, '2')
105
126
eq(logentry.rev.message, 'add one file')
106
127
d = logentry.delta
107
128
self.log('log 2 delta: %r' % d)
108
# self.checkDelta(d, added=['hello'])
129
self.checkDelta(d, added=['hello'])
131
# commit a log message with control characters
132
msg = "All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
133
self.log("original commit message: %r", msg)
136
show_log(b, lf, verbose=True)
137
committed_msg = lf.logs[0].rev.message
138
self.log("escaped commit message: %r", committed_msg)
139
self.assert_(msg != committed_msg)
140
self.assert_(len(committed_msg) > len(msg))
142
# Check that log message with only XML-valid characters isn't
143
# escaped. As ElementTree apparently does some kind of
144
# newline conversion, neither LF (\x0A) nor CR (\x0D) are
145
# included in the test commit message, even though they are
146
# valid XML 1.0 characters.
147
msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
148
self.log("original commit message: %r", msg)
151
show_log(b, lf, verbose=True)
152
committed_msg = lf.logs[0].rev.message
153
self.log("escaped commit message: %r", committed_msg)
154
self.assert_(msg == committed_msg)
156
def test_deltas_in_merge_revisions(self):
157
"""Check deltas created for both mainline and merge revisions"""
158
eq = self.assertEquals
159
wt = self.make_branch_and_tree('parent')
160
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
163
wt.commit(message='add file1 and file2')
164
self.run_bzr('branch parent child')
165
os.unlink('child/file1')
166
print >> file('child/file2', 'wb'), 'hello'
167
self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
170
self.run_bzr('merge ../child')
171
wt.commit('merge child branch')
175
lf.supports_merge_revisions = True
176
show_log(b, lf, verbose=True)
178
logentry = lf.logs[0]
179
eq(logentry.revno, '2')
180
eq(logentry.rev.message, 'merge child branch')
182
self.checkDelta(d, removed=['file1'], modified=['file2'])
183
logentry = lf.logs[1]
184
eq(logentry.revno, '1.1.1')
185
eq(logentry.rev.message, 'remove file1 and modify file2')
187
self.checkDelta(d, removed=['file1'], modified=['file2'])
188
logentry = lf.logs[2]
189
eq(logentry.revno, '1')
190
eq(logentry.rev.message, 'add file1 and file2')
192
self.checkDelta(d, added=['file1', 'file2'])
195
def make_commits_with_trailing_newlines(wt):
196
"""Helper method for LogFormatter tests"""
199
open('a', 'wb').write('hello moto\n')
201
wt.commit('simple log message', rev_id='a1'
202
, timestamp=1132586655.459960938, timezone=-6*3600
203
, committer='Joe Foo <joe@foo.com>')
204
open('b', 'wb').write('goodbye\n')
206
wt.commit('multiline\nlog\nmessage\n', rev_id='a2'
207
, timestamp=1132586842.411175966, timezone=-6*3600
208
, committer='Joe Foo <joe@foo.com>')
210
open('c', 'wb').write('just another manic monday\n')
212
wt.commit('single line with trailing newline\n', rev_id='a3'
213
, timestamp=1132587176.835228920, timezone=-6*3600
214
, committer = 'Joe Foo <joe@foo.com>')
218
class TestShortLogFormatter(TestCaseWithTransport):
220
def test_trailing_newlines(self):
221
wt = self.make_branch_and_tree('.')
222
b = make_commits_with_trailing_newlines(wt)
224
lf = ShortLogFormatter(to_file=sio)
226
self.assertEquals(sio.getvalue(), """\
227
3 Joe Foo\t2005-11-21
228
single line with trailing newline
230
2 Joe Foo\t2005-11-21
235
1 Joe Foo\t2005-11-21
241
class TestLongLogFormatter(TestCaseWithTransport):
243
def normalize_log(self,log):
244
"""Replaces the variable lines of logs with fixed lines"""
245
committer = 'committer: Lorem Ipsum <test@example.com>'
246
lines = log.splitlines(True)
247
for idx,line in enumerate(lines):
248
stripped_line = line.lstrip()
249
indent = ' ' * (len(line) - len(stripped_line))
250
if stripped_line.startswith('committer:'):
251
lines[idx] = indent + committer + '\n'
252
if stripped_line.startswith('timestamp:'):
253
lines[idx] = indent + 'timestamp: Just now\n'
254
return ''.join(lines)
256
def test_verbose_log(self):
257
"""Verbose log includes changed files
261
wt = self.make_branch_and_tree('.')
263
self.build_tree(['a'])
265
# XXX: why does a longer nick show up?
266
b.nick = 'test_verbose_log'
267
wt.commit(message='add a',
268
timestamp=1132711707,
270
committer='Lorem Ipsum <test@example.com>')
271
logfile = file('out.tmp', 'w+')
272
formatter = LongLogFormatter(to_file=logfile)
273
show_log(b, formatter, verbose=True)
276
log_contents = logfile.read()
277
self.assertEqualDiff(log_contents, '''\
278
------------------------------------------------------------
280
committer: Lorem Ipsum <test@example.com>
281
branch nick: test_verbose_log
282
timestamp: Wed 2005-11-23 12:08:27 +1000
289
def test_merges_are_indented_by_level(self):
290
wt = self.make_branch_and_tree('parent')
291
wt.commit('first post')
292
self.run_bzr('branch parent child')
293
self.run_bzr(['commit', '-m', 'branch 1', '--unchanged', 'child'])
294
self.run_bzr('branch child smallerchild')
295
self.run_bzr(['commit', '-m', 'branch 2', '--unchanged',
298
self.run_bzr('merge ../smallerchild')
299
self.run_bzr(['commit', '-m', 'merge branch 2'])
300
os.chdir('../parent')
301
self.run_bzr('merge ../child')
302
wt.commit('merge branch 1')
305
lf = LongLogFormatter(to_file=sio)
306
show_log(b, lf, verbose=True)
307
log = self.normalize_log(sio.getvalue())
308
self.assertEqualDiff("""\
309
------------------------------------------------------------
311
committer: Lorem Ipsum <test@example.com>
316
------------------------------------------------------------
318
committer: Lorem Ipsum <test@example.com>
323
------------------------------------------------------------
325
committer: Lorem Ipsum <test@example.com>
326
branch nick: smallerchild
330
------------------------------------------------------------
332
committer: Lorem Ipsum <test@example.com>
337
------------------------------------------------------------
339
committer: Lorem Ipsum <test@example.com>
346
def test_verbose_merge_revisions_contain_deltas(self):
347
wt = self.make_branch_and_tree('parent')
348
self.build_tree(['parent/f1', 'parent/f2'])
350
wt.commit('first post')
351
self.run_bzr('branch parent child')
352
os.unlink('child/f1')
353
print >> file('child/f2', 'wb'), 'hello'
354
self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
357
self.run_bzr('merge ../child')
358
wt.commit('merge branch 1')
361
lf = LongLogFormatter(to_file=sio)
362
show_log(b, lf, verbose=True)
363
log = self.normalize_log(sio.getvalue())
364
self.assertEqualDiff("""\
365
------------------------------------------------------------
367
committer: Lorem Ipsum <test@example.com>
376
------------------------------------------------------------
378
committer: Lorem Ipsum <test@example.com>
382
removed f1 and modified f2
387
------------------------------------------------------------
389
committer: Lorem Ipsum <test@example.com>
399
def test_trailing_newlines(self):
400
wt = self.make_branch_and_tree('.')
401
b = make_commits_with_trailing_newlines(wt)
403
lf = LongLogFormatter(to_file=sio)
405
self.assertEqualDiff(sio.getvalue(), """\
406
------------------------------------------------------------
408
committer: Joe Foo <joe@foo.com>
410
timestamp: Mon 2005-11-21 09:32:56 -0600
412
single line with trailing newline
413
------------------------------------------------------------
415
committer: Joe Foo <joe@foo.com>
417
timestamp: Mon 2005-11-21 09:27:22 -0600
422
------------------------------------------------------------
424
committer: Joe Foo <joe@foo.com>
426
timestamp: Mon 2005-11-21 09:24:15 -0600
431
def test_author_in_log(self):
432
"""Log includes the author name if it's set in
433
the revision properties
435
wt = self.make_branch_and_tree('.')
437
self.build_tree(['a'])
439
b.nick = 'test_author_log'
440
wt.commit(message='add a',
441
timestamp=1132711707,
443
committer='Lorem Ipsum <test@example.com>',
444
author='John Doe <jdoe@example.com>')
446
formatter = LongLogFormatter(to_file=sio)
447
show_log(b, formatter)
448
self.assertEqualDiff(sio.getvalue(), '''\
449
------------------------------------------------------------
451
committer: Lorem Ipsum <test@example.com>
452
author: John Doe <jdoe@example.com>
453
branch nick: test_author_log
454
timestamp: Wed 2005-11-23 12:08:27 +1000
461
class TestLineLogFormatter(TestCaseWithTransport):
463
def test_line_log(self):
464
"""Line log should show revno
468
wt = self.make_branch_and_tree('.')
470
self.build_tree(['a'])
472
b.nick = 'test-line-log'
473
wt.commit(message='add a',
474
timestamp=1132711707,
476
committer='Line-Log-Formatter Tester <test@line.log>')
477
logfile = file('out.tmp', 'w+')
478
formatter = LineLogFormatter(to_file=logfile)
479
show_log(b, formatter)
482
log_contents = logfile.read()
483
self.assertEqualDiff(log_contents, '1: Line-Log-Formatte... 2005-11-23 add a\n')
485
def test_short_log_with_merges(self):
486
wt = self.make_branch_and_memory_tree('.')
490
wt.commit('rev-1', rev_id='rev-1',
491
timestamp=1132586655, timezone=36000,
492
committer='Joe Foo <joe@foo.com>')
493
wt.commit('rev-merged', rev_id='rev-2a',
494
timestamp=1132586700, timezone=36000,
495
committer='Joe Foo <joe@foo.com>')
496
wt.set_parent_ids(['rev-1', 'rev-2a'])
497
wt.branch.set_last_revision_info(1, 'rev-1')
498
wt.commit('rev-2', rev_id='rev-2b',
499
timestamp=1132586800, timezone=36000,
500
committer='Joe Foo <joe@foo.com>')
502
formatter = ShortLogFormatter(to_file=logfile)
503
show_log(wt.branch, formatter)
505
self.assertEqualDiff("""\
506
2 Joe Foo\t2005-11-22 [merge]
509
1 Joe Foo\t2005-11-22
512
""", logfile.getvalue())
516
def test_trailing_newlines(self):
517
wt = self.make_branch_and_tree('.')
518
b = make_commits_with_trailing_newlines(wt)
520
lf = LineLogFormatter(to_file=sio)
522
self.assertEqualDiff(sio.getvalue(), """\
523
3: Joe Foo 2005-11-21 single line with trailing newline
524
2: Joe Foo 2005-11-21 multiline
525
1: Joe Foo 2005-11-21 simple log message
529
class TestGetViewRevisions(TestCaseWithTransport):
531
def make_tree_with_commits(self):
532
"""Create a tree with well-known revision ids"""
533
wt = self.make_branch_and_tree('tree1')
534
wt.commit('commit one', rev_id='1')
535
wt.commit('commit two', rev_id='2')
536
wt.commit('commit three', rev_id='3')
537
mainline_revs = [None, '1', '2', '3']
538
rev_nos = {'1': 1, '2': 2, '3': 3}
539
return mainline_revs, rev_nos, wt
541
def make_tree_with_merges(self):
542
"""Create a tree with well-known revision ids and a merge"""
543
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
544
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
545
tree2.commit('four-a', rev_id='4a')
546
wt.merge_from_branch(tree2.branch)
547
wt.commit('four-b', rev_id='4b')
548
mainline_revs.append('4b')
551
return mainline_revs, rev_nos, wt
553
def make_tree_with_many_merges(self):
554
"""Create a tree with well-known revision ids"""
555
wt = self.make_branch_and_tree('tree1')
556
wt.commit('commit one', rev_id='1')
557
wt.commit('commit two', rev_id='2')
558
tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
559
tree3.commit('commit three a', rev_id='3a')
560
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
561
tree2.merge_from_branch(tree3.branch)
562
tree2.commit('commit three b', rev_id='3b')
563
wt.merge_from_branch(tree2.branch)
564
wt.commit('commit three c', rev_id='3c')
565
tree2.commit('four-a', rev_id='4a')
566
wt.merge_from_branch(tree2.branch)
567
wt.commit('four-b', rev_id='4b')
568
mainline_revs = [None, '1', '2', '3c', '4b']
569
rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
570
full_rev_nos_for_reference = {
573
'3a': '2.2.1', #first commit tree 3
574
'3b': '2.1.1', # first commit tree 2
575
'3c': '3', #merges 3b to main
576
'4a': '2.1.2', # second commit tree 2
577
'4b': '4', # merges 4a to main
579
return mainline_revs, rev_nos, wt
581
def test_get_view_revisions_forward(self):
582
"""Test the get_view_revisions method"""
583
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
584
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
586
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
588
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
589
'forward', include_merges=False))
590
self.assertEqual(revisions, revisions2)
592
def test_get_view_revisions_reverse(self):
593
"""Test the get_view_revisions with reverse"""
594
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
595
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
597
self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
599
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
600
'reverse', include_merges=False))
601
self.assertEqual(revisions, revisions2)
603
def test_get_view_revisions_merge(self):
604
"""Test get_view_revisions when there are merges"""
605
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
606
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
608
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
609
('4b', '4', 0), ('4a', '3.1.1', 1)],
611
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
612
'forward', include_merges=False))
613
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
617
def test_get_view_revisions_merge_reverse(self):
618
"""Test get_view_revisions in reverse when there are merges"""
619
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
620
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
622
self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
623
('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
625
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
626
'reverse', include_merges=False))
627
self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
631
def test_get_view_revisions_merge2(self):
632
"""Test get_view_revisions when there are merges"""
633
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
634
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
636
expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
637
('3a', '2.2.1', 1), ('3b', '2.1.1', 1), ('4b', '4', 0),
639
self.assertEqual(expected, revisions)
640
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
641
'forward', include_merges=False))
642
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
647
class TestGetRevisionsTouchingFileID(TestCaseWithTransport):
649
def create_tree_with_single_merge(self):
650
"""Create a branch with a moderate layout.
652
The revision graph looks like:
660
In this graph, A introduced files f1 and f2 and f3.
661
B modifies f1 and f3, and C modifies f2 and f3.
662
D merges the changes from B and C and resolves the conflict for f3.
664
# TODO: jam 20070218 This seems like it could really be done
665
# with make_branch_and_memory_tree() if we could just
666
# create the content of those files.
667
# TODO: jam 20070218 Another alternative is that we would really
668
# like to only create this tree 1 time for all tests that
669
# use it. Since 'log' only uses the tree in a readonly
670
# fashion, it seems a shame to regenerate an identical
671
# tree for each test.
672
tree = self.make_branch_and_tree('tree')
674
self.addCleanup(tree.unlock)
676
self.build_tree_contents([('tree/f1', 'A\n'),
680
tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
681
tree.commit('A', rev_id='A')
683
self.build_tree_contents([('tree/f2', 'A\nC\n'),
684
('tree/f3', 'A\nC\n'),
686
tree.commit('C', rev_id='C')
687
# Revert back to A to build the other history.
688
tree.set_last_revision('A')
689
tree.branch.set_last_revision_info(1, 'A')
690
self.build_tree_contents([('tree/f1', 'A\nB\n'),
692
('tree/f3', 'A\nB\n'),
694
tree.commit('B', rev_id='B')
695
tree.set_parent_ids(['B', 'C'])
696
self.build_tree_contents([('tree/f1', 'A\nB\n'),
697
('tree/f2', 'A\nC\n'),
698
('tree/f3', 'A\nB\nC\n'),
700
tree.commit('D', rev_id='D')
702
# Switch to a read lock for this tree.
703
# We still have addCleanup(unlock)
708
def test_tree_with_single_merge(self):
709
"""Make sure the tree layout is correct."""
710
tree = self.create_tree_with_single_merge()
711
rev_A_tree = tree.branch.repository.revision_tree('A')
712
rev_B_tree = tree.branch.repository.revision_tree('B')
714
f1_changed = (u'f1', 'f1-id', 'file', True, False)
715
f2_changed = (u'f2', 'f2-id', 'file', True, False)
716
f3_changed = (u'f3', 'f3-id', 'file', True, False)
718
delta = rev_B_tree.changes_from(rev_A_tree)
719
self.assertEqual([f1_changed, f3_changed], delta.modified)
720
self.assertEqual([], delta.renamed)
721
self.assertEqual([], delta.added)
722
self.assertEqual([], delta.removed)
724
rev_C_tree = tree.branch.repository.revision_tree('C')
725
delta = rev_C_tree.changes_from(rev_A_tree)
726
self.assertEqual([f2_changed, f3_changed], delta.modified)
727
self.assertEqual([], delta.renamed)
728
self.assertEqual([], delta.added)
729
self.assertEqual([], delta.removed)
731
rev_D_tree = tree.branch.repository.revision_tree('D')
732
delta = rev_D_tree.changes_from(rev_B_tree)
733
self.assertEqual([f2_changed, f3_changed], delta.modified)
734
self.assertEqual([], delta.renamed)
735
self.assertEqual([], delta.added)
736
self.assertEqual([], delta.removed)
738
delta = rev_D_tree.changes_from(rev_C_tree)
739
self.assertEqual([f1_changed, f3_changed], delta.modified)
740
self.assertEqual([], delta.renamed)
741
self.assertEqual([], delta.added)
742
self.assertEqual([], delta.removed)
744
def assertAllRevisionsForFileID(self, tree, file_id, revisions):
745
"""Make sure _filter_revisions_touching_file_id returns the right values.
747
Get the return value from _filter_revisions_touching_file_id and make
748
sure they are correct.
750
# The api for _get_revisions_touching_file_id is a little crazy,
751
# So we do the setup here.
752
mainline = tree.branch.revision_history()
753
mainline.insert(0, None)
754
revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
755
view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
757
actual_revs = log._filter_revisions_touching_file_id(
761
list(view_revs_iter))
762
self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
764
def test_file_id_f1(self):
765
tree = self.create_tree_with_single_merge()
766
# f1 should be marked as modified by revisions A and B
767
self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
769
def test_file_id_f2(self):
770
tree = self.create_tree_with_single_merge()
771
# f2 should be marked as modified by revisions A, C, and D
772
# because D merged the changes from C.
773
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
775
def test_file_id_f3(self):
776
tree = self.create_tree_with_single_merge()
777
# f3 should be marked as modified by revisions A, B, C, and D
778
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
781
class TestShowChangedRevisions(TestCaseWithTransport):
783
def test_show_changed_revisions_verbose(self):
784
tree = self.make_branch_and_tree('tree_a')
785
self.build_tree(['tree_a/foo'])
787
tree.commit('bar', rev_id='bar-id')
789
log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
790
self.assertContainsRe(s.getvalue(), 'bar')
791
self.assertNotContainsRe(s.getvalue(), 'foo')