1
# Copyright (C) 2005-2010 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
from cStringIO import StringIO
29
revision as _mod_revision,
35
from bzrlib.symbol_versioning import deprecated_in
36
from bzrlib.tests import features
37
from bzrlib.tests.blackbox.test_diff import subst_dates
40
class _AttribFeature(tests.Feature):
43
if (sys.platform not in ('cygwin', 'win32')):
46
proc = subprocess.Popen(['attrib', '.'], stdout=subprocess.PIPE)
49
return (0 == proc.wait())
51
def feature_name(self):
52
return 'attrib Windows command-line tool'
54
AttribFeature = _AttribFeature()
57
compiled_patiencediff_feature = tests.ModuleAvailableFeature(
58
'bzrlib._patiencediff_c')
61
def udiff_lines(old, new, allow_binary=False):
63
diff.internal_diff('old', old, 'new', new, output, allow_binary)
65
return output.readlines()
68
def external_udiff_lines(old, new, use_stringio=False):
70
# StringIO has no fileno, so it tests a different codepath
73
output = tempfile.TemporaryFile()
75
diff.external_diff('old', old, 'new', new, output, diff_opts=['-u'])
77
raise tests.TestSkipped('external "diff" not present to test')
79
lines = output.readlines()
84
class TestDiff(tests.TestCase):
86
def test_add_nl(self):
87
"""diff generates a valid diff for patches that add a newline"""
88
lines = udiff_lines(['boo'], ['boo\n'])
89
self.check_patch(lines)
90
self.assertEquals(lines[4], '\\ No newline at end of file\n')
91
## "expected no-nl, got %r" % lines[4]
93
def test_add_nl_2(self):
94
"""diff generates a valid diff for patches that change last line and
97
lines = udiff_lines(['boo'], ['goo\n'])
98
self.check_patch(lines)
99
self.assertEquals(lines[4], '\\ No newline at end of file\n')
100
## "expected no-nl, got %r" % lines[4]
102
def test_remove_nl(self):
103
"""diff generates a valid diff for patches that change last line and
106
lines = udiff_lines(['boo\n'], ['boo'])
107
self.check_patch(lines)
108
self.assertEquals(lines[5], '\\ No newline at end of file\n')
109
## "expected no-nl, got %r" % lines[5]
111
def check_patch(self, lines):
112
self.assert_(len(lines) > 1)
113
## "Not enough lines for a file header for patch:\n%s" % "".join(lines)
114
self.assert_(lines[0].startswith ('---'))
115
## 'No orig line for patch:\n%s' % "".join(lines)
116
self.assert_(lines[1].startswith ('+++'))
117
## 'No mod line for patch:\n%s' % "".join(lines)
118
self.assert_(len(lines) > 2)
119
## "No hunks for patch:\n%s" % "".join(lines)
120
self.assert_(lines[2].startswith('@@'))
121
## "No hunk header for patch:\n%s" % "".join(lines)
122
self.assert_('@@' in lines[2][2:])
123
## "Unterminated hunk header for patch:\n%s" % "".join(lines)
125
def test_binary_lines(self):
127
uni_lines = [1023 * 'a' + '\x00']
128
self.assertRaises(errors.BinaryFile, udiff_lines, uni_lines , empty)
129
self.assertRaises(errors.BinaryFile, udiff_lines, empty, uni_lines)
130
udiff_lines(uni_lines , empty, allow_binary=True)
131
udiff_lines(empty, uni_lines, allow_binary=True)
133
def test_external_diff(self):
134
lines = external_udiff_lines(['boo\n'], ['goo\n'])
135
self.check_patch(lines)
136
self.assertEqual('\n', lines[-1])
138
def test_external_diff_no_fileno(self):
139
# Make sure that we can handle not having a fileno, even
140
# if the diff is large
141
lines = external_udiff_lines(['boo\n']*10000,
144
self.check_patch(lines)
146
def test_external_diff_binary_lang_c(self):
148
for lang in ('LANG', 'LC_ALL', 'LANGUAGE'):
149
old_env[lang] = osutils.set_or_unset_env(lang, 'C')
151
lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
152
# Older versions of diffutils say "Binary files", newer
153
# versions just say "Files".
154
self.assertContainsRe(lines[0],
155
'(Binary f|F)iles old and new differ\n')
156
self.assertEquals(lines[1:], ['\n'])
158
for lang, old_val in old_env.iteritems():
159
osutils.set_or_unset_env(lang, old_val)
161
def test_no_external_diff(self):
162
"""Check that NoDiff is raised when diff is not available"""
163
# Use os.environ['PATH'] to make sure no 'diff' command is available
164
orig_path = os.environ['PATH']
166
os.environ['PATH'] = ''
167
self.assertRaises(errors.NoDiff, diff.external_diff,
168
'old', ['boo\n'], 'new', ['goo\n'],
169
StringIO(), diff_opts=['-u'])
171
os.environ['PATH'] = orig_path
173
def test_internal_diff_default(self):
174
# Default internal diff encoding is utf8
176
diff.internal_diff(u'old_\xb5', ['old_text\n'],
177
u'new_\xe5', ['new_text\n'], output)
178
lines = output.getvalue().splitlines(True)
179
self.check_patch(lines)
180
self.assertEquals(['--- old_\xc2\xb5\n',
181
'+++ new_\xc3\xa5\n',
189
def test_internal_diff_utf8(self):
191
diff.internal_diff(u'old_\xb5', ['old_text\n'],
192
u'new_\xe5', ['new_text\n'], output,
193
path_encoding='utf8')
194
lines = output.getvalue().splitlines(True)
195
self.check_patch(lines)
196
self.assertEquals(['--- old_\xc2\xb5\n',
197
'+++ new_\xc3\xa5\n',
205
def test_internal_diff_iso_8859_1(self):
207
diff.internal_diff(u'old_\xb5', ['old_text\n'],
208
u'new_\xe5', ['new_text\n'], output,
209
path_encoding='iso-8859-1')
210
lines = output.getvalue().splitlines(True)
211
self.check_patch(lines)
212
self.assertEquals(['--- old_\xb5\n',
221
def test_internal_diff_no_content(self):
223
diff.internal_diff(u'old', [], u'new', [], output)
224
self.assertEqual('', output.getvalue())
226
def test_internal_diff_no_changes(self):
228
diff.internal_diff(u'old', ['text\n', 'contents\n'],
229
u'new', ['text\n', 'contents\n'],
231
self.assertEqual('', output.getvalue())
233
def test_internal_diff_returns_bytes(self):
235
output = StringIO.StringIO()
236
diff.internal_diff(u'old_\xb5', ['old_text\n'],
237
u'new_\xe5', ['new_text\n'], output)
238
self.failUnless(isinstance(output.getvalue(), str),
239
'internal_diff should return bytestrings')
242
class TestDiffFiles(tests.TestCaseInTempDir):
244
def test_external_diff_binary(self):
245
"""The output when using external diff should use diff's i18n error"""
246
# Make sure external_diff doesn't fail in the current LANG
247
lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
249
cmd = ['diff', '-u', '--binary', 'old', 'new']
250
open('old', 'wb').write('\x00foobar\n')
251
open('new', 'wb').write('foo\x00bar\n')
252
pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
253
stdin=subprocess.PIPE)
254
out, err = pipe.communicate()
255
# Diff returns '2' on Binary files.
256
self.assertEqual(2, pipe.returncode)
257
# We should output whatever diff tells us, plus a trailing newline
258
self.assertEqual(out.splitlines(True) + ['\n'], lines)
261
class TestShowDiffTreesHelper(tests.TestCaseWithTransport):
262
"""Has a helper for running show_diff_trees"""
264
def get_diff(self, tree1, tree2, specific_files=None, working_tree=None):
266
if working_tree is not None:
267
extra_trees = (working_tree,)
270
diff.show_diff_trees(tree1, tree2, output,
271
specific_files=specific_files,
272
extra_trees=extra_trees, old_label='old/',
274
return output.getvalue()
277
class TestDiffDates(TestShowDiffTreesHelper):
280
super(TestDiffDates, self).setUp()
281
self.wt = self.make_branch_and_tree('.')
282
self.b = self.wt.branch
283
self.build_tree_contents([
284
('file1', 'file1 contents at rev 1\n'),
285
('file2', 'file2 contents at rev 1\n')
287
self.wt.add(['file1', 'file2'])
289
message='Revision 1',
290
timestamp=1143849600, # 2006-04-01 00:00:00 UTC
293
self.build_tree_contents([('file1', 'file1 contents at rev 2\n')])
295
message='Revision 2',
296
timestamp=1143936000, # 2006-04-02 00:00:00 UTC
299
self.build_tree_contents([('file2', 'file2 contents at rev 3\n')])
301
message='Revision 3',
302
timestamp=1144022400, # 2006-04-03 00:00:00 UTC
305
self.wt.remove(['file2'])
307
message='Revision 4',
308
timestamp=1144108800, # 2006-04-04 00:00:00 UTC
311
self.build_tree_contents([
312
('file1', 'file1 contents in working tree\n')
314
# set the date stamps for files in the working tree to known values
315
os.utime('file1', (1144195200, 1144195200)) # 2006-04-05 00:00:00 UTC
317
def test_diff_rev_tree_working_tree(self):
318
output = self.get_diff(self.wt.basis_tree(), self.wt)
319
# note that the date for old/file1 is from rev 2 rather than from
320
# the basis revision (rev 4)
321
self.assertEqualDiff(output, '''\
322
=== modified file 'file1'
323
--- old/file1\t2006-04-02 00:00:00 +0000
324
+++ new/file1\t2006-04-05 00:00:00 +0000
326
-file1 contents at rev 2
327
+file1 contents in working tree
331
def test_diff_rev_tree_rev_tree(self):
332
tree1 = self.b.repository.revision_tree('rev-2')
333
tree2 = self.b.repository.revision_tree('rev-3')
334
output = self.get_diff(tree1, tree2)
335
self.assertEqualDiff(output, '''\
336
=== modified file 'file2'
337
--- old/file2\t2006-04-01 00:00:00 +0000
338
+++ new/file2\t2006-04-03 00:00:00 +0000
340
-file2 contents at rev 1
341
+file2 contents at rev 3
345
def test_diff_add_files(self):
346
tree1 = self.b.repository.revision_tree(_mod_revision.NULL_REVISION)
347
tree2 = self.b.repository.revision_tree('rev-1')
348
output = self.get_diff(tree1, tree2)
349
# the files have the epoch time stamp for the tree in which
351
self.assertEqualDiff(output, '''\
352
=== added file 'file1'
353
--- old/file1\t1970-01-01 00:00:00 +0000
354
+++ new/file1\t2006-04-01 00:00:00 +0000
356
+file1 contents at rev 1
358
=== added file 'file2'
359
--- old/file2\t1970-01-01 00:00:00 +0000
360
+++ new/file2\t2006-04-01 00:00:00 +0000
362
+file2 contents at rev 1
366
def test_diff_remove_files(self):
367
tree1 = self.b.repository.revision_tree('rev-3')
368
tree2 = self.b.repository.revision_tree('rev-4')
369
output = self.get_diff(tree1, tree2)
370
# the file has the epoch time stamp for the tree in which
372
self.assertEqualDiff(output, '''\
373
=== removed file 'file2'
374
--- old/file2\t2006-04-03 00:00:00 +0000
375
+++ new/file2\t1970-01-01 00:00:00 +0000
377
-file2 contents at rev 3
381
def test_show_diff_specified(self):
382
"""A working tree filename can be used to identify a file"""
383
self.wt.rename_one('file1', 'file1b')
384
old_tree = self.b.repository.revision_tree('rev-1')
385
new_tree = self.b.repository.revision_tree('rev-4')
386
out = self.get_diff(old_tree, new_tree, specific_files=['file1b'],
387
working_tree=self.wt)
388
self.assertContainsRe(out, 'file1\t')
390
def test_recursive_diff(self):
391
"""Children of directories are matched"""
394
self.wt.add(['dir1', 'dir2'])
395
self.wt.rename_one('file1', 'dir1/file1')
396
old_tree = self.b.repository.revision_tree('rev-1')
397
new_tree = self.b.repository.revision_tree('rev-4')
398
out = self.get_diff(old_tree, new_tree, specific_files=['dir1'],
399
working_tree=self.wt)
400
self.assertContainsRe(out, 'file1\t')
401
out = self.get_diff(old_tree, new_tree, specific_files=['dir2'],
402
working_tree=self.wt)
403
self.assertNotContainsRe(out, 'file1\t')
407
class TestShowDiffTrees(TestShowDiffTreesHelper):
408
"""Direct tests for show_diff_trees"""
410
def test_modified_file(self):
411
"""Test when a file is modified."""
412
tree = self.make_branch_and_tree('tree')
413
self.build_tree_contents([('tree/file', 'contents\n')])
414
tree.add(['file'], ['file-id'])
415
tree.commit('one', rev_id='rev-1')
417
self.build_tree_contents([('tree/file', 'new contents\n')])
418
d = self.get_diff(tree.basis_tree(), tree)
419
self.assertContainsRe(d, "=== modified file 'file'\n")
420
self.assertContainsRe(d, '--- old/file\t')
421
self.assertContainsRe(d, '\\+\\+\\+ new/file\t')
422
self.assertContainsRe(d, '-contents\n'
425
def test_modified_file_in_renamed_dir(self):
426
"""Test when a file is modified in a renamed directory."""
427
tree = self.make_branch_and_tree('tree')
428
self.build_tree(['tree/dir/'])
429
self.build_tree_contents([('tree/dir/file', 'contents\n')])
430
tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
431
tree.commit('one', rev_id='rev-1')
433
tree.rename_one('dir', 'other')
434
self.build_tree_contents([('tree/other/file', 'new contents\n')])
435
d = self.get_diff(tree.basis_tree(), tree)
436
self.assertContainsRe(d, "=== renamed directory 'dir' => 'other'\n")
437
self.assertContainsRe(d, "=== modified file 'other/file'\n")
438
# XXX: This is technically incorrect, because it used to be at another
439
# location. What to do?
440
self.assertContainsRe(d, '--- old/dir/file\t')
441
self.assertContainsRe(d, '\\+\\+\\+ new/other/file\t')
442
self.assertContainsRe(d, '-contents\n'
445
def test_renamed_directory(self):
446
"""Test when only a directory is only renamed."""
447
tree = self.make_branch_and_tree('tree')
448
self.build_tree(['tree/dir/'])
449
self.build_tree_contents([('tree/dir/file', 'contents\n')])
450
tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
451
tree.commit('one', rev_id='rev-1')
453
tree.rename_one('dir', 'newdir')
454
d = self.get_diff(tree.basis_tree(), tree)
455
# Renaming a directory should be a single "you renamed this dir" even
456
# when there are files inside.
457
self.assertEqual(d, "=== renamed directory 'dir' => 'newdir'\n")
459
def test_renamed_file(self):
460
"""Test when a file is only renamed."""
461
tree = self.make_branch_and_tree('tree')
462
self.build_tree_contents([('tree/file', 'contents\n')])
463
tree.add(['file'], ['file-id'])
464
tree.commit('one', rev_id='rev-1')
466
tree.rename_one('file', 'newname')
467
d = self.get_diff(tree.basis_tree(), tree)
468
self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
469
# We shouldn't have a --- or +++ line, because there is no content
471
self.assertNotContainsRe(d, '---')
473
def test_renamed_and_modified_file(self):
474
"""Test when a file is only renamed."""
475
tree = self.make_branch_and_tree('tree')
476
self.build_tree_contents([('tree/file', 'contents\n')])
477
tree.add(['file'], ['file-id'])
478
tree.commit('one', rev_id='rev-1')
480
tree.rename_one('file', 'newname')
481
self.build_tree_contents([('tree/newname', 'new contents\n')])
482
d = self.get_diff(tree.basis_tree(), tree)
483
self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
484
self.assertContainsRe(d, '--- old/file\t')
485
self.assertContainsRe(d, '\\+\\+\\+ new/newname\t')
486
self.assertContainsRe(d, '-contents\n'
490
def test_internal_diff_exec_property(self):
491
tree = self.make_branch_and_tree('tree')
493
tt = transform.TreeTransform(tree)
494
tt.new_file('a', tt.root, 'contents\n', 'a-id', True)
495
tt.new_file('b', tt.root, 'contents\n', 'b-id', False)
496
tt.new_file('c', tt.root, 'contents\n', 'c-id', True)
497
tt.new_file('d', tt.root, 'contents\n', 'd-id', False)
498
tt.new_file('e', tt.root, 'contents\n', 'control-e-id', True)
499
tt.new_file('f', tt.root, 'contents\n', 'control-f-id', False)
501
tree.commit('one', rev_id='rev-1')
503
tt = transform.TreeTransform(tree)
504
tt.set_executability(False, tt.trans_id_file_id('a-id'))
505
tt.set_executability(True, tt.trans_id_file_id('b-id'))
506
tt.set_executability(False, tt.trans_id_file_id('c-id'))
507
tt.set_executability(True, tt.trans_id_file_id('d-id'))
509
tree.rename_one('c', 'new-c')
510
tree.rename_one('d', 'new-d')
512
d = self.get_diff(tree.basis_tree(), tree)
514
self.assertContainsRe(d, r"file 'a'.*\(properties changed:"
516
self.assertContainsRe(d, r"file 'b'.*\(properties changed:"
518
self.assertContainsRe(d, r"file 'c'.*\(properties changed:"
520
self.assertContainsRe(d, r"file 'd'.*\(properties changed:"
522
self.assertNotContainsRe(d, r"file 'e'")
523
self.assertNotContainsRe(d, r"file 'f'")
525
def test_binary_unicode_filenames(self):
526
"""Test that contents of files are *not* encoded in UTF-8 when there
527
is a binary file in the diff.
529
# See https://bugs.launchpad.net/bugs/110092.
530
self.requireFeature(tests.UnicodeFilenameFeature)
532
# This bug isn't triggered with cStringIO.
533
from StringIO import StringIO
534
tree = self.make_branch_and_tree('tree')
535
alpha, omega = u'\u03b1', u'\u03c9'
536
alpha_utf8, omega_utf8 = alpha.encode('utf8'), omega.encode('utf8')
537
self.build_tree_contents(
538
[('tree/' + alpha, chr(0)),
540
('The %s and the %s\n' % (alpha_utf8, omega_utf8)))])
541
tree.add([alpha], ['file-id'])
542
tree.add([omega], ['file-id-2'])
543
diff_content = StringIO()
544
diff.show_diff_trees(tree.basis_tree(), tree, diff_content)
545
d = diff_content.getvalue()
546
self.assertContainsRe(d, r"=== added file '%s'" % alpha_utf8)
547
self.assertContainsRe(d, "Binary files a/%s.*and b/%s.* differ\n"
548
% (alpha_utf8, alpha_utf8))
549
self.assertContainsRe(d, r"=== added file '%s'" % omega_utf8)
550
self.assertContainsRe(d, r"--- a/%s" % (omega_utf8,))
551
self.assertContainsRe(d, r"\+\+\+ b/%s" % (omega_utf8,))
553
def test_unicode_filename(self):
554
"""Test when the filename are unicode."""
555
self.requireFeature(tests.UnicodeFilenameFeature)
557
alpha, omega = u'\u03b1', u'\u03c9'
558
autf8, outf8 = alpha.encode('utf8'), omega.encode('utf8')
560
tree = self.make_branch_and_tree('tree')
561
self.build_tree_contents([('tree/ren_'+alpha, 'contents\n')])
562
tree.add(['ren_'+alpha], ['file-id-2'])
563
self.build_tree_contents([('tree/del_'+alpha, 'contents\n')])
564
tree.add(['del_'+alpha], ['file-id-3'])
565
self.build_tree_contents([('tree/mod_'+alpha, 'contents\n')])
566
tree.add(['mod_'+alpha], ['file-id-4'])
568
tree.commit('one', rev_id='rev-1')
570
tree.rename_one('ren_'+alpha, 'ren_'+omega)
571
tree.remove('del_'+alpha)
572
self.build_tree_contents([('tree/add_'+alpha, 'contents\n')])
573
tree.add(['add_'+alpha], ['file-id'])
574
self.build_tree_contents([('tree/mod_'+alpha, 'contents_mod\n')])
576
d = self.get_diff(tree.basis_tree(), tree)
577
self.assertContainsRe(d,
578
"=== renamed file 'ren_%s' => 'ren_%s'\n"%(autf8, outf8))
579
self.assertContainsRe(d, "=== added file 'add_%s'"%autf8)
580
self.assertContainsRe(d, "=== modified file 'mod_%s'"%autf8)
581
self.assertContainsRe(d, "=== removed file 'del_%s'"%autf8)
583
def test_unicode_filename_path_encoding(self):
584
"""Test for bug #382699: unicode filenames on Windows should be shown
587
self.requireFeature(tests.UnicodeFilenameFeature)
588
# The word 'test' in Russian
589
_russian_test = u'\u0422\u0435\u0441\u0442'
590
directory = _russian_test + u'/'
591
test_txt = _russian_test + u'.txt'
592
u1234 = u'\u1234.txt'
594
tree = self.make_branch_and_tree('.')
595
self.build_tree_contents([
600
tree.add([test_txt, u1234, directory])
603
diff.show_diff_trees(tree.basis_tree(), tree, sio,
604
path_encoding='cp1251')
606
output = subst_dates(sio.getvalue())
608
=== added directory '%(directory)s'
609
=== added file '%(test_txt)s'
610
--- a/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
611
+++ b/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
615
=== added file '?.txt'
616
--- a/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
617
+++ b/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
621
''' % {'directory': _russian_test.encode('cp1251'),
622
'test_txt': test_txt.encode('cp1251'),
624
self.assertEqualDiff(output, shouldbe)
627
class DiffWasIs(diff.DiffPath):
629
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
630
self.to_file.write('was: ')
631
self.to_file.write(self.old_tree.get_file(file_id).read())
632
self.to_file.write('is: ')
633
self.to_file.write(self.new_tree.get_file(file_id).read())
637
class TestDiffTree(tests.TestCaseWithTransport):
640
super(TestDiffTree, self).setUp()
641
self.old_tree = self.make_branch_and_tree('old-tree')
642
self.old_tree.lock_write()
643
self.addCleanup(self.old_tree.unlock)
644
self.new_tree = self.make_branch_and_tree('new-tree')
645
self.new_tree.lock_write()
646
self.addCleanup(self.new_tree.unlock)
647
self.differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
649
def test_diff_text(self):
650
self.build_tree_contents([('old-tree/olddir/',),
651
('old-tree/olddir/oldfile', 'old\n')])
652
self.old_tree.add('olddir')
653
self.old_tree.add('olddir/oldfile', 'file-id')
654
self.build_tree_contents([('new-tree/newdir/',),
655
('new-tree/newdir/newfile', 'new\n')])
656
self.new_tree.add('newdir')
657
self.new_tree.add('newdir/newfile', 'file-id')
658
differ = diff.DiffText(self.old_tree, self.new_tree, StringIO())
659
differ.diff_text('file-id', None, 'old label', 'new label')
661
'--- old label\n+++ new label\n@@ -1,1 +0,0 @@\n-old\n\n',
662
differ.to_file.getvalue())
663
differ.to_file.seek(0)
664
differ.diff_text(None, 'file-id', 'old label', 'new label')
666
'--- old label\n+++ new label\n@@ -0,0 +1,1 @@\n+new\n\n',
667
differ.to_file.getvalue())
668
differ.to_file.seek(0)
669
differ.diff_text('file-id', 'file-id', 'old label', 'new label')
671
'--- old label\n+++ new label\n@@ -1,1 +1,1 @@\n-old\n+new\n\n',
672
differ.to_file.getvalue())
674
def test_diff_deletion(self):
675
self.build_tree_contents([('old-tree/file', 'contents'),
676
('new-tree/file', 'contents')])
677
self.old_tree.add('file', 'file-id')
678
self.new_tree.add('file', 'file-id')
679
os.unlink('new-tree/file')
680
self.differ.show_diff(None)
681
self.assertContainsRe(self.differ.to_file.getvalue(), '-contents')
683
def test_diff_creation(self):
684
self.build_tree_contents([('old-tree/file', 'contents'),
685
('new-tree/file', 'contents')])
686
self.old_tree.add('file', 'file-id')
687
self.new_tree.add('file', 'file-id')
688
os.unlink('old-tree/file')
689
self.differ.show_diff(None)
690
self.assertContainsRe(self.differ.to_file.getvalue(), '\+contents')
692
def test_diff_symlink(self):
693
differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
694
differ.diff_symlink('old target', None)
695
self.assertEqual("=== target was 'old target'\n",
696
differ.to_file.getvalue())
698
differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
699
differ.diff_symlink(None, 'new target')
700
self.assertEqual("=== target is 'new target'\n",
701
differ.to_file.getvalue())
703
differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
704
differ.diff_symlink('old target', 'new target')
705
self.assertEqual("=== target changed 'old target' => 'new target'\n",
706
differ.to_file.getvalue())
709
self.build_tree_contents([('old-tree/olddir/',),
710
('old-tree/olddir/oldfile', 'old\n')])
711
self.old_tree.add('olddir')
712
self.old_tree.add('olddir/oldfile', 'file-id')
713
self.build_tree_contents([('new-tree/newdir/',),
714
('new-tree/newdir/newfile', 'new\n')])
715
self.new_tree.add('newdir')
716
self.new_tree.add('newdir/newfile', 'file-id')
717
self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
718
self.assertContainsRe(
719
self.differ.to_file.getvalue(),
720
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
721
' \@\@\n-old\n\+new\n\n')
723
def test_diff_kind_change(self):
724
self.requireFeature(tests.SymlinkFeature)
725
self.build_tree_contents([('old-tree/olddir/',),
726
('old-tree/olddir/oldfile', 'old\n')])
727
self.old_tree.add('olddir')
728
self.old_tree.add('olddir/oldfile', 'file-id')
729
self.build_tree(['new-tree/newdir/'])
730
os.symlink('new', 'new-tree/newdir/newfile')
731
self.new_tree.add('newdir')
732
self.new_tree.add('newdir/newfile', 'file-id')
733
self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
734
self.assertContainsRe(
735
self.differ.to_file.getvalue(),
736
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+0,0'
738
self.assertContainsRe(self.differ.to_file.getvalue(),
739
"=== target is u'new'\n")
741
def test_diff_directory(self):
742
self.build_tree(['new-tree/new-dir/'])
743
self.new_tree.add('new-dir', 'new-dir-id')
744
self.differ.diff('new-dir-id', None, 'new-dir')
745
self.assertEqual(self.differ.to_file.getvalue(), '')
747
def create_old_new(self):
748
self.build_tree_contents([('old-tree/olddir/',),
749
('old-tree/olddir/oldfile', 'old\n')])
750
self.old_tree.add('olddir')
751
self.old_tree.add('olddir/oldfile', 'file-id')
752
self.build_tree_contents([('new-tree/newdir/',),
753
('new-tree/newdir/newfile', 'new\n')])
754
self.new_tree.add('newdir')
755
self.new_tree.add('newdir/newfile', 'file-id')
757
def test_register_diff(self):
758
self.create_old_new()
759
old_diff_factories = diff.DiffTree.diff_factories
760
diff.DiffTree.diff_factories=old_diff_factories[:]
761
diff.DiffTree.diff_factories.insert(0, DiffWasIs.from_diff_tree)
763
differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
765
diff.DiffTree.diff_factories = old_diff_factories
766
differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
767
self.assertNotContainsRe(
768
differ.to_file.getvalue(),
769
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
770
' \@\@\n-old\n\+new\n\n')
771
self.assertContainsRe(differ.to_file.getvalue(),
772
'was: old\nis: new\n')
774
def test_extra_factories(self):
775
self.create_old_new()
776
differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO(),
777
extra_factories=[DiffWasIs.from_diff_tree])
778
differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
779
self.assertNotContainsRe(
780
differ.to_file.getvalue(),
781
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
782
' \@\@\n-old\n\+new\n\n')
783
self.assertContainsRe(differ.to_file.getvalue(),
784
'was: old\nis: new\n')
786
def test_alphabetical_order(self):
787
self.build_tree(['new-tree/a-file'])
788
self.new_tree.add('a-file')
789
self.build_tree(['old-tree/b-file'])
790
self.old_tree.add('b-file')
791
self.differ.show_diff(None)
792
self.assertContainsRe(self.differ.to_file.getvalue(),
793
'.*a-file(.|\n)*b-file')
796
class TestPatienceDiffLib(tests.TestCase):
799
super(TestPatienceDiffLib, self).setUp()
800
self._unique_lcs = _patiencediff_py.unique_lcs_py
801
self._recurse_matches = _patiencediff_py.recurse_matches_py
802
self._PatienceSequenceMatcher = \
803
_patiencediff_py.PatienceSequenceMatcher_py
805
def test_diff_unicode_string(self):
806
a = ''.join([unichr(i) for i in range(4000, 4500, 3)])
807
b = ''.join([unichr(i) for i in range(4300, 4800, 2)])
808
sm = self._PatienceSequenceMatcher(None, a, b)
809
mb = sm.get_matching_blocks()
810
self.assertEquals(35, len(mb))
812
def test_unique_lcs(self):
813
unique_lcs = self._unique_lcs
814
self.assertEquals(unique_lcs('', ''), [])
815
self.assertEquals(unique_lcs('', 'a'), [])
816
self.assertEquals(unique_lcs('a', ''), [])
817
self.assertEquals(unique_lcs('a', 'a'), [(0,0)])
818
self.assertEquals(unique_lcs('a', 'b'), [])
819
self.assertEquals(unique_lcs('ab', 'ab'), [(0,0), (1,1)])
820
self.assertEquals(unique_lcs('abcde', 'cdeab'), [(2,0), (3,1), (4,2)])
821
self.assertEquals(unique_lcs('cdeab', 'abcde'), [(0,2), (1,3), (2,4)])
822
self.assertEquals(unique_lcs('abXde', 'abYde'), [(0,0), (1,1),
824
self.assertEquals(unique_lcs('acbac', 'abc'), [(2,1)])
826
def test_recurse_matches(self):
827
def test_one(a, b, matches):
829
self._recurse_matches(
830
a, b, 0, 0, len(a), len(b), test_matches, 10)
831
self.assertEquals(test_matches, matches)
833
test_one(['a', '', 'b', '', 'c'], ['a', 'a', 'b', 'c', 'c'],
834
[(0, 0), (2, 2), (4, 4)])
835
test_one(['a', 'c', 'b', 'a', 'c'], ['a', 'b', 'c'],
836
[(0, 0), (2, 1), (4, 2)])
837
# Even though 'bc' is not unique globally, and is surrounded by
838
# non-matching lines, we should still match, because they are locally
840
test_one('abcdbce', 'afbcgdbce', [(0,0), (1, 2), (2, 3), (3, 5),
841
(4, 6), (5, 7), (6, 8)])
843
# recurse_matches doesn't match non-unique
844
# lines surrounded by bogus text.
845
# The update has been done in patiencediff.SequenceMatcher instead
847
# This is what it could be
848
#test_one('aBccDe', 'abccde', [(0,0), (2,2), (3,3), (5,5)])
850
# This is what it currently gives:
851
test_one('aBccDe', 'abccde', [(0,0), (5,5)])
853
def assertDiffBlocks(self, a, b, expected_blocks):
854
"""Check that the sequence matcher returns the correct blocks.
856
:param a: A sequence to match
857
:param b: Another sequence to match
858
:param expected_blocks: The expected output, not including the final
859
matching block (len(a), len(b), 0)
861
matcher = self._PatienceSequenceMatcher(None, a, b)
862
blocks = matcher.get_matching_blocks()
864
self.assertEqual((len(a), len(b), 0), last)
865
self.assertEqual(expected_blocks, blocks)
867
def test_matching_blocks(self):
868
# Some basic matching tests
869
self.assertDiffBlocks('', '', [])
870
self.assertDiffBlocks([], [], [])
871
self.assertDiffBlocks('abc', '', [])
872
self.assertDiffBlocks('', 'abc', [])
873
self.assertDiffBlocks('abcd', 'abcd', [(0, 0, 4)])
874
self.assertDiffBlocks('abcd', 'abce', [(0, 0, 3)])
875
self.assertDiffBlocks('eabc', 'abce', [(1, 0, 3)])
876
self.assertDiffBlocks('eabce', 'abce', [(1, 0, 4)])
877
self.assertDiffBlocks('abcde', 'abXde', [(0, 0, 2), (3, 3, 2)])
878
self.assertDiffBlocks('abcde', 'abXYZde', [(0, 0, 2), (3, 5, 2)])
879
self.assertDiffBlocks('abde', 'abXYZde', [(0, 0, 2), (2, 5, 2)])
880
# This may check too much, but it checks to see that
881
# a copied block stays attached to the previous section,
883
# difflib would tend to grab the trailing longest match
884
# which would make the diff not look right
885
self.assertDiffBlocks('abcdefghijklmnop', 'abcdefxydefghijklmnop',
886
[(0, 0, 6), (6, 11, 10)])
888
# make sure it supports passing in lists
889
self.assertDiffBlocks(
892
'how are you today?\n'],
894
'how are you today?\n'],
895
[(0, 0, 1), (2, 1, 1)])
897
# non unique lines surrounded by non-matching lines
899
self.assertDiffBlocks('aBccDe', 'abccde', [(0,0,1), (5,5,1)])
901
# But they only need to be locally unique
902
self.assertDiffBlocks('aBcDec', 'abcdec', [(0,0,1), (2,2,1), (4,4,2)])
904
# non unique blocks won't be matched
905
self.assertDiffBlocks('aBcdEcdFg', 'abcdecdfg', [(0,0,1), (8,8,1)])
907
# but locally unique ones will
908
self.assertDiffBlocks('aBcdEeXcdFg', 'abcdecdfg', [(0,0,1), (2,2,2),
909
(5,4,1), (7,5,2), (10,8,1)])
911
self.assertDiffBlocks('abbabbXd', 'cabbabxd', [(7,7,1)])
912
self.assertDiffBlocks('abbabbbb', 'cabbabbc', [])
913
self.assertDiffBlocks('bbbbbbbb', 'cbbbbbbc', [])
915
def test_matching_blocks_tuples(self):
916
# Some basic matching tests
917
self.assertDiffBlocks([], [], [])
918
self.assertDiffBlocks([('a',), ('b',), ('c,')], [], [])
919
self.assertDiffBlocks([], [('a',), ('b',), ('c,')], [])
920
self.assertDiffBlocks([('a',), ('b',), ('c,')],
921
[('a',), ('b',), ('c,')],
923
self.assertDiffBlocks([('a',), ('b',), ('c,')],
924
[('a',), ('b',), ('d,')],
926
self.assertDiffBlocks([('d',), ('b',), ('c,')],
927
[('a',), ('b',), ('c,')],
929
self.assertDiffBlocks([('d',), ('a',), ('b',), ('c,')],
930
[('a',), ('b',), ('c,')],
932
self.assertDiffBlocks([('a', 'b'), ('c', 'd'), ('e', 'f')],
933
[('a', 'b'), ('c', 'X'), ('e', 'f')],
934
[(0, 0, 1), (2, 2, 1)])
935
self.assertDiffBlocks([('a', 'b'), ('c', 'd'), ('e', 'f')],
936
[('a', 'b'), ('c', 'dX'), ('e', 'f')],
937
[(0, 0, 1), (2, 2, 1)])
939
def test_opcodes(self):
940
def chk_ops(a, b, expected_codes):
941
s = self._PatienceSequenceMatcher(None, a, b)
942
self.assertEquals(expected_codes, s.get_opcodes())
946
chk_ops('abc', '', [('delete', 0,3, 0,0)])
947
chk_ops('', 'abc', [('insert', 0,0, 0,3)])
948
chk_ops('abcd', 'abcd', [('equal', 0,4, 0,4)])
949
chk_ops('abcd', 'abce', [('equal', 0,3, 0,3),
950
('replace', 3,4, 3,4)
952
chk_ops('eabc', 'abce', [('delete', 0,1, 0,0),
956
chk_ops('eabce', 'abce', [('delete', 0,1, 0,0),
959
chk_ops('abcde', 'abXde', [('equal', 0,2, 0,2),
960
('replace', 2,3, 2,3),
963
chk_ops('abcde', 'abXYZde', [('equal', 0,2, 0,2),
964
('replace', 2,3, 2,5),
967
chk_ops('abde', 'abXYZde', [('equal', 0,2, 0,2),
968
('insert', 2,2, 2,5),
971
chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
972
[('equal', 0,6, 0,6),
973
('insert', 6,6, 6,11),
974
('equal', 6,16, 11,21)
979
, 'how are you today?\n'],
981
, 'how are you today?\n'],
982
[('equal', 0,1, 0,1),
983
('delete', 1,2, 1,1),
986
chk_ops('aBccDe', 'abccde',
987
[('equal', 0,1, 0,1),
988
('replace', 1,5, 1,5),
991
chk_ops('aBcDec', 'abcdec',
992
[('equal', 0,1, 0,1),
993
('replace', 1,2, 1,2),
995
('replace', 3,4, 3,4),
998
chk_ops('aBcdEcdFg', 'abcdecdfg',
999
[('equal', 0,1, 0,1),
1000
('replace', 1,8, 1,8),
1003
chk_ops('aBcdEeXcdFg', 'abcdecdfg',
1004
[('equal', 0,1, 0,1),
1005
('replace', 1,2, 1,2),
1006
('equal', 2,4, 2,4),
1007
('delete', 4,5, 4,4),
1008
('equal', 5,6, 4,5),
1009
('delete', 6,7, 5,5),
1010
('equal', 7,9, 5,7),
1011
('replace', 9,10, 7,8),
1012
('equal', 10,11, 8,9)
1015
def test_grouped_opcodes(self):
1016
def chk_ops(a, b, expected_codes, n=3):
1017
s = self._PatienceSequenceMatcher(None, a, b)
1018
self.assertEquals(expected_codes, list(s.get_grouped_opcodes(n)))
1022
chk_ops('abc', '', [[('delete', 0,3, 0,0)]])
1023
chk_ops('', 'abc', [[('insert', 0,0, 0,3)]])
1024
chk_ops('abcd', 'abcd', [])
1025
chk_ops('abcd', 'abce', [[('equal', 0,3, 0,3),
1026
('replace', 3,4, 3,4)
1028
chk_ops('eabc', 'abce', [[('delete', 0,1, 0,0),
1029
('equal', 1,4, 0,3),
1030
('insert', 4,4, 3,4)
1032
chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
1033
[[('equal', 3,6, 3,6),
1034
('insert', 6,6, 6,11),
1035
('equal', 6,9, 11,14)
1037
chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
1038
[[('equal', 2,6, 2,6),
1039
('insert', 6,6, 6,11),
1040
('equal', 6,10, 11,15)
1042
chk_ops('Xabcdef', 'abcdef',
1043
[[('delete', 0,1, 0,0),
1046
chk_ops('abcdef', 'abcdefX',
1047
[[('equal', 3,6, 3,6),
1048
('insert', 6,6, 6,7)
1052
def test_multiple_ranges(self):
1053
# There was an earlier bug where we used a bad set of ranges,
1054
# this triggers that specific bug, to make sure it doesn't regress
1055
self.assertDiffBlocks('abcdefghijklmnop',
1056
'abcXghiYZQRSTUVWXYZijklmnop',
1057
[(0, 0, 3), (6, 4, 3), (9, 20, 7)])
1059
self.assertDiffBlocks('ABCd efghIjk L',
1060
'AxyzBCn mo pqrstuvwI1 2 L',
1061
[(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
1063
# These are rot13 code snippets.
1064
self.assertDiffBlocks('''\
1065
trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
1067
gnxrf_netf = ['svyr*']
1068
gnxrf_bcgvbaf = ['ab-erphefr']
1070
qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr):
1071
sebz omeyvo.nqq vzcbeg fzneg_nqq, nqq_ercbegre_cevag, nqq_ercbegre_ahyy
1073
ercbegre = nqq_ercbegre_ahyy
1075
ercbegre = nqq_ercbegre_cevag
1076
fzneg_nqq(svyr_yvfg, abg ab_erphefr, ercbegre)
1079
pynff pzq_zxqve(Pbzznaq):
1080
'''.splitlines(True), '''\
1081
trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
1083
--qel-eha jvyy fubj juvpu svyrf jbhyq or nqqrq, ohg abg npghnyyl
1086
gnxrf_netf = ['svyr*']
1087
gnxrf_bcgvbaf = ['ab-erphefr', 'qel-eha']
1089
qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr, qel_eha=Snyfr):
1094
# Guvf vf cbvagyrff, ohg V'q engure abg envfr na reebe
1095
npgvba = omeyvo.nqq.nqq_npgvba_ahyy
1097
npgvba = omeyvo.nqq.nqq_npgvba_cevag
1099
npgvba = omeyvo.nqq.nqq_npgvba_nqq
1101
npgvba = omeyvo.nqq.nqq_npgvba_nqq_naq_cevag
1103
omeyvo.nqq.fzneg_nqq(svyr_yvfg, abg ab_erphefr, npgvba)
1106
pynff pzq_zxqve(Pbzznaq):
1107
'''.splitlines(True)
1108
, [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
1110
def test_patience_unified_diff(self):
1111
txt_a = ['hello there\n',
1113
'how are you today?\n']
1114
txt_b = ['hello there\n',
1115
'how are you today?\n']
1116
unified_diff = patiencediff.unified_diff
1117
psm = self._PatienceSequenceMatcher
1118
self.assertEquals(['--- \n',
1120
'@@ -1,3 +1,2 @@\n',
1123
' how are you today?\n'
1125
, list(unified_diff(txt_a, txt_b,
1126
sequencematcher=psm)))
1127
txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
1128
txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
1129
# This is the result with LongestCommonSubstring matching
1130
self.assertEquals(['--- \n',
1132
'@@ -1,6 +1,11 @@\n',
1144
, list(unified_diff(txt_a, txt_b)))
1145
# And the patience diff
1146
self.assertEquals(['--- \n',
1148
'@@ -4,6 +4,11 @@\n',
1161
, list(unified_diff(txt_a, txt_b,
1162
sequencematcher=psm)))
1164
def test_patience_unified_diff_with_dates(self):
1165
txt_a = ['hello there\n',
1167
'how are you today?\n']
1168
txt_b = ['hello there\n',
1169
'how are you today?\n']
1170
unified_diff = patiencediff.unified_diff
1171
psm = self._PatienceSequenceMatcher
1172
self.assertEquals(['--- a\t2008-08-08\n',
1173
'+++ b\t2008-09-09\n',
1174
'@@ -1,3 +1,2 @@\n',
1177
' how are you today?\n'
1179
, list(unified_diff(txt_a, txt_b,
1180
fromfile='a', tofile='b',
1181
fromfiledate='2008-08-08',
1182
tofiledate='2008-09-09',
1183
sequencematcher=psm)))
1186
class TestPatienceDiffLib_c(TestPatienceDiffLib):
1188
_test_needs_features = [compiled_patiencediff_feature]
1191
super(TestPatienceDiffLib_c, self).setUp()
1192
from bzrlib import _patiencediff_c
1193
self._unique_lcs = _patiencediff_c.unique_lcs_c
1194
self._recurse_matches = _patiencediff_c.recurse_matches_c
1195
self._PatienceSequenceMatcher = \
1196
_patiencediff_c.PatienceSequenceMatcher_c
1198
def test_unhashable(self):
1199
"""We should get a proper exception here."""
1200
# We need to be able to hash items in the sequence, lists are
1201
# unhashable, and thus cannot be diffed
1202
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1204
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1205
None, ['valid', []], [])
1206
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1207
None, ['valid'], [[]])
1208
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1209
None, ['valid'], ['valid', []])
1212
class TestPatienceDiffLibFiles(tests.TestCaseInTempDir):
1215
super(TestPatienceDiffLibFiles, self).setUp()
1216
self._PatienceSequenceMatcher = \
1217
_patiencediff_py.PatienceSequenceMatcher_py
1219
def test_patience_unified_diff_files(self):
1220
txt_a = ['hello there\n',
1222
'how are you today?\n']
1223
txt_b = ['hello there\n',
1224
'how are you today?\n']
1225
open('a1', 'wb').writelines(txt_a)
1226
open('b1', 'wb').writelines(txt_b)
1228
unified_diff_files = patiencediff.unified_diff_files
1229
psm = self._PatienceSequenceMatcher
1230
self.assertEquals(['--- a1\n',
1232
'@@ -1,3 +1,2 @@\n',
1235
' how are you today?\n',
1237
, list(unified_diff_files('a1', 'b1',
1238
sequencematcher=psm)))
1240
txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
1241
txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
1242
open('a2', 'wb').writelines(txt_a)
1243
open('b2', 'wb').writelines(txt_b)
1245
# This is the result with LongestCommonSubstring matching
1246
self.assertEquals(['--- a2\n',
1248
'@@ -1,6 +1,11 @@\n',
1260
, list(unified_diff_files('a2', 'b2')))
1262
# And the patience diff
1263
self.assertEquals(['--- a2\n',
1265
'@@ -4,6 +4,11 @@\n',
1278
, list(unified_diff_files('a2', 'b2',
1279
sequencematcher=psm)))
1282
class TestPatienceDiffLibFiles_c(TestPatienceDiffLibFiles):
1284
_test_needs_features = [compiled_patiencediff_feature]
1287
super(TestPatienceDiffLibFiles_c, self).setUp()
1288
from bzrlib import _patiencediff_c
1289
self._PatienceSequenceMatcher = \
1290
_patiencediff_c.PatienceSequenceMatcher_c
1293
class TestUsingCompiledIfAvailable(tests.TestCase):
1295
def test_PatienceSequenceMatcher(self):
1296
if compiled_patiencediff_feature.available():
1297
from bzrlib._patiencediff_c import PatienceSequenceMatcher_c
1298
self.assertIs(PatienceSequenceMatcher_c,
1299
patiencediff.PatienceSequenceMatcher)
1301
from bzrlib._patiencediff_py import PatienceSequenceMatcher_py
1302
self.assertIs(PatienceSequenceMatcher_py,
1303
patiencediff.PatienceSequenceMatcher)
1305
def test_unique_lcs(self):
1306
if compiled_patiencediff_feature.available():
1307
from bzrlib._patiencediff_c import unique_lcs_c
1308
self.assertIs(unique_lcs_c,
1309
patiencediff.unique_lcs)
1311
from bzrlib._patiencediff_py import unique_lcs_py
1312
self.assertIs(unique_lcs_py,
1313
patiencediff.unique_lcs)
1315
def test_recurse_matches(self):
1316
if compiled_patiencediff_feature.available():
1317
from bzrlib._patiencediff_c import recurse_matches_c
1318
self.assertIs(recurse_matches_c,
1319
patiencediff.recurse_matches)
1321
from bzrlib._patiencediff_py import recurse_matches_py
1322
self.assertIs(recurse_matches_py,
1323
patiencediff.recurse_matches)
1326
class TestDiffFromTool(tests.TestCaseWithTransport):
1328
def test_from_string(self):
1329
diff_obj = diff.DiffFromTool.from_string('diff', None, None, None)
1330
self.addCleanup(diff_obj.finish)
1331
self.assertEqual(['diff', '@old_path', '@new_path'],
1332
diff_obj.command_template)
1334
def test_from_string_u5(self):
1335
diff_obj = diff.DiffFromTool.from_string('diff "-u 5"',
1337
self.addCleanup(diff_obj.finish)
1338
self.assertEqual(['diff', '-u 5', '@old_path', '@new_path'],
1339
diff_obj.command_template)
1340
self.assertEqual(['diff', '-u 5', 'old-path', 'new-path'],
1341
diff_obj._get_command('old-path', 'new-path'))
1343
def test_from_string_path_with_backslashes(self):
1344
self.requireFeature(features.backslashdir_feature)
1345
tool = 'C:\\Tools\\Diff.exe'
1346
diff_obj = diff.DiffFromTool.from_string(tool, None, None, None)
1347
self.addCleanup(diff_obj.finish)
1348
self.assertEqual(['C:\\Tools\\Diff.exe', '@old_path', '@new_path'],
1349
diff_obj.command_template)
1350
self.assertEqual(['C:\\Tools\\Diff.exe', 'old-path', 'new-path'],
1351
diff_obj._get_command('old-path', 'new-path'))
1353
def test_execute(self):
1355
diff_obj = diff.DiffFromTool(['python', '-c',
1356
'print "@old_path @new_path"'],
1358
self.addCleanup(diff_obj.finish)
1359
diff_obj._execute('old', 'new')
1360
self.assertEqual(output.getvalue().rstrip(), 'old new')
1362
def test_excute_missing(self):
1363
diff_obj = diff.DiffFromTool(['a-tool-which-is-unlikely-to-exist'],
1365
self.addCleanup(diff_obj.finish)
1366
e = self.assertRaises(errors.ExecutableMissing, diff_obj._execute,
1368
self.assertEqual('a-tool-which-is-unlikely-to-exist could not be found'
1369
' on this machine', str(e))
1371
def test_prepare_files_creates_paths_readable_by_windows_tool(self):
1372
self.requireFeature(AttribFeature)
1374
tree = self.make_branch_and_tree('tree')
1375
self.build_tree_contents([('tree/file', 'content')])
1376
tree.add('file', 'file-id')
1377
tree.commit('old tree')
1379
self.addCleanup(tree.unlock)
1380
basis_tree = tree.basis_tree()
1381
basis_tree.lock_read()
1382
self.addCleanup(basis_tree.unlock)
1383
diff_obj = diff.DiffFromTool(['python', '-c',
1384
'print "@old_path @new_path"'],
1385
basis_tree, tree, output)
1386
diff_obj._prepare_files('file-id', 'file', 'file')
1387
# The old content should be readonly
1388
self.assertReadableByAttrib(diff_obj._root, 'old\\file',
1390
# The new content should use the tree object, not a 'new' file anymore
1391
self.assertEndsWith(tree.basedir, 'work/tree')
1392
self.assertReadableByAttrib(tree.basedir, 'file', r'work\\tree\\file$')
1394
def assertReadableByAttrib(self, cwd, relpath, regex):
1395
proc = subprocess.Popen(['attrib', relpath],
1396
stdout=subprocess.PIPE,
1398
(result, err) = proc.communicate()
1399
self.assertContainsRe(result.replace('\r\n', '\n'), regex)
1401
def test_prepare_files(self):
1403
tree = self.make_branch_and_tree('tree')
1404
self.build_tree_contents([('tree/oldname', 'oldcontent')])
1405
self.build_tree_contents([('tree/oldname2', 'oldcontent2')])
1406
tree.add('oldname', 'file-id')
1407
tree.add('oldname2', 'file2-id')
1408
# Earliest allowable date on FAT32 filesystems is 1980-01-01
1409
tree.commit('old tree', timestamp=315532800)
1410
tree.rename_one('oldname', 'newname')
1411
tree.rename_one('oldname2', 'newname2')
1412
self.build_tree_contents([('tree/newname', 'newcontent')])
1413
self.build_tree_contents([('tree/newname2', 'newcontent2')])
1414
old_tree = tree.basis_tree()
1415
old_tree.lock_read()
1416
self.addCleanup(old_tree.unlock)
1418
self.addCleanup(tree.unlock)
1419
diff_obj = diff.DiffFromTool(['python', '-c',
1420
'print "@old_path @new_path"'],
1421
old_tree, tree, output)
1422
self.addCleanup(diff_obj.finish)
1423
self.assertContainsRe(diff_obj._root, 'bzr-diff-[^/]*')
1424
old_path, new_path = diff_obj._prepare_files('file-id', 'oldname',
1426
self.assertContainsRe(old_path, 'old/oldname$')
1427
self.assertEqual(315532800, os.stat(old_path).st_mtime)
1428
self.assertContainsRe(new_path, 'tree/newname$')
1429
self.assertFileEqual('oldcontent', old_path)
1430
self.assertFileEqual('newcontent', new_path)
1431
if osutils.host_os_dereferences_symlinks():
1432
self.assertTrue(os.path.samefile('tree/newname', new_path))
1433
# make sure we can create files with the same parent directories
1434
diff_obj._prepare_files('file2-id', 'oldname2', 'newname2')
1437
class TestGetTreesAndBranchesToDiffLocked(tests.TestCaseWithTransport):
1439
def call_gtabtd(self, path_list, revision_specs, old_url, new_url):
1440
"""Call get_trees_and_branches_to_diff_locked. Overridden by
1441
TestGetTreesAndBranchesToDiff.
1443
return diff.get_trees_and_branches_to_diff_locked(
1444
path_list, revision_specs, old_url, new_url, self.addCleanup)
1446
def test_basic(self):
1447
tree = self.make_branch_and_tree('tree')
1448
(old_tree, new_tree,
1449
old_branch, new_branch,
1450
specific_files, extra_trees) = self.call_gtabtd(
1451
['tree'], None, None, None)
1453
self.assertIsInstance(old_tree, revisiontree.RevisionTree)
1454
self.assertEqual(_mod_revision.NULL_REVISION,
1455
old_tree.get_revision_id())
1456
self.assertEqual(tree.basedir, new_tree.basedir)
1457
self.assertEqual(tree.branch.base, old_branch.base)
1458
self.assertEqual(tree.branch.base, new_branch.base)
1459
self.assertIs(None, specific_files)
1460
self.assertIs(None, extra_trees)
1462
def test_with_rev_specs(self):
1463
tree = self.make_branch_and_tree('tree')
1464
self.build_tree_contents([('tree/file', 'oldcontent')])
1465
tree.add('file', 'file-id')
1466
tree.commit('old tree', timestamp=0, rev_id="old-id")
1467
self.build_tree_contents([('tree/file', 'newcontent')])
1468
tree.commit('new tree', timestamp=0, rev_id="new-id")
1470
revisions = [revisionspec.RevisionSpec.from_string('1'),
1471
revisionspec.RevisionSpec.from_string('2')]
1472
(old_tree, new_tree,
1473
old_branch, new_branch,
1474
specific_files, extra_trees) = self.call_gtabtd(
1475
['tree'], revisions, None, None)
1477
self.assertIsInstance(old_tree, revisiontree.RevisionTree)
1478
self.assertEqual("old-id", old_tree.get_revision_id())
1479
self.assertIsInstance(new_tree, revisiontree.RevisionTree)
1480
self.assertEqual("new-id", new_tree.get_revision_id())
1481
self.assertEqual(tree.branch.base, old_branch.base)
1482
self.assertEqual(tree.branch.base, new_branch.base)
1483
self.assertIs(None, specific_files)
1484
self.assertEqual(tree.basedir, extra_trees[0].basedir)
1487
class TestGetTreesAndBranchesToDiff(TestGetTreesAndBranchesToDiffLocked):
1488
"""Apply the tests for get_trees_and_branches_to_diff_locked to the
1489
deprecated get_trees_and_branches_to_diff function.
1492
def call_gtabtd(self, path_list, revision_specs, old_url, new_url):
1493
return self.applyDeprecated(
1494
deprecated_in((2, 2, 0)), diff.get_trees_and_branches_to_diff,
1495
path_list, revision_specs, old_url, new_url)