47
104
self.assert_('@@' in lines[2][2:])
48
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_internal_diff_default(self):
147
# Default internal diff encoding is utf8
149
diff.internal_diff(u'old_\xb5', ['old_text\n'],
150
u'new_\xe5', ['new_text\n'], output)
151
lines = output.getvalue().splitlines(True)
152
self.check_patch(lines)
153
self.assertEquals(['--- old_\xc2\xb5\n',
154
'+++ new_\xc3\xa5\n',
162
def test_internal_diff_utf8(self):
164
diff.internal_diff(u'old_\xb5', ['old_text\n'],
165
u'new_\xe5', ['new_text\n'], output,
166
path_encoding='utf8')
167
lines = output.getvalue().splitlines(True)
168
self.check_patch(lines)
169
self.assertEquals(['--- old_\xc2\xb5\n',
170
'+++ new_\xc3\xa5\n',
178
def test_internal_diff_iso_8859_1(self):
180
diff.internal_diff(u'old_\xb5', ['old_text\n'],
181
u'new_\xe5', ['new_text\n'], output,
182
path_encoding='iso-8859-1')
183
lines = output.getvalue().splitlines(True)
184
self.check_patch(lines)
185
self.assertEquals(['--- old_\xb5\n',
194
def test_internal_diff_no_content(self):
196
diff.internal_diff(u'old', [], u'new', [], output)
197
self.assertEqual('', output.getvalue())
199
def test_internal_diff_no_changes(self):
201
diff.internal_diff(u'old', ['text\n', 'contents\n'],
202
u'new', ['text\n', 'contents\n'],
204
self.assertEqual('', output.getvalue())
206
def test_internal_diff_returns_bytes(self):
208
output = StringIO.StringIO()
209
diff.internal_diff(u'old_\xb5', ['old_text\n'],
210
u'new_\xe5', ['new_text\n'], output)
211
self.assertIsInstance(output.getvalue(), str,
212
'internal_diff should return bytestrings')
214
def test_internal_diff_default_context(self):
216
diff.internal_diff('old', ['same_text\n','same_text\n','same_text\n',
217
'same_text\n','same_text\n','old_text\n'],
218
'new', ['same_text\n','same_text\n','same_text\n',
219
'same_text\n','same_text\n','new_text\n'], output)
220
lines = output.getvalue().splitlines(True)
221
self.check_patch(lines)
222
self.assertEquals(['--- old\n',
234
def test_internal_diff_no_context(self):
236
diff.internal_diff('old', ['same_text\n','same_text\n','same_text\n',
237
'same_text\n','same_text\n','old_text\n'],
238
'new', ['same_text\n','same_text\n','same_text\n',
239
'same_text\n','same_text\n','new_text\n'], output,
241
lines = output.getvalue().splitlines(True)
242
self.check_patch(lines)
243
self.assertEquals(['--- old\n',
252
def test_internal_diff_more_context(self):
254
diff.internal_diff('old', ['same_text\n','same_text\n','same_text\n',
255
'same_text\n','same_text\n','old_text\n'],
256
'new', ['same_text\n','same_text\n','same_text\n',
257
'same_text\n','same_text\n','new_text\n'], output,
259
lines = output.getvalue().splitlines(True)
260
self.check_patch(lines)
261
self.assertEquals(['--- old\n',
278
class TestDiffFiles(tests.TestCaseInTempDir):
280
def test_external_diff_binary(self):
281
"""The output when using external diff should use diff's i18n error"""
282
# Make sure external_diff doesn't fail in the current LANG
283
lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
285
cmd = ['diff', '-u', '--binary', 'old', 'new']
286
with open('old', 'wb') as f: f.write('\x00foobar\n')
287
with open('new', 'wb') as f: f.write('foo\x00bar\n')
288
pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
289
stdin=subprocess.PIPE)
290
out, err = pipe.communicate()
291
# Diff returns '2' on Binary files.
292
self.assertEqual(2, pipe.returncode)
293
# We should output whatever diff tells us, plus a trailing newline
294
self.assertEqual(out.splitlines(True) + ['\n'], lines)
297
def get_diff_as_string(tree1, tree2, specific_files=None, working_tree=None):
299
if working_tree is not None:
300
extra_trees = (working_tree,)
303
diff.show_diff_trees(tree1, tree2, output,
304
specific_files=specific_files,
305
extra_trees=extra_trees, old_label='old/',
307
return output.getvalue()
310
class TestDiffDates(tests.TestCaseWithTransport):
313
super(TestDiffDates, self).setUp()
314
self.wt = self.make_branch_and_tree('.')
315
self.b = self.wt.branch
316
self.build_tree_contents([
317
('file1', 'file1 contents at rev 1\n'),
318
('file2', 'file2 contents at rev 1\n')
320
self.wt.add(['file1', 'file2'])
322
message='Revision 1',
323
timestamp=1143849600, # 2006-04-01 00:00:00 UTC
326
self.build_tree_contents([('file1', 'file1 contents at rev 2\n')])
328
message='Revision 2',
329
timestamp=1143936000, # 2006-04-02 00:00:00 UTC
332
self.build_tree_contents([('file2', 'file2 contents at rev 3\n')])
334
message='Revision 3',
335
timestamp=1144022400, # 2006-04-03 00:00:00 UTC
338
self.wt.remove(['file2'])
340
message='Revision 4',
341
timestamp=1144108800, # 2006-04-04 00:00:00 UTC
344
self.build_tree_contents([
345
('file1', 'file1 contents in working tree\n')
347
# set the date stamps for files in the working tree to known values
348
os.utime('file1', (1144195200, 1144195200)) # 2006-04-05 00:00:00 UTC
350
def test_diff_rev_tree_working_tree(self):
351
output = get_diff_as_string(self.wt.basis_tree(), self.wt)
352
# note that the date for old/file1 is from rev 2 rather than from
353
# the basis revision (rev 4)
354
self.assertEqualDiff(output, '''\
355
=== modified file 'file1'
356
--- old/file1\t2006-04-02 00:00:00 +0000
357
+++ new/file1\t2006-04-05 00:00:00 +0000
359
-file1 contents at rev 2
360
+file1 contents in working tree
364
def test_diff_rev_tree_rev_tree(self):
365
tree1 = self.b.repository.revision_tree('rev-2')
366
tree2 = self.b.repository.revision_tree('rev-3')
367
output = get_diff_as_string(tree1, tree2)
368
self.assertEqualDiff(output, '''\
369
=== modified file 'file2'
370
--- old/file2\t2006-04-01 00:00:00 +0000
371
+++ new/file2\t2006-04-03 00:00:00 +0000
373
-file2 contents at rev 1
374
+file2 contents at rev 3
378
def test_diff_add_files(self):
379
tree1 = self.b.repository.revision_tree(_mod_revision.NULL_REVISION)
380
tree2 = self.b.repository.revision_tree('rev-1')
381
output = get_diff_as_string(tree1, tree2)
382
# the files have the epoch time stamp for the tree in which
384
self.assertEqualDiff(output, '''\
385
=== added file 'file1'
386
--- old/file1\t1970-01-01 00:00:00 +0000
387
+++ new/file1\t2006-04-01 00:00:00 +0000
389
+file1 contents at rev 1
391
=== added file 'file2'
392
--- old/file2\t1970-01-01 00:00:00 +0000
393
+++ new/file2\t2006-04-01 00:00:00 +0000
395
+file2 contents at rev 1
399
def test_diff_remove_files(self):
400
tree1 = self.b.repository.revision_tree('rev-3')
401
tree2 = self.b.repository.revision_tree('rev-4')
402
output = get_diff_as_string(tree1, tree2)
403
# the file has the epoch time stamp for the tree in which
405
self.assertEqualDiff(output, '''\
406
=== removed file 'file2'
407
--- old/file2\t2006-04-03 00:00:00 +0000
408
+++ new/file2\t1970-01-01 00:00:00 +0000
410
-file2 contents at rev 3
414
def test_show_diff_specified(self):
415
"""A working tree filename can be used to identify a file"""
416
self.wt.rename_one('file1', 'file1b')
417
old_tree = self.b.repository.revision_tree('rev-1')
418
new_tree = self.b.repository.revision_tree('rev-4')
419
out = get_diff_as_string(old_tree, new_tree, specific_files=['file1b'],
420
working_tree=self.wt)
421
self.assertContainsRe(out, 'file1\t')
423
def test_recursive_diff(self):
424
"""Children of directories are matched"""
427
self.wt.add(['dir1', 'dir2'])
428
self.wt.rename_one('file1', 'dir1/file1')
429
old_tree = self.b.repository.revision_tree('rev-1')
430
new_tree = self.b.repository.revision_tree('rev-4')
431
out = get_diff_as_string(old_tree, new_tree, specific_files=['dir1'],
432
working_tree=self.wt)
433
self.assertContainsRe(out, 'file1\t')
434
out = get_diff_as_string(old_tree, new_tree, specific_files=['dir2'],
435
working_tree=self.wt)
436
self.assertNotContainsRe(out, 'file1\t')
439
class TestShowDiffTrees(tests.TestCaseWithTransport):
440
"""Direct tests for show_diff_trees"""
442
def test_modified_file(self):
443
"""Test when a file is modified."""
444
tree = self.make_branch_and_tree('tree')
445
self.build_tree_contents([('tree/file', 'contents\n')])
446
tree.add(['file'], ['file-id'])
447
tree.commit('one', rev_id='rev-1')
449
self.build_tree_contents([('tree/file', 'new contents\n')])
450
d = get_diff_as_string(tree.basis_tree(), tree)
451
self.assertContainsRe(d, "=== modified file 'file'\n")
452
self.assertContainsRe(d, '--- old/file\t')
453
self.assertContainsRe(d, '\\+\\+\\+ new/file\t')
454
self.assertContainsRe(d, '-contents\n'
457
def test_modified_file_in_renamed_dir(self):
458
"""Test when a file is modified in a renamed directory."""
459
tree = self.make_branch_and_tree('tree')
460
self.build_tree(['tree/dir/'])
461
self.build_tree_contents([('tree/dir/file', 'contents\n')])
462
tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
463
tree.commit('one', rev_id='rev-1')
465
tree.rename_one('dir', 'other')
466
self.build_tree_contents([('tree/other/file', 'new contents\n')])
467
d = get_diff_as_string(tree.basis_tree(), tree)
468
self.assertContainsRe(d, "=== renamed directory 'dir' => 'other'\n")
469
self.assertContainsRe(d, "=== modified file 'other/file'\n")
470
# XXX: This is technically incorrect, because it used to be at another
471
# location. What to do?
472
self.assertContainsRe(d, '--- old/dir/file\t')
473
self.assertContainsRe(d, '\\+\\+\\+ new/other/file\t')
474
self.assertContainsRe(d, '-contents\n'
477
def test_renamed_directory(self):
478
"""Test when only a directory is only renamed."""
479
tree = self.make_branch_and_tree('tree')
480
self.build_tree(['tree/dir/'])
481
self.build_tree_contents([('tree/dir/file', 'contents\n')])
482
tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
483
tree.commit('one', rev_id='rev-1')
485
tree.rename_one('dir', 'newdir')
486
d = get_diff_as_string(tree.basis_tree(), tree)
487
# Renaming a directory should be a single "you renamed this dir" even
488
# when there are files inside.
489
self.assertEqual(d, "=== renamed directory 'dir' => 'newdir'\n")
491
def test_renamed_file(self):
492
"""Test when a file is only renamed."""
493
tree = self.make_branch_and_tree('tree')
494
self.build_tree_contents([('tree/file', 'contents\n')])
495
tree.add(['file'], ['file-id'])
496
tree.commit('one', rev_id='rev-1')
498
tree.rename_one('file', 'newname')
499
d = get_diff_as_string(tree.basis_tree(), tree)
500
self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
501
# We shouldn't have a --- or +++ line, because there is no content
503
self.assertNotContainsRe(d, '---')
505
def test_renamed_and_modified_file(self):
506
"""Test when a file is only renamed."""
507
tree = self.make_branch_and_tree('tree')
508
self.build_tree_contents([('tree/file', 'contents\n')])
509
tree.add(['file'], ['file-id'])
510
tree.commit('one', rev_id='rev-1')
512
tree.rename_one('file', 'newname')
513
self.build_tree_contents([('tree/newname', 'new contents\n')])
514
d = get_diff_as_string(tree.basis_tree(), tree)
515
self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
516
self.assertContainsRe(d, '--- old/file\t')
517
self.assertContainsRe(d, '\\+\\+\\+ new/newname\t')
518
self.assertContainsRe(d, '-contents\n'
522
def test_internal_diff_exec_property(self):
523
tree = self.make_branch_and_tree('tree')
525
tt = transform.TreeTransform(tree)
526
tt.new_file('a', tt.root, 'contents\n', 'a-id', True)
527
tt.new_file('b', tt.root, 'contents\n', 'b-id', False)
528
tt.new_file('c', tt.root, 'contents\n', 'c-id', True)
529
tt.new_file('d', tt.root, 'contents\n', 'd-id', False)
530
tt.new_file('e', tt.root, 'contents\n', 'control-e-id', True)
531
tt.new_file('f', tt.root, 'contents\n', 'control-f-id', False)
533
tree.commit('one', rev_id='rev-1')
535
tt = transform.TreeTransform(tree)
536
tt.set_executability(False, tt.trans_id_file_id('a-id'))
537
tt.set_executability(True, tt.trans_id_file_id('b-id'))
538
tt.set_executability(False, tt.trans_id_file_id('c-id'))
539
tt.set_executability(True, tt.trans_id_file_id('d-id'))
541
tree.rename_one('c', 'new-c')
542
tree.rename_one('d', 'new-d')
544
d = get_diff_as_string(tree.basis_tree(), tree)
546
self.assertContainsRe(d, r"file 'a'.*\(properties changed:"
548
self.assertContainsRe(d, r"file 'b'.*\(properties changed:"
550
self.assertContainsRe(d, r"file 'c'.*\(properties changed:"
552
self.assertContainsRe(d, r"file 'd'.*\(properties changed:"
554
self.assertNotContainsRe(d, r"file 'e'")
555
self.assertNotContainsRe(d, r"file 'f'")
557
def test_binary_unicode_filenames(self):
558
"""Test that contents of files are *not* encoded in UTF-8 when there
559
is a binary file in the diff.
561
# See https://bugs.launchpad.net/bugs/110092.
562
self.requireFeature(features.UnicodeFilenameFeature)
564
# This bug isn't triggered with cStringIO.
565
from StringIO import StringIO
566
tree = self.make_branch_and_tree('tree')
567
alpha, omega = u'\u03b1', u'\u03c9'
568
alpha_utf8, omega_utf8 = alpha.encode('utf8'), omega.encode('utf8')
569
self.build_tree_contents(
570
[('tree/' + alpha, chr(0)),
572
('The %s and the %s\n' % (alpha_utf8, omega_utf8)))])
573
tree.add([alpha], ['file-id'])
574
tree.add([omega], ['file-id-2'])
575
diff_content = StringIO()
576
diff.show_diff_trees(tree.basis_tree(), tree, diff_content)
577
d = diff_content.getvalue()
578
self.assertContainsRe(d, r"=== added file '%s'" % alpha_utf8)
579
self.assertContainsRe(d, "Binary files a/%s.*and b/%s.* differ\n"
580
% (alpha_utf8, alpha_utf8))
581
self.assertContainsRe(d, r"=== added file '%s'" % omega_utf8)
582
self.assertContainsRe(d, r"--- a/%s" % (omega_utf8,))
583
self.assertContainsRe(d, r"\+\+\+ b/%s" % (omega_utf8,))
585
def test_unicode_filename(self):
586
"""Test when the filename are unicode."""
587
self.requireFeature(features.UnicodeFilenameFeature)
589
alpha, omega = u'\u03b1', u'\u03c9'
590
autf8, outf8 = alpha.encode('utf8'), omega.encode('utf8')
592
tree = self.make_branch_and_tree('tree')
593
self.build_tree_contents([('tree/ren_'+alpha, 'contents\n')])
594
tree.add(['ren_'+alpha], ['file-id-2'])
595
self.build_tree_contents([('tree/del_'+alpha, 'contents\n')])
596
tree.add(['del_'+alpha], ['file-id-3'])
597
self.build_tree_contents([('tree/mod_'+alpha, 'contents\n')])
598
tree.add(['mod_'+alpha], ['file-id-4'])
600
tree.commit('one', rev_id='rev-1')
602
tree.rename_one('ren_'+alpha, 'ren_'+omega)
603
tree.remove('del_'+alpha)
604
self.build_tree_contents([('tree/add_'+alpha, 'contents\n')])
605
tree.add(['add_'+alpha], ['file-id'])
606
self.build_tree_contents([('tree/mod_'+alpha, 'contents_mod\n')])
608
d = get_diff_as_string(tree.basis_tree(), tree)
609
self.assertContainsRe(d,
610
"=== renamed file 'ren_%s' => 'ren_%s'\n"%(autf8, outf8))
611
self.assertContainsRe(d, "=== added file 'add_%s'"%autf8)
612
self.assertContainsRe(d, "=== modified file 'mod_%s'"%autf8)
613
self.assertContainsRe(d, "=== removed file 'del_%s'"%autf8)
615
def test_unicode_filename_path_encoding(self):
616
"""Test for bug #382699: unicode filenames on Windows should be shown
619
self.requireFeature(features.UnicodeFilenameFeature)
620
# The word 'test' in Russian
621
_russian_test = u'\u0422\u0435\u0441\u0442'
622
directory = _russian_test + u'/'
623
test_txt = _russian_test + u'.txt'
624
u1234 = u'\u1234.txt'
626
tree = self.make_branch_and_tree('.')
627
self.build_tree_contents([
632
tree.add([test_txt, u1234, directory])
635
diff.show_diff_trees(tree.basis_tree(), tree, sio,
636
path_encoding='cp1251')
638
output = subst_dates(sio.getvalue())
640
=== added directory '%(directory)s'
641
=== added file '%(test_txt)s'
642
--- a/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
643
+++ b/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
647
=== added file '?.txt'
648
--- a/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
649
+++ b/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
653
''' % {'directory': _russian_test.encode('cp1251'),
654
'test_txt': test_txt.encode('cp1251'),
656
self.assertEqualDiff(output, shouldbe)
659
class DiffWasIs(diff.DiffPath):
661
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
662
self.to_file.write('was: ')
663
self.to_file.write(self.old_tree.get_file(file_id).read())
664
self.to_file.write('is: ')
665
self.to_file.write(self.new_tree.get_file(file_id).read())
669
class TestDiffTree(tests.TestCaseWithTransport):
672
super(TestDiffTree, self).setUp()
673
self.old_tree = self.make_branch_and_tree('old-tree')
674
self.old_tree.lock_write()
675
self.addCleanup(self.old_tree.unlock)
676
self.new_tree = self.make_branch_and_tree('new-tree')
677
self.new_tree.lock_write()
678
self.addCleanup(self.new_tree.unlock)
679
self.differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
681
def test_diff_text(self):
682
self.build_tree_contents([('old-tree/olddir/',),
683
('old-tree/olddir/oldfile', 'old\n')])
684
self.old_tree.add('olddir')
685
self.old_tree.add('olddir/oldfile', 'file-id')
686
self.build_tree_contents([('new-tree/newdir/',),
687
('new-tree/newdir/newfile', 'new\n')])
688
self.new_tree.add('newdir')
689
self.new_tree.add('newdir/newfile', 'file-id')
690
differ = diff.DiffText(self.old_tree, self.new_tree, StringIO())
691
differ.diff_text('file-id', None, 'old label', 'new label')
693
'--- old label\n+++ new label\n@@ -1,1 +0,0 @@\n-old\n\n',
694
differ.to_file.getvalue())
695
differ.to_file.seek(0)
696
differ.diff_text(None, 'file-id', 'old label', 'new label')
698
'--- old label\n+++ new label\n@@ -0,0 +1,1 @@\n+new\n\n',
699
differ.to_file.getvalue())
700
differ.to_file.seek(0)
701
differ.diff_text('file-id', 'file-id', 'old label', 'new label')
703
'--- old label\n+++ new label\n@@ -1,1 +1,1 @@\n-old\n+new\n\n',
704
differ.to_file.getvalue())
706
def test_diff_deletion(self):
707
self.build_tree_contents([('old-tree/file', 'contents'),
708
('new-tree/file', 'contents')])
709
self.old_tree.add('file', 'file-id')
710
self.new_tree.add('file', 'file-id')
711
os.unlink('new-tree/file')
712
self.differ.show_diff(None)
713
self.assertContainsRe(self.differ.to_file.getvalue(), '-contents')
715
def test_diff_creation(self):
716
self.build_tree_contents([('old-tree/file', 'contents'),
717
('new-tree/file', 'contents')])
718
self.old_tree.add('file', 'file-id')
719
self.new_tree.add('file', 'file-id')
720
os.unlink('old-tree/file')
721
self.differ.show_diff(None)
722
self.assertContainsRe(self.differ.to_file.getvalue(), '\+contents')
724
def test_diff_symlink(self):
725
differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
726
differ.diff_symlink('old target', None)
727
self.assertEqual("=== target was 'old target'\n",
728
differ.to_file.getvalue())
730
differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
731
differ.diff_symlink(None, 'new target')
732
self.assertEqual("=== target is 'new target'\n",
733
differ.to_file.getvalue())
735
differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
736
differ.diff_symlink('old target', 'new target')
737
self.assertEqual("=== target changed 'old target' => 'new target'\n",
738
differ.to_file.getvalue())
741
self.build_tree_contents([('old-tree/olddir/',),
742
('old-tree/olddir/oldfile', 'old\n')])
743
self.old_tree.add('olddir')
744
self.old_tree.add('olddir/oldfile', 'file-id')
745
self.build_tree_contents([('new-tree/newdir/',),
746
('new-tree/newdir/newfile', 'new\n')])
747
self.new_tree.add('newdir')
748
self.new_tree.add('newdir/newfile', 'file-id')
749
self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
750
self.assertContainsRe(
751
self.differ.to_file.getvalue(),
752
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
753
' \@\@\n-old\n\+new\n\n')
755
def test_diff_kind_change(self):
756
self.requireFeature(features.SymlinkFeature)
757
self.build_tree_contents([('old-tree/olddir/',),
758
('old-tree/olddir/oldfile', 'old\n')])
759
self.old_tree.add('olddir')
760
self.old_tree.add('olddir/oldfile', 'file-id')
761
self.build_tree(['new-tree/newdir/'])
762
os.symlink('new', 'new-tree/newdir/newfile')
763
self.new_tree.add('newdir')
764
self.new_tree.add('newdir/newfile', 'file-id')
765
self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
766
self.assertContainsRe(
767
self.differ.to_file.getvalue(),
768
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+0,0'
770
self.assertContainsRe(self.differ.to_file.getvalue(),
771
"=== target is u'new'\n")
773
def test_diff_directory(self):
774
self.build_tree(['new-tree/new-dir/'])
775
self.new_tree.add('new-dir', 'new-dir-id')
776
self.differ.diff('new-dir-id', None, 'new-dir')
777
self.assertEqual(self.differ.to_file.getvalue(), '')
779
def create_old_new(self):
780
self.build_tree_contents([('old-tree/olddir/',),
781
('old-tree/olddir/oldfile', 'old\n')])
782
self.old_tree.add('olddir')
783
self.old_tree.add('olddir/oldfile', 'file-id')
784
self.build_tree_contents([('new-tree/newdir/',),
785
('new-tree/newdir/newfile', 'new\n')])
786
self.new_tree.add('newdir')
787
self.new_tree.add('newdir/newfile', 'file-id')
789
def test_register_diff(self):
790
self.create_old_new()
791
old_diff_factories = diff.DiffTree.diff_factories
792
diff.DiffTree.diff_factories=old_diff_factories[:]
793
diff.DiffTree.diff_factories.insert(0, DiffWasIs.from_diff_tree)
795
differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
797
diff.DiffTree.diff_factories = old_diff_factories
798
differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
799
self.assertNotContainsRe(
800
differ.to_file.getvalue(),
801
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
802
' \@\@\n-old\n\+new\n\n')
803
self.assertContainsRe(differ.to_file.getvalue(),
804
'was: old\nis: new\n')
806
def test_extra_factories(self):
807
self.create_old_new()
808
differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO(),
809
extra_factories=[DiffWasIs.from_diff_tree])
810
differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
811
self.assertNotContainsRe(
812
differ.to_file.getvalue(),
813
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
814
' \@\@\n-old\n\+new\n\n')
815
self.assertContainsRe(differ.to_file.getvalue(),
816
'was: old\nis: new\n')
818
def test_alphabetical_order(self):
819
self.build_tree(['new-tree/a-file'])
820
self.new_tree.add('a-file')
821
self.build_tree(['old-tree/b-file'])
822
self.old_tree.add('b-file')
823
self.differ.show_diff(None)
824
self.assertContainsRe(self.differ.to_file.getvalue(),
825
'.*a-file(.|\n)*b-file')
828
class TestPatienceDiffLib(tests.TestCase):
831
super(TestPatienceDiffLib, self).setUp()
832
self._unique_lcs = _patiencediff_py.unique_lcs_py
833
self._recurse_matches = _patiencediff_py.recurse_matches_py
834
self._PatienceSequenceMatcher = \
835
_patiencediff_py.PatienceSequenceMatcher_py
837
def test_diff_unicode_string(self):
838
a = ''.join([unichr(i) for i in range(4000, 4500, 3)])
839
b = ''.join([unichr(i) for i in range(4300, 4800, 2)])
840
sm = self._PatienceSequenceMatcher(None, a, b)
841
mb = sm.get_matching_blocks()
842
self.assertEquals(35, len(mb))
844
def test_unique_lcs(self):
845
unique_lcs = self._unique_lcs
846
self.assertEquals(unique_lcs('', ''), [])
847
self.assertEquals(unique_lcs('', 'a'), [])
848
self.assertEquals(unique_lcs('a', ''), [])
849
self.assertEquals(unique_lcs('a', 'a'), [(0,0)])
850
self.assertEquals(unique_lcs('a', 'b'), [])
851
self.assertEquals(unique_lcs('ab', 'ab'), [(0,0), (1,1)])
852
self.assertEquals(unique_lcs('abcde', 'cdeab'), [(2,0), (3,1), (4,2)])
853
self.assertEquals(unique_lcs('cdeab', 'abcde'), [(0,2), (1,3), (2,4)])
854
self.assertEquals(unique_lcs('abXde', 'abYde'), [(0,0), (1,1),
856
self.assertEquals(unique_lcs('acbac', 'abc'), [(2,1)])
858
def test_recurse_matches(self):
859
def test_one(a, b, matches):
861
self._recurse_matches(
862
a, b, 0, 0, len(a), len(b), test_matches, 10)
863
self.assertEquals(test_matches, matches)
865
test_one(['a', '', 'b', '', 'c'], ['a', 'a', 'b', 'c', 'c'],
866
[(0, 0), (2, 2), (4, 4)])
867
test_one(['a', 'c', 'b', 'a', 'c'], ['a', 'b', 'c'],
868
[(0, 0), (2, 1), (4, 2)])
869
# Even though 'bc' is not unique globally, and is surrounded by
870
# non-matching lines, we should still match, because they are locally
872
test_one('abcdbce', 'afbcgdbce', [(0,0), (1, 2), (2, 3), (3, 5),
873
(4, 6), (5, 7), (6, 8)])
875
# recurse_matches doesn't match non-unique
876
# lines surrounded by bogus text.
877
# The update has been done in patiencediff.SequenceMatcher instead
879
# This is what it could be
880
#test_one('aBccDe', 'abccde', [(0,0), (2,2), (3,3), (5,5)])
882
# This is what it currently gives:
883
test_one('aBccDe', 'abccde', [(0,0), (5,5)])
885
def assertDiffBlocks(self, a, b, expected_blocks):
886
"""Check that the sequence matcher returns the correct blocks.
888
:param a: A sequence to match
889
:param b: Another sequence to match
890
:param expected_blocks: The expected output, not including the final
891
matching block (len(a), len(b), 0)
893
matcher = self._PatienceSequenceMatcher(None, a, b)
894
blocks = matcher.get_matching_blocks()
896
self.assertEqual((len(a), len(b), 0), last)
897
self.assertEqual(expected_blocks, blocks)
899
def test_matching_blocks(self):
900
# Some basic matching tests
901
self.assertDiffBlocks('', '', [])
902
self.assertDiffBlocks([], [], [])
903
self.assertDiffBlocks('abc', '', [])
904
self.assertDiffBlocks('', 'abc', [])
905
self.assertDiffBlocks('abcd', 'abcd', [(0, 0, 4)])
906
self.assertDiffBlocks('abcd', 'abce', [(0, 0, 3)])
907
self.assertDiffBlocks('eabc', 'abce', [(1, 0, 3)])
908
self.assertDiffBlocks('eabce', 'abce', [(1, 0, 4)])
909
self.assertDiffBlocks('abcde', 'abXde', [(0, 0, 2), (3, 3, 2)])
910
self.assertDiffBlocks('abcde', 'abXYZde', [(0, 0, 2), (3, 5, 2)])
911
self.assertDiffBlocks('abde', 'abXYZde', [(0, 0, 2), (2, 5, 2)])
912
# This may check too much, but it checks to see that
913
# a copied block stays attached to the previous section,
915
# difflib would tend to grab the trailing longest match
916
# which would make the diff not look right
917
self.assertDiffBlocks('abcdefghijklmnop', 'abcdefxydefghijklmnop',
918
[(0, 0, 6), (6, 11, 10)])
920
# make sure it supports passing in lists
921
self.assertDiffBlocks(
924
'how are you today?\n'],
926
'how are you today?\n'],
927
[(0, 0, 1), (2, 1, 1)])
929
# non unique lines surrounded by non-matching lines
931
self.assertDiffBlocks('aBccDe', 'abccde', [(0,0,1), (5,5,1)])
933
# But they only need to be locally unique
934
self.assertDiffBlocks('aBcDec', 'abcdec', [(0,0,1), (2,2,1), (4,4,2)])
936
# non unique blocks won't be matched
937
self.assertDiffBlocks('aBcdEcdFg', 'abcdecdfg', [(0,0,1), (8,8,1)])
939
# but locally unique ones will
940
self.assertDiffBlocks('aBcdEeXcdFg', 'abcdecdfg', [(0,0,1), (2,2,2),
941
(5,4,1), (7,5,2), (10,8,1)])
943
self.assertDiffBlocks('abbabbXd', 'cabbabxd', [(7,7,1)])
944
self.assertDiffBlocks('abbabbbb', 'cabbabbc', [])
945
self.assertDiffBlocks('bbbbbbbb', 'cbbbbbbc', [])
947
def test_matching_blocks_tuples(self):
948
# Some basic matching tests
949
self.assertDiffBlocks([], [], [])
950
self.assertDiffBlocks([('a',), ('b',), ('c,')], [], [])
951
self.assertDiffBlocks([], [('a',), ('b',), ('c,')], [])
952
self.assertDiffBlocks([('a',), ('b',), ('c,')],
953
[('a',), ('b',), ('c,')],
955
self.assertDiffBlocks([('a',), ('b',), ('c,')],
956
[('a',), ('b',), ('d,')],
958
self.assertDiffBlocks([('d',), ('b',), ('c,')],
959
[('a',), ('b',), ('c,')],
961
self.assertDiffBlocks([('d',), ('a',), ('b',), ('c,')],
962
[('a',), ('b',), ('c,')],
964
self.assertDiffBlocks([('a', 'b'), ('c', 'd'), ('e', 'f')],
965
[('a', 'b'), ('c', 'X'), ('e', 'f')],
966
[(0, 0, 1), (2, 2, 1)])
967
self.assertDiffBlocks([('a', 'b'), ('c', 'd'), ('e', 'f')],
968
[('a', 'b'), ('c', 'dX'), ('e', 'f')],
969
[(0, 0, 1), (2, 2, 1)])
971
def test_opcodes(self):
972
def chk_ops(a, b, expected_codes):
973
s = self._PatienceSequenceMatcher(None, a, b)
974
self.assertEquals(expected_codes, s.get_opcodes())
978
chk_ops('abc', '', [('delete', 0,3, 0,0)])
979
chk_ops('', 'abc', [('insert', 0,0, 0,3)])
980
chk_ops('abcd', 'abcd', [('equal', 0,4, 0,4)])
981
chk_ops('abcd', 'abce', [('equal', 0,3, 0,3),
982
('replace', 3,4, 3,4)
984
chk_ops('eabc', 'abce', [('delete', 0,1, 0,0),
988
chk_ops('eabce', 'abce', [('delete', 0,1, 0,0),
991
chk_ops('abcde', 'abXde', [('equal', 0,2, 0,2),
992
('replace', 2,3, 2,3),
995
chk_ops('abcde', 'abXYZde', [('equal', 0,2, 0,2),
996
('replace', 2,3, 2,5),
999
chk_ops('abde', 'abXYZde', [('equal', 0,2, 0,2),
1000
('insert', 2,2, 2,5),
1003
chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
1004
[('equal', 0,6, 0,6),
1005
('insert', 6,6, 6,11),
1006
('equal', 6,16, 11,21)
1011
, 'how are you today?\n'],
1013
, 'how are you today?\n'],
1014
[('equal', 0,1, 0,1),
1015
('delete', 1,2, 1,1),
1016
('equal', 2,3, 1,2),
1018
chk_ops('aBccDe', 'abccde',
1019
[('equal', 0,1, 0,1),
1020
('replace', 1,5, 1,5),
1021
('equal', 5,6, 5,6),
1023
chk_ops('aBcDec', 'abcdec',
1024
[('equal', 0,1, 0,1),
1025
('replace', 1,2, 1,2),
1026
('equal', 2,3, 2,3),
1027
('replace', 3,4, 3,4),
1028
('equal', 4,6, 4,6),
1030
chk_ops('aBcdEcdFg', 'abcdecdfg',
1031
[('equal', 0,1, 0,1),
1032
('replace', 1,8, 1,8),
1035
chk_ops('aBcdEeXcdFg', 'abcdecdfg',
1036
[('equal', 0,1, 0,1),
1037
('replace', 1,2, 1,2),
1038
('equal', 2,4, 2,4),
1039
('delete', 4,5, 4,4),
1040
('equal', 5,6, 4,5),
1041
('delete', 6,7, 5,5),
1042
('equal', 7,9, 5,7),
1043
('replace', 9,10, 7,8),
1044
('equal', 10,11, 8,9)
1047
def test_grouped_opcodes(self):
1048
def chk_ops(a, b, expected_codes, n=3):
1049
s = self._PatienceSequenceMatcher(None, a, b)
1050
self.assertEquals(expected_codes, list(s.get_grouped_opcodes(n)))
1054
chk_ops('abc', '', [[('delete', 0,3, 0,0)]])
1055
chk_ops('', 'abc', [[('insert', 0,0, 0,3)]])
1056
chk_ops('abcd', 'abcd', [])
1057
chk_ops('abcd', 'abce', [[('equal', 0,3, 0,3),
1058
('replace', 3,4, 3,4)
1060
chk_ops('eabc', 'abce', [[('delete', 0,1, 0,0),
1061
('equal', 1,4, 0,3),
1062
('insert', 4,4, 3,4)
1064
chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
1065
[[('equal', 3,6, 3,6),
1066
('insert', 6,6, 6,11),
1067
('equal', 6,9, 11,14)
1069
chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
1070
[[('equal', 2,6, 2,6),
1071
('insert', 6,6, 6,11),
1072
('equal', 6,10, 11,15)
1074
chk_ops('Xabcdef', 'abcdef',
1075
[[('delete', 0,1, 0,0),
1078
chk_ops('abcdef', 'abcdefX',
1079
[[('equal', 3,6, 3,6),
1080
('insert', 6,6, 6,7)
1084
def test_multiple_ranges(self):
1085
# There was an earlier bug where we used a bad set of ranges,
1086
# this triggers that specific bug, to make sure it doesn't regress
1087
self.assertDiffBlocks('abcdefghijklmnop',
1088
'abcXghiYZQRSTUVWXYZijklmnop',
1089
[(0, 0, 3), (6, 4, 3), (9, 20, 7)])
1091
self.assertDiffBlocks('ABCd efghIjk L',
1092
'AxyzBCn mo pqrstuvwI1 2 L',
1093
[(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
1095
# These are rot13 code snippets.
1096
self.assertDiffBlocks('''\
1097
trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
1099
gnxrf_netf = ['svyr*']
1100
gnxrf_bcgvbaf = ['ab-erphefr']
1102
qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr):
1103
sebz omeyvo.nqq vzcbeg fzneg_nqq, nqq_ercbegre_cevag, nqq_ercbegre_ahyy
1105
ercbegre = nqq_ercbegre_ahyy
1107
ercbegre = nqq_ercbegre_cevag
1108
fzneg_nqq(svyr_yvfg, abg ab_erphefr, ercbegre)
1111
pynff pzq_zxqve(Pbzznaq):
1112
'''.splitlines(True), '''\
1113
trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
1115
--qel-eha jvyy fubj juvpu svyrf jbhyq or nqqrq, ohg abg npghnyyl
1118
gnxrf_netf = ['svyr*']
1119
gnxrf_bcgvbaf = ['ab-erphefr', 'qel-eha']
1121
qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr, qel_eha=Snyfr):
1126
# Guvf vf cbvagyrff, ohg V'q engure abg envfr na reebe
1127
npgvba = omeyvo.nqq.nqq_npgvba_ahyy
1129
npgvba = omeyvo.nqq.nqq_npgvba_cevag
1131
npgvba = omeyvo.nqq.nqq_npgvba_nqq
1133
npgvba = omeyvo.nqq.nqq_npgvba_nqq_naq_cevag
1135
omeyvo.nqq.fzneg_nqq(svyr_yvfg, abg ab_erphefr, npgvba)
1138
pynff pzq_zxqve(Pbzznaq):
1139
'''.splitlines(True)
1140
, [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
1142
def test_patience_unified_diff(self):
1143
txt_a = ['hello there\n',
1145
'how are you today?\n']
1146
txt_b = ['hello there\n',
1147
'how are you today?\n']
1148
unified_diff = patiencediff.unified_diff
1149
psm = self._PatienceSequenceMatcher
1150
self.assertEquals(['--- \n',
1152
'@@ -1,3 +1,2 @@\n',
1155
' how are you today?\n'
1157
, list(unified_diff(txt_a, txt_b,
1158
sequencematcher=psm)))
1159
txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
1160
txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
1161
# This is the result with LongestCommonSubstring matching
1162
self.assertEquals(['--- \n',
1164
'@@ -1,6 +1,11 @@\n',
1176
, list(unified_diff(txt_a, txt_b)))
1177
# And the patience diff
1178
self.assertEquals(['--- \n',
1180
'@@ -4,6 +4,11 @@\n',
1193
, list(unified_diff(txt_a, txt_b,
1194
sequencematcher=psm)))
1196
def test_patience_unified_diff_with_dates(self):
1197
txt_a = ['hello there\n',
1199
'how are you today?\n']
1200
txt_b = ['hello there\n',
1201
'how are you today?\n']
1202
unified_diff = patiencediff.unified_diff
1203
psm = self._PatienceSequenceMatcher
1204
self.assertEquals(['--- a\t2008-08-08\n',
1205
'+++ b\t2008-09-09\n',
1206
'@@ -1,3 +1,2 @@\n',
1209
' how are you today?\n'
1211
, list(unified_diff(txt_a, txt_b,
1212
fromfile='a', tofile='b',
1213
fromfiledate='2008-08-08',
1214
tofiledate='2008-09-09',
1215
sequencematcher=psm)))
1218
class TestPatienceDiffLib_c(TestPatienceDiffLib):
1220
_test_needs_features = [features.compiled_patiencediff_feature]
1223
super(TestPatienceDiffLib_c, self).setUp()
1224
from bzrlib import _patiencediff_c
1225
self._unique_lcs = _patiencediff_c.unique_lcs_c
1226
self._recurse_matches = _patiencediff_c.recurse_matches_c
1227
self._PatienceSequenceMatcher = \
1228
_patiencediff_c.PatienceSequenceMatcher_c
1230
def test_unhashable(self):
1231
"""We should get a proper exception here."""
1232
# We need to be able to hash items in the sequence, lists are
1233
# unhashable, and thus cannot be diffed
1234
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1236
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1237
None, ['valid', []], [])
1238
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1239
None, ['valid'], [[]])
1240
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1241
None, ['valid'], ['valid', []])
1244
class TestPatienceDiffLibFiles(tests.TestCaseInTempDir):
1247
super(TestPatienceDiffLibFiles, self).setUp()
1248
self._PatienceSequenceMatcher = \
1249
_patiencediff_py.PatienceSequenceMatcher_py
1251
def test_patience_unified_diff_files(self):
1252
txt_a = ['hello there\n',
1254
'how are you today?\n']
1255
txt_b = ['hello there\n',
1256
'how are you today?\n']
1257
with open('a1', 'wb') as f: f.writelines(txt_a)
1258
with open('b1', 'wb') as f: f.writelines(txt_b)
1260
unified_diff_files = patiencediff.unified_diff_files
1261
psm = self._PatienceSequenceMatcher
1262
self.assertEquals(['--- a1\n',
1264
'@@ -1,3 +1,2 @@\n',
1267
' how are you today?\n',
1269
, list(unified_diff_files('a1', 'b1',
1270
sequencematcher=psm)))
1272
txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
1273
txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
1274
with open('a2', 'wb') as f: f.writelines(txt_a)
1275
with open('b2', 'wb') as f: f.writelines(txt_b)
1277
# This is the result with LongestCommonSubstring matching
1278
self.assertEquals(['--- a2\n',
1280
'@@ -1,6 +1,11 @@\n',
1292
, list(unified_diff_files('a2', 'b2')))
1294
# And the patience diff
1295
self.assertEquals(['--- a2\n',
1297
'@@ -4,6 +4,11 @@\n',
1310
, list(unified_diff_files('a2', 'b2',
1311
sequencematcher=psm)))
1314
class TestPatienceDiffLibFiles_c(TestPatienceDiffLibFiles):
1316
_test_needs_features = [features.compiled_patiencediff_feature]
1319
super(TestPatienceDiffLibFiles_c, self).setUp()
1320
from bzrlib import _patiencediff_c
1321
self._PatienceSequenceMatcher = \
1322
_patiencediff_c.PatienceSequenceMatcher_c
1325
class TestUsingCompiledIfAvailable(tests.TestCase):
1327
def test_PatienceSequenceMatcher(self):
1328
if features.compiled_patiencediff_feature.available():
1329
from bzrlib._patiencediff_c import PatienceSequenceMatcher_c
1330
self.assertIs(PatienceSequenceMatcher_c,
1331
patiencediff.PatienceSequenceMatcher)
1333
from bzrlib._patiencediff_py import PatienceSequenceMatcher_py
1334
self.assertIs(PatienceSequenceMatcher_py,
1335
patiencediff.PatienceSequenceMatcher)
1337
def test_unique_lcs(self):
1338
if features.compiled_patiencediff_feature.available():
1339
from bzrlib._patiencediff_c import unique_lcs_c
1340
self.assertIs(unique_lcs_c,
1341
patiencediff.unique_lcs)
1343
from bzrlib._patiencediff_py import unique_lcs_py
1344
self.assertIs(unique_lcs_py,
1345
patiencediff.unique_lcs)
1347
def test_recurse_matches(self):
1348
if features.compiled_patiencediff_feature.available():
1349
from bzrlib._patiencediff_c import recurse_matches_c
1350
self.assertIs(recurse_matches_c,
1351
patiencediff.recurse_matches)
1353
from bzrlib._patiencediff_py import recurse_matches_py
1354
self.assertIs(recurse_matches_py,
1355
patiencediff.recurse_matches)
1358
class TestDiffFromTool(tests.TestCaseWithTransport):
1360
def test_from_string(self):
1361
diff_obj = diff.DiffFromTool.from_string('diff', None, None, None)
1362
self.addCleanup(diff_obj.finish)
1363
self.assertEqual(['diff', '@old_path', '@new_path'],
1364
diff_obj.command_template)
1366
def test_from_string_u5(self):
1367
diff_obj = diff.DiffFromTool.from_string('diff "-u 5"',
1369
self.addCleanup(diff_obj.finish)
1370
self.assertEqual(['diff', '-u 5', '@old_path', '@new_path'],
1371
diff_obj.command_template)
1372
self.assertEqual(['diff', '-u 5', 'old-path', 'new-path'],
1373
diff_obj._get_command('old-path', 'new-path'))
1375
def test_from_string_path_with_backslashes(self):
1376
self.requireFeature(features.backslashdir_feature)
1377
tool = 'C:\\Tools\\Diff.exe'
1378
diff_obj = diff.DiffFromTool.from_string(tool, None, None, None)
1379
self.addCleanup(diff_obj.finish)
1380
self.assertEqual(['C:\\Tools\\Diff.exe', '@old_path', '@new_path'],
1381
diff_obj.command_template)
1382
self.assertEqual(['C:\\Tools\\Diff.exe', 'old-path', 'new-path'],
1383
diff_obj._get_command('old-path', 'new-path'))
1385
def test_execute(self):
1387
diff_obj = diff.DiffFromTool(['python', '-c',
1388
'print "@old_path @new_path"'],
1390
self.addCleanup(diff_obj.finish)
1391
diff_obj._execute('old', 'new')
1392
self.assertEqual(output.getvalue().rstrip(), 'old new')
1394
def test_excute_missing(self):
1395
diff_obj = diff.DiffFromTool(['a-tool-which-is-unlikely-to-exist'],
1397
self.addCleanup(diff_obj.finish)
1398
e = self.assertRaises(errors.ExecutableMissing, diff_obj._execute,
1400
self.assertEqual('a-tool-which-is-unlikely-to-exist could not be found'
1401
' on this machine', str(e))
1403
def test_prepare_files_creates_paths_readable_by_windows_tool(self):
1404
self.requireFeature(features.AttribFeature)
1406
tree = self.make_branch_and_tree('tree')
1407
self.build_tree_contents([('tree/file', 'content')])
1408
tree.add('file', 'file-id')
1409
tree.commit('old tree')
1411
self.addCleanup(tree.unlock)
1412
basis_tree = tree.basis_tree()
1413
basis_tree.lock_read()
1414
self.addCleanup(basis_tree.unlock)
1415
diff_obj = diff.DiffFromTool(['python', '-c',
1416
'print "@old_path @new_path"'],
1417
basis_tree, tree, output)
1418
diff_obj._prepare_files('file-id', 'file', 'file')
1419
# The old content should be readonly
1420
self.assertReadableByAttrib(diff_obj._root, 'old\\file',
1422
# The new content should use the tree object, not a 'new' file anymore
1423
self.assertEndsWith(tree.basedir, 'work/tree')
1424
self.assertReadableByAttrib(tree.basedir, 'file', r'work\\tree\\file$')
1426
def assertReadableByAttrib(self, cwd, relpath, regex):
1427
proc = subprocess.Popen(['attrib', relpath],
1428
stdout=subprocess.PIPE,
1430
(result, err) = proc.communicate()
1431
self.assertContainsRe(result.replace('\r\n', '\n'), regex)
1433
def test_prepare_files(self):
1435
tree = self.make_branch_and_tree('tree')
1436
self.build_tree_contents([('tree/oldname', 'oldcontent')])
1437
self.build_tree_contents([('tree/oldname2', 'oldcontent2')])
1438
tree.add('oldname', 'file-id')
1439
tree.add('oldname2', 'file2-id')
1440
# Earliest allowable date on FAT32 filesystems is 1980-01-01
1441
tree.commit('old tree', timestamp=315532800)
1442
tree.rename_one('oldname', 'newname')
1443
tree.rename_one('oldname2', 'newname2')
1444
self.build_tree_contents([('tree/newname', 'newcontent')])
1445
self.build_tree_contents([('tree/newname2', 'newcontent2')])
1446
old_tree = tree.basis_tree()
1447
old_tree.lock_read()
1448
self.addCleanup(old_tree.unlock)
1450
self.addCleanup(tree.unlock)
1451
diff_obj = diff.DiffFromTool(['python', '-c',
1452
'print "@old_path @new_path"'],
1453
old_tree, tree, output)
1454
self.addCleanup(diff_obj.finish)
1455
self.assertContainsRe(diff_obj._root, 'bzr-diff-[^/]*')
1456
old_path, new_path = diff_obj._prepare_files('file-id', 'oldname',
1458
self.assertContainsRe(old_path, 'old/oldname$')
1459
self.assertEqual(315532800, os.stat(old_path).st_mtime)
1460
self.assertContainsRe(new_path, 'tree/newname$')
1461
self.assertFileEqual('oldcontent', old_path)
1462
self.assertFileEqual('newcontent', new_path)
1463
if osutils.host_os_dereferences_symlinks():
1464
self.assertTrue(os.path.samefile('tree/newname', new_path))
1465
# make sure we can create files with the same parent directories
1466
diff_obj._prepare_files('file2-id', 'oldname2', 'newname2')
1469
class TestDiffFromToolEncodedFilename(tests.TestCaseWithTransport):
1471
def test_encodable_filename(self):
1472
# Just checks file path for external diff tool.
1473
# We cannot change CPython's internal encoding used by os.exec*.
1475
diffobj = diff.DiffFromTool(['dummy', '@old_path', '@new_path'],
1477
for _, scenario in EncodingAdapter.encoding_scenarios:
1478
encoding = scenario['encoding']
1479
dirname = scenario['info']['directory']
1480
filename = scenario['info']['filename']
1482
self.overrideAttr(diffobj, '_fenc', lambda: encoding)
1483
relpath = dirname + u'/' + filename
1484
fullpath = diffobj._safe_filename('safe', relpath)
1487
fullpath.encode(encoding).decode(encoding)
1489
self.assert_(fullpath.startswith(diffobj._root + '/safe'))
1491
def test_unencodable_filename(self):
1493
diffobj = diff.DiffFromTool(['dummy', '@old_path', '@new_path'],
1495
for _, scenario in EncodingAdapter.encoding_scenarios:
1496
encoding = scenario['encoding']
1497
dirname = scenario['info']['directory']
1498
filename = scenario['info']['filename']
1500
if encoding == 'iso-8859-1':
1501
encoding = 'iso-8859-2'
1503
encoding = 'iso-8859-1'
1505
self.overrideAttr(diffobj, '_fenc', lambda: encoding)
1506
relpath = dirname + u'/' + filename
1507
fullpath = diffobj._safe_filename('safe', relpath)
1510
fullpath.encode(encoding).decode(encoding)
1512
self.assert_(fullpath.startswith(diffobj._root + '/safe'))
1515
class TestGetTreesAndBranchesToDiffLocked(tests.TestCaseWithTransport):
1517
def call_gtabtd(self, path_list, revision_specs, old_url, new_url):
1518
"""Call get_trees_and_branches_to_diff_locked."""
1519
return diff.get_trees_and_branches_to_diff_locked(
1520
path_list, revision_specs, old_url, new_url, self.addCleanup)
1522
def test_basic(self):
1523
tree = self.make_branch_and_tree('tree')
1524
(old_tree, new_tree,
1525
old_branch, new_branch,
1526
specific_files, extra_trees) = self.call_gtabtd(
1527
['tree'], None, None, None)
1529
self.assertIsInstance(old_tree, revisiontree.RevisionTree)
1530
self.assertEqual(_mod_revision.NULL_REVISION,
1531
old_tree.get_revision_id())
1532
self.assertEqual(tree.basedir, new_tree.basedir)
1533
self.assertEqual(tree.branch.base, old_branch.base)
1534
self.assertEqual(tree.branch.base, new_branch.base)
1535
self.assertIs(None, specific_files)
1536
self.assertIs(None, extra_trees)
1538
def test_with_rev_specs(self):
1539
tree = self.make_branch_and_tree('tree')
1540
self.build_tree_contents([('tree/file', 'oldcontent')])
1541
tree.add('file', 'file-id')
1542
tree.commit('old tree', timestamp=0, rev_id="old-id")
1543
self.build_tree_contents([('tree/file', 'newcontent')])
1544
tree.commit('new tree', timestamp=0, rev_id="new-id")
1546
revisions = [revisionspec.RevisionSpec.from_string('1'),
1547
revisionspec.RevisionSpec.from_string('2')]
1548
(old_tree, new_tree,
1549
old_branch, new_branch,
1550
specific_files, extra_trees) = self.call_gtabtd(
1551
['tree'], revisions, None, None)
1553
self.assertIsInstance(old_tree, revisiontree.RevisionTree)
1554
self.assertEqual("old-id", old_tree.get_revision_id())
1555
self.assertIsInstance(new_tree, revisiontree.RevisionTree)
1556
self.assertEqual("new-id", new_tree.get_revision_id())
1557
self.assertEqual(tree.branch.base, old_branch.base)
1558
self.assertEqual(tree.branch.base, new_branch.base)
1559
self.assertIs(None, specific_files)
1560
self.assertEqual(tree.basedir, extra_trees[0].basedir)