~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_diff.py

(jelmer) Support upgrading between the 2a and development-colo formats.
 (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
 
1
# Copyright (C) 2005-2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
import os
18
 
import os.path
19
18
from cStringIO import StringIO
20
 
import errno
21
19
import subprocess
22
 
from tempfile import TemporaryFile
23
 
 
24
 
from bzrlib import tests
25
 
from bzrlib.diff import (
26
 
    DiffFromTool,
27
 
    DiffPath,
28
 
    DiffSymlink,
29
 
    DiffTree,
30
 
    DiffText,
31
 
    external_diff,
32
 
    internal_diff,
33
 
    show_diff_trees,
34
 
    )
35
 
from bzrlib.errors import BinaryFile, NoDiff, ExecutableMissing
36
 
import bzrlib.osutils as osutils
37
 
import bzrlib.patiencediff
38
 
import bzrlib._patiencediff_py
39
 
from bzrlib.tests import (Feature, TestCase, TestCaseWithTransport,
40
 
                          TestCaseInTempDir, TestSkipped)
41
 
 
42
 
 
43
 
class _CompiledPatienceDiffFeature(Feature):
44
 
 
45
 
    def _probe(self):
46
 
        try:
47
 
            import bzrlib._patiencediff_c
48
 
        except ImportError:
49
 
            return False
50
 
        return True
51
 
 
52
 
    def feature_name(self):
53
 
        return 'bzrlib._patiencediff_c'
54
 
 
55
 
CompiledPatienceDiffFeature = _CompiledPatienceDiffFeature()
56
 
 
57
 
 
58
 
class _UnicodeFilename(Feature):
59
 
    """Does the filesystem support Unicode filenames?"""
60
 
 
61
 
    def _probe(self):
62
 
        try:
63
 
            os.stat(u'\u03b1')
64
 
        except UnicodeEncodeError:
65
 
            return False
66
 
        except (IOError, OSError):
67
 
            # The filesystem allows the Unicode filename but the file doesn't
68
 
            # exist.
69
 
            return True
70
 
        else:
71
 
            # The filesystem allows the Unicode filename and the file exists,
72
 
            # for some reason.
73
 
            return True
74
 
 
75
 
UnicodeFilename = _UnicodeFilename()
76
 
 
77
 
 
78
 
class TestUnicodeFilename(TestCase):
79
 
 
80
 
    def test_probe_passes(self):
81
 
        """UnicodeFilename._probe passes."""
82
 
        # We can't test much more than that because the behaviour depends
83
 
        # on the platform.
84
 
        UnicodeFilename._probe()
85
 
        
 
20
import sys
 
21
import tempfile
 
22
 
 
23
from bzrlib import (
 
24
    diff,
 
25
    errors,
 
26
    osutils,
 
27
    patiencediff,
 
28
    _patiencediff_py,
 
29
    revision as _mod_revision,
 
30
    revisionspec,
 
31
    revisiontree,
 
32
    tests,
 
33
    transform,
 
34
    )
 
35
from bzrlib.symbol_versioning import deprecated_in
 
36
from bzrlib.tests import features, EncodingAdapter
 
37
from bzrlib.tests.blackbox.test_diff import subst_dates
 
38
from bzrlib.tests import (
 
39
    features,
 
40
    )
 
41
 
86
42
 
87
43
def udiff_lines(old, new, allow_binary=False):
88
44
    output = StringIO()
89
 
    internal_diff('old', old, 'new', new, output, allow_binary)
 
45
    diff.internal_diff('old', old, 'new', new, output, allow_binary)
90
46
    output.seek(0, 0)
91
47
    return output.readlines()
92
48
 
96
52
        # StringIO has no fileno, so it tests a different codepath
97
53
        output = StringIO()
98
54
    else:
99
 
        output = TemporaryFile()
 
55
        output = tempfile.TemporaryFile()
100
56
    try:
101
 
        external_diff('old', old, 'new', new, output, diff_opts=['-u'])
102
 
    except NoDiff:
103
 
        raise TestSkipped('external "diff" not present to test')
 
57
        diff.external_diff('old', old, 'new', new, output, diff_opts=['-u'])
 
58
    except errors.NoDiff:
 
59
        raise tests.TestSkipped('external "diff" not present to test')
104
60
    output.seek(0, 0)
105
61
    lines = output.readlines()
106
62
    output.close()
107
63
    return lines
108
64
 
109
65
 
110
 
class TestDiff(TestCase):
 
66
class TestDiff(tests.TestCase):
111
67
 
112
68
    def test_add_nl(self):
113
69
        """diff generates a valid diff for patches that add a newline"""
149
105
            ## "Unterminated hunk header for patch:\n%s" % "".join(lines)
150
106
 
151
107
    def test_binary_lines(self):
152
 
        self.assertRaises(BinaryFile, udiff_lines, [1023 * 'a' + '\x00'], [])
153
 
        self.assertRaises(BinaryFile, udiff_lines, [], [1023 * 'a' + '\x00'])
154
 
        udiff_lines([1023 * 'a' + '\x00'], [], allow_binary=True)
155
 
        udiff_lines([], [1023 * 'a' + '\x00'], allow_binary=True)
 
108
        empty = []
 
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)
156
114
 
157
115
    def test_external_diff(self):
158
116
        lines = external_udiff_lines(['boo\n'], ['goo\n'])
168
126
        self.check_patch(lines)
169
127
 
170
128
    def test_external_diff_binary_lang_c(self):
171
 
        old_env = {}
172
129
        for lang in ('LANG', 'LC_ALL', 'LANGUAGE'):
173
 
            old_env[lang] = osutils.set_or_unset_env(lang, 'C')
174
 
        try:
175
 
            lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
176
 
            # Older versions of diffutils say "Binary files", newer
177
 
            # versions just say "Files".
178
 
            self.assertContainsRe(lines[0],
179
 
                                  '(Binary f|F)iles old and new differ\n')
180
 
            self.assertEquals(lines[1:], ['\n'])
181
 
        finally:
182
 
            for lang, old_val in old_env.iteritems():
183
 
                osutils.set_or_unset_env(lang, old_val)
 
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'])
184
136
 
185
137
    def test_no_external_diff(self):
186
138
        """Check that NoDiff is raised when diff is not available"""
187
 
        # Use os.environ['PATH'] to make sure no 'diff' command is available
188
 
        orig_path = os.environ['PATH']
189
 
        try:
190
 
            os.environ['PATH'] = ''
191
 
            self.assertRaises(NoDiff, external_diff,
192
 
                              'old', ['boo\n'], 'new', ['goo\n'],
193
 
                              StringIO(), diff_opts=['-u'])
194
 
        finally:
195
 
            os.environ['PATH'] = orig_path
196
 
        
 
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'])
 
145
 
197
146
    def test_internal_diff_default(self):
198
147
        # Default internal diff encoding is utf8
199
148
        output = StringIO()
200
 
        internal_diff(u'old_\xb5', ['old_text\n'],
201
 
                    u'new_\xe5', ['new_text\n'], output)
 
149
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
 
150
                           u'new_\xe5', ['new_text\n'], output)
202
151
        lines = output.getvalue().splitlines(True)
203
152
        self.check_patch(lines)
204
153
        self.assertEquals(['--- old_\xc2\xb5\n',
212
161
 
213
162
    def test_internal_diff_utf8(self):
214
163
        output = StringIO()
215
 
        internal_diff(u'old_\xb5', ['old_text\n'],
216
 
                    u'new_\xe5', ['new_text\n'], output,
217
 
                    path_encoding='utf8')
 
164
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
 
165
                           u'new_\xe5', ['new_text\n'], output,
 
166
                           path_encoding='utf8')
218
167
        lines = output.getvalue().splitlines(True)
219
168
        self.check_patch(lines)
220
169
        self.assertEquals(['--- old_\xc2\xb5\n',
228
177
 
229
178
    def test_internal_diff_iso_8859_1(self):
230
179
        output = StringIO()
231
 
        internal_diff(u'old_\xb5', ['old_text\n'],
232
 
                    u'new_\xe5', ['new_text\n'], output,
233
 
                    path_encoding='iso-8859-1')
 
180
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
 
181
                           u'new_\xe5', ['new_text\n'], output,
 
182
                           path_encoding='iso-8859-1')
234
183
        lines = output.getvalue().splitlines(True)
235
184
        self.check_patch(lines)
236
185
        self.assertEquals(['--- old_\xb5\n',
244
193
 
245
194
    def test_internal_diff_no_content(self):
246
195
        output = StringIO()
247
 
        internal_diff(u'old', [], u'new', [], output)
 
196
        diff.internal_diff(u'old', [], u'new', [], output)
248
197
        self.assertEqual('', output.getvalue())
249
198
 
250
199
    def test_internal_diff_no_changes(self):
251
200
        output = StringIO()
252
 
        internal_diff(u'old', ['text\n', 'contents\n'],
253
 
                      u'new', ['text\n', 'contents\n'],
254
 
                      output)
 
201
        diff.internal_diff(u'old', ['text\n', 'contents\n'],
 
202
                           u'new', ['text\n', 'contents\n'],
 
203
                           output)
255
204
        self.assertEqual('', output.getvalue())
256
205
 
257
206
    def test_internal_diff_returns_bytes(self):
258
207
        import StringIO
259
208
        output = StringIO.StringIO()
260
 
        internal_diff(u'old_\xb5', ['old_text\n'],
261
 
                    u'new_\xe5', ['new_text\n'], output)
262
 
        self.failUnless(isinstance(output.getvalue(), str),
 
209
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
 
210
                            u'new_\xe5', ['new_text\n'], output)
 
211
        self.assertIsInstance(output.getvalue(), str,
263
212
            'internal_diff should return bytestrings')
264
213
 
265
214
 
266
 
class TestDiffFiles(TestCaseInTempDir):
 
215
class TestDiffFiles(tests.TestCaseInTempDir):
267
216
 
268
217
    def test_external_diff_binary(self):
269
218
        """The output when using external diff should use diff's i18n error"""
282
231
        self.assertEqual(out.splitlines(True) + ['\n'], lines)
283
232
 
284
233
 
285
 
class TestShowDiffTreesHelper(TestCaseWithTransport):
286
 
    """Has a helper for running show_diff_trees"""
287
 
 
288
 
    def get_diff(self, tree1, tree2, specific_files=None, working_tree=None):
289
 
        output = StringIO()
290
 
        if working_tree is not None:
291
 
            extra_trees = (working_tree,)
292
 
        else:
293
 
            extra_trees = ()
294
 
        show_diff_trees(tree1, tree2, output, specific_files=specific_files,
295
 
                        extra_trees=extra_trees, old_label='old/',
296
 
                        new_label='new/')
297
 
        return output.getvalue()
298
 
 
299
 
 
300
 
class TestDiffDates(TestShowDiffTreesHelper):
 
234
def get_diff_as_string(tree1, tree2, specific_files=None, working_tree=None):
 
235
    output = StringIO()
 
236
    if working_tree is not None:
 
237
        extra_trees = (working_tree,)
 
238
    else:
 
239
        extra_trees = ()
 
240
    diff.show_diff_trees(tree1, tree2, output,
 
241
        specific_files=specific_files,
 
242
        extra_trees=extra_trees, old_label='old/',
 
243
        new_label='new/')
 
244
    return output.getvalue()
 
245
 
 
246
 
 
247
class TestDiffDates(tests.TestCaseWithTransport):
301
248
 
302
249
    def setUp(self):
303
250
        super(TestDiffDates, self).setUp()
338
285
        os.utime('file1', (1144195200, 1144195200)) # 2006-04-05 00:00:00 UTC
339
286
 
340
287
    def test_diff_rev_tree_working_tree(self):
341
 
        output = self.get_diff(self.wt.basis_tree(), self.wt)
 
288
        output = get_diff_as_string(self.wt.basis_tree(), self.wt)
342
289
        # note that the date for old/file1 is from rev 2 rather than from
343
290
        # the basis revision (rev 4)
344
291
        self.assertEqualDiff(output, '''\
354
301
    def test_diff_rev_tree_rev_tree(self):
355
302
        tree1 = self.b.repository.revision_tree('rev-2')
356
303
        tree2 = self.b.repository.revision_tree('rev-3')
357
 
        output = self.get_diff(tree1, tree2)
 
304
        output = get_diff_as_string(tree1, tree2)
358
305
        self.assertEqualDiff(output, '''\
359
306
=== modified file 'file2'
360
307
--- old/file2\t2006-04-01 00:00:00 +0000
364
311
+file2 contents at rev 3
365
312
 
366
313
''')
367
 
        
 
314
 
368
315
    def test_diff_add_files(self):
369
 
        tree1 = self.b.repository.revision_tree(None)
 
316
        tree1 = self.b.repository.revision_tree(_mod_revision.NULL_REVISION)
370
317
        tree2 = self.b.repository.revision_tree('rev-1')
371
 
        output = self.get_diff(tree1, tree2)
 
318
        output = get_diff_as_string(tree1, tree2)
372
319
        # the files have the epoch time stamp for the tree in which
373
320
        # they don't exist.
374
321
        self.assertEqualDiff(output, '''\
389
336
    def test_diff_remove_files(self):
390
337
        tree1 = self.b.repository.revision_tree('rev-3')
391
338
        tree2 = self.b.repository.revision_tree('rev-4')
392
 
        output = self.get_diff(tree1, tree2)
 
339
        output = get_diff_as_string(tree1, tree2)
393
340
        # the file has the epoch time stamp for the tree in which
394
341
        # it doesn't exist.
395
342
        self.assertEqualDiff(output, '''\
406
353
        self.wt.rename_one('file1', 'file1b')
407
354
        old_tree = self.b.repository.revision_tree('rev-1')
408
355
        new_tree = self.b.repository.revision_tree('rev-4')
409
 
        out = self.get_diff(old_tree, new_tree, specific_files=['file1b'], 
 
356
        out = get_diff_as_string(old_tree, new_tree, specific_files=['file1b'],
410
357
                            working_tree=self.wt)
411
358
        self.assertContainsRe(out, 'file1\t')
412
359
 
418
365
        self.wt.rename_one('file1', 'dir1/file1')
419
366
        old_tree = self.b.repository.revision_tree('rev-1')
420
367
        new_tree = self.b.repository.revision_tree('rev-4')
421
 
        out = self.get_diff(old_tree, new_tree, specific_files=['dir1'], 
 
368
        out = get_diff_as_string(old_tree, new_tree, specific_files=['dir1'],
422
369
                            working_tree=self.wt)
423
370
        self.assertContainsRe(out, 'file1\t')
424
 
        out = self.get_diff(old_tree, new_tree, specific_files=['dir2'], 
 
371
        out = get_diff_as_string(old_tree, new_tree, specific_files=['dir2'],
425
372
                            working_tree=self.wt)
426
373
        self.assertNotContainsRe(out, 'file1\t')
427
374
 
428
375
 
429
 
 
430
 
class TestShowDiffTrees(TestShowDiffTreesHelper):
 
376
class TestShowDiffTrees(tests.TestCaseWithTransport):
431
377
    """Direct tests for show_diff_trees"""
432
378
 
433
379
    def test_modified_file(self):
438
384
        tree.commit('one', rev_id='rev-1')
439
385
 
440
386
        self.build_tree_contents([('tree/file', 'new contents\n')])
441
 
        diff = self.get_diff(tree.basis_tree(), tree)
442
 
        self.assertContainsRe(diff, "=== modified file 'file'\n")
443
 
        self.assertContainsRe(diff, '--- old/file\t')
444
 
        self.assertContainsRe(diff, '\\+\\+\\+ new/file\t')
445
 
        self.assertContainsRe(diff, '-contents\n'
446
 
                                    '\\+new contents\n')
 
387
        d = get_diff_as_string(tree.basis_tree(), tree)
 
388
        self.assertContainsRe(d, "=== modified file 'file'\n")
 
389
        self.assertContainsRe(d, '--- old/file\t')
 
390
        self.assertContainsRe(d, '\\+\\+\\+ new/file\t')
 
391
        self.assertContainsRe(d, '-contents\n'
 
392
                                 '\\+new contents\n')
447
393
 
448
394
    def test_modified_file_in_renamed_dir(self):
449
395
        """Test when a file is modified in a renamed directory."""
455
401
 
456
402
        tree.rename_one('dir', 'other')
457
403
        self.build_tree_contents([('tree/other/file', 'new contents\n')])
458
 
        diff = self.get_diff(tree.basis_tree(), tree)
459
 
        self.assertContainsRe(diff, "=== renamed directory 'dir' => 'other'\n")
460
 
        self.assertContainsRe(diff, "=== modified file 'other/file'\n")
 
404
        d = get_diff_as_string(tree.basis_tree(), tree)
 
405
        self.assertContainsRe(d, "=== renamed directory 'dir' => 'other'\n")
 
406
        self.assertContainsRe(d, "=== modified file 'other/file'\n")
461
407
        # XXX: This is technically incorrect, because it used to be at another
462
408
        # location. What to do?
463
 
        self.assertContainsRe(diff, '--- old/dir/file\t')
464
 
        self.assertContainsRe(diff, '\\+\\+\\+ new/other/file\t')
465
 
        self.assertContainsRe(diff, '-contents\n'
466
 
                                    '\\+new contents\n')
 
409
        self.assertContainsRe(d, '--- old/dir/file\t')
 
410
        self.assertContainsRe(d, '\\+\\+\\+ new/other/file\t')
 
411
        self.assertContainsRe(d, '-contents\n'
 
412
                                 '\\+new contents\n')
467
413
 
468
414
    def test_renamed_directory(self):
469
415
        """Test when only a directory is only renamed."""
474
420
        tree.commit('one', rev_id='rev-1')
475
421
 
476
422
        tree.rename_one('dir', 'newdir')
477
 
        diff = self.get_diff(tree.basis_tree(), tree)
 
423
        d = get_diff_as_string(tree.basis_tree(), tree)
478
424
        # Renaming a directory should be a single "you renamed this dir" even
479
425
        # when there are files inside.
480
 
        self.assertEqual("=== renamed directory 'dir' => 'newdir'\n", diff)
 
426
        self.assertEqual(d, "=== renamed directory 'dir' => 'newdir'\n")
481
427
 
482
428
    def test_renamed_file(self):
483
429
        """Test when a file is only renamed."""
487
433
        tree.commit('one', rev_id='rev-1')
488
434
 
489
435
        tree.rename_one('file', 'newname')
490
 
        diff = self.get_diff(tree.basis_tree(), tree)
491
 
        self.assertContainsRe(diff, "=== renamed file 'file' => 'newname'\n")
 
436
        d = get_diff_as_string(tree.basis_tree(), tree)
 
437
        self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
492
438
        # We shouldn't have a --- or +++ line, because there is no content
493
439
        # change
494
 
        self.assertNotContainsRe(diff, '---')
 
440
        self.assertNotContainsRe(d, '---')
495
441
 
496
442
    def test_renamed_and_modified_file(self):
497
443
        """Test when a file is only renamed."""
502
448
 
503
449
        tree.rename_one('file', 'newname')
504
450
        self.build_tree_contents([('tree/newname', 'new contents\n')])
505
 
        diff = self.get_diff(tree.basis_tree(), tree)
506
 
        self.assertContainsRe(diff, "=== renamed file 'file' => 'newname'\n")
507
 
        self.assertContainsRe(diff, '--- old/file\t')
508
 
        self.assertContainsRe(diff, '\\+\\+\\+ new/newname\t')
509
 
        self.assertContainsRe(diff, '-contents\n'
510
 
                                    '\\+new contents\n')
 
451
        d = get_diff_as_string(tree.basis_tree(), tree)
 
452
        self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
 
453
        self.assertContainsRe(d, '--- old/file\t')
 
454
        self.assertContainsRe(d, '\\+\\+\\+ new/newname\t')
 
455
        self.assertContainsRe(d, '-contents\n'
 
456
                                 '\\+new contents\n')
 
457
 
 
458
 
 
459
    def test_internal_diff_exec_property(self):
 
460
        tree = self.make_branch_and_tree('tree')
 
461
 
 
462
        tt = transform.TreeTransform(tree)
 
463
        tt.new_file('a', tt.root, 'contents\n', 'a-id', True)
 
464
        tt.new_file('b', tt.root, 'contents\n', 'b-id', False)
 
465
        tt.new_file('c', tt.root, 'contents\n', 'c-id', True)
 
466
        tt.new_file('d', tt.root, 'contents\n', 'd-id', False)
 
467
        tt.new_file('e', tt.root, 'contents\n', 'control-e-id', True)
 
468
        tt.new_file('f', tt.root, 'contents\n', 'control-f-id', False)
 
469
        tt.apply()
 
470
        tree.commit('one', rev_id='rev-1')
 
471
 
 
472
        tt = transform.TreeTransform(tree)
 
473
        tt.set_executability(False, tt.trans_id_file_id('a-id'))
 
474
        tt.set_executability(True, tt.trans_id_file_id('b-id'))
 
475
        tt.set_executability(False, tt.trans_id_file_id('c-id'))
 
476
        tt.set_executability(True, tt.trans_id_file_id('d-id'))
 
477
        tt.apply()
 
478
        tree.rename_one('c', 'new-c')
 
479
        tree.rename_one('d', 'new-d')
 
480
 
 
481
        d = get_diff_as_string(tree.basis_tree(), tree)
 
482
 
 
483
        self.assertContainsRe(d, r"file 'a'.*\(properties changed:"
 
484
                                  ".*\+x to -x.*\)")
 
485
        self.assertContainsRe(d, r"file 'b'.*\(properties changed:"
 
486
                                  ".*-x to \+x.*\)")
 
487
        self.assertContainsRe(d, r"file 'c'.*\(properties changed:"
 
488
                                  ".*\+x to -x.*\)")
 
489
        self.assertContainsRe(d, r"file 'd'.*\(properties changed:"
 
490
                                  ".*-x to \+x.*\)")
 
491
        self.assertNotContainsRe(d, r"file 'e'")
 
492
        self.assertNotContainsRe(d, r"file 'f'")
511
493
 
512
494
    def test_binary_unicode_filenames(self):
513
495
        """Test that contents of files are *not* encoded in UTF-8 when there
514
496
        is a binary file in the diff.
515
497
        """
516
498
        # See https://bugs.launchpad.net/bugs/110092.
517
 
        self.requireFeature(UnicodeFilename)
 
499
        self.requireFeature(features.UnicodeFilenameFeature)
518
500
 
519
501
        # This bug isn't triggered with cStringIO.
520
502
        from StringIO import StringIO
528
510
        tree.add([alpha], ['file-id'])
529
511
        tree.add([omega], ['file-id-2'])
530
512
        diff_content = StringIO()
531
 
        show_diff_trees(tree.basis_tree(), tree, diff_content)
532
 
        diff = diff_content.getvalue()
533
 
        self.assertContainsRe(diff, r"=== added file '%s'" % alpha_utf8)
534
 
        self.assertContainsRe(
535
 
            diff, "Binary files a/%s.*and b/%s.* differ\n" % (alpha_utf8, alpha_utf8))
536
 
        self.assertContainsRe(diff, r"=== added file '%s'" % omega_utf8)
537
 
        self.assertContainsRe(diff, r"--- a/%s" % (omega_utf8,))
538
 
        self.assertContainsRe(diff, r"\+\+\+ b/%s" % (omega_utf8,))
 
513
        diff.show_diff_trees(tree.basis_tree(), tree, diff_content)
 
514
        d = diff_content.getvalue()
 
515
        self.assertContainsRe(d, r"=== added file '%s'" % alpha_utf8)
 
516
        self.assertContainsRe(d, "Binary files a/%s.*and b/%s.* differ\n"
 
517
                              % (alpha_utf8, alpha_utf8))
 
518
        self.assertContainsRe(d, r"=== added file '%s'" % omega_utf8)
 
519
        self.assertContainsRe(d, r"--- a/%s" % (omega_utf8,))
 
520
        self.assertContainsRe(d, r"\+\+\+ b/%s" % (omega_utf8,))
539
521
 
540
522
    def test_unicode_filename(self):
541
523
        """Test when the filename are unicode."""
542
 
        self.requireFeature(UnicodeFilename)
 
524
        self.requireFeature(features.UnicodeFilenameFeature)
543
525
 
544
526
        alpha, omega = u'\u03b1', u'\u03c9'
545
527
        autf8, outf8 = alpha.encode('utf8'), omega.encode('utf8')
560
542
        tree.add(['add_'+alpha], ['file-id'])
561
543
        self.build_tree_contents([('tree/mod_'+alpha, 'contents_mod\n')])
562
544
 
563
 
        diff = self.get_diff(tree.basis_tree(), tree)
564
 
        self.assertContainsRe(diff,
 
545
        d = get_diff_as_string(tree.basis_tree(), tree)
 
546
        self.assertContainsRe(d,
565
547
                "=== renamed file 'ren_%s' => 'ren_%s'\n"%(autf8, outf8))
566
 
        self.assertContainsRe(diff, "=== added file 'add_%s'"%autf8)
567
 
        self.assertContainsRe(diff, "=== modified file 'mod_%s'"%autf8)
568
 
        self.assertContainsRe(diff, "=== removed file 'del_%s'"%autf8)
569
 
 
570
 
 
571
 
class DiffWasIs(DiffPath):
 
548
        self.assertContainsRe(d, "=== added file 'add_%s'"%autf8)
 
549
        self.assertContainsRe(d, "=== modified file 'mod_%s'"%autf8)
 
550
        self.assertContainsRe(d, "=== removed file 'del_%s'"%autf8)
 
551
 
 
552
    def test_unicode_filename_path_encoding(self):
 
553
        """Test for bug #382699: unicode filenames on Windows should be shown
 
554
        in user encoding.
 
555
        """
 
556
        self.requireFeature(features.UnicodeFilenameFeature)
 
557
        # The word 'test' in Russian
 
558
        _russian_test = u'\u0422\u0435\u0441\u0442'
 
559
        directory = _russian_test + u'/'
 
560
        test_txt = _russian_test + u'.txt'
 
561
        u1234 = u'\u1234.txt'
 
562
 
 
563
        tree = self.make_branch_and_tree('.')
 
564
        self.build_tree_contents([
 
565
            (test_txt, 'foo\n'),
 
566
            (u1234, 'foo\n'),
 
567
            (directory, None),
 
568
            ])
 
569
        tree.add([test_txt, u1234, directory])
 
570
 
 
571
        sio = StringIO()
 
572
        diff.show_diff_trees(tree.basis_tree(), tree, sio,
 
573
            path_encoding='cp1251')
 
574
 
 
575
        output = subst_dates(sio.getvalue())
 
576
        shouldbe = ('''\
 
577
=== added directory '%(directory)s'
 
578
=== added file '%(test_txt)s'
 
579
--- a/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
580
+++ b/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
581
@@ -0,0 +1,1 @@
 
582
+foo
 
583
 
 
584
=== added file '?.txt'
 
585
--- a/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
586
+++ b/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
587
@@ -0,0 +1,1 @@
 
588
+foo
 
589
 
 
590
''' % {'directory': _russian_test.encode('cp1251'),
 
591
       'test_txt': test_txt.encode('cp1251'),
 
592
      })
 
593
        self.assertEqualDiff(output, shouldbe)
 
594
 
 
595
 
 
596
class DiffWasIs(diff.DiffPath):
572
597
 
573
598
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
574
599
        self.to_file.write('was: ')
578
603
        pass
579
604
 
580
605
 
581
 
class TestDiffTree(TestCaseWithTransport):
 
606
class TestDiffTree(tests.TestCaseWithTransport):
582
607
 
583
608
    def setUp(self):
584
 
        TestCaseWithTransport.setUp(self)
 
609
        super(TestDiffTree, self).setUp()
585
610
        self.old_tree = self.make_branch_and_tree('old-tree')
586
611
        self.old_tree.lock_write()
587
612
        self.addCleanup(self.old_tree.unlock)
588
613
        self.new_tree = self.make_branch_and_tree('new-tree')
589
614
        self.new_tree.lock_write()
590
615
        self.addCleanup(self.new_tree.unlock)
591
 
        self.differ = DiffTree(self.old_tree, self.new_tree, StringIO())
 
616
        self.differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
592
617
 
593
618
    def test_diff_text(self):
594
619
        self.build_tree_contents([('old-tree/olddir/',),
599
624
                                  ('new-tree/newdir/newfile', 'new\n')])
600
625
        self.new_tree.add('newdir')
601
626
        self.new_tree.add('newdir/newfile', 'file-id')
602
 
        differ = DiffText(self.old_tree, self.new_tree, StringIO())
 
627
        differ = diff.DiffText(self.old_tree, self.new_tree, StringIO())
603
628
        differ.diff_text('file-id', None, 'old label', 'new label')
604
629
        self.assertEqual(
605
630
            '--- old label\n+++ new label\n@@ -1,1 +0,0 @@\n-old\n\n',
634
659
        self.assertContainsRe(self.differ.to_file.getvalue(), '\+contents')
635
660
 
636
661
    def test_diff_symlink(self):
637
 
        differ = DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
662
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
638
663
        differ.diff_symlink('old target', None)
639
664
        self.assertEqual("=== target was 'old target'\n",
640
665
                         differ.to_file.getvalue())
641
666
 
642
 
        differ = DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
667
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
643
668
        differ.diff_symlink(None, 'new target')
644
669
        self.assertEqual("=== target is 'new target'\n",
645
670
                         differ.to_file.getvalue())
646
671
 
647
 
        differ = DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
672
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
648
673
        differ.diff_symlink('old target', 'new target')
649
674
        self.assertEqual("=== target changed 'old target' => 'new target'\n",
650
675
                         differ.to_file.getvalue())
665
690
             ' \@\@\n-old\n\+new\n\n')
666
691
 
667
692
    def test_diff_kind_change(self):
668
 
        self.requireFeature(tests.SymlinkFeature)
 
693
        self.requireFeature(features.SymlinkFeature)
669
694
        self.build_tree_contents([('old-tree/olddir/',),
670
695
                                  ('old-tree/olddir/oldfile', 'old\n')])
671
696
        self.old_tree.add('olddir')
680
705
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+0,0'
681
706
             ' \@\@\n-old\n\n')
682
707
        self.assertContainsRe(self.differ.to_file.getvalue(),
683
 
                              "=== target is 'new'\n")
 
708
                              "=== target is u'new'\n")
684
709
 
685
710
    def test_diff_directory(self):
686
711
        self.build_tree(['new-tree/new-dir/'])
700
725
 
701
726
    def test_register_diff(self):
702
727
        self.create_old_new()
703
 
        old_diff_factories = DiffTree.diff_factories
704
 
        DiffTree.diff_factories=old_diff_factories[:]
705
 
        DiffTree.diff_factories.insert(0, DiffWasIs.from_diff_tree)
 
728
        old_diff_factories = diff.DiffTree.diff_factories
 
729
        diff.DiffTree.diff_factories=old_diff_factories[:]
 
730
        diff.DiffTree.diff_factories.insert(0, DiffWasIs.from_diff_tree)
706
731
        try:
707
 
            differ = DiffTree(self.old_tree, self.new_tree, StringIO())
 
732
            differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
708
733
        finally:
709
 
            DiffTree.diff_factories = old_diff_factories
 
734
            diff.DiffTree.diff_factories = old_diff_factories
710
735
        differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
711
736
        self.assertNotContainsRe(
712
737
            differ.to_file.getvalue(),
717
742
 
718
743
    def test_extra_factories(self):
719
744
        self.create_old_new()
720
 
        differ = DiffTree(self.old_tree, self.new_tree, StringIO(),
721
 
                            extra_factories=[DiffWasIs.from_diff_tree])
 
745
        differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO(),
 
746
                               extra_factories=[DiffWasIs.from_diff_tree])
722
747
        differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
723
748
        self.assertNotContainsRe(
724
749
            differ.to_file.getvalue(),
737
762
            '.*a-file(.|\n)*b-file')
738
763
 
739
764
 
740
 
class TestPatienceDiffLib(TestCase):
 
765
class TestPatienceDiffLib(tests.TestCase):
741
766
 
742
767
    def setUp(self):
743
768
        super(TestPatienceDiffLib, self).setUp()
744
 
        self._unique_lcs = bzrlib._patiencediff_py.unique_lcs_py
745
 
        self._recurse_matches = bzrlib._patiencediff_py.recurse_matches_py
 
769
        self._unique_lcs = _patiencediff_py.unique_lcs_py
 
770
        self._recurse_matches = _patiencediff_py.recurse_matches_py
746
771
        self._PatienceSequenceMatcher = \
747
 
            bzrlib._patiencediff_py.PatienceSequenceMatcher_py
 
772
            _patiencediff_py.PatienceSequenceMatcher_py
 
773
 
 
774
    def test_diff_unicode_string(self):
 
775
        a = ''.join([unichr(i) for i in range(4000, 4500, 3)])
 
776
        b = ''.join([unichr(i) for i in range(4300, 4800, 2)])
 
777
        sm = self._PatienceSequenceMatcher(None, a, b)
 
778
        mb = sm.get_matching_blocks()
 
779
        self.assertEquals(35, len(mb))
748
780
 
749
781
    def test_unique_lcs(self):
750
782
        unique_lcs = self._unique_lcs
756
788
        self.assertEquals(unique_lcs('ab', 'ab'), [(0,0), (1,1)])
757
789
        self.assertEquals(unique_lcs('abcde', 'cdeab'), [(2,0), (3,1), (4,2)])
758
790
        self.assertEquals(unique_lcs('cdeab', 'abcde'), [(0,2), (1,3), (2,4)])
759
 
        self.assertEquals(unique_lcs('abXde', 'abYde'), [(0,0), (1,1), 
 
791
        self.assertEquals(unique_lcs('abXde', 'abYde'), [(0,0), (1,1),
760
792
                                                         (3,3), (4,4)])
761
793
        self.assertEquals(unique_lcs('acbac', 'abc'), [(2,1)])
762
794
 
777
809
        test_one('abcdbce', 'afbcgdbce', [(0,0), (1, 2), (2, 3), (3, 5),
778
810
                                          (4, 6), (5, 7), (6, 8)])
779
811
 
780
 
        # recurse_matches doesn't match non-unique 
 
812
        # recurse_matches doesn't match non-unique
781
813
        # lines surrounded by bogus text.
782
814
        # The update has been done in patiencediff.SequenceMatcher instead
783
815
 
920
952
                 ('delete', 1,2, 1,1),
921
953
                 ('equal',  2,3, 1,2),
922
954
                ])
923
 
        chk_ops('aBccDe', 'abccde', 
 
955
        chk_ops('aBccDe', 'abccde',
924
956
                [('equal',   0,1, 0,1),
925
957
                 ('replace', 1,5, 1,5),
926
958
                 ('equal',   5,6, 5,6),
927
959
                ])
928
 
        chk_ops('aBcDec', 'abcdec', 
 
960
        chk_ops('aBcDec', 'abcdec',
929
961
                [('equal',   0,1, 0,1),
930
962
                 ('replace', 1,2, 1,2),
931
963
                 ('equal',   2,3, 2,3),
932
964
                 ('replace', 3,4, 3,4),
933
965
                 ('equal',   4,6, 4,6),
934
966
                ])
935
 
        chk_ops('aBcdEcdFg', 'abcdecdfg', 
 
967
        chk_ops('aBcdEcdFg', 'abcdecdfg',
936
968
                [('equal',   0,1, 0,1),
937
969
                 ('replace', 1,8, 1,8),
938
970
                 ('equal',   8,9, 8,9)
939
971
                ])
940
 
        chk_ops('aBcdEeXcdFg', 'abcdecdfg', 
 
972
        chk_ops('aBcdEeXcdFg', 'abcdecdfg',
941
973
                [('equal',   0,1, 0,1),
942
974
                 ('replace', 1,2, 1,2),
943
975
                 ('equal',   2,4, 2,4),
1003
1035
    """
1004
1036
    gnxrf_netf = ['svyr*']
1005
1037
    gnxrf_bcgvbaf = ['ab-erphefr']
1006
 
  
 
1038
 
1007
1039
    qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr):
1008
1040
        sebz omeyvo.nqq vzcbeg fzneg_nqq, nqq_ercbegre_cevag, nqq_ercbegre_ahyy
1009
1041
        vs vf_dhvrg():
1017
1049
'''.splitlines(True), '''\
1018
1050
    trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
1019
1051
 
1020
 
    --qel-eha jvyy fubj juvpu svyrf jbhyq or nqqrq, ohg abg npghnyyl 
 
1052
    --qel-eha jvyy fubj juvpu svyrf jbhyq or nqqrq, ohg abg npghnyyl
1021
1053
    nqq gurz.
1022
1054
    """
1023
1055
    gnxrf_netf = ['svyr*']
1050
1082
                 'how are you today?\n']
1051
1083
        txt_b = ['hello there\n',
1052
1084
                 'how are you today?\n']
1053
 
        unified_diff = bzrlib.patiencediff.unified_diff
 
1085
        unified_diff = patiencediff.unified_diff
1054
1086
        psm = self._PatienceSequenceMatcher
1055
 
        self.assertEquals([ '---  \n',
1056
 
                           '+++  \n',
 
1087
        self.assertEquals(['--- \n',
 
1088
                           '+++ \n',
1057
1089
                           '@@ -1,3 +1,2 @@\n',
1058
1090
                           ' hello there\n',
1059
1091
                           '-world\n',
1064
1096
        txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
1065
1097
        txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
1066
1098
        # This is the result with LongestCommonSubstring matching
1067
 
        self.assertEquals(['---  \n',
1068
 
                           '+++  \n',
 
1099
        self.assertEquals(['--- \n',
 
1100
                           '+++ \n',
1069
1101
                           '@@ -1,6 +1,11 @@\n',
1070
1102
                           ' a\n',
1071
1103
                           ' b\n',
1080
1112
                           ' f\n']
1081
1113
                          , list(unified_diff(txt_a, txt_b)))
1082
1114
        # And the patience diff
1083
 
        self.assertEquals(['---  \n',
1084
 
                           '+++  \n',
 
1115
        self.assertEquals(['--- \n',
 
1116
                           '+++ \n',
1085
1117
                           '@@ -4,6 +4,11 @@\n',
1086
1118
                           ' d\n',
1087
1119
                           ' e\n',
1098
1130
                          , list(unified_diff(txt_a, txt_b,
1099
1131
                                 sequencematcher=psm)))
1100
1132
 
 
1133
    def test_patience_unified_diff_with_dates(self):
 
1134
        txt_a = ['hello there\n',
 
1135
                 'world\n',
 
1136
                 'how are you today?\n']
 
1137
        txt_b = ['hello there\n',
 
1138
                 'how are you today?\n']
 
1139
        unified_diff = patiencediff.unified_diff
 
1140
        psm = self._PatienceSequenceMatcher
 
1141
        self.assertEquals(['--- a\t2008-08-08\n',
 
1142
                           '+++ b\t2008-09-09\n',
 
1143
                           '@@ -1,3 +1,2 @@\n',
 
1144
                           ' hello there\n',
 
1145
                           '-world\n',
 
1146
                           ' how are you today?\n'
 
1147
                          ]
 
1148
                          , list(unified_diff(txt_a, txt_b,
 
1149
                                 fromfile='a', tofile='b',
 
1150
                                 fromfiledate='2008-08-08',
 
1151
                                 tofiledate='2008-09-09',
 
1152
                                 sequencematcher=psm)))
 
1153
 
1101
1154
 
1102
1155
class TestPatienceDiffLib_c(TestPatienceDiffLib):
1103
1156
 
1104
 
    _test_needs_features = [CompiledPatienceDiffFeature]
 
1157
    _test_needs_features = [features.compiled_patiencediff_feature]
1105
1158
 
1106
1159
    def setUp(self):
1107
1160
        super(TestPatienceDiffLib_c, self).setUp()
1108
 
        import bzrlib._patiencediff_c
1109
 
        self._unique_lcs = bzrlib._patiencediff_c.unique_lcs_c
1110
 
        self._recurse_matches = bzrlib._patiencediff_c.recurse_matches_c
 
1161
        from bzrlib import _patiencediff_c
 
1162
        self._unique_lcs = _patiencediff_c.unique_lcs_c
 
1163
        self._recurse_matches = _patiencediff_c.recurse_matches_c
1111
1164
        self._PatienceSequenceMatcher = \
1112
 
            bzrlib._patiencediff_c.PatienceSequenceMatcher_c
 
1165
            _patiencediff_c.PatienceSequenceMatcher_c
1113
1166
 
1114
1167
    def test_unhashable(self):
1115
1168
        """We should get a proper exception here."""
1125
1178
                                         None, ['valid'], ['valid', []])
1126
1179
 
1127
1180
 
1128
 
class TestPatienceDiffLibFiles(TestCaseInTempDir):
 
1181
class TestPatienceDiffLibFiles(tests.TestCaseInTempDir):
1129
1182
 
1130
1183
    def setUp(self):
1131
1184
        super(TestPatienceDiffLibFiles, self).setUp()
1132
1185
        self._PatienceSequenceMatcher = \
1133
 
            bzrlib._patiencediff_py.PatienceSequenceMatcher_py
 
1186
            _patiencediff_py.PatienceSequenceMatcher_py
1134
1187
 
1135
1188
    def test_patience_unified_diff_files(self):
1136
1189
        txt_a = ['hello there\n',
1141
1194
        open('a1', 'wb').writelines(txt_a)
1142
1195
        open('b1', 'wb').writelines(txt_b)
1143
1196
 
1144
 
        unified_diff_files = bzrlib.patiencediff.unified_diff_files
 
1197
        unified_diff_files = patiencediff.unified_diff_files
1145
1198
        psm = self._PatienceSequenceMatcher
1146
 
        self.assertEquals(['--- a1 \n',
1147
 
                           '+++ b1 \n',
 
1199
        self.assertEquals(['--- a1\n',
 
1200
                           '+++ b1\n',
1148
1201
                           '@@ -1,3 +1,2 @@\n',
1149
1202
                           ' hello there\n',
1150
1203
                           '-world\n',
1159
1212
        open('b2', 'wb').writelines(txt_b)
1160
1213
 
1161
1214
        # This is the result with LongestCommonSubstring matching
1162
 
        self.assertEquals(['--- a2 \n',
1163
 
                           '+++ b2 \n',
 
1215
        self.assertEquals(['--- a2\n',
 
1216
                           '+++ b2\n',
1164
1217
                           '@@ -1,6 +1,11 @@\n',
1165
1218
                           ' a\n',
1166
1219
                           ' b\n',
1176
1229
                          , list(unified_diff_files('a2', 'b2')))
1177
1230
 
1178
1231
        # And the patience diff
1179
 
        self.assertEquals(['--- a2 \n',
1180
 
                           '+++ b2 \n',
 
1232
        self.assertEquals(['--- a2\n',
 
1233
                           '+++ b2\n',
1181
1234
                           '@@ -4,6 +4,11 @@\n',
1182
1235
                           ' d\n',
1183
1236
                           ' e\n',
1197
1250
 
1198
1251
class TestPatienceDiffLibFiles_c(TestPatienceDiffLibFiles):
1199
1252
 
1200
 
    _test_needs_features = [CompiledPatienceDiffFeature]
 
1253
    _test_needs_features = [features.compiled_patiencediff_feature]
1201
1254
 
1202
1255
    def setUp(self):
1203
1256
        super(TestPatienceDiffLibFiles_c, self).setUp()
1204
 
        import bzrlib._patiencediff_c
 
1257
        from bzrlib import _patiencediff_c
1205
1258
        self._PatienceSequenceMatcher = \
1206
 
            bzrlib._patiencediff_c.PatienceSequenceMatcher_c
1207
 
 
1208
 
 
1209
 
class TestUsingCompiledIfAvailable(TestCase):
 
1259
            _patiencediff_c.PatienceSequenceMatcher_c
 
1260
 
 
1261
 
 
1262
class TestUsingCompiledIfAvailable(tests.TestCase):
1210
1263
 
1211
1264
    def test_PatienceSequenceMatcher(self):
1212
 
        if CompiledPatienceDiffFeature.available():
 
1265
        if features.compiled_patiencediff_feature.available():
1213
1266
            from bzrlib._patiencediff_c import PatienceSequenceMatcher_c
1214
1267
            self.assertIs(PatienceSequenceMatcher_c,
1215
 
                          bzrlib.patiencediff.PatienceSequenceMatcher)
 
1268
                          patiencediff.PatienceSequenceMatcher)
1216
1269
        else:
1217
1270
            from bzrlib._patiencediff_py import PatienceSequenceMatcher_py
1218
1271
            self.assertIs(PatienceSequenceMatcher_py,
1219
 
                          bzrlib.patiencediff.PatienceSequenceMatcher)
 
1272
                          patiencediff.PatienceSequenceMatcher)
1220
1273
 
1221
1274
    def test_unique_lcs(self):
1222
 
        if CompiledPatienceDiffFeature.available():
 
1275
        if features.compiled_patiencediff_feature.available():
1223
1276
            from bzrlib._patiencediff_c import unique_lcs_c
1224
1277
            self.assertIs(unique_lcs_c,
1225
 
                          bzrlib.patiencediff.unique_lcs)
 
1278
                          patiencediff.unique_lcs)
1226
1279
        else:
1227
1280
            from bzrlib._patiencediff_py import unique_lcs_py
1228
1281
            self.assertIs(unique_lcs_py,
1229
 
                          bzrlib.patiencediff.unique_lcs)
 
1282
                          patiencediff.unique_lcs)
1230
1283
 
1231
1284
    def test_recurse_matches(self):
1232
 
        if CompiledPatienceDiffFeature.available():
 
1285
        if features.compiled_patiencediff_feature.available():
1233
1286
            from bzrlib._patiencediff_c import recurse_matches_c
1234
1287
            self.assertIs(recurse_matches_c,
1235
 
                          bzrlib.patiencediff.recurse_matches)
 
1288
                          patiencediff.recurse_matches)
1236
1289
        else:
1237
1290
            from bzrlib._patiencediff_py import recurse_matches_py
1238
1291
            self.assertIs(recurse_matches_py,
1239
 
                          bzrlib.patiencediff.recurse_matches)
1240
 
 
1241
 
 
1242
 
class TestDiffFromTool(TestCaseWithTransport):
 
1292
                          patiencediff.recurse_matches)
 
1293
 
 
1294
 
 
1295
class TestDiffFromTool(tests.TestCaseWithTransport):
1243
1296
 
1244
1297
    def test_from_string(self):
1245
 
        diff_obj = DiffFromTool.from_string('diff', None, None, None)
 
1298
        diff_obj = diff.DiffFromTool.from_string('diff', None, None, None)
1246
1299
        self.addCleanup(diff_obj.finish)
1247
 
        self.assertEqual(['diff', '%(old_path)s', '%(new_path)s'],
 
1300
        self.assertEqual(['diff', '@old_path', '@new_path'],
1248
1301
            diff_obj.command_template)
1249
1302
 
1250
1303
    def test_from_string_u5(self):
1251
 
        diff_obj = DiffFromTool.from_string('diff -u\\ 5', None, None, None)
 
1304
        diff_obj = diff.DiffFromTool.from_string('diff "-u 5"',
 
1305
                                                 None, None, None)
1252
1306
        self.addCleanup(diff_obj.finish)
1253
 
        self.assertEqual(['diff', '-u 5', '%(old_path)s', '%(new_path)s'],
 
1307
        self.assertEqual(['diff', '-u 5', '@old_path', '@new_path'],
1254
1308
                         diff_obj.command_template)
1255
1309
        self.assertEqual(['diff', '-u 5', 'old-path', 'new-path'],
1256
1310
                         diff_obj._get_command('old-path', 'new-path'))
1257
1311
 
 
1312
    def test_from_string_path_with_backslashes(self):
 
1313
        self.requireFeature(features.backslashdir_feature)
 
1314
        tool = 'C:\\Tools\\Diff.exe'
 
1315
        diff_obj = diff.DiffFromTool.from_string(tool, None, None, None)
 
1316
        self.addCleanup(diff_obj.finish)
 
1317
        self.assertEqual(['C:\\Tools\\Diff.exe', '@old_path', '@new_path'],
 
1318
                         diff_obj.command_template)
 
1319
        self.assertEqual(['C:\\Tools\\Diff.exe', 'old-path', 'new-path'],
 
1320
                         diff_obj._get_command('old-path', 'new-path'))
 
1321
 
1258
1322
    def test_execute(self):
1259
1323
        output = StringIO()
1260
 
        diff_obj = DiffFromTool(['python', '-c',
1261
 
                                 'print "%(old_path)s %(new_path)s"'],
1262
 
                                None, None, output)
 
1324
        diff_obj = diff.DiffFromTool(['python', '-c',
 
1325
                                      'print "@old_path @new_path"'],
 
1326
                                     None, None, output)
1263
1327
        self.addCleanup(diff_obj.finish)
1264
1328
        diff_obj._execute('old', 'new')
1265
1329
        self.assertEqual(output.getvalue().rstrip(), 'old new')
1266
1330
 
1267
1331
    def test_excute_missing(self):
1268
 
        diff_obj = DiffFromTool(['a-tool-which-is-unlikely-to-exist'],
1269
 
                                None, None, None)
 
1332
        diff_obj = diff.DiffFromTool(['a-tool-which-is-unlikely-to-exist'],
 
1333
                                     None, None, None)
1270
1334
        self.addCleanup(diff_obj.finish)
1271
 
        e = self.assertRaises(ExecutableMissing, diff_obj._execute, 'old',
1272
 
                              'new')
 
1335
        e = self.assertRaises(errors.ExecutableMissing, diff_obj._execute,
 
1336
                              'old', 'new')
1273
1337
        self.assertEqual('a-tool-which-is-unlikely-to-exist could not be found'
1274
1338
                         ' on this machine', str(e))
1275
1339
 
 
1340
    def test_prepare_files_creates_paths_readable_by_windows_tool(self):
 
1341
        self.requireFeature(features.AttribFeature)
 
1342
        output = StringIO()
 
1343
        tree = self.make_branch_and_tree('tree')
 
1344
        self.build_tree_contents([('tree/file', 'content')])
 
1345
        tree.add('file', 'file-id')
 
1346
        tree.commit('old tree')
 
1347
        tree.lock_read()
 
1348
        self.addCleanup(tree.unlock)
 
1349
        basis_tree = tree.basis_tree()
 
1350
        basis_tree.lock_read()
 
1351
        self.addCleanup(basis_tree.unlock)
 
1352
        diff_obj = diff.DiffFromTool(['python', '-c',
 
1353
                                      'print "@old_path @new_path"'],
 
1354
                                     basis_tree, tree, output)
 
1355
        diff_obj._prepare_files('file-id', 'file', 'file')
 
1356
        # The old content should be readonly
 
1357
        self.assertReadableByAttrib(diff_obj._root, 'old\\file',
 
1358
                                    r'R.*old\\file$')
 
1359
        # The new content should use the tree object, not a 'new' file anymore
 
1360
        self.assertEndsWith(tree.basedir, 'work/tree')
 
1361
        self.assertReadableByAttrib(tree.basedir, 'file', r'work\\tree\\file$')
 
1362
 
 
1363
    def assertReadableByAttrib(self, cwd, relpath, regex):
 
1364
        proc = subprocess.Popen(['attrib', relpath],
 
1365
                                stdout=subprocess.PIPE,
 
1366
                                cwd=cwd)
 
1367
        (result, err) = proc.communicate()
 
1368
        self.assertContainsRe(result.replace('\r\n', '\n'), regex)
 
1369
 
1276
1370
    def test_prepare_files(self):
1277
1371
        output = StringIO()
1278
1372
        tree = self.make_branch_and_tree('tree')
1279
1373
        self.build_tree_contents([('tree/oldname', 'oldcontent')])
 
1374
        self.build_tree_contents([('tree/oldname2', 'oldcontent2')])
1280
1375
        tree.add('oldname', 'file-id')
1281
 
        tree.commit('old tree', timestamp=0)
 
1376
        tree.add('oldname2', 'file2-id')
 
1377
        # Earliest allowable date on FAT32 filesystems is 1980-01-01
 
1378
        tree.commit('old tree', timestamp=315532800)
1282
1379
        tree.rename_one('oldname', 'newname')
 
1380
        tree.rename_one('oldname2', 'newname2')
1283
1381
        self.build_tree_contents([('tree/newname', 'newcontent')])
 
1382
        self.build_tree_contents([('tree/newname2', 'newcontent2')])
1284
1383
        old_tree = tree.basis_tree()
1285
1384
        old_tree.lock_read()
1286
1385
        self.addCleanup(old_tree.unlock)
1287
1386
        tree.lock_read()
1288
1387
        self.addCleanup(tree.unlock)
1289
 
        diff_obj = DiffFromTool(['python', '-c',
1290
 
                                 'print "%(old_path)s %(new_path)s"'],
1291
 
                                old_tree, tree, output)
 
1388
        diff_obj = diff.DiffFromTool(['python', '-c',
 
1389
                                      'print "@old_path @new_path"'],
 
1390
                                     old_tree, tree, output)
1292
1391
        self.addCleanup(diff_obj.finish)
1293
1392
        self.assertContainsRe(diff_obj._root, 'bzr-diff-[^/]*')
1294
1393
        old_path, new_path = diff_obj._prepare_files('file-id', 'oldname',
1295
1394
                                                     'newname')
1296
1395
        self.assertContainsRe(old_path, 'old/oldname$')
1297
 
        self.assertEqual(0, os.stat(old_path).st_mtime)
1298
 
        self.assertContainsRe(new_path, 'new/newname$')
 
1396
        self.assertEqual(315532800, os.stat(old_path).st_mtime)
 
1397
        self.assertContainsRe(new_path, 'tree/newname$')
1299
1398
        self.assertFileEqual('oldcontent', old_path)
1300
1399
        self.assertFileEqual('newcontent', new_path)
1301
 
        if osutils.has_symlinks():
 
1400
        if osutils.host_os_dereferences_symlinks():
1302
1401
            self.assertTrue(os.path.samefile('tree/newname', new_path))
1303
1402
        # make sure we can create files with the same parent directories
1304
 
        diff_obj._prepare_files('file-id', 'oldname2', 'newname2')
 
1403
        diff_obj._prepare_files('file2-id', 'oldname2', 'newname2')
 
1404
 
 
1405
 
 
1406
class TestDiffFromToolEncodedFilename(tests.TestCaseWithTransport):
 
1407
 
 
1408
    def test_encodable_filename(self):
 
1409
        # Just checks file path for external diff tool.
 
1410
        # We cannot change CPython's internal encoding used by os.exec*.
 
1411
        import sys
 
1412
        diffobj = diff.DiffFromTool(['dummy', '@old_path', '@new_path'],
 
1413
                                    None, None, None)
 
1414
        for _, scenario in EncodingAdapter.encoding_scenarios:
 
1415
            encoding = scenario['encoding']
 
1416
            dirname  = scenario['info']['directory']
 
1417
            filename = scenario['info']['filename']
 
1418
 
 
1419
            self.overrideAttr(diffobj, '_fenc', lambda: encoding)
 
1420
            relpath = dirname + u'/' + filename
 
1421
            fullpath = diffobj._safe_filename('safe', relpath)
 
1422
            self.assertEqual(
 
1423
                    fullpath,
 
1424
                    fullpath.encode(encoding).decode(encoding)
 
1425
                    )
 
1426
            self.assert_(fullpath.startswith(diffobj._root + '/safe'))
 
1427
 
 
1428
    def test_unencodable_filename(self):
 
1429
        import sys
 
1430
        diffobj = diff.DiffFromTool(['dummy', '@old_path', '@new_path'],
 
1431
                                    None, None, None)
 
1432
        for _, scenario in EncodingAdapter.encoding_scenarios:
 
1433
            encoding = scenario['encoding']
 
1434
            dirname  = scenario['info']['directory']
 
1435
            filename = scenario['info']['filename']
 
1436
 
 
1437
            if encoding == 'iso-8859-1':
 
1438
                encoding = 'iso-8859-2'
 
1439
            else:
 
1440
                encoding = 'iso-8859-1'
 
1441
 
 
1442
            self.overrideAttr(diffobj, '_fenc', lambda: encoding)
 
1443
            relpath = dirname + u'/' + filename
 
1444
            fullpath = diffobj._safe_filename('safe', relpath)
 
1445
            self.assertEqual(
 
1446
                    fullpath,
 
1447
                    fullpath.encode(encoding).decode(encoding)
 
1448
                    )
 
1449
            self.assert_(fullpath.startswith(diffobj._root + '/safe'))
 
1450
 
 
1451
 
 
1452
class TestGetTreesAndBranchesToDiffLocked(tests.TestCaseWithTransport):
 
1453
 
 
1454
    def call_gtabtd(self, path_list, revision_specs, old_url, new_url):
 
1455
        """Call get_trees_and_branches_to_diff_locked."""
 
1456
        return diff.get_trees_and_branches_to_diff_locked(
 
1457
            path_list, revision_specs, old_url, new_url, self.addCleanup)
 
1458
 
 
1459
    def test_basic(self):
 
1460
        tree = self.make_branch_and_tree('tree')
 
1461
        (old_tree, new_tree,
 
1462
         old_branch, new_branch,
 
1463
         specific_files, extra_trees) = self.call_gtabtd(
 
1464
             ['tree'], None, None, None)
 
1465
 
 
1466
        self.assertIsInstance(old_tree, revisiontree.RevisionTree)
 
1467
        self.assertEqual(_mod_revision.NULL_REVISION,
 
1468
                         old_tree.get_revision_id())
 
1469
        self.assertEqual(tree.basedir, new_tree.basedir)
 
1470
        self.assertEqual(tree.branch.base, old_branch.base)
 
1471
        self.assertEqual(tree.branch.base, new_branch.base)
 
1472
        self.assertIs(None, specific_files)
 
1473
        self.assertIs(None, extra_trees)
 
1474
 
 
1475
    def test_with_rev_specs(self):
 
1476
        tree = self.make_branch_and_tree('tree')
 
1477
        self.build_tree_contents([('tree/file', 'oldcontent')])
 
1478
        tree.add('file', 'file-id')
 
1479
        tree.commit('old tree', timestamp=0, rev_id="old-id")
 
1480
        self.build_tree_contents([('tree/file', 'newcontent')])
 
1481
        tree.commit('new tree', timestamp=0, rev_id="new-id")
 
1482
 
 
1483
        revisions = [revisionspec.RevisionSpec.from_string('1'),
 
1484
                     revisionspec.RevisionSpec.from_string('2')]
 
1485
        (old_tree, new_tree,
 
1486
         old_branch, new_branch,
 
1487
         specific_files, extra_trees) = self.call_gtabtd(
 
1488
            ['tree'], revisions, None, None)
 
1489
 
 
1490
        self.assertIsInstance(old_tree, revisiontree.RevisionTree)
 
1491
        self.assertEqual("old-id", old_tree.get_revision_id())
 
1492
        self.assertIsInstance(new_tree, revisiontree.RevisionTree)
 
1493
        self.assertEqual("new-id", new_tree.get_revision_id())
 
1494
        self.assertEqual(tree.branch.base, old_branch.base)
 
1495
        self.assertEqual(tree.branch.base, new_branch.base)
 
1496
        self.assertIs(None, specific_files)
 
1497
        self.assertEqual(tree.basedir, extra_trees[0].basedir)