52
45
We should also test the LogFormatter.
57
47
def __init__(self):
58
48
super(LogCatcher, self).__init__(to_file=None)
61
def log_revision(self, revision):
62
self.logs.append(revision)
65
class TestShowLog(tests.TestCaseWithTransport):
51
def show(self, revno, rev, delta):
59
class SimpleLogTest(TestCaseWithTransport):
67
61
def checkDelta(self, delta, **kw):
68
"""Check the filenames touched by a delta are as expected.
70
Caller only have to pass in the list of files for each part, all
71
unspecified parts are considered empty (and checked as such).
62
"""Check the filenames touched by a delta are as expected."""
73
63
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
74
# By default we expect an empty list
75
64
expected = kw.get(n, [])
66
# tests are written with unix paths; fix them up for windows
68
# expected = [x.replace('/', os.sep) for x in expected]
76
70
# strip out only the path components
77
71
got = [x[0] for x in getattr(delta, n)]
78
self.assertEqual(expected, got)
80
def assertInvalidRevisonNumber(self, br, start, end):
82
self.assertRaises(errors.InvalidRevisionNumber,
84
start_revision=start, end_revision=end)
86
def test_cur_revno(self):
87
wt = self.make_branch_and_tree('.')
91
wt.commit('empty commit')
92
log.show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
94
# Since there is a single revision in the branch all the combinations
96
self.assertInvalidRevisonNumber(b, 2, 1)
97
self.assertInvalidRevisonNumber(b, 1, 2)
98
self.assertInvalidRevisonNumber(b, 0, 2)
99
self.assertInvalidRevisonNumber(b, 1, 0)
100
self.assertInvalidRevisonNumber(b, -1, 1)
101
self.assertInvalidRevisonNumber(b, 1, -1)
103
def test_empty_branch(self):
104
wt = self.make_branch_and_tree('.')
107
log.show_log(wt.branch, lf)
72
self.assertEquals(expected, got)
74
def test_cur_revno(self):
75
wt = self.make_branch_and_tree('.')
79
wt.commit('empty commit')
80
show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
81
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
82
start_revision=2, end_revision=1)
83
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
84
start_revision=1, end_revision=2)
85
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
86
start_revision=0, end_revision=2)
87
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
88
start_revision=1, end_revision=0)
89
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
90
start_revision=-1, end_revision=1)
91
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
92
start_revision=1, end_revision=-1)
94
def test_cur_revno(self):
95
wt = self.make_branch_and_tree('.')
99
wt.commit('empty commit')
100
show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
101
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
102
start_revision=2, end_revision=1)
103
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
104
start_revision=1, end_revision=2)
105
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
106
start_revision=0, end_revision=2)
107
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
108
start_revision=1, end_revision=0)
109
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
110
start_revision=-1, end_revision=1)
111
self.assertRaises(InvalidRevisionNumber, show_log, b, lf,
112
start_revision=1, end_revision=-1)
114
def test_simple_log(self):
115
eq = self.assertEquals
117
wt = self.make_branch_and_tree('.')
109
self.assertEqual([], lf.logs)
111
def test_empty_commit(self):
112
wt = self.make_branch_and_tree('.')
114
125
wt.commit('empty commit')
115
126
lf = LogCatcher()
116
log.show_log(wt.branch, lf, verbose=True)
117
self.assertEqual(1, len(lf.logs))
118
self.assertEqual('1', lf.logs[0].revno)
119
self.assertEqual('empty commit', lf.logs[0].rev.message)
120
self.checkDelta(lf.logs[0].delta)
127
show_log(b, lf, verbose=True)
129
eq(lf.logs[0].revno, 1)
130
eq(lf.logs[0].rev.message, 'empty commit')
132
self.log('log delta: %r' % d)
122
def test_simple_commit(self):
123
wt = self.make_branch_and_tree('.')
124
wt.commit('empty commit')
125
135
self.build_tree(['hello'])
127
wt.commit('add one file',
128
committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
129
u'<test@example.com>')
137
wt.commit('add one file')
140
# log using regular thing
141
show_log(b, LongLogFormatter(lf))
143
for l in lf.readlines():
146
# get log as data structure
130
147
lf = LogCatcher()
131
log.show_log(wt.branch, lf, verbose=True)
132
self.assertEqual(2, len(lf.logs))
148
show_log(b, lf, verbose=True)
150
self.log('log entries:')
151
for logentry in lf.logs:
152
self.log('%4d %s' % (logentry.revno, logentry.rev.message))
133
154
# first one is most recent
134
log_entry = lf.logs[0]
135
self.assertEqual('2', log_entry.revno)
136
self.assertEqual('add one file', log_entry.rev.message)
137
self.checkDelta(log_entry.delta, added=['hello'])
139
def test_commit_message_with_control_chars(self):
140
wt = self.make_branch_and_tree('.')
141
msg = u"All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
142
msg = msg.replace(u'\r', u'\n')
155
logentry = lf.logs[0]
156
eq(logentry.revno, 2)
157
eq(logentry.rev.message, 'add one file')
159
self.log('log 2 delta: %r' % d)
160
# self.checkDelta(d, added=['hello'])
162
# commit a log message with control characters
163
msg = "All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
164
self.log("original commit message: %r", msg)
144
166
lf = LogCatcher()
145
log.show_log(wt.branch, lf, verbose=True)
167
show_log(b, lf, verbose=True)
146
168
committed_msg = lf.logs[0].rev.message
147
self.assertNotEqual(msg, committed_msg)
148
self.assertTrue(len(committed_msg) > len(msg))
169
self.log("escaped commit message: %r", committed_msg)
170
self.assert_(msg != committed_msg)
171
self.assert_(len(committed_msg) > len(msg))
150
def test_commit_message_without_control_chars(self):
151
wt = self.make_branch_and_tree('.')
173
# Check that log message with only XML-valid characters isn't
152
174
# escaped. As ElementTree apparently does some kind of
153
175
# newline conversion, neither LF (\x0A) nor CR (\x0D) are
154
176
# included in the test commit message, even though they are
155
177
# valid XML 1.0 characters.
156
178
msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
179
self.log("original commit message: %r", msg)
158
181
lf = LogCatcher()
159
log.show_log(wt.branch, lf, verbose=True)
182
show_log(b, lf, verbose=True)
160
183
committed_msg = lf.logs[0].rev.message
161
self.assertEqual(msg, committed_msg)
163
def test_deltas_in_merge_revisions(self):
164
"""Check deltas created for both mainline and merge revisions"""
165
wt = self.make_branch_and_tree('parent')
166
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
169
wt.commit(message='add file1 and file2')
170
self.run_bzr('branch parent child')
171
os.unlink('child/file1')
172
file('child/file2', 'wb').write('hello\n')
173
self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
176
self.run_bzr('merge ../child')
177
wt.commit('merge child branch')
181
lf.supports_merge_revisions = True
182
log.show_log(b, lf, verbose=True)
184
self.assertEqual(3, len(lf.logs))
186
logentry = lf.logs[0]
187
self.assertEqual('2', logentry.revno)
188
self.assertEqual('merge child branch', logentry.rev.message)
189
self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
191
logentry = lf.logs[1]
192
self.assertEqual('1.1.1', logentry.revno)
193
self.assertEqual('remove file1 and modify file2', logentry.rev.message)
194
self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
196
logentry = lf.logs[2]
197
self.assertEqual('1', logentry.revno)
198
self.assertEqual('add file1 and file2', logentry.rev.message)
199
self.checkDelta(logentry.delta, added=['file1', 'file2'])
202
def make_commits_with_trailing_newlines(wt):
203
"""Helper method for LogFormatter tests"""
206
open('a', 'wb').write('hello moto\n')
208
wt.commit('simple log message', rev_id='a1',
209
timestamp=1132586655.459960938, timezone=-6*3600,
210
committer='Joe Foo <joe@foo.com>')
211
open('b', 'wb').write('goodbye\n')
213
wt.commit('multiline\nlog\nmessage\n', rev_id='a2',
214
timestamp=1132586842.411175966, timezone=-6*3600,
215
committer='Joe Foo <joe@foo.com>',
216
authors=['Joe Bar <joe@bar.com>'])
218
open('c', 'wb').write('just another manic monday\n')
220
wt.commit('single line with trailing newline\n', rev_id='a3',
221
timestamp=1132587176.835228920, timezone=-6*3600,
222
committer = 'Joe Foo <joe@foo.com>')
226
def normalize_log(log):
227
"""Replaces the variable lines of logs with fixed lines"""
228
author = 'author: Dolor Sit <test@example.com>'
229
committer = 'committer: Lorem Ipsum <test@example.com>'
230
lines = log.splitlines(True)
231
for idx,line in enumerate(lines):
232
stripped_line = line.lstrip()
233
indent = ' ' * (len(line) - len(stripped_line))
234
if stripped_line.startswith('author:'):
235
lines[idx] = indent + author + '\n'
236
elif stripped_line.startswith('committer:'):
237
lines[idx] = indent + committer + '\n'
238
elif stripped_line.startswith('timestamp:'):
239
lines[idx] = indent + 'timestamp: Just now\n'
240
return ''.join(lines)
243
class TestShortLogFormatter(tests.TestCaseWithTransport):
184
self.log("escaped commit message: %r", committed_msg)
185
self.assert_(msg == committed_msg)
245
187
def test_trailing_newlines(self):
246
188
wt = self.make_branch_and_tree('.')
247
b = make_commits_with_trailing_newlines(wt)
248
sio = self.make_utf8_encoded_stringio()
249
lf = log.ShortLogFormatter(to_file=sio)
251
self.assertEqualDiff("""\
191
open('a', 'wb').write('hello moto\n')
193
wt.commit('simple log message', rev_id='a1'
194
, timestamp=1132586655.459960938, timezone=-6*3600
195
, committer='Joe Foo <joe@foo.com>')
196
open('b', 'wb').write('goodbye\n')
198
wt.commit('multiline\nlog\nmessage\n', rev_id='a2'
199
, timestamp=1132586842.411175966, timezone=-6*3600
200
, committer='Joe Foo <joe@foo.com>')
202
open('c', 'wb').write('just another manic monday\n')
204
wt.commit('single line with trailing newline\n', rev_id='a3'
205
, timestamp=1132587176.835228920, timezone=-6*3600
206
, committer = 'Joe Foo <joe@foo.com>')
209
lf = ShortLogFormatter(to_file=sio)
211
self.assertEquals(sio.getvalue(), """\
252
212
3 Joe Foo\t2005-11-21
253
213
single line with trailing newline
255
2 Joe Bar\t2005-11-21
215
2 Joe Foo\t2005-11-21
260
220
1 Joe Foo\t2005-11-21
261
221
simple log message
266
def _prepare_tree_with_merges(self, with_tags=False):
267
wt = self.make_branch_and_memory_tree('.')
269
self.addCleanup(wt.unlock)
271
wt.commit('rev-1', rev_id='rev-1',
272
timestamp=1132586655, timezone=36000,
273
committer='Joe Foo <joe@foo.com>')
274
wt.commit('rev-merged', rev_id='rev-2a',
275
timestamp=1132586700, timezone=36000,
276
committer='Joe Foo <joe@foo.com>')
277
wt.set_parent_ids(['rev-1', 'rev-2a'])
278
wt.branch.set_last_revision_info(1, 'rev-1')
279
wt.commit('rev-2', rev_id='rev-2b',
280
timestamp=1132586800, timezone=36000,
281
committer='Joe Foo <joe@foo.com>')
284
branch.tags.set_tag('v0.2', 'rev-2b')
285
wt.commit('rev-3', rev_id='rev-3',
286
timestamp=1132586900, timezone=36000,
287
committer='Jane Foo <jane@foo.com>')
288
branch.tags.set_tag('v1.0rc1', 'rev-3')
289
branch.tags.set_tag('v1.0', 'rev-3')
292
def test_short_log_with_merges(self):
293
wt = self._prepare_tree_with_merges()
294
logfile = self.make_utf8_encoded_stringio()
295
formatter = log.ShortLogFormatter(to_file=logfile)
296
log.show_log(wt.branch, formatter)
297
self.assertEqualDiff("""\
298
2 Joe Foo\t2005-11-22 [merge]
301
1 Joe Foo\t2005-11-22
304
Use --levels 0 (or -n0) to see merged revisions.
308
def test_short_log_with_merges_and_range(self):
309
wt = self.make_branch_and_memory_tree('.')
311
self.addCleanup(wt.unlock)
313
wt.commit('rev-1', rev_id='rev-1',
314
timestamp=1132586655, timezone=36000,
315
committer='Joe Foo <joe@foo.com>')
316
wt.commit('rev-merged', rev_id='rev-2a',
317
timestamp=1132586700, timezone=36000,
318
committer='Joe Foo <joe@foo.com>')
319
wt.branch.set_last_revision_info(1, 'rev-1')
320
wt.set_parent_ids(['rev-1', 'rev-2a'])
321
wt.commit('rev-2b', rev_id='rev-2b',
322
timestamp=1132586800, timezone=36000,
323
committer='Joe Foo <joe@foo.com>')
324
wt.commit('rev-3a', rev_id='rev-3a',
325
timestamp=1132586800, timezone=36000,
326
committer='Joe Foo <joe@foo.com>')
327
wt.branch.set_last_revision_info(2, 'rev-2b')
328
wt.set_parent_ids(['rev-2b', 'rev-3a'])
329
wt.commit('rev-3b', rev_id='rev-3b',
330
timestamp=1132586800, timezone=36000,
331
committer='Joe Foo <joe@foo.com>')
332
logfile = self.make_utf8_encoded_stringio()
333
formatter = log.ShortLogFormatter(to_file=logfile)
334
log.show_log(wt.branch, formatter,
335
start_revision=2, end_revision=3)
336
self.assertEqualDiff("""\
337
3 Joe Foo\t2005-11-22 [merge]
340
2 Joe Foo\t2005-11-22 [merge]
343
Use --levels 0 (or -n0) to see merged revisions.
347
def test_short_log_with_tags(self):
348
wt = self._prepare_tree_with_merges(with_tags=True)
349
logfile = self.make_utf8_encoded_stringio()
350
formatter = log.ShortLogFormatter(to_file=logfile)
351
log.show_log(wt.branch, formatter)
352
self.assertEqualDiff("""\
353
3 Jane Foo\t2005-11-22 {v1.0, v1.0rc1}
356
2 Joe Foo\t2005-11-22 {v0.2} [merge]
359
1 Joe Foo\t2005-11-22
362
Use --levels 0 (or -n0) to see merged revisions.
366
def test_short_log_single_merge_revision(self):
367
wt = self.make_branch_and_memory_tree('.')
369
self.addCleanup(wt.unlock)
371
wt.commit('rev-1', rev_id='rev-1',
372
timestamp=1132586655, timezone=36000,
373
committer='Joe Foo <joe@foo.com>')
374
wt.commit('rev-merged', rev_id='rev-2a',
375
timestamp=1132586700, timezone=36000,
376
committer='Joe Foo <joe@foo.com>')
377
wt.set_parent_ids(['rev-1', 'rev-2a'])
378
wt.branch.set_last_revision_info(1, 'rev-1')
379
wt.commit('rev-2', rev_id='rev-2b',
380
timestamp=1132586800, timezone=36000,
381
committer='Joe Foo <joe@foo.com>')
382
logfile = self.make_utf8_encoded_stringio()
383
formatter = log.ShortLogFormatter(to_file=logfile)
384
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
386
rev = revspec.in_history(wtb)
387
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
388
self.assertEqualDiff("""\
389
1.1.1 Joe Foo\t2005-11-22
396
class TestShortLogFormatterWithMergeRevisions(tests.TestCaseWithTransport):
398
def test_short_merge_revs_log_with_merges(self):
399
wt = self.make_branch_and_memory_tree('.')
401
self.addCleanup(wt.unlock)
403
wt.commit('rev-1', rev_id='rev-1',
404
timestamp=1132586655, timezone=36000,
405
committer='Joe Foo <joe@foo.com>')
406
wt.commit('rev-merged', rev_id='rev-2a',
407
timestamp=1132586700, timezone=36000,
408
committer='Joe Foo <joe@foo.com>')
409
wt.set_parent_ids(['rev-1', 'rev-2a'])
410
wt.branch.set_last_revision_info(1, 'rev-1')
411
wt.commit('rev-2', rev_id='rev-2b',
412
timestamp=1132586800, timezone=36000,
413
committer='Joe Foo <joe@foo.com>')
414
logfile = self.make_utf8_encoded_stringio()
415
formatter = log.ShortLogFormatter(to_file=logfile, levels=0)
416
log.show_log(wt.branch, formatter)
417
# Note that the 1.1.1 indenting is in fact correct given that
418
# the revision numbers are right justified within 5 characters
419
# for mainline revnos and 9 characters for dotted revnos.
420
self.assertEqualDiff("""\
421
2 Joe Foo\t2005-11-22 [merge]
424
1.1.1 Joe Foo\t2005-11-22
427
1 Joe Foo\t2005-11-22
433
def test_short_merge_revs_log_single_merge_revision(self):
434
wt = self.make_branch_and_memory_tree('.')
436
self.addCleanup(wt.unlock)
438
wt.commit('rev-1', rev_id='rev-1',
439
timestamp=1132586655, timezone=36000,
440
committer='Joe Foo <joe@foo.com>')
441
wt.commit('rev-merged', rev_id='rev-2a',
442
timestamp=1132586700, timezone=36000,
443
committer='Joe Foo <joe@foo.com>')
444
wt.set_parent_ids(['rev-1', 'rev-2a'])
445
wt.branch.set_last_revision_info(1, 'rev-1')
446
wt.commit('rev-2', rev_id='rev-2b',
447
timestamp=1132586800, timezone=36000,
448
committer='Joe Foo <joe@foo.com>')
449
logfile = self.make_utf8_encoded_stringio()
450
formatter = log.ShortLogFormatter(to_file=logfile, levels=0)
451
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
453
rev = revspec.in_history(wtb)
454
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
455
self.assertEqualDiff("""\
456
1.1.1 Joe Foo\t2005-11-22
463
class TestLongLogFormatter(TestCaseWithoutPropsHandler):
226
lf = LongLogFormatter(to_file=sio)
228
self.assertEquals(sio.getvalue(), """\
229
------------------------------------------------------------
231
committer: Joe Foo <joe@foo.com>
233
timestamp: Mon 2005-11-21 09:32:56 -0600
235
single line with trailing newline
236
------------------------------------------------------------
238
committer: Joe Foo <joe@foo.com>
240
timestamp: Mon 2005-11-21 09:27:22 -0600
245
------------------------------------------------------------
247
committer: Joe Foo <joe@foo.com>
249
timestamp: Mon 2005-11-21 09:24:15 -0600
465
254
def test_verbose_log(self):
466
255
"""Verbose log includes changed files
470
wt = self.make_branch_and_tree('.')
472
self.build_tree(['a'])
474
# XXX: why does a longer nick show up?
475
b.nick = 'test_verbose_log'
476
wt.commit(message='add a',
477
timestamp=1132711707,
479
committer='Lorem Ipsum <test@example.com>')
480
logfile = file('out.tmp', 'w+')
481
formatter = log.LongLogFormatter(to_file=logfile)
482
log.show_log(b, formatter, verbose=True)
485
log_contents = logfile.read()
486
self.assertEqualDiff('''\
487
------------------------------------------------------------
489
committer: Lorem Ipsum <test@example.com>
490
branch nick: test_verbose_log
491
timestamp: Wed 2005-11-23 12:08:27 +1000
499
def test_merges_are_indented_by_level(self):
500
wt = self.make_branch_and_tree('parent')
501
wt.commit('first post')
502
self.run_bzr('branch parent child')
503
self.run_bzr(['commit', '-m', 'branch 1', '--unchanged', 'child'])
504
self.run_bzr('branch child smallerchild')
505
self.run_bzr(['commit', '-m', 'branch 2', '--unchanged',
508
self.run_bzr('merge ../smallerchild')
509
self.run_bzr(['commit', '-m', 'merge branch 2'])
510
os.chdir('../parent')
511
self.run_bzr('merge ../child')
512
wt.commit('merge branch 1')
514
sio = self.make_utf8_encoded_stringio()
515
lf = log.LongLogFormatter(to_file=sio, levels=0)
516
log.show_log(b, lf, verbose=True)
517
the_log = normalize_log(sio.getvalue())
518
self.assertEqualDiff("""\
519
------------------------------------------------------------
521
committer: Lorem Ipsum <test@example.com>
526
------------------------------------------------------------
528
committer: Lorem Ipsum <test@example.com>
533
------------------------------------------------------------
535
committer: Lorem Ipsum <test@example.com>
536
branch nick: smallerchild
540
------------------------------------------------------------
542
committer: Lorem Ipsum <test@example.com>
547
------------------------------------------------------------
549
committer: Lorem Ipsum <test@example.com>
557
def test_verbose_merge_revisions_contain_deltas(self):
558
wt = self.make_branch_and_tree('parent')
559
self.build_tree(['parent/f1', 'parent/f2'])
561
wt.commit('first post')
562
self.run_bzr('branch parent child')
563
os.unlink('child/f1')
564
file('child/f2', 'wb').write('hello\n')
565
self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
568
self.run_bzr('merge ../child')
569
wt.commit('merge branch 1')
571
sio = self.make_utf8_encoded_stringio()
572
lf = log.LongLogFormatter(to_file=sio, levels=0)
573
log.show_log(b, lf, verbose=True)
574
the_log = normalize_log(sio.getvalue())
575
self.assertEqualDiff("""\
576
------------------------------------------------------------
578
committer: Lorem Ipsum <test@example.com>
587
------------------------------------------------------------
589
committer: Lorem Ipsum <test@example.com>
593
removed f1 and modified f2
598
------------------------------------------------------------
600
committer: Lorem Ipsum <test@example.com>
611
def test_trailing_newlines(self):
612
wt = self.make_branch_and_tree('.')
613
b = make_commits_with_trailing_newlines(wt)
614
sio = self.make_utf8_encoded_stringio()
615
lf = log.LongLogFormatter(to_file=sio)
617
self.assertEqualDiff("""\
618
------------------------------------------------------------
620
committer: Joe Foo <joe@foo.com>
622
timestamp: Mon 2005-11-21 09:32:56 -0600
624
single line with trailing newline
625
------------------------------------------------------------
627
author: Joe Bar <joe@bar.com>
628
committer: Joe Foo <joe@foo.com>
630
timestamp: Mon 2005-11-21 09:27:22 -0600
635
------------------------------------------------------------
637
committer: Joe Foo <joe@foo.com>
639
timestamp: Mon 2005-11-21 09:24:15 -0600
645
def test_author_in_log(self):
646
"""Log includes the author name if it's set in
647
the revision properties
649
wt = self.make_branch_and_tree('.')
651
self.build_tree(['a'])
653
b.nick = 'test_author_log'
654
wt.commit(message='add a',
655
timestamp=1132711707,
657
committer='Lorem Ipsum <test@example.com>',
658
authors=['John Doe <jdoe@example.com>',
659
'Jane Rey <jrey@example.com>'])
661
formatter = log.LongLogFormatter(to_file=sio)
662
log.show_log(b, formatter)
663
self.assertEqualDiff('''\
664
------------------------------------------------------------
666
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
667
committer: Lorem Ipsum <test@example.com>
668
branch nick: test_author_log
669
timestamp: Wed 2005-11-23 12:08:27 +1000
675
def test_properties_in_log(self):
676
"""Log includes the custom properties returned by the registered
679
wt = self.make_branch_and_tree('.')
681
self.build_tree(['a'])
683
b.nick = 'test_properties_in_log'
684
wt.commit(message='add a',
685
timestamp=1132711707,
687
committer='Lorem Ipsum <test@example.com>',
688
authors=['John Doe <jdoe@example.com>'])
690
formatter = log.LongLogFormatter(to_file=sio)
692
def trivial_custom_prop_handler(revision):
693
return {'test_prop':'test_value'}
695
log.properties_handler_registry.register(
696
'trivial_custom_prop_handler',
697
trivial_custom_prop_handler)
698
log.show_log(b, formatter)
700
log.properties_handler_registry.remove(
701
'trivial_custom_prop_handler')
702
self.assertEqualDiff('''\
703
------------------------------------------------------------
705
test_prop: test_value
706
author: John Doe <jdoe@example.com>
707
committer: Lorem Ipsum <test@example.com>
708
branch nick: test_properties_in_log
709
timestamp: Wed 2005-11-23 12:08:27 +1000
715
def test_properties_in_short_log(self):
716
"""Log includes the custom properties returned by the registered
719
wt = self.make_branch_and_tree('.')
721
self.build_tree(['a'])
723
b.nick = 'test_properties_in_short_log'
724
wt.commit(message='add a',
725
timestamp=1132711707,
727
committer='Lorem Ipsum <test@example.com>',
728
authors=['John Doe <jdoe@example.com>'])
730
formatter = log.ShortLogFormatter(to_file=sio)
732
def trivial_custom_prop_handler(revision):
733
return {'test_prop':'test_value'}
735
log.properties_handler_registry.register(
736
'trivial_custom_prop_handler',
737
trivial_custom_prop_handler)
738
log.show_log(b, formatter)
740
log.properties_handler_registry.remove(
741
'trivial_custom_prop_handler')
742
self.assertEqualDiff('''\
743
1 John Doe\t2005-11-23
744
test_prop: test_value
750
def test_error_in_properties_handler(self):
751
"""Log includes the custom properties returned by the registered
754
wt = self.make_branch_and_tree('.')
756
self.build_tree(['a'])
758
b.nick = 'test_author_log'
759
wt.commit(message='add a',
760
timestamp=1132711707,
762
committer='Lorem Ipsum <test@example.com>',
763
authors=['John Doe <jdoe@example.com>'],
764
revprops={'first_prop':'first_value'})
766
formatter = log.LongLogFormatter(to_file=sio)
768
def trivial_custom_prop_handler(revision):
769
raise StandardError("a test error")
771
log.properties_handler_registry.register(
772
'trivial_custom_prop_handler',
773
trivial_custom_prop_handler)
774
self.assertRaises(StandardError, log.show_log, b, formatter,)
776
log.properties_handler_registry.remove(
777
'trivial_custom_prop_handler')
779
def test_properties_handler_bad_argument(self):
780
wt = self.make_branch_and_tree('.')
782
self.build_tree(['a'])
784
b.nick = 'test_author_log'
785
wt.commit(message='add a',
786
timestamp=1132711707,
788
committer='Lorem Ipsum <test@example.com>',
789
authors=['John Doe <jdoe@example.com>'],
790
revprops={'a_prop':'test_value'})
792
formatter = log.LongLogFormatter(to_file=sio)
794
def bad_argument_prop_handler(revision):
795
return {'custom_prop_name':revision.properties['a_prop']}
797
log.properties_handler_registry.register(
798
'bad_argument_prop_handler',
799
bad_argument_prop_handler)
801
self.assertRaises(AttributeError, formatter.show_properties,
804
revision = b.repository.get_revision(b.last_revision())
805
formatter.show_properties(revision, '')
806
self.assertEqualDiff('''custom_prop_name: test_value\n''',
809
log.properties_handler_registry.remove(
810
'bad_argument_prop_handler')
813
class TestLongLogFormatterWithoutMergeRevisions(TestCaseWithoutPropsHandler):
815
def test_long_verbose_log(self):
816
"""Verbose log includes changed files
820
wt = self.make_branch_and_tree('.')
822
self.build_tree(['a'])
824
# XXX: why does a longer nick show up?
825
b.nick = 'test_verbose_log'
826
wt.commit(message='add a',
827
timestamp=1132711707,
829
committer='Lorem Ipsum <test@example.com>')
830
logfile = file('out.tmp', 'w+')
831
formatter = log.LongLogFormatter(to_file=logfile, levels=1)
832
log.show_log(b, formatter, verbose=True)
835
log_contents = logfile.read()
836
self.assertEqualDiff('''\
837
------------------------------------------------------------
839
committer: Lorem Ipsum <test@example.com>
840
branch nick: test_verbose_log
841
timestamp: Wed 2005-11-23 12:08:27 +1000
849
def test_long_verbose_contain_deltas(self):
850
wt = self.make_branch_and_tree('parent')
851
self.build_tree(['parent/f1', 'parent/f2'])
853
wt.commit('first post')
854
self.run_bzr('branch parent child')
855
os.unlink('child/f1')
856
file('child/f2', 'wb').write('hello\n')
857
self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
860
self.run_bzr('merge ../child')
861
wt.commit('merge branch 1')
863
sio = self.make_utf8_encoded_stringio()
864
lf = log.LongLogFormatter(to_file=sio, levels=1)
865
log.show_log(b, lf, verbose=True)
866
the_log = normalize_log(sio.getvalue())
867
self.assertEqualDiff("""\
868
------------------------------------------------------------
870
committer: Lorem Ipsum <test@example.com>
879
------------------------------------------------------------
881
committer: Lorem Ipsum <test@example.com>
889
------------------------------------------------------------
890
Use --levels 0 (or -n0) to see merged revisions.
894
def test_long_trailing_newlines(self):
895
wt = self.make_branch_and_tree('.')
896
b = make_commits_with_trailing_newlines(wt)
897
sio = self.make_utf8_encoded_stringio()
898
lf = log.LongLogFormatter(to_file=sio, levels=1)
900
self.assertEqualDiff("""\
901
------------------------------------------------------------
903
committer: Joe Foo <joe@foo.com>
905
timestamp: Mon 2005-11-21 09:32:56 -0600
907
single line with trailing newline
908
------------------------------------------------------------
910
author: Joe Bar <joe@bar.com>
911
committer: Joe Foo <joe@foo.com>
913
timestamp: Mon 2005-11-21 09:27:22 -0600
918
------------------------------------------------------------
920
committer: Joe Foo <joe@foo.com>
922
timestamp: Mon 2005-11-21 09:24:15 -0600
928
def test_long_author_in_log(self):
929
"""Log includes the author name if it's set in
930
the revision properties
932
wt = self.make_branch_and_tree('.')
934
self.build_tree(['a'])
936
b.nick = 'test_author_log'
937
wt.commit(message='add a',
938
timestamp=1132711707,
940
committer='Lorem Ipsum <test@example.com>',
941
authors=['John Doe <jdoe@example.com>'])
943
formatter = log.LongLogFormatter(to_file=sio, levels=1)
944
log.show_log(b, formatter)
945
self.assertEqualDiff('''\
946
------------------------------------------------------------
948
author: John Doe <jdoe@example.com>
949
committer: Lorem Ipsum <test@example.com>
950
branch nick: test_author_log
951
timestamp: Wed 2005-11-23 12:08:27 +1000
957
def test_long_properties_in_log(self):
958
"""Log includes the custom properties returned by the registered
961
wt = self.make_branch_and_tree('.')
963
self.build_tree(['a'])
965
b.nick = 'test_properties_in_log'
966
wt.commit(message='add a',
967
timestamp=1132711707,
969
committer='Lorem Ipsum <test@example.com>',
970
authors=['John Doe <jdoe@example.com>'])
972
formatter = log.LongLogFormatter(to_file=sio, levels=1)
974
def trivial_custom_prop_handler(revision):
975
return {'test_prop':'test_value'}
977
log.properties_handler_registry.register(
978
'trivial_custom_prop_handler',
979
trivial_custom_prop_handler)
980
log.show_log(b, formatter)
982
log.properties_handler_registry.remove(
983
'trivial_custom_prop_handler')
984
self.assertEqualDiff('''\
985
------------------------------------------------------------
987
test_prop: test_value
988
author: John Doe <jdoe@example.com>
989
committer: Lorem Ipsum <test@example.com>
990
branch nick: test_properties_in_log
991
timestamp: Wed 2005-11-23 12:08:27 +1000
998
class TestLineLogFormatter(tests.TestCaseWithTransport):
259
wt = self.make_branch_and_tree('.')
261
self.build_tree(['a'])
263
# XXX: why does a longer nick show up?
264
b.nick = 'test_verbose_log'
265
wt.commit(message='add a',
266
timestamp=1132711707,
268
committer='Lorem Ipsum <test@example.com>')
269
logfile = file('out.tmp', 'w+')
270
formatter = LongLogFormatter(to_file=logfile)
271
show_log(b, formatter, verbose=True)
274
log_contents = logfile.read()
275
self.assertEqualDiff(log_contents, '''\
276
------------------------------------------------------------
278
committer: Lorem Ipsum <test@example.com>
279
branch nick: test_verbose_log
280
timestamp: Wed 2005-11-23 12:08:27 +1000
1000
287
def test_line_log(self):
1001
288
"""Line log should show revno
1005
wt = self.make_branch_and_tree('.')
1007
self.build_tree(['a'])
1009
b.nick = 'test-line-log'
1010
wt.commit(message='add a',
1011
timestamp=1132711707,
1013
committer='Line-Log-Formatter Tester <test@line.log>')
1014
logfile = file('out.tmp', 'w+')
1015
formatter = log.LineLogFormatter(to_file=logfile)
1016
log.show_log(b, formatter)
1019
log_contents = logfile.read()
1020
self.assertEqualDiff('1: Line-Log-Formatte... 2005-11-23 add a\n',
1023
def test_trailing_newlines(self):
1024
wt = self.make_branch_and_tree('.')
1025
b = make_commits_with_trailing_newlines(wt)
1026
sio = self.make_utf8_encoded_stringio()
1027
lf = log.LineLogFormatter(to_file=sio)
1029
self.assertEqualDiff("""\
1030
3: Joe Foo 2005-11-21 single line with trailing newline
1031
2: Joe Bar 2005-11-21 multiline
1032
1: Joe Foo 2005-11-21 simple log message
1036
def _prepare_tree_with_merges(self, with_tags=False):
1037
wt = self.make_branch_and_memory_tree('.')
1039
self.addCleanup(wt.unlock)
1041
wt.commit('rev-1', rev_id='rev-1',
1042
timestamp=1132586655, timezone=36000,
1043
committer='Joe Foo <joe@foo.com>')
1044
wt.commit('rev-merged', rev_id='rev-2a',
1045
timestamp=1132586700, timezone=36000,
1046
committer='Joe Foo <joe@foo.com>')
1047
wt.set_parent_ids(['rev-1', 'rev-2a'])
1048
wt.branch.set_last_revision_info(1, 'rev-1')
1049
wt.commit('rev-2', rev_id='rev-2b',
1050
timestamp=1132586800, timezone=36000,
1051
committer='Joe Foo <joe@foo.com>')
1054
branch.tags.set_tag('v0.2', 'rev-2b')
1055
wt.commit('rev-3', rev_id='rev-3',
1056
timestamp=1132586900, timezone=36000,
1057
committer='Jane Foo <jane@foo.com>')
1058
branch.tags.set_tag('v1.0rc1', 'rev-3')
1059
branch.tags.set_tag('v1.0', 'rev-3')
1062
def test_line_log_single_merge_revision(self):
1063
wt = self._prepare_tree_with_merges()
1064
logfile = self.make_utf8_encoded_stringio()
1065
formatter = log.LineLogFormatter(to_file=logfile)
1066
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
1068
rev = revspec.in_history(wtb)
1069
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
1070
self.assertEqualDiff("""\
1071
1.1.1: Joe Foo 2005-11-22 rev-merged
1075
def test_line_log_with_tags(self):
1076
wt = self._prepare_tree_with_merges(with_tags=True)
1077
logfile = self.make_utf8_encoded_stringio()
1078
formatter = log.LineLogFormatter(to_file=logfile)
1079
log.show_log(wt.branch, formatter)
1080
self.assertEqualDiff("""\
1081
3: Jane Foo 2005-11-22 {v1.0, v1.0rc1} rev-3
1082
2: Joe Foo 2005-11-22 [merge] {v0.2} rev-2
1083
1: Joe Foo 2005-11-22 rev-1
1087
class TestLineLogFormatterWithMergeRevisions(tests.TestCaseWithTransport):
1089
def test_line_merge_revs_log(self):
1090
"""Line log should show revno
1094
wt = self.make_branch_and_tree('.')
1096
self.build_tree(['a'])
1098
b.nick = 'test-line-log'
1099
wt.commit(message='add a',
1100
timestamp=1132711707,
1102
committer='Line-Log-Formatter Tester <test@line.log>')
1103
logfile = file('out.tmp', 'w+')
1104
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1105
log.show_log(b, formatter)
1108
log_contents = logfile.read()
1109
self.assertEqualDiff('1: Line-Log-Formatte... 2005-11-23 add a\n',
1112
def test_line_merge_revs_log_single_merge_revision(self):
1113
wt = self.make_branch_and_memory_tree('.')
1115
self.addCleanup(wt.unlock)
1117
wt.commit('rev-1', rev_id='rev-1',
1118
timestamp=1132586655, timezone=36000,
1119
committer='Joe Foo <joe@foo.com>')
1120
wt.commit('rev-merged', rev_id='rev-2a',
1121
timestamp=1132586700, timezone=36000,
1122
committer='Joe Foo <joe@foo.com>')
1123
wt.set_parent_ids(['rev-1', 'rev-2a'])
1124
wt.branch.set_last_revision_info(1, 'rev-1')
1125
wt.commit('rev-2', rev_id='rev-2b',
1126
timestamp=1132586800, timezone=36000,
1127
committer='Joe Foo <joe@foo.com>')
1128
logfile = self.make_utf8_encoded_stringio()
1129
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1130
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
1132
rev = revspec.in_history(wtb)
1133
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
1134
self.assertEqualDiff("""\
1135
1.1.1: Joe Foo 2005-11-22 rev-merged
1139
def test_line_merge_revs_log_with_merges(self):
1140
wt = self.make_branch_and_memory_tree('.')
1142
self.addCleanup(wt.unlock)
1144
wt.commit('rev-1', rev_id='rev-1',
1145
timestamp=1132586655, timezone=36000,
1146
committer='Joe Foo <joe@foo.com>')
1147
wt.commit('rev-merged', rev_id='rev-2a',
1148
timestamp=1132586700, timezone=36000,
1149
committer='Joe Foo <joe@foo.com>')
1150
wt.set_parent_ids(['rev-1', 'rev-2a'])
1151
wt.branch.set_last_revision_info(1, 'rev-1')
1152
wt.commit('rev-2', rev_id='rev-2b',
1153
timestamp=1132586800, timezone=36000,
1154
committer='Joe Foo <joe@foo.com>')
1155
logfile = self.make_utf8_encoded_stringio()
1156
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1157
log.show_log(wt.branch, formatter)
1158
self.assertEqualDiff("""\
1159
2: Joe Foo 2005-11-22 [merge] rev-2
1160
1.1.1: Joe Foo 2005-11-22 rev-merged
1161
1: Joe Foo 2005-11-22 rev-1
1165
class TestGetViewRevisions(tests.TestCaseWithTransport):
292
wt = self.make_branch_and_tree('.')
294
self.build_tree(['a'])
296
b.nick = 'test-line-log'
297
wt.commit(message='add a',
298
timestamp=1132711707,
300
committer='Line-Log-Formatter Tester <test@line.log>')
301
logfile = file('out.tmp', 'w+')
302
formatter = LineLogFormatter(to_file=logfile)
303
show_log(b, formatter)
306
log_contents = logfile.read()
307
self.assertEqualDiff(log_contents, '1: Line-Log-Formatte... 2005-11-23 add a\n')
1167
309
def make_tree_with_commits(self):
1168
310
"""Create a tree with well-known revision ids"""
1183
325
wt.commit('four-b', rev_id='4b')
1184
326
mainline_revs.append('4b')
1185
327
rev_nos['4b'] = 4
1187
328
return mainline_revs, rev_nos, wt
1189
330
def make_tree_with_many_merges(self):
1190
331
"""Create a tree with well-known revision ids"""
1191
332
wt = self.make_branch_and_tree('tree1')
1192
self.build_tree_contents([('tree1/f', '1\n')])
1193
wt.add(['f'], ['f-id'])
1194
333
wt.commit('commit one', rev_id='1')
1195
334
wt.commit('commit two', rev_id='2')
335
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
1197
336
tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
1198
self.build_tree_contents([('tree3/f', '1\n2\n3a\n')])
1199
337
tree3.commit('commit three a', rev_id='3a')
1201
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
1202
338
tree2.merge_from_branch(tree3.branch)
1203
339
tree2.commit('commit three b', rev_id='3b')
1205
340
wt.merge_from_branch(tree2.branch)
1206
341
wt.commit('commit three c', rev_id='3c')
1207
342
tree2.commit('four-a', rev_id='4a')
1209
343
wt.merge_from_branch(tree2.branch)
1210
344
wt.commit('four-b', rev_id='4b')
1212
345
mainline_revs = [None, '1', '2', '3c', '4b']
1213
rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
1214
full_rev_nos_for_reference = {
1217
'3a': '2.1.1', #first commit tree 3
1218
'3b': '2.2.1', # first commit tree 2
1219
'3c': '3', #merges 3b to main
1220
'4a': '2.2.2', # second commit tree 2
1221
'4b': '4', # merges 4a to main
346
rev_nos = {'1': 1, '2': 2, '3c': 3, '4b': 4}
1223
347
return mainline_revs, rev_nos, wt
1225
349
def test_get_view_revisions_forward(self):
1226
350
"""Test the get_view_revisions method"""
1227
351
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1229
self.addCleanup(wt.unlock)
1230
revisions = list(log.get_view_revisions(
1231
mainline_revs, rev_nos, wt.branch, 'forward'))
1232
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
1234
revisions2 = list(log.get_view_revisions(
1235
mainline_revs, rev_nos, wt.branch, 'forward',
1236
include_merges=False))
352
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
354
self.assertEqual(revisions, [('1', 1, 0), ('2', 2, 0), ('3', 3, 0)])
355
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
356
'forward', include_merges=False))
1237
357
self.assertEqual(revisions, revisions2)
1239
359
def test_get_view_revisions_reverse(self):
1240
360
"""Test the get_view_revisions with reverse"""
1241
361
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1243
self.addCleanup(wt.unlock)
1244
revisions = list(log.get_view_revisions(
1245
mainline_revs, rev_nos, wt.branch, 'reverse'))
1246
self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
1248
revisions2 = list(log.get_view_revisions(
1249
mainline_revs, rev_nos, wt.branch, 'reverse',
1250
include_merges=False))
362
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
364
self.assertEqual(revisions, [('3', 3, 0), ('2', 2, 0), ('1', 1, 0), ])
365
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
366
'reverse', include_merges=False))
1251
367
self.assertEqual(revisions, revisions2)
1253
369
def test_get_view_revisions_merge(self):
1254
370
"""Test get_view_revisions when there are merges"""
1255
371
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
1257
self.addCleanup(wt.unlock)
1258
revisions = list(log.get_view_revisions(
1259
mainline_revs, rev_nos, wt.branch, 'forward'))
1260
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
1261
('4b', '4', 0), ('4a', '3.1.1', 1)],
1263
revisions = list(log.get_view_revisions(
1264
mainline_revs, rev_nos, wt.branch, 'forward',
1265
include_merges=False))
1266
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
372
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
374
self.assertEqual(revisions, [('1', 1, 0), ('2', 2, 0), ('3', 3, 0),
375
('4b', 4, 0), ('4a', None, 1)])
376
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
377
'forward', include_merges=False))
378
self.assertEqual(revisions, [('1', 1, 0), ('2', 2, 0), ('3', 3, 0),
1270
381
def test_get_view_revisions_merge_reverse(self):
1271
382
"""Test get_view_revisions in reverse when there are merges"""
1272
383
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
1274
self.addCleanup(wt.unlock)
1275
revisions = list(log.get_view_revisions(
1276
mainline_revs, rev_nos, wt.branch, 'reverse'))
1277
self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
1278
('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
1280
revisions = list(log.get_view_revisions(
1281
mainline_revs, rev_nos, wt.branch, 'reverse',
1282
include_merges=False))
1283
self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
384
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
386
self.assertEqual(revisions, [('4b', 4, 0), ('4a', None, 1),
387
('3', 3, 0), ('2', 2, 0), ('1', 1, 0)])
388
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
389
'reverse', include_merges=False))
390
self.assertEqual(revisions, [('4b', 4, 0), ('3', 3, 0), ('2', 2, 0),
1287
393
def test_get_view_revisions_merge2(self):
1288
394
"""Test get_view_revisions when there are merges"""
1289
395
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
1291
self.addCleanup(wt.unlock)
1292
revisions = list(log.get_view_revisions(
1293
mainline_revs, rev_nos, wt.branch, 'forward'))
1294
expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
1295
('3a', '2.1.1', 1), ('3b', '2.2.1', 1), ('4b', '4', 0),
1297
self.assertEqual(expected, revisions)
1298
revisions = list(log.get_view_revisions(
1299
mainline_revs, rev_nos, wt.branch, 'forward',
1300
include_merges=False))
1301
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
1306
def test_file_id_for_range(self):
1307
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
1309
self.addCleanup(wt.unlock)
1311
def rev_from_rev_id(revid, branch):
1312
revspec = revisionspec.RevisionSpec.from_string('revid:%s' % revid)
1313
return revspec.in_history(branch)
1315
def view_revs(start_rev, end_rev, file_id, direction):
1316
revs = log.calculate_view_revisions(
1318
start_rev, # start_revision
1319
end_rev, # end_revision
1320
direction, # direction
1321
file_id, # specific_fileid
1322
True, # generate_merge_revisions
1326
rev_3a = rev_from_rev_id('3a', wt.branch)
1327
rev_4b = rev_from_rev_id('4b', wt.branch)
1328
self.assertEqual([('3c', '3', 0), ('3a', '2.1.1', 1)],
1329
view_revs(rev_3a, rev_4b, 'f-id', 'reverse'))
1330
# Note: 3c still appears before 3a here because of depth-based sorting
1331
self.assertEqual([('3c', '3', 0), ('3a', '2.1.1', 1)],
1332
view_revs(rev_3a, rev_4b, 'f-id', 'forward'))
1335
class TestGetRevisionsTouchingFileID(tests.TestCaseWithTransport):
1337
def create_tree_with_single_merge(self):
1338
"""Create a branch with a moderate layout.
1340
The revision graph looks like:
1348
In this graph, A introduced files f1 and f2 and f3.
1349
B modifies f1 and f3, and C modifies f2 and f3.
1350
D merges the changes from B and C and resolves the conflict for f3.
1352
# TODO: jam 20070218 This seems like it could really be done
1353
# with make_branch_and_memory_tree() if we could just
1354
# create the content of those files.
1355
# TODO: jam 20070218 Another alternative is that we would really
1356
# like to only create this tree 1 time for all tests that
1357
# use it. Since 'log' only uses the tree in a readonly
1358
# fashion, it seems a shame to regenerate an identical
1359
# tree for each test.
1360
tree = self.make_branch_and_tree('tree')
1362
self.addCleanup(tree.unlock)
1364
self.build_tree_contents([('tree/f1', 'A\n'),
1368
tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
1369
tree.commit('A', rev_id='A')
1371
self.build_tree_contents([('tree/f2', 'A\nC\n'),
1372
('tree/f3', 'A\nC\n'),
1374
tree.commit('C', rev_id='C')
1375
# Revert back to A to build the other history.
1376
tree.set_last_revision('A')
1377
tree.branch.set_last_revision_info(1, 'A')
1378
self.build_tree_contents([('tree/f1', 'A\nB\n'),
1380
('tree/f3', 'A\nB\n'),
1382
tree.commit('B', rev_id='B')
1383
tree.set_parent_ids(['B', 'C'])
1384
self.build_tree_contents([('tree/f1', 'A\nB\n'),
1385
('tree/f2', 'A\nC\n'),
1386
('tree/f3', 'A\nB\nC\n'),
1388
tree.commit('D', rev_id='D')
1390
# Switch to a read lock for this tree.
1391
# We still have an addCleanup(tree.unlock) pending
1396
def check_delta(self, delta, **kw):
1397
"""Check the filenames touched by a delta are as expected.
1399
Caller only have to pass in the list of files for each part, all
1400
unspecified parts are considered empty (and checked as such).
1402
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
1403
# By default we expect an empty list
1404
expected = kw.get(n, [])
1405
# strip out only the path components
1406
got = [x[0] for x in getattr(delta, n)]
1407
self.assertEqual(expected, got)
1409
def test_tree_with_single_merge(self):
1410
"""Make sure the tree layout is correct."""
1411
tree = self.create_tree_with_single_merge()
1412
rev_A_tree = tree.branch.repository.revision_tree('A')
1413
rev_B_tree = tree.branch.repository.revision_tree('B')
1414
rev_C_tree = tree.branch.repository.revision_tree('C')
1415
rev_D_tree = tree.branch.repository.revision_tree('D')
1417
self.check_delta(rev_B_tree.changes_from(rev_A_tree),
1418
modified=['f1', 'f3'])
1420
self.check_delta(rev_C_tree.changes_from(rev_A_tree),
1421
modified=['f2', 'f3'])
1423
self.check_delta(rev_D_tree.changes_from(rev_B_tree),
1424
modified=['f2', 'f3'])
1426
self.check_delta(rev_D_tree.changes_from(rev_C_tree),
1427
modified=['f1', 'f3'])
1429
def assertAllRevisionsForFileID(self, tree, file_id, revisions):
1430
"""Ensure _filter_revisions_touching_file_id returns the right values.
1432
Get the return value from _filter_revisions_touching_file_id and make
1433
sure they are correct.
1435
# The api for _filter_revisions_touching_file_id is a little crazy.
1436
# So we do the setup here.
1437
mainline = tree.branch.revision_history()
1438
mainline.insert(0, None)
1439
revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
1440
view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
1442
actual_revs = log._filter_revisions_touching_file_id(
1445
list(view_revs_iter))
1446
self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
1448
def test_file_id_f1(self):
1449
tree = self.create_tree_with_single_merge()
1450
# f1 should be marked as modified by revisions A and B
1451
self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
1453
def test_file_id_f2(self):
1454
tree = self.create_tree_with_single_merge()
1455
# f2 should be marked as modified by revisions A, C, and D
1456
# because D merged the changes from C.
1457
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1459
def test_file_id_f3(self):
1460
tree = self.create_tree_with_single_merge()
1461
# f3 should be marked as modified by revisions A, B, C, and D
1462
self.assertAllRevisionsForFileID(tree, 'f3-id', ['D', 'C', 'B', 'A'])
1464
def test_file_id_with_ghosts(self):
1465
# This is testing bug #209948, where having a ghost would cause
1466
# _filter_revisions_touching_file_id() to fail.
1467
tree = self.create_tree_with_single_merge()
1468
# We need to add a revision, so switch back to a write-locked tree
1469
# (still a single addCleanup(tree.unlock) pending).
1472
first_parent = tree.last_revision()
1473
tree.set_parent_ids([first_parent, 'ghost-revision-id'])
1474
self.build_tree_contents([('tree/f1', 'A\nB\nXX\n')])
1475
tree.commit('commit with a ghost', rev_id='XX')
1476
self.assertAllRevisionsForFileID(tree, 'f1-id', ['XX', 'B', 'A'])
1477
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1479
def test_unknown_file_id(self):
1480
tree = self.create_tree_with_single_merge()
1481
self.assertAllRevisionsForFileID(tree, 'unknown', [])
1483
def test_empty_branch_unknown_file_id(self):
1484
tree = self.make_branch_and_tree('tree')
1485
self.assertAllRevisionsForFileID(tree, 'unknown', [])
1488
class TestShowChangedRevisions(tests.TestCaseWithTransport):
1490
def test_show_changed_revisions_verbose(self):
1491
tree = self.make_branch_and_tree('tree_a')
1492
self.build_tree(['tree_a/foo'])
1494
tree.commit('bar', rev_id='bar-id')
1495
s = self.make_utf8_encoded_stringio()
1496
log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
1497
self.assertContainsRe(s.getvalue(), 'bar')
1498
self.assertNotContainsRe(s.getvalue(), 'foo')
1501
class TestLogFormatter(tests.TestCase):
1503
def test_short_committer(self):
1504
rev = revision.Revision('a-id')
1505
rev.committer = 'John Doe <jdoe@example.com>'
1506
lf = log.LogFormatter(None)
1507
self.assertEqual('John Doe', lf.short_committer(rev))
1508
rev.committer = 'John Smith <jsmith@example.com>'
1509
self.assertEqual('John Smith', lf.short_committer(rev))
1510
rev.committer = 'John Smith'
1511
self.assertEqual('John Smith', lf.short_committer(rev))
1512
rev.committer = 'jsmith@example.com'
1513
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1514
rev.committer = '<jsmith@example.com>'
1515
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1516
rev.committer = 'John Smith jsmith@example.com'
1517
self.assertEqual('John Smith', lf.short_committer(rev))
1519
def test_short_author(self):
1520
rev = revision.Revision('a-id')
1521
rev.committer = 'John Doe <jdoe@example.com>'
1522
lf = log.LogFormatter(None)
1523
self.assertEqual('John Doe', lf.short_author(rev))
1524
rev.properties['author'] = 'John Smith <jsmith@example.com>'
1525
self.assertEqual('John Smith', lf.short_author(rev))
1526
rev.properties['author'] = 'John Smith'
1527
self.assertEqual('John Smith', lf.short_author(rev))
1528
rev.properties['author'] = 'jsmith@example.com'
1529
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1530
rev.properties['author'] = '<jsmith@example.com>'
1531
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1532
rev.properties['author'] = 'John Smith jsmith@example.com'
1533
self.assertEqual('John Smith', lf.short_author(rev))
1534
del rev.properties['author']
1535
rev.properties['authors'] = ('John Smith <jsmith@example.com>\n'
1536
'Jane Rey <jrey@example.com>')
1537
self.assertEqual('John Smith', lf.short_author(rev))
1540
class TestReverseByDepth(tests.TestCase):
1541
"""Test reverse_by_depth behavior.
1543
This is used to present revisions in forward (oldest first) order in a nice
1546
The tests use lighter revision description to ease reading.
1549
def assertReversed(self, forward, backward):
1550
# Transform the descriptions to suit the API: tests use (revno, depth),
1551
# while the API expects (revid, revno, depth)
1552
def complete_revisions(l):
1553
"""Transform the description to suit the API.
1555
Tests use (revno, depth) whil the API expects (revid, revno, depth).
1556
Since the revid is arbitrary, we just duplicate revno
1558
return [ (r, r, d) for r, d in l]
1559
forward = complete_revisions(forward)
1560
backward= complete_revisions(backward)
1561
self.assertEqual(forward, log.reverse_by_depth(backward))
1564
def test_mainline_revisions(self):
1565
self.assertReversed([( '1', 0), ('2', 0)],
1566
[('2', 0), ('1', 0)])
1568
def test_merged_revisions(self):
1569
self.assertReversed([('1', 0), ('2', 0), ('2.2', 1), ('2.1', 1),],
1570
[('2', 0), ('2.1', 1), ('2.2', 1), ('1', 0),])
1571
def test_shifted_merged_revisions(self):
1572
"""Test irregular layout.
1574
Requesting revisions touching a file can produce "holes" in the depths.
1576
self.assertReversed([('1', 0), ('2', 0), ('1.1', 2), ('1.2', 2),],
1577
[('2', 0), ('1.2', 2), ('1.1', 2), ('1', 0),])
1579
def test_merged_without_child_revisions(self):
1580
"""Test irregular layout.
1582
Revision ranges can produce "holes" in the depths.
1584
# When a revision of higher depth doesn't follow one of lower depth, we
1585
# assume a lower depth one is virtually there
1586
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1587
[('4', 4), ('3', 3), ('2', 2), ('1', 2),])
1588
# So we get the same order after reversing below even if the original
1589
# revisions are not in the same order.
1590
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1591
[('3', 3), ('4', 4), ('2', 2), ('1', 2),])
1594
class TestHistoryChange(tests.TestCaseWithTransport):
1596
def setup_a_tree(self):
1597
tree = self.make_branch_and_tree('tree')
1599
self.addCleanup(tree.unlock)
1600
tree.commit('1a', rev_id='1a')
1601
tree.commit('2a', rev_id='2a')
1602
tree.commit('3a', rev_id='3a')
1605
def setup_ab_tree(self):
1606
tree = self.setup_a_tree()
1607
tree.set_last_revision('1a')
1608
tree.branch.set_last_revision_info(1, '1a')
1609
tree.commit('2b', rev_id='2b')
1610
tree.commit('3b', rev_id='3b')
1613
def setup_ac_tree(self):
1614
tree = self.setup_a_tree()
1615
tree.set_last_revision(revision.NULL_REVISION)
1616
tree.branch.set_last_revision_info(0, revision.NULL_REVISION)
1617
tree.commit('1c', rev_id='1c')
1618
tree.commit('2c', rev_id='2c')
1619
tree.commit('3c', rev_id='3c')
1622
def test_all_new(self):
1623
tree = self.setup_ab_tree()
1624
old, new = log.get_history_change('1a', '3a', tree.branch.repository)
1625
self.assertEqual([], old)
1626
self.assertEqual(['2a', '3a'], new)
1628
def test_all_old(self):
1629
tree = self.setup_ab_tree()
1630
old, new = log.get_history_change('3a', '1a', tree.branch.repository)
1631
self.assertEqual([], new)
1632
self.assertEqual(['2a', '3a'], old)
1634
def test_null_old(self):
1635
tree = self.setup_ab_tree()
1636
old, new = log.get_history_change(revision.NULL_REVISION,
1637
'3a', tree.branch.repository)
1638
self.assertEqual([], old)
1639
self.assertEqual(['1a', '2a', '3a'], new)
1641
def test_null_new(self):
1642
tree = self.setup_ab_tree()
1643
old, new = log.get_history_change('3a', revision.NULL_REVISION,
1644
tree.branch.repository)
1645
self.assertEqual([], new)
1646
self.assertEqual(['1a', '2a', '3a'], old)
1648
def test_diverged(self):
1649
tree = self.setup_ab_tree()
1650
old, new = log.get_history_change('3a', '3b', tree.branch.repository)
1651
self.assertEqual(old, ['2a', '3a'])
1652
self.assertEqual(new, ['2b', '3b'])
1654
def test_unrelated(self):
1655
tree = self.setup_ac_tree()
1656
old, new = log.get_history_change('3a', '3c', tree.branch.repository)
1657
self.assertEqual(old, ['1a', '2a', '3a'])
1658
self.assertEqual(new, ['1c', '2c', '3c'])
1660
def test_show_branch_change(self):
1661
tree = self.setup_ab_tree()
1663
log.show_branch_change(tree.branch, s, 3, '3a')
1664
self.assertContainsRe(s.getvalue(),
1665
'[*]{60}\nRemoved Revisions:\n(.|\n)*2a(.|\n)*3a(.|\n)*'
1666
'[*]{60}\n\nAdded Revisions:\n(.|\n)*2b(.|\n)*3b')
1668
def test_show_branch_change_no_change(self):
1669
tree = self.setup_ab_tree()
1671
log.show_branch_change(tree.branch, s, 3, '3b')
1672
self.assertEqual(s.getvalue(),
1673
'Nothing seems to have changed\n')
1675
def test_show_branch_change_no_old(self):
1676
tree = self.setup_ab_tree()
1678
log.show_branch_change(tree.branch, s, 2, '2b')
1679
self.assertContainsRe(s.getvalue(), 'Added Revisions:')
1680
self.assertNotContainsRe(s.getvalue(), 'Removed Revisions:')
1682
def test_show_branch_change_no_new(self):
1683
tree = self.setup_ab_tree()
1684
tree.branch.set_last_revision_info(2, '2b')
1686
log.show_branch_change(tree.branch, s, 3, '3b')
1687
self.assertContainsRe(s.getvalue(), 'Removed Revisions:')
1688
self.assertNotContainsRe(s.getvalue(), 'Added Revisions:')
396
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
398
expected = [('1', 1, 0), ('2', 2, 0), ('3c', 3, 0), ('3a', None, 1),
399
('3b', None, 1), ('4b', 4, 0), ('4a', None, 1)]
400
self.assertEqual(revisions, expected)
401
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
402
'forward', include_merges=False))
403
self.assertEqual(revisions, [('1', 1, 0), ('2', 2, 0), ('3c', 3, 0),