169
151
self.check_patch(lines)
171
153
def test_external_diff_binary_lang_c(self):
173
154
for lang in ('LANG', 'LC_ALL', 'LANGUAGE'):
174
old_env[lang] = osutils.set_or_unset_env(lang, 'C')
176
lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
177
# Older versions of diffutils say "Binary files", newer
178
# versions just say "Files".
179
self.assertContainsRe(lines[0],
180
'(Binary f|F)iles old and new differ\n')
181
self.assertEquals(lines[1:], ['\n'])
183
for lang, old_val in old_env.iteritems():
184
osutils.set_or_unset_env(lang, old_val)
155
self.overrideEnv(lang, 'C')
156
lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
157
# Older versions of diffutils say "Binary files", newer
158
# versions just say "Files".
159
self.assertContainsRe(lines[0], '(Binary f|F)iles old and new differ\n')
160
self.assertEquals(lines[1:], ['\n'])
186
162
def test_no_external_diff(self):
187
163
"""Check that NoDiff is raised when diff is not available"""
188
# Use os.environ['PATH'] to make sure no 'diff' command is available
189
orig_path = os.environ['PATH']
191
os.environ['PATH'] = ''
192
self.assertRaises(NoDiff, external_diff,
193
'old', ['boo\n'], 'new', ['goo\n'],
194
StringIO(), diff_opts=['-u'])
196
os.environ['PATH'] = orig_path
164
# Make sure no 'diff' command is available
165
# XXX: Weird, using None instead of '' breaks the test -- vila 20101216
166
self.overrideEnv('PATH', '')
167
self.assertRaises(errors.NoDiff, diff.external_diff,
168
'old', ['boo\n'], 'new', ['goo\n'],
169
StringIO(), diff_opts=['-u'])
198
171
def test_internal_diff_default(self):
199
172
# Default internal diff encoding is utf8
200
173
output = StringIO()
201
internal_diff(u'old_\xb5', ['old_text\n'],
202
u'new_\xe5', ['new_text\n'], output)
174
diff.internal_diff(u'old_\xb5', ['old_text\n'],
175
u'new_\xe5', ['new_text\n'], output)
203
176
lines = output.getvalue().splitlines(True)
204
177
self.check_patch(lines)
205
178
self.assertEquals(['--- old_\xc2\xb5\n',
246
219
def test_internal_diff_no_content(self):
247
220
output = StringIO()
248
internal_diff(u'old', [], u'new', [], output)
221
diff.internal_diff(u'old', [], u'new', [], output)
249
222
self.assertEqual('', output.getvalue())
251
224
def test_internal_diff_no_changes(self):
252
225
output = StringIO()
253
internal_diff(u'old', ['text\n', 'contents\n'],
254
u'new', ['text\n', 'contents\n'],
226
diff.internal_diff(u'old', ['text\n', 'contents\n'],
227
u'new', ['text\n', 'contents\n'],
256
229
self.assertEqual('', output.getvalue())
258
231
def test_internal_diff_returns_bytes(self):
260
233
output = StringIO.StringIO()
261
internal_diff(u'old_\xb5', ['old_text\n'],
262
u'new_\xe5', ['new_text\n'], output)
263
self.failUnless(isinstance(output.getvalue(), str),
234
diff.internal_diff(u'old_\xb5', ['old_text\n'],
235
u'new_\xe5', ['new_text\n'], output)
236
self.assertIsInstance(output.getvalue(), str,
264
237
'internal_diff should return bytestrings')
267
class TestDiffFiles(TestCaseInTempDir):
239
def test_internal_diff_default_context(self):
241
diff.internal_diff('old', ['same_text\n','same_text\n','same_text\n',
242
'same_text\n','same_text\n','old_text\n'],
243
'new', ['same_text\n','same_text\n','same_text\n',
244
'same_text\n','same_text\n','new_text\n'], output)
245
lines = output.getvalue().splitlines(True)
246
self.check_patch(lines)
247
self.assertEquals(['--- old\n',
259
def test_internal_diff_no_context(self):
261
diff.internal_diff('old', ['same_text\n','same_text\n','same_text\n',
262
'same_text\n','same_text\n','old_text\n'],
263
'new', ['same_text\n','same_text\n','same_text\n',
264
'same_text\n','same_text\n','new_text\n'], output,
266
lines = output.getvalue().splitlines(True)
267
self.check_patch(lines)
268
self.assertEquals(['--- old\n',
277
def test_internal_diff_more_context(self):
279
diff.internal_diff('old', ['same_text\n','same_text\n','same_text\n',
280
'same_text\n','same_text\n','old_text\n'],
281
'new', ['same_text\n','same_text\n','same_text\n',
282
'same_text\n','same_text\n','new_text\n'], output,
284
lines = output.getvalue().splitlines(True)
285
self.check_patch(lines)
286
self.assertEquals(['--- old\n',
303
class TestDiffFiles(tests.TestCaseInTempDir):
269
305
def test_external_diff_binary(self):
270
306
"""The output when using external diff should use diff's i18n error"""
1232
1339
class TestPatienceDiffLibFiles_c(TestPatienceDiffLibFiles):
1234
_test_needs_features = [CompiledPatienceDiffFeature]
1341
_test_needs_features = [features.compiled_patiencediff_feature]
1236
1343
def setUp(self):
1237
1344
super(TestPatienceDiffLibFiles_c, self).setUp()
1238
import bzrlib._patiencediff_c
1345
from bzrlib import _patiencediff_c
1239
1346
self._PatienceSequenceMatcher = \
1240
bzrlib._patiencediff_c.PatienceSequenceMatcher_c
1243
class TestUsingCompiledIfAvailable(TestCase):
1347
_patiencediff_c.PatienceSequenceMatcher_c
1350
class TestUsingCompiledIfAvailable(tests.TestCase):
1245
1352
def test_PatienceSequenceMatcher(self):
1246
if CompiledPatienceDiffFeature.available():
1353
if features.compiled_patiencediff_feature.available():
1247
1354
from bzrlib._patiencediff_c import PatienceSequenceMatcher_c
1248
1355
self.assertIs(PatienceSequenceMatcher_c,
1249
bzrlib.patiencediff.PatienceSequenceMatcher)
1356
patiencediff.PatienceSequenceMatcher)
1251
1358
from bzrlib._patiencediff_py import PatienceSequenceMatcher_py
1252
1359
self.assertIs(PatienceSequenceMatcher_py,
1253
bzrlib.patiencediff.PatienceSequenceMatcher)
1360
patiencediff.PatienceSequenceMatcher)
1255
1362
def test_unique_lcs(self):
1256
if CompiledPatienceDiffFeature.available():
1363
if features.compiled_patiencediff_feature.available():
1257
1364
from bzrlib._patiencediff_c import unique_lcs_c
1258
1365
self.assertIs(unique_lcs_c,
1259
bzrlib.patiencediff.unique_lcs)
1366
patiencediff.unique_lcs)
1261
1368
from bzrlib._patiencediff_py import unique_lcs_py
1262
1369
self.assertIs(unique_lcs_py,
1263
bzrlib.patiencediff.unique_lcs)
1370
patiencediff.unique_lcs)
1265
1372
def test_recurse_matches(self):
1266
if CompiledPatienceDiffFeature.available():
1373
if features.compiled_patiencediff_feature.available():
1267
1374
from bzrlib._patiencediff_c import recurse_matches_c
1268
1375
self.assertIs(recurse_matches_c,
1269
bzrlib.patiencediff.recurse_matches)
1376
patiencediff.recurse_matches)
1271
1378
from bzrlib._patiencediff_py import recurse_matches_py
1272
1379
self.assertIs(recurse_matches_py,
1273
bzrlib.patiencediff.recurse_matches)
1276
class TestDiffFromTool(TestCaseWithTransport):
1380
patiencediff.recurse_matches)
1383
class TestDiffFromTool(tests.TestCaseWithTransport):
1278
1385
def test_from_string(self):
1279
diff_obj = DiffFromTool.from_string('diff', None, None, None)
1386
diff_obj = diff.DiffFromTool.from_string('diff', None, None, None)
1280
1387
self.addCleanup(diff_obj.finish)
1281
self.assertEqual(['diff', '%(old_path)s', '%(new_path)s'],
1388
self.assertEqual(['diff', '@old_path', '@new_path'],
1282
1389
diff_obj.command_template)
1284
1391
def test_from_string_u5(self):
1285
diff_obj = DiffFromTool.from_string('diff -u\\ 5', None, None, None)
1392
diff_obj = diff.DiffFromTool.from_string('diff "-u 5"',
1286
1394
self.addCleanup(diff_obj.finish)
1287
self.assertEqual(['diff', '-u 5', '%(old_path)s', '%(new_path)s'],
1395
self.assertEqual(['diff', '-u 5', '@old_path', '@new_path'],
1288
1396
diff_obj.command_template)
1289
1397
self.assertEqual(['diff', '-u 5', 'old-path', 'new-path'],
1290
1398
diff_obj._get_command('old-path', 'new-path'))
1400
def test_from_string_path_with_backslashes(self):
1401
self.requireFeature(features.backslashdir_feature)
1402
tool = 'C:\\Tools\\Diff.exe'
1403
diff_obj = diff.DiffFromTool.from_string(tool, None, None, None)
1404
self.addCleanup(diff_obj.finish)
1405
self.assertEqual(['C:\\Tools\\Diff.exe', '@old_path', '@new_path'],
1406
diff_obj.command_template)
1407
self.assertEqual(['C:\\Tools\\Diff.exe', 'old-path', 'new-path'],
1408
diff_obj._get_command('old-path', 'new-path'))
1292
1410
def test_execute(self):
1293
1411
output = StringIO()
1294
diff_obj = DiffFromTool(['python', '-c',
1295
'print "%(old_path)s %(new_path)s"'],
1412
diff_obj = diff.DiffFromTool(['python', '-c',
1413
'print "@old_path @new_path"'],
1297
1415
self.addCleanup(diff_obj.finish)
1298
1416
diff_obj._execute('old', 'new')
1299
1417
self.assertEqual(output.getvalue().rstrip(), 'old new')
1301
def test_excute_missing(self):
1302
diff_obj = DiffFromTool(['a-tool-which-is-unlikely-to-exist'],
1419
def test_execute_missing(self):
1420
diff_obj = diff.DiffFromTool(['a-tool-which-is-unlikely-to-exist'],
1304
1422
self.addCleanup(diff_obj.finish)
1305
e = self.assertRaises(ExecutableMissing, diff_obj._execute, 'old',
1423
e = self.assertRaises(errors.ExecutableMissing, diff_obj._execute,
1307
1425
self.assertEqual('a-tool-which-is-unlikely-to-exist could not be found'
1308
1426
' on this machine', str(e))
1428
def test_prepare_files_creates_paths_readable_by_windows_tool(self):
1429
self.requireFeature(features.AttribFeature)
1431
tree = self.make_branch_and_tree('tree')
1432
self.build_tree_contents([('tree/file', 'content')])
1433
tree.add('file', 'file-id')
1434
tree.commit('old tree')
1436
self.addCleanup(tree.unlock)
1437
basis_tree = tree.basis_tree()
1438
basis_tree.lock_read()
1439
self.addCleanup(basis_tree.unlock)
1440
diff_obj = diff.DiffFromTool(['python', '-c',
1441
'print "@old_path @new_path"'],
1442
basis_tree, tree, output)
1443
diff_obj._prepare_files('file-id', 'file', 'file')
1444
# The old content should be readonly
1445
self.assertReadableByAttrib(diff_obj._root, 'old\\file',
1447
# The new content should use the tree object, not a 'new' file anymore
1448
self.assertEndsWith(tree.basedir, 'work/tree')
1449
self.assertReadableByAttrib(tree.basedir, 'file', r'work\\tree\\file$')
1451
def assertReadableByAttrib(self, cwd, relpath, regex):
1452
proc = subprocess.Popen(['attrib', relpath],
1453
stdout=subprocess.PIPE,
1455
(result, err) = proc.communicate()
1456
self.assertContainsRe(result.replace('\r\n', '\n'), regex)
1310
1458
def test_prepare_files(self):
1311
1459
output = StringIO()
1312
1460
tree = self.make_branch_and_tree('tree')
1313
1461
self.build_tree_contents([('tree/oldname', 'oldcontent')])
1462
self.build_tree_contents([('tree/oldname2', 'oldcontent2')])
1314
1463
tree.add('oldname', 'file-id')
1315
tree.commit('old tree', timestamp=0)
1464
tree.add('oldname2', 'file2-id')
1465
# Earliest allowable date on FAT32 filesystems is 1980-01-01
1466
tree.commit('old tree', timestamp=315532800)
1316
1467
tree.rename_one('oldname', 'newname')
1468
tree.rename_one('oldname2', 'newname2')
1317
1469
self.build_tree_contents([('tree/newname', 'newcontent')])
1470
self.build_tree_contents([('tree/newname2', 'newcontent2')])
1318
1471
old_tree = tree.basis_tree()
1319
1472
old_tree.lock_read()
1320
1473
self.addCleanup(old_tree.unlock)
1321
1474
tree.lock_read()
1322
1475
self.addCleanup(tree.unlock)
1323
diff_obj = DiffFromTool(['python', '-c',
1324
'print "%(old_path)s %(new_path)s"'],
1325
old_tree, tree, output)
1476
diff_obj = diff.DiffFromTool(['python', '-c',
1477
'print "@old_path @new_path"'],
1478
old_tree, tree, output)
1326
1479
self.addCleanup(diff_obj.finish)
1327
1480
self.assertContainsRe(diff_obj._root, 'bzr-diff-[^/]*')
1328
1481
old_path, new_path = diff_obj._prepare_files('file-id', 'oldname',
1330
1483
self.assertContainsRe(old_path, 'old/oldname$')
1331
self.assertEqual(0, os.stat(old_path).st_mtime)
1332
self.assertContainsRe(new_path, 'new/newname$')
1484
self.assertEqual(315532800, os.stat(old_path).st_mtime)
1485
self.assertContainsRe(new_path, 'tree/newname$')
1333
1486
self.assertFileEqual('oldcontent', old_path)
1334
1487
self.assertFileEqual('newcontent', new_path)
1335
if osutils.has_symlinks():
1488
if osutils.host_os_dereferences_symlinks():
1336
1489
self.assertTrue(os.path.samefile('tree/newname', new_path))
1337
1490
# make sure we can create files with the same parent directories
1338
diff_obj._prepare_files('file-id', 'oldname2', 'newname2')
1491
diff_obj._prepare_files('file2-id', 'oldname2', 'newname2')
1494
class TestDiffFromToolEncodedFilename(tests.TestCaseWithTransport):
1496
def test_encodable_filename(self):
1497
# Just checks file path for external diff tool.
1498
# We cannot change CPython's internal encoding used by os.exec*.
1499
diffobj = diff.DiffFromTool(['dummy', '@old_path', '@new_path'],
1501
for _, scenario in EncodingAdapter.encoding_scenarios:
1502
encoding = scenario['encoding']
1503
dirname = scenario['info']['directory']
1504
filename = scenario['info']['filename']
1506
self.overrideAttr(diffobj, '_fenc', lambda: encoding)
1507
relpath = dirname + u'/' + filename
1508
fullpath = diffobj._safe_filename('safe', relpath)
1511
fullpath.encode(encoding).decode(encoding)
1513
self.assert_(fullpath.startswith(diffobj._root + '/safe'))
1515
def test_unencodable_filename(self):
1516
diffobj = diff.DiffFromTool(['dummy', '@old_path', '@new_path'],
1518
for _, scenario in EncodingAdapter.encoding_scenarios:
1519
encoding = scenario['encoding']
1520
dirname = scenario['info']['directory']
1521
filename = scenario['info']['filename']
1523
if encoding == 'iso-8859-1':
1524
encoding = 'iso-8859-2'
1526
encoding = 'iso-8859-1'
1528
self.overrideAttr(diffobj, '_fenc', lambda: encoding)
1529
relpath = dirname + u'/' + filename
1530
fullpath = diffobj._safe_filename('safe', relpath)
1533
fullpath.encode(encoding).decode(encoding)
1535
self.assert_(fullpath.startswith(diffobj._root + '/safe'))
1538
class TestGetTreesAndBranchesToDiffLocked(tests.TestCaseWithTransport):
1540
def call_gtabtd(self, path_list, revision_specs, old_url, new_url):
1541
"""Call get_trees_and_branches_to_diff_locked."""
1542
return diff.get_trees_and_branches_to_diff_locked(
1543
path_list, revision_specs, old_url, new_url, self.addCleanup)
1545
def test_basic(self):
1546
tree = self.make_branch_and_tree('tree')
1547
(old_tree, new_tree,
1548
old_branch, new_branch,
1549
specific_files, extra_trees) = self.call_gtabtd(
1550
['tree'], None, None, None)
1552
self.assertIsInstance(old_tree, revisiontree.RevisionTree)
1553
self.assertEqual(_mod_revision.NULL_REVISION,
1554
old_tree.get_revision_id())
1555
self.assertEqual(tree.basedir, new_tree.basedir)
1556
self.assertEqual(tree.branch.base, old_branch.base)
1557
self.assertEqual(tree.branch.base, new_branch.base)
1558
self.assertIs(None, specific_files)
1559
self.assertIs(None, extra_trees)
1561
def test_with_rev_specs(self):
1562
tree = self.make_branch_and_tree('tree')
1563
self.build_tree_contents([('tree/file', 'oldcontent')])
1564
tree.add('file', 'file-id')
1565
tree.commit('old tree', timestamp=0, rev_id="old-id")
1566
self.build_tree_contents([('tree/file', 'newcontent')])
1567
tree.commit('new tree', timestamp=0, rev_id="new-id")
1569
revisions = [revisionspec.RevisionSpec.from_string('1'),
1570
revisionspec.RevisionSpec.from_string('2')]
1571
(old_tree, new_tree,
1572
old_branch, new_branch,
1573
specific_files, extra_trees) = self.call_gtabtd(
1574
['tree'], revisions, None, None)
1576
self.assertIsInstance(old_tree, revisiontree.RevisionTree)
1577
self.assertEqual("old-id", old_tree.get_revision_id())
1578
self.assertIsInstance(new_tree, revisiontree.RevisionTree)
1579
self.assertEqual("new-id", new_tree.get_revision_id())
1580
self.assertEqual(tree.branch.base, old_branch.base)
1581
self.assertEqual(tree.branch.base, new_branch.base)
1582
self.assertIs(None, specific_files)
1583
self.assertEqual(tree.basedir, extra_trees[0].basedir)