13
15
# You should have received a copy of the GNU General Public License
14
16
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
20
from cStringIO import StringIO
30
class TestCaseWithoutPropsHandler(tests.TestCaseWithTransport):
33
super(TestCaseWithoutPropsHandler, self).setUp()
34
# keep a reference to the "current" custom prop. handler registry
35
self.properties_handler_registry = log.properties_handler_registry
36
# Use a clean registry for log
37
log.properties_handler_registry = registry.Registry()
40
log.properties_handler_registry = self.properties_handler_registry
41
self.addCleanup(restore)
44
class LogCatcher(log.LogFormatter):
45
"""Pull log messages into a list rather than displaying them.
47
To simplify testing we save logged revisions here rather than actually
48
formatting anything, so that we can precisely check the result without
49
being dependent on the formatting.
22
from bzrlib.tests import BzrTestBase, TestCaseWithTransport
23
from bzrlib.log import (show_log,
29
from bzrlib.branch import Branch
30
from bzrlib.errors import InvalidRevisionNumber
33
class _LogEntry(object):
34
# should probably move into bzrlib.log?
38
class LogCatcher(LogFormatter):
39
"""Pull log messages into list rather than displaying them.
41
For ease of testing we save log messages here rather than actually
42
formatting them, so that we can precisely check the result without
43
being too dependent on the exact formatting.
45
We should also test the LogFormatter.
54
47
def __init__(self):
55
48
super(LogCatcher, self).__init__(to_file=None)
58
def log_revision(self, revision):
59
self.revisions.append(revision)
62
class TestShowLog(tests.TestCaseWithTransport):
51
def show(self, revno, rev, delta):
59
class SimpleLogTest(TestCaseWithTransport):
64
61
def checkDelta(self, delta, **kw):
65
"""Check the filenames touched by a delta are as expected.
67
Caller only have to pass in the list of files for each part, all
68
unspecified parts are considered empty (and checked as such).
62
"""Check the filenames touched by a delta are as expected."""
70
63
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
71
# By default we expect an empty list
72
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]
73
70
# strip out only the path components
74
71
got = [x[0] for x in getattr(delta, n)]
75
self.assertEqual(expected, got)
77
def assertInvalidRevisonNumber(self, br, start, end):
79
self.assertRaises(errors.InvalidRevisionNumber,
81
start_revision=start, end_revision=end)
83
def test_cur_revno(self):
84
wt = self.make_branch_and_tree('.')
88
wt.commit('empty commit')
89
log.show_log(b, lf, verbose=True, start_revision=1, end_revision=1)
91
# Since there is a single revision in the branch all the combinations
93
self.assertInvalidRevisonNumber(b, 2, 1)
94
self.assertInvalidRevisonNumber(b, 1, 2)
95
self.assertInvalidRevisonNumber(b, 0, 2)
96
self.assertInvalidRevisonNumber(b, 1, 0)
97
self.assertInvalidRevisonNumber(b, -1, 1)
98
self.assertInvalidRevisonNumber(b, 1, -1)
100
def test_empty_branch(self):
101
wt = self.make_branch_and_tree('.')
104
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('.')
106
self.assertEqual([], lf.revisions)
108
def test_empty_commit(self):
109
wt = self.make_branch_and_tree('.')
111
125
wt.commit('empty commit')
112
126
lf = LogCatcher()
113
log.show_log(wt.branch, lf, verbose=True)
115
self.assertEqual(1, len(revs))
116
self.assertEqual('1', revs[0].revno)
117
self.assertEqual('empty commit', revs[0].rev.message)
118
self.checkDelta(revs[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)
120
def test_simple_commit(self):
121
wt = self.make_branch_and_tree('.')
122
wt.commit('empty commit')
123
135
self.build_tree(['hello'])
125
wt.commit('add one file',
126
committer=u'\u013d\xf3r\xe9m \xcdp\u0161\xfam '
127
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
128
147
lf = LogCatcher()
129
log.show_log(wt.branch, lf, verbose=True)
130
self.assertEqual(2, len(lf.revisions))
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))
131
154
# first one is most recent
132
log_entry = lf.revisions[0]
133
self.assertEqual('2', log_entry.revno)
134
self.assertEqual('add one file', log_entry.rev.message)
135
self.checkDelta(log_entry.delta, added=['hello'])
137
def test_commit_message_with_control_chars(self):
138
wt = self.make_branch_and_tree('.')
139
msg = u"All 8-bit chars: " + ''.join([unichr(x) for x in range(256)])
140
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)
142
166
lf = LogCatcher()
143
log.show_log(wt.branch, lf, verbose=True)
144
committed_msg = lf.revisions[0].rev.message
145
if wt.branch.repository._serializer.squashes_xml_invalid_characters:
146
self.assertNotEqual(msg, committed_msg)
147
self.assertTrue(len(committed_msg) > len(msg))
149
self.assertEqual(msg, committed_msg)
167
show_log(b, lf, verbose=True)
168
committed_msg = lf.logs[0].rev.message
169
self.log("escaped commit message: %r", committed_msg)
170
self.assert_(msg != committed_msg)
171
self.assert_(len(committed_msg) > len(msg))
151
def test_commit_message_without_control_chars(self):
152
wt = self.make_branch_and_tree('.')
173
# Check that log message with only XML-valid characters isn't
153
174
# escaped. As ElementTree apparently does some kind of
154
175
# newline conversion, neither LF (\x0A) nor CR (\x0D) are
155
176
# included in the test commit message, even though they are
156
177
# valid XML 1.0 characters.
157
178
msg = "\x09" + ''.join([unichr(x) for x in range(0x20, 256)])
179
self.log("original commit message: %r", msg)
159
181
lf = LogCatcher()
160
log.show_log(wt.branch, lf, verbose=True)
161
committed_msg = lf.revisions[0].rev.message
162
self.assertEqual(msg, committed_msg)
164
def test_deltas_in_merge_revisions(self):
165
"""Check deltas created for both mainline and merge revisions"""
166
wt = self.make_branch_and_tree('parent')
167
self.build_tree(['parent/file1', 'parent/file2', 'parent/file3'])
170
wt.commit(message='add file1 and file2')
171
self.run_bzr('branch parent child')
172
os.unlink('child/file1')
173
file('child/file2', 'wb').write('hello\n')
174
self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
177
self.run_bzr('merge ../child')
178
wt.commit('merge child branch')
182
lf.supports_merge_revisions = True
183
log.show_log(b, lf, verbose=True)
186
self.assertEqual(3, len(revs))
189
self.assertEqual('2', logentry.revno)
190
self.assertEqual('merge child branch', logentry.rev.message)
191
self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
194
self.assertEqual('1.1.1', logentry.revno)
195
self.assertEqual('remove file1 and modify file2', logentry.rev.message)
196
self.checkDelta(logentry.delta, removed=['file1'], modified=['file2'])
199
self.assertEqual('1', logentry.revno)
200
self.assertEqual('add file1 and file2', logentry.rev.message)
201
self.checkDelta(logentry.delta, added=['file1', 'file2'])
204
def make_commits_with_trailing_newlines(wt):
205
"""Helper method for LogFormatter tests"""
208
open('a', 'wb').write('hello moto\n')
210
wt.commit('simple log message', rev_id='a1',
211
timestamp=1132586655.459960938, timezone=-6*3600,
212
committer='Joe Foo <joe@foo.com>')
213
open('b', 'wb').write('goodbye\n')
215
wt.commit('multiline\nlog\nmessage\n', rev_id='a2',
216
timestamp=1132586842.411175966, timezone=-6*3600,
217
committer='Joe Foo <joe@foo.com>',
218
authors=['Joe Bar <joe@bar.com>'])
220
open('c', 'wb').write('just another manic monday\n')
222
wt.commit('single line with trailing newline\n', rev_id='a3',
223
timestamp=1132587176.835228920, timezone=-6*3600,
224
committer = 'Joe Foo <joe@foo.com>')
228
def normalize_log(log):
229
"""Replaces the variable lines of logs with fixed lines"""
230
author = 'author: Dolor Sit <test@example.com>'
231
committer = 'committer: Lorem Ipsum <test@example.com>'
232
lines = log.splitlines(True)
233
for idx,line in enumerate(lines):
234
stripped_line = line.lstrip()
235
indent = ' ' * (len(line) - len(stripped_line))
236
if stripped_line.startswith('author:'):
237
lines[idx] = indent + author + '\n'
238
elif stripped_line.startswith('committer:'):
239
lines[idx] = indent + committer + '\n'
240
elif stripped_line.startswith('timestamp:'):
241
lines[idx] = indent + 'timestamp: Just now\n'
242
return ''.join(lines)
245
class TestShortLogFormatter(tests.TestCaseWithTransport):
182
show_log(b, lf, verbose=True)
183
committed_msg = lf.logs[0].rev.message
184
self.log("escaped commit message: %r", committed_msg)
185
self.assert_(msg == committed_msg)
247
187
def test_trailing_newlines(self):
248
188
wt = self.make_branch_and_tree('.')
249
b = make_commits_with_trailing_newlines(wt)
250
sio = self.make_utf8_encoded_stringio()
251
lf = log.ShortLogFormatter(to_file=sio)
253
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(), """\
254
212
3 Joe Foo\t2005-11-21
255
213
single line with trailing newline
257
2 Joe Bar\t2005-11-21
215
2 Joe Foo\t2005-11-21
262
220
1 Joe Foo\t2005-11-21
263
221
simple log message
268
def _prepare_tree_with_merges(self, with_tags=False):
269
wt = self.make_branch_and_memory_tree('.')
271
self.addCleanup(wt.unlock)
273
wt.commit('rev-1', rev_id='rev-1',
274
timestamp=1132586655, timezone=36000,
275
committer='Joe Foo <joe@foo.com>')
276
wt.commit('rev-merged', rev_id='rev-2a',
277
timestamp=1132586700, timezone=36000,
278
committer='Joe Foo <joe@foo.com>')
279
wt.set_parent_ids(['rev-1', 'rev-2a'])
280
wt.branch.set_last_revision_info(1, 'rev-1')
281
wt.commit('rev-2', rev_id='rev-2b',
282
timestamp=1132586800, timezone=36000,
283
committer='Joe Foo <joe@foo.com>')
286
branch.tags.set_tag('v0.2', 'rev-2b')
287
wt.commit('rev-3', rev_id='rev-3',
288
timestamp=1132586900, timezone=36000,
289
committer='Jane Foo <jane@foo.com>')
290
branch.tags.set_tag('v1.0rc1', 'rev-3')
291
branch.tags.set_tag('v1.0', 'rev-3')
294
def test_short_log_with_merges(self):
295
wt = self._prepare_tree_with_merges()
296
logfile = self.make_utf8_encoded_stringio()
297
formatter = log.ShortLogFormatter(to_file=logfile)
298
log.show_log(wt.branch, formatter)
299
self.assertEqualDiff("""\
300
2 Joe Foo\t2005-11-22 [merge]
303
1 Joe Foo\t2005-11-22
309
def test_short_log_with_merges_and_advice(self):
310
wt = self._prepare_tree_with_merges()
311
logfile = self.make_utf8_encoded_stringio()
312
formatter = log.ShortLogFormatter(to_file=logfile,
314
log.show_log(wt.branch, formatter)
315
self.assertEqualDiff("""\
316
2 Joe Foo\t2005-11-22 [merge]
319
1 Joe Foo\t2005-11-22
322
Use --include-merges or -n0 to see merged revisions.
326
def test_short_log_with_merges_and_range(self):
327
wt = self.make_branch_and_memory_tree('.')
329
self.addCleanup(wt.unlock)
331
wt.commit('rev-1', rev_id='rev-1',
332
timestamp=1132586655, timezone=36000,
333
committer='Joe Foo <joe@foo.com>')
334
wt.commit('rev-merged', rev_id='rev-2a',
335
timestamp=1132586700, timezone=36000,
336
committer='Joe Foo <joe@foo.com>')
337
wt.branch.set_last_revision_info(1, 'rev-1')
338
wt.set_parent_ids(['rev-1', 'rev-2a'])
339
wt.commit('rev-2b', rev_id='rev-2b',
340
timestamp=1132586800, timezone=36000,
341
committer='Joe Foo <joe@foo.com>')
342
wt.commit('rev-3a', rev_id='rev-3a',
343
timestamp=1132586800, timezone=36000,
344
committer='Joe Foo <joe@foo.com>')
345
wt.branch.set_last_revision_info(2, 'rev-2b')
346
wt.set_parent_ids(['rev-2b', 'rev-3a'])
347
wt.commit('rev-3b', rev_id='rev-3b',
348
timestamp=1132586800, timezone=36000,
349
committer='Joe Foo <joe@foo.com>')
350
logfile = self.make_utf8_encoded_stringio()
351
formatter = log.ShortLogFormatter(to_file=logfile)
352
log.show_log(wt.branch, formatter,
353
start_revision=2, end_revision=3)
354
self.assertEqualDiff("""\
355
3 Joe Foo\t2005-11-22 [merge]
358
2 Joe Foo\t2005-11-22 [merge]
364
def test_short_log_with_tags(self):
365
wt = self._prepare_tree_with_merges(with_tags=True)
366
logfile = self.make_utf8_encoded_stringio()
367
formatter = log.ShortLogFormatter(to_file=logfile)
368
log.show_log(wt.branch, formatter)
369
self.assertEqualDiff("""\
370
3 Jane Foo\t2005-11-22 {v1.0, v1.0rc1}
373
2 Joe Foo\t2005-11-22 {v0.2} [merge]
376
1 Joe Foo\t2005-11-22
382
def test_short_log_single_merge_revision(self):
383
wt = self.make_branch_and_memory_tree('.')
385
self.addCleanup(wt.unlock)
387
wt.commit('rev-1', rev_id='rev-1',
388
timestamp=1132586655, timezone=36000,
389
committer='Joe Foo <joe@foo.com>')
390
wt.commit('rev-merged', rev_id='rev-2a',
391
timestamp=1132586700, timezone=36000,
392
committer='Joe Foo <joe@foo.com>')
393
wt.set_parent_ids(['rev-1', 'rev-2a'])
394
wt.branch.set_last_revision_info(1, 'rev-1')
395
wt.commit('rev-2', rev_id='rev-2b',
396
timestamp=1132586800, timezone=36000,
397
committer='Joe Foo <joe@foo.com>')
398
logfile = self.make_utf8_encoded_stringio()
399
formatter = log.ShortLogFormatter(to_file=logfile)
400
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
402
rev = revspec.in_history(wtb)
403
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
404
self.assertEqualDiff("""\
405
1.1.1 Joe Foo\t2005-11-22
412
class TestShortLogFormatterWithMergeRevisions(tests.TestCaseWithTransport):
414
def test_short_merge_revs_log_with_merges(self):
415
wt = self.make_branch_and_memory_tree('.')
417
self.addCleanup(wt.unlock)
419
wt.commit('rev-1', rev_id='rev-1',
420
timestamp=1132586655, timezone=36000,
421
committer='Joe Foo <joe@foo.com>')
422
wt.commit('rev-merged', rev_id='rev-2a',
423
timestamp=1132586700, timezone=36000,
424
committer='Joe Foo <joe@foo.com>')
425
wt.set_parent_ids(['rev-1', 'rev-2a'])
426
wt.branch.set_last_revision_info(1, 'rev-1')
427
wt.commit('rev-2', rev_id='rev-2b',
428
timestamp=1132586800, timezone=36000,
429
committer='Joe Foo <joe@foo.com>')
430
logfile = self.make_utf8_encoded_stringio()
431
formatter = log.ShortLogFormatter(to_file=logfile, levels=0)
432
log.show_log(wt.branch, formatter)
433
# Note that the 1.1.1 indenting is in fact correct given that
434
# the revision numbers are right justified within 5 characters
435
# for mainline revnos and 9 characters for dotted revnos.
436
self.assertEqualDiff("""\
437
2 Joe Foo\t2005-11-22 [merge]
440
1.1.1 Joe Foo\t2005-11-22
443
1 Joe Foo\t2005-11-22
449
def test_short_merge_revs_log_single_merge_revision(self):
450
wt = self.make_branch_and_memory_tree('.')
452
self.addCleanup(wt.unlock)
454
wt.commit('rev-1', rev_id='rev-1',
455
timestamp=1132586655, timezone=36000,
456
committer='Joe Foo <joe@foo.com>')
457
wt.commit('rev-merged', rev_id='rev-2a',
458
timestamp=1132586700, timezone=36000,
459
committer='Joe Foo <joe@foo.com>')
460
wt.set_parent_ids(['rev-1', 'rev-2a'])
461
wt.branch.set_last_revision_info(1, 'rev-1')
462
wt.commit('rev-2', rev_id='rev-2b',
463
timestamp=1132586800, timezone=36000,
464
committer='Joe Foo <joe@foo.com>')
465
logfile = self.make_utf8_encoded_stringio()
466
formatter = log.ShortLogFormatter(to_file=logfile, levels=0)
467
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
469
rev = revspec.in_history(wtb)
470
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
471
self.assertEqualDiff("""\
472
1.1.1 Joe Foo\t2005-11-22
479
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
481
254
def test_verbose_log(self):
482
255
"""Verbose log includes changed files
486
wt = self.make_branch_and_tree('.')
488
self.build_tree(['a'])
490
# XXX: why does a longer nick show up?
491
b.nick = 'test_verbose_log'
492
wt.commit(message='add a',
493
timestamp=1132711707,
495
committer='Lorem Ipsum <test@example.com>')
496
logfile = file('out.tmp', 'w+')
497
formatter = log.LongLogFormatter(to_file=logfile)
498
log.show_log(b, formatter, verbose=True)
501
log_contents = logfile.read()
502
self.assertEqualDiff('''\
503
------------------------------------------------------------
505
committer: Lorem Ipsum <test@example.com>
506
branch nick: test_verbose_log
507
timestamp: Wed 2005-11-23 12:08:27 +1000
515
def test_merges_are_indented_by_level(self):
516
wt = self.make_branch_and_tree('parent')
517
wt.commit('first post')
518
self.run_bzr('branch parent child')
519
self.run_bzr(['commit', '-m', 'branch 1', '--unchanged', 'child'])
520
self.run_bzr('branch child smallerchild')
521
self.run_bzr(['commit', '-m', 'branch 2', '--unchanged',
524
self.run_bzr('merge ../smallerchild')
525
self.run_bzr(['commit', '-m', 'merge branch 2'])
526
os.chdir('../parent')
527
self.run_bzr('merge ../child')
528
wt.commit('merge branch 1')
530
sio = self.make_utf8_encoded_stringio()
531
lf = log.LongLogFormatter(to_file=sio, levels=0)
532
log.show_log(b, lf, verbose=True)
533
the_log = normalize_log(sio.getvalue())
534
self.assertEqualDiff("""\
535
------------------------------------------------------------
537
committer: Lorem Ipsum <test@example.com>
542
------------------------------------------------------------
544
committer: Lorem Ipsum <test@example.com>
549
------------------------------------------------------------
551
committer: Lorem Ipsum <test@example.com>
552
branch nick: smallerchild
556
------------------------------------------------------------
558
committer: Lorem Ipsum <test@example.com>
563
------------------------------------------------------------
565
committer: Lorem Ipsum <test@example.com>
573
def test_verbose_merge_revisions_contain_deltas(self):
574
wt = self.make_branch_and_tree('parent')
575
self.build_tree(['parent/f1', 'parent/f2'])
577
wt.commit('first post')
578
self.run_bzr('branch parent child')
579
os.unlink('child/f1')
580
file('child/f2', 'wb').write('hello\n')
581
self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
584
self.run_bzr('merge ../child')
585
wt.commit('merge branch 1')
587
sio = self.make_utf8_encoded_stringio()
588
lf = log.LongLogFormatter(to_file=sio, levels=0)
589
log.show_log(b, lf, verbose=True)
590
the_log = normalize_log(sio.getvalue())
591
self.assertEqualDiff("""\
592
------------------------------------------------------------
594
committer: Lorem Ipsum <test@example.com>
603
------------------------------------------------------------
605
committer: Lorem Ipsum <test@example.com>
609
removed f1 and modified f2
614
------------------------------------------------------------
616
committer: Lorem Ipsum <test@example.com>
627
def test_trailing_newlines(self):
628
wt = self.make_branch_and_tree('.')
629
b = make_commits_with_trailing_newlines(wt)
630
sio = self.make_utf8_encoded_stringio()
631
lf = log.LongLogFormatter(to_file=sio)
633
self.assertEqualDiff("""\
634
------------------------------------------------------------
636
committer: Joe Foo <joe@foo.com>
638
timestamp: Mon 2005-11-21 09:32:56 -0600
640
single line with trailing newline
641
------------------------------------------------------------
643
author: Joe Bar <joe@bar.com>
644
committer: Joe Foo <joe@foo.com>
646
timestamp: Mon 2005-11-21 09:27:22 -0600
651
------------------------------------------------------------
653
committer: Joe Foo <joe@foo.com>
655
timestamp: Mon 2005-11-21 09:24:15 -0600
661
def test_author_in_log(self):
662
"""Log includes the author name if it's set in
663
the revision properties
665
wt = self.make_branch_and_tree('.')
667
self.build_tree(['a'])
669
b.nick = 'test_author_log'
670
wt.commit(message='add a',
671
timestamp=1132711707,
673
committer='Lorem Ipsum <test@example.com>',
674
authors=['John Doe <jdoe@example.com>',
675
'Jane Rey <jrey@example.com>'])
677
formatter = log.LongLogFormatter(to_file=sio)
678
log.show_log(b, formatter)
679
self.assertEqualDiff('''\
680
------------------------------------------------------------
682
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
683
committer: Lorem Ipsum <test@example.com>
684
branch nick: test_author_log
685
timestamp: Wed 2005-11-23 12:08:27 +1000
691
def test_properties_in_log(self):
692
"""Log includes the custom properties returned by the registered
695
wt = self.make_branch_and_tree('.')
697
self.build_tree(['a'])
699
b.nick = 'test_properties_in_log'
700
wt.commit(message='add a',
701
timestamp=1132711707,
703
committer='Lorem Ipsum <test@example.com>',
704
authors=['John Doe <jdoe@example.com>'])
706
formatter = log.LongLogFormatter(to_file=sio)
708
def trivial_custom_prop_handler(revision):
709
return {'test_prop':'test_value'}
711
log.properties_handler_registry.register(
712
'trivial_custom_prop_handler',
713
trivial_custom_prop_handler)
714
log.show_log(b, formatter)
716
log.properties_handler_registry.remove(
717
'trivial_custom_prop_handler')
718
self.assertEqualDiff('''\
719
------------------------------------------------------------
721
test_prop: test_value
722
author: John Doe <jdoe@example.com>
723
committer: Lorem Ipsum <test@example.com>
724
branch nick: test_properties_in_log
725
timestamp: Wed 2005-11-23 12:08:27 +1000
731
def test_properties_in_short_log(self):
732
"""Log includes the custom properties returned by the registered
735
wt = self.make_branch_and_tree('.')
737
self.build_tree(['a'])
739
b.nick = 'test_properties_in_short_log'
740
wt.commit(message='add a',
741
timestamp=1132711707,
743
committer='Lorem Ipsum <test@example.com>',
744
authors=['John Doe <jdoe@example.com>'])
746
formatter = log.ShortLogFormatter(to_file=sio)
748
def trivial_custom_prop_handler(revision):
749
return {'test_prop':'test_value'}
751
log.properties_handler_registry.register(
752
'trivial_custom_prop_handler',
753
trivial_custom_prop_handler)
754
log.show_log(b, formatter)
756
log.properties_handler_registry.remove(
757
'trivial_custom_prop_handler')
758
self.assertEqualDiff('''\
759
1 John Doe\t2005-11-23
760
test_prop: test_value
766
def test_error_in_properties_handler(self):
767
"""Log includes the custom properties returned by the registered
770
wt = self.make_branch_and_tree('.')
772
self.build_tree(['a'])
774
b.nick = 'test_author_log'
775
wt.commit(message='add a',
776
timestamp=1132711707,
778
committer='Lorem Ipsum <test@example.com>',
779
authors=['John Doe <jdoe@example.com>'],
780
revprops={'first_prop':'first_value'})
782
formatter = log.LongLogFormatter(to_file=sio)
784
def trivial_custom_prop_handler(revision):
785
raise StandardError("a test error")
787
log.properties_handler_registry.register(
788
'trivial_custom_prop_handler',
789
trivial_custom_prop_handler)
790
self.assertRaises(StandardError, log.show_log, b, formatter,)
792
log.properties_handler_registry.remove(
793
'trivial_custom_prop_handler')
795
def test_properties_handler_bad_argument(self):
796
wt = self.make_branch_and_tree('.')
798
self.build_tree(['a'])
800
b.nick = 'test_author_log'
801
wt.commit(message='add a',
802
timestamp=1132711707,
804
committer='Lorem Ipsum <test@example.com>',
805
authors=['John Doe <jdoe@example.com>'],
806
revprops={'a_prop':'test_value'})
808
formatter = log.LongLogFormatter(to_file=sio)
810
def bad_argument_prop_handler(revision):
811
return {'custom_prop_name':revision.properties['a_prop']}
813
log.properties_handler_registry.register(
814
'bad_argument_prop_handler',
815
bad_argument_prop_handler)
817
self.assertRaises(AttributeError, formatter.show_properties,
820
revision = b.repository.get_revision(b.last_revision())
821
formatter.show_properties(revision, '')
822
self.assertEqualDiff('''custom_prop_name: test_value\n''',
825
log.properties_handler_registry.remove(
826
'bad_argument_prop_handler')
829
class TestLongLogFormatterWithoutMergeRevisions(TestCaseWithoutPropsHandler):
831
def test_long_verbose_log(self):
832
"""Verbose log includes changed files
836
wt = self.make_branch_and_tree('.')
838
self.build_tree(['a'])
840
# XXX: why does a longer nick show up?
841
b.nick = 'test_verbose_log'
842
wt.commit(message='add a',
843
timestamp=1132711707,
845
committer='Lorem Ipsum <test@example.com>')
846
logfile = file('out.tmp', 'w+')
847
formatter = log.LongLogFormatter(to_file=logfile, levels=1)
848
log.show_log(b, formatter, verbose=True)
851
log_contents = logfile.read()
852
self.assertEqualDiff('''\
853
------------------------------------------------------------
855
committer: Lorem Ipsum <test@example.com>
856
branch nick: test_verbose_log
857
timestamp: Wed 2005-11-23 12:08:27 +1000
865
def test_long_verbose_contain_deltas(self):
866
wt = self.make_branch_and_tree('parent')
867
self.build_tree(['parent/f1', 'parent/f2'])
869
wt.commit('first post')
870
self.run_bzr('branch parent child')
871
os.unlink('child/f1')
872
file('child/f2', 'wb').write('hello\n')
873
self.run_bzr(['commit', '-m', 'removed f1 and modified f2',
876
self.run_bzr('merge ../child')
877
wt.commit('merge branch 1')
879
sio = self.make_utf8_encoded_stringio()
880
lf = log.LongLogFormatter(to_file=sio, levels=1)
881
log.show_log(b, lf, verbose=True)
882
the_log = normalize_log(sio.getvalue())
883
self.assertEqualDiff("""\
884
------------------------------------------------------------
886
committer: Lorem Ipsum <test@example.com>
895
------------------------------------------------------------
897
committer: Lorem Ipsum <test@example.com>
908
def test_long_trailing_newlines(self):
909
wt = self.make_branch_and_tree('.')
910
b = make_commits_with_trailing_newlines(wt)
911
sio = self.make_utf8_encoded_stringio()
912
lf = log.LongLogFormatter(to_file=sio, levels=1)
914
self.assertEqualDiff("""\
915
------------------------------------------------------------
917
committer: Joe Foo <joe@foo.com>
919
timestamp: Mon 2005-11-21 09:32:56 -0600
921
single line with trailing newline
922
------------------------------------------------------------
924
author: Joe Bar <joe@bar.com>
925
committer: Joe Foo <joe@foo.com>
927
timestamp: Mon 2005-11-21 09:27:22 -0600
932
------------------------------------------------------------
934
committer: Joe Foo <joe@foo.com>
936
timestamp: Mon 2005-11-21 09:24:15 -0600
942
def test_long_author_in_log(self):
943
"""Log includes the author name if it's set in
944
the revision properties
946
wt = self.make_branch_and_tree('.')
948
self.build_tree(['a'])
950
b.nick = 'test_author_log'
951
wt.commit(message='add a',
952
timestamp=1132711707,
954
committer='Lorem Ipsum <test@example.com>',
955
authors=['John Doe <jdoe@example.com>'])
957
formatter = log.LongLogFormatter(to_file=sio, levels=1)
958
log.show_log(b, formatter)
959
self.assertEqualDiff('''\
960
------------------------------------------------------------
962
author: John Doe <jdoe@example.com>
963
committer: Lorem Ipsum <test@example.com>
964
branch nick: test_author_log
965
timestamp: Wed 2005-11-23 12:08:27 +1000
971
def test_long_properties_in_log(self):
972
"""Log includes the custom properties returned by the registered
975
wt = self.make_branch_and_tree('.')
977
self.build_tree(['a'])
979
b.nick = 'test_properties_in_log'
980
wt.commit(message='add a',
981
timestamp=1132711707,
983
committer='Lorem Ipsum <test@example.com>',
984
authors=['John Doe <jdoe@example.com>'])
986
formatter = log.LongLogFormatter(to_file=sio, levels=1)
988
def trivial_custom_prop_handler(revision):
989
return {'test_prop':'test_value'}
991
log.properties_handler_registry.register(
992
'trivial_custom_prop_handler',
993
trivial_custom_prop_handler)
994
log.show_log(b, formatter)
996
log.properties_handler_registry.remove(
997
'trivial_custom_prop_handler')
998
self.assertEqualDiff('''\
999
------------------------------------------------------------
1001
test_prop: test_value
1002
author: John Doe <jdoe@example.com>
1003
committer: Lorem Ipsum <test@example.com>
1004
branch nick: test_properties_in_log
1005
timestamp: Wed 2005-11-23 12:08:27 +1000
1012
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
1014
287
def test_line_log(self):
1015
288
"""Line log should show revno
1019
wt = self.make_branch_and_tree('.')
1021
self.build_tree(['a'])
1023
b.nick = 'test-line-log'
1024
wt.commit(message='add a',
1025
timestamp=1132711707,
1027
committer='Line-Log-Formatter Tester <test@line.log>')
1028
logfile = file('out.tmp', 'w+')
1029
formatter = log.LineLogFormatter(to_file=logfile)
1030
log.show_log(b, formatter)
1033
log_contents = logfile.read()
1034
self.assertEqualDiff('1: Line-Log-Formatte... 2005-11-23 add a\n',
1037
def test_trailing_newlines(self):
1038
wt = self.make_branch_and_tree('.')
1039
b = make_commits_with_trailing_newlines(wt)
1040
sio = self.make_utf8_encoded_stringio()
1041
lf = log.LineLogFormatter(to_file=sio)
1043
self.assertEqualDiff("""\
1044
3: Joe Foo 2005-11-21 single line with trailing newline
1045
2: Joe Bar 2005-11-21 multiline
1046
1: Joe Foo 2005-11-21 simple log message
1050
def _prepare_tree_with_merges(self, with_tags=False):
1051
wt = self.make_branch_and_memory_tree('.')
1053
self.addCleanup(wt.unlock)
1055
wt.commit('rev-1', rev_id='rev-1',
1056
timestamp=1132586655, timezone=36000,
1057
committer='Joe Foo <joe@foo.com>')
1058
wt.commit('rev-merged', rev_id='rev-2a',
1059
timestamp=1132586700, timezone=36000,
1060
committer='Joe Foo <joe@foo.com>')
1061
wt.set_parent_ids(['rev-1', 'rev-2a'])
1062
wt.branch.set_last_revision_info(1, 'rev-1')
1063
wt.commit('rev-2', rev_id='rev-2b',
1064
timestamp=1132586800, timezone=36000,
1065
committer='Joe Foo <joe@foo.com>')
1068
branch.tags.set_tag('v0.2', 'rev-2b')
1069
wt.commit('rev-3', rev_id='rev-3',
1070
timestamp=1132586900, timezone=36000,
1071
committer='Jane Foo <jane@foo.com>')
1072
branch.tags.set_tag('v1.0rc1', 'rev-3')
1073
branch.tags.set_tag('v1.0', 'rev-3')
1076
def test_line_log_single_merge_revision(self):
1077
wt = self._prepare_tree_with_merges()
1078
logfile = self.make_utf8_encoded_stringio()
1079
formatter = log.LineLogFormatter(to_file=logfile)
1080
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
1082
rev = revspec.in_history(wtb)
1083
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
1084
self.assertEqualDiff("""\
1085
1.1.1: Joe Foo 2005-11-22 rev-merged
1089
def test_line_log_with_tags(self):
1090
wt = self._prepare_tree_with_merges(with_tags=True)
1091
logfile = self.make_utf8_encoded_stringio()
1092
formatter = log.LineLogFormatter(to_file=logfile)
1093
log.show_log(wt.branch, formatter)
1094
self.assertEqualDiff("""\
1095
3: Jane Foo 2005-11-22 {v1.0, v1.0rc1} rev-3
1096
2: Joe Foo 2005-11-22 [merge] {v0.2} rev-2
1097
1: Joe Foo 2005-11-22 rev-1
1101
class TestLineLogFormatterWithMergeRevisions(tests.TestCaseWithTransport):
1103
def test_line_merge_revs_log(self):
1104
"""Line log should show revno
1108
wt = self.make_branch_and_tree('.')
1110
self.build_tree(['a'])
1112
b.nick = 'test-line-log'
1113
wt.commit(message='add a',
1114
timestamp=1132711707,
1116
committer='Line-Log-Formatter Tester <test@line.log>')
1117
logfile = file('out.tmp', 'w+')
1118
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1119
log.show_log(b, formatter)
1122
log_contents = logfile.read()
1123
self.assertEqualDiff('1: Line-Log-Formatte... 2005-11-23 add a\n',
1126
def test_line_merge_revs_log_single_merge_revision(self):
1127
wt = self.make_branch_and_memory_tree('.')
1129
self.addCleanup(wt.unlock)
1131
wt.commit('rev-1', rev_id='rev-1',
1132
timestamp=1132586655, timezone=36000,
1133
committer='Joe Foo <joe@foo.com>')
1134
wt.commit('rev-merged', rev_id='rev-2a',
1135
timestamp=1132586700, timezone=36000,
1136
committer='Joe Foo <joe@foo.com>')
1137
wt.set_parent_ids(['rev-1', 'rev-2a'])
1138
wt.branch.set_last_revision_info(1, 'rev-1')
1139
wt.commit('rev-2', rev_id='rev-2b',
1140
timestamp=1132586800, timezone=36000,
1141
committer='Joe Foo <joe@foo.com>')
1142
logfile = self.make_utf8_encoded_stringio()
1143
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1144
revspec = revisionspec.RevisionSpec.from_string('1.1.1')
1146
rev = revspec.in_history(wtb)
1147
log.show_log(wtb, formatter, start_revision=rev, end_revision=rev)
1148
self.assertEqualDiff("""\
1149
1.1.1: Joe Foo 2005-11-22 rev-merged
1153
def test_line_merge_revs_log_with_merges(self):
1154
wt = self.make_branch_and_memory_tree('.')
1156
self.addCleanup(wt.unlock)
1158
wt.commit('rev-1', rev_id='rev-1',
1159
timestamp=1132586655, timezone=36000,
1160
committer='Joe Foo <joe@foo.com>')
1161
wt.commit('rev-merged', rev_id='rev-2a',
1162
timestamp=1132586700, timezone=36000,
1163
committer='Joe Foo <joe@foo.com>')
1164
wt.set_parent_ids(['rev-1', 'rev-2a'])
1165
wt.branch.set_last_revision_info(1, 'rev-1')
1166
wt.commit('rev-2', rev_id='rev-2b',
1167
timestamp=1132586800, timezone=36000,
1168
committer='Joe Foo <joe@foo.com>')
1169
logfile = self.make_utf8_encoded_stringio()
1170
formatter = log.LineLogFormatter(to_file=logfile, levels=0)
1171
log.show_log(wt.branch, formatter)
1172
self.assertEqualDiff("""\
1173
2: Joe Foo 2005-11-22 [merge] rev-2
1174
1.1.1: Joe Foo 2005-11-22 rev-merged
1175
1: Joe Foo 2005-11-22 rev-1
1179
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')
1181
309
def make_tree_with_commits(self):
1182
310
"""Create a tree with well-known revision ids"""
1188
316
rev_nos = {'1': 1, '2': 2, '3': 3}
1189
317
return mainline_revs, rev_nos, wt
319
def pseudo_merge(self, source, target):
320
revision_id = source.last_revision()
321
target.branch.fetch(source.branch, revision_id)
322
target.add_pending_merge(revision_id)
1191
324
def make_tree_with_merges(self):
1192
325
"""Create a tree with well-known revision ids and a merge"""
1193
326
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1194
327
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
1195
328
tree2.commit('four-a', rev_id='4a')
1196
wt.merge_from_branch(tree2.branch)
329
self.pseudo_merge(tree2, wt)
1197
330
wt.commit('four-b', rev_id='4b')
1198
331
mainline_revs.append('4b')
1199
332
rev_nos['4b'] = 4
1201
333
return mainline_revs, rev_nos, wt
1203
def make_branch_with_many_merges(self):
335
def make_tree_with_many_merges(self):
1204
336
"""Create a tree with well-known revision ids"""
1205
builder = self.make_branch_builder('tree1')
1206
builder.start_series()
1207
builder.build_snapshot('1', None, [
1208
('add', ('', 'TREE_ROOT', 'directory', '')),
1209
('add', ('f', 'f-id', 'file', '1\n'))])
1210
builder.build_snapshot('2', ['1'], [])
1211
builder.build_snapshot('3a', ['2'], [
1212
('modify', ('f-id', '1\n2\n3a\n'))])
1213
builder.build_snapshot('3b', ['2', '3a'], [
1214
('modify', ('f-id', '1\n2\n3a\n'))])
1215
builder.build_snapshot('3c', ['2', '3b'], [
1216
('modify', ('f-id', '1\n2\n3a\n'))])
1217
builder.build_snapshot('4a', ['3b'], [])
1218
builder.build_snapshot('4b', ['3c', '4a'], [])
1219
builder.finish_series()
337
wt = self.make_branch_and_tree('tree1')
338
wt.commit('commit one', rev_id='1')
339
wt.commit('commit two', rev_id='2')
340
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
341
tree3 = wt.bzrdir.sprout('tree3').open_workingtree()
342
tree3.commit('commit three a', rev_id='3a')
343
self.pseudo_merge(tree3, tree2)
344
tree2.commit('commit three b', rev_id='3b')
345
self.pseudo_merge(tree2, wt)
346
wt.commit('commit three c', rev_id='3c')
347
tree2.commit('four-a', rev_id='4a')
348
self.pseudo_merge(tree2, wt)
349
wt.commit('four-b', rev_id='4b')
1233
350
mainline_revs = [None, '1', '2', '3c', '4b']
1234
rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
1235
full_rev_nos_for_reference = {
1238
'3a': '2.1.1', #first commit tree 3
1239
'3b': '2.2.1', # first commit tree 2
1240
'3c': '3', #merges 3b to main
1241
'4a': '2.2.2', # second commit tree 2
1242
'4b': '4', # merges 4a to main
1244
return mainline_revs, rev_nos, builder.get_branch()
351
rev_nos = {'1': 1, '2': 2, '3c': 3, '4b': 4}
352
return mainline_revs, rev_nos, wt
1246
354
def test_get_view_revisions_forward(self):
1247
355
"""Test the get_view_revisions method"""
1248
356
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1250
self.addCleanup(wt.unlock)
1251
revisions = list(log.get_view_revisions(
1252
mainline_revs, rev_nos, wt.branch, 'forward'))
1253
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
1255
revisions2 = list(log.get_view_revisions(
1256
mainline_revs, rev_nos, wt.branch, 'forward',
1257
include_merges=False))
357
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
359
self.assertEqual(revisions, [('1', 1, 0), ('2', 2, 0), ('3', 3, 0)])
360
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
361
'forward', include_merges=False))
1258
362
self.assertEqual(revisions, revisions2)
1260
364
def test_get_view_revisions_reverse(self):
1261
365
"""Test the get_view_revisions with reverse"""
1262
366
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1264
self.addCleanup(wt.unlock)
1265
revisions = list(log.get_view_revisions(
1266
mainline_revs, rev_nos, wt.branch, 'reverse'))
1267
self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
1269
revisions2 = list(log.get_view_revisions(
1270
mainline_revs, rev_nos, wt.branch, 'reverse',
1271
include_merges=False))
367
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
369
self.assertEqual(revisions, [('3', 3, 0), ('2', 2, 0), ('1', 1, 0), ])
370
revisions2 = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
371
'reverse', include_merges=False))
1272
372
self.assertEqual(revisions, revisions2)
1274
374
def test_get_view_revisions_merge(self):
1275
375
"""Test get_view_revisions when there are merges"""
1276
376
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
1278
self.addCleanup(wt.unlock)
1279
revisions = list(log.get_view_revisions(
1280
mainline_revs, rev_nos, wt.branch, 'forward'))
1281
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
1282
('4b', '4', 0), ('4a', '3.1.1', 1)],
1284
revisions = list(log.get_view_revisions(
1285
mainline_revs, rev_nos, wt.branch, 'forward',
1286
include_merges=False))
1287
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
377
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
379
self.assertEqual(revisions, [('1', 1, 0), ('2', 2, 0), ('3', 3, 0),
380
('4b', 4, 0), ('4a', None, 1)])
381
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
382
'forward', include_merges=False))
383
self.assertEqual(revisions, [('1', 1, 0), ('2', 2, 0), ('3', 3, 0),
1291
386
def test_get_view_revisions_merge_reverse(self):
1292
387
"""Test get_view_revisions in reverse when there are merges"""
1293
388
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
1295
self.addCleanup(wt.unlock)
1296
revisions = list(log.get_view_revisions(
1297
mainline_revs, rev_nos, wt.branch, 'reverse'))
1298
self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
1299
('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
1301
revisions = list(log.get_view_revisions(
1302
mainline_revs, rev_nos, wt.branch, 'reverse',
1303
include_merges=False))
1304
self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
389
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
391
self.assertEqual(revisions, [('4b', 4, 0), ('4a', None, 1),
392
('3', 3, 0), ('2', 2, 0), ('1', 1, 0)])
393
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
394
'reverse', include_merges=False))
395
self.assertEqual(revisions, [('4b', 4, 0), ('3', 3, 0), ('2', 2, 0),
1308
398
def test_get_view_revisions_merge2(self):
1309
399
"""Test get_view_revisions when there are merges"""
1310
mainline_revs, rev_nos, b = self.make_branch_with_many_merges()
1312
self.addCleanup(b.unlock)
1313
revisions = list(log.get_view_revisions(
1314
mainline_revs, rev_nos, b, 'forward'))
1315
expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
1316
('3b', '2.2.1', 1), ('3a', '2.1.1', 2), ('4b', '4', 0),
1318
self.assertEqual(expected, revisions)
1319
revisions = list(log.get_view_revisions(
1320
mainline_revs, rev_nos, b, 'forward',
1321
include_merges=False))
1322
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
1327
def test_file_id_for_range(self):
1328
mainline_revs, rev_nos, b = self.make_branch_with_many_merges()
1330
self.addCleanup(b.unlock)
1332
def rev_from_rev_id(revid, branch):
1333
revspec = revisionspec.RevisionSpec.from_string('revid:%s' % revid)
1334
return revspec.in_history(branch)
1336
def view_revs(start_rev, end_rev, file_id, direction):
1337
revs = log.calculate_view_revisions(
1339
start_rev, # start_revision
1340
end_rev, # end_revision
1341
direction, # direction
1342
file_id, # specific_fileid
1343
True, # generate_merge_revisions
1347
rev_3a = rev_from_rev_id('3a', b)
1348
rev_4b = rev_from_rev_id('4b', b)
1349
self.assertEqual([('3c', '3', 0), ('3b', '2.2.1', 1), ('3a', '2.1.1', 2)],
1350
view_revs(rev_3a, rev_4b, 'f-id', 'reverse'))
1351
# Note: 3c still appears before 3a here because of depth-based sorting
1352
self.assertEqual([('3c', '3', 0), ('3b', '2.2.1', 1), ('3a', '2.1.1', 2)],
1353
view_revs(rev_3a, rev_4b, 'f-id', 'forward'))
1356
class TestGetRevisionsTouchingFileID(tests.TestCaseWithTransport):
1358
def create_tree_with_single_merge(self):
1359
"""Create a branch with a moderate layout.
1361
The revision graph looks like:
1369
In this graph, A introduced files f1 and f2 and f3.
1370
B modifies f1 and f3, and C modifies f2 and f3.
1371
D merges the changes from B and C and resolves the conflict for f3.
1373
# TODO: jam 20070218 This seems like it could really be done
1374
# with make_branch_and_memory_tree() if we could just
1375
# create the content of those files.
1376
# TODO: jam 20070218 Another alternative is that we would really
1377
# like to only create this tree 1 time for all tests that
1378
# use it. Since 'log' only uses the tree in a readonly
1379
# fashion, it seems a shame to regenerate an identical
1380
# tree for each test.
1381
tree = self.make_branch_and_tree('tree')
1383
self.addCleanup(tree.unlock)
1385
self.build_tree_contents([('tree/f1', 'A\n'),
1389
tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
1390
tree.commit('A', rev_id='A')
1392
self.build_tree_contents([('tree/f2', 'A\nC\n'),
1393
('tree/f3', 'A\nC\n'),
1395
tree.commit('C', rev_id='C')
1396
# Revert back to A to build the other history.
1397
tree.set_last_revision('A')
1398
tree.branch.set_last_revision_info(1, 'A')
1399
self.build_tree_contents([('tree/f1', 'A\nB\n'),
1401
('tree/f3', 'A\nB\n'),
1403
tree.commit('B', rev_id='B')
1404
tree.set_parent_ids(['B', 'C'])
1405
self.build_tree_contents([('tree/f1', 'A\nB\n'),
1406
('tree/f2', 'A\nC\n'),
1407
('tree/f3', 'A\nB\nC\n'),
1409
tree.commit('D', rev_id='D')
1411
# Switch to a read lock for this tree.
1412
# We still have an addCleanup(tree.unlock) pending
1417
def check_delta(self, delta, **kw):
1418
"""Check the filenames touched by a delta are as expected.
1420
Caller only have to pass in the list of files for each part, all
1421
unspecified parts are considered empty (and checked as such).
1423
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
1424
# By default we expect an empty list
1425
expected = kw.get(n, [])
1426
# strip out only the path components
1427
got = [x[0] for x in getattr(delta, n)]
1428
self.assertEqual(expected, got)
1430
def test_tree_with_single_merge(self):
1431
"""Make sure the tree layout is correct."""
1432
tree = self.create_tree_with_single_merge()
1433
rev_A_tree = tree.branch.repository.revision_tree('A')
1434
rev_B_tree = tree.branch.repository.revision_tree('B')
1435
rev_C_tree = tree.branch.repository.revision_tree('C')
1436
rev_D_tree = tree.branch.repository.revision_tree('D')
1438
self.check_delta(rev_B_tree.changes_from(rev_A_tree),
1439
modified=['f1', 'f3'])
1441
self.check_delta(rev_C_tree.changes_from(rev_A_tree),
1442
modified=['f2', 'f3'])
1444
self.check_delta(rev_D_tree.changes_from(rev_B_tree),
1445
modified=['f2', 'f3'])
1447
self.check_delta(rev_D_tree.changes_from(rev_C_tree),
1448
modified=['f1', 'f3'])
1450
def assertAllRevisionsForFileID(self, tree, file_id, revisions):
1451
"""Ensure _filter_revisions_touching_file_id returns the right values.
1453
Get the return value from _filter_revisions_touching_file_id and make
1454
sure they are correct.
1456
# The api for _filter_revisions_touching_file_id is a little crazy.
1457
# So we do the setup here.
1458
mainline = tree.branch.revision_history()
1459
mainline.insert(0, None)
1460
revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
1461
view_revs_iter = log.get_view_revisions(mainline, revnos, tree.branch,
1463
actual_revs = log._filter_revisions_touching_file_id(
1466
list(view_revs_iter))
1467
self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
1469
def test_file_id_f1(self):
1470
tree = self.create_tree_with_single_merge()
1471
# f1 should be marked as modified by revisions A and B
1472
self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
1474
def test_file_id_f2(self):
1475
tree = self.create_tree_with_single_merge()
1476
# f2 should be marked as modified by revisions A, C, and D
1477
# because D merged the changes from C.
1478
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1480
def test_file_id_f3(self):
1481
tree = self.create_tree_with_single_merge()
1482
# f3 should be marked as modified by revisions A, B, C, and D
1483
self.assertAllRevisionsForFileID(tree, 'f3-id', ['D', 'C', 'B', 'A'])
1485
def test_file_id_with_ghosts(self):
1486
# This is testing bug #209948, where having a ghost would cause
1487
# _filter_revisions_touching_file_id() to fail.
1488
tree = self.create_tree_with_single_merge()
1489
# We need to add a revision, so switch back to a write-locked tree
1490
# (still a single addCleanup(tree.unlock) pending).
1493
first_parent = tree.last_revision()
1494
tree.set_parent_ids([first_parent, 'ghost-revision-id'])
1495
self.build_tree_contents([('tree/f1', 'A\nB\nXX\n')])
1496
tree.commit('commit with a ghost', rev_id='XX')
1497
self.assertAllRevisionsForFileID(tree, 'f1-id', ['XX', 'B', 'A'])
1498
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1500
def test_unknown_file_id(self):
1501
tree = self.create_tree_with_single_merge()
1502
self.assertAllRevisionsForFileID(tree, 'unknown', [])
1504
def test_empty_branch_unknown_file_id(self):
1505
tree = self.make_branch_and_tree('tree')
1506
self.assertAllRevisionsForFileID(tree, 'unknown', [])
1509
class TestShowChangedRevisions(tests.TestCaseWithTransport):
1511
def test_show_changed_revisions_verbose(self):
1512
tree = self.make_branch_and_tree('tree_a')
1513
self.build_tree(['tree_a/foo'])
1515
tree.commit('bar', rev_id='bar-id')
1516
s = self.make_utf8_encoded_stringio()
1517
log.show_changed_revisions(tree.branch, [], ['bar-id'], s)
1518
self.assertContainsRe(s.getvalue(), 'bar')
1519
self.assertNotContainsRe(s.getvalue(), 'foo')
1522
class TestLogFormatter(tests.TestCase):
1524
def test_short_committer(self):
1525
rev = revision.Revision('a-id')
1526
rev.committer = 'John Doe <jdoe@example.com>'
1527
lf = log.LogFormatter(None)
1528
self.assertEqual('John Doe', lf.short_committer(rev))
1529
rev.committer = 'John Smith <jsmith@example.com>'
1530
self.assertEqual('John Smith', lf.short_committer(rev))
1531
rev.committer = 'John Smith'
1532
self.assertEqual('John Smith', lf.short_committer(rev))
1533
rev.committer = 'jsmith@example.com'
1534
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1535
rev.committer = '<jsmith@example.com>'
1536
self.assertEqual('jsmith@example.com', lf.short_committer(rev))
1537
rev.committer = 'John Smith jsmith@example.com'
1538
self.assertEqual('John Smith', lf.short_committer(rev))
1540
def test_short_author(self):
1541
rev = revision.Revision('a-id')
1542
rev.committer = 'John Doe <jdoe@example.com>'
1543
lf = log.LogFormatter(None)
1544
self.assertEqual('John Doe', lf.short_author(rev))
1545
rev.properties['author'] = 'John Smith <jsmith@example.com>'
1546
self.assertEqual('John Smith', lf.short_author(rev))
1547
rev.properties['author'] = 'John Smith'
1548
self.assertEqual('John Smith', lf.short_author(rev))
1549
rev.properties['author'] = 'jsmith@example.com'
1550
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1551
rev.properties['author'] = '<jsmith@example.com>'
1552
self.assertEqual('jsmith@example.com', lf.short_author(rev))
1553
rev.properties['author'] = 'John Smith jsmith@example.com'
1554
self.assertEqual('John Smith', lf.short_author(rev))
1555
del rev.properties['author']
1556
rev.properties['authors'] = ('John Smith <jsmith@example.com>\n'
1557
'Jane Rey <jrey@example.com>')
1558
self.assertEqual('John Smith', lf.short_author(rev))
1561
class TestReverseByDepth(tests.TestCase):
1562
"""Test reverse_by_depth behavior.
1564
This is used to present revisions in forward (oldest first) order in a nice
1567
The tests use lighter revision description to ease reading.
1570
def assertReversed(self, forward, backward):
1571
# Transform the descriptions to suit the API: tests use (revno, depth),
1572
# while the API expects (revid, revno, depth)
1573
def complete_revisions(l):
1574
"""Transform the description to suit the API.
1576
Tests use (revno, depth) whil the API expects (revid, revno, depth).
1577
Since the revid is arbitrary, we just duplicate revno
1579
return [ (r, r, d) for r, d in l]
1580
forward = complete_revisions(forward)
1581
backward= complete_revisions(backward)
1582
self.assertEqual(forward, log.reverse_by_depth(backward))
1585
def test_mainline_revisions(self):
1586
self.assertReversed([( '1', 0), ('2', 0)],
1587
[('2', 0), ('1', 0)])
1589
def test_merged_revisions(self):
1590
self.assertReversed([('1', 0), ('2', 0), ('2.2', 1), ('2.1', 1),],
1591
[('2', 0), ('2.1', 1), ('2.2', 1), ('1', 0),])
1592
def test_shifted_merged_revisions(self):
1593
"""Test irregular layout.
1595
Requesting revisions touching a file can produce "holes" in the depths.
1597
self.assertReversed([('1', 0), ('2', 0), ('1.1', 2), ('1.2', 2),],
1598
[('2', 0), ('1.2', 2), ('1.1', 2), ('1', 0),])
1600
def test_merged_without_child_revisions(self):
1601
"""Test irregular layout.
1603
Revision ranges can produce "holes" in the depths.
1605
# When a revision of higher depth doesn't follow one of lower depth, we
1606
# assume a lower depth one is virtually there
1607
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1608
[('4', 4), ('3', 3), ('2', 2), ('1', 2),])
1609
# So we get the same order after reversing below even if the original
1610
# revisions are not in the same order.
1611
self.assertReversed([('1', 2), ('2', 2), ('3', 3), ('4', 4)],
1612
[('3', 3), ('4', 4), ('2', 2), ('1', 2),])
1615
class TestHistoryChange(tests.TestCaseWithTransport):
1617
def setup_a_tree(self):
1618
tree = self.make_branch_and_tree('tree')
1620
self.addCleanup(tree.unlock)
1621
tree.commit('1a', rev_id='1a')
1622
tree.commit('2a', rev_id='2a')
1623
tree.commit('3a', rev_id='3a')
1626
def setup_ab_tree(self):
1627
tree = self.setup_a_tree()
1628
tree.set_last_revision('1a')
1629
tree.branch.set_last_revision_info(1, '1a')
1630
tree.commit('2b', rev_id='2b')
1631
tree.commit('3b', rev_id='3b')
1634
def setup_ac_tree(self):
1635
tree = self.setup_a_tree()
1636
tree.set_last_revision(revision.NULL_REVISION)
1637
tree.branch.set_last_revision_info(0, revision.NULL_REVISION)
1638
tree.commit('1c', rev_id='1c')
1639
tree.commit('2c', rev_id='2c')
1640
tree.commit('3c', rev_id='3c')
1643
def test_all_new(self):
1644
tree = self.setup_ab_tree()
1645
old, new = log.get_history_change('1a', '3a', tree.branch.repository)
1646
self.assertEqual([], old)
1647
self.assertEqual(['2a', '3a'], new)
1649
def test_all_old(self):
1650
tree = self.setup_ab_tree()
1651
old, new = log.get_history_change('3a', '1a', tree.branch.repository)
1652
self.assertEqual([], new)
1653
self.assertEqual(['2a', '3a'], old)
1655
def test_null_old(self):
1656
tree = self.setup_ab_tree()
1657
old, new = log.get_history_change(revision.NULL_REVISION,
1658
'3a', tree.branch.repository)
1659
self.assertEqual([], old)
1660
self.assertEqual(['1a', '2a', '3a'], new)
1662
def test_null_new(self):
1663
tree = self.setup_ab_tree()
1664
old, new = log.get_history_change('3a', revision.NULL_REVISION,
1665
tree.branch.repository)
1666
self.assertEqual([], new)
1667
self.assertEqual(['1a', '2a', '3a'], old)
1669
def test_diverged(self):
1670
tree = self.setup_ab_tree()
1671
old, new = log.get_history_change('3a', '3b', tree.branch.repository)
1672
self.assertEqual(old, ['2a', '3a'])
1673
self.assertEqual(new, ['2b', '3b'])
1675
def test_unrelated(self):
1676
tree = self.setup_ac_tree()
1677
old, new = log.get_history_change('3a', '3c', tree.branch.repository)
1678
self.assertEqual(old, ['1a', '2a', '3a'])
1679
self.assertEqual(new, ['1c', '2c', '3c'])
1681
def test_show_branch_change(self):
1682
tree = self.setup_ab_tree()
1684
log.show_branch_change(tree.branch, s, 3, '3a')
1685
self.assertContainsRe(s.getvalue(),
1686
'[*]{60}\nRemoved Revisions:\n(.|\n)*2a(.|\n)*3a(.|\n)*'
1687
'[*]{60}\n\nAdded Revisions:\n(.|\n)*2b(.|\n)*3b')
1689
def test_show_branch_change_no_change(self):
1690
tree = self.setup_ab_tree()
1692
log.show_branch_change(tree.branch, s, 3, '3b')
1693
self.assertEqual(s.getvalue(),
1694
'Nothing seems to have changed\n')
1696
def test_show_branch_change_no_old(self):
1697
tree = self.setup_ab_tree()
1699
log.show_branch_change(tree.branch, s, 2, '2b')
1700
self.assertContainsRe(s.getvalue(), 'Added Revisions:')
1701
self.assertNotContainsRe(s.getvalue(), 'Removed Revisions:')
1703
def test_show_branch_change_no_new(self):
1704
tree = self.setup_ab_tree()
1705
tree.branch.set_last_revision_info(2, '2b')
1707
log.show_branch_change(tree.branch, s, 3, '3b')
1708
self.assertContainsRe(s.getvalue(), 'Removed Revisions:')
1709
self.assertNotContainsRe(s.getvalue(), 'Added Revisions:')
400
mainline_revs, rev_nos, wt = self.make_tree_with_many_merges()
401
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
403
expected = [('1', 1, 0), ('2', 2, 0), ('3c', 3, 0), ('3a', None, 1),
404
('3b', None, 1), ('4b', 4, 0), ('4a', None, 1)]
405
self.assertEqual(revisions, expected)
406
revisions = list(get_view_revisions(mainline_revs, rev_nos, wt.branch,
407
'forward', include_merges=False))
408
self.assertEqual(revisions, [('1', 1, 0), ('2', 2, 0), ('3c', 3, 0),