~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_diff.py

  • Committer: Robert Collins
  • Date: 2009-09-30 21:38:49 UTC
  • mto: (4634.52.8 2.0)
  • mto: This revision was merged to the branch mainline in revision 4723.
  • Revision ID: robertc@robertcollins.net-20090930213849-0vyqtge2lfd16nx5
Review feedback.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
 
1
# Copyright (C) 2005, 2006 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
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
import os
 
18
import os.path
18
19
from cStringIO import StringIO
 
20
import errno
19
21
import subprocess
20
22
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
 
    )
 
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()
41
76
 
42
77
 
43
78
def udiff_lines(old, new, allow_binary=False):
44
79
    output = StringIO()
45
 
    diff.internal_diff('old', old, 'new', new, output, allow_binary)
 
80
    internal_diff('old', old, 'new', new, output, allow_binary)
46
81
    output.seek(0, 0)
47
82
    return output.readlines()
48
83
 
52
87
        # StringIO has no fileno, so it tests a different codepath
53
88
        output = StringIO()
54
89
    else:
55
 
        output = tempfile.TemporaryFile()
 
90
        output = TemporaryFile()
56
91
    try:
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')
 
92
        external_diff('old', old, 'new', new, output, diff_opts=['-u'])
 
93
    except NoDiff:
 
94
        raise TestSkipped('external "diff" not present to test')
60
95
    output.seek(0, 0)
61
96
    lines = output.readlines()
62
97
    output.close()
63
98
    return lines
64
99
 
65
100
 
66
 
class TestDiff(tests.TestCase):
 
101
class TestDiff(TestCase):
67
102
 
68
103
    def test_add_nl(self):
69
104
        """diff generates a valid diff for patches that add a newline"""
105
140
            ## "Unterminated hunk header for patch:\n%s" % "".join(lines)
106
141
 
107
142
    def test_binary_lines(self):
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)
 
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)
114
147
 
115
148
    def test_external_diff(self):
116
149
        lines = external_udiff_lines(['boo\n'], ['goo\n'])
126
159
        self.check_patch(lines)
127
160
 
128
161
    def test_external_diff_binary_lang_c(self):
 
162
        old_env = {}
129
163
        for lang in ('LANG', 'LC_ALL', 'LANGUAGE'):
130
 
            self.overrideEnv(lang, 'C')
131
 
        lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
132
 
        # Older versions of diffutils say "Binary files", newer
133
 
        # versions just say "Files".
134
 
        self.assertContainsRe(lines[0], '(Binary f|F)iles old and new differ\n')
135
 
        self.assertEquals(lines[1:], ['\n'])
 
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)
136
175
 
137
176
    def test_no_external_diff(self):
138
177
        """Check that NoDiff is raised when diff is not available"""
139
 
        # Make sure no 'diff' command is available
140
 
        # XXX: Weird, using None instead of '' breaks the test -- vila 20101216
141
 
        self.overrideEnv('PATH', '')
142
 
        self.assertRaises(errors.NoDiff, diff.external_diff,
143
 
                          'old', ['boo\n'], 'new', ['goo\n'],
144
 
                          StringIO(), diff_opts=['-u'])
 
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
145
187
 
146
188
    def test_internal_diff_default(self):
147
189
        # Default internal diff encoding is utf8
148
190
        output = StringIO()
149
 
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
150
 
                           u'new_\xe5', ['new_text\n'], output)
 
191
        internal_diff(u'old_\xb5', ['old_text\n'],
 
192
                    u'new_\xe5', ['new_text\n'], output)
151
193
        lines = output.getvalue().splitlines(True)
152
194
        self.check_patch(lines)
153
195
        self.assertEquals(['--- old_\xc2\xb5\n',
161
203
 
162
204
    def test_internal_diff_utf8(self):
163
205
        output = StringIO()
164
 
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
165
 
                           u'new_\xe5', ['new_text\n'], output,
166
 
                           path_encoding='utf8')
 
206
        internal_diff(u'old_\xb5', ['old_text\n'],
 
207
                    u'new_\xe5', ['new_text\n'], output,
 
208
                    path_encoding='utf8')
167
209
        lines = output.getvalue().splitlines(True)
168
210
        self.check_patch(lines)
169
211
        self.assertEquals(['--- old_\xc2\xb5\n',
177
219
 
178
220
    def test_internal_diff_iso_8859_1(self):
179
221
        output = StringIO()
180
 
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
181
 
                           u'new_\xe5', ['new_text\n'], output,
182
 
                           path_encoding='iso-8859-1')
 
222
        internal_diff(u'old_\xb5', ['old_text\n'],
 
223
                    u'new_\xe5', ['new_text\n'], output,
 
224
                    path_encoding='iso-8859-1')
183
225
        lines = output.getvalue().splitlines(True)
184
226
        self.check_patch(lines)
185
227
        self.assertEquals(['--- old_\xb5\n',
193
235
 
194
236
    def test_internal_diff_no_content(self):
195
237
        output = StringIO()
196
 
        diff.internal_diff(u'old', [], u'new', [], output)
 
238
        internal_diff(u'old', [], u'new', [], output)
197
239
        self.assertEqual('', output.getvalue())
198
240
 
199
241
    def test_internal_diff_no_changes(self):
200
242
        output = StringIO()
201
 
        diff.internal_diff(u'old', ['text\n', 'contents\n'],
202
 
                           u'new', ['text\n', 'contents\n'],
203
 
                           output)
 
243
        internal_diff(u'old', ['text\n', 'contents\n'],
 
244
                      u'new', ['text\n', 'contents\n'],
 
245
                      output)
204
246
        self.assertEqual('', output.getvalue())
205
247
 
206
248
    def test_internal_diff_returns_bytes(self):
207
249
        import StringIO
208
250
        output = StringIO.StringIO()
209
 
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
210
 
                            u'new_\xe5', ['new_text\n'], output)
211
 
        self.assertIsInstance(output.getvalue(), str,
 
251
        internal_diff(u'old_\xb5', ['old_text\n'],
 
252
                    u'new_\xe5', ['new_text\n'], output)
 
253
        self.failUnless(isinstance(output.getvalue(), str),
212
254
            'internal_diff should return bytestrings')
213
255
 
214
256
 
215
 
class TestDiffFiles(tests.TestCaseInTempDir):
 
257
class TestDiffFiles(TestCaseInTempDir):
216
258
 
217
259
    def test_external_diff_binary(self):
218
260
        """The output when using external diff should use diff's i18n error"""
220
262
        lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
221
263
 
222
264
        cmd = ['diff', '-u', '--binary', 'old', 'new']
223
 
        with open('old', 'wb') as f: f.write('\x00foobar\n')
224
 
        with open('new', 'wb') as f: f.write('foo\x00bar\n')
 
265
        open('old', 'wb').write('\x00foobar\n')
 
266
        open('new', 'wb').write('foo\x00bar\n')
225
267
        pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
226
268
                                     stdin=subprocess.PIPE)
227
269
        out, err = pipe.communicate()
231
273
        self.assertEqual(out.splitlines(True) + ['\n'], lines)
232
274
 
233
275
 
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):
 
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):
248
292
 
249
293
    def setUp(self):
250
294
        super(TestDiffDates, self).setUp()
285
329
        os.utime('file1', (1144195200, 1144195200)) # 2006-04-05 00:00:00 UTC
286
330
 
287
331
    def test_diff_rev_tree_working_tree(self):
288
 
        output = get_diff_as_string(self.wt.basis_tree(), self.wt)
 
332
        output = self.get_diff(self.wt.basis_tree(), self.wt)
289
333
        # note that the date for old/file1 is from rev 2 rather than from
290
334
        # the basis revision (rev 4)
291
335
        self.assertEqualDiff(output, '''\
301
345
    def test_diff_rev_tree_rev_tree(self):
302
346
        tree1 = self.b.repository.revision_tree('rev-2')
303
347
        tree2 = self.b.repository.revision_tree('rev-3')
304
 
        output = get_diff_as_string(tree1, tree2)
 
348
        output = self.get_diff(tree1, tree2)
305
349
        self.assertEqualDiff(output, '''\
306
350
=== modified file 'file2'
307
351
--- old/file2\t2006-04-01 00:00:00 +0000
315
359
    def test_diff_add_files(self):
316
360
        tree1 = self.b.repository.revision_tree(_mod_revision.NULL_REVISION)
317
361
        tree2 = self.b.repository.revision_tree('rev-1')
318
 
        output = get_diff_as_string(tree1, tree2)
 
362
        output = self.get_diff(tree1, tree2)
319
363
        # the files have the epoch time stamp for the tree in which
320
364
        # they don't exist.
321
365
        self.assertEqualDiff(output, '''\
336
380
    def test_diff_remove_files(self):
337
381
        tree1 = self.b.repository.revision_tree('rev-3')
338
382
        tree2 = self.b.repository.revision_tree('rev-4')
339
 
        output = get_diff_as_string(tree1, tree2)
 
383
        output = self.get_diff(tree1, tree2)
340
384
        # the file has the epoch time stamp for the tree in which
341
385
        # it doesn't exist.
342
386
        self.assertEqualDiff(output, '''\
353
397
        self.wt.rename_one('file1', 'file1b')
354
398
        old_tree = self.b.repository.revision_tree('rev-1')
355
399
        new_tree = self.b.repository.revision_tree('rev-4')
356
 
        out = get_diff_as_string(old_tree, new_tree, specific_files=['file1b'],
 
400
        out = self.get_diff(old_tree, new_tree, specific_files=['file1b'],
357
401
                            working_tree=self.wt)
358
402
        self.assertContainsRe(out, 'file1\t')
359
403
 
365
409
        self.wt.rename_one('file1', 'dir1/file1')
366
410
        old_tree = self.b.repository.revision_tree('rev-1')
367
411
        new_tree = self.b.repository.revision_tree('rev-4')
368
 
        out = get_diff_as_string(old_tree, new_tree, specific_files=['dir1'],
 
412
        out = self.get_diff(old_tree, new_tree, specific_files=['dir1'],
369
413
                            working_tree=self.wt)
370
414
        self.assertContainsRe(out, 'file1\t')
371
 
        out = get_diff_as_string(old_tree, new_tree, specific_files=['dir2'],
 
415
        out = self.get_diff(old_tree, new_tree, specific_files=['dir2'],
372
416
                            working_tree=self.wt)
373
417
        self.assertNotContainsRe(out, 'file1\t')
374
418
 
375
419
 
376
 
class TestShowDiffTrees(tests.TestCaseWithTransport):
 
420
 
 
421
class TestShowDiffTrees(TestShowDiffTreesHelper):
377
422
    """Direct tests for show_diff_trees"""
378
423
 
379
424
    def test_modified_file(self):
384
429
        tree.commit('one', rev_id='rev-1')
385
430
 
386
431
        self.build_tree_contents([('tree/file', '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')
 
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')
393
438
 
394
439
    def test_modified_file_in_renamed_dir(self):
395
440
        """Test when a file is modified in a renamed directory."""
401
446
 
402
447
        tree.rename_one('dir', 'other')
403
448
        self.build_tree_contents([('tree/other/file', 'new contents\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")
 
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")
407
452
        # XXX: This is technically incorrect, because it used to be at another
408
453
        # location. What to do?
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')
 
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')
413
458
 
414
459
    def test_renamed_directory(self):
415
460
        """Test when only a directory is only renamed."""
420
465
        tree.commit('one', rev_id='rev-1')
421
466
 
422
467
        tree.rename_one('dir', 'newdir')
423
 
        d = get_diff_as_string(tree.basis_tree(), tree)
 
468
        diff = self.get_diff(tree.basis_tree(), tree)
424
469
        # Renaming a directory should be a single "you renamed this dir" even
425
470
        # when there are files inside.
426
 
        self.assertEqual(d, "=== renamed directory 'dir' => 'newdir'\n")
 
471
        self.assertEqual("=== renamed directory 'dir' => 'newdir'\n", diff)
427
472
 
428
473
    def test_renamed_file(self):
429
474
        """Test when a file is only renamed."""
433
478
        tree.commit('one', rev_id='rev-1')
434
479
 
435
480
        tree.rename_one('file', 'newname')
436
 
        d = get_diff_as_string(tree.basis_tree(), tree)
437
 
        self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
 
481
        diff = self.get_diff(tree.basis_tree(), tree)
 
482
        self.assertContainsRe(diff, "=== renamed file 'file' => 'newname'\n")
438
483
        # We shouldn't have a --- or +++ line, because there is no content
439
484
        # change
440
 
        self.assertNotContainsRe(d, '---')
 
485
        self.assertNotContainsRe(diff, '---')
441
486
 
442
487
    def test_renamed_and_modified_file(self):
443
488
        """Test when a file is only renamed."""
448
493
 
449
494
        tree.rename_one('file', 'newname')
450
495
        self.build_tree_contents([('tree/newname', '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')
 
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')
457
502
 
458
503
 
459
504
    def test_internal_diff_exec_property(self):
478
523
        tree.rename_one('c', 'new-c')
479
524
        tree.rename_one('d', 'new-d')
480
525
 
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'")
 
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
 
493
535
 
494
536
    def test_binary_unicode_filenames(self):
495
537
        """Test that contents of files are *not* encoded in UTF-8 when there
496
538
        is a binary file in the diff.
497
539
        """
498
540
        # See https://bugs.launchpad.net/bugs/110092.
499
 
        self.requireFeature(features.UnicodeFilenameFeature)
 
541
        self.requireFeature(tests.UnicodeFilenameFeature)
500
542
 
501
543
        # This bug isn't triggered with cStringIO.
502
544
        from StringIO import StringIO
510
552
        tree.add([alpha], ['file-id'])
511
553
        tree.add([omega], ['file-id-2'])
512
554
        diff_content = StringIO()
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,))
 
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,))
521
563
 
522
564
    def test_unicode_filename(self):
523
565
        """Test when the filename are unicode."""
524
 
        self.requireFeature(features.UnicodeFilenameFeature)
 
566
        self.requireFeature(tests.UnicodeFilenameFeature)
525
567
 
526
568
        alpha, omega = u'\u03b1', u'\u03c9'
527
569
        autf8, outf8 = alpha.encode('utf8'), omega.encode('utf8')
542
584
        tree.add(['add_'+alpha], ['file-id'])
543
585
        self.build_tree_contents([('tree/mod_'+alpha, 'contents_mod\n')])
544
586
 
545
 
        d = get_diff_as_string(tree.basis_tree(), tree)
546
 
        self.assertContainsRe(d,
 
587
        diff = self.get_diff(tree.basis_tree(), tree)
 
588
        self.assertContainsRe(diff,
547
589
                "=== renamed file 'ren_%s' => 'ren_%s'\n"%(autf8, outf8))
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):
 
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):
597
596
 
598
597
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
599
598
        self.to_file.write('was: ')
603
602
        pass
604
603
 
605
604
 
606
 
class TestDiffTree(tests.TestCaseWithTransport):
 
605
class TestDiffTree(TestCaseWithTransport):
607
606
 
608
607
    def setUp(self):
609
 
        super(TestDiffTree, self).setUp()
 
608
        TestCaseWithTransport.setUp(self)
610
609
        self.old_tree = self.make_branch_and_tree('old-tree')
611
610
        self.old_tree.lock_write()
612
611
        self.addCleanup(self.old_tree.unlock)
613
612
        self.new_tree = self.make_branch_and_tree('new-tree')
614
613
        self.new_tree.lock_write()
615
614
        self.addCleanup(self.new_tree.unlock)
616
 
        self.differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
 
615
        self.differ = DiffTree(self.old_tree, self.new_tree, StringIO())
617
616
 
618
617
    def test_diff_text(self):
619
618
        self.build_tree_contents([('old-tree/olddir/',),
624
623
                                  ('new-tree/newdir/newfile', 'new\n')])
625
624
        self.new_tree.add('newdir')
626
625
        self.new_tree.add('newdir/newfile', 'file-id')
627
 
        differ = diff.DiffText(self.old_tree, self.new_tree, StringIO())
 
626
        differ = DiffText(self.old_tree, self.new_tree, StringIO())
628
627
        differ.diff_text('file-id', None, 'old label', 'new label')
629
628
        self.assertEqual(
630
629
            '--- old label\n+++ new label\n@@ -1,1 +0,0 @@\n-old\n\n',
659
658
        self.assertContainsRe(self.differ.to_file.getvalue(), '\+contents')
660
659
 
661
660
    def test_diff_symlink(self):
662
 
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
661
        differ = DiffSymlink(self.old_tree, self.new_tree, StringIO())
663
662
        differ.diff_symlink('old target', None)
664
663
        self.assertEqual("=== target was 'old target'\n",
665
664
                         differ.to_file.getvalue())
666
665
 
667
 
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
666
        differ = DiffSymlink(self.old_tree, self.new_tree, StringIO())
668
667
        differ.diff_symlink(None, 'new target')
669
668
        self.assertEqual("=== target is 'new target'\n",
670
669
                         differ.to_file.getvalue())
671
670
 
672
 
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
671
        differ = DiffSymlink(self.old_tree, self.new_tree, StringIO())
673
672
        differ.diff_symlink('old target', 'new target')
674
673
        self.assertEqual("=== target changed 'old target' => 'new target'\n",
675
674
                         differ.to_file.getvalue())
690
689
             ' \@\@\n-old\n\+new\n\n')
691
690
 
692
691
    def test_diff_kind_change(self):
693
 
        self.requireFeature(features.SymlinkFeature)
 
692
        self.requireFeature(tests.SymlinkFeature)
694
693
        self.build_tree_contents([('old-tree/olddir/',),
695
694
                                  ('old-tree/olddir/oldfile', 'old\n')])
696
695
        self.old_tree.add('olddir')
725
724
 
726
725
    def test_register_diff(self):
727
726
        self.create_old_new()
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)
 
727
        old_diff_factories = DiffTree.diff_factories
 
728
        DiffTree.diff_factories=old_diff_factories[:]
 
729
        DiffTree.diff_factories.insert(0, DiffWasIs.from_diff_tree)
731
730
        try:
732
 
            differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
 
731
            differ = DiffTree(self.old_tree, self.new_tree, StringIO())
733
732
        finally:
734
 
            diff.DiffTree.diff_factories = old_diff_factories
 
733
            DiffTree.diff_factories = old_diff_factories
735
734
        differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
736
735
        self.assertNotContainsRe(
737
736
            differ.to_file.getvalue(),
742
741
 
743
742
    def test_extra_factories(self):
744
743
        self.create_old_new()
745
 
        differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO(),
746
 
                               extra_factories=[DiffWasIs.from_diff_tree])
 
744
        differ = DiffTree(self.old_tree, self.new_tree, StringIO(),
 
745
                            extra_factories=[DiffWasIs.from_diff_tree])
747
746
        differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
748
747
        self.assertNotContainsRe(
749
748
            differ.to_file.getvalue(),
762
761
            '.*a-file(.|\n)*b-file')
763
762
 
764
763
 
765
 
class TestPatienceDiffLib(tests.TestCase):
 
764
class TestPatienceDiffLib(TestCase):
766
765
 
767
766
    def setUp(self):
768
767
        super(TestPatienceDiffLib, self).setUp()
769
 
        self._unique_lcs = _patiencediff_py.unique_lcs_py
770
 
        self._recurse_matches = _patiencediff_py.recurse_matches_py
 
768
        self._unique_lcs = bzrlib._patiencediff_py.unique_lcs_py
 
769
        self._recurse_matches = bzrlib._patiencediff_py.recurse_matches_py
771
770
        self._PatienceSequenceMatcher = \
772
 
            _patiencediff_py.PatienceSequenceMatcher_py
 
771
            bzrlib._patiencediff_py.PatienceSequenceMatcher_py
773
772
 
774
773
    def test_diff_unicode_string(self):
775
774
        a = ''.join([unichr(i) for i in range(4000, 4500, 3)])
1082
1081
                 'how are you today?\n']
1083
1082
        txt_b = ['hello there\n',
1084
1083
                 'how are you today?\n']
1085
 
        unified_diff = patiencediff.unified_diff
 
1084
        unified_diff = bzrlib.patiencediff.unified_diff
1086
1085
        psm = self._PatienceSequenceMatcher
1087
1086
        self.assertEquals(['--- \n',
1088
1087
                           '+++ \n',
1136
1135
                 'how are you today?\n']
1137
1136
        txt_b = ['hello there\n',
1138
1137
                 'how are you today?\n']
1139
 
        unified_diff = patiencediff.unified_diff
 
1138
        unified_diff = bzrlib.patiencediff.unified_diff
1140
1139
        psm = self._PatienceSequenceMatcher
1141
1140
        self.assertEquals(['--- a\t2008-08-08\n',
1142
1141
                           '+++ b\t2008-09-09\n',
1154
1153
 
1155
1154
class TestPatienceDiffLib_c(TestPatienceDiffLib):
1156
1155
 
1157
 
    _test_needs_features = [features.compiled_patiencediff_feature]
 
1156
    _test_needs_features = [CompiledPatienceDiffFeature]
1158
1157
 
1159
1158
    def setUp(self):
1160
1159
        super(TestPatienceDiffLib_c, self).setUp()
1161
 
        from bzrlib import _patiencediff_c
1162
 
        self._unique_lcs = _patiencediff_c.unique_lcs_c
1163
 
        self._recurse_matches = _patiencediff_c.recurse_matches_c
 
1160
        import bzrlib._patiencediff_c
 
1161
        self._unique_lcs = bzrlib._patiencediff_c.unique_lcs_c
 
1162
        self._recurse_matches = bzrlib._patiencediff_c.recurse_matches_c
1164
1163
        self._PatienceSequenceMatcher = \
1165
 
            _patiencediff_c.PatienceSequenceMatcher_c
 
1164
            bzrlib._patiencediff_c.PatienceSequenceMatcher_c
1166
1165
 
1167
1166
    def test_unhashable(self):
1168
1167
        """We should get a proper exception here."""
1178
1177
                                         None, ['valid'], ['valid', []])
1179
1178
 
1180
1179
 
1181
 
class TestPatienceDiffLibFiles(tests.TestCaseInTempDir):
 
1180
class TestPatienceDiffLibFiles(TestCaseInTempDir):
1182
1181
 
1183
1182
    def setUp(self):
1184
1183
        super(TestPatienceDiffLibFiles, self).setUp()
1185
1184
        self._PatienceSequenceMatcher = \
1186
 
            _patiencediff_py.PatienceSequenceMatcher_py
 
1185
            bzrlib._patiencediff_py.PatienceSequenceMatcher_py
1187
1186
 
1188
1187
    def test_patience_unified_diff_files(self):
1189
1188
        txt_a = ['hello there\n',
1191
1190
                 'how are you today?\n']
1192
1191
        txt_b = ['hello there\n',
1193
1192
                 'how are you today?\n']
1194
 
        with open('a1', 'wb') as f: f.writelines(txt_a)
1195
 
        with open('b1', 'wb') as f: f.writelines(txt_b)
 
1193
        open('a1', 'wb').writelines(txt_a)
 
1194
        open('b1', 'wb').writelines(txt_b)
1196
1195
 
1197
 
        unified_diff_files = patiencediff.unified_diff_files
 
1196
        unified_diff_files = bzrlib.patiencediff.unified_diff_files
1198
1197
        psm = self._PatienceSequenceMatcher
1199
1198
        self.assertEquals(['--- a1\n',
1200
1199
                           '+++ b1\n',
1208
1207
 
1209
1208
        txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
1210
1209
        txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
1211
 
        with open('a2', 'wb') as f: f.writelines(txt_a)
1212
 
        with open('b2', 'wb') as f: f.writelines(txt_b)
 
1210
        open('a2', 'wb').writelines(txt_a)
 
1211
        open('b2', 'wb').writelines(txt_b)
1213
1212
 
1214
1213
        # This is the result with LongestCommonSubstring matching
1215
1214
        self.assertEquals(['--- a2\n',
1250
1249
 
1251
1250
class TestPatienceDiffLibFiles_c(TestPatienceDiffLibFiles):
1252
1251
 
1253
 
    _test_needs_features = [features.compiled_patiencediff_feature]
 
1252
    _test_needs_features = [CompiledPatienceDiffFeature]
1254
1253
 
1255
1254
    def setUp(self):
1256
1255
        super(TestPatienceDiffLibFiles_c, self).setUp()
1257
 
        from bzrlib import _patiencediff_c
 
1256
        import bzrlib._patiencediff_c
1258
1257
        self._PatienceSequenceMatcher = \
1259
 
            _patiencediff_c.PatienceSequenceMatcher_c
1260
 
 
1261
 
 
1262
 
class TestUsingCompiledIfAvailable(tests.TestCase):
 
1258
            bzrlib._patiencediff_c.PatienceSequenceMatcher_c
 
1259
 
 
1260
 
 
1261
class TestUsingCompiledIfAvailable(TestCase):
1263
1262
 
1264
1263
    def test_PatienceSequenceMatcher(self):
1265
 
        if features.compiled_patiencediff_feature.available():
 
1264
        if CompiledPatienceDiffFeature.available():
1266
1265
            from bzrlib._patiencediff_c import PatienceSequenceMatcher_c
1267
1266
            self.assertIs(PatienceSequenceMatcher_c,
1268
 
                          patiencediff.PatienceSequenceMatcher)
 
1267
                          bzrlib.patiencediff.PatienceSequenceMatcher)
1269
1268
        else:
1270
1269
            from bzrlib._patiencediff_py import PatienceSequenceMatcher_py
1271
1270
            self.assertIs(PatienceSequenceMatcher_py,
1272
 
                          patiencediff.PatienceSequenceMatcher)
 
1271
                          bzrlib.patiencediff.PatienceSequenceMatcher)
1273
1272
 
1274
1273
    def test_unique_lcs(self):
1275
 
        if features.compiled_patiencediff_feature.available():
 
1274
        if CompiledPatienceDiffFeature.available():
1276
1275
            from bzrlib._patiencediff_c import unique_lcs_c
1277
1276
            self.assertIs(unique_lcs_c,
1278
 
                          patiencediff.unique_lcs)
 
1277
                          bzrlib.patiencediff.unique_lcs)
1279
1278
        else:
1280
1279
            from bzrlib._patiencediff_py import unique_lcs_py
1281
1280
            self.assertIs(unique_lcs_py,
1282
 
                          patiencediff.unique_lcs)
 
1281
                          bzrlib.patiencediff.unique_lcs)
1283
1282
 
1284
1283
    def test_recurse_matches(self):
1285
 
        if features.compiled_patiencediff_feature.available():
 
1284
        if CompiledPatienceDiffFeature.available():
1286
1285
            from bzrlib._patiencediff_c import recurse_matches_c
1287
1286
            self.assertIs(recurse_matches_c,
1288
 
                          patiencediff.recurse_matches)
 
1287
                          bzrlib.patiencediff.recurse_matches)
1289
1288
        else:
1290
1289
            from bzrlib._patiencediff_py import recurse_matches_py
1291
1290
            self.assertIs(recurse_matches_py,
1292
 
                          patiencediff.recurse_matches)
1293
 
 
1294
 
 
1295
 
class TestDiffFromTool(tests.TestCaseWithTransport):
 
1291
                          bzrlib.patiencediff.recurse_matches)
 
1292
 
 
1293
 
 
1294
class TestDiffFromTool(TestCaseWithTransport):
1296
1295
 
1297
1296
    def test_from_string(self):
1298
 
        diff_obj = diff.DiffFromTool.from_string('diff', None, None, None)
 
1297
        diff_obj = DiffFromTool.from_string('diff', None, None, None)
1299
1298
        self.addCleanup(diff_obj.finish)
1300
 
        self.assertEqual(['diff', '@old_path', '@new_path'],
 
1299
        self.assertEqual(['diff', '%(old_path)s', '%(new_path)s'],
1301
1300
            diff_obj.command_template)
1302
1301
 
1303
1302
    def test_from_string_u5(self):
1304
 
        diff_obj = diff.DiffFromTool.from_string('diff "-u 5"',
1305
 
                                                 None, None, None)
 
1303
        diff_obj = DiffFromTool.from_string('diff -u\\ 5', None, None, None)
1306
1304
        self.addCleanup(diff_obj.finish)
1307
 
        self.assertEqual(['diff', '-u 5', '@old_path', '@new_path'],
 
1305
        self.assertEqual(['diff', '-u 5', '%(old_path)s', '%(new_path)s'],
1308
1306
                         diff_obj.command_template)
1309
1307
        self.assertEqual(['diff', '-u 5', 'old-path', 'new-path'],
1310
1308
                         diff_obj._get_command('old-path', 'new-path'))
1311
1309
 
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
 
 
1322
1310
    def test_execute(self):
1323
1311
        output = StringIO()
1324
 
        diff_obj = diff.DiffFromTool(['python', '-c',
1325
 
                                      'print "@old_path @new_path"'],
1326
 
                                     None, None, output)
 
1312
        diff_obj = DiffFromTool(['python', '-c',
 
1313
                                 'print "%(old_path)s %(new_path)s"'],
 
1314
                                None, None, output)
1327
1315
        self.addCleanup(diff_obj.finish)
1328
1316
        diff_obj._execute('old', 'new')
1329
1317
        self.assertEqual(output.getvalue().rstrip(), 'old new')
1330
1318
 
1331
1319
    def test_excute_missing(self):
1332
 
        diff_obj = diff.DiffFromTool(['a-tool-which-is-unlikely-to-exist'],
1333
 
                                     None, None, None)
 
1320
        diff_obj = DiffFromTool(['a-tool-which-is-unlikely-to-exist'],
 
1321
                                None, None, None)
1334
1322
        self.addCleanup(diff_obj.finish)
1335
 
        e = self.assertRaises(errors.ExecutableMissing, diff_obj._execute,
1336
 
                              'old', 'new')
 
1323
        e = self.assertRaises(ExecutableMissing, diff_obj._execute, 'old',
 
1324
                              'new')
1337
1325
        self.assertEqual('a-tool-which-is-unlikely-to-exist could not be found'
1338
1326
                         ' on this machine', str(e))
1339
1327
 
1340
1328
    def test_prepare_files_creates_paths_readable_by_windows_tool(self):
1341
 
        self.requireFeature(features.AttribFeature)
 
1329
        self.requireFeature(AttribFeature)
1342
1330
        output = StringIO()
1343
1331
        tree = self.make_branch_and_tree('tree')
1344
1332
        self.build_tree_contents([('tree/file', 'content')])
1346
1334
        tree.commit('old tree')
1347
1335
        tree.lock_read()
1348
1336
        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)
 
1337
        diff_obj = DiffFromTool(['python', '-c',
 
1338
                                 'print "%(old_path)s %(new_path)s"'],
 
1339
                                tree, tree, output)
1355
1340
        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$')
 
1341
        self.assertReadableByAttrib(diff_obj._root, 'old\\file', r'old\\file')
 
1342
        self.assertReadableByAttrib(diff_obj._root, 'new\\file', r'new\\file')
1362
1343
 
1363
1344
    def assertReadableByAttrib(self, cwd, relpath, regex):
1364
1345
        proc = subprocess.Popen(['attrib', relpath],
1365
1346
                                stdout=subprocess.PIPE,
1366
1347
                                cwd=cwd)
1367
 
        (result, err) = proc.communicate()
1368
 
        self.assertContainsRe(result.replace('\r\n', '\n'), regex)
 
1348
        proc.wait()
 
1349
        result = proc.stdout.read()
 
1350
        self.assertContainsRe(result, regex)
1369
1351
 
1370
1352
    def test_prepare_files(self):
1371
1353
        output = StringIO()
1374
1356
        self.build_tree_contents([('tree/oldname2', 'oldcontent2')])
1375
1357
        tree.add('oldname', 'file-id')
1376
1358
        tree.add('oldname2', 'file2-id')
1377
 
        # Earliest allowable date on FAT32 filesystems is 1980-01-01
1378
 
        tree.commit('old tree', timestamp=315532800)
 
1359
        tree.commit('old tree', timestamp=0)
1379
1360
        tree.rename_one('oldname', 'newname')
1380
1361
        tree.rename_one('oldname2', 'newname2')
1381
1362
        self.build_tree_contents([('tree/newname', 'newcontent')])
1385
1366
        self.addCleanup(old_tree.unlock)
1386
1367
        tree.lock_read()
1387
1368
        self.addCleanup(tree.unlock)
1388
 
        diff_obj = diff.DiffFromTool(['python', '-c',
1389
 
                                      'print "@old_path @new_path"'],
1390
 
                                     old_tree, tree, output)
 
1369
        diff_obj = DiffFromTool(['python', '-c',
 
1370
                                 'print "%(old_path)s %(new_path)s"'],
 
1371
                                old_tree, tree, output)
1391
1372
        self.addCleanup(diff_obj.finish)
1392
1373
        self.assertContainsRe(diff_obj._root, 'bzr-diff-[^/]*')
1393
1374
        old_path, new_path = diff_obj._prepare_files('file-id', 'oldname',
1394
1375
                                                     'newname')
1395
1376
        self.assertContainsRe(old_path, 'old/oldname$')
1396
 
        self.assertEqual(315532800, os.stat(old_path).st_mtime)
1397
 
        self.assertContainsRe(new_path, 'tree/newname$')
 
1377
        self.assertEqual(0, os.stat(old_path).st_mtime)
 
1378
        self.assertContainsRe(new_path, 'new/newname$')
1398
1379
        self.assertFileEqual('oldcontent', old_path)
1399
1380
        self.assertFileEqual('newcontent', new_path)
1400
1381
        if osutils.host_os_dereferences_symlinks():
1401
1382
            self.assertTrue(os.path.samefile('tree/newname', new_path))
1402
1383
        # make sure we can create files with the same parent directories
1403
1384
        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)