122
128
eq(len(lf.logs), 2)
123
129
self.log('log entries:')
124
130
for logentry in lf.logs:
125
self.log('%4d %s' % (logentry.revno, logentry.rev.message))
131
self.log('%4s %s' % (logentry.revno, logentry.rev.message))
127
133
# first one is most recent
128
134
logentry = lf.logs[0]
129
eq(logentry.revno, 2)
135
eq(logentry.revno, '2')
130
136
eq(logentry.rev.message, 'add one file')
131
137
d = logentry.delta
132
138
self.log('log 2 delta: %r' % d)
133
139
# self.checkDelta(d, added=['hello'])
141
# commit a log message with control characters
142
msg = "All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
143
self.log("original commit message: %r", msg)
146
show_log(b, lf, verbose=True)
147
committed_msg = lf.logs[0].rev.message
148
self.log("escaped commit message: %r", committed_msg)
149
self.assert_(msg != committed_msg)
150
self.assert_(len(committed_msg) > len(msg))
152
# Check that log message with only XML-valid characters isn't
153
# escaped. As ElementTree apparently does some kind of
154
# newline conversion, neither LF (\x0A) nor CR (\x0D) are
155
# included in the test commit message, even though they are
156
# valid XML 1.0 characters.
157
msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
158
self.log("original commit message: %r", msg)
161
show_log(b, lf, verbose=True)
162
committed_msg = lf.logs[0].rev.message
163
self.log("escaped commit message: %r", committed_msg)
164
self.assert_(msg == committed_msg)
166
def test_trailing_newlines(self):
167
wt = self.make_branch_and_tree('.')
170
open('a', 'wb').write('hello moto\n')
172
wt.commit('simple log message', rev_id='a1'
173
, timestamp=1132586655.459960938, timezone=-6*3600
174
, committer='Joe Foo <joe@foo.com>')
175
open('b', 'wb').write('goodbye\n')
177
wt.commit('multiline\nlog\nmessage\n', rev_id='a2'
178
, timestamp=1132586842.411175966, timezone=-6*3600
179
, committer='Joe Foo <joe@foo.com>')
181
open('c', 'wb').write('just another manic monday\n')
183
wt.commit('single line with trailing newline\n', rev_id='a3'
184
, timestamp=1132587176.835228920, timezone=-6*3600
185
, committer = 'Joe Foo <joe@foo.com>')
188
lf = ShortLogFormatter(to_file=sio)
190
self.assertEquals(sio.getvalue(), """\
191
3 Joe Foo\t2005-11-21
192
single line with trailing newline
194
2 Joe Foo\t2005-11-21
199
1 Joe Foo\t2005-11-21
205
lf = LongLogFormatter(to_file=sio)
207
self.assertEquals(sio.getvalue(), """\
208
------------------------------------------------------------
210
committer: Joe Foo <joe@foo.com>
212
timestamp: Mon 2005-11-21 09:32:56 -0600
214
single line with trailing newline
215
------------------------------------------------------------
217
committer: Joe Foo <joe@foo.com>
219
timestamp: Mon 2005-11-21 09:27:22 -0600
224
------------------------------------------------------------
226
committer: Joe Foo <joe@foo.com>
228
timestamp: Mon 2005-11-21 09:24:15 -0600
233
def test_verbose_log(self):
234
"""Verbose log includes changed files
238
wt = self.make_branch_and_tree('.')
240
self.build_tree(['a'])
242
# XXX: why does a longer nick show up?
243
b.nick = 'test_verbose_log'
244
wt.commit(message='add a',
245
timestamp=1132711707,
247
committer='Lorem Ipsum <test@example.com>')
248
logfile = file('out.tmp', 'w+')
249
formatter = LongLogFormatter(to_file=logfile)
250
show_log(b, formatter, verbose=True)
253
log_contents = logfile.read()
254
self.assertEqualDiff(log_contents, '''\
255
------------------------------------------------------------
257
committer: Lorem Ipsum <test@example.com>
258
branch nick: test_verbose_log
259
timestamp: Wed 2005-11-23 12:08:27 +1000
266
def test_line_log(self):
267
"""Line log should show revno
271
wt = self.make_branch_and_tree('.')
273
self.build_tree(['a'])
275
b.nick = 'test-line-log'
276
wt.commit(message='add a',
277
timestamp=1132711707,
279
committer='Line-Log-Formatter Tester <test@line.log>')
280
logfile = file('out.tmp', 'w+')
281
formatter = LineLogFormatter(to_file=logfile)
282
show_log(b, formatter)
285
log_contents = logfile.read()
286
self.assertEqualDiff(log_contents, '1: Line-Log-Formatte... 2005-11-23 add a\n')
288
def make_tree_with_commits(self):
289
"""Create a tree with well-known revision ids"""
290
wt = self.make_branch_and_tree('tree1')
291
wt.commit('commit one', rev_id='1')
292
wt.commit('commit two', rev_id='2')
293
wt.commit('commit three', rev_id='3')
294
mainline_revs = [None, '1', '2', '3']
295
rev_nos = {'1': 1, '2': 2, '3': 3}
296
return mainline_revs, rev_nos, wt
298
def make_tree_with_merges(self):
299
"""Create a tree with well-known revision ids and a merge"""
300
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
301
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
302
tree2.commit('four-a', rev_id='4a')
303
wt.merge_from_branch(tree2.branch)
304
wt.commit('four-b', rev_id='4b')
305
mainline_revs.append('4b')
308
return mainline_revs, rev_nos, wt
310
def make_tree_with_many_merges(self):
311
"""Create a tree with well-known revision ids"""
312
wt = self.make_branch_and_tree('tree1')
313
wt.commit('commit one', rev_id='1')
314
wt.commit('commit two', rev_id='2')
315
tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
316
tree3.commit('commit three a', rev_id='3a')
317
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
318
tree2.merge_from_branch(tree3.branch)
319
tree2.commit('commit three b', rev_id='3b')
320
wt.merge_from_branch(tree2.branch)
321
wt.commit('commit three c', rev_id='3c')
322
tree2.commit('four-a', rev_id='4a')
323
wt.merge_from_branch(tree2.branch)
324
wt.commit('four-b', rev_id='4b')
325
mainline_revs = [None, '1', '2', '3c', '4b']
326
rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
327
full_rev_nos_for_reference = {
330
'3a': '2.2.1', #first commit tree 3
331
'3b': '2.1.1', # first commit tree 2
332
'3c': '3', #merges 3b to main
333
'4a': '2.1.2', # second commit tree 2
334
'4b': '4', # merges 4a to main
336
return mainline_revs, rev_nos, wt
338
def test_get_view_revisions_forward(self):
339
"""Test the get_view_revisions method"""
340
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
341
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
343
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
345
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
346
'forward', include_merges=False))
347
self.assertEqual(revisions, revisions2)
349
def test_get_view_revisions_reverse(self):
350
"""Test the get_view_revisions with reverse"""
351
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
352
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
354
self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
356
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
357
'reverse', include_merges=False))
358
self.assertEqual(revisions, revisions2)
360
def test_get_view_revisions_merge(self):
361
"""Test get_view_revisions when there are merges"""
362
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
363
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
365
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
366
('4b', '4', 0), ('4a', '3.1.1', 1)],
368
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
369
'forward', include_merges=False))
370
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
374
def test_get_view_revisions_merge_reverse(self):
375
"""Test get_view_revisions in reverse when there are merges"""
376
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
377
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
379
self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
380
('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
382
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
383
'reverse', include_merges=False))
384
self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
388
def test_get_view_revisions_merge2(self):
389
"""Test get_view_revisions when there are merges"""
390
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
391
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
393
expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
394
('3a', '2.2.1', 1), ('3b', '2.1.1', 1), ('4b', '4', 0),
396
self.assertEqual(expected, revisions)
397
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
398
'forward', include_merges=False))
399
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
404
class TestGetRevisionsTouchingFileID(TestCaseWithTransport):
406
def create_tree_with_single_merge(self):
407
"""Create a branch with a moderate layout.
409
The revision graph looks like:
417
In this graph, A introduced files f1 and f2 and f3.
418
B modifies f1 and f3, and C modifies f2 and f3.
419
D merges the changes from B and C and resolves the conflict for f3.
421
# TODO: jam 20070218 This seems like it could really be done
422
# with make_branch_and_memory_tree() if we could just
423
# create the content of those files.
424
# TODO: jam 20070218 Another alternative is that we would really
425
# like to only create this tree 1 time for all tests that
426
# use it. Since 'log' only uses the tree in a readonly
427
# fashion, it seems a shame to regenerate an identical
428
# tree for each test.
429
tree = self.make_branch_and_tree('tree')
431
self.addCleanup(tree.unlock)
433
self.build_tree_contents([('tree/f1', 'A\n'),
437
tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
438
tree.commit('A', rev_id='A')
440
self.build_tree_contents([('tree/f2', 'A\nC\n'),
441
('tree/f3', 'A\nC\n'),
443
tree.commit('C', rev_id='C')
444
# Revert back to A to build the other history.
445
tree.set_last_revision('A')
446
tree.branch.set_last_revision_info(1, 'A')
447
self.build_tree_contents([('tree/f1', 'A\nB\n'),
449
('tree/f3', 'A\nB\n'),
451
tree.commit('B', rev_id='B')
452
tree.set_parent_ids(['B', 'C'])
453
self.build_tree_contents([('tree/f1', 'A\nB\n'),
454
('tree/f2', 'A\nC\n'),
455
('tree/f3', 'A\nB\nC\n'),
457
tree.commit('D', rev_id='D')
459
# Switch to a read lock for this tree.
460
# We still have addCleanup(unlock)
465
def test_tree_with_single_merge(self):
466
"""Make sure the tree layout is correct."""
467
tree = self.create_tree_with_single_merge()
468
rev_A_tree = tree.branch.repository.revision_tree('A')
469
rev_B_tree = tree.branch.repository.revision_tree('B')
471
f1_changed = (u'f1', 'f1-id', 'file', True, False)
472
f2_changed = (u'f2', 'f2-id', 'file', True, False)
473
f3_changed = (u'f3', 'f3-id', 'file', True, False)
475
delta = rev_B_tree.changes_from(rev_A_tree)
476
self.assertEqual([f1_changed, f3_changed], delta.modified)
477
self.assertEqual([], delta.renamed)
478
self.assertEqual([], delta.added)
479
self.assertEqual([], delta.removed)
481
rev_C_tree = tree.branch.repository.revision_tree('C')
482
delta = rev_C_tree.changes_from(rev_A_tree)
483
self.assertEqual([f2_changed, f3_changed], delta.modified)
484
self.assertEqual([], delta.renamed)
485
self.assertEqual([], delta.added)
486
self.assertEqual([], delta.removed)
488
rev_D_tree = tree.branch.repository.revision_tree('D')
489
delta = rev_D_tree.changes_from(rev_B_tree)
490
self.assertEqual([f2_changed, f3_changed], delta.modified)
491
self.assertEqual([], delta.renamed)
492
self.assertEqual([], delta.added)
493
self.assertEqual([], delta.removed)
495
delta = rev_D_tree.changes_from(rev_C_tree)
496
self.assertEqual([f1_changed, f3_changed], delta.modified)
497
self.assertEqual([], delta.renamed)
498
self.assertEqual([], delta.added)
499
self.assertEqual([], delta.removed)
501
def assertAllRevisionsForFileID(self, tree, file_id, revisions):
502
"""Make sure _get_revisions_touching_file_id returns the right values.
504
Get the return value from _get_revisions_touching_file_id and make
505
sure they are correct.
507
# The api for _get_revisions_touching_file_id is a little crazy,
508
# So we do the setup here.
509
mainline = tree.branch.revision_history()
510
mainline.insert(0, None)
511
revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
512
view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
514
actual_revs = log._get_revisions_touching_file_id(tree.branch, file_id,
517
self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
519
def test_file_id_f1(self):
520
tree = self.create_tree_with_single_merge()
521
# f1 should be marked as modified by revisions A and B
522
self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
524
def test_file_id_f2(self):
525
tree = self.create_tree_with_single_merge()
526
# f2 should be marked as modified by revisions A, C, and D
527
# because D merged the changes from C.
528
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
530
def test_file_id_f3(self):
531
tree = self.create_tree_with_single_merge()
532
# f3 should be marked as modified by revisions A, B, C, and D
533
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])