47
129
self.assert_('@@' in lines[2][2:])
48
130
## "Unterminated hunk header for patch:\n%s" % "".join(lines)
132
def test_binary_lines(self):
134
uni_lines = [1023 * 'a' + '\x00']
135
self.assertRaises(errors.BinaryFile, udiff_lines, uni_lines , empty)
136
self.assertRaises(errors.BinaryFile, udiff_lines, empty, uni_lines)
137
udiff_lines(uni_lines , empty, allow_binary=True)
138
udiff_lines(empty, uni_lines, allow_binary=True)
140
def test_external_diff(self):
141
lines = external_udiff_lines(['boo\n'], ['goo\n'])
142
self.check_patch(lines)
143
self.assertEqual('\n', lines[-1])
145
def test_external_diff_no_fileno(self):
146
# Make sure that we can handle not having a fileno, even
147
# if the diff is large
148
lines = external_udiff_lines(['boo\n']*10000,
151
self.check_patch(lines)
153
def test_external_diff_binary_lang_c(self):
154
for lang in ('LANG', 'LC_ALL', 'LANGUAGE'):
155
self.overrideEnv(lang, 'C')
156
lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
157
# Older versions of diffutils say "Binary files", newer
158
# versions just say "Files".
159
self.assertContainsRe(lines[0], '(Binary f|F)iles old and new differ\n')
160
self.assertEquals(lines[1:], ['\n'])
162
def test_no_external_diff(self):
163
"""Check that NoDiff is raised when diff is not available"""
164
# Make sure no 'diff' command is available
165
# XXX: Weird, using None instead of '' breaks the test -- vila 20101216
166
self.overrideEnv('PATH', '')
167
self.assertRaises(errors.NoDiff, diff.external_diff,
168
'old', ['boo\n'], 'new', ['goo\n'],
169
StringIO(), diff_opts=['-u'])
171
def test_internal_diff_default(self):
172
# Default internal diff encoding is utf8
174
diff.internal_diff(u'old_\xb5', ['old_text\n'],
175
u'new_\xe5', ['new_text\n'], output)
176
lines = output.getvalue().splitlines(True)
177
self.check_patch(lines)
178
self.assertEquals(['--- old_\xc2\xb5\n',
179
'+++ new_\xc3\xa5\n',
187
def test_internal_diff_utf8(self):
189
diff.internal_diff(u'old_\xb5', ['old_text\n'],
190
u'new_\xe5', ['new_text\n'], output,
191
path_encoding='utf8')
192
lines = output.getvalue().splitlines(True)
193
self.check_patch(lines)
194
self.assertEquals(['--- old_\xc2\xb5\n',
195
'+++ new_\xc3\xa5\n',
203
def test_internal_diff_iso_8859_1(self):
205
diff.internal_diff(u'old_\xb5', ['old_text\n'],
206
u'new_\xe5', ['new_text\n'], output,
207
path_encoding='iso-8859-1')
208
lines = output.getvalue().splitlines(True)
209
self.check_patch(lines)
210
self.assertEquals(['--- old_\xb5\n',
219
def test_internal_diff_no_content(self):
221
diff.internal_diff(u'old', [], u'new', [], output)
222
self.assertEqual('', output.getvalue())
224
def test_internal_diff_no_changes(self):
226
diff.internal_diff(u'old', ['text\n', 'contents\n'],
227
u'new', ['text\n', 'contents\n'],
229
self.assertEqual('', output.getvalue())
231
def test_internal_diff_returns_bytes(self):
233
output = StringIO.StringIO()
234
diff.internal_diff(u'old_\xb5', ['old_text\n'],
235
u'new_\xe5', ['new_text\n'], output)
236
self.assertIsInstance(output.getvalue(), str,
237
'internal_diff should return bytestrings')
239
def test_internal_diff_default_context(self):
241
diff.internal_diff('old', ['same_text\n','same_text\n','same_text\n',
242
'same_text\n','same_text\n','old_text\n'],
243
'new', ['same_text\n','same_text\n','same_text\n',
244
'same_text\n','same_text\n','new_text\n'], output)
245
lines = output.getvalue().splitlines(True)
246
self.check_patch(lines)
247
self.assertEquals(['--- old\n',
259
def test_internal_diff_no_context(self):
261
diff.internal_diff('old', ['same_text\n','same_text\n','same_text\n',
262
'same_text\n','same_text\n','old_text\n'],
263
'new', ['same_text\n','same_text\n','same_text\n',
264
'same_text\n','same_text\n','new_text\n'], output,
266
lines = output.getvalue().splitlines(True)
267
self.check_patch(lines)
268
self.assertEquals(['--- old\n',
277
def test_internal_diff_more_context(self):
279
diff.internal_diff('old', ['same_text\n','same_text\n','same_text\n',
280
'same_text\n','same_text\n','old_text\n'],
281
'new', ['same_text\n','same_text\n','same_text\n',
282
'same_text\n','same_text\n','new_text\n'], output,
284
lines = output.getvalue().splitlines(True)
285
self.check_patch(lines)
286
self.assertEquals(['--- old\n',
303
class TestDiffFiles(tests.TestCaseInTempDir):
305
def test_external_diff_binary(self):
306
"""The output when using external diff should use diff's i18n error"""
307
# Make sure external_diff doesn't fail in the current LANG
308
lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
310
cmd = ['diff', '-u', '--binary', 'old', 'new']
311
with open('old', 'wb') as f: f.write('\x00foobar\n')
312
with open('new', 'wb') as f: f.write('foo\x00bar\n')
313
pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
314
stdin=subprocess.PIPE)
315
out, err = pipe.communicate()
316
# Diff returns '2' on Binary files.
317
self.assertEqual(2, pipe.returncode)
318
# We should output whatever diff tells us, plus a trailing newline
319
self.assertEqual(out.splitlines(True) + ['\n'], lines)
322
def get_diff_as_string(tree1, tree2, specific_files=None, working_tree=None):
324
if working_tree is not None:
325
extra_trees = (working_tree,)
328
diff.show_diff_trees(tree1, tree2, output,
329
specific_files=specific_files,
330
extra_trees=extra_trees, old_label='old/',
332
return output.getvalue()
335
class TestDiffDates(tests.TestCaseWithTransport):
338
super(TestDiffDates, self).setUp()
339
self.wt = self.make_branch_and_tree('.')
340
self.b = self.wt.branch
341
self.build_tree_contents([
342
('file1', 'file1 contents at rev 1\n'),
343
('file2', 'file2 contents at rev 1\n')
345
self.wt.add(['file1', 'file2'])
347
message='Revision 1',
348
timestamp=1143849600, # 2006-04-01 00:00:00 UTC
351
self.build_tree_contents([('file1', 'file1 contents at rev 2\n')])
353
message='Revision 2',
354
timestamp=1143936000, # 2006-04-02 00:00:00 UTC
357
self.build_tree_contents([('file2', 'file2 contents at rev 3\n')])
359
message='Revision 3',
360
timestamp=1144022400, # 2006-04-03 00:00:00 UTC
363
self.wt.remove(['file2'])
365
message='Revision 4',
366
timestamp=1144108800, # 2006-04-04 00:00:00 UTC
369
self.build_tree_contents([
370
('file1', 'file1 contents in working tree\n')
372
# set the date stamps for files in the working tree to known values
373
os.utime('file1', (1144195200, 1144195200)) # 2006-04-05 00:00:00 UTC
375
def test_diff_rev_tree_working_tree(self):
376
output = get_diff_as_string(self.wt.basis_tree(), self.wt)
377
# note that the date for old/file1 is from rev 2 rather than from
378
# the basis revision (rev 4)
379
self.assertEqualDiff(output, '''\
380
=== modified file 'file1'
381
--- old/file1\t2006-04-02 00:00:00 +0000
382
+++ new/file1\t2006-04-05 00:00:00 +0000
384
-file1 contents at rev 2
385
+file1 contents in working tree
389
def test_diff_rev_tree_rev_tree(self):
390
tree1 = self.b.repository.revision_tree('rev-2')
391
tree2 = self.b.repository.revision_tree('rev-3')
392
output = get_diff_as_string(tree1, tree2)
393
self.assertEqualDiff(output, '''\
394
=== modified file 'file2'
395
--- old/file2\t2006-04-01 00:00:00 +0000
396
+++ new/file2\t2006-04-03 00:00:00 +0000
398
-file2 contents at rev 1
399
+file2 contents at rev 3
403
def test_diff_add_files(self):
404
tree1 = self.b.repository.revision_tree(_mod_revision.NULL_REVISION)
405
tree2 = self.b.repository.revision_tree('rev-1')
406
output = get_diff_as_string(tree1, tree2)
407
# the files have the epoch time stamp for the tree in which
409
self.assertEqualDiff(output, '''\
410
=== added file 'file1'
411
--- old/file1\t1970-01-01 00:00:00 +0000
412
+++ new/file1\t2006-04-01 00:00:00 +0000
414
+file1 contents at rev 1
416
=== added file 'file2'
417
--- old/file2\t1970-01-01 00:00:00 +0000
418
+++ new/file2\t2006-04-01 00:00:00 +0000
420
+file2 contents at rev 1
424
def test_diff_remove_files(self):
425
tree1 = self.b.repository.revision_tree('rev-3')
426
tree2 = self.b.repository.revision_tree('rev-4')
427
output = get_diff_as_string(tree1, tree2)
428
# the file has the epoch time stamp for the tree in which
430
self.assertEqualDiff(output, '''\
431
=== removed file 'file2'
432
--- old/file2\t2006-04-03 00:00:00 +0000
433
+++ new/file2\t1970-01-01 00:00:00 +0000
435
-file2 contents at rev 3
439
def test_show_diff_specified(self):
440
"""A working tree filename can be used to identify a file"""
441
self.wt.rename_one('file1', 'file1b')
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=['file1b'],
445
working_tree=self.wt)
446
self.assertContainsRe(out, 'file1\t')
448
def test_recursive_diff(self):
449
"""Children of directories are matched"""
452
self.wt.add(['dir1', 'dir2'])
453
self.wt.rename_one('file1', 'dir1/file1')
454
old_tree = self.b.repository.revision_tree('rev-1')
455
new_tree = self.b.repository.revision_tree('rev-4')
456
out = get_diff_as_string(old_tree, new_tree, specific_files=['dir1'],
457
working_tree=self.wt)
458
self.assertContainsRe(out, 'file1\t')
459
out = get_diff_as_string(old_tree, new_tree, specific_files=['dir2'],
460
working_tree=self.wt)
461
self.assertNotContainsRe(out, 'file1\t')
464
class TestShowDiffTrees(tests.TestCaseWithTransport):
465
"""Direct tests for show_diff_trees"""
467
def test_modified_file(self):
468
"""Test when a file is modified."""
469
tree = self.make_branch_and_tree('tree')
470
self.build_tree_contents([('tree/file', 'contents\n')])
471
tree.add(['file'], ['file-id'])
472
tree.commit('one', rev_id='rev-1')
474
self.build_tree_contents([('tree/file', 'new contents\n')])
475
d = get_diff_as_string(tree.basis_tree(), tree)
476
self.assertContainsRe(d, "=== modified file 'file'\n")
477
self.assertContainsRe(d, '--- old/file\t')
478
self.assertContainsRe(d, '\\+\\+\\+ new/file\t')
479
self.assertContainsRe(d, '-contents\n'
482
def test_modified_file_in_renamed_dir(self):
483
"""Test when a file is modified in a renamed directory."""
484
tree = self.make_branch_and_tree('tree')
485
self.build_tree(['tree/dir/'])
486
self.build_tree_contents([('tree/dir/file', 'contents\n')])
487
tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
488
tree.commit('one', rev_id='rev-1')
490
tree.rename_one('dir', 'other')
491
self.build_tree_contents([('tree/other/file', 'new contents\n')])
492
d = get_diff_as_string(tree.basis_tree(), tree)
493
self.assertContainsRe(d, "=== renamed directory 'dir' => 'other'\n")
494
self.assertContainsRe(d, "=== modified file 'other/file'\n")
495
# XXX: This is technically incorrect, because it used to be at another
496
# location. What to do?
497
self.assertContainsRe(d, '--- old/dir/file\t')
498
self.assertContainsRe(d, '\\+\\+\\+ new/other/file\t')
499
self.assertContainsRe(d, '-contents\n'
502
def test_renamed_directory(self):
503
"""Test when only a directory is only renamed."""
504
tree = self.make_branch_and_tree('tree')
505
self.build_tree(['tree/dir/'])
506
self.build_tree_contents([('tree/dir/file', 'contents\n')])
507
tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
508
tree.commit('one', rev_id='rev-1')
510
tree.rename_one('dir', 'newdir')
511
d = get_diff_as_string(tree.basis_tree(), tree)
512
# Renaming a directory should be a single "you renamed this dir" even
513
# when there are files inside.
514
self.assertEqual(d, "=== renamed directory 'dir' => 'newdir'\n")
516
def test_renamed_file(self):
517
"""Test when a file is only renamed."""
518
tree = self.make_branch_and_tree('tree')
519
self.build_tree_contents([('tree/file', 'contents\n')])
520
tree.add(['file'], ['file-id'])
521
tree.commit('one', rev_id='rev-1')
523
tree.rename_one('file', 'newname')
524
d = get_diff_as_string(tree.basis_tree(), tree)
525
self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
526
# We shouldn't have a --- or +++ line, because there is no content
528
self.assertNotContainsRe(d, '---')
530
def test_renamed_and_modified_file(self):
531
"""Test when a file is only renamed."""
532
tree = self.make_branch_and_tree('tree')
533
self.build_tree_contents([('tree/file', 'contents\n')])
534
tree.add(['file'], ['file-id'])
535
tree.commit('one', rev_id='rev-1')
537
tree.rename_one('file', 'newname')
538
self.build_tree_contents([('tree/newname', 'new contents\n')])
539
d = get_diff_as_string(tree.basis_tree(), tree)
540
self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
541
self.assertContainsRe(d, '--- old/file\t')
542
self.assertContainsRe(d, '\\+\\+\\+ new/newname\t')
543
self.assertContainsRe(d, '-contents\n'
547
def test_internal_diff_exec_property(self):
548
tree = self.make_branch_and_tree('tree')
550
tt = transform.TreeTransform(tree)
551
tt.new_file('a', tt.root, 'contents\n', 'a-id', True)
552
tt.new_file('b', tt.root, 'contents\n', 'b-id', False)
553
tt.new_file('c', tt.root, 'contents\n', 'c-id', True)
554
tt.new_file('d', tt.root, 'contents\n', 'd-id', False)
555
tt.new_file('e', tt.root, 'contents\n', 'control-e-id', True)
556
tt.new_file('f', tt.root, 'contents\n', 'control-f-id', False)
558
tree.commit('one', rev_id='rev-1')
560
tt = transform.TreeTransform(tree)
561
tt.set_executability(False, tt.trans_id_file_id('a-id'))
562
tt.set_executability(True, tt.trans_id_file_id('b-id'))
563
tt.set_executability(False, tt.trans_id_file_id('c-id'))
564
tt.set_executability(True, tt.trans_id_file_id('d-id'))
566
tree.rename_one('c', 'new-c')
567
tree.rename_one('d', 'new-d')
569
d = get_diff_as_string(tree.basis_tree(), tree)
571
self.assertContainsRe(d, r"file 'a'.*\(properties changed:"
573
self.assertContainsRe(d, r"file 'b'.*\(properties changed:"
575
self.assertContainsRe(d, r"file 'c'.*\(properties changed:"
577
self.assertContainsRe(d, r"file 'd'.*\(properties changed:"
579
self.assertNotContainsRe(d, r"file 'e'")
580
self.assertNotContainsRe(d, r"file 'f'")
582
def test_binary_unicode_filenames(self):
583
"""Test that contents of files are *not* encoded in UTF-8 when there
584
is a binary file in the diff.
586
# See https://bugs.launchpad.net/bugs/110092.
587
self.requireFeature(features.UnicodeFilenameFeature)
589
# This bug isn't triggered with cStringIO.
590
from StringIO import StringIO
591
tree = self.make_branch_and_tree('tree')
592
alpha, omega = u'\u03b1', u'\u03c9'
593
alpha_utf8, omega_utf8 = alpha.encode('utf8'), omega.encode('utf8')
594
self.build_tree_contents(
595
[('tree/' + alpha, chr(0)),
597
('The %s and the %s\n' % (alpha_utf8, omega_utf8)))])
598
tree.add([alpha], ['file-id'])
599
tree.add([omega], ['file-id-2'])
600
diff_content = StringIO()
601
diff.show_diff_trees(tree.basis_tree(), tree, diff_content)
602
d = diff_content.getvalue()
603
self.assertContainsRe(d, r"=== added file '%s'" % alpha_utf8)
604
self.assertContainsRe(d, "Binary files a/%s.*and b/%s.* differ\n"
605
% (alpha_utf8, alpha_utf8))
606
self.assertContainsRe(d, r"=== added file '%s'" % omega_utf8)
607
self.assertContainsRe(d, r"--- a/%s" % (omega_utf8,))
608
self.assertContainsRe(d, r"\+\+\+ b/%s" % (omega_utf8,))
610
def test_unicode_filename(self):
611
"""Test when the filename are unicode."""
612
self.requireFeature(features.UnicodeFilenameFeature)
614
alpha, omega = u'\u03b1', u'\u03c9'
615
autf8, outf8 = alpha.encode('utf8'), omega.encode('utf8')
617
tree = self.make_branch_and_tree('tree')
618
self.build_tree_contents([('tree/ren_'+alpha, 'contents\n')])
619
tree.add(['ren_'+alpha], ['file-id-2'])
620
self.build_tree_contents([('tree/del_'+alpha, 'contents\n')])
621
tree.add(['del_'+alpha], ['file-id-3'])
622
self.build_tree_contents([('tree/mod_'+alpha, 'contents\n')])
623
tree.add(['mod_'+alpha], ['file-id-4'])
625
tree.commit('one', rev_id='rev-1')
627
tree.rename_one('ren_'+alpha, 'ren_'+omega)
628
tree.remove('del_'+alpha)
629
self.build_tree_contents([('tree/add_'+alpha, 'contents\n')])
630
tree.add(['add_'+alpha], ['file-id'])
631
self.build_tree_contents([('tree/mod_'+alpha, 'contents_mod\n')])
633
d = get_diff_as_string(tree.basis_tree(), tree)
634
self.assertContainsRe(d,
635
"=== renamed file 'ren_%s' => 'ren_%s'\n"%(autf8, outf8))
636
self.assertContainsRe(d, "=== added file 'add_%s'"%autf8)
637
self.assertContainsRe(d, "=== modified file 'mod_%s'"%autf8)
638
self.assertContainsRe(d, "=== removed file 'del_%s'"%autf8)
640
def test_unicode_filename_path_encoding(self):
641
"""Test for bug #382699: unicode filenames on Windows should be shown
644
self.requireFeature(features.UnicodeFilenameFeature)
645
# The word 'test' in Russian
646
_russian_test = u'\u0422\u0435\u0441\u0442'
647
directory = _russian_test + u'/'
648
test_txt = _russian_test + u'.txt'
649
u1234 = u'\u1234.txt'
651
tree = self.make_branch_and_tree('.')
652
self.build_tree_contents([
657
tree.add([test_txt, u1234, directory])
660
diff.show_diff_trees(tree.basis_tree(), tree, sio,
661
path_encoding='cp1251')
663
output = subst_dates(sio.getvalue())
665
=== added directory '%(directory)s'
666
=== added file '%(test_txt)s'
667
--- a/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
668
+++ b/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
672
=== added file '?.txt'
673
--- a/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
674
+++ b/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
678
''' % {'directory': _russian_test.encode('cp1251'),
679
'test_txt': test_txt.encode('cp1251'),
681
self.assertEqualDiff(output, shouldbe)
684
class DiffWasIs(diff.DiffPath):
686
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
687
self.to_file.write('was: ')
688
self.to_file.write(self.old_tree.get_file(file_id).read())
689
self.to_file.write('is: ')
690
self.to_file.write(self.new_tree.get_file(file_id).read())
694
class TestDiffTree(tests.TestCaseWithTransport):
697
super(TestDiffTree, self).setUp()
698
self.old_tree = self.make_branch_and_tree('old-tree')
699
self.old_tree.lock_write()
700
self.addCleanup(self.old_tree.unlock)
701
self.new_tree = self.make_branch_and_tree('new-tree')
702
self.new_tree.lock_write()
703
self.addCleanup(self.new_tree.unlock)
704
self.differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
706
def test_diff_text(self):
707
self.build_tree_contents([('old-tree/olddir/',),
708
('old-tree/olddir/oldfile', 'old\n')])
709
self.old_tree.add('olddir')
710
self.old_tree.add('olddir/oldfile', 'file-id')
711
self.build_tree_contents([('new-tree/newdir/',),
712
('new-tree/newdir/newfile', 'new\n')])
713
self.new_tree.add('newdir')
714
self.new_tree.add('newdir/newfile', 'file-id')
715
differ = diff.DiffText(self.old_tree, self.new_tree, StringIO())
716
differ.diff_text('file-id', None, 'old label', 'new label')
718
'--- old label\n+++ new label\n@@ -1,1 +0,0 @@\n-old\n\n',
719
differ.to_file.getvalue())
720
differ.to_file.seek(0)
721
differ.diff_text(None, 'file-id', 'old label', 'new label')
723
'--- old label\n+++ new label\n@@ -0,0 +1,1 @@\n+new\n\n',
724
differ.to_file.getvalue())
725
differ.to_file.seek(0)
726
differ.diff_text('file-id', 'file-id', 'old label', 'new label')
728
'--- old label\n+++ new label\n@@ -1,1 +1,1 @@\n-old\n+new\n\n',
729
differ.to_file.getvalue())
731
def test_diff_deletion(self):
732
self.build_tree_contents([('old-tree/file', 'contents'),
733
('new-tree/file', 'contents')])
734
self.old_tree.add('file', 'file-id')
735
self.new_tree.add('file', 'file-id')
736
os.unlink('new-tree/file')
737
self.differ.show_diff(None)
738
self.assertContainsRe(self.differ.to_file.getvalue(), '-contents')
740
def test_diff_creation(self):
741
self.build_tree_contents([('old-tree/file', 'contents'),
742
('new-tree/file', 'contents')])
743
self.old_tree.add('file', 'file-id')
744
self.new_tree.add('file', 'file-id')
745
os.unlink('old-tree/file')
746
self.differ.show_diff(None)
747
self.assertContainsRe(self.differ.to_file.getvalue(), '\+contents')
749
def test_diff_symlink(self):
750
differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
751
differ.diff_symlink('old target', None)
752
self.assertEqual("=== target was 'old target'\n",
753
differ.to_file.getvalue())
755
differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
756
differ.diff_symlink(None, 'new target')
757
self.assertEqual("=== target is 'new target'\n",
758
differ.to_file.getvalue())
760
differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
761
differ.diff_symlink('old target', 'new target')
762
self.assertEqual("=== target changed 'old target' => 'new target'\n",
763
differ.to_file.getvalue())
766
self.build_tree_contents([('old-tree/olddir/',),
767
('old-tree/olddir/oldfile', 'old\n')])
768
self.old_tree.add('olddir')
769
self.old_tree.add('olddir/oldfile', 'file-id')
770
self.build_tree_contents([('new-tree/newdir/',),
771
('new-tree/newdir/newfile', 'new\n')])
772
self.new_tree.add('newdir')
773
self.new_tree.add('newdir/newfile', 'file-id')
774
self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
775
self.assertContainsRe(
776
self.differ.to_file.getvalue(),
777
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
778
' \@\@\n-old\n\+new\n\n')
780
def test_diff_kind_change(self):
781
self.requireFeature(features.SymlinkFeature)
782
self.build_tree_contents([('old-tree/olddir/',),
783
('old-tree/olddir/oldfile', 'old\n')])
784
self.old_tree.add('olddir')
785
self.old_tree.add('olddir/oldfile', 'file-id')
786
self.build_tree(['new-tree/newdir/'])
787
os.symlink('new', 'new-tree/newdir/newfile')
788
self.new_tree.add('newdir')
789
self.new_tree.add('newdir/newfile', 'file-id')
790
self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
791
self.assertContainsRe(
792
self.differ.to_file.getvalue(),
793
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+0,0'
795
self.assertContainsRe(self.differ.to_file.getvalue(),
796
"=== target is u'new'\n")
798
def test_diff_directory(self):
799
self.build_tree(['new-tree/new-dir/'])
800
self.new_tree.add('new-dir', 'new-dir-id')
801
self.differ.diff('new-dir-id', None, 'new-dir')
802
self.assertEqual(self.differ.to_file.getvalue(), '')
804
def create_old_new(self):
805
self.build_tree_contents([('old-tree/olddir/',),
806
('old-tree/olddir/oldfile', 'old\n')])
807
self.old_tree.add('olddir')
808
self.old_tree.add('olddir/oldfile', 'file-id')
809
self.build_tree_contents([('new-tree/newdir/',),
810
('new-tree/newdir/newfile', 'new\n')])
811
self.new_tree.add('newdir')
812
self.new_tree.add('newdir/newfile', 'file-id')
814
def test_register_diff(self):
815
self.create_old_new()
816
old_diff_factories = diff.DiffTree.diff_factories
817
diff.DiffTree.diff_factories=old_diff_factories[:]
818
diff.DiffTree.diff_factories.insert(0, DiffWasIs.from_diff_tree)
820
differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
822
diff.DiffTree.diff_factories = old_diff_factories
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_extra_factories(self):
832
self.create_old_new()
833
differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO(),
834
extra_factories=[DiffWasIs.from_diff_tree])
835
differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
836
self.assertNotContainsRe(
837
differ.to_file.getvalue(),
838
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
839
' \@\@\n-old\n\+new\n\n')
840
self.assertContainsRe(differ.to_file.getvalue(),
841
'was: old\nis: new\n')
843
def test_alphabetical_order(self):
844
self.build_tree(['new-tree/a-file'])
845
self.new_tree.add('a-file')
846
self.build_tree(['old-tree/b-file'])
847
self.old_tree.add('b-file')
848
self.differ.show_diff(None)
849
self.assertContainsRe(self.differ.to_file.getvalue(),
850
'.*a-file(.|\n)*b-file')
853
class TestPatienceDiffLib(tests.TestCase):
856
super(TestPatienceDiffLib, self).setUp()
857
self._unique_lcs = _patiencediff_py.unique_lcs_py
858
self._recurse_matches = _patiencediff_py.recurse_matches_py
859
self._PatienceSequenceMatcher = \
860
_patiencediff_py.PatienceSequenceMatcher_py
862
def test_diff_unicode_string(self):
863
a = ''.join([unichr(i) for i in range(4000, 4500, 3)])
864
b = ''.join([unichr(i) for i in range(4300, 4800, 2)])
865
sm = self._PatienceSequenceMatcher(None, a, b)
866
mb = sm.get_matching_blocks()
867
self.assertEquals(35, len(mb))
869
def test_unique_lcs(self):
870
unique_lcs = self._unique_lcs
871
self.assertEquals(unique_lcs('', ''), [])
872
self.assertEquals(unique_lcs('', 'a'), [])
873
self.assertEquals(unique_lcs('a', ''), [])
874
self.assertEquals(unique_lcs('a', 'a'), [(0,0)])
875
self.assertEquals(unique_lcs('a', 'b'), [])
876
self.assertEquals(unique_lcs('ab', 'ab'), [(0,0), (1,1)])
877
self.assertEquals(unique_lcs('abcde', 'cdeab'), [(2,0), (3,1), (4,2)])
878
self.assertEquals(unique_lcs('cdeab', 'abcde'), [(0,2), (1,3), (2,4)])
879
self.assertEquals(unique_lcs('abXde', 'abYde'), [(0,0), (1,1),
881
self.assertEquals(unique_lcs('acbac', 'abc'), [(2,1)])
883
def test_recurse_matches(self):
884
def test_one(a, b, matches):
886
self._recurse_matches(
887
a, b, 0, 0, len(a), len(b), test_matches, 10)
888
self.assertEquals(test_matches, matches)
890
test_one(['a', '', 'b', '', 'c'], ['a', 'a', 'b', 'c', 'c'],
891
[(0, 0), (2, 2), (4, 4)])
892
test_one(['a', 'c', 'b', 'a', 'c'], ['a', 'b', 'c'],
893
[(0, 0), (2, 1), (4, 2)])
894
# Even though 'bc' is not unique globally, and is surrounded by
895
# non-matching lines, we should still match, because they are locally
897
test_one('abcdbce', 'afbcgdbce', [(0,0), (1, 2), (2, 3), (3, 5),
898
(4, 6), (5, 7), (6, 8)])
900
# recurse_matches doesn't match non-unique
901
# lines surrounded by bogus text.
902
# The update has been done in patiencediff.SequenceMatcher instead
904
# This is what it could be
905
#test_one('aBccDe', 'abccde', [(0,0), (2,2), (3,3), (5,5)])
907
# This is what it currently gives:
908
test_one('aBccDe', 'abccde', [(0,0), (5,5)])
910
def assertDiffBlocks(self, a, b, expected_blocks):
911
"""Check that the sequence matcher returns the correct blocks.
913
:param a: A sequence to match
914
:param b: Another sequence to match
915
:param expected_blocks: The expected output, not including the final
916
matching block (len(a), len(b), 0)
918
matcher = self._PatienceSequenceMatcher(None, a, b)
919
blocks = matcher.get_matching_blocks()
921
self.assertEqual((len(a), len(b), 0), last)
922
self.assertEqual(expected_blocks, blocks)
924
def test_matching_blocks(self):
925
# Some basic matching tests
926
self.assertDiffBlocks('', '', [])
927
self.assertDiffBlocks([], [], [])
928
self.assertDiffBlocks('abc', '', [])
929
self.assertDiffBlocks('', 'abc', [])
930
self.assertDiffBlocks('abcd', 'abcd', [(0, 0, 4)])
931
self.assertDiffBlocks('abcd', 'abce', [(0, 0, 3)])
932
self.assertDiffBlocks('eabc', 'abce', [(1, 0, 3)])
933
self.assertDiffBlocks('eabce', 'abce', [(1, 0, 4)])
934
self.assertDiffBlocks('abcde', 'abXde', [(0, 0, 2), (3, 3, 2)])
935
self.assertDiffBlocks('abcde', 'abXYZde', [(0, 0, 2), (3, 5, 2)])
936
self.assertDiffBlocks('abde', 'abXYZde', [(0, 0, 2), (2, 5, 2)])
937
# This may check too much, but it checks to see that
938
# a copied block stays attached to the previous section,
940
# difflib would tend to grab the trailing longest match
941
# which would make the diff not look right
942
self.assertDiffBlocks('abcdefghijklmnop', 'abcdefxydefghijklmnop',
943
[(0, 0, 6), (6, 11, 10)])
945
# make sure it supports passing in lists
946
self.assertDiffBlocks(
949
'how are you today?\n'],
951
'how are you today?\n'],
952
[(0, 0, 1), (2, 1, 1)])
954
# non unique lines surrounded by non-matching lines
956
self.assertDiffBlocks('aBccDe', 'abccde', [(0,0,1), (5,5,1)])
958
# But they only need to be locally unique
959
self.assertDiffBlocks('aBcDec', 'abcdec', [(0,0,1), (2,2,1), (4,4,2)])
961
# non unique blocks won't be matched
962
self.assertDiffBlocks('aBcdEcdFg', 'abcdecdfg', [(0,0,1), (8,8,1)])
964
# but locally unique ones will
965
self.assertDiffBlocks('aBcdEeXcdFg', 'abcdecdfg', [(0,0,1), (2,2,2),
966
(5,4,1), (7,5,2), (10,8,1)])
968
self.assertDiffBlocks('abbabbXd', 'cabbabxd', [(7,7,1)])
969
self.assertDiffBlocks('abbabbbb', 'cabbabbc', [])
970
self.assertDiffBlocks('bbbbbbbb', 'cbbbbbbc', [])
972
def test_matching_blocks_tuples(self):
973
# Some basic matching tests
974
self.assertDiffBlocks([], [], [])
975
self.assertDiffBlocks([('a',), ('b',), ('c,')], [], [])
976
self.assertDiffBlocks([], [('a',), ('b',), ('c,')], [])
977
self.assertDiffBlocks([('a',), ('b',), ('c,')],
978
[('a',), ('b',), ('c,')],
980
self.assertDiffBlocks([('a',), ('b',), ('c,')],
981
[('a',), ('b',), ('d,')],
983
self.assertDiffBlocks([('d',), ('b',), ('c,')],
984
[('a',), ('b',), ('c,')],
986
self.assertDiffBlocks([('d',), ('a',), ('b',), ('c,')],
987
[('a',), ('b',), ('c,')],
989
self.assertDiffBlocks([('a', 'b'), ('c', 'd'), ('e', 'f')],
990
[('a', 'b'), ('c', 'X'), ('e', 'f')],
991
[(0, 0, 1), (2, 2, 1)])
992
self.assertDiffBlocks([('a', 'b'), ('c', 'd'), ('e', 'f')],
993
[('a', 'b'), ('c', 'dX'), ('e', 'f')],
994
[(0, 0, 1), (2, 2, 1)])
996
def test_opcodes(self):
997
def chk_ops(a, b, expected_codes):
998
s = self._PatienceSequenceMatcher(None, a, b)
999
self.assertEquals(expected_codes, s.get_opcodes())
1003
chk_ops('abc', '', [('delete', 0,3, 0,0)])
1004
chk_ops('', 'abc', [('insert', 0,0, 0,3)])
1005
chk_ops('abcd', 'abcd', [('equal', 0,4, 0,4)])
1006
chk_ops('abcd', 'abce', [('equal', 0,3, 0,3),
1007
('replace', 3,4, 3,4)
1009
chk_ops('eabc', 'abce', [('delete', 0,1, 0,0),
1010
('equal', 1,4, 0,3),
1011
('insert', 4,4, 3,4)
1013
chk_ops('eabce', 'abce', [('delete', 0,1, 0,0),
1016
chk_ops('abcde', 'abXde', [('equal', 0,2, 0,2),
1017
('replace', 2,3, 2,3),
1020
chk_ops('abcde', 'abXYZde', [('equal', 0,2, 0,2),
1021
('replace', 2,3, 2,5),
1024
chk_ops('abde', 'abXYZde', [('equal', 0,2, 0,2),
1025
('insert', 2,2, 2,5),
1028
chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
1029
[('equal', 0,6, 0,6),
1030
('insert', 6,6, 6,11),
1031
('equal', 6,16, 11,21)
1036
, 'how are you today?\n'],
1038
, 'how are you today?\n'],
1039
[('equal', 0,1, 0,1),
1040
('delete', 1,2, 1,1),
1041
('equal', 2,3, 1,2),
1043
chk_ops('aBccDe', 'abccde',
1044
[('equal', 0,1, 0,1),
1045
('replace', 1,5, 1,5),
1046
('equal', 5,6, 5,6),
1048
chk_ops('aBcDec', 'abcdec',
1049
[('equal', 0,1, 0,1),
1050
('replace', 1,2, 1,2),
1051
('equal', 2,3, 2,3),
1052
('replace', 3,4, 3,4),
1053
('equal', 4,6, 4,6),
1055
chk_ops('aBcdEcdFg', 'abcdecdfg',
1056
[('equal', 0,1, 0,1),
1057
('replace', 1,8, 1,8),
1060
chk_ops('aBcdEeXcdFg', 'abcdecdfg',
1061
[('equal', 0,1, 0,1),
1062
('replace', 1,2, 1,2),
1063
('equal', 2,4, 2,4),
1064
('delete', 4,5, 4,4),
1065
('equal', 5,6, 4,5),
1066
('delete', 6,7, 5,5),
1067
('equal', 7,9, 5,7),
1068
('replace', 9,10, 7,8),
1069
('equal', 10,11, 8,9)
1072
def test_grouped_opcodes(self):
1073
def chk_ops(a, b, expected_codes, n=3):
1074
s = self._PatienceSequenceMatcher(None, a, b)
1075
self.assertEquals(expected_codes, list(s.get_grouped_opcodes(n)))
1079
chk_ops('abc', '', [[('delete', 0,3, 0,0)]])
1080
chk_ops('', 'abc', [[('insert', 0,0, 0,3)]])
1081
chk_ops('abcd', 'abcd', [])
1082
chk_ops('abcd', 'abce', [[('equal', 0,3, 0,3),
1083
('replace', 3,4, 3,4)
1085
chk_ops('eabc', 'abce', [[('delete', 0,1, 0,0),
1086
('equal', 1,4, 0,3),
1087
('insert', 4,4, 3,4)
1089
chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
1090
[[('equal', 3,6, 3,6),
1091
('insert', 6,6, 6,11),
1092
('equal', 6,9, 11,14)
1094
chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
1095
[[('equal', 2,6, 2,6),
1096
('insert', 6,6, 6,11),
1097
('equal', 6,10, 11,15)
1099
chk_ops('Xabcdef', 'abcdef',
1100
[[('delete', 0,1, 0,0),
1103
chk_ops('abcdef', 'abcdefX',
1104
[[('equal', 3,6, 3,6),
1105
('insert', 6,6, 6,7)
1109
def test_multiple_ranges(self):
1110
# There was an earlier bug where we used a bad set of ranges,
1111
# this triggers that specific bug, to make sure it doesn't regress
1112
self.assertDiffBlocks('abcdefghijklmnop',
1113
'abcXghiYZQRSTUVWXYZijklmnop',
1114
[(0, 0, 3), (6, 4, 3), (9, 20, 7)])
1116
self.assertDiffBlocks('ABCd efghIjk L',
1117
'AxyzBCn mo pqrstuvwI1 2 L',
1118
[(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
1120
# These are rot13 code snippets.
1121
self.assertDiffBlocks('''\
1122
trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
1124
gnxrf_netf = ['svyr*']
1125
gnxrf_bcgvbaf = ['ab-erphefr']
1127
qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr):
1128
sebz omeyvo.nqq vzcbeg fzneg_nqq, nqq_ercbegre_cevag, nqq_ercbegre_ahyy
1130
ercbegre = nqq_ercbegre_ahyy
1132
ercbegre = nqq_ercbegre_cevag
1133
fzneg_nqq(svyr_yvfg, abg ab_erphefr, ercbegre)
1136
pynff pzq_zxqve(Pbzznaq):
1137
'''.splitlines(True), '''\
1138
trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
1140
--qel-eha jvyy fubj juvpu svyrf jbhyq or nqqrq, ohg abg npghnyyl
1143
gnxrf_netf = ['svyr*']
1144
gnxrf_bcgvbaf = ['ab-erphefr', 'qel-eha']
1146
qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr, qel_eha=Snyfr):
1151
# Guvf vf cbvagyrff, ohg V'q engure abg envfr na reebe
1152
npgvba = omeyvo.nqq.nqq_npgvba_ahyy
1154
npgvba = omeyvo.nqq.nqq_npgvba_cevag
1156
npgvba = omeyvo.nqq.nqq_npgvba_nqq
1158
npgvba = omeyvo.nqq.nqq_npgvba_nqq_naq_cevag
1160
omeyvo.nqq.fzneg_nqq(svyr_yvfg, abg ab_erphefr, npgvba)
1163
pynff pzq_zxqve(Pbzznaq):
1164
'''.splitlines(True)
1165
, [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
1167
def test_patience_unified_diff(self):
1168
txt_a = ['hello there\n',
1170
'how are you today?\n']
1171
txt_b = ['hello there\n',
1172
'how are you today?\n']
1173
unified_diff = patiencediff.unified_diff
1174
psm = self._PatienceSequenceMatcher
1175
self.assertEquals(['--- \n',
1177
'@@ -1,3 +1,2 @@\n',
1180
' how are you today?\n'
1182
, list(unified_diff(txt_a, txt_b,
1183
sequencematcher=psm)))
1184
txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
1185
txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
1186
# This is the result with LongestCommonSubstring matching
1187
self.assertEquals(['--- \n',
1189
'@@ -1,6 +1,11 @@\n',
1201
, list(unified_diff(txt_a, txt_b)))
1202
# And the patience diff
1203
self.assertEquals(['--- \n',
1205
'@@ -4,6 +4,11 @@\n',
1218
, list(unified_diff(txt_a, txt_b,
1219
sequencematcher=psm)))
1221
def test_patience_unified_diff_with_dates(self):
1222
txt_a = ['hello there\n',
1224
'how are you today?\n']
1225
txt_b = ['hello there\n',
1226
'how are you today?\n']
1227
unified_diff = patiencediff.unified_diff
1228
psm = self._PatienceSequenceMatcher
1229
self.assertEquals(['--- a\t2008-08-08\n',
1230
'+++ b\t2008-09-09\n',
1231
'@@ -1,3 +1,2 @@\n',
1234
' how are you today?\n'
1236
, list(unified_diff(txt_a, txt_b,
1237
fromfile='a', tofile='b',
1238
fromfiledate='2008-08-08',
1239
tofiledate='2008-09-09',
1240
sequencematcher=psm)))
1243
class TestPatienceDiffLib_c(TestPatienceDiffLib):
1245
_test_needs_features = [features.compiled_patiencediff_feature]
1248
super(TestPatienceDiffLib_c, self).setUp()
1249
from bzrlib import _patiencediff_c
1250
self._unique_lcs = _patiencediff_c.unique_lcs_c
1251
self._recurse_matches = _patiencediff_c.recurse_matches_c
1252
self._PatienceSequenceMatcher = \
1253
_patiencediff_c.PatienceSequenceMatcher_c
1255
def test_unhashable(self):
1256
"""We should get a proper exception here."""
1257
# We need to be able to hash items in the sequence, lists are
1258
# unhashable, and thus cannot be diffed
1259
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1261
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1262
None, ['valid', []], [])
1263
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1264
None, ['valid'], [[]])
1265
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1266
None, ['valid'], ['valid', []])
1269
class TestPatienceDiffLibFiles(tests.TestCaseInTempDir):
1272
super(TestPatienceDiffLibFiles, self).setUp()
1273
self._PatienceSequenceMatcher = \
1274
_patiencediff_py.PatienceSequenceMatcher_py
1276
def test_patience_unified_diff_files(self):
1277
txt_a = ['hello there\n',
1279
'how are you today?\n']
1280
txt_b = ['hello there\n',
1281
'how are you today?\n']
1282
with open('a1', 'wb') as f: f.writelines(txt_a)
1283
with open('b1', 'wb') as f: f.writelines(txt_b)
1285
unified_diff_files = patiencediff.unified_diff_files
1286
psm = self._PatienceSequenceMatcher
1287
self.assertEquals(['--- a1\n',
1289
'@@ -1,3 +1,2 @@\n',
1292
' how are you today?\n',
1294
, list(unified_diff_files('a1', 'b1',
1295
sequencematcher=psm)))
1297
txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
1298
txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
1299
with open('a2', 'wb') as f: f.writelines(txt_a)
1300
with open('b2', 'wb') as f: f.writelines(txt_b)
1302
# This is the result with LongestCommonSubstring matching
1303
self.assertEquals(['--- a2\n',
1305
'@@ -1,6 +1,11 @@\n',
1317
, list(unified_diff_files('a2', 'b2')))
1319
# And the patience diff
1320
self.assertEquals(['--- a2\n',
1322
'@@ -4,6 +4,11 @@\n',
1335
, list(unified_diff_files('a2', 'b2',
1336
sequencematcher=psm)))
1339
class TestPatienceDiffLibFiles_c(TestPatienceDiffLibFiles):
1341
_test_needs_features = [features.compiled_patiencediff_feature]
1344
super(TestPatienceDiffLibFiles_c, self).setUp()
1345
from bzrlib import _patiencediff_c
1346
self._PatienceSequenceMatcher = \
1347
_patiencediff_c.PatienceSequenceMatcher_c
1350
class TestUsingCompiledIfAvailable(tests.TestCase):
1352
def test_PatienceSequenceMatcher(self):
1353
if features.compiled_patiencediff_feature.available():
1354
from bzrlib._patiencediff_c import PatienceSequenceMatcher_c
1355
self.assertIs(PatienceSequenceMatcher_c,
1356
patiencediff.PatienceSequenceMatcher)
1358
from bzrlib._patiencediff_py import PatienceSequenceMatcher_py
1359
self.assertIs(PatienceSequenceMatcher_py,
1360
patiencediff.PatienceSequenceMatcher)
1362
def test_unique_lcs(self):
1363
if features.compiled_patiencediff_feature.available():
1364
from bzrlib._patiencediff_c import unique_lcs_c
1365
self.assertIs(unique_lcs_c,
1366
patiencediff.unique_lcs)
1368
from bzrlib._patiencediff_py import unique_lcs_py
1369
self.assertIs(unique_lcs_py,
1370
patiencediff.unique_lcs)
1372
def test_recurse_matches(self):
1373
if features.compiled_patiencediff_feature.available():
1374
from bzrlib._patiencediff_c import recurse_matches_c
1375
self.assertIs(recurse_matches_c,
1376
patiencediff.recurse_matches)
1378
from bzrlib._patiencediff_py import recurse_matches_py
1379
self.assertIs(recurse_matches_py,
1380
patiencediff.recurse_matches)
1383
class TestDiffFromTool(tests.TestCaseWithTransport):
1385
def test_from_string(self):
1386
diff_obj = diff.DiffFromTool.from_string('diff', None, None, None)
1387
self.addCleanup(diff_obj.finish)
1388
self.assertEqual(['diff', '@old_path', '@new_path'],
1389
diff_obj.command_template)
1391
def test_from_string_u5(self):
1392
diff_obj = diff.DiffFromTool.from_string('diff "-u 5"',
1394
self.addCleanup(diff_obj.finish)
1395
self.assertEqual(['diff', '-u 5', '@old_path', '@new_path'],
1396
diff_obj.command_template)
1397
self.assertEqual(['diff', '-u 5', 'old-path', 'new-path'],
1398
diff_obj._get_command('old-path', 'new-path'))
1400
def test_from_string_path_with_backslashes(self):
1401
self.requireFeature(features.backslashdir_feature)
1402
tool = 'C:\\Tools\\Diff.exe'
1403
diff_obj = diff.DiffFromTool.from_string(tool, None, None, None)
1404
self.addCleanup(diff_obj.finish)
1405
self.assertEqual(['C:\\Tools\\Diff.exe', '@old_path', '@new_path'],
1406
diff_obj.command_template)
1407
self.assertEqual(['C:\\Tools\\Diff.exe', 'old-path', 'new-path'],
1408
diff_obj._get_command('old-path', 'new-path'))
1410
def test_execute(self):
1412
diff_obj = diff.DiffFromTool(['python', '-c',
1413
'print "@old_path @new_path"'],
1415
self.addCleanup(diff_obj.finish)
1416
diff_obj._execute('old', 'new')
1417
self.assertEqual(output.getvalue().rstrip(), 'old new')
1419
def test_execute_missing(self):
1420
diff_obj = diff.DiffFromTool(['a-tool-which-is-unlikely-to-exist'],
1422
self.addCleanup(diff_obj.finish)
1423
e = self.assertRaises(errors.ExecutableMissing, diff_obj._execute,
1425
self.assertEqual('a-tool-which-is-unlikely-to-exist could not be found'
1426
' on this machine', str(e))
1428
def test_prepare_files_creates_paths_readable_by_windows_tool(self):
1429
self.requireFeature(features.AttribFeature)
1431
tree = self.make_branch_and_tree('tree')
1432
self.build_tree_contents([('tree/file', 'content')])
1433
tree.add('file', 'file-id')
1434
tree.commit('old tree')
1436
self.addCleanup(tree.unlock)
1437
basis_tree = tree.basis_tree()
1438
basis_tree.lock_read()
1439
self.addCleanup(basis_tree.unlock)
1440
diff_obj = diff.DiffFromTool(['python', '-c',
1441
'print "@old_path @new_path"'],
1442
basis_tree, tree, output)
1443
diff_obj._prepare_files('file-id', 'file', 'file')
1444
# The old content should be readonly
1445
self.assertReadableByAttrib(diff_obj._root, 'old\\file',
1447
# The new content should use the tree object, not a 'new' file anymore
1448
self.assertEndsWith(tree.basedir, 'work/tree')
1449
self.assertReadableByAttrib(tree.basedir, 'file', r'work\\tree\\file$')
1451
def assertReadableByAttrib(self, cwd, relpath, regex):
1452
proc = subprocess.Popen(['attrib', relpath],
1453
stdout=subprocess.PIPE,
1455
(result, err) = proc.communicate()
1456
self.assertContainsRe(result.replace('\r\n', '\n'), regex)
1458
def test_prepare_files(self):
1460
tree = self.make_branch_and_tree('tree')
1461
self.build_tree_contents([('tree/oldname', 'oldcontent')])
1462
self.build_tree_contents([('tree/oldname2', 'oldcontent2')])
1463
tree.add('oldname', 'file-id')
1464
tree.add('oldname2', 'file2-id')
1465
# Earliest allowable date on FAT32 filesystems is 1980-01-01
1466
tree.commit('old tree', timestamp=315532800)
1467
tree.rename_one('oldname', 'newname')
1468
tree.rename_one('oldname2', 'newname2')
1469
self.build_tree_contents([('tree/newname', 'newcontent')])
1470
self.build_tree_contents([('tree/newname2', 'newcontent2')])
1471
old_tree = tree.basis_tree()
1472
old_tree.lock_read()
1473
self.addCleanup(old_tree.unlock)
1475
self.addCleanup(tree.unlock)
1476
diff_obj = diff.DiffFromTool(['python', '-c',
1477
'print "@old_path @new_path"'],
1478
old_tree, tree, output)
1479
self.addCleanup(diff_obj.finish)
1480
self.assertContainsRe(diff_obj._root, 'bzr-diff-[^/]*')
1481
old_path, new_path = diff_obj._prepare_files('file-id', 'oldname',
1483
self.assertContainsRe(old_path, 'old/oldname$')
1484
self.assertEqual(315532800, os.stat(old_path).st_mtime)
1485
self.assertContainsRe(new_path, 'tree/newname$')
1486
self.assertFileEqual('oldcontent', old_path)
1487
self.assertFileEqual('newcontent', new_path)
1488
if osutils.host_os_dereferences_symlinks():
1489
self.assertTrue(os.path.samefile('tree/newname', new_path))
1490
# make sure we can create files with the same parent directories
1491
diff_obj._prepare_files('file2-id', 'oldname2', 'newname2')
1494
class TestDiffFromToolEncodedFilename(tests.TestCaseWithTransport):
1496
def test_encodable_filename(self):
1497
# Just checks file path for external diff tool.
1498
# We cannot change CPython's internal encoding used by os.exec*.
1499
diffobj = diff.DiffFromTool(['dummy', '@old_path', '@new_path'],
1501
for _, scenario in EncodingAdapter.encoding_scenarios:
1502
encoding = scenario['encoding']
1503
dirname = scenario['info']['directory']
1504
filename = scenario['info']['filename']
1506
self.overrideAttr(diffobj, '_fenc', lambda: encoding)
1507
relpath = dirname + u'/' + filename
1508
fullpath = diffobj._safe_filename('safe', relpath)
1511
fullpath.encode(encoding).decode(encoding)
1513
self.assert_(fullpath.startswith(diffobj._root + '/safe'))
1515
def test_unencodable_filename(self):
1516
diffobj = diff.DiffFromTool(['dummy', '@old_path', '@new_path'],
1518
for _, scenario in EncodingAdapter.encoding_scenarios:
1519
encoding = scenario['encoding']
1520
dirname = scenario['info']['directory']
1521
filename = scenario['info']['filename']
1523
if encoding == 'iso-8859-1':
1524
encoding = 'iso-8859-2'
1526
encoding = 'iso-8859-1'
1528
self.overrideAttr(diffobj, '_fenc', lambda: encoding)
1529
relpath = dirname + u'/' + filename
1530
fullpath = diffobj._safe_filename('safe', relpath)
1533
fullpath.encode(encoding).decode(encoding)
1535
self.assert_(fullpath.startswith(diffobj._root + '/safe'))
1538
class TestGetTreesAndBranchesToDiffLocked(tests.TestCaseWithTransport):
1540
def call_gtabtd(self, path_list, revision_specs, old_url, new_url):
1541
"""Call get_trees_and_branches_to_diff_locked."""
1542
return diff.get_trees_and_branches_to_diff_locked(
1543
path_list, revision_specs, old_url, new_url, self.addCleanup)
1545
def test_basic(self):
1546
tree = self.make_branch_and_tree('tree')
1547
(old_tree, new_tree,
1548
old_branch, new_branch,
1549
specific_files, extra_trees) = self.call_gtabtd(
1550
['tree'], None, None, None)
1552
self.assertIsInstance(old_tree, revisiontree.RevisionTree)
1553
self.assertEqual(_mod_revision.NULL_REVISION,
1554
old_tree.get_revision_id())
1555
self.assertEqual(tree.basedir, new_tree.basedir)
1556
self.assertEqual(tree.branch.base, old_branch.base)
1557
self.assertEqual(tree.branch.base, new_branch.base)
1558
self.assertIs(None, specific_files)
1559
self.assertIs(None, extra_trees)
1561
def test_with_rev_specs(self):
1562
tree = self.make_branch_and_tree('tree')
1563
self.build_tree_contents([('tree/file', 'oldcontent')])
1564
tree.add('file', 'file-id')
1565
tree.commit('old tree', timestamp=0, rev_id="old-id")
1566
self.build_tree_contents([('tree/file', 'newcontent')])
1567
tree.commit('new tree', timestamp=0, rev_id="new-id")
1569
revisions = [revisionspec.RevisionSpec.from_string('1'),
1570
revisionspec.RevisionSpec.from_string('2')]
1571
(old_tree, new_tree,
1572
old_branch, new_branch,
1573
specific_files, extra_trees) = self.call_gtabtd(
1574
['tree'], revisions, None, None)
1576
self.assertIsInstance(old_tree, revisiontree.RevisionTree)
1577
self.assertEqual("old-id", old_tree.get_revision_id())
1578
self.assertIsInstance(new_tree, revisiontree.RevisionTree)
1579
self.assertEqual("new-id", new_tree.get_revision_id())
1580
self.assertEqual(tree.branch.base, old_branch.base)
1581
self.assertEqual(tree.branch.base, new_branch.base)
1582
self.assertIs(None, specific_files)
1583
self.assertEqual(tree.basedir, extra_trees[0].basedir)