15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
from cStringIO import StringIO
28
revision as _mod_revision,
33
from ..sixish import (
41
from ..tests.scenarios import load_tests_apply_scenarios
44
load_tests = load_tests_apply_scenarios
47
def subst_dates(string):
48
"""Replace date strings with constant values."""
49
return re.sub(br'\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} [-\+]\d{4}',
50
b'YYYY-MM-DD HH:MM:SS +ZZZZ', string)
23
from tempfile import TemporaryFile
25
from bzrlib import tests
26
from bzrlib.diff import (
36
from bzrlib.errors import BinaryFile, NoDiff, ExecutableMissing
37
import bzrlib.osutils as osutils
38
import bzrlib.revision as _mod_revision
39
import bzrlib.transform as transform
40
import bzrlib.patiencediff
41
import bzrlib._patiencediff_py
42
from bzrlib.tests import (Feature, TestCase, TestCaseWithTransport,
43
TestCaseInTempDir, TestSkipped)
46
class _AttribFeature(Feature):
49
if (sys.platform not in ('cygwin', 'win32')):
52
proc = subprocess.Popen(['attrib', '.'], stdout=subprocess.PIPE)
55
return (0 == proc.wait())
57
def feature_name(self):
58
return 'attrib Windows command-line tool'
60
AttribFeature = _AttribFeature()
63
class _CompiledPatienceDiffFeature(Feature):
67
import bzrlib._patiencediff_c
72
def feature_name(self):
73
return 'bzrlib._patiencediff_c'
75
CompiledPatienceDiffFeature = _CompiledPatienceDiffFeature()
53
78
def udiff_lines(old, new, allow_binary=False):
55
diff.internal_diff('old', old, 'new', new, output, allow_binary)
80
internal_diff('old', old, 'new', new, output, allow_binary)
57
82
return output.readlines()
60
85
def external_udiff_lines(old, new, use_stringio=False):
62
# BytesIO has no fileno, so it tests a different codepath
87
# StringIO has no fileno, so it tests a different codepath
65
output = tempfile.TemporaryFile()
90
output = TemporaryFile()
67
diff.external_diff('old', old, 'new', new, output, diff_opts=['-u'])
69
raise tests.TestSkipped('external "diff" not present to test')
92
external_diff('old', old, 'new', new, output, diff_opts=['-u'])
94
raise TestSkipped('external "diff" not present to test')
71
96
lines = output.readlines()
77
"""Simple file-like object that allows writes with any type and records."""
80
self.write_record = []
82
def write(self, data):
83
self.write_record.append(data)
85
def check_types(self, testcase, expected_type):
87
any(not isinstance(o, expected_type) for o in self.write_record),
88
"Not all writes of type %s: %r" % (
89
expected_type.__name__, self.write_record))
92
class TestDiffOptions(tests.TestCase):
94
def test_unified_added(self):
95
"""Check for default style '-u' only if no other style specified
98
# Verify that style defaults to unified, id est '-u' appended
99
# to option list, in the absence of an alternative style.
100
self.assertEqual(['-a', '-u'], diff.default_style_unified(['-a']))
103
class TestDiffOptionsScenarios(tests.TestCase):
105
scenarios = [(s, dict(style=s)) for s in diff.style_option_list]
106
style = None # Set by load_tests_apply_scenarios from scenarios
108
def test_unified_not_added(self):
109
# Verify that for all valid style options, '-u' is not
110
# appended to option list.
111
ret_opts = diff.default_style_unified(diff_opts=["%s" % (self.style,)])
112
self.assertEqual(["%s" % (self.style,)], ret_opts)
115
class TestDiff(tests.TestCase):
101
class TestDiff(TestCase):
117
103
def test_add_nl(self):
118
104
"""diff generates a valid diff for patches that add a newline"""
119
lines = udiff_lines([b'boo'], [b'boo\n'])
105
lines = udiff_lines(['boo'], ['boo\n'])
120
106
self.check_patch(lines)
121
self.assertEqual(lines[4], b'\\ No newline at end of file\n')
122
## "expected no-nl, got %r" % lines[4]
107
self.assertEquals(lines[4], '\\ No newline at end of file\n')
108
## "expected no-nl, got %r" % lines[4]
124
110
def test_add_nl_2(self):
125
111
"""diff generates a valid diff for patches that change last line and
128
lines = udiff_lines([b'boo'], [b'goo\n'])
114
lines = udiff_lines(['boo'], ['goo\n'])
129
115
self.check_patch(lines)
130
self.assertEqual(lines[4], b'\\ No newline at end of file\n')
131
## "expected no-nl, got %r" % lines[4]
116
self.assertEquals(lines[4], '\\ No newline at end of file\n')
117
## "expected no-nl, got %r" % lines[4]
133
119
def test_remove_nl(self):
134
120
"""diff generates a valid diff for patches that change last line and
137
lines = udiff_lines([b'boo\n'], [b'boo'])
123
lines = udiff_lines(['boo\n'], ['boo'])
138
124
self.check_patch(lines)
139
self.assertEqual(lines[5], b'\\ No newline at end of file\n')
140
## "expected no-nl, got %r" % lines[5]
125
self.assertEquals(lines[5], '\\ No newline at end of file\n')
126
## "expected no-nl, got %r" % lines[5]
142
128
def check_patch(self, lines):
143
self.assertTrue(len(lines) > 1)
144
## "Not enough lines for a file header for patch:\n%s" % "".join(lines)
145
self.assertTrue(lines[0].startswith(b'---'))
146
## 'No orig line for patch:\n%s' % "".join(lines)
147
self.assertTrue(lines[1].startswith(b'+++'))
148
## 'No mod line for patch:\n%s' % "".join(lines)
149
self.assertTrue(len(lines) > 2)
150
## "No hunks for patch:\n%s" % "".join(lines)
151
self.assertTrue(lines[2].startswith(b'@@'))
152
## "No hunk header for patch:\n%s" % "".join(lines)
153
self.assertTrue(b'@@' in lines[2][2:])
154
## "Unterminated hunk header for patch:\n%s" % "".join(lines)
129
self.assert_(len(lines) > 1)
130
## "Not enough lines for a file header for patch:\n%s" % "".join(lines)
131
self.assert_(lines[0].startswith ('---'))
132
## 'No orig line for patch:\n%s' % "".join(lines)
133
self.assert_(lines[1].startswith ('+++'))
134
## 'No mod line for patch:\n%s' % "".join(lines)
135
self.assert_(len(lines) > 2)
136
## "No hunks for patch:\n%s" % "".join(lines)
137
self.assert_(lines[2].startswith('@@'))
138
## "No hunk header for patch:\n%s" % "".join(lines)
139
self.assert_('@@' in lines[2][2:])
140
## "Unterminated hunk header for patch:\n%s" % "".join(lines)
156
142
def test_binary_lines(self):
158
uni_lines = [1023 * b'a' + b'\x00']
159
self.assertRaises(errors.BinaryFile, udiff_lines, uni_lines, empty)
160
self.assertRaises(errors.BinaryFile, udiff_lines, empty, uni_lines)
161
udiff_lines(uni_lines, empty, allow_binary=True)
162
udiff_lines(empty, uni_lines, allow_binary=True)
143
self.assertRaises(BinaryFile, udiff_lines, [1023 * 'a' + '\x00'], [])
144
self.assertRaises(BinaryFile, udiff_lines, [], [1023 * 'a' + '\x00'])
145
udiff_lines([1023 * 'a' + '\x00'], [], allow_binary=True)
146
udiff_lines([], [1023 * 'a' + '\x00'], allow_binary=True)
164
148
def test_external_diff(self):
165
lines = external_udiff_lines([b'boo\n'], [b'goo\n'])
149
lines = external_udiff_lines(['boo\n'], ['goo\n'])
166
150
self.check_patch(lines)
167
self.assertEqual(b'\n', lines[-1])
151
self.assertEqual('\n', lines[-1])
169
153
def test_external_diff_no_fileno(self):
170
154
# Make sure that we can handle not having a fileno, even
171
155
# if the diff is large
172
lines = external_udiff_lines([b'boo\n'] * 10000,
156
lines = external_udiff_lines(['boo\n']*10000,
174
158
use_stringio=True)
175
159
self.check_patch(lines)
177
161
def test_external_diff_binary_lang_c(self):
178
163
for lang in ('LANG', 'LC_ALL', 'LANGUAGE'):
179
self.overrideEnv(lang, 'C')
180
lines = external_udiff_lines([b'\x00foobar\n'], [b'foo\x00bar\n'])
181
# Older versions of diffutils say "Binary files", newer
182
# versions just say "Files".
183
self.assertContainsRe(
184
lines[0], b'(Binary f|F)iles old and new differ\n')
185
self.assertEqual(lines[1:], [b'\n'])
164
old_env[lang] = osutils.set_or_unset_env(lang, 'C')
166
lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
167
# Older versions of diffutils say "Binary files", newer
168
# versions just say "Files".
169
self.assertContainsRe(lines[0],
170
'(Binary f|F)iles old and new differ\n')
171
self.assertEquals(lines[1:], ['\n'])
173
for lang, old_val in old_env.iteritems():
174
osutils.set_or_unset_env(lang, old_val)
187
176
def test_no_external_diff(self):
188
177
"""Check that NoDiff is raised when diff is not available"""
189
# Make sure no 'diff' command is available
190
# XXX: Weird, using None instead of '' breaks the test -- vila 20101216
191
self.overrideEnv('PATH', '')
192
self.assertRaises(errors.NoDiff, diff.external_diff,
193
b'old', [b'boo\n'], b'new', [b'goo\n'],
194
BytesIO(), diff_opts=['-u'])
178
# Use os.environ['PATH'] to make sure no 'diff' command is available
179
orig_path = os.environ['PATH']
181
os.environ['PATH'] = ''
182
self.assertRaises(NoDiff, external_diff,
183
'old', ['boo\n'], 'new', ['goo\n'],
184
StringIO(), diff_opts=['-u'])
186
os.environ['PATH'] = orig_path
196
188
def test_internal_diff_default(self):
197
189
# Default internal diff encoding is utf8
199
diff.internal_diff(u'old_\xb5', [b'old_text\n'],
200
u'new_\xe5', [b'new_text\n'], output)
191
internal_diff(u'old_\xb5', ['old_text\n'],
192
u'new_\xe5', ['new_text\n'], output)
201
193
lines = output.getvalue().splitlines(True)
202
194
self.check_patch(lines)
203
self.assertEqual([b'--- old_\xc2\xb5\n',
204
b'+++ new_\xc3\xa5\n',
205
b'@@ -1,1 +1,1 @@\n',
195
self.assertEquals(['--- old_\xc2\xb5\n',
196
'+++ new_\xc3\xa5\n',
211
204
def test_internal_diff_utf8(self):
213
diff.internal_diff(u'old_\xb5', [b'old_text\n'],
214
u'new_\xe5', [b'new_text\n'], output,
215
path_encoding='utf8')
206
internal_diff(u'old_\xb5', ['old_text\n'],
207
u'new_\xe5', ['new_text\n'], output,
208
path_encoding='utf8')
216
209
lines = output.getvalue().splitlines(True)
217
210
self.check_patch(lines)
218
self.assertEqual([b'--- old_\xc2\xb5\n',
219
b'+++ new_\xc3\xa5\n',
220
b'@@ -1,1 +1,1 @@\n',
211
self.assertEquals(['--- old_\xc2\xb5\n',
212
'+++ new_\xc3\xa5\n',
226
220
def test_internal_diff_iso_8859_1(self):
228
diff.internal_diff(u'old_\xb5', [b'old_text\n'],
229
u'new_\xe5', [b'new_text\n'], output,
230
path_encoding='iso-8859-1')
222
internal_diff(u'old_\xb5', ['old_text\n'],
223
u'new_\xe5', ['new_text\n'], output,
224
path_encoding='iso-8859-1')
231
225
lines = output.getvalue().splitlines(True)
232
226
self.check_patch(lines)
233
self.assertEqual([b'--- old_\xb5\n',
235
b'@@ -1,1 +1,1 @@\n',
227
self.assertEquals(['--- old_\xb5\n',
241
236
def test_internal_diff_no_content(self):
243
diff.internal_diff(u'old', [], u'new', [], output)
244
self.assertEqual(b'', output.getvalue())
238
internal_diff(u'old', [], u'new', [], output)
239
self.assertEqual('', output.getvalue())
246
241
def test_internal_diff_no_changes(self):
248
diff.internal_diff(u'old', [b'text\n', b'contents\n'],
249
u'new', [b'text\n', b'contents\n'],
251
self.assertEqual(b'', output.getvalue())
243
internal_diff(u'old', ['text\n', 'contents\n'],
244
u'new', ['text\n', 'contents\n'],
246
self.assertEqual('', output.getvalue())
253
248
def test_internal_diff_returns_bytes(self):
255
diff.internal_diff(u'old_\xb5', [b'old_text\n'],
256
u'new_\xe5', [b'new_text\n'], output)
257
output.check_types(self, bytes)
259
def test_internal_diff_default_context(self):
261
diff.internal_diff('old', [b'same_text\n', b'same_text\n', b'same_text\n',
262
b'same_text\n', b'same_text\n', b'old_text\n'],
263
'new', [b'same_text\n', b'same_text\n', b'same_text\n',
264
b'same_text\n', b'same_text\n', b'new_text\n'], output)
265
lines = output.getvalue().splitlines(True)
266
self.check_patch(lines)
267
self.assertEqual([b'--- old\n',
269
b'@@ -3,4 +3,4 @@\n',
278
def test_internal_diff_no_context(self):
280
diff.internal_diff('old', [b'same_text\n', b'same_text\n', b'same_text\n',
281
b'same_text\n', b'same_text\n', b'old_text\n'],
282
'new', [b'same_text\n', b'same_text\n', b'same_text\n',
283
b'same_text\n', b'same_text\n', b'new_text\n'], output,
285
lines = output.getvalue().splitlines(True)
286
self.check_patch(lines)
287
self.assertEqual([b'--- old\n',
289
b'@@ -6,1 +6,1 @@\n',
295
def test_internal_diff_more_context(self):
297
diff.internal_diff('old', [b'same_text\n', b'same_text\n', b'same_text\n',
298
b'same_text\n', b'same_text\n', b'old_text\n'],
299
'new', [b'same_text\n', b'same_text\n', b'same_text\n',
300
b'same_text\n', b'same_text\n', b'new_text\n'], output,
302
lines = output.getvalue().splitlines(True)
303
self.check_patch(lines)
304
self.assertEqual([b'--- old\n',
306
b'@@ -2,5 +2,5 @@\n',
317
class TestDiffFiles(tests.TestCaseInTempDir):
250
output = StringIO.StringIO()
251
internal_diff(u'old_\xb5', ['old_text\n'],
252
u'new_\xe5', ['new_text\n'], output)
253
self.failUnless(isinstance(output.getvalue(), str),
254
'internal_diff should return bytestrings')
257
class TestDiffFiles(TestCaseInTempDir):
319
259
def test_external_diff_binary(self):
320
260
"""The output when using external diff should use diff's i18n error"""
321
for lang in ('LANG', 'LC_ALL', 'LANGUAGE'):
322
self.overrideEnv(lang, 'C')
323
261
# Make sure external_diff doesn't fail in the current LANG
324
lines = external_udiff_lines([b'\x00foobar\n'], [b'foo\x00bar\n'])
262
lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
326
264
cmd = ['diff', '-u', '--binary', 'old', 'new']
327
with open('old', 'wb') as f:
328
f.write(b'\x00foobar\n')
329
with open('new', 'wb') as f:
330
f.write(b'foo\x00bar\n')
265
open('old', 'wb').write('\x00foobar\n')
266
open('new', 'wb').write('foo\x00bar\n')
331
267
pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
332
stdin=subprocess.PIPE)
268
stdin=subprocess.PIPE)
333
269
out, err = pipe.communicate()
270
# Diff returns '2' on Binary files.
271
self.assertEqual(2, pipe.returncode)
334
272
# We should output whatever diff tells us, plus a trailing newline
335
self.assertEqual(out.splitlines(True) + [b'\n'], lines)
338
def get_diff_as_string(tree1, tree2, specific_files=None, working_tree=None):
340
if working_tree is not None:
341
extra_trees = (working_tree,)
344
diff.show_diff_trees(tree1, tree2, output,
345
specific_files=specific_files,
346
extra_trees=extra_trees, old_label='old/',
348
return output.getvalue()
351
class TestDiffDates(tests.TestCaseWithTransport):
273
self.assertEqual(out.splitlines(True) + ['\n'], lines)
276
class TestShowDiffTreesHelper(TestCaseWithTransport):
277
"""Has a helper for running show_diff_trees"""
279
def get_diff(self, tree1, tree2, specific_files=None, working_tree=None):
281
if working_tree is not None:
282
extra_trees = (working_tree,)
285
show_diff_trees(tree1, tree2, output, specific_files=specific_files,
286
extra_trees=extra_trees, old_label='old/',
288
return output.getvalue()
291
class TestDiffDates(TestShowDiffTreesHelper):
354
294
super(TestDiffDates, self).setUp()
355
295
self.wt = self.make_branch_and_tree('.')
356
296
self.b = self.wt.branch
357
297
self.build_tree_contents([
358
('file1', b'file1 contents at rev 1\n'),
359
('file2', b'file2 contents at rev 1\n')
298
('file1', 'file1 contents at rev 1\n'),
299
('file2', 'file2 contents at rev 1\n')
361
301
self.wt.add(['file1', 'file2'])
363
303
message='Revision 1',
364
timestamp=1143849600, # 2006-04-01 00:00:00 UTC
304
timestamp=1143849600, # 2006-04-01 00:00:00 UTC
367
self.build_tree_contents([('file1', b'file1 contents at rev 2\n')])
307
self.build_tree_contents([('file1', 'file1 contents at rev 2\n')])
369
309
message='Revision 2',
370
timestamp=1143936000, # 2006-04-02 00:00:00 UTC
310
timestamp=1143936000, # 2006-04-02 00:00:00 UTC
373
self.build_tree_contents([('file2', b'file2 contents at rev 3\n')])
313
self.build_tree_contents([('file2', 'file2 contents at rev 3\n')])
375
315
message='Revision 3',
376
timestamp=1144022400, # 2006-04-03 00:00:00 UTC
316
timestamp=1144022400, # 2006-04-03 00:00:00 UTC
379
319
self.wt.remove(['file2'])
381
321
message='Revision 4',
382
timestamp=1144108800, # 2006-04-04 00:00:00 UTC
322
timestamp=1144108800, # 2006-04-04 00:00:00 UTC
385
325
self.build_tree_contents([
386
('file1', b'file1 contents in working tree\n')
326
('file1', 'file1 contents in working tree\n')
388
328
# set the date stamps for files in the working tree to known values
389
os.utime('file1', (1144195200, 1144195200)) # 2006-04-05 00:00:00 UTC
329
os.utime('file1', (1144195200, 1144195200)) # 2006-04-05 00:00:00 UTC
391
331
def test_diff_rev_tree_working_tree(self):
392
output = get_diff_as_string(self.wt.basis_tree(), self.wt)
332
output = self.get_diff(self.wt.basis_tree(), self.wt)
393
333
# note that the date for old/file1 is from rev 2 rather than from
394
334
# the basis revision (rev 4)
395
self.assertEqualDiff(output, b'''\
335
self.assertEqualDiff(output, '''\
396
336
=== modified file 'file1'
397
337
--- old/file1\t2006-04-02 00:00:00 +0000
398
338
+++ new/file1\t2006-04-05 00:00:00 +0000
468
408
self.wt.add(['dir1', 'dir2'])
469
409
self.wt.rename_one('file1', 'dir1/file1')
470
old_tree = self.b.repository.revision_tree(b'rev-1')
471
new_tree = self.b.repository.revision_tree(b'rev-4')
472
out = get_diff_as_string(old_tree, new_tree, specific_files=['dir1'],
473
working_tree=self.wt)
474
self.assertContainsRe(out, b'file1\t')
475
out = get_diff_as_string(old_tree, new_tree, specific_files=['dir2'],
476
working_tree=self.wt)
477
self.assertNotContainsRe(out, b'file1\t')
480
class TestShowDiffTrees(tests.TestCaseWithTransport):
410
old_tree = self.b.repository.revision_tree('rev-1')
411
new_tree = self.b.repository.revision_tree('rev-4')
412
out = self.get_diff(old_tree, new_tree, specific_files=['dir1'],
413
working_tree=self.wt)
414
self.assertContainsRe(out, 'file1\t')
415
out = self.get_diff(old_tree, new_tree, specific_files=['dir2'],
416
working_tree=self.wt)
417
self.assertNotContainsRe(out, 'file1\t')
421
class TestShowDiffTrees(TestShowDiffTreesHelper):
481
422
"""Direct tests for show_diff_trees"""
483
424
def test_modified_file(self):
484
425
"""Test when a file is modified."""
485
426
tree = self.make_branch_and_tree('tree')
486
self.build_tree_contents([('tree/file', b'contents\n')])
487
tree.add(['file'], [b'file-id'])
488
tree.commit('one', rev_id=b'rev-1')
427
self.build_tree_contents([('tree/file', 'contents\n')])
428
tree.add(['file'], ['file-id'])
429
tree.commit('one', rev_id='rev-1')
490
self.build_tree_contents([('tree/file', b'new contents\n')])
491
d = get_diff_as_string(tree.basis_tree(), tree)
492
self.assertContainsRe(d, b"=== modified file 'file'\n")
493
self.assertContainsRe(d, b'--- old/file\t')
494
self.assertContainsRe(d, b'\\+\\+\\+ new/file\t')
495
self.assertContainsRe(d, b'-contents\n'
496
b'\\+new contents\n')
431
self.build_tree_contents([('tree/file', 'new contents\n')])
432
diff = self.get_diff(tree.basis_tree(), tree)
433
self.assertContainsRe(diff, "=== modified file 'file'\n")
434
self.assertContainsRe(diff, '--- old/file\t')
435
self.assertContainsRe(diff, '\\+\\+\\+ new/file\t')
436
self.assertContainsRe(diff, '-contents\n'
498
439
def test_modified_file_in_renamed_dir(self):
499
440
"""Test when a file is modified in a renamed directory."""
500
441
tree = self.make_branch_and_tree('tree')
501
442
self.build_tree(['tree/dir/'])
502
self.build_tree_contents([('tree/dir/file', b'contents\n')])
503
tree.add(['dir', 'dir/file'], [b'dir-id', b'file-id'])
504
tree.commit('one', rev_id=b'rev-1')
443
self.build_tree_contents([('tree/dir/file', 'contents\n')])
444
tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
445
tree.commit('one', rev_id='rev-1')
506
447
tree.rename_one('dir', 'other')
507
self.build_tree_contents([('tree/other/file', b'new contents\n')])
508
d = get_diff_as_string(tree.basis_tree(), tree)
509
self.assertContainsRe(d, b"=== renamed directory 'dir' => 'other'\n")
510
self.assertContainsRe(d, b"=== modified file 'other/file'\n")
448
self.build_tree_contents([('tree/other/file', 'new contents\n')])
449
diff = self.get_diff(tree.basis_tree(), tree)
450
self.assertContainsRe(diff, "=== renamed directory 'dir' => 'other'\n")
451
self.assertContainsRe(diff, "=== modified file 'other/file'\n")
511
452
# XXX: This is technically incorrect, because it used to be at another
512
453
# location. What to do?
513
self.assertContainsRe(d, b'--- old/dir/file\t')
514
self.assertContainsRe(d, b'\\+\\+\\+ new/other/file\t')
515
self.assertContainsRe(d, b'-contents\n'
516
b'\\+new contents\n')
454
self.assertContainsRe(diff, '--- old/dir/file\t')
455
self.assertContainsRe(diff, '\\+\\+\\+ new/other/file\t')
456
self.assertContainsRe(diff, '-contents\n'
518
459
def test_renamed_directory(self):
519
460
"""Test when only a directory is only renamed."""
520
461
tree = self.make_branch_and_tree('tree')
521
462
self.build_tree(['tree/dir/'])
522
self.build_tree_contents([('tree/dir/file', b'contents\n')])
523
tree.add(['dir', 'dir/file'], [b'dir-id', b'file-id'])
524
tree.commit('one', rev_id=b'rev-1')
463
self.build_tree_contents([('tree/dir/file', 'contents\n')])
464
tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
465
tree.commit('one', rev_id='rev-1')
526
467
tree.rename_one('dir', 'newdir')
527
d = get_diff_as_string(tree.basis_tree(), tree)
468
diff = self.get_diff(tree.basis_tree(), tree)
528
469
# Renaming a directory should be a single "you renamed this dir" even
529
470
# when there are files inside.
530
self.assertEqual(d, b"=== renamed directory 'dir' => 'newdir'\n")
471
self.assertEqual("=== renamed directory 'dir' => 'newdir'\n", diff)
532
473
def test_renamed_file(self):
533
474
"""Test when a file is only renamed."""
534
475
tree = self.make_branch_and_tree('tree')
535
self.build_tree_contents([('tree/file', b'contents\n')])
536
tree.add(['file'], [b'file-id'])
537
tree.commit('one', rev_id=b'rev-1')
476
self.build_tree_contents([('tree/file', 'contents\n')])
477
tree.add(['file'], ['file-id'])
478
tree.commit('one', rev_id='rev-1')
539
480
tree.rename_one('file', 'newname')
540
d = get_diff_as_string(tree.basis_tree(), tree)
541
self.assertContainsRe(d, b"=== renamed file 'file' => 'newname'\n")
481
diff = self.get_diff(tree.basis_tree(), tree)
482
self.assertContainsRe(diff, "=== renamed file 'file' => 'newname'\n")
542
483
# We shouldn't have a --- or +++ line, because there is no content
544
self.assertNotContainsRe(d, b'---')
485
self.assertNotContainsRe(diff, '---')
546
487
def test_renamed_and_modified_file(self):
547
488
"""Test when a file is only renamed."""
548
489
tree = self.make_branch_and_tree('tree')
549
self.build_tree_contents([('tree/file', b'contents\n')])
550
tree.add(['file'], [b'file-id'])
551
tree.commit('one', rev_id=b'rev-1')
490
self.build_tree_contents([('tree/file', 'contents\n')])
491
tree.add(['file'], ['file-id'])
492
tree.commit('one', rev_id='rev-1')
553
494
tree.rename_one('file', 'newname')
554
self.build_tree_contents([('tree/newname', b'new contents\n')])
555
d = get_diff_as_string(tree.basis_tree(), tree)
556
self.assertContainsRe(d, b"=== renamed file 'file' => 'newname'\n")
557
self.assertContainsRe(d, b'--- old/file\t')
558
self.assertContainsRe(d, b'\\+\\+\\+ new/newname\t')
559
self.assertContainsRe(d, b'-contents\n'
560
b'\\+new contents\n')
495
self.build_tree_contents([('tree/newname', 'new contents\n')])
496
diff = self.get_diff(tree.basis_tree(), tree)
497
self.assertContainsRe(diff, "=== renamed file 'file' => 'newname'\n")
498
self.assertContainsRe(diff, '--- old/file\t')
499
self.assertContainsRe(diff, '\\+\\+\\+ new/newname\t')
500
self.assertContainsRe(diff, '-contents\n'
562
504
def test_internal_diff_exec_property(self):
563
505
tree = self.make_branch_and_tree('tree')
565
tt = tree.get_transform()
566
tt.new_file('a', tt.root, [b'contents\n'], b'a-id', True)
567
tt.new_file('b', tt.root, [b'contents\n'], b'b-id', False)
568
tt.new_file('c', tt.root, [b'contents\n'], b'c-id', True)
569
tt.new_file('d', tt.root, [b'contents\n'], b'd-id', False)
570
tt.new_file('e', tt.root, [b'contents\n'], b'control-e-id', True)
571
tt.new_file('f', tt.root, [b'contents\n'], b'control-f-id', False)
507
tt = transform.TreeTransform(tree)
508
tt.new_file('a', tt.root, 'contents\n', 'a-id', True)
509
tt.new_file('b', tt.root, 'contents\n', 'b-id', False)
510
tt.new_file('c', tt.root, 'contents\n', 'c-id', True)
511
tt.new_file('d', tt.root, 'contents\n', 'd-id', False)
512
tt.new_file('e', tt.root, 'contents\n', 'control-e-id', True)
513
tt.new_file('f', tt.root, 'contents\n', 'control-f-id', False)
573
tree.commit('one', rev_id=b'rev-1')
515
tree.commit('one', rev_id='rev-1')
575
tt = tree.get_transform()
576
tt.set_executability(False, tt.trans_id_file_id(b'a-id'))
577
tt.set_executability(True, tt.trans_id_file_id(b'b-id'))
578
tt.set_executability(False, tt.trans_id_file_id(b'c-id'))
579
tt.set_executability(True, tt.trans_id_file_id(b'd-id'))
517
tt = transform.TreeTransform(tree)
518
tt.set_executability(False, tt.trans_id_file_id('a-id'))
519
tt.set_executability(True, tt.trans_id_file_id('b-id'))
520
tt.set_executability(False, tt.trans_id_file_id('c-id'))
521
tt.set_executability(True, tt.trans_id_file_id('d-id'))
581
523
tree.rename_one('c', 'new-c')
582
524
tree.rename_one('d', 'new-d')
584
d = get_diff_as_string(tree.basis_tree(), tree)
586
self.assertContainsRe(d, br"file 'a'.*\(properties changed:"
588
self.assertContainsRe(d, br"file 'b'.*\(properties changed:"
590
self.assertContainsRe(d, br"file 'c'.*\(properties changed:"
592
self.assertContainsRe(d, br"file 'd'.*\(properties changed:"
594
self.assertNotContainsRe(d, br"file 'e'")
595
self.assertNotContainsRe(d, br"file 'f'")
526
diff = self.get_diff(tree.basis_tree(), tree)
528
self.assertContainsRe(diff, r"file 'a'.*\(properties changed:.*\+x to -x.*\)")
529
self.assertContainsRe(diff, r"file 'b'.*\(properties changed:.*-x to \+x.*\)")
530
self.assertContainsRe(diff, r"file 'c'.*\(properties changed:.*\+x to -x.*\)")
531
self.assertContainsRe(diff, r"file 'd'.*\(properties changed:.*-x to \+x.*\)")
532
self.assertNotContainsRe(diff, r"file 'e'")
533
self.assertNotContainsRe(diff, r"file 'f'")
597
536
def test_binary_unicode_filenames(self):
598
537
"""Test that contents of files are *not* encoded in UTF-8 when there
599
538
is a binary file in the diff.
601
540
# See https://bugs.launchpad.net/bugs/110092.
602
self.requireFeature(features.UnicodeFilenameFeature)
541
self.requireFeature(tests.UnicodeFilenameFeature)
543
# This bug isn't triggered with cStringIO.
544
from StringIO import StringIO
604
545
tree = self.make_branch_and_tree('tree')
605
546
alpha, omega = u'\u03b1', u'\u03c9'
606
547
alpha_utf8, omega_utf8 = alpha.encode('utf8'), omega.encode('utf8')
607
548
self.build_tree_contents(
608
[('tree/' + alpha, b'\0'),
549
[('tree/' + alpha, chr(0)),
609
550
('tree/' + omega,
610
(b'The %s and the %s\n' % (alpha_utf8, omega_utf8)))])
611
tree.add([alpha], [b'file-id'])
612
tree.add([omega], [b'file-id-2'])
613
diff_content = StubO()
614
diff.show_diff_trees(tree.basis_tree(), tree, diff_content)
615
diff_content.check_types(self, bytes)
616
d = b''.join(diff_content.write_record)
617
self.assertContainsRe(d, br"=== added file '%s'" % alpha_utf8)
618
self.assertContainsRe(d, b"Binary files a/%s.*and b/%s.* differ\n"
619
% (alpha_utf8, alpha_utf8))
620
self.assertContainsRe(d, br"=== added file '%s'" % omega_utf8)
621
self.assertContainsRe(d, br"--- a/%s" % (omega_utf8,))
622
self.assertContainsRe(d, br"\+\+\+ b/%s" % (omega_utf8,))
551
('The %s and the %s\n' % (alpha_utf8, omega_utf8)))])
552
tree.add([alpha], ['file-id'])
553
tree.add([omega], ['file-id-2'])
554
diff_content = StringIO()
555
show_diff_trees(tree.basis_tree(), tree, diff_content)
556
diff = diff_content.getvalue()
557
self.assertContainsRe(diff, r"=== added file '%s'" % alpha_utf8)
558
self.assertContainsRe(
559
diff, "Binary files a/%s.*and b/%s.* differ\n" % (alpha_utf8, alpha_utf8))
560
self.assertContainsRe(diff, r"=== added file '%s'" % omega_utf8)
561
self.assertContainsRe(diff, r"--- a/%s" % (omega_utf8,))
562
self.assertContainsRe(diff, r"\+\+\+ b/%s" % (omega_utf8,))
624
564
def test_unicode_filename(self):
625
565
"""Test when the filename are unicode."""
626
self.requireFeature(features.UnicodeFilenameFeature)
566
self.requireFeature(tests.UnicodeFilenameFeature)
628
568
alpha, omega = u'\u03b1', u'\u03c9'
629
569
autf8, outf8 = alpha.encode('utf8'), omega.encode('utf8')
631
571
tree = self.make_branch_and_tree('tree')
632
self.build_tree_contents([('tree/ren_' + alpha, b'contents\n')])
633
tree.add(['ren_' + alpha], [b'file-id-2'])
634
self.build_tree_contents([('tree/del_' + alpha, b'contents\n')])
635
tree.add(['del_' + alpha], [b'file-id-3'])
636
self.build_tree_contents([('tree/mod_' + alpha, b'contents\n')])
637
tree.add(['mod_' + alpha], [b'file-id-4'])
639
tree.commit('one', rev_id=b'rev-1')
641
tree.rename_one('ren_' + alpha, 'ren_' + omega)
642
tree.remove('del_' + alpha)
643
self.build_tree_contents([('tree/add_' + alpha, b'contents\n')])
644
tree.add(['add_' + alpha], [b'file-id'])
645
self.build_tree_contents([('tree/mod_' + alpha, b'contents_mod\n')])
647
d = get_diff_as_string(tree.basis_tree(), tree)
648
self.assertContainsRe(d,
649
b"=== renamed file 'ren_%s' => 'ren_%s'\n" % (autf8, outf8))
650
self.assertContainsRe(d, b"=== added file 'add_%s'" % autf8)
651
self.assertContainsRe(d, b"=== modified file 'mod_%s'" % autf8)
652
self.assertContainsRe(d, b"=== removed file 'del_%s'" % autf8)
654
def test_unicode_filename_path_encoding(self):
655
"""Test for bug #382699: unicode filenames on Windows should be shown
658
self.requireFeature(features.UnicodeFilenameFeature)
659
# The word 'test' in Russian
660
_russian_test = u'\u0422\u0435\u0441\u0442'
661
directory = _russian_test + u'/'
662
test_txt = _russian_test + u'.txt'
663
u1234 = u'\u1234.txt'
665
tree = self.make_branch_and_tree('.')
666
self.build_tree_contents([
667
(test_txt, b'foo\n'),
671
tree.add([test_txt, u1234, directory])
674
diff.show_diff_trees(tree.basis_tree(), tree, sio,
675
path_encoding='cp1251')
677
output = subst_dates(sio.getvalue())
679
=== added directory '%(directory)s'
680
=== added file '%(test_txt)s'
681
--- a/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
682
+++ b/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
686
=== added file '?.txt'
687
--- a/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
688
+++ b/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
692
''' % {b'directory': _russian_test.encode('cp1251'),
693
b'test_txt': test_txt.encode('cp1251'),
695
self.assertEqualDiff(output, shouldbe)
698
class DiffWasIs(diff.DiffPath):
700
def diff(self, old_path, new_path, old_kind, new_kind):
701
self.to_file.write(b'was: ')
702
self.to_file.write(self.old_tree.get_file(old_path).read())
703
self.to_file.write(b'is: ')
704
self.to_file.write(self.new_tree.get_file(new_path).read())
707
class TestDiffTree(tests.TestCaseWithTransport):
572
self.build_tree_contents([('tree/ren_'+alpha, 'contents\n')])
573
tree.add(['ren_'+alpha], ['file-id-2'])
574
self.build_tree_contents([('tree/del_'+alpha, 'contents\n')])
575
tree.add(['del_'+alpha], ['file-id-3'])
576
self.build_tree_contents([('tree/mod_'+alpha, 'contents\n')])
577
tree.add(['mod_'+alpha], ['file-id-4'])
579
tree.commit('one', rev_id='rev-1')
581
tree.rename_one('ren_'+alpha, 'ren_'+omega)
582
tree.remove('del_'+alpha)
583
self.build_tree_contents([('tree/add_'+alpha, 'contents\n')])
584
tree.add(['add_'+alpha], ['file-id'])
585
self.build_tree_contents([('tree/mod_'+alpha, 'contents_mod\n')])
587
diff = self.get_diff(tree.basis_tree(), tree)
588
self.assertContainsRe(diff,
589
"=== renamed file 'ren_%s' => 'ren_%s'\n"%(autf8, outf8))
590
self.assertContainsRe(diff, "=== added file 'add_%s'"%autf8)
591
self.assertContainsRe(diff, "=== modified file 'mod_%s'"%autf8)
592
self.assertContainsRe(diff, "=== removed file 'del_%s'"%autf8)
595
class DiffWasIs(DiffPath):
597
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
598
self.to_file.write('was: ')
599
self.to_file.write(self.old_tree.get_file(file_id).read())
600
self.to_file.write('is: ')
601
self.to_file.write(self.new_tree.get_file(file_id).read())
605
class TestDiffTree(TestCaseWithTransport):
710
super(TestDiffTree, self).setUp()
608
TestCaseWithTransport.setUp(self)
711
609
self.old_tree = self.make_branch_and_tree('old-tree')
712
610
self.old_tree.lock_write()
713
611
self.addCleanup(self.old_tree.unlock)
714
612
self.new_tree = self.make_branch_and_tree('new-tree')
715
613
self.new_tree.lock_write()
716
614
self.addCleanup(self.new_tree.unlock)
717
self.differ = diff.DiffTree(self.old_tree, self.new_tree, BytesIO())
615
self.differ = DiffTree(self.old_tree, self.new_tree, StringIO())
719
617
def test_diff_text(self):
720
618
self.build_tree_contents([('old-tree/olddir/',),
721
('old-tree/olddir/oldfile', b'old\n')])
619
('old-tree/olddir/oldfile', 'old\n')])
722
620
self.old_tree.add('olddir')
723
self.old_tree.add('olddir/oldfile', b'file-id')
621
self.old_tree.add('olddir/oldfile', 'file-id')
724
622
self.build_tree_contents([('new-tree/newdir/',),
725
('new-tree/newdir/newfile', b'new\n')])
623
('new-tree/newdir/newfile', 'new\n')])
726
624
self.new_tree.add('newdir')
727
self.new_tree.add('newdir/newfile', b'file-id')
728
differ = diff.DiffText(self.old_tree, self.new_tree, BytesIO())
729
differ.diff_text('olddir/oldfile', None, 'old label', 'new label')
731
b'--- old label\n+++ new label\n@@ -1,1 +0,0 @@\n-old\n\n',
732
differ.to_file.getvalue())
733
differ.to_file.seek(0)
734
differ.diff_text(None, 'newdir/newfile',
735
'old label', 'new label')
737
b'--- old label\n+++ new label\n@@ -0,0 +1,1 @@\n+new\n\n',
738
differ.to_file.getvalue())
739
differ.to_file.seek(0)
740
differ.diff_text('olddir/oldfile', 'newdir/newfile',
741
'old label', 'new label')
743
b'--- old label\n+++ new label\n@@ -1,1 +1,1 @@\n-old\n+new\n\n',
625
self.new_tree.add('newdir/newfile', 'file-id')
626
differ = DiffText(self.old_tree, self.new_tree, StringIO())
627
differ.diff_text('file-id', None, 'old label', 'new label')
629
'--- old label\n+++ new label\n@@ -1,1 +0,0 @@\n-old\n\n',
630
differ.to_file.getvalue())
631
differ.to_file.seek(0)
632
differ.diff_text(None, 'file-id', 'old label', 'new label')
634
'--- old label\n+++ new label\n@@ -0,0 +1,1 @@\n+new\n\n',
635
differ.to_file.getvalue())
636
differ.to_file.seek(0)
637
differ.diff_text('file-id', 'file-id', 'old label', 'new label')
639
'--- old label\n+++ new label\n@@ -1,1 +1,1 @@\n-old\n+new\n\n',
744
640
differ.to_file.getvalue())
746
642
def test_diff_deletion(self):
747
self.build_tree_contents([('old-tree/file', b'contents'),
748
('new-tree/file', b'contents')])
749
self.old_tree.add('file', b'file-id')
750
self.new_tree.add('file', b'file-id')
643
self.build_tree_contents([('old-tree/file', 'contents'),
644
('new-tree/file', 'contents')])
645
self.old_tree.add('file', 'file-id')
646
self.new_tree.add('file', 'file-id')
751
647
os.unlink('new-tree/file')
752
648
self.differ.show_diff(None)
753
self.assertContainsRe(self.differ.to_file.getvalue(), b'-contents')
649
self.assertContainsRe(self.differ.to_file.getvalue(), '-contents')
755
651
def test_diff_creation(self):
756
self.build_tree_contents([('old-tree/file', b'contents'),
757
('new-tree/file', b'contents')])
758
self.old_tree.add('file', b'file-id')
759
self.new_tree.add('file', b'file-id')
652
self.build_tree_contents([('old-tree/file', 'contents'),
653
('new-tree/file', 'contents')])
654
self.old_tree.add('file', 'file-id')
655
self.new_tree.add('file', 'file-id')
760
656
os.unlink('old-tree/file')
761
657
self.differ.show_diff(None)
762
self.assertContainsRe(self.differ.to_file.getvalue(), br'\+contents')
658
self.assertContainsRe(self.differ.to_file.getvalue(), '\+contents')
764
660
def test_diff_symlink(self):
765
differ = diff.DiffSymlink(self.old_tree, self.new_tree, BytesIO())
661
differ = DiffSymlink(self.old_tree, self.new_tree, StringIO())
766
662
differ.diff_symlink('old target', None)
767
self.assertEqual(b"=== target was 'old target'\n",
663
self.assertEqual("=== target was 'old target'\n",
768
664
differ.to_file.getvalue())
770
differ = diff.DiffSymlink(self.old_tree, self.new_tree, BytesIO())
666
differ = DiffSymlink(self.old_tree, self.new_tree, StringIO())
771
667
differ.diff_symlink(None, 'new target')
772
self.assertEqual(b"=== target is 'new target'\n",
668
self.assertEqual("=== target is 'new target'\n",
773
669
differ.to_file.getvalue())
775
differ = diff.DiffSymlink(self.old_tree, self.new_tree, BytesIO())
671
differ = DiffSymlink(self.old_tree, self.new_tree, StringIO())
776
672
differ.diff_symlink('old target', 'new target')
777
self.assertEqual(b"=== target changed 'old target' => 'new target'\n",
673
self.assertEqual("=== target changed 'old target' => 'new target'\n",
778
674
differ.to_file.getvalue())
780
676
def test_diff(self):
781
677
self.build_tree_contents([('old-tree/olddir/',),
782
('old-tree/olddir/oldfile', b'old\n')])
678
('old-tree/olddir/oldfile', 'old\n')])
783
679
self.old_tree.add('olddir')
784
self.old_tree.add('olddir/oldfile', b'file-id')
680
self.old_tree.add('olddir/oldfile', 'file-id')
785
681
self.build_tree_contents([('new-tree/newdir/',),
786
('new-tree/newdir/newfile', b'new\n')])
682
('new-tree/newdir/newfile', 'new\n')])
787
683
self.new_tree.add('newdir')
788
self.new_tree.add('newdir/newfile', b'file-id')
789
self.differ.diff('olddir/oldfile', 'newdir/newfile')
684
self.new_tree.add('newdir/newfile', 'file-id')
685
self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
790
686
self.assertContainsRe(
791
687
self.differ.to_file.getvalue(),
792
br'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
793
br' \@\@\n-old\n\+new\n\n')
688
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
689
' \@\@\n-old\n\+new\n\n')
795
691
def test_diff_kind_change(self):
796
self.requireFeature(features.SymlinkFeature)
692
self.requireFeature(tests.SymlinkFeature)
797
693
self.build_tree_contents([('old-tree/olddir/',),
798
('old-tree/olddir/oldfile', b'old\n')])
694
('old-tree/olddir/oldfile', 'old\n')])
799
695
self.old_tree.add('olddir')
800
self.old_tree.add('olddir/oldfile', b'file-id')
696
self.old_tree.add('olddir/oldfile', 'file-id')
801
697
self.build_tree(['new-tree/newdir/'])
802
698
os.symlink('new', 'new-tree/newdir/newfile')
803
699
self.new_tree.add('newdir')
804
self.new_tree.add('newdir/newfile', b'file-id')
805
self.differ.diff('olddir/oldfile', 'newdir/newfile')
700
self.new_tree.add('newdir/newfile', 'file-id')
701
self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
806
702
self.assertContainsRe(
807
703
self.differ.to_file.getvalue(),
808
br'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+0,0'
704
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+0,0'
810
706
self.assertContainsRe(self.differ.to_file.getvalue(),
811
b"=== target is 'new'\n")
707
"=== target is u'new'\n")
813
709
def test_diff_directory(self):
814
710
self.build_tree(['new-tree/new-dir/'])
815
self.new_tree.add('new-dir', b'new-dir-id')
816
self.differ.diff(None, 'new-dir')
817
self.assertEqual(self.differ.to_file.getvalue(), b'')
711
self.new_tree.add('new-dir', 'new-dir-id')
712
self.differ.diff('new-dir-id', None, 'new-dir')
713
self.assertEqual(self.differ.to_file.getvalue(), '')
819
715
def create_old_new(self):
820
716
self.build_tree_contents([('old-tree/olddir/',),
821
('old-tree/olddir/oldfile', b'old\n')])
717
('old-tree/olddir/oldfile', 'old\n')])
822
718
self.old_tree.add('olddir')
823
self.old_tree.add('olddir/oldfile', b'file-id')
719
self.old_tree.add('olddir/oldfile', 'file-id')
824
720
self.build_tree_contents([('new-tree/newdir/',),
825
('new-tree/newdir/newfile', b'new\n')])
721
('new-tree/newdir/newfile', 'new\n')])
826
722
self.new_tree.add('newdir')
827
self.new_tree.add('newdir/newfile', b'file-id')
723
self.new_tree.add('newdir/newfile', 'file-id')
829
725
def test_register_diff(self):
830
726
self.create_old_new()
831
old_diff_factories = diff.DiffTree.diff_factories
832
diff.DiffTree.diff_factories = old_diff_factories[:]
833
diff.DiffTree.diff_factories.insert(0, DiffWasIs.from_diff_tree)
727
old_diff_factories = DiffTree.diff_factories
728
DiffTree.diff_factories=old_diff_factories[:]
729
DiffTree.diff_factories.insert(0, DiffWasIs.from_diff_tree)
835
differ = diff.DiffTree(self.old_tree, self.new_tree, BytesIO())
731
differ = DiffTree(self.old_tree, self.new_tree, StringIO())
837
diff.DiffTree.diff_factories = old_diff_factories
838
differ.diff('olddir/oldfile', 'newdir/newfile')
733
DiffTree.diff_factories = old_diff_factories
734
differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
839
735
self.assertNotContainsRe(
840
736
differ.to_file.getvalue(),
841
br'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
842
br' \@\@\n-old\n\+new\n\n')
737
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
738
' \@\@\n-old\n\+new\n\n')
843
739
self.assertContainsRe(differ.to_file.getvalue(),
844
b'was: old\nis: new\n')
740
'was: old\nis: new\n')
846
742
def test_extra_factories(self):
847
743
self.create_old_new()
848
differ = diff.DiffTree(self.old_tree, self.new_tree, BytesIO(),
849
extra_factories=[DiffWasIs.from_diff_tree])
850
differ.diff('olddir/oldfile', 'newdir/newfile')
744
differ = DiffTree(self.old_tree, self.new_tree, StringIO(),
745
extra_factories=[DiffWasIs.from_diff_tree])
746
differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
851
747
self.assertNotContainsRe(
852
748
differ.to_file.getvalue(),
853
br'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
854
br' \@\@\n-old\n\+new\n\n')
749
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
750
' \@\@\n-old\n\+new\n\n')
855
751
self.assertContainsRe(differ.to_file.getvalue(),
856
b'was: old\nis: new\n')
752
'was: old\nis: new\n')
858
754
def test_alphabetical_order(self):
859
755
self.build_tree(['new-tree/a-file'])
862
758
self.old_tree.add('b-file')
863
759
self.differ.show_diff(None)
864
760
self.assertContainsRe(self.differ.to_file.getvalue(),
865
b'.*a-file(.|\n)*b-file')
868
class TestDiffFromTool(tests.TestCaseWithTransport):
761
'.*a-file(.|\n)*b-file')
764
class TestPatienceDiffLib(TestCase):
767
super(TestPatienceDiffLib, self).setUp()
768
self._unique_lcs = bzrlib._patiencediff_py.unique_lcs_py
769
self._recurse_matches = bzrlib._patiencediff_py.recurse_matches_py
770
self._PatienceSequenceMatcher = \
771
bzrlib._patiencediff_py.PatienceSequenceMatcher_py
773
def test_diff_unicode_string(self):
774
a = ''.join([unichr(i) for i in range(4000, 4500, 3)])
775
b = ''.join([unichr(i) for i in range(4300, 4800, 2)])
776
sm = self._PatienceSequenceMatcher(None, a, b)
777
mb = sm.get_matching_blocks()
778
self.assertEquals(35, len(mb))
780
def test_unique_lcs(self):
781
unique_lcs = self._unique_lcs
782
self.assertEquals(unique_lcs('', ''), [])
783
self.assertEquals(unique_lcs('', 'a'), [])
784
self.assertEquals(unique_lcs('a', ''), [])
785
self.assertEquals(unique_lcs('a', 'a'), [(0,0)])
786
self.assertEquals(unique_lcs('a', 'b'), [])
787
self.assertEquals(unique_lcs('ab', 'ab'), [(0,0), (1,1)])
788
self.assertEquals(unique_lcs('abcde', 'cdeab'), [(2,0), (3,1), (4,2)])
789
self.assertEquals(unique_lcs('cdeab', 'abcde'), [(0,2), (1,3), (2,4)])
790
self.assertEquals(unique_lcs('abXde', 'abYde'), [(0,0), (1,1),
792
self.assertEquals(unique_lcs('acbac', 'abc'), [(2,1)])
794
def test_recurse_matches(self):
795
def test_one(a, b, matches):
797
self._recurse_matches(
798
a, b, 0, 0, len(a), len(b), test_matches, 10)
799
self.assertEquals(test_matches, matches)
801
test_one(['a', '', 'b', '', 'c'], ['a', 'a', 'b', 'c', 'c'],
802
[(0, 0), (2, 2), (4, 4)])
803
test_one(['a', 'c', 'b', 'a', 'c'], ['a', 'b', 'c'],
804
[(0, 0), (2, 1), (4, 2)])
805
# Even though 'bc' is not unique globally, and is surrounded by
806
# non-matching lines, we should still match, because they are locally
808
test_one('abcdbce', 'afbcgdbce', [(0,0), (1, 2), (2, 3), (3, 5),
809
(4, 6), (5, 7), (6, 8)])
811
# recurse_matches doesn't match non-unique
812
# lines surrounded by bogus text.
813
# The update has been done in patiencediff.SequenceMatcher instead
815
# This is what it could be
816
#test_one('aBccDe', 'abccde', [(0,0), (2,2), (3,3), (5,5)])
818
# This is what it currently gives:
819
test_one('aBccDe', 'abccde', [(0,0), (5,5)])
821
def assertDiffBlocks(self, a, b, expected_blocks):
822
"""Check that the sequence matcher returns the correct blocks.
824
:param a: A sequence to match
825
:param b: Another sequence to match
826
:param expected_blocks: The expected output, not including the final
827
matching block (len(a), len(b), 0)
829
matcher = self._PatienceSequenceMatcher(None, a, b)
830
blocks = matcher.get_matching_blocks()
832
self.assertEqual((len(a), len(b), 0), last)
833
self.assertEqual(expected_blocks, blocks)
835
def test_matching_blocks(self):
836
# Some basic matching tests
837
self.assertDiffBlocks('', '', [])
838
self.assertDiffBlocks([], [], [])
839
self.assertDiffBlocks('abc', '', [])
840
self.assertDiffBlocks('', 'abc', [])
841
self.assertDiffBlocks('abcd', 'abcd', [(0, 0, 4)])
842
self.assertDiffBlocks('abcd', 'abce', [(0, 0, 3)])
843
self.assertDiffBlocks('eabc', 'abce', [(1, 0, 3)])
844
self.assertDiffBlocks('eabce', 'abce', [(1, 0, 4)])
845
self.assertDiffBlocks('abcde', 'abXde', [(0, 0, 2), (3, 3, 2)])
846
self.assertDiffBlocks('abcde', 'abXYZde', [(0, 0, 2), (3, 5, 2)])
847
self.assertDiffBlocks('abde', 'abXYZde', [(0, 0, 2), (2, 5, 2)])
848
# This may check too much, but it checks to see that
849
# a copied block stays attached to the previous section,
851
# difflib would tend to grab the trailing longest match
852
# which would make the diff not look right
853
self.assertDiffBlocks('abcdefghijklmnop', 'abcdefxydefghijklmnop',
854
[(0, 0, 6), (6, 11, 10)])
856
# make sure it supports passing in lists
857
self.assertDiffBlocks(
860
'how are you today?\n'],
862
'how are you today?\n'],
863
[(0, 0, 1), (2, 1, 1)])
865
# non unique lines surrounded by non-matching lines
867
self.assertDiffBlocks('aBccDe', 'abccde', [(0,0,1), (5,5,1)])
869
# But they only need to be locally unique
870
self.assertDiffBlocks('aBcDec', 'abcdec', [(0,0,1), (2,2,1), (4,4,2)])
872
# non unique blocks won't be matched
873
self.assertDiffBlocks('aBcdEcdFg', 'abcdecdfg', [(0,0,1), (8,8,1)])
875
# but locally unique ones will
876
self.assertDiffBlocks('aBcdEeXcdFg', 'abcdecdfg', [(0,0,1), (2,2,2),
877
(5,4,1), (7,5,2), (10,8,1)])
879
self.assertDiffBlocks('abbabbXd', 'cabbabxd', [(7,7,1)])
880
self.assertDiffBlocks('abbabbbb', 'cabbabbc', [])
881
self.assertDiffBlocks('bbbbbbbb', 'cbbbbbbc', [])
883
def test_matching_blocks_tuples(self):
884
# Some basic matching tests
885
self.assertDiffBlocks([], [], [])
886
self.assertDiffBlocks([('a',), ('b',), ('c,')], [], [])
887
self.assertDiffBlocks([], [('a',), ('b',), ('c,')], [])
888
self.assertDiffBlocks([('a',), ('b',), ('c,')],
889
[('a',), ('b',), ('c,')],
891
self.assertDiffBlocks([('a',), ('b',), ('c,')],
892
[('a',), ('b',), ('d,')],
894
self.assertDiffBlocks([('d',), ('b',), ('c,')],
895
[('a',), ('b',), ('c,')],
897
self.assertDiffBlocks([('d',), ('a',), ('b',), ('c,')],
898
[('a',), ('b',), ('c,')],
900
self.assertDiffBlocks([('a', 'b'), ('c', 'd'), ('e', 'f')],
901
[('a', 'b'), ('c', 'X'), ('e', 'f')],
902
[(0, 0, 1), (2, 2, 1)])
903
self.assertDiffBlocks([('a', 'b'), ('c', 'd'), ('e', 'f')],
904
[('a', 'b'), ('c', 'dX'), ('e', 'f')],
905
[(0, 0, 1), (2, 2, 1)])
907
def test_opcodes(self):
908
def chk_ops(a, b, expected_codes):
909
s = self._PatienceSequenceMatcher(None, a, b)
910
self.assertEquals(expected_codes, s.get_opcodes())
914
chk_ops('abc', '', [('delete', 0,3, 0,0)])
915
chk_ops('', 'abc', [('insert', 0,0, 0,3)])
916
chk_ops('abcd', 'abcd', [('equal', 0,4, 0,4)])
917
chk_ops('abcd', 'abce', [('equal', 0,3, 0,3),
918
('replace', 3,4, 3,4)
920
chk_ops('eabc', 'abce', [('delete', 0,1, 0,0),
924
chk_ops('eabce', 'abce', [('delete', 0,1, 0,0),
927
chk_ops('abcde', 'abXde', [('equal', 0,2, 0,2),
928
('replace', 2,3, 2,3),
931
chk_ops('abcde', 'abXYZde', [('equal', 0,2, 0,2),
932
('replace', 2,3, 2,5),
935
chk_ops('abde', 'abXYZde', [('equal', 0,2, 0,2),
936
('insert', 2,2, 2,5),
939
chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
940
[('equal', 0,6, 0,6),
941
('insert', 6,6, 6,11),
942
('equal', 6,16, 11,21)
947
, 'how are you today?\n'],
949
, 'how are you today?\n'],
950
[('equal', 0,1, 0,1),
951
('delete', 1,2, 1,1),
954
chk_ops('aBccDe', 'abccde',
955
[('equal', 0,1, 0,1),
956
('replace', 1,5, 1,5),
959
chk_ops('aBcDec', 'abcdec',
960
[('equal', 0,1, 0,1),
961
('replace', 1,2, 1,2),
963
('replace', 3,4, 3,4),
966
chk_ops('aBcdEcdFg', 'abcdecdfg',
967
[('equal', 0,1, 0,1),
968
('replace', 1,8, 1,8),
971
chk_ops('aBcdEeXcdFg', 'abcdecdfg',
972
[('equal', 0,1, 0,1),
973
('replace', 1,2, 1,2),
975
('delete', 4,5, 4,4),
977
('delete', 6,7, 5,5),
979
('replace', 9,10, 7,8),
980
('equal', 10,11, 8,9)
983
def test_grouped_opcodes(self):
984
def chk_ops(a, b, expected_codes, n=3):
985
s = self._PatienceSequenceMatcher(None, a, b)
986
self.assertEquals(expected_codes, list(s.get_grouped_opcodes(n)))
990
chk_ops('abc', '', [[('delete', 0,3, 0,0)]])
991
chk_ops('', 'abc', [[('insert', 0,0, 0,3)]])
992
chk_ops('abcd', 'abcd', [])
993
chk_ops('abcd', 'abce', [[('equal', 0,3, 0,3),
994
('replace', 3,4, 3,4)
996
chk_ops('eabc', 'abce', [[('delete', 0,1, 0,0),
1000
chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
1001
[[('equal', 3,6, 3,6),
1002
('insert', 6,6, 6,11),
1003
('equal', 6,9, 11,14)
1005
chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
1006
[[('equal', 2,6, 2,6),
1007
('insert', 6,6, 6,11),
1008
('equal', 6,10, 11,15)
1010
chk_ops('Xabcdef', 'abcdef',
1011
[[('delete', 0,1, 0,0),
1014
chk_ops('abcdef', 'abcdefX',
1015
[[('equal', 3,6, 3,6),
1016
('insert', 6,6, 6,7)
1020
def test_multiple_ranges(self):
1021
# There was an earlier bug where we used a bad set of ranges,
1022
# this triggers that specific bug, to make sure it doesn't regress
1023
self.assertDiffBlocks('abcdefghijklmnop',
1024
'abcXghiYZQRSTUVWXYZijklmnop',
1025
[(0, 0, 3), (6, 4, 3), (9, 20, 7)])
1027
self.assertDiffBlocks('ABCd efghIjk L',
1028
'AxyzBCn mo pqrstuvwI1 2 L',
1029
[(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
1031
# These are rot13 code snippets.
1032
self.assertDiffBlocks('''\
1033
trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
1035
gnxrf_netf = ['svyr*']
1036
gnxrf_bcgvbaf = ['ab-erphefr']
1038
qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr):
1039
sebz omeyvo.nqq vzcbeg fzneg_nqq, nqq_ercbegre_cevag, nqq_ercbegre_ahyy
1041
ercbegre = nqq_ercbegre_ahyy
1043
ercbegre = nqq_ercbegre_cevag
1044
fzneg_nqq(svyr_yvfg, abg ab_erphefr, ercbegre)
1047
pynff pzq_zxqve(Pbzznaq):
1048
'''.splitlines(True), '''\
1049
trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
1051
--qel-eha jvyy fubj juvpu svyrf jbhyq or nqqrq, ohg abg npghnyyl
1054
gnxrf_netf = ['svyr*']
1055
gnxrf_bcgvbaf = ['ab-erphefr', 'qel-eha']
1057
qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr, qel_eha=Snyfr):
1062
# Guvf vf cbvagyrff, ohg V'q engure abg envfr na reebe
1063
npgvba = omeyvo.nqq.nqq_npgvba_ahyy
1065
npgvba = omeyvo.nqq.nqq_npgvba_cevag
1067
npgvba = omeyvo.nqq.nqq_npgvba_nqq
1069
npgvba = omeyvo.nqq.nqq_npgvba_nqq_naq_cevag
1071
omeyvo.nqq.fzneg_nqq(svyr_yvfg, abg ab_erphefr, npgvba)
1074
pynff pzq_zxqve(Pbzznaq):
1075
'''.splitlines(True)
1076
, [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
1078
def test_patience_unified_diff(self):
1079
txt_a = ['hello there\n',
1081
'how are you today?\n']
1082
txt_b = ['hello there\n',
1083
'how are you today?\n']
1084
unified_diff = bzrlib.patiencediff.unified_diff
1085
psm = self._PatienceSequenceMatcher
1086
self.assertEquals(['--- \n',
1088
'@@ -1,3 +1,2 @@\n',
1091
' how are you today?\n'
1093
, list(unified_diff(txt_a, txt_b,
1094
sequencematcher=psm)))
1095
txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
1096
txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
1097
# This is the result with LongestCommonSubstring matching
1098
self.assertEquals(['--- \n',
1100
'@@ -1,6 +1,11 @@\n',
1112
, list(unified_diff(txt_a, txt_b)))
1113
# And the patience diff
1114
self.assertEquals(['--- \n',
1116
'@@ -4,6 +4,11 @@\n',
1129
, list(unified_diff(txt_a, txt_b,
1130
sequencematcher=psm)))
1132
def test_patience_unified_diff_with_dates(self):
1133
txt_a = ['hello there\n',
1135
'how are you today?\n']
1136
txt_b = ['hello there\n',
1137
'how are you today?\n']
1138
unified_diff = bzrlib.patiencediff.unified_diff
1139
psm = self._PatienceSequenceMatcher
1140
self.assertEquals(['--- a\t2008-08-08\n',
1141
'+++ b\t2008-09-09\n',
1142
'@@ -1,3 +1,2 @@\n',
1145
' how are you today?\n'
1147
, list(unified_diff(txt_a, txt_b,
1148
fromfile='a', tofile='b',
1149
fromfiledate='2008-08-08',
1150
tofiledate='2008-09-09',
1151
sequencematcher=psm)))
1154
class TestPatienceDiffLib_c(TestPatienceDiffLib):
1156
_test_needs_features = [CompiledPatienceDiffFeature]
1159
super(TestPatienceDiffLib_c, self).setUp()
1160
import bzrlib._patiencediff_c
1161
self._unique_lcs = bzrlib._patiencediff_c.unique_lcs_c
1162
self._recurse_matches = bzrlib._patiencediff_c.recurse_matches_c
1163
self._PatienceSequenceMatcher = \
1164
bzrlib._patiencediff_c.PatienceSequenceMatcher_c
1166
def test_unhashable(self):
1167
"""We should get a proper exception here."""
1168
# We need to be able to hash items in the sequence, lists are
1169
# unhashable, and thus cannot be diffed
1170
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1172
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1173
None, ['valid', []], [])
1174
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1175
None, ['valid'], [[]])
1176
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1177
None, ['valid'], ['valid', []])
1180
class TestPatienceDiffLibFiles(TestCaseInTempDir):
1183
super(TestPatienceDiffLibFiles, self).setUp()
1184
self._PatienceSequenceMatcher = \
1185
bzrlib._patiencediff_py.PatienceSequenceMatcher_py
1187
def test_patience_unified_diff_files(self):
1188
txt_a = ['hello there\n',
1190
'how are you today?\n']
1191
txt_b = ['hello there\n',
1192
'how are you today?\n']
1193
open('a1', 'wb').writelines(txt_a)
1194
open('b1', 'wb').writelines(txt_b)
1196
unified_diff_files = bzrlib.patiencediff.unified_diff_files
1197
psm = self._PatienceSequenceMatcher
1198
self.assertEquals(['--- a1\n',
1200
'@@ -1,3 +1,2 @@\n',
1203
' how are you today?\n',
1205
, list(unified_diff_files('a1', 'b1',
1206
sequencematcher=psm)))
1208
txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
1209
txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
1210
open('a2', 'wb').writelines(txt_a)
1211
open('b2', 'wb').writelines(txt_b)
1213
# This is the result with LongestCommonSubstring matching
1214
self.assertEquals(['--- a2\n',
1216
'@@ -1,6 +1,11 @@\n',
1228
, list(unified_diff_files('a2', 'b2')))
1230
# And the patience diff
1231
self.assertEquals(['--- a2\n',
1233
'@@ -4,6 +4,11 @@\n',
1246
, list(unified_diff_files('a2', 'b2',
1247
sequencematcher=psm)))
1250
class TestPatienceDiffLibFiles_c(TestPatienceDiffLibFiles):
1252
_test_needs_features = [CompiledPatienceDiffFeature]
1255
super(TestPatienceDiffLibFiles_c, self).setUp()
1256
import bzrlib._patiencediff_c
1257
self._PatienceSequenceMatcher = \
1258
bzrlib._patiencediff_c.PatienceSequenceMatcher_c
1261
class TestUsingCompiledIfAvailable(TestCase):
1263
def test_PatienceSequenceMatcher(self):
1264
if CompiledPatienceDiffFeature.available():
1265
from bzrlib._patiencediff_c import PatienceSequenceMatcher_c
1266
self.assertIs(PatienceSequenceMatcher_c,
1267
bzrlib.patiencediff.PatienceSequenceMatcher)
1269
from bzrlib._patiencediff_py import PatienceSequenceMatcher_py
1270
self.assertIs(PatienceSequenceMatcher_py,
1271
bzrlib.patiencediff.PatienceSequenceMatcher)
1273
def test_unique_lcs(self):
1274
if CompiledPatienceDiffFeature.available():
1275
from bzrlib._patiencediff_c import unique_lcs_c
1276
self.assertIs(unique_lcs_c,
1277
bzrlib.patiencediff.unique_lcs)
1279
from bzrlib._patiencediff_py import unique_lcs_py
1280
self.assertIs(unique_lcs_py,
1281
bzrlib.patiencediff.unique_lcs)
1283
def test_recurse_matches(self):
1284
if CompiledPatienceDiffFeature.available():
1285
from bzrlib._patiencediff_c import recurse_matches_c
1286
self.assertIs(recurse_matches_c,
1287
bzrlib.patiencediff.recurse_matches)
1289
from bzrlib._patiencediff_py import recurse_matches_py
1290
self.assertIs(recurse_matches_py,
1291
bzrlib.patiencediff.recurse_matches)
1294
class TestDiffFromTool(TestCaseWithTransport):
870
1296
def test_from_string(self):
871
diff_obj = diff.DiffFromTool.from_string(
872
['diff', '{old_path}', '{new_path}'],
874
self.addCleanup(diff_obj.finish)
875
self.assertEqual(['diff', '{old_path}', '{new_path}'],
876
diff_obj.command_template)
878
def test_from_string_no_paths(self):
879
diff_obj = diff.DiffFromTool.from_string(
880
['diff', "-u5"], None, None, None)
881
self.addCleanup(diff_obj.finish)
882
self.assertEqual(['diff', '-u5'],
883
diff_obj.command_template)
884
self.assertEqual(['diff', '-u5', 'old-path', 'new-path'],
885
diff_obj._get_command('old-path', 'new-path'))
1297
diff_obj = DiffFromTool.from_string('diff', None, None, None)
1298
self.addCleanup(diff_obj.finish)
1299
self.assertEqual(['diff', '%(old_path)s', '%(new_path)s'],
1300
diff_obj.command_template)
887
1302
def test_from_string_u5(self):
888
diff_obj = diff.DiffFromTool.from_string(
889
['diff', "-u 5", '{old_path}', '{new_path}'], None, None, None)
1303
diff_obj = DiffFromTool.from_string('diff -u\\ 5', None, None, None)
890
1304
self.addCleanup(diff_obj.finish)
891
self.assertEqual(['diff', '-u 5', '{old_path}', '{new_path}'],
1305
self.assertEqual(['diff', '-u 5', '%(old_path)s', '%(new_path)s'],
892
1306
diff_obj.command_template)
893
1307
self.assertEqual(['diff', '-u 5', 'old-path', 'new-path'],
894
1308
diff_obj._get_command('old-path', 'new-path'))
896
def test_from_string_path_with_backslashes(self):
897
self.requireFeature(features.backslashdir_feature)
898
tool = ['C:\\Tools\\Diff.exe', '{old_path}', '{new_path}']
899
diff_obj = diff.DiffFromTool.from_string(tool, None, None, None)
900
self.addCleanup(diff_obj.finish)
901
self.assertEqual(['C:\\Tools\\Diff.exe', '{old_path}', '{new_path}'],
902
diff_obj.command_template)
903
self.assertEqual(['C:\\Tools\\Diff.exe', 'old-path', 'new-path'],
904
diff_obj._get_command('old-path', 'new-path'))
906
1310
def test_execute(self):
908
diff_obj = diff.DiffFromTool([sys.executable, '-c',
909
'print("{old_path} {new_path}")'],
1312
diff_obj = DiffFromTool(['python', '-c',
1313
'print "%(old_path)s %(new_path)s"'],
911
1315
self.addCleanup(diff_obj.finish)
912
1316
diff_obj._execute('old', 'new')
913
self.assertEqual(output.getvalue().rstrip(), b'old new')
1317
self.assertEqual(output.getvalue().rstrip(), 'old new')
915
def test_execute_missing(self):
916
diff_obj = diff.DiffFromTool(['a-tool-which-is-unlikely-to-exist'],
1319
def test_excute_missing(self):
1320
diff_obj = DiffFromTool(['a-tool-which-is-unlikely-to-exist'],
918
1322
self.addCleanup(diff_obj.finish)
919
e = self.assertRaises(errors.ExecutableMissing, diff_obj._execute,
1323
e = self.assertRaises(ExecutableMissing, diff_obj._execute, 'old',
921
1325
self.assertEqual('a-tool-which-is-unlikely-to-exist could not be found'
922
1326
' on this machine', str(e))
924
1328
def test_prepare_files_creates_paths_readable_by_windows_tool(self):
925
self.requireFeature(features.AttribFeature)
1329
self.requireFeature(AttribFeature)
927
1331
tree = self.make_branch_and_tree('tree')
928
self.build_tree_contents([('tree/file', b'content')])
929
tree.add('file', b'file-id')
1332
self.build_tree_contents([('tree/file', 'content')])
1333
tree.add('file', 'file-id')
930
1334
tree.commit('old tree')
931
1335
tree.lock_read()
932
1336
self.addCleanup(tree.unlock)
933
basis_tree = tree.basis_tree()
934
basis_tree.lock_read()
935
self.addCleanup(basis_tree.unlock)
936
diff_obj = diff.DiffFromTool([sys.executable, '-c',
937
'print "{old_path} {new_path}"'],
938
basis_tree, tree, output)
939
diff_obj._prepare_files('file', 'file', file_id=b'file-id')
940
# The old content should be readonly
941
self.assertReadableByAttrib(diff_obj._root, 'old\\file',
943
# The new content should use the tree object, not a 'new' file anymore
944
self.assertEndsWith(tree.basedir, 'work/tree')
945
self.assertReadableByAttrib(tree.basedir, 'file', r'work\\tree\\file$')
1337
diff_obj = DiffFromTool(['python', '-c',
1338
'print "%(old_path)s %(new_path)s"'],
1340
diff_obj._prepare_files('file-id', 'file', 'file')
1341
self.assertReadableByAttrib(diff_obj._root, 'old\\file', r'old\\file')
1342
self.assertReadableByAttrib(diff_obj._root, 'new\\file', r'new\\file')
947
1344
def assertReadableByAttrib(self, cwd, relpath, regex):
948
1345
proc = subprocess.Popen(['attrib', relpath],
949
1346
stdout=subprocess.PIPE,
951
(result, err) = proc.communicate()
952
self.assertContainsRe(result.replace('\r\n', '\n'), regex)
1349
result = proc.stdout.read()
1350
self.assertContainsRe(result, regex)
954
1352
def test_prepare_files(self):
956
1354
tree = self.make_branch_and_tree('tree')
957
self.build_tree_contents([('tree/oldname', b'oldcontent')])
958
self.build_tree_contents([('tree/oldname2', b'oldcontent2')])
959
tree.add('oldname', b'file-id')
960
tree.add('oldname2', b'file2-id')
961
# Earliest allowable date on FAT32 filesystems is 1980-01-01
962
tree.commit('old tree', timestamp=315532800)
1355
self.build_tree_contents([('tree/oldname', 'oldcontent')])
1356
self.build_tree_contents([('tree/oldname2', 'oldcontent2')])
1357
tree.add('oldname', 'file-id')
1358
tree.add('oldname2', 'file2-id')
1359
tree.commit('old tree', timestamp=0)
963
1360
tree.rename_one('oldname', 'newname')
964
1361
tree.rename_one('oldname2', 'newname2')
965
self.build_tree_contents([('tree/newname', b'newcontent')])
966
self.build_tree_contents([('tree/newname2', b'newcontent2')])
1362
self.build_tree_contents([('tree/newname', 'newcontent')])
1363
self.build_tree_contents([('tree/newname2', 'newcontent2')])
967
1364
old_tree = tree.basis_tree()
968
1365
old_tree.lock_read()
969
1366
self.addCleanup(old_tree.unlock)
970
1367
tree.lock_read()
971
1368
self.addCleanup(tree.unlock)
972
diff_obj = diff.DiffFromTool([sys.executable, '-c',
973
'print "{old_path} {new_path}"'],
974
old_tree, tree, output)
1369
diff_obj = DiffFromTool(['python', '-c',
1370
'print "%(old_path)s %(new_path)s"'],
1371
old_tree, tree, output)
975
1372
self.addCleanup(diff_obj.finish)
976
self.assertContainsRe(diff_obj._root, 'brz-diff-[^/]*')
977
old_path, new_path = diff_obj._prepare_files(
978
'oldname', 'newname')
1373
self.assertContainsRe(diff_obj._root, 'bzr-diff-[^/]*')
1374
old_path, new_path = diff_obj._prepare_files('file-id', 'oldname',
979
1376
self.assertContainsRe(old_path, 'old/oldname$')
980
self.assertEqual(315532800, os.stat(old_path).st_mtime)
981
self.assertContainsRe(new_path, 'tree/newname$')
982
self.assertFileEqual(b'oldcontent', old_path)
983
self.assertFileEqual(b'newcontent', new_path)
1377
self.assertEqual(0, os.stat(old_path).st_mtime)
1378
self.assertContainsRe(new_path, 'new/newname$')
1379
self.assertFileEqual('oldcontent', old_path)
1380
self.assertFileEqual('newcontent', new_path)
984
1381
if osutils.host_os_dereferences_symlinks():
985
1382
self.assertTrue(os.path.samefile('tree/newname', new_path))
986
1383
# make sure we can create files with the same parent directories
987
diff_obj._prepare_files('oldname2', 'newname2')
990
class TestDiffFromToolEncodedFilename(tests.TestCaseWithTransport):
992
def test_encodable_filename(self):
993
# Just checks file path for external diff tool.
994
# We cannot change CPython's internal encoding used by os.exec*.
995
diffobj = diff.DiffFromTool(['dummy', '{old_path}', '{new_path}'],
997
for _, scenario in EncodingAdapter.encoding_scenarios:
998
encoding = scenario['encoding']
999
dirname = scenario['info']['directory']
1000
filename = scenario['info']['filename']
1002
self.overrideAttr(diffobj, '_fenc', lambda: encoding)
1003
relpath = dirname + u'/' + filename
1004
fullpath = diffobj._safe_filename('safe', relpath)
1005
self.assertEqual(fullpath,
1006
fullpath.encode(encoding).decode(encoding))
1007
self.assertTrue(fullpath.startswith(diffobj._root + '/safe'))
1009
def test_unencodable_filename(self):
1010
diffobj = diff.DiffFromTool(['dummy', '{old_path}', '{new_path}'],
1012
for _, scenario in EncodingAdapter.encoding_scenarios:
1013
encoding = scenario['encoding']
1014
dirname = scenario['info']['directory']
1015
filename = scenario['info']['filename']
1017
if encoding == 'iso-8859-1':
1018
encoding = 'iso-8859-2'
1020
encoding = 'iso-8859-1'
1022
self.overrideAttr(diffobj, '_fenc', lambda: encoding)
1023
relpath = dirname + u'/' + filename
1024
fullpath = diffobj._safe_filename('safe', relpath)
1025
self.assertEqual(fullpath,
1026
fullpath.encode(encoding).decode(encoding))
1027
self.assertTrue(fullpath.startswith(diffobj._root + '/safe'))
1030
class TestGetTreesAndBranchesToDiffLocked(tests.TestCaseWithTransport):
1032
def call_gtabtd(self, path_list, revision_specs, old_url, new_url):
1033
"""Call get_trees_and_branches_to_diff_locked."""
1034
exit_stack = cleanup.ExitStack()
1035
self.addCleanup(exit_stack.close)
1036
return diff.get_trees_and_branches_to_diff_locked(
1037
path_list, revision_specs, old_url, new_url, exit_stack)
1039
def test_basic(self):
1040
tree = self.make_branch_and_tree('tree')
1041
(old_tree, new_tree,
1042
old_branch, new_branch,
1043
specific_files, extra_trees) = self.call_gtabtd(
1044
['tree'], None, None, None)
1046
self.assertIsInstance(old_tree, revisiontree.RevisionTree)
1047
self.assertEqual(_mod_revision.NULL_REVISION,
1048
old_tree.get_revision_id())
1049
self.assertEqual(tree.basedir, new_tree.basedir)
1050
self.assertEqual(tree.branch.base, old_branch.base)
1051
self.assertEqual(tree.branch.base, new_branch.base)
1052
self.assertIs(None, specific_files)
1053
self.assertIs(None, extra_trees)
1055
def test_with_rev_specs(self):
1056
tree = self.make_branch_and_tree('tree')
1057
self.build_tree_contents([('tree/file', b'oldcontent')])
1058
tree.add('file', b'file-id')
1059
tree.commit('old tree', timestamp=0, rev_id=b"old-id")
1060
self.build_tree_contents([('tree/file', b'newcontent')])
1061
tree.commit('new tree', timestamp=0, rev_id=b"new-id")
1063
revisions = [revisionspec.RevisionSpec.from_string('1'),
1064
revisionspec.RevisionSpec.from_string('2')]
1065
(old_tree, new_tree,
1066
old_branch, new_branch,
1067
specific_files, extra_trees) = self.call_gtabtd(
1068
['tree'], revisions, None, None)
1070
self.assertIsInstance(old_tree, revisiontree.RevisionTree)
1071
self.assertEqual(b"old-id", old_tree.get_revision_id())
1072
self.assertIsInstance(new_tree, revisiontree.RevisionTree)
1073
self.assertEqual(b"new-id", new_tree.get_revision_id())
1074
self.assertEqual(tree.branch.base, old_branch.base)
1075
self.assertEqual(tree.branch.base, new_branch.base)
1076
self.assertIs(None, specific_files)
1077
self.assertEqual(tree.basedir, extra_trees[0].basedir)
1384
diff_obj._prepare_files('file2-id', 'oldname2', 'newname2')