~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_diff.py

Merge bzr.dev and tree-file-ids-as-tuples.

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
20
import sys
23
 
from tempfile import TemporaryFile
24
 
 
25
 
from bzrlib import tests
26
 
from bzrlib.diff import (
27
 
    DiffFromTool,
28
 
    DiffPath,
29
 
    DiffSymlink,
30
 
    DiffTree,
31
 
    DiffText,
32
 
    external_diff,
33
 
    internal_diff,
34
 
    show_diff_trees,
35
 
    )
36
 
from bzrlib.errors import BinaryFile, NoDiff, ExecutableMissing
37
 
import bzrlib.osutils as osutils
38
 
import bzrlib.revision as _mod_revision
39
 
import bzrlib.transform as transform
40
 
import bzrlib.patiencediff
41
 
import bzrlib._patiencediff_py
42
 
from bzrlib.tests import (Feature, TestCase, TestCaseWithTransport,
43
 
                          TestCaseInTempDir, TestSkipped)
44
 
 
45
 
 
46
 
class _AttribFeature(Feature):
47
 
 
48
 
    def _probe(self):
49
 
        if (sys.platform not in ('cygwin', 'win32')):
50
 
            return False
51
 
        try:
52
 
            proc = subprocess.Popen(['attrib', '.'], stdout=subprocess.PIPE)
53
 
        except OSError, e:
54
 
            return False
55
 
        return (0 == proc.wait())
56
 
 
57
 
    def feature_name(self):
58
 
        return 'attrib Windows command-line tool'
59
 
 
60
 
AttribFeature = _AttribFeature()
61
 
 
62
 
 
63
 
class _CompiledPatienceDiffFeature(Feature):
64
 
 
65
 
    def _probe(self):
66
 
        try:
67
 
            import bzrlib._patiencediff_c
68
 
        except ImportError:
69
 
            return False
70
 
        return True
71
 
 
72
 
    def feature_name(self):
73
 
        return 'bzrlib._patiencediff_c'
74
 
 
75
 
CompiledPatienceDiffFeature = _CompiledPatienceDiffFeature()
 
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
    )
76
41
 
77
42
 
78
43
def udiff_lines(old, new, allow_binary=False):
79
44
    output = StringIO()
80
 
    internal_diff('old', old, 'new', new, output, allow_binary)
 
45
    diff.internal_diff('old', old, 'new', new, output, allow_binary)
81
46
    output.seek(0, 0)
82
47
    return output.readlines()
83
48
 
87
52
        # StringIO has no fileno, so it tests a different codepath
88
53
        output = StringIO()
89
54
    else:
90
 
        output = TemporaryFile()
 
55
        output = tempfile.TemporaryFile()
91
56
    try:
92
 
        external_diff('old', old, 'new', new, output, diff_opts=['-u'])
93
 
    except NoDiff:
94
 
        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')
95
60
    output.seek(0, 0)
96
61
    lines = output.readlines()
97
62
    output.close()
98
63
    return lines
99
64
 
100
65
 
101
 
class TestDiff(TestCase):
 
66
class TestDiff(tests.TestCase):
102
67
 
103
68
    def test_add_nl(self):
104
69
        """diff generates a valid diff for patches that add a newline"""
140
105
            ## "Unterminated hunk header for patch:\n%s" % "".join(lines)
141
106
 
142
107
    def test_binary_lines(self):
143
 
        self.assertRaises(BinaryFile, udiff_lines, [1023 * 'a' + '\x00'], [])
144
 
        self.assertRaises(BinaryFile, udiff_lines, [], [1023 * 'a' + '\x00'])
145
 
        udiff_lines([1023 * 'a' + '\x00'], [], allow_binary=True)
146
 
        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)
147
114
 
148
115
    def test_external_diff(self):
149
116
        lines = external_udiff_lines(['boo\n'], ['goo\n'])
159
126
        self.check_patch(lines)
160
127
 
161
128
    def test_external_diff_binary_lang_c(self):
162
 
        old_env = {}
163
129
        for lang in ('LANG', 'LC_ALL', 'LANGUAGE'):
164
 
            old_env[lang] = osutils.set_or_unset_env(lang, 'C')
165
 
        try:
166
 
            lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
167
 
            # Older versions of diffutils say "Binary files", newer
168
 
            # versions just say "Files".
169
 
            self.assertContainsRe(lines[0],
170
 
                                  '(Binary f|F)iles old and new differ\n')
171
 
            self.assertEquals(lines[1:], ['\n'])
172
 
        finally:
173
 
            for lang, old_val in old_env.iteritems():
174
 
                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'])
175
136
 
176
137
    def test_no_external_diff(self):
177
138
        """Check that NoDiff is raised when diff is not available"""
178
 
        # Use os.environ['PATH'] to make sure no 'diff' command is available
179
 
        orig_path = os.environ['PATH']
180
 
        try:
181
 
            os.environ['PATH'] = ''
182
 
            self.assertRaises(NoDiff, external_diff,
183
 
                              'old', ['boo\n'], 'new', ['goo\n'],
184
 
                              StringIO(), diff_opts=['-u'])
185
 
        finally:
186
 
            os.environ['PATH'] = orig_path
187
 
        
 
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
 
188
146
    def test_internal_diff_default(self):
189
147
        # Default internal diff encoding is utf8
190
148
        output = StringIO()
191
 
        internal_diff(u'old_\xb5', ['old_text\n'],
192
 
                    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)
193
151
        lines = output.getvalue().splitlines(True)
194
152
        self.check_patch(lines)
195
153
        self.assertEquals(['--- old_\xc2\xb5\n',
203
161
 
204
162
    def test_internal_diff_utf8(self):
205
163
        output = StringIO()
206
 
        internal_diff(u'old_\xb5', ['old_text\n'],
207
 
                    u'new_\xe5', ['new_text\n'], output,
208
 
                    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')
209
167
        lines = output.getvalue().splitlines(True)
210
168
        self.check_patch(lines)
211
169
        self.assertEquals(['--- old_\xc2\xb5\n',
219
177
 
220
178
    def test_internal_diff_iso_8859_1(self):
221
179
        output = StringIO()
222
 
        internal_diff(u'old_\xb5', ['old_text\n'],
223
 
                    u'new_\xe5', ['new_text\n'], output,
224
 
                    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')
225
183
        lines = output.getvalue().splitlines(True)
226
184
        self.check_patch(lines)
227
185
        self.assertEquals(['--- old_\xb5\n',
235
193
 
236
194
    def test_internal_diff_no_content(self):
237
195
        output = StringIO()
238
 
        internal_diff(u'old', [], u'new', [], output)
 
196
        diff.internal_diff(u'old', [], u'new', [], output)
239
197
        self.assertEqual('', output.getvalue())
240
198
 
241
199
    def test_internal_diff_no_changes(self):
242
200
        output = StringIO()
243
 
        internal_diff(u'old', ['text\n', 'contents\n'],
244
 
                      u'new', ['text\n', 'contents\n'],
245
 
                      output)
 
201
        diff.internal_diff(u'old', ['text\n', 'contents\n'],
 
202
                           u'new', ['text\n', 'contents\n'],
 
203
                           output)
246
204
        self.assertEqual('', output.getvalue())
247
205
 
248
206
    def test_internal_diff_returns_bytes(self):
249
207
        import StringIO
250
208
        output = StringIO.StringIO()
251
 
        internal_diff(u'old_\xb5', ['old_text\n'],
252
 
                    u'new_\xe5', ['new_text\n'], output)
253
 
        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,
254
212
            'internal_diff should return bytestrings')
255
213
 
256
214
 
257
 
class TestDiffFiles(TestCaseInTempDir):
 
215
class TestDiffFiles(tests.TestCaseInTempDir):
258
216
 
259
217
    def test_external_diff_binary(self):
260
218
        """The output when using external diff should use diff's i18n error"""
273
231
        self.assertEqual(out.splitlines(True) + ['\n'], lines)
274
232
 
275
233
 
276
 
class TestShowDiffTreesHelper(TestCaseWithTransport):
277
 
    """Has a helper for running show_diff_trees"""
278
 
 
279
 
    def get_diff(self, tree1, tree2, specific_files=None, working_tree=None):
280
 
        output = StringIO()
281
 
        if working_tree is not None:
282
 
            extra_trees = (working_tree,)
283
 
        else:
284
 
            extra_trees = ()
285
 
        show_diff_trees(tree1, tree2, output, specific_files=specific_files,
286
 
                        extra_trees=extra_trees, old_label='old/',
287
 
                        new_label='new/')
288
 
        return output.getvalue()
289
 
 
290
 
 
291
 
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):
292
248
 
293
249
    def setUp(self):
294
250
        super(TestDiffDates, self).setUp()
329
285
        os.utime('file1', (1144195200, 1144195200)) # 2006-04-05 00:00:00 UTC
330
286
 
331
287
    def test_diff_rev_tree_working_tree(self):
332
 
        output = self.get_diff(self.wt.basis_tree(), self.wt)
 
288
        output = get_diff_as_string(self.wt.basis_tree(), self.wt)
333
289
        # note that the date for old/file1 is from rev 2 rather than from
334
290
        # the basis revision (rev 4)
335
291
        self.assertEqualDiff(output, '''\
345
301
    def test_diff_rev_tree_rev_tree(self):
346
302
        tree1 = self.b.repository.revision_tree('rev-2')
347
303
        tree2 = self.b.repository.revision_tree('rev-3')
348
 
        output = self.get_diff(tree1, tree2)
 
304
        output = get_diff_as_string(tree1, tree2)
349
305
        self.assertEqualDiff(output, '''\
350
306
=== modified file 'file2'
351
307
--- old/file2\t2006-04-01 00:00:00 +0000
355
311
+file2 contents at rev 3
356
312
 
357
313
''')
358
 
        
 
314
 
359
315
    def test_diff_add_files(self):
360
316
        tree1 = self.b.repository.revision_tree(_mod_revision.NULL_REVISION)
361
317
        tree2 = self.b.repository.revision_tree('rev-1')
362
 
        output = self.get_diff(tree1, tree2)
 
318
        output = get_diff_as_string(tree1, tree2)
363
319
        # the files have the epoch time stamp for the tree in which
364
320
        # they don't exist.
365
321
        self.assertEqualDiff(output, '''\
380
336
    def test_diff_remove_files(self):
381
337
        tree1 = self.b.repository.revision_tree('rev-3')
382
338
        tree2 = self.b.repository.revision_tree('rev-4')
383
 
        output = self.get_diff(tree1, tree2)
 
339
        output = get_diff_as_string(tree1, tree2)
384
340
        # the file has the epoch time stamp for the tree in which
385
341
        # it doesn't exist.
386
342
        self.assertEqualDiff(output, '''\
397
353
        self.wt.rename_one('file1', 'file1b')
398
354
        old_tree = self.b.repository.revision_tree('rev-1')
399
355
        new_tree = self.b.repository.revision_tree('rev-4')
400
 
        out = self.get_diff(old_tree, new_tree, specific_files=['file1b'], 
 
356
        out = get_diff_as_string(old_tree, new_tree, specific_files=['file1b'],
401
357
                            working_tree=self.wt)
402
358
        self.assertContainsRe(out, 'file1\t')
403
359
 
409
365
        self.wt.rename_one('file1', 'dir1/file1')
410
366
        old_tree = self.b.repository.revision_tree('rev-1')
411
367
        new_tree = self.b.repository.revision_tree('rev-4')
412
 
        out = self.get_diff(old_tree, new_tree, specific_files=['dir1'], 
 
368
        out = get_diff_as_string(old_tree, new_tree, specific_files=['dir1'],
413
369
                            working_tree=self.wt)
414
370
        self.assertContainsRe(out, 'file1\t')
415
 
        out = self.get_diff(old_tree, new_tree, specific_files=['dir2'], 
 
371
        out = get_diff_as_string(old_tree, new_tree, specific_files=['dir2'],
416
372
                            working_tree=self.wt)
417
373
        self.assertNotContainsRe(out, 'file1\t')
418
374
 
419
375
 
420
 
 
421
 
class TestShowDiffTrees(TestShowDiffTreesHelper):
 
376
class TestShowDiffTrees(tests.TestCaseWithTransport):
422
377
    """Direct tests for show_diff_trees"""
423
378
 
424
379
    def test_modified_file(self):
429
384
        tree.commit('one', rev_id='rev-1')
430
385
 
431
386
        self.build_tree_contents([('tree/file', 'new contents\n')])
432
 
        diff = self.get_diff(tree.basis_tree(), tree)
433
 
        self.assertContainsRe(diff, "=== modified file 'file'\n")
434
 
        self.assertContainsRe(diff, '--- old/file\t')
435
 
        self.assertContainsRe(diff, '\\+\\+\\+ new/file\t')
436
 
        self.assertContainsRe(diff, '-contents\n'
437
 
                                    '\\+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')
438
393
 
439
394
    def test_modified_file_in_renamed_dir(self):
440
395
        """Test when a file is modified in a renamed directory."""
446
401
 
447
402
        tree.rename_one('dir', 'other')
448
403
        self.build_tree_contents([('tree/other/file', 'new contents\n')])
449
 
        diff = self.get_diff(tree.basis_tree(), tree)
450
 
        self.assertContainsRe(diff, "=== renamed directory 'dir' => 'other'\n")
451
 
        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")
452
407
        # XXX: This is technically incorrect, because it used to be at another
453
408
        # location. What to do?
454
 
        self.assertContainsRe(diff, '--- old/dir/file\t')
455
 
        self.assertContainsRe(diff, '\\+\\+\\+ new/other/file\t')
456
 
        self.assertContainsRe(diff, '-contents\n'
457
 
                                    '\\+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')
458
413
 
459
414
    def test_renamed_directory(self):
460
415
        """Test when only a directory is only renamed."""
465
420
        tree.commit('one', rev_id='rev-1')
466
421
 
467
422
        tree.rename_one('dir', 'newdir')
468
 
        diff = self.get_diff(tree.basis_tree(), tree)
 
423
        d = get_diff_as_string(tree.basis_tree(), tree)
469
424
        # Renaming a directory should be a single "you renamed this dir" even
470
425
        # when there are files inside.
471
 
        self.assertEqual("=== renamed directory 'dir' => 'newdir'\n", diff)
 
426
        self.assertEqual(d, "=== renamed directory 'dir' => 'newdir'\n")
472
427
 
473
428
    def test_renamed_file(self):
474
429
        """Test when a file is only renamed."""
478
433
        tree.commit('one', rev_id='rev-1')
479
434
 
480
435
        tree.rename_one('file', 'newname')
481
 
        diff = self.get_diff(tree.basis_tree(), tree)
482
 
        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")
483
438
        # We shouldn't have a --- or +++ line, because there is no content
484
439
        # change
485
 
        self.assertNotContainsRe(diff, '---')
 
440
        self.assertNotContainsRe(d, '---')
486
441
 
487
442
    def test_renamed_and_modified_file(self):
488
443
        """Test when a file is only renamed."""
493
448
 
494
449
        tree.rename_one('file', 'newname')
495
450
        self.build_tree_contents([('tree/newname', 'new contents\n')])
496
 
        diff = self.get_diff(tree.basis_tree(), tree)
497
 
        self.assertContainsRe(diff, "=== renamed file 'file' => 'newname'\n")
498
 
        self.assertContainsRe(diff, '--- old/file\t')
499
 
        self.assertContainsRe(diff, '\\+\\+\\+ new/newname\t')
500
 
        self.assertContainsRe(diff, '-contents\n'
501
 
                                    '\\+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')
502
457
 
503
458
 
504
459
    def test_internal_diff_exec_property(self):
523
478
        tree.rename_one('c', 'new-c')
524
479
        tree.rename_one('d', 'new-d')
525
480
 
526
 
        diff = self.get_diff(tree.basis_tree(), tree)
527
 
 
528
 
        self.assertContainsRe(diff, r"file 'a'.*\(properties changed:.*\+x to -x.*\)")
529
 
        self.assertContainsRe(diff, r"file 'b'.*\(properties changed:.*-x to \+x.*\)")
530
 
        self.assertContainsRe(diff, r"file 'c'.*\(properties changed:.*\+x to -x.*\)")
531
 
        self.assertContainsRe(diff, r"file 'd'.*\(properties changed:.*-x to \+x.*\)")
532
 
        self.assertNotContainsRe(diff, r"file 'e'")
533
 
        self.assertNotContainsRe(diff, r"file 'f'")
534
 
 
 
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'")
535
493
 
536
494
    def test_binary_unicode_filenames(self):
537
495
        """Test that contents of files are *not* encoded in UTF-8 when there
538
496
        is a binary file in the diff.
539
497
        """
540
498
        # See https://bugs.launchpad.net/bugs/110092.
541
 
        self.requireFeature(tests.UnicodeFilenameFeature)
 
499
        self.requireFeature(features.UnicodeFilenameFeature)
542
500
 
543
501
        # This bug isn't triggered with cStringIO.
544
502
        from StringIO import StringIO
552
510
        tree.add([alpha], ['file-id'])
553
511
        tree.add([omega], ['file-id-2'])
554
512
        diff_content = StringIO()
555
 
        show_diff_trees(tree.basis_tree(), tree, diff_content)
556
 
        diff = diff_content.getvalue()
557
 
        self.assertContainsRe(diff, r"=== added file '%s'" % alpha_utf8)
558
 
        self.assertContainsRe(
559
 
            diff, "Binary files a/%s.*and b/%s.* differ\n" % (alpha_utf8, alpha_utf8))
560
 
        self.assertContainsRe(diff, r"=== added file '%s'" % omega_utf8)
561
 
        self.assertContainsRe(diff, r"--- a/%s" % (omega_utf8,))
562
 
        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,))
563
521
 
564
522
    def test_unicode_filename(self):
565
523
        """Test when the filename are unicode."""
566
 
        self.requireFeature(tests.UnicodeFilenameFeature)
 
524
        self.requireFeature(features.UnicodeFilenameFeature)
567
525
 
568
526
        alpha, omega = u'\u03b1', u'\u03c9'
569
527
        autf8, outf8 = alpha.encode('utf8'), omega.encode('utf8')
584
542
        tree.add(['add_'+alpha], ['file-id'])
585
543
        self.build_tree_contents([('tree/mod_'+alpha, 'contents_mod\n')])
586
544
 
587
 
        diff = self.get_diff(tree.basis_tree(), tree)
588
 
        self.assertContainsRe(diff,
 
545
        d = get_diff_as_string(tree.basis_tree(), tree)
 
546
        self.assertContainsRe(d,
589
547
                "=== renamed file 'ren_%s' => 'ren_%s'\n"%(autf8, outf8))
590
 
        self.assertContainsRe(diff, "=== added file 'add_%s'"%autf8)
591
 
        self.assertContainsRe(diff, "=== modified file 'mod_%s'"%autf8)
592
 
        self.assertContainsRe(diff, "=== removed file 'del_%s'"%autf8)
593
 
 
594
 
 
595
 
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):
596
597
 
597
598
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
598
599
        self.to_file.write('was: ')
602
603
        pass
603
604
 
604
605
 
605
 
class TestDiffTree(TestCaseWithTransport):
 
606
class TestDiffTree(tests.TestCaseWithTransport):
606
607
 
607
608
    def setUp(self):
608
 
        TestCaseWithTransport.setUp(self)
 
609
        super(TestDiffTree, self).setUp()
609
610
        self.old_tree = self.make_branch_and_tree('old-tree')
610
611
        self.old_tree.lock_write()
611
612
        self.addCleanup(self.old_tree.unlock)
612
613
        self.new_tree = self.make_branch_and_tree('new-tree')
613
614
        self.new_tree.lock_write()
614
615
        self.addCleanup(self.new_tree.unlock)
615
 
        self.differ = DiffTree(self.old_tree, self.new_tree, StringIO())
 
616
        self.differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
616
617
 
617
618
    def test_diff_text(self):
618
619
        self.build_tree_contents([('old-tree/olddir/',),
623
624
                                  ('new-tree/newdir/newfile', 'new\n')])
624
625
        self.new_tree.add('newdir')
625
626
        self.new_tree.add('newdir/newfile', 'file-id')
626
 
        differ = DiffText(self.old_tree, self.new_tree, StringIO())
 
627
        differ = diff.DiffText(self.old_tree, self.new_tree, StringIO())
627
628
        differ.diff_text('file-id', None, 'old label', 'new label')
628
629
        self.assertEqual(
629
630
            '--- old label\n+++ new label\n@@ -1,1 +0,0 @@\n-old\n\n',
658
659
        self.assertContainsRe(self.differ.to_file.getvalue(), '\+contents')
659
660
 
660
661
    def test_diff_symlink(self):
661
 
        differ = DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
662
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
662
663
        differ.diff_symlink('old target', None)
663
664
        self.assertEqual("=== target was 'old target'\n",
664
665
                         differ.to_file.getvalue())
665
666
 
666
 
        differ = DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
667
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
667
668
        differ.diff_symlink(None, 'new target')
668
669
        self.assertEqual("=== target is 'new target'\n",
669
670
                         differ.to_file.getvalue())
670
671
 
671
 
        differ = DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
672
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
672
673
        differ.diff_symlink('old target', 'new target')
673
674
        self.assertEqual("=== target changed 'old target' => 'new target'\n",
674
675
                         differ.to_file.getvalue())
689
690
             ' \@\@\n-old\n\+new\n\n')
690
691
 
691
692
    def test_diff_kind_change(self):
692
 
        self.requireFeature(tests.SymlinkFeature)
 
693
        self.requireFeature(features.SymlinkFeature)
693
694
        self.build_tree_contents([('old-tree/olddir/',),
694
695
                                  ('old-tree/olddir/oldfile', 'old\n')])
695
696
        self.old_tree.add('olddir')
704
705
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+0,0'
705
706
             ' \@\@\n-old\n\n')
706
707
        self.assertContainsRe(self.differ.to_file.getvalue(),
707
 
                              "=== target is 'new'\n")
 
708
                              "=== target is u'new'\n")
708
709
 
709
710
    def test_diff_directory(self):
710
711
        self.build_tree(['new-tree/new-dir/'])
724
725
 
725
726
    def test_register_diff(self):
726
727
        self.create_old_new()
727
 
        old_diff_factories = DiffTree.diff_factories
728
 
        DiffTree.diff_factories=old_diff_factories[:]
729
 
        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)
730
731
        try:
731
 
            differ = DiffTree(self.old_tree, self.new_tree, StringIO())
 
732
            differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
732
733
        finally:
733
 
            DiffTree.diff_factories = old_diff_factories
 
734
            diff.DiffTree.diff_factories = old_diff_factories
734
735
        differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
735
736
        self.assertNotContainsRe(
736
737
            differ.to_file.getvalue(),
741
742
 
742
743
    def test_extra_factories(self):
743
744
        self.create_old_new()
744
 
        differ = DiffTree(self.old_tree, self.new_tree, StringIO(),
745
 
                            extra_factories=[DiffWasIs.from_diff_tree])
 
745
        differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO(),
 
746
                               extra_factories=[DiffWasIs.from_diff_tree])
746
747
        differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
747
748
        self.assertNotContainsRe(
748
749
            differ.to_file.getvalue(),
761
762
            '.*a-file(.|\n)*b-file')
762
763
 
763
764
 
764
 
class TestPatienceDiffLib(TestCase):
 
765
class TestPatienceDiffLib(tests.TestCase):
765
766
 
766
767
    def setUp(self):
767
768
        super(TestPatienceDiffLib, self).setUp()
768
 
        self._unique_lcs = bzrlib._patiencediff_py.unique_lcs_py
769
 
        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
770
771
        self._PatienceSequenceMatcher = \
771
 
            bzrlib._patiencediff_py.PatienceSequenceMatcher_py
 
772
            _patiencediff_py.PatienceSequenceMatcher_py
772
773
 
773
774
    def test_diff_unicode_string(self):
774
775
        a = ''.join([unichr(i) for i in range(4000, 4500, 3)])
787
788
        self.assertEquals(unique_lcs('ab', 'ab'), [(0,0), (1,1)])
788
789
        self.assertEquals(unique_lcs('abcde', 'cdeab'), [(2,0), (3,1), (4,2)])
789
790
        self.assertEquals(unique_lcs('cdeab', 'abcde'), [(0,2), (1,3), (2,4)])
790
 
        self.assertEquals(unique_lcs('abXde', 'abYde'), [(0,0), (1,1), 
 
791
        self.assertEquals(unique_lcs('abXde', 'abYde'), [(0,0), (1,1),
791
792
                                                         (3,3), (4,4)])
792
793
        self.assertEquals(unique_lcs('acbac', 'abc'), [(2,1)])
793
794
 
808
809
        test_one('abcdbce', 'afbcgdbce', [(0,0), (1, 2), (2, 3), (3, 5),
809
810
                                          (4, 6), (5, 7), (6, 8)])
810
811
 
811
 
        # recurse_matches doesn't match non-unique 
 
812
        # recurse_matches doesn't match non-unique
812
813
        # lines surrounded by bogus text.
813
814
        # The update has been done in patiencediff.SequenceMatcher instead
814
815
 
951
952
                 ('delete', 1,2, 1,1),
952
953
                 ('equal',  2,3, 1,2),
953
954
                ])
954
 
        chk_ops('aBccDe', 'abccde', 
 
955
        chk_ops('aBccDe', 'abccde',
955
956
                [('equal',   0,1, 0,1),
956
957
                 ('replace', 1,5, 1,5),
957
958
                 ('equal',   5,6, 5,6),
958
959
                ])
959
 
        chk_ops('aBcDec', 'abcdec', 
 
960
        chk_ops('aBcDec', 'abcdec',
960
961
                [('equal',   0,1, 0,1),
961
962
                 ('replace', 1,2, 1,2),
962
963
                 ('equal',   2,3, 2,3),
963
964
                 ('replace', 3,4, 3,4),
964
965
                 ('equal',   4,6, 4,6),
965
966
                ])
966
 
        chk_ops('aBcdEcdFg', 'abcdecdfg', 
 
967
        chk_ops('aBcdEcdFg', 'abcdecdfg',
967
968
                [('equal',   0,1, 0,1),
968
969
                 ('replace', 1,8, 1,8),
969
970
                 ('equal',   8,9, 8,9)
970
971
                ])
971
 
        chk_ops('aBcdEeXcdFg', 'abcdecdfg', 
 
972
        chk_ops('aBcdEeXcdFg', 'abcdecdfg',
972
973
                [('equal',   0,1, 0,1),
973
974
                 ('replace', 1,2, 1,2),
974
975
                 ('equal',   2,4, 2,4),
1034
1035
    """
1035
1036
    gnxrf_netf = ['svyr*']
1036
1037
    gnxrf_bcgvbaf = ['ab-erphefr']
1037
 
  
 
1038
 
1038
1039
    qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr):
1039
1040
        sebz omeyvo.nqq vzcbeg fzneg_nqq, nqq_ercbegre_cevag, nqq_ercbegre_ahyy
1040
1041
        vs vf_dhvrg():
1048
1049
'''.splitlines(True), '''\
1049
1050
    trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
1050
1051
 
1051
 
    --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
1052
1053
    nqq gurz.
1053
1054
    """
1054
1055
    gnxrf_netf = ['svyr*']
1081
1082
                 'how are you today?\n']
1082
1083
        txt_b = ['hello there\n',
1083
1084
                 'how are you today?\n']
1084
 
        unified_diff = bzrlib.patiencediff.unified_diff
 
1085
        unified_diff = patiencediff.unified_diff
1085
1086
        psm = self._PatienceSequenceMatcher
1086
 
        self.assertEquals([ '---  \n',
1087
 
                           '+++  \n',
 
1087
        self.assertEquals(['--- \n',
 
1088
                           '+++ \n',
1088
1089
                           '@@ -1,3 +1,2 @@\n',
1089
1090
                           ' hello there\n',
1090
1091
                           '-world\n',
1095
1096
        txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
1096
1097
        txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
1097
1098
        # This is the result with LongestCommonSubstring matching
1098
 
        self.assertEquals(['---  \n',
1099
 
                           '+++  \n',
 
1099
        self.assertEquals(['--- \n',
 
1100
                           '+++ \n',
1100
1101
                           '@@ -1,6 +1,11 @@\n',
1101
1102
                           ' a\n',
1102
1103
                           ' b\n',
1111
1112
                           ' f\n']
1112
1113
                          , list(unified_diff(txt_a, txt_b)))
1113
1114
        # And the patience diff
1114
 
        self.assertEquals(['---  \n',
1115
 
                           '+++  \n',
 
1115
        self.assertEquals(['--- \n',
 
1116
                           '+++ \n',
1116
1117
                           '@@ -4,6 +4,11 @@\n',
1117
1118
                           ' d\n',
1118
1119
                           ' e\n',
1129
1130
                          , list(unified_diff(txt_a, txt_b,
1130
1131
                                 sequencematcher=psm)))
1131
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
 
1132
1154
 
1133
1155
class TestPatienceDiffLib_c(TestPatienceDiffLib):
1134
1156
 
1135
 
    _test_needs_features = [CompiledPatienceDiffFeature]
 
1157
    _test_needs_features = [features.compiled_patiencediff_feature]
1136
1158
 
1137
1159
    def setUp(self):
1138
1160
        super(TestPatienceDiffLib_c, self).setUp()
1139
 
        import bzrlib._patiencediff_c
1140
 
        self._unique_lcs = bzrlib._patiencediff_c.unique_lcs_c
1141
 
        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
1142
1164
        self._PatienceSequenceMatcher = \
1143
 
            bzrlib._patiencediff_c.PatienceSequenceMatcher_c
 
1165
            _patiencediff_c.PatienceSequenceMatcher_c
1144
1166
 
1145
1167
    def test_unhashable(self):
1146
1168
        """We should get a proper exception here."""
1156
1178
                                         None, ['valid'], ['valid', []])
1157
1179
 
1158
1180
 
1159
 
class TestPatienceDiffLibFiles(TestCaseInTempDir):
 
1181
class TestPatienceDiffLibFiles(tests.TestCaseInTempDir):
1160
1182
 
1161
1183
    def setUp(self):
1162
1184
        super(TestPatienceDiffLibFiles, self).setUp()
1163
1185
        self._PatienceSequenceMatcher = \
1164
 
            bzrlib._patiencediff_py.PatienceSequenceMatcher_py
 
1186
            _patiencediff_py.PatienceSequenceMatcher_py
1165
1187
 
1166
1188
    def test_patience_unified_diff_files(self):
1167
1189
        txt_a = ['hello there\n',
1172
1194
        open('a1', 'wb').writelines(txt_a)
1173
1195
        open('b1', 'wb').writelines(txt_b)
1174
1196
 
1175
 
        unified_diff_files = bzrlib.patiencediff.unified_diff_files
 
1197
        unified_diff_files = patiencediff.unified_diff_files
1176
1198
        psm = self._PatienceSequenceMatcher
1177
 
        self.assertEquals(['--- a1 \n',
1178
 
                           '+++ b1 \n',
 
1199
        self.assertEquals(['--- a1\n',
 
1200
                           '+++ b1\n',
1179
1201
                           '@@ -1,3 +1,2 @@\n',
1180
1202
                           ' hello there\n',
1181
1203
                           '-world\n',
1190
1212
        open('b2', 'wb').writelines(txt_b)
1191
1213
 
1192
1214
        # This is the result with LongestCommonSubstring matching
1193
 
        self.assertEquals(['--- a2 \n',
1194
 
                           '+++ b2 \n',
 
1215
        self.assertEquals(['--- a2\n',
 
1216
                           '+++ b2\n',
1195
1217
                           '@@ -1,6 +1,11 @@\n',
1196
1218
                           ' a\n',
1197
1219
                           ' b\n',
1207
1229
                          , list(unified_diff_files('a2', 'b2')))
1208
1230
 
1209
1231
        # And the patience diff
1210
 
        self.assertEquals(['--- a2 \n',
1211
 
                           '+++ b2 \n',
 
1232
        self.assertEquals(['--- a2\n',
 
1233
                           '+++ b2\n',
1212
1234
                           '@@ -4,6 +4,11 @@\n',
1213
1235
                           ' d\n',
1214
1236
                           ' e\n',
1228
1250
 
1229
1251
class TestPatienceDiffLibFiles_c(TestPatienceDiffLibFiles):
1230
1252
 
1231
 
    _test_needs_features = [CompiledPatienceDiffFeature]
 
1253
    _test_needs_features = [features.compiled_patiencediff_feature]
1232
1254
 
1233
1255
    def setUp(self):
1234
1256
        super(TestPatienceDiffLibFiles_c, self).setUp()
1235
 
        import bzrlib._patiencediff_c
 
1257
        from bzrlib import _patiencediff_c
1236
1258
        self._PatienceSequenceMatcher = \
1237
 
            bzrlib._patiencediff_c.PatienceSequenceMatcher_c
1238
 
 
1239
 
 
1240
 
class TestUsingCompiledIfAvailable(TestCase):
 
1259
            _patiencediff_c.PatienceSequenceMatcher_c
 
1260
 
 
1261
 
 
1262
class TestUsingCompiledIfAvailable(tests.TestCase):
1241
1263
 
1242
1264
    def test_PatienceSequenceMatcher(self):
1243
 
        if CompiledPatienceDiffFeature.available():
 
1265
        if features.compiled_patiencediff_feature.available():
1244
1266
            from bzrlib._patiencediff_c import PatienceSequenceMatcher_c
1245
1267
            self.assertIs(PatienceSequenceMatcher_c,
1246
 
                          bzrlib.patiencediff.PatienceSequenceMatcher)
 
1268
                          patiencediff.PatienceSequenceMatcher)
1247
1269
        else:
1248
1270
            from bzrlib._patiencediff_py import PatienceSequenceMatcher_py
1249
1271
            self.assertIs(PatienceSequenceMatcher_py,
1250
 
                          bzrlib.patiencediff.PatienceSequenceMatcher)
 
1272
                          patiencediff.PatienceSequenceMatcher)
1251
1273
 
1252
1274
    def test_unique_lcs(self):
1253
 
        if CompiledPatienceDiffFeature.available():
 
1275
        if features.compiled_patiencediff_feature.available():
1254
1276
            from bzrlib._patiencediff_c import unique_lcs_c
1255
1277
            self.assertIs(unique_lcs_c,
1256
 
                          bzrlib.patiencediff.unique_lcs)
 
1278
                          patiencediff.unique_lcs)
1257
1279
        else:
1258
1280
            from bzrlib._patiencediff_py import unique_lcs_py
1259
1281
            self.assertIs(unique_lcs_py,
1260
 
                          bzrlib.patiencediff.unique_lcs)
 
1282
                          patiencediff.unique_lcs)
1261
1283
 
1262
1284
    def test_recurse_matches(self):
1263
 
        if CompiledPatienceDiffFeature.available():
 
1285
        if features.compiled_patiencediff_feature.available():
1264
1286
            from bzrlib._patiencediff_c import recurse_matches_c
1265
1287
            self.assertIs(recurse_matches_c,
1266
 
                          bzrlib.patiencediff.recurse_matches)
 
1288
                          patiencediff.recurse_matches)
1267
1289
        else:
1268
1290
            from bzrlib._patiencediff_py import recurse_matches_py
1269
1291
            self.assertIs(recurse_matches_py,
1270
 
                          bzrlib.patiencediff.recurse_matches)
1271
 
 
1272
 
 
1273
 
class TestDiffFromTool(TestCaseWithTransport):
 
1292
                          patiencediff.recurse_matches)
 
1293
 
 
1294
 
 
1295
class TestDiffFromTool(tests.TestCaseWithTransport):
1274
1296
 
1275
1297
    def test_from_string(self):
1276
 
        diff_obj = DiffFromTool.from_string('diff', None, None, None)
 
1298
        diff_obj = diff.DiffFromTool.from_string('diff', None, None, None)
1277
1299
        self.addCleanup(diff_obj.finish)
1278
 
        self.assertEqual(['diff', '%(old_path)s', '%(new_path)s'],
 
1300
        self.assertEqual(['diff', '@old_path', '@new_path'],
1279
1301
            diff_obj.command_template)
1280
1302
 
1281
1303
    def test_from_string_u5(self):
1282
 
        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)
1283
1306
        self.addCleanup(diff_obj.finish)
1284
 
        self.assertEqual(['diff', '-u 5', '%(old_path)s', '%(new_path)s'],
 
1307
        self.assertEqual(['diff', '-u 5', '@old_path', '@new_path'],
1285
1308
                         diff_obj.command_template)
1286
1309
        self.assertEqual(['diff', '-u 5', 'old-path', 'new-path'],
1287
1310
                         diff_obj._get_command('old-path', 'new-path'))
1288
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
 
1289
1322
    def test_execute(self):
1290
1323
        output = StringIO()
1291
 
        diff_obj = DiffFromTool(['python', '-c',
1292
 
                                 'print "%(old_path)s %(new_path)s"'],
1293
 
                                None, None, output)
 
1324
        diff_obj = diff.DiffFromTool(['python', '-c',
 
1325
                                      'print "@old_path @new_path"'],
 
1326
                                     None, None, output)
1294
1327
        self.addCleanup(diff_obj.finish)
1295
1328
        diff_obj._execute('old', 'new')
1296
1329
        self.assertEqual(output.getvalue().rstrip(), 'old new')
1297
1330
 
1298
1331
    def test_excute_missing(self):
1299
 
        diff_obj = DiffFromTool(['a-tool-which-is-unlikely-to-exist'],
1300
 
                                None, None, None)
 
1332
        diff_obj = diff.DiffFromTool(['a-tool-which-is-unlikely-to-exist'],
 
1333
                                     None, None, None)
1301
1334
        self.addCleanup(diff_obj.finish)
1302
 
        e = self.assertRaises(ExecutableMissing, diff_obj._execute, 'old',
1303
 
                              'new')
 
1335
        e = self.assertRaises(errors.ExecutableMissing, diff_obj._execute,
 
1336
                              'old', 'new')
1304
1337
        self.assertEqual('a-tool-which-is-unlikely-to-exist could not be found'
1305
1338
                         ' on this machine', str(e))
1306
1339
 
1307
1340
    def test_prepare_files_creates_paths_readable_by_windows_tool(self):
1308
 
        self.requireFeature(AttribFeature)
 
1341
        self.requireFeature(features.AttribFeature)
1309
1342
        output = StringIO()
1310
1343
        tree = self.make_branch_and_tree('tree')
1311
1344
        self.build_tree_contents([('tree/file', 'content')])
1313
1346
        tree.commit('old tree')
1314
1347
        tree.lock_read()
1315
1348
        self.addCleanup(tree.unlock)
1316
 
        diff_obj = DiffFromTool(['python', '-c',
1317
 
                                 'print "%(old_path)s %(new_path)s"'],
1318
 
                                tree, tree, output)
 
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)
1319
1355
        diff_obj._prepare_files('file-id', 'file', 'file')
1320
 
        self.assertReadableByAttrib(diff_obj._root, 'old\\file', r'old\\file')
1321
 
        self.assertReadableByAttrib(diff_obj._root, 'new\\file', r'new\\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$')
1322
1362
 
1323
1363
    def assertReadableByAttrib(self, cwd, relpath, regex):
1324
1364
        proc = subprocess.Popen(['attrib', relpath],
1325
1365
                                stdout=subprocess.PIPE,
1326
1366
                                cwd=cwd)
1327
 
        proc.wait()
1328
 
        result = proc.stdout.read()
1329
 
        self.assertContainsRe(result, regex)
 
1367
        (result, err) = proc.communicate()
 
1368
        self.assertContainsRe(result.replace('\r\n', '\n'), regex)
1330
1369
 
1331
1370
    def test_prepare_files(self):
1332
1371
        output = StringIO()
1335
1374
        self.build_tree_contents([('tree/oldname2', 'oldcontent2')])
1336
1375
        tree.add('oldname', 'file-id')
1337
1376
        tree.add('oldname2', 'file2-id')
1338
 
        tree.commit('old tree', timestamp=0)
 
1377
        # Earliest allowable date on FAT32 filesystems is 1980-01-01
 
1378
        tree.commit('old tree', timestamp=315532800)
1339
1379
        tree.rename_one('oldname', 'newname')
1340
1380
        tree.rename_one('oldname2', 'newname2')
1341
1381
        self.build_tree_contents([('tree/newname', 'newcontent')])
1345
1385
        self.addCleanup(old_tree.unlock)
1346
1386
        tree.lock_read()
1347
1387
        self.addCleanup(tree.unlock)
1348
 
        diff_obj = DiffFromTool(['python', '-c',
1349
 
                                 'print "%(old_path)s %(new_path)s"'],
1350
 
                                old_tree, tree, output)
 
1388
        diff_obj = diff.DiffFromTool(['python', '-c',
 
1389
                                      'print "@old_path @new_path"'],
 
1390
                                     old_tree, tree, output)
1351
1391
        self.addCleanup(diff_obj.finish)
1352
1392
        self.assertContainsRe(diff_obj._root, 'bzr-diff-[^/]*')
1353
1393
        old_path, new_path = diff_obj._prepare_files('file-id', 'oldname',
1354
1394
                                                     'newname')
1355
1395
        self.assertContainsRe(old_path, 'old/oldname$')
1356
 
        self.assertEqual(0, os.stat(old_path).st_mtime)
1357
 
        self.assertContainsRe(new_path, 'new/newname$')
 
1396
        self.assertEqual(315532800, os.stat(old_path).st_mtime)
 
1397
        self.assertContainsRe(new_path, 'tree/newname$')
1358
1398
        self.assertFileEqual('oldcontent', old_path)
1359
1399
        self.assertFileEqual('newcontent', new_path)
1360
1400
        if osutils.host_os_dereferences_symlinks():
1361
1401
            self.assertTrue(os.path.samefile('tree/newname', new_path))
1362
1402
        # make sure we can create files with the same parent directories
1363
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)