122
123
eq(len(lf.logs), 2)
123
124
self.log('log entries:')
124
125
for logentry in lf.logs:
125
self.log('%4d %s' % (logentry.revno, logentry.rev.message))
126
self.log('%4s %s' % (logentry.revno, logentry.rev.message))
127
128
# first one is most recent
128
129
logentry = lf.logs[0]
129
eq(logentry.revno, 2)
130
eq(logentry.revno, '2')
130
131
eq(logentry.rev.message, 'add one file')
131
132
d = logentry.delta
132
133
self.log('log 2 delta: %r' % d)
133
134
# self.checkDelta(d, added=['hello'])
136
# commit a log message with control characters
137
msg = "All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
138
self.log("original commit message: %r", msg)
141
show_log(b, lf, verbose=True)
142
committed_msg = lf.logs[0].rev.message
143
self.log("escaped commit message: %r", committed_msg)
144
self.assert_(msg != committed_msg)
145
self.assert_(len(committed_msg) > len(msg))
147
# Check that log message with only XML-valid characters isn't
148
# escaped. As ElementTree apparently does some kind of
149
# newline conversion, neither LF (\x0A) nor CR (\x0D) are
150
# included in the test commit message, even though they are
151
# valid XML 1.0 characters.
152
msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
153
self.log("original commit message: %r", msg)
156
show_log(b, lf, verbose=True)
157
committed_msg = lf.logs[0].rev.message
158
self.log("escaped commit message: %r", committed_msg)
159
self.assert_(msg == committed_msg)
161
def test_trailing_newlines(self):
162
wt = self.make_branch_and_tree('.')
165
open('a', 'wb').write('hello moto\n')
167
wt.commit('simple log message', rev_id='a1'
168
, timestamp=1132586655.459960938, timezone=-6*3600
169
, committer='Joe Foo <joe@foo.com>')
170
open('b', 'wb').write('goodbye\n')
172
wt.commit('multiline\nlog\nmessage\n', rev_id='a2'
173
, timestamp=1132586842.411175966, timezone=-6*3600
174
, committer='Joe Foo <joe@foo.com>')
176
open('c', 'wb').write('just another manic monday\n')
178
wt.commit('single line with trailing newline\n', rev_id='a3'
179
, timestamp=1132587176.835228920, timezone=-6*3600
180
, committer = 'Joe Foo <joe@foo.com>')
183
lf = ShortLogFormatter(to_file=sio)
185
self.assertEquals(sio.getvalue(), """\
186
3 Joe Foo\t2005-11-21
187
single line with trailing newline
189
2 Joe Foo\t2005-11-21
194
1 Joe Foo\t2005-11-21
200
lf = LongLogFormatter(to_file=sio)
202
self.assertEquals(sio.getvalue(), """\
203
------------------------------------------------------------
205
committer: Joe Foo <joe@foo.com>
207
timestamp: Mon 2005-11-21 09:32:56 -0600
209
single line with trailing newline
210
------------------------------------------------------------
212
committer: Joe Foo <joe@foo.com>
214
timestamp: Mon 2005-11-21 09:27:22 -0600
219
------------------------------------------------------------
221
committer: Joe Foo <joe@foo.com>
223
timestamp: Mon 2005-11-21 09:24:15 -0600
228
def test_verbose_log(self):
229
"""Verbose log includes changed files
233
wt = self.make_branch_and_tree('.')
235
self.build_tree(['a'])
237
# XXX: why does a longer nick show up?
238
b.nick = 'test_verbose_log'
239
wt.commit(message='add a',
240
timestamp=1132711707,
242
committer='Lorem Ipsum <test@example.com>')
243
logfile = file('out.tmp', 'w+')
244
formatter = LongLogFormatter(to_file=logfile)
245
show_log(b, formatter, verbose=True)
248
log_contents = logfile.read()
249
self.assertEqualDiff(log_contents, '''\
250
------------------------------------------------------------
252
committer: Lorem Ipsum <test@example.com>
253
branch nick: test_verbose_log
254
timestamp: Wed 2005-11-23 12:08:27 +1000
261
def test_line_log(self):
262
"""Line log should show revno
266
wt = self.make_branch_and_tree('.')
268
self.build_tree(['a'])
270
b.nick = 'test-line-log'
271
wt.commit(message='add a',
272
timestamp=1132711707,
274
committer='Line-Log-Formatter Tester <test@line.log>')
275
logfile = file('out.tmp', 'w+')
276
formatter = LineLogFormatter(to_file=logfile)
277
show_log(b, formatter)
280
log_contents = logfile.read()
281
self.assertEqualDiff(log_contents, '1: Line-Log-Formatte... 2005-11-23 add a\n')
283
def test_short_log_with_merges(self):
284
wt = self.make_branch_and_memory_tree('.')
288
wt.commit('rev-1', rev_id='rev-1',
289
timestamp=1132586655, timezone=36000,
290
committer='Joe Foo <joe@foo.com>')
291
wt.commit('rev-merged', rev_id='rev-2a',
292
timestamp=1132586700, timezone=36000,
293
committer='Joe Foo <joe@foo.com>')
294
wt.set_parent_ids(['rev-1', 'rev-2a'])
295
wt.branch.set_last_revision_info(1, 'rev-1')
296
wt.commit('rev-2', rev_id='rev-2b',
297
timestamp=1132586800, timezone=36000,
298
committer='Joe Foo <joe@foo.com>')
300
formatter = ShortLogFormatter(to_file=logfile)
301
show_log(wt.branch, formatter)
303
self.assertEqualDiff("""\
304
2 Joe Foo\t2005-11-22 [merge]
307
1 Joe Foo\t2005-11-22
310
""", logfile.getvalue())
314
def make_tree_with_commits(self):
315
"""Create a tree with well-known revision ids"""
316
wt = self.make_branch_and_tree('tree1')
317
wt.commit('commit one', rev_id='1')
318
wt.commit('commit two', rev_id='2')
319
wt.commit('commit three', rev_id='3')
320
mainline_revs = [None, '1', '2', '3']
321
rev_nos = {'1': 1, '2': 2, '3': 3}
322
return mainline_revs, rev_nos, wt
324
def make_tree_with_merges(self):
325
"""Create a tree with well-known revision ids and a merge"""
326
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
327
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
328
tree2.commit('four-a', rev_id='4a')
329
wt.merge_from_branch(tree2.branch)
330
wt.commit('four-b', rev_id='4b')
331
mainline_revs.append('4b')
334
return mainline_revs, rev_nos, wt
336
def make_tree_with_many_merges(self):
337
"""Create a tree with well-known revision ids"""
338
wt = self.make_branch_and_tree('tree1')
339
wt.commit('commit one', rev_id='1')
340
wt.commit('commit two', rev_id='2')
341
tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
342
tree3.commit('commit three a', rev_id='3a')
343
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
344
tree2.merge_from_branch(tree3.branch)
345
tree2.commit('commit three b', rev_id='3b')
346
wt.merge_from_branch(tree2.branch)
347
wt.commit('commit three c', rev_id='3c')
348
tree2.commit('four-a', rev_id='4a')
349
wt.merge_from_branch(tree2.branch)
350
wt.commit('four-b', rev_id='4b')
351
mainline_revs = [None, '1', '2', '3c', '4b']
352
rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
353
full_rev_nos_for_reference = {
356
'3a': '2.2.1', #first commit tree 3
357
'3b': '2.1.1', # first commit tree 2
358
'3c': '3', #merges 3b to main
359
'4a': '2.1.2', # second commit tree 2
360
'4b': '4', # merges 4a to main
362
return mainline_revs, rev_nos, wt
364
def test_get_view_revisions_forward(self):
365
"""Test the get_view_revisions method"""
366
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
367
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
369
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
371
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
372
'forward', include_merges=False))
373
self.assertEqual(revisions, revisions2)
375
def test_get_view_revisions_reverse(self):
376
"""Test the get_view_revisions with reverse"""
377
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
378
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
380
self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
382
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
383
'reverse', include_merges=False))
384
self.assertEqual(revisions, revisions2)
386
def test_get_view_revisions_merge(self):
387
"""Test get_view_revisions when there are merges"""
388
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
389
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
391
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
392
('4b', '4', 0), ('4a', '3.1.1', 1)],
394
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
395
'forward', include_merges=False))
396
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
400
def test_get_view_revisions_merge_reverse(self):
401
"""Test get_view_revisions in reverse when there are merges"""
402
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
403
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
405
self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
406
('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
408
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
409
'reverse', include_merges=False))
410
self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
414
def test_get_view_revisions_merge2(self):
415
"""Test get_view_revisions when there are merges"""
416
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
417
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
419
expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
420
('3a', '2.2.1', 1), ('3b', '2.1.1', 1), ('4b', '4', 0),
422
self.assertEqual(expected, revisions)
423
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
424
'forward', include_merges=False))
425
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
430
class TestGetRevisionsTouchingFileID(TestCaseWithTransport):
432
def create_tree_with_single_merge(self):
433
"""Create a branch with a moderate layout.
435
The revision graph looks like:
443
In this graph, A introduced files f1 and f2 and f3.
444
B modifies f1 and f3, and C modifies f2 and f3.
445
D merges the changes from B and C and resolves the conflict for f3.
447
# TODO: jam 20070218 This seems like it could really be done
448
# with make_branch_and_memory_tree() if we could just
449
# create the content of those files.
450
# TODO: jam 20070218 Another alternative is that we would really
451
# like to only create this tree 1 time for all tests that
452
# use it. Since 'log' only uses the tree in a readonly
453
# fashion, it seems a shame to regenerate an identical
454
# tree for each test.
455
tree = self.make_branch_and_tree('tree')
457
self.addCleanup(tree.unlock)
459
self.build_tree_contents([('tree/f1', 'A\n'),
463
tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
464
tree.commit('A', rev_id='A')
466
self.build_tree_contents([('tree/f2', 'A\nC\n'),
467
('tree/f3', 'A\nC\n'),
469
tree.commit('C', rev_id='C')
470
# Revert back to A to build the other history.
471
tree.set_last_revision('A')
472
tree.branch.set_last_revision_info(1, 'A')
473
self.build_tree_contents([('tree/f1', 'A\nB\n'),
475
('tree/f3', 'A\nB\n'),
477
tree.commit('B', rev_id='B')
478
tree.set_parent_ids(['B', 'C'])
479
self.build_tree_contents([('tree/f1', 'A\nB\n'),
480
('tree/f2', 'A\nC\n'),
481
('tree/f3', 'A\nB\nC\n'),
483
tree.commit('D', rev_id='D')
485
# Switch to a read lock for this tree.
486
# We still have addCleanup(unlock)
491
def test_tree_with_single_merge(self):
492
"""Make sure the tree layout is correct."""
493
tree = self.create_tree_with_single_merge()
494
rev_A_tree = tree.branch.repository.revision_tree('A')
495
rev_B_tree = tree.branch.repository.revision_tree('B')
497
f1_changed = (u'f1', 'f1-id', 'file', True, False)
498
f2_changed = (u'f2', 'f2-id', 'file', True, False)
499
f3_changed = (u'f3', 'f3-id', 'file', True, False)
501
delta = rev_B_tree.changes_from(rev_A_tree)
502
self.assertEqual([f1_changed, f3_changed], delta.modified)
503
self.assertEqual([], delta.renamed)
504
self.assertEqual([], delta.added)
505
self.assertEqual([], delta.removed)
507
rev_C_tree = tree.branch.repository.revision_tree('C')
508
delta = rev_C_tree.changes_from(rev_A_tree)
509
self.assertEqual([f2_changed, f3_changed], delta.modified)
510
self.assertEqual([], delta.renamed)
511
self.assertEqual([], delta.added)
512
self.assertEqual([], delta.removed)
514
rev_D_tree = tree.branch.repository.revision_tree('D')
515
delta = rev_D_tree.changes_from(rev_B_tree)
516
self.assertEqual([f2_changed, f3_changed], delta.modified)
517
self.assertEqual([], delta.renamed)
518
self.assertEqual([], delta.added)
519
self.assertEqual([], delta.removed)
521
delta = rev_D_tree.changes_from(rev_C_tree)
522
self.assertEqual([f1_changed, f3_changed], delta.modified)
523
self.assertEqual([], delta.renamed)
524
self.assertEqual([], delta.added)
525
self.assertEqual([], delta.removed)
527
def assertAllRevisionsForFileID(self, tree, file_id, revisions):
528
"""Make sure _get_revisions_touching_file_id returns the right values.
530
Get the return value from _get_revisions_touching_file_id and make
531
sure they are correct.
533
# The api for _get_revisions_touching_file_id is a little crazy,
534
# So we do the setup here.
535
mainline = tree.branch.revision_history()
536
mainline.insert(0, None)
537
revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
538
view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
540
actual_revs = log._get_revisions_touching_file_id(tree.branch, file_id,
543
self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
545
def test_file_id_f1(self):
546
tree = self.create_tree_with_single_merge()
547
# f1 should be marked as modified by revisions A and B
548
self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
550
def test_file_id_f2(self):
551
tree = self.create_tree_with_single_merge()
552
# f2 should be marked as modified by revisions A, C, and D
553
# because D merged the changes from C.
554
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
556
def test_file_id_f3(self):
557
tree = self.create_tree_with_single_merge()
558
# f3 should be marked as modified by revisions A, B, C, and D
559
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])