105
131
lines = udiff_lines(['boo\n'], ['boo'])
106
132
self.check_patch(lines)
107
self.assertEquals(lines[5], '\\ No newline at end of file\n')
133
self.assertEqual(lines[5], '\\ No newline at end of file\n')
108
134
## "expected no-nl, got %r" % lines[5]
110
136
def check_patch(self, lines):
111
self.assert_(len(lines) > 1)
137
self.assertTrue(len(lines) > 1)
112
138
## "Not enough lines for a file header for patch:\n%s" % "".join(lines)
113
self.assert_(lines[0].startswith ('---'))
139
self.assertTrue(lines[0].startswith ('---'))
114
140
## 'No orig line for patch:\n%s' % "".join(lines)
115
self.assert_(lines[1].startswith ('+++'))
141
self.assertTrue(lines[1].startswith ('+++'))
116
142
## 'No mod line for patch:\n%s' % "".join(lines)
117
self.assert_(len(lines) > 2)
143
self.assertTrue(len(lines) > 2)
118
144
## "No hunks for patch:\n%s" % "".join(lines)
119
self.assert_(lines[2].startswith('@@'))
145
self.assertTrue(lines[2].startswith('@@'))
120
146
## "No hunk header for patch:\n%s" % "".join(lines)
121
self.assert_('@@' in lines[2][2:])
147
self.assertTrue('@@' in lines[2][2:])
122
148
## "Unterminated hunk header for patch:\n%s" % "".join(lines)
124
150
def test_binary_lines(self):
126
152
uni_lines = [1023 * 'a' + '\x00']
127
self.assertRaises(errors.BinaryFile, udiff_lines, uni_lines , empty)
153
self.assertRaises(errors.BinaryFile, udiff_lines, uni_lines, empty)
128
154
self.assertRaises(errors.BinaryFile, udiff_lines, empty, uni_lines)
129
udiff_lines(uni_lines , empty, allow_binary=True)
155
udiff_lines(uni_lines, empty, allow_binary=True)
130
156
udiff_lines(empty, uni_lines, allow_binary=True)
132
158
def test_external_diff(self):
143
169
self.check_patch(lines)
145
171
def test_external_diff_binary_lang_c(self):
147
172
for lang in ('LANG', 'LC_ALL', 'LANGUAGE'):
148
old_env[lang] = osutils.set_or_unset_env(lang, 'C')
150
lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
151
# Older versions of diffutils say "Binary files", newer
152
# versions just say "Files".
153
self.assertContainsRe(lines[0],
154
'(Binary f|F)iles old and new differ\n')
155
self.assertEquals(lines[1:], ['\n'])
157
for lang, old_val in old_env.iteritems():
158
osutils.set_or_unset_env(lang, old_val)
173
self.overrideEnv(lang, 'C')
174
lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
175
# Older versions of diffutils say "Binary files", newer
176
# versions just say "Files".
177
self.assertContainsRe(lines[0], '(Binary f|F)iles old and new differ\n')
178
self.assertEqual(lines[1:], ['\n'])
160
180
def test_no_external_diff(self):
161
181
"""Check that NoDiff is raised when diff is not available"""
162
# Use os.environ['PATH'] to make sure no 'diff' command is available
163
orig_path = os.environ['PATH']
165
os.environ['PATH'] = ''
166
self.assertRaises(errors.NoDiff, diff.external_diff,
167
'old', ['boo\n'], 'new', ['goo\n'],
168
StringIO(), diff_opts=['-u'])
170
os.environ['PATH'] = orig_path
182
# Make sure no 'diff' command is available
183
# XXX: Weird, using None instead of '' breaks the test -- vila 20101216
184
self.overrideEnv('PATH', '')
185
self.assertRaises(errors.NoDiff, diff.external_diff,
186
'old', ['boo\n'], 'new', ['goo\n'],
187
BytesIO(), diff_opts=['-u'])
172
189
def test_internal_diff_default(self):
173
190
# Default internal diff encoding is utf8
175
192
diff.internal_diff(u'old_\xb5', ['old_text\n'],
176
193
u'new_\xe5', ['new_text\n'], output)
177
194
lines = output.getvalue().splitlines(True)
178
195
self.check_patch(lines)
179
self.assertEquals(['--- old_\xc2\xb5\n',
196
self.assertEqual(['--- old_\xc2\xb5\n',
180
197
'+++ new_\xc3\xa5\n',
181
198
'@@ -1,1 +1,1 @@\n',
204
221
def test_internal_diff_iso_8859_1(self):
206
diff.internal_diff(u'old_\xb5', ['old_text\n'],
207
u'new_\xe5', ['new_text\n'], output,
223
diff.internal_diff(u'old_\xb5', [b'old_text\n'],
224
u'new_\xe5', [b'new_text\n'], output,
208
225
path_encoding='iso-8859-1')
209
226
lines = output.getvalue().splitlines(True)
210
227
self.check_patch(lines)
211
self.assertEquals(['--- old_\xb5\n',
228
self.assertEqual([b'--- old_\xb5\n',
230
b'@@ -1,1 +1,1 @@\n',
220
237
def test_internal_diff_no_content(self):
222
239
diff.internal_diff(u'old', [], u'new', [], output)
223
self.assertEqual('', output.getvalue())
240
self.assertEqual(b'', output.getvalue())
225
242
def test_internal_diff_no_changes(self):
227
diff.internal_diff(u'old', ['text\n', 'contents\n'],
228
u'new', ['text\n', 'contents\n'],
244
diff.internal_diff(u'old', [b'text\n', b'contents\n'],
245
u'new', [b'text\n', b'contents\n'],
230
self.assertEqual('', output.getvalue())
247
self.assertEqual(b'', output.getvalue())
232
249
def test_internal_diff_returns_bytes(self):
234
output = StringIO.StringIO()
235
diff.internal_diff(u'old_\xb5', ['old_text\n'],
236
u'new_\xe5', ['new_text\n'], output)
237
self.failUnless(isinstance(output.getvalue(), str),
238
'internal_diff should return bytestrings')
251
diff.internal_diff(u'old_\xb5', [b'old_text\n'],
252
u'new_\xe5', [b'new_text\n'], output)
253
output.check_types(self, bytes)
255
def test_internal_diff_default_context(self):
257
diff.internal_diff('old', ['same_text\n', 'same_text\n', 'same_text\n',
258
'same_text\n', 'same_text\n', 'old_text\n'],
259
'new', ['same_text\n', 'same_text\n', 'same_text\n',
260
'same_text\n', 'same_text\n', 'new_text\n'], output)
261
lines = output.getvalue().splitlines(True)
262
self.check_patch(lines)
263
self.assertEqual(['--- old\n',
275
def test_internal_diff_no_context(self):
277
diff.internal_diff('old', ['same_text\n', 'same_text\n', 'same_text\n',
278
'same_text\n', 'same_text\n', 'old_text\n'],
279
'new', ['same_text\n', 'same_text\n', 'same_text\n',
280
'same_text\n', 'same_text\n', 'new_text\n'], output,
282
lines = output.getvalue().splitlines(True)
283
self.check_patch(lines)
284
self.assertEqual(['--- old\n',
293
def test_internal_diff_more_context(self):
295
diff.internal_diff('old', ['same_text\n', 'same_text\n', 'same_text\n',
296
'same_text\n', 'same_text\n', 'old_text\n'],
297
'new', ['same_text\n', 'same_text\n', 'same_text\n',
298
'same_text\n', 'same_text\n', 'new_text\n'], output,
300
lines = output.getvalue().splitlines(True)
301
self.check_patch(lines)
302
self.assertEqual(['--- old\n',
241
317
class TestDiffFiles(tests.TestCaseInTempDir):
243
319
def test_external_diff_binary(self):
244
320
"""The output when using external diff should use diff's i18n error"""
321
for lang in ('LANG', 'LC_ALL', 'LANGUAGE'):
322
self.overrideEnv(lang, 'C')
245
323
# Make sure external_diff doesn't fail in the current LANG
246
324
lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
248
326
cmd = ['diff', '-u', '--binary', 'old', 'new']
249
open('old', 'wb').write('\x00foobar\n')
250
open('new', 'wb').write('foo\x00bar\n')
327
with open('old', 'wb') as f: f.write(b'\x00foobar\n')
328
with open('new', 'wb') as f: f.write(b'foo\x00bar\n')
251
329
pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
252
330
stdin=subprocess.PIPE)
253
331
out, err = pipe.communicate()
254
# Diff returns '2' on Binary files.
255
self.assertEqual(2, pipe.returncode)
256
332
# We should output whatever diff tells us, plus a trailing newline
257
333
self.assertEqual(out.splitlines(True) + ['\n'], lines)
260
class TestShowDiffTreesHelper(tests.TestCaseWithTransport):
261
"""Has a helper for running show_diff_trees"""
263
def get_diff(self, tree1, tree2, specific_files=None, working_tree=None):
265
if working_tree is not None:
266
extra_trees = (working_tree,)
269
diff.show_diff_trees(tree1, tree2, output,
270
specific_files=specific_files,
271
extra_trees=extra_trees, old_label='old/',
273
return output.getvalue()
276
class TestDiffDates(TestShowDiffTreesHelper):
336
def get_diff_as_string(tree1, tree2, specific_files=None, working_tree=None):
338
if working_tree is not None:
339
extra_trees = (working_tree,)
342
diff.show_diff_trees(tree1, tree2, output,
343
specific_files=specific_files,
344
extra_trees=extra_trees, old_label='old/',
346
return output.getvalue()
349
class TestDiffDates(tests.TestCaseWithTransport):
279
352
super(TestDiffDates, self).setUp()
280
353
self.wt = self.make_branch_and_tree('.')
281
354
self.b = self.wt.branch
282
355
self.build_tree_contents([
283
('file1', 'file1 contents at rev 1\n'),
284
('file2', 'file2 contents at rev 1\n')
356
('file1', b'file1 contents at rev 1\n'),
357
('file2', b'file2 contents at rev 1\n')
286
359
self.wt.add(['file1', 'file2'])
288
361
message='Revision 1',
289
362
timestamp=1143849600, # 2006-04-01 00:00:00 UTC
292
self.build_tree_contents([('file1', 'file1 contents at rev 2\n')])
365
self.build_tree_contents([('file1', b'file1 contents at rev 2\n')])
294
367
message='Revision 2',
295
368
timestamp=1143936000, # 2006-04-02 00:00:00 UTC
298
self.build_tree_contents([('file2', 'file2 contents at rev 3\n')])
371
self.build_tree_contents([('file2', b'file2 contents at rev 3\n')])
300
373
message='Revision 3',
301
374
timestamp=1144022400, # 2006-04-03 00:00:00 UTC
304
377
self.wt.remove(['file2'])
306
379
message='Revision 4',
307
380
timestamp=1144108800, # 2006-04-04 00:00:00 UTC
310
383
self.build_tree_contents([
311
('file1', 'file1 contents in working tree\n')
384
('file1', b'file1 contents in working tree\n')
313
386
# set the date stamps for files in the working tree to known values
314
387
os.utime('file1', (1144195200, 1144195200)) # 2006-04-05 00:00:00 UTC
316
389
def test_diff_rev_tree_working_tree(self):
317
output = self.get_diff(self.wt.basis_tree(), self.wt)
390
output = get_diff_as_string(self.wt.basis_tree(), self.wt)
318
391
# note that the date for old/file1 is from rev 2 rather than from
319
392
# the basis revision (rev 4)
320
393
self.assertEqualDiff(output, '''\
393
466
self.wt.add(['dir1', 'dir2'])
394
467
self.wt.rename_one('file1', 'dir1/file1')
395
old_tree = self.b.repository.revision_tree('rev-1')
396
new_tree = self.b.repository.revision_tree('rev-4')
397
out = self.get_diff(old_tree, new_tree, specific_files=['dir1'],
398
working_tree=self.wt)
399
self.assertContainsRe(out, 'file1\t')
400
out = self.get_diff(old_tree, new_tree, specific_files=['dir2'],
401
working_tree=self.wt)
402
self.assertNotContainsRe(out, 'file1\t')
406
class TestShowDiffTrees(TestShowDiffTreesHelper):
468
old_tree = self.b.repository.revision_tree(b'rev-1')
469
new_tree = self.b.repository.revision_tree(b'rev-4')
470
out = get_diff_as_string(old_tree, new_tree, specific_files=['dir1'],
471
working_tree=self.wt)
472
self.assertContainsRe(out, b'file1\t')
473
out = get_diff_as_string(old_tree, new_tree, specific_files=['dir2'],
474
working_tree=self.wt)
475
self.assertNotContainsRe(out, b'file1\t')
478
class TestShowDiffTrees(tests.TestCaseWithTransport):
407
479
"""Direct tests for show_diff_trees"""
409
481
def test_modified_file(self):
410
482
"""Test when a file is modified."""
411
483
tree = self.make_branch_and_tree('tree')
412
self.build_tree_contents([('tree/file', 'contents\n')])
413
tree.add(['file'], ['file-id'])
414
tree.commit('one', rev_id='rev-1')
484
self.build_tree_contents([('tree/file', b'contents\n')])
485
tree.add(['file'], [b'file-id'])
486
tree.commit('one', rev_id=b'rev-1')
416
self.build_tree_contents([('tree/file', 'new contents\n')])
417
d = self.get_diff(tree.basis_tree(), tree)
488
self.build_tree_contents([('tree/file', b'new contents\n')])
489
d = get_diff_as_string(tree.basis_tree(), tree)
418
490
self.assertContainsRe(d, "=== modified file 'file'\n")
419
491
self.assertContainsRe(d, '--- old/file\t')
420
492
self.assertContainsRe(d, '\\+\\+\\+ new/file\t')
490
562
tree = self.make_branch_and_tree('tree')
492
564
tt = transform.TreeTransform(tree)
493
tt.new_file('a', tt.root, 'contents\n', 'a-id', True)
494
tt.new_file('b', tt.root, 'contents\n', 'b-id', False)
495
tt.new_file('c', tt.root, 'contents\n', 'c-id', True)
496
tt.new_file('d', tt.root, 'contents\n', 'd-id', False)
497
tt.new_file('e', tt.root, 'contents\n', 'control-e-id', True)
498
tt.new_file('f', tt.root, 'contents\n', 'control-f-id', False)
565
tt.new_file('a', tt.root, [b'contents\n'], b'a-id', True)
566
tt.new_file('b', tt.root, [b'contents\n'], b'b-id', False)
567
tt.new_file('c', tt.root, [b'contents\n'], b'c-id', True)
568
tt.new_file('d', tt.root, [b'contents\n'], b'd-id', False)
569
tt.new_file('e', tt.root, [b'contents\n'], b'control-e-id', True)
570
tt.new_file('f', tt.root, [b'contents\n'], b'control-f-id', False)
500
tree.commit('one', rev_id='rev-1')
572
tree.commit('one', rev_id=b'rev-1')
502
574
tt = transform.TreeTransform(tree)
503
tt.set_executability(False, tt.trans_id_file_id('a-id'))
504
tt.set_executability(True, tt.trans_id_file_id('b-id'))
505
tt.set_executability(False, tt.trans_id_file_id('c-id'))
506
tt.set_executability(True, tt.trans_id_file_id('d-id'))
575
tt.set_executability(False, tt.trans_id_file_id(b'a-id'))
576
tt.set_executability(True, tt.trans_id_file_id(b'b-id'))
577
tt.set_executability(False, tt.trans_id_file_id(b'c-id'))
578
tt.set_executability(True, tt.trans_id_file_id(b'd-id'))
508
580
tree.rename_one('c', 'new-c')
509
581
tree.rename_one('d', 'new-d')
511
d = self.get_diff(tree.basis_tree(), tree)
583
d = get_diff_as_string(tree.basis_tree(), tree)
513
585
self.assertContainsRe(d, r"file 'a'.*\(properties changed:"
515
587
self.assertContainsRe(d, r"file 'b'.*\(properties changed:"
517
589
self.assertContainsRe(d, r"file 'c'.*\(properties changed:"
519
591
self.assertContainsRe(d, r"file 'd'.*\(properties changed:"
521
593
self.assertNotContainsRe(d, r"file 'e'")
522
594
self.assertNotContainsRe(d, r"file 'f'")
525
596
def test_binary_unicode_filenames(self):
526
597
"""Test that contents of files are *not* encoded in UTF-8 when there
527
598
is a binary file in the diff.
529
600
# See https://bugs.launchpad.net/bugs/110092.
530
self.requireFeature(tests.UnicodeFilenameFeature)
601
self.requireFeature(features.UnicodeFilenameFeature)
532
# This bug isn't triggered with cStringIO.
533
from StringIO import StringIO
534
603
tree = self.make_branch_and_tree('tree')
535
604
alpha, omega = u'\u03b1', u'\u03c9'
536
605
alpha_utf8, omega_utf8 = alpha.encode('utf8'), omega.encode('utf8')
553
623
def test_unicode_filename(self):
554
624
"""Test when the filename are unicode."""
555
self.requireFeature(tests.UnicodeFilenameFeature)
625
self.requireFeature(features.UnicodeFilenameFeature)
557
627
alpha, omega = u'\u03b1', u'\u03c9'
558
628
autf8, outf8 = alpha.encode('utf8'), omega.encode('utf8')
560
630
tree = self.make_branch_and_tree('tree')
561
self.build_tree_contents([('tree/ren_'+alpha, 'contents\n')])
562
tree.add(['ren_'+alpha], ['file-id-2'])
563
self.build_tree_contents([('tree/del_'+alpha, 'contents\n')])
564
tree.add(['del_'+alpha], ['file-id-3'])
565
self.build_tree_contents([('tree/mod_'+alpha, 'contents\n')])
566
tree.add(['mod_'+alpha], ['file-id-4'])
631
self.build_tree_contents([('tree/ren_'+alpha, b'contents\n')])
632
tree.add(['ren_'+alpha], [b'file-id-2'])
633
self.build_tree_contents([('tree/del_'+alpha, b'contents\n')])
634
tree.add(['del_'+alpha], [b'file-id-3'])
635
self.build_tree_contents([('tree/mod_'+alpha, b'contents\n')])
636
tree.add(['mod_'+alpha], [b'file-id-4'])
568
tree.commit('one', rev_id='rev-1')
638
tree.commit('one', rev_id=b'rev-1')
570
640
tree.rename_one('ren_'+alpha, 'ren_'+omega)
571
641
tree.remove('del_'+alpha)
572
self.build_tree_contents([('tree/add_'+alpha, 'contents\n')])
573
tree.add(['add_'+alpha], ['file-id'])
574
self.build_tree_contents([('tree/mod_'+alpha, 'contents_mod\n')])
642
self.build_tree_contents([('tree/add_'+alpha, b'contents\n')])
643
tree.add(['add_'+alpha], [b'file-id'])
644
self.build_tree_contents([('tree/mod_'+alpha, b'contents_mod\n')])
576
d = self.get_diff(tree.basis_tree(), tree)
646
d = get_diff_as_string(tree.basis_tree(), tree)
577
647
self.assertContainsRe(d,
578
648
"=== renamed file 'ren_%s' => 'ren_%s'\n"%(autf8, outf8))
579
649
self.assertContainsRe(d, "=== added file 'add_%s'"%autf8)
580
650
self.assertContainsRe(d, "=== modified file 'mod_%s'"%autf8)
581
651
self.assertContainsRe(d, "=== removed file 'del_%s'"%autf8)
653
def test_unicode_filename_path_encoding(self):
654
"""Test for bug #382699: unicode filenames on Windows should be shown
657
self.requireFeature(features.UnicodeFilenameFeature)
658
# The word 'test' in Russian
659
_russian_test = u'\u0422\u0435\u0441\u0442'
660
directory = _russian_test + u'/'
661
test_txt = _russian_test + u'.txt'
662
u1234 = u'\u1234.txt'
664
tree = self.make_branch_and_tree('.')
665
self.build_tree_contents([
666
(test_txt, b'foo\n'),
670
tree.add([test_txt, u1234, directory])
673
diff.show_diff_trees(tree.basis_tree(), tree, sio,
674
path_encoding='cp1251')
676
output = subst_dates(sio.getvalue())
678
=== added directory '%(directory)s'
679
=== added file '%(test_txt)s'
680
--- a/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
681
+++ b/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
685
=== added file '?.txt'
686
--- a/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
687
+++ b/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
691
''' % {'directory': _russian_test.encode('cp1251'),
692
'test_txt': test_txt.encode('cp1251'),
694
self.assertEqualDiff(output, shouldbe)
584
697
class DiffWasIs(diff.DiffPath):
586
699
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
587
700
self.to_file.write('was: ')
588
self.to_file.write(self.old_tree.get_file(file_id).read())
701
self.to_file.write(self.old_tree.get_file(old_path).read())
589
702
self.to_file.write('is: ')
590
self.to_file.write(self.new_tree.get_file(file_id).read())
703
self.to_file.write(self.new_tree.get_file(new_path).read())
594
706
class TestDiffTree(tests.TestCaseWithTransport):
601
713
self.new_tree = self.make_branch_and_tree('new-tree')
602
714
self.new_tree.lock_write()
603
715
self.addCleanup(self.new_tree.unlock)
604
self.differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
716
self.differ = diff.DiffTree(self.old_tree, self.new_tree, BytesIO())
606
718
def test_diff_text(self):
607
719
self.build_tree_contents([('old-tree/olddir/',),
608
('old-tree/olddir/oldfile', 'old\n')])
720
('old-tree/olddir/oldfile', b'old\n')])
609
721
self.old_tree.add('olddir')
610
self.old_tree.add('olddir/oldfile', 'file-id')
722
self.old_tree.add('olddir/oldfile', b'file-id')
611
723
self.build_tree_contents([('new-tree/newdir/',),
612
('new-tree/newdir/newfile', 'new\n')])
724
('new-tree/newdir/newfile', b'new\n')])
613
725
self.new_tree.add('newdir')
614
self.new_tree.add('newdir/newfile', 'file-id')
615
differ = diff.DiffText(self.old_tree, self.new_tree, StringIO())
616
differ.diff_text('file-id', None, 'old label', 'new label')
726
self.new_tree.add('newdir/newfile', b'file-id')
727
differ = diff.DiffText(self.old_tree, self.new_tree, BytesIO())
728
differ.diff_text('olddir/oldfile', None, 'old label',
729
'new label', b'file-id', None)
617
730
self.assertEqual(
618
731
'--- old label\n+++ new label\n@@ -1,1 +0,0 @@\n-old\n\n',
619
732
differ.to_file.getvalue())
620
733
differ.to_file.seek(0)
621
differ.diff_text(None, 'file-id', 'old label', 'new label')
734
differ.diff_text(None, 'newdir/newfile',
735
'old label', 'new label', None, b'file-id')
622
736
self.assertEqual(
623
737
'--- old label\n+++ new label\n@@ -0,0 +1,1 @@\n+new\n\n',
624
738
differ.to_file.getvalue())
625
739
differ.to_file.seek(0)
626
differ.diff_text('file-id', 'file-id', 'old label', 'new label')
740
differ.diff_text('olddir/oldfile', 'newdir/newfile',
741
'old label', 'new label', b'file-id', b'file-id')
627
742
self.assertEqual(
628
743
'--- old label\n+++ new label\n@@ -1,1 +1,1 @@\n-old\n+new\n\n',
629
744
differ.to_file.getvalue())
631
746
def test_diff_deletion(self):
632
self.build_tree_contents([('old-tree/file', 'contents'),
633
('new-tree/file', 'contents')])
634
self.old_tree.add('file', 'file-id')
635
self.new_tree.add('file', 'file-id')
747
self.build_tree_contents([('old-tree/file', b'contents'),
748
('new-tree/file', b'contents')])
749
self.old_tree.add('file', b'file-id')
750
self.new_tree.add('file', b'file-id')
636
751
os.unlink('new-tree/file')
637
752
self.differ.show_diff(None)
638
753
self.assertContainsRe(self.differ.to_file.getvalue(), '-contents')
640
755
def test_diff_creation(self):
641
self.build_tree_contents([('old-tree/file', 'contents'),
642
('new-tree/file', 'contents')])
643
self.old_tree.add('file', 'file-id')
644
self.new_tree.add('file', 'file-id')
756
self.build_tree_contents([('old-tree/file', b'contents'),
757
('new-tree/file', b'contents')])
758
self.old_tree.add('file', b'file-id')
759
self.new_tree.add('file', b'file-id')
645
760
os.unlink('old-tree/file')
646
761
self.differ.show_diff(None)
647
self.assertContainsRe(self.differ.to_file.getvalue(), '\+contents')
762
self.assertContainsRe(self.differ.to_file.getvalue(), r'\+contents')
649
764
def test_diff_symlink(self):
650
differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
765
differ = diff.DiffSymlink(self.old_tree, self.new_tree, BytesIO())
651
766
differ.diff_symlink('old target', None)
652
767
self.assertEqual("=== target was 'old target'\n",
653
768
differ.to_file.getvalue())
655
differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
770
differ = diff.DiffSymlink(self.old_tree, self.new_tree, BytesIO())
656
771
differ.diff_symlink(None, 'new target')
657
772
self.assertEqual("=== target is 'new target'\n",
658
773
differ.to_file.getvalue())
660
differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
775
differ = diff.DiffSymlink(self.old_tree, self.new_tree, BytesIO())
661
776
differ.diff_symlink('old target', 'new target')
662
777
self.assertEqual("=== target changed 'old target' => 'new target'\n",
663
778
differ.to_file.getvalue())
665
780
def test_diff(self):
666
781
self.build_tree_contents([('old-tree/olddir/',),
667
('old-tree/olddir/oldfile', 'old\n')])
782
('old-tree/olddir/oldfile', b'old\n')])
668
783
self.old_tree.add('olddir')
669
self.old_tree.add('olddir/oldfile', 'file-id')
784
self.old_tree.add('olddir/oldfile', b'file-id')
670
785
self.build_tree_contents([('new-tree/newdir/',),
671
('new-tree/newdir/newfile', 'new\n')])
786
('new-tree/newdir/newfile', b'new\n')])
672
787
self.new_tree.add('newdir')
673
self.new_tree.add('newdir/newfile', 'file-id')
674
self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
788
self.new_tree.add('newdir/newfile', b'file-id')
789
self.differ.diff(b'file-id', 'olddir/oldfile', 'newdir/newfile')
675
790
self.assertContainsRe(
676
791
self.differ.to_file.getvalue(),
677
792
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
678
' \@\@\n-old\n\+new\n\n')
793
r' \@\@\n-old\n\+new\n\n')
680
795
def test_diff_kind_change(self):
681
self.requireFeature(tests.SymlinkFeature)
796
self.requireFeature(features.SymlinkFeature)
682
797
self.build_tree_contents([('old-tree/olddir/',),
683
('old-tree/olddir/oldfile', 'old\n')])
798
('old-tree/olddir/oldfile', b'old\n')])
684
799
self.old_tree.add('olddir')
685
self.old_tree.add('olddir/oldfile', 'file-id')
800
self.old_tree.add('olddir/oldfile', b'file-id')
686
801
self.build_tree(['new-tree/newdir/'])
687
802
os.symlink('new', 'new-tree/newdir/newfile')
688
803
self.new_tree.add('newdir')
689
self.new_tree.add('newdir/newfile', 'file-id')
690
self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
804
self.new_tree.add('newdir/newfile', b'file-id')
805
self.differ.diff(b'file-id', 'olddir/oldfile', 'newdir/newfile')
691
806
self.assertContainsRe(
692
807
self.differ.to_file.getvalue(),
693
808
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+0,0'
695
810
self.assertContainsRe(self.differ.to_file.getvalue(),
696
811
"=== target is u'new'\n")
698
813
def test_diff_directory(self):
699
814
self.build_tree(['new-tree/new-dir/'])
700
self.new_tree.add('new-dir', 'new-dir-id')
701
self.differ.diff('new-dir-id', None, 'new-dir')
702
self.assertEqual(self.differ.to_file.getvalue(), '')
815
self.new_tree.add('new-dir', b'new-dir-id')
816
self.differ.diff(b'new-dir-id', None, 'new-dir')
817
self.assertEqual(self.differ.to_file.getvalue(), b'')
704
819
def create_old_new(self):
705
820
self.build_tree_contents([('old-tree/olddir/',),
706
('old-tree/olddir/oldfile', 'old\n')])
821
('old-tree/olddir/oldfile', b'old\n')])
707
822
self.old_tree.add('olddir')
708
self.old_tree.add('olddir/oldfile', 'file-id')
823
self.old_tree.add('olddir/oldfile', b'file-id')
709
824
self.build_tree_contents([('new-tree/newdir/',),
710
('new-tree/newdir/newfile', 'new\n')])
825
('new-tree/newdir/newfile', b'new\n')])
711
826
self.new_tree.add('newdir')
712
self.new_tree.add('newdir/newfile', 'file-id')
827
self.new_tree.add('newdir/newfile', b'file-id')
714
829
def test_register_diff(self):
715
830
self.create_old_new()
717
832
diff.DiffTree.diff_factories=old_diff_factories[:]
718
833
diff.DiffTree.diff_factories.insert(0, DiffWasIs.from_diff_tree)
720
differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
835
differ = diff.DiffTree(self.old_tree, self.new_tree, BytesIO())
722
837
diff.DiffTree.diff_factories = old_diff_factories
723
differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
838
differ.diff(b'file-id', 'olddir/oldfile', 'newdir/newfile')
724
839
self.assertNotContainsRe(
725
840
differ.to_file.getvalue(),
726
841
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
727
' \@\@\n-old\n\+new\n\n')
842
r' \@\@\n-old\n\+new\n\n')
728
843
self.assertContainsRe(differ.to_file.getvalue(),
729
844
'was: old\nis: new\n')
731
846
def test_extra_factories(self):
732
847
self.create_old_new()
733
differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO(),
848
differ = diff.DiffTree(self.old_tree, self.new_tree, BytesIO(),
734
849
extra_factories=[DiffWasIs.from_diff_tree])
735
differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
850
differ.diff(b'file-id', 'olddir/oldfile', 'newdir/newfile')
736
851
self.assertNotContainsRe(
737
852
differ.to_file.getvalue(),
738
853
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
739
' \@\@\n-old\n\+new\n\n')
854
r' \@\@\n-old\n\+new\n\n')
740
855
self.assertContainsRe(differ.to_file.getvalue(),
741
856
'was: old\nis: new\n')
764
879
b = ''.join([unichr(i) for i in range(4300, 4800, 2)])
765
880
sm = self._PatienceSequenceMatcher(None, a, b)
766
881
mb = sm.get_matching_blocks()
767
self.assertEquals(35, len(mb))
882
self.assertEqual(35, len(mb))
769
884
def test_unique_lcs(self):
770
885
unique_lcs = self._unique_lcs
771
self.assertEquals(unique_lcs('', ''), [])
772
self.assertEquals(unique_lcs('', 'a'), [])
773
self.assertEquals(unique_lcs('a', ''), [])
774
self.assertEquals(unique_lcs('a', 'a'), [(0,0)])
775
self.assertEquals(unique_lcs('a', 'b'), [])
776
self.assertEquals(unique_lcs('ab', 'ab'), [(0,0), (1,1)])
777
self.assertEquals(unique_lcs('abcde', 'cdeab'), [(2,0), (3,1), (4,2)])
778
self.assertEquals(unique_lcs('cdeab', 'abcde'), [(0,2), (1,3), (2,4)])
779
self.assertEquals(unique_lcs('abXde', 'abYde'), [(0,0), (1,1),
781
self.assertEquals(unique_lcs('acbac', 'abc'), [(2,1)])
886
self.assertEqual(unique_lcs('', ''), [])
887
self.assertEqual(unique_lcs('', 'a'), [])
888
self.assertEqual(unique_lcs('a', ''), [])
889
self.assertEqual(unique_lcs('a', 'a'), [(0, 0)])
890
self.assertEqual(unique_lcs('a', 'b'), [])
891
self.assertEqual(unique_lcs('ab', 'ab'), [(0, 0), (1, 1)])
892
self.assertEqual(unique_lcs('abcde', 'cdeab'), [(2, 0), (3, 1), (4, 2)])
893
self.assertEqual(unique_lcs('cdeab', 'abcde'), [(0, 2), (1, 3), (2, 4)])
894
self.assertEqual(unique_lcs('abXde', 'abYde'), [(0, 0), (1, 1),
896
self.assertEqual(unique_lcs('acbac', 'abc'), [(2, 1)])
783
898
def test_recurse_matches(self):
784
899
def test_one(a, b, matches):
785
900
test_matches = []
786
901
self._recurse_matches(
787
902
a, b, 0, 0, len(a), len(b), test_matches, 10)
788
self.assertEquals(test_matches, matches)
903
self.assertEqual(test_matches, matches)
790
905
test_one(['a', '', 'b', '', 'c'], ['a', 'a', 'b', 'c', 'c'],
791
906
[(0, 0), (2, 2), (4, 4)])
854
969
# non unique lines surrounded by non-matching lines
856
self.assertDiffBlocks('aBccDe', 'abccde', [(0,0,1), (5,5,1)])
971
self.assertDiffBlocks('aBccDe', 'abccde', [(0, 0, 1), (5, 5, 1)])
858
973
# But they only need to be locally unique
859
self.assertDiffBlocks('aBcDec', 'abcdec', [(0,0,1), (2,2,1), (4,4,2)])
974
self.assertDiffBlocks('aBcDec', 'abcdec', [(0, 0, 1), (2, 2, 1), (4, 4, 2)])
861
976
# non unique blocks won't be matched
862
self.assertDiffBlocks('aBcdEcdFg', 'abcdecdfg', [(0,0,1), (8,8,1)])
977
self.assertDiffBlocks('aBcdEcdFg', 'abcdecdfg', [(0, 0, 1), (8, 8, 1)])
864
979
# but locally unique ones will
865
self.assertDiffBlocks('aBcdEeXcdFg', 'abcdecdfg', [(0,0,1), (2,2,2),
866
(5,4,1), (7,5,2), (10,8,1)])
980
self.assertDiffBlocks('aBcdEeXcdFg', 'abcdecdfg', [(0, 0, 1), (2, 2, 2),
981
(5, 4, 1), (7, 5, 2), (10, 8, 1)])
868
self.assertDiffBlocks('abbabbXd', 'cabbabxd', [(7,7,1)])
983
self.assertDiffBlocks('abbabbXd', 'cabbabxd', [(7, 7, 1)])
869
984
self.assertDiffBlocks('abbabbbb', 'cabbabbc', [])
870
985
self.assertDiffBlocks('bbbbbbbb', 'cbbbbbbc', [])
896
1011
def test_opcodes(self):
897
1012
def chk_ops(a, b, expected_codes):
898
1013
s = self._PatienceSequenceMatcher(None, a, b)
899
self.assertEquals(expected_codes, s.get_opcodes())
1014
self.assertEqual(expected_codes, s.get_opcodes())
901
1016
chk_ops('', '', [])
902
1017
chk_ops([], [], [])
903
chk_ops('abc', '', [('delete', 0,3, 0,0)])
904
chk_ops('', 'abc', [('insert', 0,0, 0,3)])
905
chk_ops('abcd', 'abcd', [('equal', 0,4, 0,4)])
906
chk_ops('abcd', 'abce', [('equal', 0,3, 0,3),
907
('replace', 3,4, 3,4)
909
chk_ops('eabc', 'abce', [('delete', 0,1, 0,0),
913
chk_ops('eabce', 'abce', [('delete', 0,1, 0,0),
1018
chk_ops('abc', '', [('delete', 0, 3, 0, 0)])
1019
chk_ops('', 'abc', [('insert', 0, 0, 0, 3)])
1020
chk_ops('abcd', 'abcd', [('equal', 0, 4, 0, 4)])
1021
chk_ops('abcd', 'abce', [('equal', 0, 3, 0, 3),
1022
('replace', 3, 4, 3, 4)
1024
chk_ops('eabc', 'abce', [('delete', 0, 1, 0, 0),
1025
('equal', 1, 4, 0, 3),
1026
('insert', 4, 4, 3, 4)
1028
chk_ops('eabce', 'abce', [('delete', 0, 1, 0, 0),
1029
('equal', 1, 5, 0, 4)
916
chk_ops('abcde', 'abXde', [('equal', 0,2, 0,2),
917
('replace', 2,3, 2,3),
1031
chk_ops('abcde', 'abXde', [('equal', 0, 2, 0, 2),
1032
('replace', 2, 3, 2, 3),
1033
('equal', 3, 5, 3, 5)
920
chk_ops('abcde', 'abXYZde', [('equal', 0,2, 0,2),
921
('replace', 2,3, 2,5),
1035
chk_ops('abcde', 'abXYZde', [('equal', 0, 2, 0, 2),
1036
('replace', 2, 3, 2, 5),
1037
('equal', 3, 5, 5, 7)
924
chk_ops('abde', 'abXYZde', [('equal', 0,2, 0,2),
925
('insert', 2,2, 2,5),
1039
chk_ops('abde', 'abXYZde', [('equal', 0, 2, 0, 2),
1040
('insert', 2, 2, 2, 5),
1041
('equal', 2, 4, 5, 7)
928
1043
chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
929
[('equal', 0,6, 0,6),
930
('insert', 6,6, 6,11),
931
('equal', 6,16, 11,21)
1044
[('equal', 0, 6, 0, 6),
1045
('insert', 6, 6, 6, 11),
1046
('equal', 6, 16, 11, 21)
934
1049
[ 'hello there\n'
936
1051
, 'how are you today?\n'],
937
1052
[ 'hello there\n'
938
1053
, 'how are you today?\n'],
939
[('equal', 0,1, 0,1),
940
('delete', 1,2, 1,1),
1054
[('equal', 0, 1, 0, 1),
1055
('delete', 1, 2, 1, 1),
1056
('equal', 2, 3, 1, 2),
943
1058
chk_ops('aBccDe', 'abccde',
944
[('equal', 0,1, 0,1),
945
('replace', 1,5, 1,5),
1059
[('equal', 0, 1, 0, 1),
1060
('replace', 1, 5, 1, 5),
1061
('equal', 5, 6, 5, 6),
948
1063
chk_ops('aBcDec', 'abcdec',
949
[('equal', 0,1, 0,1),
950
('replace', 1,2, 1,2),
952
('replace', 3,4, 3,4),
1064
[('equal', 0, 1, 0, 1),
1065
('replace', 1, 2, 1, 2),
1066
('equal', 2, 3, 2, 3),
1067
('replace', 3, 4, 3, 4),
1068
('equal', 4, 6, 4, 6),
955
1070
chk_ops('aBcdEcdFg', 'abcdecdfg',
956
[('equal', 0,1, 0,1),
957
('replace', 1,8, 1,8),
1071
[('equal', 0, 1, 0, 1),
1072
('replace', 1, 8, 1, 8),
1073
('equal', 8, 9, 8, 9)
960
1075
chk_ops('aBcdEeXcdFg', 'abcdecdfg',
961
[('equal', 0,1, 0,1),
962
('replace', 1,2, 1,2),
964
('delete', 4,5, 4,4),
966
('delete', 6,7, 5,5),
968
('replace', 9,10, 7,8),
969
('equal', 10,11, 8,9)
1076
[('equal', 0, 1, 0, 1),
1077
('replace', 1, 2, 1, 2),
1078
('equal', 2, 4, 2, 4),
1079
('delete', 4, 5, 4, 4),
1080
('equal', 5, 6, 4, 5),
1081
('delete', 6, 7, 5, 5),
1082
('equal', 7, 9, 5, 7),
1083
('replace', 9, 10, 7, 8),
1084
('equal', 10, 11, 8, 9)
972
1087
def test_grouped_opcodes(self):
973
1088
def chk_ops(a, b, expected_codes, n=3):
974
1089
s = self._PatienceSequenceMatcher(None, a, b)
975
self.assertEquals(expected_codes, list(s.get_grouped_opcodes(n)))
1090
self.assertEqual(expected_codes, list(s.get_grouped_opcodes(n)))
977
1092
chk_ops('', '', [])
978
1093
chk_ops([], [], [])
979
chk_ops('abc', '', [[('delete', 0,3, 0,0)]])
980
chk_ops('', 'abc', [[('insert', 0,0, 0,3)]])
1094
chk_ops('abc', '', [[('delete', 0, 3, 0, 0)]])
1095
chk_ops('', 'abc', [[('insert', 0, 0, 0, 3)]])
981
1096
chk_ops('abcd', 'abcd', [])
982
chk_ops('abcd', 'abce', [[('equal', 0,3, 0,3),
983
('replace', 3,4, 3,4)
1097
chk_ops('abcd', 'abce', [[('equal', 0, 3, 0, 3),
1098
('replace', 3, 4, 3, 4)
985
chk_ops('eabc', 'abce', [[('delete', 0,1, 0,0),
1100
chk_ops('eabc', 'abce', [[('delete', 0, 1, 0, 0),
1101
('equal', 1, 4, 0, 3),
1102
('insert', 4, 4, 3, 4)
989
1104
chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
990
[[('equal', 3,6, 3,6),
991
('insert', 6,6, 6,11),
992
('equal', 6,9, 11,14)
1105
[[('equal', 3, 6, 3, 6),
1106
('insert', 6, 6, 6, 11),
1107
('equal', 6, 9, 11, 14)
994
1109
chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
995
[[('equal', 2,6, 2,6),
996
('insert', 6,6, 6,11),
997
('equal', 6,10, 11,15)
1110
[[('equal', 2, 6, 2, 6),
1111
('insert', 6, 6, 6, 11),
1112
('equal', 6, 10, 11, 15)
999
1114
chk_ops('Xabcdef', 'abcdef',
1000
[[('delete', 0,1, 0,0),
1115
[[('delete', 0, 1, 0, 0),
1116
('equal', 1, 4, 0, 3)
1003
1118
chk_ops('abcdef', 'abcdefX',
1004
[[('equal', 3,6, 3,6),
1005
('insert', 6,6, 6,7)
1119
[[('equal', 3, 6, 3, 6),
1120
('insert', 6, 6, 6, 7)
1388
1502
if osutils.host_os_dereferences_symlinks():
1389
1503
self.assertTrue(os.path.samefile('tree/newname', new_path))
1390
1504
# make sure we can create files with the same parent directories
1391
diff_obj._prepare_files('file2-id', 'oldname2', 'newname2')
1505
diff_obj._prepare_files('oldname2', 'newname2', file_id='file2-id')
1508
class TestDiffFromToolEncodedFilename(tests.TestCaseWithTransport):
1510
def test_encodable_filename(self):
1511
# Just checks file path for external diff tool.
1512
# We cannot change CPython's internal encoding used by os.exec*.
1513
diffobj = diff.DiffFromTool(['dummy', '@old_path', '@new_path'],
1515
for _, scenario in EncodingAdapter.encoding_scenarios:
1516
encoding = scenario['encoding']
1517
dirname = scenario['info']['directory']
1518
filename = scenario['info']['filename']
1520
self.overrideAttr(diffobj, '_fenc', lambda: encoding)
1521
relpath = dirname + u'/' + filename
1522
fullpath = diffobj._safe_filename('safe', relpath)
1523
self.assertEqual(fullpath,
1524
fullpath.encode(encoding).decode(encoding))
1525
self.assertTrue(fullpath.startswith(diffobj._root + '/safe'))
1527
def test_unencodable_filename(self):
1528
diffobj = diff.DiffFromTool(['dummy', '@old_path', '@new_path'],
1530
for _, scenario in EncodingAdapter.encoding_scenarios:
1531
encoding = scenario['encoding']
1532
dirname = scenario['info']['directory']
1533
filename = scenario['info']['filename']
1535
if encoding == 'iso-8859-1':
1536
encoding = 'iso-8859-2'
1538
encoding = 'iso-8859-1'
1540
self.overrideAttr(diffobj, '_fenc', lambda: encoding)
1541
relpath = dirname + u'/' + filename
1542
fullpath = diffobj._safe_filename('safe', relpath)
1543
self.assertEqual(fullpath,
1544
fullpath.encode(encoding).decode(encoding))
1545
self.assertTrue(fullpath.startswith(diffobj._root + '/safe'))
1394
1548
class TestGetTreesAndBranchesToDiffLocked(tests.TestCaseWithTransport):
1396
1550
def call_gtabtd(self, path_list, revision_specs, old_url, new_url):
1397
"""Call get_trees_and_branches_to_diff_locked. Overridden by
1398
TestGetTreesAndBranchesToDiff.
1551
"""Call get_trees_and_branches_to_diff_locked."""
1400
1552
return diff.get_trees_and_branches_to_diff_locked(
1401
1553
path_list, revision_specs, old_url, new_url, self.addCleanup)