1
# Copyright (C) 2005-2014 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, EncodingAdapter
37
from bzrlib.tests.blackbox.test_diff import subst_dates
38
from bzrlib.tests import (
43
def udiff_lines(old, new, allow_binary=False):
45
diff.internal_diff('old', old, 'new', new, output, allow_binary)
47
return output.readlines()
50
def external_udiff_lines(old, new, use_stringio=False):
52
# StringIO has no fileno, so it tests a different codepath
55
output = tempfile.TemporaryFile()
57
diff.external_diff('old', old, 'new', new, output, diff_opts=['-u'])
59
raise tests.TestSkipped('external "diff" not present to test')
61
lines = output.readlines()
66
class TestDiff(tests.TestCase):
68
def test_add_nl(self):
69
"""diff generates a valid diff for patches that add a newline"""
70
lines = udiff_lines(['boo'], ['boo\n'])
71
self.check_patch(lines)
72
self.assertEquals(lines[4], '\\ No newline at end of file\n')
73
## "expected no-nl, got %r" % lines[4]
75
def test_add_nl_2(self):
76
"""diff generates a valid diff for patches that change last line and
79
lines = udiff_lines(['boo'], ['goo\n'])
80
self.check_patch(lines)
81
self.assertEquals(lines[4], '\\ No newline at end of file\n')
82
## "expected no-nl, got %r" % lines[4]
84
def test_remove_nl(self):
85
"""diff generates a valid diff for patches that change last line and
88
lines = udiff_lines(['boo\n'], ['boo'])
89
self.check_patch(lines)
90
self.assertEquals(lines[5], '\\ No newline at end of file\n')
91
## "expected no-nl, got %r" % lines[5]
93
def check_patch(self, lines):
94
self.assert_(len(lines) > 1)
95
## "Not enough lines for a file header for patch:\n%s" % "".join(lines)
96
self.assert_(lines[0].startswith ('---'))
97
## 'No orig line for patch:\n%s' % "".join(lines)
98
self.assert_(lines[1].startswith ('+++'))
99
## 'No mod line for patch:\n%s' % "".join(lines)
100
self.assert_(len(lines) > 2)
101
## "No hunks for patch:\n%s" % "".join(lines)
102
self.assert_(lines[2].startswith('@@'))
103
## "No hunk header for patch:\n%s" % "".join(lines)
104
self.assert_('@@' in lines[2][2:])
105
## "Unterminated hunk header for patch:\n%s" % "".join(lines)
107
def test_binary_lines(self):
109
uni_lines = [1023 * 'a' + '\x00']
110
self.assertRaises(errors.BinaryFile, udiff_lines, uni_lines , empty)
111
self.assertRaises(errors.BinaryFile, udiff_lines, empty, uni_lines)
112
udiff_lines(uni_lines , empty, allow_binary=True)
113
udiff_lines(empty, uni_lines, allow_binary=True)
115
def test_external_diff(self):
116
lines = external_udiff_lines(['boo\n'], ['goo\n'])
117
self.check_patch(lines)
118
self.assertEqual('\n', lines[-1])
120
def test_external_diff_no_fileno(self):
121
# Make sure that we can handle not having a fileno, even
122
# if the diff is large
123
lines = external_udiff_lines(['boo\n']*10000,
126
self.check_patch(lines)
128
def test_external_diff_binary_lang_c(self):
129
for lang in ('LANG', 'LC_ALL', 'LANGUAGE'):
130
self.overrideEnv(lang, 'C')
131
lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
132
# Older versions of diffutils say "Binary files", newer
133
# versions just say "Files".
134
self.assertContainsRe(lines[0], '(Binary f|F)iles old and new differ\n')
135
self.assertEquals(lines[1:], ['\n'])
137
def test_no_external_diff(self):
138
"""Check that NoDiff is raised when diff is not available"""
139
# Make sure no 'diff' command is available
140
# XXX: Weird, using None instead of '' breaks the test -- vila 20101216
141
self.overrideEnv('PATH', '')
142
self.assertRaises(errors.NoDiff, diff.external_diff,
143
'old', ['boo\n'], 'new', ['goo\n'],
144
StringIO(), diff_opts=['-u'])
146
def test_default_style_unified(self):
147
"""Check for default style '-u' only if no other style specified
150
# Verify that style defaults to unified, id est '-u' appended
151
# to option list, in the absence of an alternative style.
152
self.assertEqual(['-a', '-u'], diff.default_style_unified(["-a"]))
153
# Verify that for all valid style options, '-u' is not
154
# appended to option list.
155
for s in diff.style_option_list:
156
ret_opts = diff.default_style_unified(diff_opts=["%s" % (s,)])
157
self.assertEqual(["%s" % (s,)], ret_opts)
159
def test_internal_diff_default(self):
160
# Default internal diff encoding is utf8
162
diff.internal_diff(u'old_\xb5', ['old_text\n'],
163
u'new_\xe5', ['new_text\n'], output)
164
lines = output.getvalue().splitlines(True)
165
self.check_patch(lines)
166
self.assertEquals(['--- old_\xc2\xb5\n',
167
'+++ new_\xc3\xa5\n',
175
def test_internal_diff_utf8(self):
177
diff.internal_diff(u'old_\xb5', ['old_text\n'],
178
u'new_\xe5', ['new_text\n'], output,
179
path_encoding='utf8')
180
lines = output.getvalue().splitlines(True)
181
self.check_patch(lines)
182
self.assertEquals(['--- old_\xc2\xb5\n',
183
'+++ new_\xc3\xa5\n',
191
def test_internal_diff_iso_8859_1(self):
193
diff.internal_diff(u'old_\xb5', ['old_text\n'],
194
u'new_\xe5', ['new_text\n'], output,
195
path_encoding='iso-8859-1')
196
lines = output.getvalue().splitlines(True)
197
self.check_patch(lines)
198
self.assertEquals(['--- old_\xb5\n',
207
def test_internal_diff_no_content(self):
209
diff.internal_diff(u'old', [], u'new', [], output)
210
self.assertEqual('', output.getvalue())
212
def test_internal_diff_no_changes(self):
214
diff.internal_diff(u'old', ['text\n', 'contents\n'],
215
u'new', ['text\n', 'contents\n'],
217
self.assertEqual('', output.getvalue())
219
def test_internal_diff_returns_bytes(self):
221
output = StringIO.StringIO()
222
diff.internal_diff(u'old_\xb5', ['old_text\n'],
223
u'new_\xe5', ['new_text\n'], output)
224
self.assertIsInstance(output.getvalue(), str,
225
'internal_diff should return bytestrings')
227
def test_internal_diff_default_context(self):
229
diff.internal_diff('old', ['same_text\n','same_text\n','same_text\n',
230
'same_text\n','same_text\n','old_text\n'],
231
'new', ['same_text\n','same_text\n','same_text\n',
232
'same_text\n','same_text\n','new_text\n'], output)
233
lines = output.getvalue().splitlines(True)
234
self.check_patch(lines)
235
self.assertEquals(['--- old\n',
247
def test_internal_diff_no_context(self):
249
diff.internal_diff('old', ['same_text\n','same_text\n','same_text\n',
250
'same_text\n','same_text\n','old_text\n'],
251
'new', ['same_text\n','same_text\n','same_text\n',
252
'same_text\n','same_text\n','new_text\n'], output,
254
lines = output.getvalue().splitlines(True)
255
self.check_patch(lines)
256
self.assertEquals(['--- old\n',
265
def test_internal_diff_more_context(self):
267
diff.internal_diff('old', ['same_text\n','same_text\n','same_text\n',
268
'same_text\n','same_text\n','old_text\n'],
269
'new', ['same_text\n','same_text\n','same_text\n',
270
'same_text\n','same_text\n','new_text\n'], output,
272
lines = output.getvalue().splitlines(True)
273
self.check_patch(lines)
274
self.assertEquals(['--- old\n',
291
class TestDiffFiles(tests.TestCaseInTempDir):
293
def test_external_diff_binary(self):
294
"""The output when using external diff should use diff's i18n error"""
295
# Make sure external_diff doesn't fail in the current LANG
296
lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
298
cmd = ['diff', '-u', '--binary', 'old', 'new']
299
with open('old', 'wb') as f: f.write('\x00foobar\n')
300
with open('new', 'wb') as f: f.write('foo\x00bar\n')
301
pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
302
stdin=subprocess.PIPE)
303
out, err = pipe.communicate()
304
# Diff returns '2' on Binary files.
305
self.assertEqual(2, pipe.returncode)
306
# We should output whatever diff tells us, plus a trailing newline
307
self.assertEqual(out.splitlines(True) + ['\n'], lines)
310
def get_diff_as_string(tree1, tree2, specific_files=None, working_tree=None):
312
if working_tree is not None:
313
extra_trees = (working_tree,)
316
diff.show_diff_trees(tree1, tree2, output,
317
specific_files=specific_files,
318
extra_trees=extra_trees, old_label='old/',
320
return output.getvalue()
323
class TestDiffDates(tests.TestCaseWithTransport):
326
super(TestDiffDates, self).setUp()
327
self.wt = self.make_branch_and_tree('.')
328
self.b = self.wt.branch
329
self.build_tree_contents([
330
('file1', 'file1 contents at rev 1\n'),
331
('file2', 'file2 contents at rev 1\n')
333
self.wt.add(['file1', 'file2'])
335
message='Revision 1',
336
timestamp=1143849600, # 2006-04-01 00:00:00 UTC
339
self.build_tree_contents([('file1', 'file1 contents at rev 2\n')])
341
message='Revision 2',
342
timestamp=1143936000, # 2006-04-02 00:00:00 UTC
345
self.build_tree_contents([('file2', 'file2 contents at rev 3\n')])
347
message='Revision 3',
348
timestamp=1144022400, # 2006-04-03 00:00:00 UTC
351
self.wt.remove(['file2'])
353
message='Revision 4',
354
timestamp=1144108800, # 2006-04-04 00:00:00 UTC
357
self.build_tree_contents([
358
('file1', 'file1 contents in working tree\n')
360
# set the date stamps for files in the working tree to known values
361
os.utime('file1', (1144195200, 1144195200)) # 2006-04-05 00:00:00 UTC
363
def test_diff_rev_tree_working_tree(self):
364
output = get_diff_as_string(self.wt.basis_tree(), self.wt)
365
# note that the date for old/file1 is from rev 2 rather than from
366
# the basis revision (rev 4)
367
self.assertEqualDiff(output, '''\
368
=== modified file 'file1'
369
--- old/file1\t2006-04-02 00:00:00 +0000
370
+++ new/file1\t2006-04-05 00:00:00 +0000
372
-file1 contents at rev 2
373
+file1 contents in working tree
377
def test_diff_rev_tree_rev_tree(self):
378
tree1 = self.b.repository.revision_tree('rev-2')
379
tree2 = self.b.repository.revision_tree('rev-3')
380
output = get_diff_as_string(tree1, tree2)
381
self.assertEqualDiff(output, '''\
382
=== modified file 'file2'
383
--- old/file2\t2006-04-01 00:00:00 +0000
384
+++ new/file2\t2006-04-03 00:00:00 +0000
386
-file2 contents at rev 1
387
+file2 contents at rev 3
391
def test_diff_add_files(self):
392
tree1 = self.b.repository.revision_tree(_mod_revision.NULL_REVISION)
393
tree2 = self.b.repository.revision_tree('rev-1')
394
output = get_diff_as_string(tree1, tree2)
395
# the files have the epoch time stamp for the tree in which
397
self.assertEqualDiff(output, '''\
398
=== added file 'file1'
399
--- old/file1\t1970-01-01 00:00:00 +0000
400
+++ new/file1\t2006-04-01 00:00:00 +0000
402
+file1 contents at rev 1
404
=== added file 'file2'
405
--- old/file2\t1970-01-01 00:00:00 +0000
406
+++ new/file2\t2006-04-01 00:00:00 +0000
408
+file2 contents at rev 1
412
def test_diff_remove_files(self):
413
tree1 = self.b.repository.revision_tree('rev-3')
414
tree2 = self.b.repository.revision_tree('rev-4')
415
output = get_diff_as_string(tree1, tree2)
416
# the file has the epoch time stamp for the tree in which
418
self.assertEqualDiff(output, '''\
419
=== removed file 'file2'
420
--- old/file2\t2006-04-03 00:00:00 +0000
421
+++ new/file2\t1970-01-01 00:00:00 +0000
423
-file2 contents at rev 3
427
def test_show_diff_specified(self):
428
"""A working tree filename can be used to identify a file"""
429
self.wt.rename_one('file1', 'file1b')
430
old_tree = self.b.repository.revision_tree('rev-1')
431
new_tree = self.b.repository.revision_tree('rev-4')
432
out = get_diff_as_string(old_tree, new_tree, specific_files=['file1b'],
433
working_tree=self.wt)
434
self.assertContainsRe(out, 'file1\t')
436
def test_recursive_diff(self):
437
"""Children of directories are matched"""
440
self.wt.add(['dir1', 'dir2'])
441
self.wt.rename_one('file1', 'dir1/file1')
442
old_tree = self.b.repository.revision_tree('rev-1')
443
new_tree = self.b.repository.revision_tree('rev-4')
444
out = get_diff_as_string(old_tree, new_tree, specific_files=['dir1'],
445
working_tree=self.wt)
446
self.assertContainsRe(out, 'file1\t')
447
out = get_diff_as_string(old_tree, new_tree, specific_files=['dir2'],
448
working_tree=self.wt)
449
self.assertNotContainsRe(out, 'file1\t')
452
class TestShowDiffTrees(tests.TestCaseWithTransport):
453
"""Direct tests for show_diff_trees"""
455
def test_modified_file(self):
456
"""Test when a file is modified."""
457
tree = self.make_branch_and_tree('tree')
458
self.build_tree_contents([('tree/file', 'contents\n')])
459
tree.add(['file'], ['file-id'])
460
tree.commit('one', rev_id='rev-1')
462
self.build_tree_contents([('tree/file', 'new contents\n')])
463
d = get_diff_as_string(tree.basis_tree(), tree)
464
self.assertContainsRe(d, "=== modified file 'file'\n")
465
self.assertContainsRe(d, '--- old/file\t')
466
self.assertContainsRe(d, '\\+\\+\\+ new/file\t')
467
self.assertContainsRe(d, '-contents\n'
470
def test_modified_file_in_renamed_dir(self):
471
"""Test when a file is modified in a renamed directory."""
472
tree = self.make_branch_and_tree('tree')
473
self.build_tree(['tree/dir/'])
474
self.build_tree_contents([('tree/dir/file', 'contents\n')])
475
tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
476
tree.commit('one', rev_id='rev-1')
478
tree.rename_one('dir', 'other')
479
self.build_tree_contents([('tree/other/file', 'new contents\n')])
480
d = get_diff_as_string(tree.basis_tree(), tree)
481
self.assertContainsRe(d, "=== renamed directory 'dir' => 'other'\n")
482
self.assertContainsRe(d, "=== modified file 'other/file'\n")
483
# XXX: This is technically incorrect, because it used to be at another
484
# location. What to do?
485
self.assertContainsRe(d, '--- old/dir/file\t')
486
self.assertContainsRe(d, '\\+\\+\\+ new/other/file\t')
487
self.assertContainsRe(d, '-contents\n'
490
def test_renamed_directory(self):
491
"""Test when only a directory is only renamed."""
492
tree = self.make_branch_and_tree('tree')
493
self.build_tree(['tree/dir/'])
494
self.build_tree_contents([('tree/dir/file', 'contents\n')])
495
tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
496
tree.commit('one', rev_id='rev-1')
498
tree.rename_one('dir', 'newdir')
499
d = get_diff_as_string(tree.basis_tree(), tree)
500
# Renaming a directory should be a single "you renamed this dir" even
501
# when there are files inside.
502
self.assertEqual(d, "=== renamed directory 'dir' => 'newdir'\n")
504
def test_renamed_file(self):
505
"""Test when a file is only renamed."""
506
tree = self.make_branch_and_tree('tree')
507
self.build_tree_contents([('tree/file', 'contents\n')])
508
tree.add(['file'], ['file-id'])
509
tree.commit('one', rev_id='rev-1')
511
tree.rename_one('file', 'newname')
512
d = get_diff_as_string(tree.basis_tree(), tree)
513
self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
514
# We shouldn't have a --- or +++ line, because there is no content
516
self.assertNotContainsRe(d, '---')
518
def test_renamed_and_modified_file(self):
519
"""Test when a file is only renamed."""
520
tree = self.make_branch_and_tree('tree')
521
self.build_tree_contents([('tree/file', 'contents\n')])
522
tree.add(['file'], ['file-id'])
523
tree.commit('one', rev_id='rev-1')
525
tree.rename_one('file', 'newname')
526
self.build_tree_contents([('tree/newname', 'new contents\n')])
527
d = get_diff_as_string(tree.basis_tree(), tree)
528
self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
529
self.assertContainsRe(d, '--- old/file\t')
530
self.assertContainsRe(d, '\\+\\+\\+ new/newname\t')
531
self.assertContainsRe(d, '-contents\n'
535
def test_internal_diff_exec_property(self):
536
tree = self.make_branch_and_tree('tree')
538
tt = transform.TreeTransform(tree)
539
tt.new_file('a', tt.root, 'contents\n', 'a-id', True)
540
tt.new_file('b', tt.root, 'contents\n', 'b-id', False)
541
tt.new_file('c', tt.root, 'contents\n', 'c-id', True)
542
tt.new_file('d', tt.root, 'contents\n', 'd-id', False)
543
tt.new_file('e', tt.root, 'contents\n', 'control-e-id', True)
544
tt.new_file('f', tt.root, 'contents\n', 'control-f-id', False)
546
tree.commit('one', rev_id='rev-1')
548
tt = transform.TreeTransform(tree)
549
tt.set_executability(False, tt.trans_id_file_id('a-id'))
550
tt.set_executability(True, tt.trans_id_file_id('b-id'))
551
tt.set_executability(False, tt.trans_id_file_id('c-id'))
552
tt.set_executability(True, tt.trans_id_file_id('d-id'))
554
tree.rename_one('c', 'new-c')
555
tree.rename_one('d', 'new-d')
557
d = get_diff_as_string(tree.basis_tree(), tree)
559
self.assertContainsRe(d, r"file 'a'.*\(properties changed:"
561
self.assertContainsRe(d, r"file 'b'.*\(properties changed:"
563
self.assertContainsRe(d, r"file 'c'.*\(properties changed:"
565
self.assertContainsRe(d, r"file 'd'.*\(properties changed:"
567
self.assertNotContainsRe(d, r"file 'e'")
568
self.assertNotContainsRe(d, r"file 'f'")
570
def test_binary_unicode_filenames(self):
571
"""Test that contents of files are *not* encoded in UTF-8 when there
572
is a binary file in the diff.
574
# See https://bugs.launchpad.net/bugs/110092.
575
self.requireFeature(features.UnicodeFilenameFeature)
577
# This bug isn't triggered with cStringIO.
578
from StringIO import StringIO
579
tree = self.make_branch_and_tree('tree')
580
alpha, omega = u'\u03b1', u'\u03c9'
581
alpha_utf8, omega_utf8 = alpha.encode('utf8'), omega.encode('utf8')
582
self.build_tree_contents(
583
[('tree/' + alpha, chr(0)),
585
('The %s and the %s\n' % (alpha_utf8, omega_utf8)))])
586
tree.add([alpha], ['file-id'])
587
tree.add([omega], ['file-id-2'])
588
diff_content = StringIO()
589
diff.show_diff_trees(tree.basis_tree(), tree, diff_content)
590
d = diff_content.getvalue()
591
self.assertContainsRe(d, r"=== added file '%s'" % alpha_utf8)
592
self.assertContainsRe(d, "Binary files a/%s.*and b/%s.* differ\n"
593
% (alpha_utf8, alpha_utf8))
594
self.assertContainsRe(d, r"=== added file '%s'" % omega_utf8)
595
self.assertContainsRe(d, r"--- a/%s" % (omega_utf8,))
596
self.assertContainsRe(d, r"\+\+\+ b/%s" % (omega_utf8,))
598
def test_unicode_filename(self):
599
"""Test when the filename are unicode."""
600
self.requireFeature(features.UnicodeFilenameFeature)
602
alpha, omega = u'\u03b1', u'\u03c9'
603
autf8, outf8 = alpha.encode('utf8'), omega.encode('utf8')
605
tree = self.make_branch_and_tree('tree')
606
self.build_tree_contents([('tree/ren_'+alpha, 'contents\n')])
607
tree.add(['ren_'+alpha], ['file-id-2'])
608
self.build_tree_contents([('tree/del_'+alpha, 'contents\n')])
609
tree.add(['del_'+alpha], ['file-id-3'])
610
self.build_tree_contents([('tree/mod_'+alpha, 'contents\n')])
611
tree.add(['mod_'+alpha], ['file-id-4'])
613
tree.commit('one', rev_id='rev-1')
615
tree.rename_one('ren_'+alpha, 'ren_'+omega)
616
tree.remove('del_'+alpha)
617
self.build_tree_contents([('tree/add_'+alpha, 'contents\n')])
618
tree.add(['add_'+alpha], ['file-id'])
619
self.build_tree_contents([('tree/mod_'+alpha, 'contents_mod\n')])
621
d = get_diff_as_string(tree.basis_tree(), tree)
622
self.assertContainsRe(d,
623
"=== renamed file 'ren_%s' => 'ren_%s'\n"%(autf8, outf8))
624
self.assertContainsRe(d, "=== added file 'add_%s'"%autf8)
625
self.assertContainsRe(d, "=== modified file 'mod_%s'"%autf8)
626
self.assertContainsRe(d, "=== removed file 'del_%s'"%autf8)
628
def test_unicode_filename_path_encoding(self):
629
"""Test for bug #382699: unicode filenames on Windows should be shown
632
self.requireFeature(features.UnicodeFilenameFeature)
633
# The word 'test' in Russian
634
_russian_test = u'\u0422\u0435\u0441\u0442'
635
directory = _russian_test + u'/'
636
test_txt = _russian_test + u'.txt'
637
u1234 = u'\u1234.txt'
639
tree = self.make_branch_and_tree('.')
640
self.build_tree_contents([
645
tree.add([test_txt, u1234, directory])
648
diff.show_diff_trees(tree.basis_tree(), tree, sio,
649
path_encoding='cp1251')
651
output = subst_dates(sio.getvalue())
653
=== added directory '%(directory)s'
654
=== added file '%(test_txt)s'
655
--- a/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
656
+++ b/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
660
=== added file '?.txt'
661
--- a/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
662
+++ b/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
666
''' % {'directory': _russian_test.encode('cp1251'),
667
'test_txt': test_txt.encode('cp1251'),
669
self.assertEqualDiff(output, shouldbe)
672
class DiffWasIs(diff.DiffPath):
674
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
675
self.to_file.write('was: ')
676
self.to_file.write(self.old_tree.get_file(file_id).read())
677
self.to_file.write('is: ')
678
self.to_file.write(self.new_tree.get_file(file_id).read())
682
class TestDiffTree(tests.TestCaseWithTransport):
685
super(TestDiffTree, self).setUp()
686
self.old_tree = self.make_branch_and_tree('old-tree')
687
self.old_tree.lock_write()
688
self.addCleanup(self.old_tree.unlock)
689
self.new_tree = self.make_branch_and_tree('new-tree')
690
self.new_tree.lock_write()
691
self.addCleanup(self.new_tree.unlock)
692
self.differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
694
def test_diff_text(self):
695
self.build_tree_contents([('old-tree/olddir/',),
696
('old-tree/olddir/oldfile', 'old\n')])
697
self.old_tree.add('olddir')
698
self.old_tree.add('olddir/oldfile', 'file-id')
699
self.build_tree_contents([('new-tree/newdir/',),
700
('new-tree/newdir/newfile', 'new\n')])
701
self.new_tree.add('newdir')
702
self.new_tree.add('newdir/newfile', 'file-id')
703
differ = diff.DiffText(self.old_tree, self.new_tree, StringIO())
704
differ.diff_text('file-id', None, 'old label', 'new label')
706
'--- old label\n+++ new label\n@@ -1,1 +0,0 @@\n-old\n\n',
707
differ.to_file.getvalue())
708
differ.to_file.seek(0)
709
differ.diff_text(None, 'file-id', 'old label', 'new label')
711
'--- old label\n+++ new label\n@@ -0,0 +1,1 @@\n+new\n\n',
712
differ.to_file.getvalue())
713
differ.to_file.seek(0)
714
differ.diff_text('file-id', 'file-id', 'old label', 'new label')
716
'--- old label\n+++ new label\n@@ -1,1 +1,1 @@\n-old\n+new\n\n',
717
differ.to_file.getvalue())
719
def test_diff_deletion(self):
720
self.build_tree_contents([('old-tree/file', 'contents'),
721
('new-tree/file', 'contents')])
722
self.old_tree.add('file', 'file-id')
723
self.new_tree.add('file', 'file-id')
724
os.unlink('new-tree/file')
725
self.differ.show_diff(None)
726
self.assertContainsRe(self.differ.to_file.getvalue(), '-contents')
728
def test_diff_creation(self):
729
self.build_tree_contents([('old-tree/file', 'contents'),
730
('new-tree/file', 'contents')])
731
self.old_tree.add('file', 'file-id')
732
self.new_tree.add('file', 'file-id')
733
os.unlink('old-tree/file')
734
self.differ.show_diff(None)
735
self.assertContainsRe(self.differ.to_file.getvalue(), '\+contents')
737
def test_diff_symlink(self):
738
differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
739
differ.diff_symlink('old target', None)
740
self.assertEqual("=== target was 'old target'\n",
741
differ.to_file.getvalue())
743
differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
744
differ.diff_symlink(None, 'new target')
745
self.assertEqual("=== target is 'new target'\n",
746
differ.to_file.getvalue())
748
differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
749
differ.diff_symlink('old target', 'new target')
750
self.assertEqual("=== target changed 'old target' => 'new target'\n",
751
differ.to_file.getvalue())
754
self.build_tree_contents([('old-tree/olddir/',),
755
('old-tree/olddir/oldfile', 'old\n')])
756
self.old_tree.add('olddir')
757
self.old_tree.add('olddir/oldfile', 'file-id')
758
self.build_tree_contents([('new-tree/newdir/',),
759
('new-tree/newdir/newfile', 'new\n')])
760
self.new_tree.add('newdir')
761
self.new_tree.add('newdir/newfile', 'file-id')
762
self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
763
self.assertContainsRe(
764
self.differ.to_file.getvalue(),
765
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
766
' \@\@\n-old\n\+new\n\n')
768
def test_diff_kind_change(self):
769
self.requireFeature(features.SymlinkFeature)
770
self.build_tree_contents([('old-tree/olddir/',),
771
('old-tree/olddir/oldfile', 'old\n')])
772
self.old_tree.add('olddir')
773
self.old_tree.add('olddir/oldfile', 'file-id')
774
self.build_tree(['new-tree/newdir/'])
775
os.symlink('new', 'new-tree/newdir/newfile')
776
self.new_tree.add('newdir')
777
self.new_tree.add('newdir/newfile', 'file-id')
778
self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
779
self.assertContainsRe(
780
self.differ.to_file.getvalue(),
781
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+0,0'
783
self.assertContainsRe(self.differ.to_file.getvalue(),
784
"=== target is u'new'\n")
786
def test_diff_directory(self):
787
self.build_tree(['new-tree/new-dir/'])
788
self.new_tree.add('new-dir', 'new-dir-id')
789
self.differ.diff('new-dir-id', None, 'new-dir')
790
self.assertEqual(self.differ.to_file.getvalue(), '')
792
def create_old_new(self):
793
self.build_tree_contents([('old-tree/olddir/',),
794
('old-tree/olddir/oldfile', 'old\n')])
795
self.old_tree.add('olddir')
796
self.old_tree.add('olddir/oldfile', 'file-id')
797
self.build_tree_contents([('new-tree/newdir/',),
798
('new-tree/newdir/newfile', 'new\n')])
799
self.new_tree.add('newdir')
800
self.new_tree.add('newdir/newfile', 'file-id')
802
def test_register_diff(self):
803
self.create_old_new()
804
old_diff_factories = diff.DiffTree.diff_factories
805
diff.DiffTree.diff_factories=old_diff_factories[:]
806
diff.DiffTree.diff_factories.insert(0, DiffWasIs.from_diff_tree)
808
differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
810
diff.DiffTree.diff_factories = old_diff_factories
811
differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
812
self.assertNotContainsRe(
813
differ.to_file.getvalue(),
814
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
815
' \@\@\n-old\n\+new\n\n')
816
self.assertContainsRe(differ.to_file.getvalue(),
817
'was: old\nis: new\n')
819
def test_extra_factories(self):
820
self.create_old_new()
821
differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO(),
822
extra_factories=[DiffWasIs.from_diff_tree])
823
differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
824
self.assertNotContainsRe(
825
differ.to_file.getvalue(),
826
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
827
' \@\@\n-old\n\+new\n\n')
828
self.assertContainsRe(differ.to_file.getvalue(),
829
'was: old\nis: new\n')
831
def test_alphabetical_order(self):
832
self.build_tree(['new-tree/a-file'])
833
self.new_tree.add('a-file')
834
self.build_tree(['old-tree/b-file'])
835
self.old_tree.add('b-file')
836
self.differ.show_diff(None)
837
self.assertContainsRe(self.differ.to_file.getvalue(),
838
'.*a-file(.|\n)*b-file')
841
class TestPatienceDiffLib(tests.TestCase):
844
super(TestPatienceDiffLib, self).setUp()
845
self._unique_lcs = _patiencediff_py.unique_lcs_py
846
self._recurse_matches = _patiencediff_py.recurse_matches_py
847
self._PatienceSequenceMatcher = \
848
_patiencediff_py.PatienceSequenceMatcher_py
850
def test_diff_unicode_string(self):
851
a = ''.join([unichr(i) for i in range(4000, 4500, 3)])
852
b = ''.join([unichr(i) for i in range(4300, 4800, 2)])
853
sm = self._PatienceSequenceMatcher(None, a, b)
854
mb = sm.get_matching_blocks()
855
self.assertEquals(35, len(mb))
857
def test_unique_lcs(self):
858
unique_lcs = self._unique_lcs
859
self.assertEquals(unique_lcs('', ''), [])
860
self.assertEquals(unique_lcs('', 'a'), [])
861
self.assertEquals(unique_lcs('a', ''), [])
862
self.assertEquals(unique_lcs('a', 'a'), [(0,0)])
863
self.assertEquals(unique_lcs('a', 'b'), [])
864
self.assertEquals(unique_lcs('ab', 'ab'), [(0,0), (1,1)])
865
self.assertEquals(unique_lcs('abcde', 'cdeab'), [(2,0), (3,1), (4,2)])
866
self.assertEquals(unique_lcs('cdeab', 'abcde'), [(0,2), (1,3), (2,4)])
867
self.assertEquals(unique_lcs('abXde', 'abYde'), [(0,0), (1,1),
869
self.assertEquals(unique_lcs('acbac', 'abc'), [(2,1)])
871
def test_recurse_matches(self):
872
def test_one(a, b, matches):
874
self._recurse_matches(
875
a, b, 0, 0, len(a), len(b), test_matches, 10)
876
self.assertEquals(test_matches, matches)
878
test_one(['a', '', 'b', '', 'c'], ['a', 'a', 'b', 'c', 'c'],
879
[(0, 0), (2, 2), (4, 4)])
880
test_one(['a', 'c', 'b', 'a', 'c'], ['a', 'b', 'c'],
881
[(0, 0), (2, 1), (4, 2)])
882
# Even though 'bc' is not unique globally, and is surrounded by
883
# non-matching lines, we should still match, because they are locally
885
test_one('abcdbce', 'afbcgdbce', [(0,0), (1, 2), (2, 3), (3, 5),
886
(4, 6), (5, 7), (6, 8)])
888
# recurse_matches doesn't match non-unique
889
# lines surrounded by bogus text.
890
# The update has been done in patiencediff.SequenceMatcher instead
892
# This is what it could be
893
#test_one('aBccDe', 'abccde', [(0,0), (2,2), (3,3), (5,5)])
895
# This is what it currently gives:
896
test_one('aBccDe', 'abccde', [(0,0), (5,5)])
898
def assertDiffBlocks(self, a, b, expected_blocks):
899
"""Check that the sequence matcher returns the correct blocks.
901
:param a: A sequence to match
902
:param b: Another sequence to match
903
:param expected_blocks: The expected output, not including the final
904
matching block (len(a), len(b), 0)
906
matcher = self._PatienceSequenceMatcher(None, a, b)
907
blocks = matcher.get_matching_blocks()
909
self.assertEqual((len(a), len(b), 0), last)
910
self.assertEqual(expected_blocks, blocks)
912
def test_matching_blocks(self):
913
# Some basic matching tests
914
self.assertDiffBlocks('', '', [])
915
self.assertDiffBlocks([], [], [])
916
self.assertDiffBlocks('abc', '', [])
917
self.assertDiffBlocks('', 'abc', [])
918
self.assertDiffBlocks('abcd', 'abcd', [(0, 0, 4)])
919
self.assertDiffBlocks('abcd', 'abce', [(0, 0, 3)])
920
self.assertDiffBlocks('eabc', 'abce', [(1, 0, 3)])
921
self.assertDiffBlocks('eabce', 'abce', [(1, 0, 4)])
922
self.assertDiffBlocks('abcde', 'abXde', [(0, 0, 2), (3, 3, 2)])
923
self.assertDiffBlocks('abcde', 'abXYZde', [(0, 0, 2), (3, 5, 2)])
924
self.assertDiffBlocks('abde', 'abXYZde', [(0, 0, 2), (2, 5, 2)])
925
# This may check too much, but it checks to see that
926
# a copied block stays attached to the previous section,
928
# difflib would tend to grab the trailing longest match
929
# which would make the diff not look right
930
self.assertDiffBlocks('abcdefghijklmnop', 'abcdefxydefghijklmnop',
931
[(0, 0, 6), (6, 11, 10)])
933
# make sure it supports passing in lists
934
self.assertDiffBlocks(
937
'how are you today?\n'],
939
'how are you today?\n'],
940
[(0, 0, 1), (2, 1, 1)])
942
# non unique lines surrounded by non-matching lines
944
self.assertDiffBlocks('aBccDe', 'abccde', [(0,0,1), (5,5,1)])
946
# But they only need to be locally unique
947
self.assertDiffBlocks('aBcDec', 'abcdec', [(0,0,1), (2,2,1), (4,4,2)])
949
# non unique blocks won't be matched
950
self.assertDiffBlocks('aBcdEcdFg', 'abcdecdfg', [(0,0,1), (8,8,1)])
952
# but locally unique ones will
953
self.assertDiffBlocks('aBcdEeXcdFg', 'abcdecdfg', [(0,0,1), (2,2,2),
954
(5,4,1), (7,5,2), (10,8,1)])
956
self.assertDiffBlocks('abbabbXd', 'cabbabxd', [(7,7,1)])
957
self.assertDiffBlocks('abbabbbb', 'cabbabbc', [])
958
self.assertDiffBlocks('bbbbbbbb', 'cbbbbbbc', [])
960
def test_matching_blocks_tuples(self):
961
# Some basic matching tests
962
self.assertDiffBlocks([], [], [])
963
self.assertDiffBlocks([('a',), ('b',), ('c,')], [], [])
964
self.assertDiffBlocks([], [('a',), ('b',), ('c,')], [])
965
self.assertDiffBlocks([('a',), ('b',), ('c,')],
966
[('a',), ('b',), ('c,')],
968
self.assertDiffBlocks([('a',), ('b',), ('c,')],
969
[('a',), ('b',), ('d,')],
971
self.assertDiffBlocks([('d',), ('b',), ('c,')],
972
[('a',), ('b',), ('c,')],
974
self.assertDiffBlocks([('d',), ('a',), ('b',), ('c,')],
975
[('a',), ('b',), ('c,')],
977
self.assertDiffBlocks([('a', 'b'), ('c', 'd'), ('e', 'f')],
978
[('a', 'b'), ('c', 'X'), ('e', 'f')],
979
[(0, 0, 1), (2, 2, 1)])
980
self.assertDiffBlocks([('a', 'b'), ('c', 'd'), ('e', 'f')],
981
[('a', 'b'), ('c', 'dX'), ('e', 'f')],
982
[(0, 0, 1), (2, 2, 1)])
984
def test_opcodes(self):
985
def chk_ops(a, b, expected_codes):
986
s = self._PatienceSequenceMatcher(None, a, b)
987
self.assertEquals(expected_codes, s.get_opcodes())
991
chk_ops('abc', '', [('delete', 0,3, 0,0)])
992
chk_ops('', 'abc', [('insert', 0,0, 0,3)])
993
chk_ops('abcd', 'abcd', [('equal', 0,4, 0,4)])
994
chk_ops('abcd', 'abce', [('equal', 0,3, 0,3),
995
('replace', 3,4, 3,4)
997
chk_ops('eabc', 'abce', [('delete', 0,1, 0,0),
1001
chk_ops('eabce', 'abce', [('delete', 0,1, 0,0),
1004
chk_ops('abcde', 'abXde', [('equal', 0,2, 0,2),
1005
('replace', 2,3, 2,3),
1008
chk_ops('abcde', 'abXYZde', [('equal', 0,2, 0,2),
1009
('replace', 2,3, 2,5),
1012
chk_ops('abde', 'abXYZde', [('equal', 0,2, 0,2),
1013
('insert', 2,2, 2,5),
1016
chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
1017
[('equal', 0,6, 0,6),
1018
('insert', 6,6, 6,11),
1019
('equal', 6,16, 11,21)
1024
, 'how are you today?\n'],
1026
, 'how are you today?\n'],
1027
[('equal', 0,1, 0,1),
1028
('delete', 1,2, 1,1),
1029
('equal', 2,3, 1,2),
1031
chk_ops('aBccDe', 'abccde',
1032
[('equal', 0,1, 0,1),
1033
('replace', 1,5, 1,5),
1034
('equal', 5,6, 5,6),
1036
chk_ops('aBcDec', 'abcdec',
1037
[('equal', 0,1, 0,1),
1038
('replace', 1,2, 1,2),
1039
('equal', 2,3, 2,3),
1040
('replace', 3,4, 3,4),
1041
('equal', 4,6, 4,6),
1043
chk_ops('aBcdEcdFg', 'abcdecdfg',
1044
[('equal', 0,1, 0,1),
1045
('replace', 1,8, 1,8),
1048
chk_ops('aBcdEeXcdFg', 'abcdecdfg',
1049
[('equal', 0,1, 0,1),
1050
('replace', 1,2, 1,2),
1051
('equal', 2,4, 2,4),
1052
('delete', 4,5, 4,4),
1053
('equal', 5,6, 4,5),
1054
('delete', 6,7, 5,5),
1055
('equal', 7,9, 5,7),
1056
('replace', 9,10, 7,8),
1057
('equal', 10,11, 8,9)
1060
def test_grouped_opcodes(self):
1061
def chk_ops(a, b, expected_codes, n=3):
1062
s = self._PatienceSequenceMatcher(None, a, b)
1063
self.assertEquals(expected_codes, list(s.get_grouped_opcodes(n)))
1067
chk_ops('abc', '', [[('delete', 0,3, 0,0)]])
1068
chk_ops('', 'abc', [[('insert', 0,0, 0,3)]])
1069
chk_ops('abcd', 'abcd', [])
1070
chk_ops('abcd', 'abce', [[('equal', 0,3, 0,3),
1071
('replace', 3,4, 3,4)
1073
chk_ops('eabc', 'abce', [[('delete', 0,1, 0,0),
1074
('equal', 1,4, 0,3),
1075
('insert', 4,4, 3,4)
1077
chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
1078
[[('equal', 3,6, 3,6),
1079
('insert', 6,6, 6,11),
1080
('equal', 6,9, 11,14)
1082
chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
1083
[[('equal', 2,6, 2,6),
1084
('insert', 6,6, 6,11),
1085
('equal', 6,10, 11,15)
1087
chk_ops('Xabcdef', 'abcdef',
1088
[[('delete', 0,1, 0,0),
1091
chk_ops('abcdef', 'abcdefX',
1092
[[('equal', 3,6, 3,6),
1093
('insert', 6,6, 6,7)
1097
def test_multiple_ranges(self):
1098
# There was an earlier bug where we used a bad set of ranges,
1099
# this triggers that specific bug, to make sure it doesn't regress
1100
self.assertDiffBlocks('abcdefghijklmnop',
1101
'abcXghiYZQRSTUVWXYZijklmnop',
1102
[(0, 0, 3), (6, 4, 3), (9, 20, 7)])
1104
self.assertDiffBlocks('ABCd efghIjk L',
1105
'AxyzBCn mo pqrstuvwI1 2 L',
1106
[(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
1108
# These are rot13 code snippets.
1109
self.assertDiffBlocks('''\
1110
trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
1112
gnxrf_netf = ['svyr*']
1113
gnxrf_bcgvbaf = ['ab-erphefr']
1115
qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr):
1116
sebz omeyvo.nqq vzcbeg fzneg_nqq, nqq_ercbegre_cevag, nqq_ercbegre_ahyy
1118
ercbegre = nqq_ercbegre_ahyy
1120
ercbegre = nqq_ercbegre_cevag
1121
fzneg_nqq(svyr_yvfg, abg ab_erphefr, ercbegre)
1124
pynff pzq_zxqve(Pbzznaq):
1125
'''.splitlines(True), '''\
1126
trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
1128
--qel-eha jvyy fubj juvpu svyrf jbhyq or nqqrq, ohg abg npghnyyl
1131
gnxrf_netf = ['svyr*']
1132
gnxrf_bcgvbaf = ['ab-erphefr', 'qel-eha']
1134
qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr, qel_eha=Snyfr):
1139
# Guvf vf cbvagyrff, ohg V'q engure abg envfr na reebe
1140
npgvba = omeyvo.nqq.nqq_npgvba_ahyy
1142
npgvba = omeyvo.nqq.nqq_npgvba_cevag
1144
npgvba = omeyvo.nqq.nqq_npgvba_nqq
1146
npgvba = omeyvo.nqq.nqq_npgvba_nqq_naq_cevag
1148
omeyvo.nqq.fzneg_nqq(svyr_yvfg, abg ab_erphefr, npgvba)
1151
pynff pzq_zxqve(Pbzznaq):
1152
'''.splitlines(True)
1153
, [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
1155
def test_patience_unified_diff(self):
1156
txt_a = ['hello there\n',
1158
'how are you today?\n']
1159
txt_b = ['hello there\n',
1160
'how are you today?\n']
1161
unified_diff = patiencediff.unified_diff
1162
psm = self._PatienceSequenceMatcher
1163
self.assertEquals(['--- \n',
1165
'@@ -1,3 +1,2 @@\n',
1168
' how are you today?\n'
1170
, list(unified_diff(txt_a, txt_b,
1171
sequencematcher=psm)))
1172
txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
1173
txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
1174
# This is the result with LongestCommonSubstring matching
1175
self.assertEquals(['--- \n',
1177
'@@ -1,6 +1,11 @@\n',
1189
, list(unified_diff(txt_a, txt_b)))
1190
# And the patience diff
1191
self.assertEquals(['--- \n',
1193
'@@ -4,6 +4,11 @@\n',
1206
, list(unified_diff(txt_a, txt_b,
1207
sequencematcher=psm)))
1209
def test_patience_unified_diff_with_dates(self):
1210
txt_a = ['hello there\n',
1212
'how are you today?\n']
1213
txt_b = ['hello there\n',
1214
'how are you today?\n']
1215
unified_diff = patiencediff.unified_diff
1216
psm = self._PatienceSequenceMatcher
1217
self.assertEquals(['--- a\t2008-08-08\n',
1218
'+++ b\t2008-09-09\n',
1219
'@@ -1,3 +1,2 @@\n',
1222
' how are you today?\n'
1224
, list(unified_diff(txt_a, txt_b,
1225
fromfile='a', tofile='b',
1226
fromfiledate='2008-08-08',
1227
tofiledate='2008-09-09',
1228
sequencematcher=psm)))
1231
class TestPatienceDiffLib_c(TestPatienceDiffLib):
1233
_test_needs_features = [features.compiled_patiencediff_feature]
1236
super(TestPatienceDiffLib_c, self).setUp()
1237
from bzrlib import _patiencediff_c
1238
self._unique_lcs = _patiencediff_c.unique_lcs_c
1239
self._recurse_matches = _patiencediff_c.recurse_matches_c
1240
self._PatienceSequenceMatcher = \
1241
_patiencediff_c.PatienceSequenceMatcher_c
1243
def test_unhashable(self):
1244
"""We should get a proper exception here."""
1245
# We need to be able to hash items in the sequence, lists are
1246
# unhashable, and thus cannot be diffed
1247
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1249
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1250
None, ['valid', []], [])
1251
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1252
None, ['valid'], [[]])
1253
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1254
None, ['valid'], ['valid', []])
1257
class TestPatienceDiffLibFiles(tests.TestCaseInTempDir):
1260
super(TestPatienceDiffLibFiles, self).setUp()
1261
self._PatienceSequenceMatcher = \
1262
_patiencediff_py.PatienceSequenceMatcher_py
1264
def test_patience_unified_diff_files(self):
1265
txt_a = ['hello there\n',
1267
'how are you today?\n']
1268
txt_b = ['hello there\n',
1269
'how are you today?\n']
1270
with open('a1', 'wb') as f: f.writelines(txt_a)
1271
with open('b1', 'wb') as f: f.writelines(txt_b)
1273
unified_diff_files = patiencediff.unified_diff_files
1274
psm = self._PatienceSequenceMatcher
1275
self.assertEquals(['--- a1\n',
1277
'@@ -1,3 +1,2 @@\n',
1280
' how are you today?\n',
1282
, list(unified_diff_files('a1', 'b1',
1283
sequencematcher=psm)))
1285
txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
1286
txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
1287
with open('a2', 'wb') as f: f.writelines(txt_a)
1288
with open('b2', 'wb') as f: f.writelines(txt_b)
1290
# This is the result with LongestCommonSubstring matching
1291
self.assertEquals(['--- a2\n',
1293
'@@ -1,6 +1,11 @@\n',
1305
, list(unified_diff_files('a2', 'b2')))
1307
# And the patience diff
1308
self.assertEquals(['--- a2\n',
1310
'@@ -4,6 +4,11 @@\n',
1323
, list(unified_diff_files('a2', 'b2',
1324
sequencematcher=psm)))
1327
class TestPatienceDiffLibFiles_c(TestPatienceDiffLibFiles):
1329
_test_needs_features = [features.compiled_patiencediff_feature]
1332
super(TestPatienceDiffLibFiles_c, self).setUp()
1333
from bzrlib import _patiencediff_c
1334
self._PatienceSequenceMatcher = \
1335
_patiencediff_c.PatienceSequenceMatcher_c
1338
class TestUsingCompiledIfAvailable(tests.TestCase):
1340
def test_PatienceSequenceMatcher(self):
1341
if features.compiled_patiencediff_feature.available():
1342
from bzrlib._patiencediff_c import PatienceSequenceMatcher_c
1343
self.assertIs(PatienceSequenceMatcher_c,
1344
patiencediff.PatienceSequenceMatcher)
1346
from bzrlib._patiencediff_py import PatienceSequenceMatcher_py
1347
self.assertIs(PatienceSequenceMatcher_py,
1348
patiencediff.PatienceSequenceMatcher)
1350
def test_unique_lcs(self):
1351
if features.compiled_patiencediff_feature.available():
1352
from bzrlib._patiencediff_c import unique_lcs_c
1353
self.assertIs(unique_lcs_c,
1354
patiencediff.unique_lcs)
1356
from bzrlib._patiencediff_py import unique_lcs_py
1357
self.assertIs(unique_lcs_py,
1358
patiencediff.unique_lcs)
1360
def test_recurse_matches(self):
1361
if features.compiled_patiencediff_feature.available():
1362
from bzrlib._patiencediff_c import recurse_matches_c
1363
self.assertIs(recurse_matches_c,
1364
patiencediff.recurse_matches)
1366
from bzrlib._patiencediff_py import recurse_matches_py
1367
self.assertIs(recurse_matches_py,
1368
patiencediff.recurse_matches)
1371
class TestDiffFromTool(tests.TestCaseWithTransport):
1373
def test_from_string(self):
1374
diff_obj = diff.DiffFromTool.from_string('diff', None, None, None)
1375
self.addCleanup(diff_obj.finish)
1376
self.assertEqual(['diff', '@old_path', '@new_path'],
1377
diff_obj.command_template)
1379
def test_from_string_u5(self):
1380
diff_obj = diff.DiffFromTool.from_string('diff "-u 5"',
1382
self.addCleanup(diff_obj.finish)
1383
self.assertEqual(['diff', '-u 5', '@old_path', '@new_path'],
1384
diff_obj.command_template)
1385
self.assertEqual(['diff', '-u 5', 'old-path', 'new-path'],
1386
diff_obj._get_command('old-path', 'new-path'))
1388
def test_from_string_path_with_backslashes(self):
1389
self.requireFeature(features.backslashdir_feature)
1390
tool = 'C:\\Tools\\Diff.exe'
1391
diff_obj = diff.DiffFromTool.from_string(tool, None, None, None)
1392
self.addCleanup(diff_obj.finish)
1393
self.assertEqual(['C:\\Tools\\Diff.exe', '@old_path', '@new_path'],
1394
diff_obj.command_template)
1395
self.assertEqual(['C:\\Tools\\Diff.exe', 'old-path', 'new-path'],
1396
diff_obj._get_command('old-path', 'new-path'))
1398
def test_execute(self):
1400
diff_obj = diff.DiffFromTool(['python', '-c',
1401
'print "@old_path @new_path"'],
1403
self.addCleanup(diff_obj.finish)
1404
diff_obj._execute('old', 'new')
1405
self.assertEqual(output.getvalue().rstrip(), 'old new')
1407
def test_execute_missing(self):
1408
diff_obj = diff.DiffFromTool(['a-tool-which-is-unlikely-to-exist'],
1410
self.addCleanup(diff_obj.finish)
1411
e = self.assertRaises(errors.ExecutableMissing, diff_obj._execute,
1413
self.assertEqual('a-tool-which-is-unlikely-to-exist could not be found'
1414
' on this machine', str(e))
1416
def test_prepare_files_creates_paths_readable_by_windows_tool(self):
1417
self.requireFeature(features.AttribFeature)
1419
tree = self.make_branch_and_tree('tree')
1420
self.build_tree_contents([('tree/file', 'content')])
1421
tree.add('file', 'file-id')
1422
tree.commit('old tree')
1424
self.addCleanup(tree.unlock)
1425
basis_tree = tree.basis_tree()
1426
basis_tree.lock_read()
1427
self.addCleanup(basis_tree.unlock)
1428
diff_obj = diff.DiffFromTool(['python', '-c',
1429
'print "@old_path @new_path"'],
1430
basis_tree, tree, output)
1431
diff_obj._prepare_files('file-id', 'file', 'file')
1432
# The old content should be readonly
1433
self.assertReadableByAttrib(diff_obj._root, 'old\\file',
1435
# The new content should use the tree object, not a 'new' file anymore
1436
self.assertEndsWith(tree.basedir, 'work/tree')
1437
self.assertReadableByAttrib(tree.basedir, 'file', r'work\\tree\\file$')
1439
def assertReadableByAttrib(self, cwd, relpath, regex):
1440
proc = subprocess.Popen(['attrib', relpath],
1441
stdout=subprocess.PIPE,
1443
(result, err) = proc.communicate()
1444
self.assertContainsRe(result.replace('\r\n', '\n'), regex)
1446
def test_prepare_files(self):
1448
tree = self.make_branch_and_tree('tree')
1449
self.build_tree_contents([('tree/oldname', 'oldcontent')])
1450
self.build_tree_contents([('tree/oldname2', 'oldcontent2')])
1451
tree.add('oldname', 'file-id')
1452
tree.add('oldname2', 'file2-id')
1453
# Earliest allowable date on FAT32 filesystems is 1980-01-01
1454
tree.commit('old tree', timestamp=315532800)
1455
tree.rename_one('oldname', 'newname')
1456
tree.rename_one('oldname2', 'newname2')
1457
self.build_tree_contents([('tree/newname', 'newcontent')])
1458
self.build_tree_contents([('tree/newname2', 'newcontent2')])
1459
old_tree = tree.basis_tree()
1460
old_tree.lock_read()
1461
self.addCleanup(old_tree.unlock)
1463
self.addCleanup(tree.unlock)
1464
diff_obj = diff.DiffFromTool(['python', '-c',
1465
'print "@old_path @new_path"'],
1466
old_tree, tree, output)
1467
self.addCleanup(diff_obj.finish)
1468
self.assertContainsRe(diff_obj._root, 'bzr-diff-[^/]*')
1469
old_path, new_path = diff_obj._prepare_files('file-id', 'oldname',
1471
self.assertContainsRe(old_path, 'old/oldname$')
1472
self.assertEqual(315532800, os.stat(old_path).st_mtime)
1473
self.assertContainsRe(new_path, 'tree/newname$')
1474
self.assertFileEqual('oldcontent', old_path)
1475
self.assertFileEqual('newcontent', new_path)
1476
if osutils.host_os_dereferences_symlinks():
1477
self.assertTrue(os.path.samefile('tree/newname', new_path))
1478
# make sure we can create files with the same parent directories
1479
diff_obj._prepare_files('file2-id', 'oldname2', 'newname2')
1482
class TestDiffFromToolEncodedFilename(tests.TestCaseWithTransport):
1484
def test_encodable_filename(self):
1485
# Just checks file path for external diff tool.
1486
# We cannot change CPython's internal encoding used by os.exec*.
1488
diffobj = diff.DiffFromTool(['dummy', '@old_path', '@new_path'],
1490
for _, scenario in EncodingAdapter.encoding_scenarios:
1491
encoding = scenario['encoding']
1492
dirname = scenario['info']['directory']
1493
filename = scenario['info']['filename']
1495
self.overrideAttr(diffobj, '_fenc', lambda: encoding)
1496
relpath = dirname + u'/' + filename
1497
fullpath = diffobj._safe_filename('safe', relpath)
1500
fullpath.encode(encoding).decode(encoding)
1502
self.assert_(fullpath.startswith(diffobj._root + '/safe'))
1504
def test_unencodable_filename(self):
1506
diffobj = diff.DiffFromTool(['dummy', '@old_path', '@new_path'],
1508
for _, scenario in EncodingAdapter.encoding_scenarios:
1509
encoding = scenario['encoding']
1510
dirname = scenario['info']['directory']
1511
filename = scenario['info']['filename']
1513
if encoding == 'iso-8859-1':
1514
encoding = 'iso-8859-2'
1516
encoding = 'iso-8859-1'
1518
self.overrideAttr(diffobj, '_fenc', lambda: encoding)
1519
relpath = dirname + u'/' + filename
1520
fullpath = diffobj._safe_filename('safe', relpath)
1523
fullpath.encode(encoding).decode(encoding)
1525
self.assert_(fullpath.startswith(diffobj._root + '/safe'))
1528
class TestGetTreesAndBranchesToDiffLocked(tests.TestCaseWithTransport):
1530
def call_gtabtd(self, path_list, revision_specs, old_url, new_url):
1531
"""Call get_trees_and_branches_to_diff_locked."""
1532
return diff.get_trees_and_branches_to_diff_locked(
1533
path_list, revision_specs, old_url, new_url, self.addCleanup)
1535
def test_basic(self):
1536
tree = self.make_branch_and_tree('tree')
1537
(old_tree, new_tree,
1538
old_branch, new_branch,
1539
specific_files, extra_trees) = self.call_gtabtd(
1540
['tree'], None, None, None)
1542
self.assertIsInstance(old_tree, revisiontree.RevisionTree)
1543
self.assertEqual(_mod_revision.NULL_REVISION,
1544
old_tree.get_revision_id())
1545
self.assertEqual(tree.basedir, new_tree.basedir)
1546
self.assertEqual(tree.branch.base, old_branch.base)
1547
self.assertEqual(tree.branch.base, new_branch.base)
1548
self.assertIs(None, specific_files)
1549
self.assertIs(None, extra_trees)
1551
def test_with_rev_specs(self):
1552
tree = self.make_branch_and_tree('tree')
1553
self.build_tree_contents([('tree/file', 'oldcontent')])
1554
tree.add('file', 'file-id')
1555
tree.commit('old tree', timestamp=0, rev_id="old-id")
1556
self.build_tree_contents([('tree/file', 'newcontent')])
1557
tree.commit('new tree', timestamp=0, rev_id="new-id")
1559
revisions = [revisionspec.RevisionSpec.from_string('1'),
1560
revisionspec.RevisionSpec.from_string('2')]
1561
(old_tree, new_tree,
1562
old_branch, new_branch,
1563
specific_files, extra_trees) = self.call_gtabtd(
1564
['tree'], revisions, None, None)
1566
self.assertIsInstance(old_tree, revisiontree.RevisionTree)
1567
self.assertEqual("old-id", old_tree.get_revision_id())
1568
self.assertIsInstance(new_tree, revisiontree.RevisionTree)
1569
self.assertEqual("new-id", new_tree.get_revision_id())
1570
self.assertEqual(tree.branch.base, old_branch.base)
1571
self.assertEqual(tree.branch.base, new_branch.base)
1572
self.assertIs(None, specific_files)
1573
self.assertEqual(tree.basedir, extra_trees[0].basedir)