~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_diff.py

Add bzrlib.tests.per_repository_vf.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005-2011 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
import os
 
18
from cStringIO import StringIO
 
19
import subprocess
 
20
import sys
 
21
import tempfile
 
22
 
 
23
from bzrlib import (
 
24
    diff,
 
25
    errors,
 
26
    osutils,
 
27
    patiencediff,
 
28
    _patiencediff_py,
 
29
    revision as _mod_revision,
 
30
    revisionspec,
 
31
    revisiontree,
 
32
    tests,
 
33
    transform,
 
34
    )
 
35
from bzrlib.symbol_versioning import deprecated_in
 
36
from bzrlib.tests import features
 
37
from bzrlib.tests.blackbox.test_diff import subst_dates
 
38
 
 
39
 
 
40
class _AttribFeature(tests.Feature):
 
41
 
 
42
    def _probe(self):
 
43
        if (sys.platform not in ('cygwin', 'win32')):
 
44
            return False
 
45
        try:
 
46
            proc = subprocess.Popen(['attrib', '.'], stdout=subprocess.PIPE)
 
47
        except OSError, e:
 
48
            return False
 
49
        return (0 == proc.wait())
 
50
 
 
51
    def feature_name(self):
 
52
        return 'attrib Windows command-line tool'
 
53
 
 
54
AttribFeature = _AttribFeature()
 
55
 
 
56
 
 
57
compiled_patiencediff_feature = tests.ModuleAvailableFeature(
 
58
                                    'bzrlib._patiencediff_c')
 
59
 
 
60
 
 
61
def udiff_lines(old, new, allow_binary=False):
 
62
    output = StringIO()
 
63
    diff.internal_diff('old', old, 'new', new, output, allow_binary)
 
64
    output.seek(0, 0)
 
65
    return output.readlines()
 
66
 
 
67
 
 
68
def external_udiff_lines(old, new, use_stringio=False):
 
69
    if use_stringio:
 
70
        # StringIO has no fileno, so it tests a different codepath
 
71
        output = StringIO()
 
72
    else:
 
73
        output = tempfile.TemporaryFile()
 
74
    try:
 
75
        diff.external_diff('old', old, 'new', new, output, diff_opts=['-u'])
 
76
    except errors.NoDiff:
 
77
        raise tests.TestSkipped('external "diff" not present to test')
 
78
    output.seek(0, 0)
 
79
    lines = output.readlines()
 
80
    output.close()
 
81
    return lines
 
82
 
 
83
 
 
84
class TestDiff(tests.TestCase):
 
85
 
 
86
    def test_add_nl(self):
 
87
        """diff generates a valid diff for patches that add a newline"""
 
88
        lines = udiff_lines(['boo'], ['boo\n'])
 
89
        self.check_patch(lines)
 
90
        self.assertEquals(lines[4], '\\ No newline at end of file\n')
 
91
            ## "expected no-nl, got %r" % lines[4]
 
92
 
 
93
    def test_add_nl_2(self):
 
94
        """diff generates a valid diff for patches that change last line and
 
95
        add a newline.
 
96
        """
 
97
        lines = udiff_lines(['boo'], ['goo\n'])
 
98
        self.check_patch(lines)
 
99
        self.assertEquals(lines[4], '\\ No newline at end of file\n')
 
100
            ## "expected no-nl, got %r" % lines[4]
 
101
 
 
102
    def test_remove_nl(self):
 
103
        """diff generates a valid diff for patches that change last line and
 
104
        add a newline.
 
105
        """
 
106
        lines = udiff_lines(['boo\n'], ['boo'])
 
107
        self.check_patch(lines)
 
108
        self.assertEquals(lines[5], '\\ No newline at end of file\n')
 
109
            ## "expected no-nl, got %r" % lines[5]
 
110
 
 
111
    def check_patch(self, lines):
 
112
        self.assert_(len(lines) > 1)
 
113
            ## "Not enough lines for a file header for patch:\n%s" % "".join(lines)
 
114
        self.assert_(lines[0].startswith ('---'))
 
115
            ## 'No orig line for patch:\n%s' % "".join(lines)
 
116
        self.assert_(lines[1].startswith ('+++'))
 
117
            ## 'No mod line for patch:\n%s' % "".join(lines)
 
118
        self.assert_(len(lines) > 2)
 
119
            ## "No hunks for patch:\n%s" % "".join(lines)
 
120
        self.assert_(lines[2].startswith('@@'))
 
121
            ## "No hunk header for patch:\n%s" % "".join(lines)
 
122
        self.assert_('@@' in lines[2][2:])
 
123
            ## "Unterminated hunk header for patch:\n%s" % "".join(lines)
 
124
 
 
125
    def test_binary_lines(self):
 
126
        empty = []
 
127
        uni_lines = [1023 * 'a' + '\x00']
 
128
        self.assertRaises(errors.BinaryFile, udiff_lines, uni_lines , empty)
 
129
        self.assertRaises(errors.BinaryFile, udiff_lines, empty, uni_lines)
 
130
        udiff_lines(uni_lines , empty, allow_binary=True)
 
131
        udiff_lines(empty, uni_lines, allow_binary=True)
 
132
 
 
133
    def test_external_diff(self):
 
134
        lines = external_udiff_lines(['boo\n'], ['goo\n'])
 
135
        self.check_patch(lines)
 
136
        self.assertEqual('\n', lines[-1])
 
137
 
 
138
    def test_external_diff_no_fileno(self):
 
139
        # Make sure that we can handle not having a fileno, even
 
140
        # if the diff is large
 
141
        lines = external_udiff_lines(['boo\n']*10000,
 
142
                                     ['goo\n']*10000,
 
143
                                     use_stringio=True)
 
144
        self.check_patch(lines)
 
145
 
 
146
    def test_external_diff_binary_lang_c(self):
 
147
        for lang in ('LANG', 'LC_ALL', 'LANGUAGE'):
 
148
            self.overrideEnv(lang, 'C')
 
149
        lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
 
150
        # Older versions of diffutils say "Binary files", newer
 
151
        # versions just say "Files".
 
152
        self.assertContainsRe(lines[0], '(Binary f|F)iles old and new differ\n')
 
153
        self.assertEquals(lines[1:], ['\n'])
 
154
 
 
155
    def test_no_external_diff(self):
 
156
        """Check that NoDiff is raised when diff is not available"""
 
157
        # Make sure no 'diff' command is available
 
158
        # XXX: Weird, using None instead of '' breaks the test -- vila 20101216
 
159
        self.overrideEnv('PATH', '')
 
160
        self.assertRaises(errors.NoDiff, diff.external_diff,
 
161
                          'old', ['boo\n'], 'new', ['goo\n'],
 
162
                          StringIO(), diff_opts=['-u'])
 
163
 
 
164
    def test_internal_diff_default(self):
 
165
        # Default internal diff encoding is utf8
 
166
        output = StringIO()
 
167
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
 
168
                           u'new_\xe5', ['new_text\n'], output)
 
169
        lines = output.getvalue().splitlines(True)
 
170
        self.check_patch(lines)
 
171
        self.assertEquals(['--- old_\xc2\xb5\n',
 
172
                           '+++ new_\xc3\xa5\n',
 
173
                           '@@ -1,1 +1,1 @@\n',
 
174
                           '-old_text\n',
 
175
                           '+new_text\n',
 
176
                           '\n',
 
177
                          ]
 
178
                          , lines)
 
179
 
 
180
    def test_internal_diff_utf8(self):
 
181
        output = StringIO()
 
182
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
 
183
                           u'new_\xe5', ['new_text\n'], output,
 
184
                           path_encoding='utf8')
 
185
        lines = output.getvalue().splitlines(True)
 
186
        self.check_patch(lines)
 
187
        self.assertEquals(['--- old_\xc2\xb5\n',
 
188
                           '+++ new_\xc3\xa5\n',
 
189
                           '@@ -1,1 +1,1 @@\n',
 
190
                           '-old_text\n',
 
191
                           '+new_text\n',
 
192
                           '\n',
 
193
                          ]
 
194
                          , lines)
 
195
 
 
196
    def test_internal_diff_iso_8859_1(self):
 
197
        output = StringIO()
 
198
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
 
199
                           u'new_\xe5', ['new_text\n'], output,
 
200
                           path_encoding='iso-8859-1')
 
201
        lines = output.getvalue().splitlines(True)
 
202
        self.check_patch(lines)
 
203
        self.assertEquals(['--- old_\xb5\n',
 
204
                           '+++ new_\xe5\n',
 
205
                           '@@ -1,1 +1,1 @@\n',
 
206
                           '-old_text\n',
 
207
                           '+new_text\n',
 
208
                           '\n',
 
209
                          ]
 
210
                          , lines)
 
211
 
 
212
    def test_internal_diff_no_content(self):
 
213
        output = StringIO()
 
214
        diff.internal_diff(u'old', [], u'new', [], output)
 
215
        self.assertEqual('', output.getvalue())
 
216
 
 
217
    def test_internal_diff_no_changes(self):
 
218
        output = StringIO()
 
219
        diff.internal_diff(u'old', ['text\n', 'contents\n'],
 
220
                           u'new', ['text\n', 'contents\n'],
 
221
                           output)
 
222
        self.assertEqual('', output.getvalue())
 
223
 
 
224
    def test_internal_diff_returns_bytes(self):
 
225
        import StringIO
 
226
        output = StringIO.StringIO()
 
227
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
 
228
                            u'new_\xe5', ['new_text\n'], output)
 
229
        self.failUnless(isinstance(output.getvalue(), str),
 
230
            'internal_diff should return bytestrings')
 
231
 
 
232
 
 
233
class TestDiffFiles(tests.TestCaseInTempDir):
 
234
 
 
235
    def test_external_diff_binary(self):
 
236
        """The output when using external diff should use diff's i18n error"""
 
237
        # Make sure external_diff doesn't fail in the current LANG
 
238
        lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
 
239
 
 
240
        cmd = ['diff', '-u', '--binary', 'old', 'new']
 
241
        open('old', 'wb').write('\x00foobar\n')
 
242
        open('new', 'wb').write('foo\x00bar\n')
 
243
        pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
 
244
                                     stdin=subprocess.PIPE)
 
245
        out, err = pipe.communicate()
 
246
        # Diff returns '2' on Binary files.
 
247
        self.assertEqual(2, pipe.returncode)
 
248
        # We should output whatever diff tells us, plus a trailing newline
 
249
        self.assertEqual(out.splitlines(True) + ['\n'], lines)
 
250
 
 
251
 
 
252
class TestShowDiffTreesHelper(tests.TestCaseWithTransport):
 
253
    """Has a helper for running show_diff_trees"""
 
254
 
 
255
    def get_diff(self, tree1, tree2, specific_files=None, working_tree=None):
 
256
        output = StringIO()
 
257
        if working_tree is not None:
 
258
            extra_trees = (working_tree,)
 
259
        else:
 
260
            extra_trees = ()
 
261
        diff.show_diff_trees(tree1, tree2, output,
 
262
                             specific_files=specific_files,
 
263
                             extra_trees=extra_trees, old_label='old/',
 
264
                             new_label='new/')
 
265
        return output.getvalue()
 
266
 
 
267
 
 
268
class TestDiffDates(TestShowDiffTreesHelper):
 
269
 
 
270
    def setUp(self):
 
271
        super(TestDiffDates, self).setUp()
 
272
        self.wt = self.make_branch_and_tree('.')
 
273
        self.b = self.wt.branch
 
274
        self.build_tree_contents([
 
275
            ('file1', 'file1 contents at rev 1\n'),
 
276
            ('file2', 'file2 contents at rev 1\n')
 
277
            ])
 
278
        self.wt.add(['file1', 'file2'])
 
279
        self.wt.commit(
 
280
            message='Revision 1',
 
281
            timestamp=1143849600, # 2006-04-01 00:00:00 UTC
 
282
            timezone=0,
 
283
            rev_id='rev-1')
 
284
        self.build_tree_contents([('file1', 'file1 contents at rev 2\n')])
 
285
        self.wt.commit(
 
286
            message='Revision 2',
 
287
            timestamp=1143936000, # 2006-04-02 00:00:00 UTC
 
288
            timezone=28800,
 
289
            rev_id='rev-2')
 
290
        self.build_tree_contents([('file2', 'file2 contents at rev 3\n')])
 
291
        self.wt.commit(
 
292
            message='Revision 3',
 
293
            timestamp=1144022400, # 2006-04-03 00:00:00 UTC
 
294
            timezone=-3600,
 
295
            rev_id='rev-3')
 
296
        self.wt.remove(['file2'])
 
297
        self.wt.commit(
 
298
            message='Revision 4',
 
299
            timestamp=1144108800, # 2006-04-04 00:00:00 UTC
 
300
            timezone=0,
 
301
            rev_id='rev-4')
 
302
        self.build_tree_contents([
 
303
            ('file1', 'file1 contents in working tree\n')
 
304
            ])
 
305
        # set the date stamps for files in the working tree to known values
 
306
        os.utime('file1', (1144195200, 1144195200)) # 2006-04-05 00:00:00 UTC
 
307
 
 
308
    def test_diff_rev_tree_working_tree(self):
 
309
        output = self.get_diff(self.wt.basis_tree(), self.wt)
 
310
        # note that the date for old/file1 is from rev 2 rather than from
 
311
        # the basis revision (rev 4)
 
312
        self.assertEqualDiff(output, '''\
 
313
=== modified file 'file1'
 
314
--- old/file1\t2006-04-02 00:00:00 +0000
 
315
+++ new/file1\t2006-04-05 00:00:00 +0000
 
316
@@ -1,1 +1,1 @@
 
317
-file1 contents at rev 2
 
318
+file1 contents in working tree
 
319
 
 
320
''')
 
321
 
 
322
    def test_diff_rev_tree_rev_tree(self):
 
323
        tree1 = self.b.repository.revision_tree('rev-2')
 
324
        tree2 = self.b.repository.revision_tree('rev-3')
 
325
        output = self.get_diff(tree1, tree2)
 
326
        self.assertEqualDiff(output, '''\
 
327
=== modified file 'file2'
 
328
--- old/file2\t2006-04-01 00:00:00 +0000
 
329
+++ new/file2\t2006-04-03 00:00:00 +0000
 
330
@@ -1,1 +1,1 @@
 
331
-file2 contents at rev 1
 
332
+file2 contents at rev 3
 
333
 
 
334
''')
 
335
 
 
336
    def test_diff_add_files(self):
 
337
        tree1 = self.b.repository.revision_tree(_mod_revision.NULL_REVISION)
 
338
        tree2 = self.b.repository.revision_tree('rev-1')
 
339
        output = self.get_diff(tree1, tree2)
 
340
        # the files have the epoch time stamp for the tree in which
 
341
        # they don't exist.
 
342
        self.assertEqualDiff(output, '''\
 
343
=== added file 'file1'
 
344
--- old/file1\t1970-01-01 00:00:00 +0000
 
345
+++ new/file1\t2006-04-01 00:00:00 +0000
 
346
@@ -0,0 +1,1 @@
 
347
+file1 contents at rev 1
 
348
 
 
349
=== added file 'file2'
 
350
--- old/file2\t1970-01-01 00:00:00 +0000
 
351
+++ new/file2\t2006-04-01 00:00:00 +0000
 
352
@@ -0,0 +1,1 @@
 
353
+file2 contents at rev 1
 
354
 
 
355
''')
 
356
 
 
357
    def test_diff_remove_files(self):
 
358
        tree1 = self.b.repository.revision_tree('rev-3')
 
359
        tree2 = self.b.repository.revision_tree('rev-4')
 
360
        output = self.get_diff(tree1, tree2)
 
361
        # the file has the epoch time stamp for the tree in which
 
362
        # it doesn't exist.
 
363
        self.assertEqualDiff(output, '''\
 
364
=== removed file 'file2'
 
365
--- old/file2\t2006-04-03 00:00:00 +0000
 
366
+++ new/file2\t1970-01-01 00:00:00 +0000
 
367
@@ -1,1 +0,0 @@
 
368
-file2 contents at rev 3
 
369
 
 
370
''')
 
371
 
 
372
    def test_show_diff_specified(self):
 
373
        """A working tree filename can be used to identify a file"""
 
374
        self.wt.rename_one('file1', 'file1b')
 
375
        old_tree = self.b.repository.revision_tree('rev-1')
 
376
        new_tree = self.b.repository.revision_tree('rev-4')
 
377
        out = self.get_diff(old_tree, new_tree, specific_files=['file1b'],
 
378
                            working_tree=self.wt)
 
379
        self.assertContainsRe(out, 'file1\t')
 
380
 
 
381
    def test_recursive_diff(self):
 
382
        """Children of directories are matched"""
 
383
        os.mkdir('dir1')
 
384
        os.mkdir('dir2')
 
385
        self.wt.add(['dir1', 'dir2'])
 
386
        self.wt.rename_one('file1', 'dir1/file1')
 
387
        old_tree = self.b.repository.revision_tree('rev-1')
 
388
        new_tree = self.b.repository.revision_tree('rev-4')
 
389
        out = self.get_diff(old_tree, new_tree, specific_files=['dir1'],
 
390
                            working_tree=self.wt)
 
391
        self.assertContainsRe(out, 'file1\t')
 
392
        out = self.get_diff(old_tree, new_tree, specific_files=['dir2'],
 
393
                            working_tree=self.wt)
 
394
        self.assertNotContainsRe(out, 'file1\t')
 
395
 
 
396
 
 
397
 
 
398
class TestShowDiffTrees(TestShowDiffTreesHelper):
 
399
    """Direct tests for show_diff_trees"""
 
400
 
 
401
    def test_modified_file(self):
 
402
        """Test when a file is modified."""
 
403
        tree = self.make_branch_and_tree('tree')
 
404
        self.build_tree_contents([('tree/file', 'contents\n')])
 
405
        tree.add(['file'], ['file-id'])
 
406
        tree.commit('one', rev_id='rev-1')
 
407
 
 
408
        self.build_tree_contents([('tree/file', 'new contents\n')])
 
409
        d = self.get_diff(tree.basis_tree(), tree)
 
410
        self.assertContainsRe(d, "=== modified file 'file'\n")
 
411
        self.assertContainsRe(d, '--- old/file\t')
 
412
        self.assertContainsRe(d, '\\+\\+\\+ new/file\t')
 
413
        self.assertContainsRe(d, '-contents\n'
 
414
                                 '\\+new contents\n')
 
415
 
 
416
    def test_modified_file_in_renamed_dir(self):
 
417
        """Test when a file is modified in a renamed directory."""
 
418
        tree = self.make_branch_and_tree('tree')
 
419
        self.build_tree(['tree/dir/'])
 
420
        self.build_tree_contents([('tree/dir/file', 'contents\n')])
 
421
        tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
 
422
        tree.commit('one', rev_id='rev-1')
 
423
 
 
424
        tree.rename_one('dir', 'other')
 
425
        self.build_tree_contents([('tree/other/file', 'new contents\n')])
 
426
        d = self.get_diff(tree.basis_tree(), tree)
 
427
        self.assertContainsRe(d, "=== renamed directory 'dir' => 'other'\n")
 
428
        self.assertContainsRe(d, "=== modified file 'other/file'\n")
 
429
        # XXX: This is technically incorrect, because it used to be at another
 
430
        # location. What to do?
 
431
        self.assertContainsRe(d, '--- old/dir/file\t')
 
432
        self.assertContainsRe(d, '\\+\\+\\+ new/other/file\t')
 
433
        self.assertContainsRe(d, '-contents\n'
 
434
                                 '\\+new contents\n')
 
435
 
 
436
    def test_renamed_directory(self):
 
437
        """Test when only a directory is only renamed."""
 
438
        tree = self.make_branch_and_tree('tree')
 
439
        self.build_tree(['tree/dir/'])
 
440
        self.build_tree_contents([('tree/dir/file', 'contents\n')])
 
441
        tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
 
442
        tree.commit('one', rev_id='rev-1')
 
443
 
 
444
        tree.rename_one('dir', 'newdir')
 
445
        d = self.get_diff(tree.basis_tree(), tree)
 
446
        # Renaming a directory should be a single "you renamed this dir" even
 
447
        # when there are files inside.
 
448
        self.assertEqual(d, "=== renamed directory 'dir' => 'newdir'\n")
 
449
 
 
450
    def test_renamed_file(self):
 
451
        """Test when a file is only renamed."""
 
452
        tree = self.make_branch_and_tree('tree')
 
453
        self.build_tree_contents([('tree/file', 'contents\n')])
 
454
        tree.add(['file'], ['file-id'])
 
455
        tree.commit('one', rev_id='rev-1')
 
456
 
 
457
        tree.rename_one('file', 'newname')
 
458
        d = self.get_diff(tree.basis_tree(), tree)
 
459
        self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
 
460
        # We shouldn't have a --- or +++ line, because there is no content
 
461
        # change
 
462
        self.assertNotContainsRe(d, '---')
 
463
 
 
464
    def test_renamed_and_modified_file(self):
 
465
        """Test when a file is only renamed."""
 
466
        tree = self.make_branch_and_tree('tree')
 
467
        self.build_tree_contents([('tree/file', 'contents\n')])
 
468
        tree.add(['file'], ['file-id'])
 
469
        tree.commit('one', rev_id='rev-1')
 
470
 
 
471
        tree.rename_one('file', 'newname')
 
472
        self.build_tree_contents([('tree/newname', 'new contents\n')])
 
473
        d = self.get_diff(tree.basis_tree(), tree)
 
474
        self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
 
475
        self.assertContainsRe(d, '--- old/file\t')
 
476
        self.assertContainsRe(d, '\\+\\+\\+ new/newname\t')
 
477
        self.assertContainsRe(d, '-contents\n'
 
478
                                 '\\+new contents\n')
 
479
 
 
480
 
 
481
    def test_internal_diff_exec_property(self):
 
482
        tree = self.make_branch_and_tree('tree')
 
483
 
 
484
        tt = transform.TreeTransform(tree)
 
485
        tt.new_file('a', tt.root, 'contents\n', 'a-id', True)
 
486
        tt.new_file('b', tt.root, 'contents\n', 'b-id', False)
 
487
        tt.new_file('c', tt.root, 'contents\n', 'c-id', True)
 
488
        tt.new_file('d', tt.root, 'contents\n', 'd-id', False)
 
489
        tt.new_file('e', tt.root, 'contents\n', 'control-e-id', True)
 
490
        tt.new_file('f', tt.root, 'contents\n', 'control-f-id', False)
 
491
        tt.apply()
 
492
        tree.commit('one', rev_id='rev-1')
 
493
 
 
494
        tt = transform.TreeTransform(tree)
 
495
        tt.set_executability(False, tt.trans_id_file_id('a-id'))
 
496
        tt.set_executability(True, tt.trans_id_file_id('b-id'))
 
497
        tt.set_executability(False, tt.trans_id_file_id('c-id'))
 
498
        tt.set_executability(True, tt.trans_id_file_id('d-id'))
 
499
        tt.apply()
 
500
        tree.rename_one('c', 'new-c')
 
501
        tree.rename_one('d', 'new-d')
 
502
 
 
503
        d = self.get_diff(tree.basis_tree(), tree)
 
504
 
 
505
        self.assertContainsRe(d, r"file 'a'.*\(properties changed:"
 
506
                                  ".*\+x to -x.*\)")
 
507
        self.assertContainsRe(d, r"file 'b'.*\(properties changed:"
 
508
                                  ".*-x to \+x.*\)")
 
509
        self.assertContainsRe(d, r"file 'c'.*\(properties changed:"
 
510
                                  ".*\+x to -x.*\)")
 
511
        self.assertContainsRe(d, r"file 'd'.*\(properties changed:"
 
512
                                  ".*-x to \+x.*\)")
 
513
        self.assertNotContainsRe(d, r"file 'e'")
 
514
        self.assertNotContainsRe(d, r"file 'f'")
 
515
 
 
516
    def test_binary_unicode_filenames(self):
 
517
        """Test that contents of files are *not* encoded in UTF-8 when there
 
518
        is a binary file in the diff.
 
519
        """
 
520
        # See https://bugs.launchpad.net/bugs/110092.
 
521
        self.requireFeature(tests.UnicodeFilenameFeature)
 
522
 
 
523
        # This bug isn't triggered with cStringIO.
 
524
        from StringIO import StringIO
 
525
        tree = self.make_branch_and_tree('tree')
 
526
        alpha, omega = u'\u03b1', u'\u03c9'
 
527
        alpha_utf8, omega_utf8 = alpha.encode('utf8'), omega.encode('utf8')
 
528
        self.build_tree_contents(
 
529
            [('tree/' + alpha, chr(0)),
 
530
             ('tree/' + omega,
 
531
              ('The %s and the %s\n' % (alpha_utf8, omega_utf8)))])
 
532
        tree.add([alpha], ['file-id'])
 
533
        tree.add([omega], ['file-id-2'])
 
534
        diff_content = StringIO()
 
535
        diff.show_diff_trees(tree.basis_tree(), tree, diff_content)
 
536
        d = diff_content.getvalue()
 
537
        self.assertContainsRe(d, r"=== added file '%s'" % alpha_utf8)
 
538
        self.assertContainsRe(d, "Binary files a/%s.*and b/%s.* differ\n"
 
539
                              % (alpha_utf8, alpha_utf8))
 
540
        self.assertContainsRe(d, r"=== added file '%s'" % omega_utf8)
 
541
        self.assertContainsRe(d, r"--- a/%s" % (omega_utf8,))
 
542
        self.assertContainsRe(d, r"\+\+\+ b/%s" % (omega_utf8,))
 
543
 
 
544
    def test_unicode_filename(self):
 
545
        """Test when the filename are unicode."""
 
546
        self.requireFeature(tests.UnicodeFilenameFeature)
 
547
 
 
548
        alpha, omega = u'\u03b1', u'\u03c9'
 
549
        autf8, outf8 = alpha.encode('utf8'), omega.encode('utf8')
 
550
 
 
551
        tree = self.make_branch_and_tree('tree')
 
552
        self.build_tree_contents([('tree/ren_'+alpha, 'contents\n')])
 
553
        tree.add(['ren_'+alpha], ['file-id-2'])
 
554
        self.build_tree_contents([('tree/del_'+alpha, 'contents\n')])
 
555
        tree.add(['del_'+alpha], ['file-id-3'])
 
556
        self.build_tree_contents([('tree/mod_'+alpha, 'contents\n')])
 
557
        tree.add(['mod_'+alpha], ['file-id-4'])
 
558
 
 
559
        tree.commit('one', rev_id='rev-1')
 
560
 
 
561
        tree.rename_one('ren_'+alpha, 'ren_'+omega)
 
562
        tree.remove('del_'+alpha)
 
563
        self.build_tree_contents([('tree/add_'+alpha, 'contents\n')])
 
564
        tree.add(['add_'+alpha], ['file-id'])
 
565
        self.build_tree_contents([('tree/mod_'+alpha, 'contents_mod\n')])
 
566
 
 
567
        d = self.get_diff(tree.basis_tree(), tree)
 
568
        self.assertContainsRe(d,
 
569
                "=== renamed file 'ren_%s' => 'ren_%s'\n"%(autf8, outf8))
 
570
        self.assertContainsRe(d, "=== added file 'add_%s'"%autf8)
 
571
        self.assertContainsRe(d, "=== modified file 'mod_%s'"%autf8)
 
572
        self.assertContainsRe(d, "=== removed file 'del_%s'"%autf8)
 
573
 
 
574
    def test_unicode_filename_path_encoding(self):
 
575
        """Test for bug #382699: unicode filenames on Windows should be shown
 
576
        in user encoding.
 
577
        """
 
578
        self.requireFeature(tests.UnicodeFilenameFeature)
 
579
        # The word 'test' in Russian
 
580
        _russian_test = u'\u0422\u0435\u0441\u0442'
 
581
        directory = _russian_test + u'/'
 
582
        test_txt = _russian_test + u'.txt'
 
583
        u1234 = u'\u1234.txt'
 
584
 
 
585
        tree = self.make_branch_and_tree('.')
 
586
        self.build_tree_contents([
 
587
            (test_txt, 'foo\n'),
 
588
            (u1234, 'foo\n'),
 
589
            (directory, None),
 
590
            ])
 
591
        tree.add([test_txt, u1234, directory])
 
592
 
 
593
        sio = StringIO()
 
594
        diff.show_diff_trees(tree.basis_tree(), tree, sio,
 
595
            path_encoding='cp1251')
 
596
 
 
597
        output = subst_dates(sio.getvalue())
 
598
        shouldbe = ('''\
 
599
=== added directory '%(directory)s'
 
600
=== added file '%(test_txt)s'
 
601
--- a/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
602
+++ b/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
603
@@ -0,0 +1,1 @@
 
604
+foo
 
605
 
 
606
=== added file '?.txt'
 
607
--- a/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
608
+++ b/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
609
@@ -0,0 +1,1 @@
 
610
+foo
 
611
 
 
612
''' % {'directory': _russian_test.encode('cp1251'),
 
613
       'test_txt': test_txt.encode('cp1251'),
 
614
      })
 
615
        self.assertEqualDiff(output, shouldbe)
 
616
 
 
617
 
 
618
class DiffWasIs(diff.DiffPath):
 
619
 
 
620
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
 
621
        self.to_file.write('was: ')
 
622
        self.to_file.write(self.old_tree.get_file(file_id).read())
 
623
        self.to_file.write('is: ')
 
624
        self.to_file.write(self.new_tree.get_file(file_id).read())
 
625
        pass
 
626
 
 
627
 
 
628
class TestDiffTree(tests.TestCaseWithTransport):
 
629
 
 
630
    def setUp(self):
 
631
        super(TestDiffTree, self).setUp()
 
632
        self.old_tree = self.make_branch_and_tree('old-tree')
 
633
        self.old_tree.lock_write()
 
634
        self.addCleanup(self.old_tree.unlock)
 
635
        self.new_tree = self.make_branch_and_tree('new-tree')
 
636
        self.new_tree.lock_write()
 
637
        self.addCleanup(self.new_tree.unlock)
 
638
        self.differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
 
639
 
 
640
    def test_diff_text(self):
 
641
        self.build_tree_contents([('old-tree/olddir/',),
 
642
                                  ('old-tree/olddir/oldfile', 'old\n')])
 
643
        self.old_tree.add('olddir')
 
644
        self.old_tree.add('olddir/oldfile', 'file-id')
 
645
        self.build_tree_contents([('new-tree/newdir/',),
 
646
                                  ('new-tree/newdir/newfile', 'new\n')])
 
647
        self.new_tree.add('newdir')
 
648
        self.new_tree.add('newdir/newfile', 'file-id')
 
649
        differ = diff.DiffText(self.old_tree, self.new_tree, StringIO())
 
650
        differ.diff_text('file-id', None, 'old label', 'new label')
 
651
        self.assertEqual(
 
652
            '--- old label\n+++ new label\n@@ -1,1 +0,0 @@\n-old\n\n',
 
653
            differ.to_file.getvalue())
 
654
        differ.to_file.seek(0)
 
655
        differ.diff_text(None, 'file-id', 'old label', 'new label')
 
656
        self.assertEqual(
 
657
            '--- old label\n+++ new label\n@@ -0,0 +1,1 @@\n+new\n\n',
 
658
            differ.to_file.getvalue())
 
659
        differ.to_file.seek(0)
 
660
        differ.diff_text('file-id', 'file-id', 'old label', 'new label')
 
661
        self.assertEqual(
 
662
            '--- old label\n+++ new label\n@@ -1,1 +1,1 @@\n-old\n+new\n\n',
 
663
            differ.to_file.getvalue())
 
664
 
 
665
    def test_diff_deletion(self):
 
666
        self.build_tree_contents([('old-tree/file', 'contents'),
 
667
                                  ('new-tree/file', 'contents')])
 
668
        self.old_tree.add('file', 'file-id')
 
669
        self.new_tree.add('file', 'file-id')
 
670
        os.unlink('new-tree/file')
 
671
        self.differ.show_diff(None)
 
672
        self.assertContainsRe(self.differ.to_file.getvalue(), '-contents')
 
673
 
 
674
    def test_diff_creation(self):
 
675
        self.build_tree_contents([('old-tree/file', 'contents'),
 
676
                                  ('new-tree/file', 'contents')])
 
677
        self.old_tree.add('file', 'file-id')
 
678
        self.new_tree.add('file', 'file-id')
 
679
        os.unlink('old-tree/file')
 
680
        self.differ.show_diff(None)
 
681
        self.assertContainsRe(self.differ.to_file.getvalue(), '\+contents')
 
682
 
 
683
    def test_diff_symlink(self):
 
684
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
685
        differ.diff_symlink('old target', None)
 
686
        self.assertEqual("=== target was 'old target'\n",
 
687
                         differ.to_file.getvalue())
 
688
 
 
689
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
690
        differ.diff_symlink(None, 'new target')
 
691
        self.assertEqual("=== target is 'new target'\n",
 
692
                         differ.to_file.getvalue())
 
693
 
 
694
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
695
        differ.diff_symlink('old target', 'new target')
 
696
        self.assertEqual("=== target changed 'old target' => 'new target'\n",
 
697
                         differ.to_file.getvalue())
 
698
 
 
699
    def test_diff(self):
 
700
        self.build_tree_contents([('old-tree/olddir/',),
 
701
                                  ('old-tree/olddir/oldfile', 'old\n')])
 
702
        self.old_tree.add('olddir')
 
703
        self.old_tree.add('olddir/oldfile', 'file-id')
 
704
        self.build_tree_contents([('new-tree/newdir/',),
 
705
                                  ('new-tree/newdir/newfile', 'new\n')])
 
706
        self.new_tree.add('newdir')
 
707
        self.new_tree.add('newdir/newfile', 'file-id')
 
708
        self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
 
709
        self.assertContainsRe(
 
710
            self.differ.to_file.getvalue(),
 
711
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
 
712
             ' \@\@\n-old\n\+new\n\n')
 
713
 
 
714
    def test_diff_kind_change(self):
 
715
        self.requireFeature(tests.SymlinkFeature)
 
716
        self.build_tree_contents([('old-tree/olddir/',),
 
717
                                  ('old-tree/olddir/oldfile', 'old\n')])
 
718
        self.old_tree.add('olddir')
 
719
        self.old_tree.add('olddir/oldfile', 'file-id')
 
720
        self.build_tree(['new-tree/newdir/'])
 
721
        os.symlink('new', 'new-tree/newdir/newfile')
 
722
        self.new_tree.add('newdir')
 
723
        self.new_tree.add('newdir/newfile', 'file-id')
 
724
        self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
 
725
        self.assertContainsRe(
 
726
            self.differ.to_file.getvalue(),
 
727
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+0,0'
 
728
             ' \@\@\n-old\n\n')
 
729
        self.assertContainsRe(self.differ.to_file.getvalue(),
 
730
                              "=== target is u'new'\n")
 
731
 
 
732
    def test_diff_directory(self):
 
733
        self.build_tree(['new-tree/new-dir/'])
 
734
        self.new_tree.add('new-dir', 'new-dir-id')
 
735
        self.differ.diff('new-dir-id', None, 'new-dir')
 
736
        self.assertEqual(self.differ.to_file.getvalue(), '')
 
737
 
 
738
    def create_old_new(self):
 
739
        self.build_tree_contents([('old-tree/olddir/',),
 
740
                                  ('old-tree/olddir/oldfile', 'old\n')])
 
741
        self.old_tree.add('olddir')
 
742
        self.old_tree.add('olddir/oldfile', 'file-id')
 
743
        self.build_tree_contents([('new-tree/newdir/',),
 
744
                                  ('new-tree/newdir/newfile', 'new\n')])
 
745
        self.new_tree.add('newdir')
 
746
        self.new_tree.add('newdir/newfile', 'file-id')
 
747
 
 
748
    def test_register_diff(self):
 
749
        self.create_old_new()
 
750
        old_diff_factories = diff.DiffTree.diff_factories
 
751
        diff.DiffTree.diff_factories=old_diff_factories[:]
 
752
        diff.DiffTree.diff_factories.insert(0, DiffWasIs.from_diff_tree)
 
753
        try:
 
754
            differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
 
755
        finally:
 
756
            diff.DiffTree.diff_factories = old_diff_factories
 
757
        differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
 
758
        self.assertNotContainsRe(
 
759
            differ.to_file.getvalue(),
 
760
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
 
761
             ' \@\@\n-old\n\+new\n\n')
 
762
        self.assertContainsRe(differ.to_file.getvalue(),
 
763
                              'was: old\nis: new\n')
 
764
 
 
765
    def test_extra_factories(self):
 
766
        self.create_old_new()
 
767
        differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO(),
 
768
                               extra_factories=[DiffWasIs.from_diff_tree])
 
769
        differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
 
770
        self.assertNotContainsRe(
 
771
            differ.to_file.getvalue(),
 
772
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
 
773
             ' \@\@\n-old\n\+new\n\n')
 
774
        self.assertContainsRe(differ.to_file.getvalue(),
 
775
                              'was: old\nis: new\n')
 
776
 
 
777
    def test_alphabetical_order(self):
 
778
        self.build_tree(['new-tree/a-file'])
 
779
        self.new_tree.add('a-file')
 
780
        self.build_tree(['old-tree/b-file'])
 
781
        self.old_tree.add('b-file')
 
782
        self.differ.show_diff(None)
 
783
        self.assertContainsRe(self.differ.to_file.getvalue(),
 
784
            '.*a-file(.|\n)*b-file')
 
785
 
 
786
 
 
787
class TestPatienceDiffLib(tests.TestCase):
 
788
 
 
789
    def setUp(self):
 
790
        super(TestPatienceDiffLib, self).setUp()
 
791
        self._unique_lcs = _patiencediff_py.unique_lcs_py
 
792
        self._recurse_matches = _patiencediff_py.recurse_matches_py
 
793
        self._PatienceSequenceMatcher = \
 
794
            _patiencediff_py.PatienceSequenceMatcher_py
 
795
 
 
796
    def test_diff_unicode_string(self):
 
797
        a = ''.join([unichr(i) for i in range(4000, 4500, 3)])
 
798
        b = ''.join([unichr(i) for i in range(4300, 4800, 2)])
 
799
        sm = self._PatienceSequenceMatcher(None, a, b)
 
800
        mb = sm.get_matching_blocks()
 
801
        self.assertEquals(35, len(mb))
 
802
 
 
803
    def test_unique_lcs(self):
 
804
        unique_lcs = self._unique_lcs
 
805
        self.assertEquals(unique_lcs('', ''), [])
 
806
        self.assertEquals(unique_lcs('', 'a'), [])
 
807
        self.assertEquals(unique_lcs('a', ''), [])
 
808
        self.assertEquals(unique_lcs('a', 'a'), [(0,0)])
 
809
        self.assertEquals(unique_lcs('a', 'b'), [])
 
810
        self.assertEquals(unique_lcs('ab', 'ab'), [(0,0), (1,1)])
 
811
        self.assertEquals(unique_lcs('abcde', 'cdeab'), [(2,0), (3,1), (4,2)])
 
812
        self.assertEquals(unique_lcs('cdeab', 'abcde'), [(0,2), (1,3), (2,4)])
 
813
        self.assertEquals(unique_lcs('abXde', 'abYde'), [(0,0), (1,1),
 
814
                                                         (3,3), (4,4)])
 
815
        self.assertEquals(unique_lcs('acbac', 'abc'), [(2,1)])
 
816
 
 
817
    def test_recurse_matches(self):
 
818
        def test_one(a, b, matches):
 
819
            test_matches = []
 
820
            self._recurse_matches(
 
821
                a, b, 0, 0, len(a), len(b), test_matches, 10)
 
822
            self.assertEquals(test_matches, matches)
 
823
 
 
824
        test_one(['a', '', 'b', '', 'c'], ['a', 'a', 'b', 'c', 'c'],
 
825
                 [(0, 0), (2, 2), (4, 4)])
 
826
        test_one(['a', 'c', 'b', 'a', 'c'], ['a', 'b', 'c'],
 
827
                 [(0, 0), (2, 1), (4, 2)])
 
828
        # Even though 'bc' is not unique globally, and is surrounded by
 
829
        # non-matching lines, we should still match, because they are locally
 
830
        # unique
 
831
        test_one('abcdbce', 'afbcgdbce', [(0,0), (1, 2), (2, 3), (3, 5),
 
832
                                          (4, 6), (5, 7), (6, 8)])
 
833
 
 
834
        # recurse_matches doesn't match non-unique
 
835
        # lines surrounded by bogus text.
 
836
        # The update has been done in patiencediff.SequenceMatcher instead
 
837
 
 
838
        # This is what it could be
 
839
        #test_one('aBccDe', 'abccde', [(0,0), (2,2), (3,3), (5,5)])
 
840
 
 
841
        # This is what it currently gives:
 
842
        test_one('aBccDe', 'abccde', [(0,0), (5,5)])
 
843
 
 
844
    def assertDiffBlocks(self, a, b, expected_blocks):
 
845
        """Check that the sequence matcher returns the correct blocks.
 
846
 
 
847
        :param a: A sequence to match
 
848
        :param b: Another sequence to match
 
849
        :param expected_blocks: The expected output, not including the final
 
850
            matching block (len(a), len(b), 0)
 
851
        """
 
852
        matcher = self._PatienceSequenceMatcher(None, a, b)
 
853
        blocks = matcher.get_matching_blocks()
 
854
        last = blocks.pop()
 
855
        self.assertEqual((len(a), len(b), 0), last)
 
856
        self.assertEqual(expected_blocks, blocks)
 
857
 
 
858
    def test_matching_blocks(self):
 
859
        # Some basic matching tests
 
860
        self.assertDiffBlocks('', '', [])
 
861
        self.assertDiffBlocks([], [], [])
 
862
        self.assertDiffBlocks('abc', '', [])
 
863
        self.assertDiffBlocks('', 'abc', [])
 
864
        self.assertDiffBlocks('abcd', 'abcd', [(0, 0, 4)])
 
865
        self.assertDiffBlocks('abcd', 'abce', [(0, 0, 3)])
 
866
        self.assertDiffBlocks('eabc', 'abce', [(1, 0, 3)])
 
867
        self.assertDiffBlocks('eabce', 'abce', [(1, 0, 4)])
 
868
        self.assertDiffBlocks('abcde', 'abXde', [(0, 0, 2), (3, 3, 2)])
 
869
        self.assertDiffBlocks('abcde', 'abXYZde', [(0, 0, 2), (3, 5, 2)])
 
870
        self.assertDiffBlocks('abde', 'abXYZde', [(0, 0, 2), (2, 5, 2)])
 
871
        # This may check too much, but it checks to see that
 
872
        # a copied block stays attached to the previous section,
 
873
        # not the later one.
 
874
        # difflib would tend to grab the trailing longest match
 
875
        # which would make the diff not look right
 
876
        self.assertDiffBlocks('abcdefghijklmnop', 'abcdefxydefghijklmnop',
 
877
                              [(0, 0, 6), (6, 11, 10)])
 
878
 
 
879
        # make sure it supports passing in lists
 
880
        self.assertDiffBlocks(
 
881
                   ['hello there\n',
 
882
                    'world\n',
 
883
                    'how are you today?\n'],
 
884
                   ['hello there\n',
 
885
                    'how are you today?\n'],
 
886
                [(0, 0, 1), (2, 1, 1)])
 
887
 
 
888
        # non unique lines surrounded by non-matching lines
 
889
        # won't be found
 
890
        self.assertDiffBlocks('aBccDe', 'abccde', [(0,0,1), (5,5,1)])
 
891
 
 
892
        # But they only need to be locally unique
 
893
        self.assertDiffBlocks('aBcDec', 'abcdec', [(0,0,1), (2,2,1), (4,4,2)])
 
894
 
 
895
        # non unique blocks won't be matched
 
896
        self.assertDiffBlocks('aBcdEcdFg', 'abcdecdfg', [(0,0,1), (8,8,1)])
 
897
 
 
898
        # but locally unique ones will
 
899
        self.assertDiffBlocks('aBcdEeXcdFg', 'abcdecdfg', [(0,0,1), (2,2,2),
 
900
                                              (5,4,1), (7,5,2), (10,8,1)])
 
901
 
 
902
        self.assertDiffBlocks('abbabbXd', 'cabbabxd', [(7,7,1)])
 
903
        self.assertDiffBlocks('abbabbbb', 'cabbabbc', [])
 
904
        self.assertDiffBlocks('bbbbbbbb', 'cbbbbbbc', [])
 
905
 
 
906
    def test_matching_blocks_tuples(self):
 
907
        # Some basic matching tests
 
908
        self.assertDiffBlocks([], [], [])
 
909
        self.assertDiffBlocks([('a',), ('b',), ('c,')], [], [])
 
910
        self.assertDiffBlocks([], [('a',), ('b',), ('c,')], [])
 
911
        self.assertDiffBlocks([('a',), ('b',), ('c,')],
 
912
                              [('a',), ('b',), ('c,')],
 
913
                              [(0, 0, 3)])
 
914
        self.assertDiffBlocks([('a',), ('b',), ('c,')],
 
915
                              [('a',), ('b',), ('d,')],
 
916
                              [(0, 0, 2)])
 
917
        self.assertDiffBlocks([('d',), ('b',), ('c,')],
 
918
                              [('a',), ('b',), ('c,')],
 
919
                              [(1, 1, 2)])
 
920
        self.assertDiffBlocks([('d',), ('a',), ('b',), ('c,')],
 
921
                              [('a',), ('b',), ('c,')],
 
922
                              [(1, 0, 3)])
 
923
        self.assertDiffBlocks([('a', 'b'), ('c', 'd'), ('e', 'f')],
 
924
                              [('a', 'b'), ('c', 'X'), ('e', 'f')],
 
925
                              [(0, 0, 1), (2, 2, 1)])
 
926
        self.assertDiffBlocks([('a', 'b'), ('c', 'd'), ('e', 'f')],
 
927
                              [('a', 'b'), ('c', 'dX'), ('e', 'f')],
 
928
                              [(0, 0, 1), (2, 2, 1)])
 
929
 
 
930
    def test_opcodes(self):
 
931
        def chk_ops(a, b, expected_codes):
 
932
            s = self._PatienceSequenceMatcher(None, a, b)
 
933
            self.assertEquals(expected_codes, s.get_opcodes())
 
934
 
 
935
        chk_ops('', '', [])
 
936
        chk_ops([], [], [])
 
937
        chk_ops('abc', '', [('delete', 0,3, 0,0)])
 
938
        chk_ops('', 'abc', [('insert', 0,0, 0,3)])
 
939
        chk_ops('abcd', 'abcd', [('equal',    0,4, 0,4)])
 
940
        chk_ops('abcd', 'abce', [('equal',   0,3, 0,3),
 
941
                                 ('replace', 3,4, 3,4)
 
942
                                ])
 
943
        chk_ops('eabc', 'abce', [('delete', 0,1, 0,0),
 
944
                                 ('equal',  1,4, 0,3),
 
945
                                 ('insert', 4,4, 3,4)
 
946
                                ])
 
947
        chk_ops('eabce', 'abce', [('delete', 0,1, 0,0),
 
948
                                  ('equal',  1,5, 0,4)
 
949
                                 ])
 
950
        chk_ops('abcde', 'abXde', [('equal',   0,2, 0,2),
 
951
                                   ('replace', 2,3, 2,3),
 
952
                                   ('equal',   3,5, 3,5)
 
953
                                  ])
 
954
        chk_ops('abcde', 'abXYZde', [('equal',   0,2, 0,2),
 
955
                                     ('replace', 2,3, 2,5),
 
956
                                     ('equal',   3,5, 5,7)
 
957
                                    ])
 
958
        chk_ops('abde', 'abXYZde', [('equal',  0,2, 0,2),
 
959
                                    ('insert', 2,2, 2,5),
 
960
                                    ('equal',  2,4, 5,7)
 
961
                                   ])
 
962
        chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
 
963
                [('equal',  0,6,  0,6),
 
964
                 ('insert', 6,6,  6,11),
 
965
                 ('equal',  6,16, 11,21)
 
966
                ])
 
967
        chk_ops(
 
968
                [ 'hello there\n'
 
969
                , 'world\n'
 
970
                , 'how are you today?\n'],
 
971
                [ 'hello there\n'
 
972
                , 'how are you today?\n'],
 
973
                [('equal',  0,1, 0,1),
 
974
                 ('delete', 1,2, 1,1),
 
975
                 ('equal',  2,3, 1,2),
 
976
                ])
 
977
        chk_ops('aBccDe', 'abccde',
 
978
                [('equal',   0,1, 0,1),
 
979
                 ('replace', 1,5, 1,5),
 
980
                 ('equal',   5,6, 5,6),
 
981
                ])
 
982
        chk_ops('aBcDec', 'abcdec',
 
983
                [('equal',   0,1, 0,1),
 
984
                 ('replace', 1,2, 1,2),
 
985
                 ('equal',   2,3, 2,3),
 
986
                 ('replace', 3,4, 3,4),
 
987
                 ('equal',   4,6, 4,6),
 
988
                ])
 
989
        chk_ops('aBcdEcdFg', 'abcdecdfg',
 
990
                [('equal',   0,1, 0,1),
 
991
                 ('replace', 1,8, 1,8),
 
992
                 ('equal',   8,9, 8,9)
 
993
                ])
 
994
        chk_ops('aBcdEeXcdFg', 'abcdecdfg',
 
995
                [('equal',   0,1, 0,1),
 
996
                 ('replace', 1,2, 1,2),
 
997
                 ('equal',   2,4, 2,4),
 
998
                 ('delete', 4,5, 4,4),
 
999
                 ('equal',   5,6, 4,5),
 
1000
                 ('delete', 6,7, 5,5),
 
1001
                 ('equal',   7,9, 5,7),
 
1002
                 ('replace', 9,10, 7,8),
 
1003
                 ('equal',   10,11, 8,9)
 
1004
                ])
 
1005
 
 
1006
    def test_grouped_opcodes(self):
 
1007
        def chk_ops(a, b, expected_codes, n=3):
 
1008
            s = self._PatienceSequenceMatcher(None, a, b)
 
1009
            self.assertEquals(expected_codes, list(s.get_grouped_opcodes(n)))
 
1010
 
 
1011
        chk_ops('', '', [])
 
1012
        chk_ops([], [], [])
 
1013
        chk_ops('abc', '', [[('delete', 0,3, 0,0)]])
 
1014
        chk_ops('', 'abc', [[('insert', 0,0, 0,3)]])
 
1015
        chk_ops('abcd', 'abcd', [])
 
1016
        chk_ops('abcd', 'abce', [[('equal',   0,3, 0,3),
 
1017
                                  ('replace', 3,4, 3,4)
 
1018
                                 ]])
 
1019
        chk_ops('eabc', 'abce', [[('delete', 0,1, 0,0),
 
1020
                                 ('equal',  1,4, 0,3),
 
1021
                                 ('insert', 4,4, 3,4)
 
1022
                                ]])
 
1023
        chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
 
1024
                [[('equal',  3,6, 3,6),
 
1025
                  ('insert', 6,6, 6,11),
 
1026
                  ('equal',  6,9, 11,14)
 
1027
                  ]])
 
1028
        chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
 
1029
                [[('equal',  2,6, 2,6),
 
1030
                  ('insert', 6,6, 6,11),
 
1031
                  ('equal',  6,10, 11,15)
 
1032
                  ]], 4)
 
1033
        chk_ops('Xabcdef', 'abcdef',
 
1034
                [[('delete', 0,1, 0,0),
 
1035
                  ('equal',  1,4, 0,3)
 
1036
                  ]])
 
1037
        chk_ops('abcdef', 'abcdefX',
 
1038
                [[('equal',  3,6, 3,6),
 
1039
                  ('insert', 6,6, 6,7)
 
1040
                  ]])
 
1041
 
 
1042
 
 
1043
    def test_multiple_ranges(self):
 
1044
        # There was an earlier bug where we used a bad set of ranges,
 
1045
        # this triggers that specific bug, to make sure it doesn't regress
 
1046
        self.assertDiffBlocks('abcdefghijklmnop',
 
1047
                              'abcXghiYZQRSTUVWXYZijklmnop',
 
1048
                              [(0, 0, 3), (6, 4, 3), (9, 20, 7)])
 
1049
 
 
1050
        self.assertDiffBlocks('ABCd efghIjk  L',
 
1051
                              'AxyzBCn mo pqrstuvwI1 2  L',
 
1052
                              [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
 
1053
 
 
1054
        # These are rot13 code snippets.
 
1055
        self.assertDiffBlocks('''\
 
1056
    trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
 
1057
    """
 
1058
    gnxrf_netf = ['svyr*']
 
1059
    gnxrf_bcgvbaf = ['ab-erphefr']
 
1060
 
 
1061
    qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr):
 
1062
        sebz omeyvo.nqq vzcbeg fzneg_nqq, nqq_ercbegre_cevag, nqq_ercbegre_ahyy
 
1063
        vs vf_dhvrg():
 
1064
            ercbegre = nqq_ercbegre_ahyy
 
1065
        ryfr:
 
1066
            ercbegre = nqq_ercbegre_cevag
 
1067
        fzneg_nqq(svyr_yvfg, abg ab_erphefr, ercbegre)
 
1068
 
 
1069
 
 
1070
pynff pzq_zxqve(Pbzznaq):
 
1071
'''.splitlines(True), '''\
 
1072
    trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
 
1073
 
 
1074
    --qel-eha jvyy fubj juvpu svyrf jbhyq or nqqrq, ohg abg npghnyyl
 
1075
    nqq gurz.
 
1076
    """
 
1077
    gnxrf_netf = ['svyr*']
 
1078
    gnxrf_bcgvbaf = ['ab-erphefr', 'qel-eha']
 
1079
 
 
1080
    qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr, qel_eha=Snyfr):
 
1081
        vzcbeg omeyvo.nqq
 
1082
 
 
1083
        vs qel_eha:
 
1084
            vs vf_dhvrg():
 
1085
                # Guvf vf cbvagyrff, ohg V'q engure abg envfr na reebe
 
1086
                npgvba = omeyvo.nqq.nqq_npgvba_ahyy
 
1087
            ryfr:
 
1088
  npgvba = omeyvo.nqq.nqq_npgvba_cevag
 
1089
        ryvs vf_dhvrg():
 
1090
            npgvba = omeyvo.nqq.nqq_npgvba_nqq
 
1091
        ryfr:
 
1092
       npgvba = omeyvo.nqq.nqq_npgvba_nqq_naq_cevag
 
1093
 
 
1094
        omeyvo.nqq.fzneg_nqq(svyr_yvfg, abg ab_erphefr, npgvba)
 
1095
 
 
1096
 
 
1097
pynff pzq_zxqve(Pbzznaq):
 
1098
'''.splitlines(True)
 
1099
, [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
 
1100
 
 
1101
    def test_patience_unified_diff(self):
 
1102
        txt_a = ['hello there\n',
 
1103
                 'world\n',
 
1104
                 'how are you today?\n']
 
1105
        txt_b = ['hello there\n',
 
1106
                 'how are you today?\n']
 
1107
        unified_diff = patiencediff.unified_diff
 
1108
        psm = self._PatienceSequenceMatcher
 
1109
        self.assertEquals(['--- \n',
 
1110
                           '+++ \n',
 
1111
                           '@@ -1,3 +1,2 @@\n',
 
1112
                           ' hello there\n',
 
1113
                           '-world\n',
 
1114
                           ' how are you today?\n'
 
1115
                          ]
 
1116
                          , list(unified_diff(txt_a, txt_b,
 
1117
                                 sequencematcher=psm)))
 
1118
        txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
 
1119
        txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
 
1120
        # This is the result with LongestCommonSubstring matching
 
1121
        self.assertEquals(['--- \n',
 
1122
                           '+++ \n',
 
1123
                           '@@ -1,6 +1,11 @@\n',
 
1124
                           ' a\n',
 
1125
                           ' b\n',
 
1126
                           ' c\n',
 
1127
                           '+d\n',
 
1128
                           '+e\n',
 
1129
                           '+f\n',
 
1130
                           '+x\n',
 
1131
                           '+y\n',
 
1132
                           ' d\n',
 
1133
                           ' e\n',
 
1134
                           ' f\n']
 
1135
                          , list(unified_diff(txt_a, txt_b)))
 
1136
        # And the patience diff
 
1137
        self.assertEquals(['--- \n',
 
1138
                           '+++ \n',
 
1139
                           '@@ -4,6 +4,11 @@\n',
 
1140
                           ' d\n',
 
1141
                           ' e\n',
 
1142
                           ' f\n',
 
1143
                           '+x\n',
 
1144
                           '+y\n',
 
1145
                           '+d\n',
 
1146
                           '+e\n',
 
1147
                           '+f\n',
 
1148
                           ' g\n',
 
1149
                           ' h\n',
 
1150
                           ' i\n',
 
1151
                          ]
 
1152
                          , list(unified_diff(txt_a, txt_b,
 
1153
                                 sequencematcher=psm)))
 
1154
 
 
1155
    def test_patience_unified_diff_with_dates(self):
 
1156
        txt_a = ['hello there\n',
 
1157
                 'world\n',
 
1158
                 'how are you today?\n']
 
1159
        txt_b = ['hello there\n',
 
1160
                 'how are you today?\n']
 
1161
        unified_diff = patiencediff.unified_diff
 
1162
        psm = self._PatienceSequenceMatcher
 
1163
        self.assertEquals(['--- a\t2008-08-08\n',
 
1164
                           '+++ b\t2008-09-09\n',
 
1165
                           '@@ -1,3 +1,2 @@\n',
 
1166
                           ' hello there\n',
 
1167
                           '-world\n',
 
1168
                           ' how are you today?\n'
 
1169
                          ]
 
1170
                          , list(unified_diff(txt_a, txt_b,
 
1171
                                 fromfile='a', tofile='b',
 
1172
                                 fromfiledate='2008-08-08',
 
1173
                                 tofiledate='2008-09-09',
 
1174
                                 sequencematcher=psm)))
 
1175
 
 
1176
 
 
1177
class TestPatienceDiffLib_c(TestPatienceDiffLib):
 
1178
 
 
1179
    _test_needs_features = [compiled_patiencediff_feature]
 
1180
 
 
1181
    def setUp(self):
 
1182
        super(TestPatienceDiffLib_c, self).setUp()
 
1183
        from bzrlib import _patiencediff_c
 
1184
        self._unique_lcs = _patiencediff_c.unique_lcs_c
 
1185
        self._recurse_matches = _patiencediff_c.recurse_matches_c
 
1186
        self._PatienceSequenceMatcher = \
 
1187
            _patiencediff_c.PatienceSequenceMatcher_c
 
1188
 
 
1189
    def test_unhashable(self):
 
1190
        """We should get a proper exception here."""
 
1191
        # We need to be able to hash items in the sequence, lists are
 
1192
        # unhashable, and thus cannot be diffed
 
1193
        e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
 
1194
                                         None, [[]], [])
 
1195
        e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
 
1196
                                         None, ['valid', []], [])
 
1197
        e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
 
1198
                                         None, ['valid'], [[]])
 
1199
        e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
 
1200
                                         None, ['valid'], ['valid', []])
 
1201
 
 
1202
 
 
1203
class TestPatienceDiffLibFiles(tests.TestCaseInTempDir):
 
1204
 
 
1205
    def setUp(self):
 
1206
        super(TestPatienceDiffLibFiles, self).setUp()
 
1207
        self._PatienceSequenceMatcher = \
 
1208
            _patiencediff_py.PatienceSequenceMatcher_py
 
1209
 
 
1210
    def test_patience_unified_diff_files(self):
 
1211
        txt_a = ['hello there\n',
 
1212
                 'world\n',
 
1213
                 'how are you today?\n']
 
1214
        txt_b = ['hello there\n',
 
1215
                 'how are you today?\n']
 
1216
        open('a1', 'wb').writelines(txt_a)
 
1217
        open('b1', 'wb').writelines(txt_b)
 
1218
 
 
1219
        unified_diff_files = patiencediff.unified_diff_files
 
1220
        psm = self._PatienceSequenceMatcher
 
1221
        self.assertEquals(['--- a1\n',
 
1222
                           '+++ b1\n',
 
1223
                           '@@ -1,3 +1,2 @@\n',
 
1224
                           ' hello there\n',
 
1225
                           '-world\n',
 
1226
                           ' how are you today?\n',
 
1227
                          ]
 
1228
                          , list(unified_diff_files('a1', 'b1',
 
1229
                                 sequencematcher=psm)))
 
1230
 
 
1231
        txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
 
1232
        txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
 
1233
        open('a2', 'wb').writelines(txt_a)
 
1234
        open('b2', 'wb').writelines(txt_b)
 
1235
 
 
1236
        # This is the result with LongestCommonSubstring matching
 
1237
        self.assertEquals(['--- a2\n',
 
1238
                           '+++ b2\n',
 
1239
                           '@@ -1,6 +1,11 @@\n',
 
1240
                           ' a\n',
 
1241
                           ' b\n',
 
1242
                           ' c\n',
 
1243
                           '+d\n',
 
1244
                           '+e\n',
 
1245
                           '+f\n',
 
1246
                           '+x\n',
 
1247
                           '+y\n',
 
1248
                           ' d\n',
 
1249
                           ' e\n',
 
1250
                           ' f\n']
 
1251
                          , list(unified_diff_files('a2', 'b2')))
 
1252
 
 
1253
        # And the patience diff
 
1254
        self.assertEquals(['--- a2\n',
 
1255
                           '+++ b2\n',
 
1256
                           '@@ -4,6 +4,11 @@\n',
 
1257
                           ' d\n',
 
1258
                           ' e\n',
 
1259
                           ' f\n',
 
1260
                           '+x\n',
 
1261
                           '+y\n',
 
1262
                           '+d\n',
 
1263
                           '+e\n',
 
1264
                           '+f\n',
 
1265
                           ' g\n',
 
1266
                           ' h\n',
 
1267
                           ' i\n',
 
1268
                          ]
 
1269
                          , list(unified_diff_files('a2', 'b2',
 
1270
                                 sequencematcher=psm)))
 
1271
 
 
1272
 
 
1273
class TestPatienceDiffLibFiles_c(TestPatienceDiffLibFiles):
 
1274
 
 
1275
    _test_needs_features = [compiled_patiencediff_feature]
 
1276
 
 
1277
    def setUp(self):
 
1278
        super(TestPatienceDiffLibFiles_c, self).setUp()
 
1279
        from bzrlib import _patiencediff_c
 
1280
        self._PatienceSequenceMatcher = \
 
1281
            _patiencediff_c.PatienceSequenceMatcher_c
 
1282
 
 
1283
 
 
1284
class TestUsingCompiledIfAvailable(tests.TestCase):
 
1285
 
 
1286
    def test_PatienceSequenceMatcher(self):
 
1287
        if compiled_patiencediff_feature.available():
 
1288
            from bzrlib._patiencediff_c import PatienceSequenceMatcher_c
 
1289
            self.assertIs(PatienceSequenceMatcher_c,
 
1290
                          patiencediff.PatienceSequenceMatcher)
 
1291
        else:
 
1292
            from bzrlib._patiencediff_py import PatienceSequenceMatcher_py
 
1293
            self.assertIs(PatienceSequenceMatcher_py,
 
1294
                          patiencediff.PatienceSequenceMatcher)
 
1295
 
 
1296
    def test_unique_lcs(self):
 
1297
        if compiled_patiencediff_feature.available():
 
1298
            from bzrlib._patiencediff_c import unique_lcs_c
 
1299
            self.assertIs(unique_lcs_c,
 
1300
                          patiencediff.unique_lcs)
 
1301
        else:
 
1302
            from bzrlib._patiencediff_py import unique_lcs_py
 
1303
            self.assertIs(unique_lcs_py,
 
1304
                          patiencediff.unique_lcs)
 
1305
 
 
1306
    def test_recurse_matches(self):
 
1307
        if compiled_patiencediff_feature.available():
 
1308
            from bzrlib._patiencediff_c import recurse_matches_c
 
1309
            self.assertIs(recurse_matches_c,
 
1310
                          patiencediff.recurse_matches)
 
1311
        else:
 
1312
            from bzrlib._patiencediff_py import recurse_matches_py
 
1313
            self.assertIs(recurse_matches_py,
 
1314
                          patiencediff.recurse_matches)
 
1315
 
 
1316
 
 
1317
class TestDiffFromTool(tests.TestCaseWithTransport):
 
1318
 
 
1319
    def test_from_string(self):
 
1320
        diff_obj = diff.DiffFromTool.from_string('diff', None, None, None)
 
1321
        self.addCleanup(diff_obj.finish)
 
1322
        self.assertEqual(['diff', '@old_path', '@new_path'],
 
1323
            diff_obj.command_template)
 
1324
 
 
1325
    def test_from_string_u5(self):
 
1326
        diff_obj = diff.DiffFromTool.from_string('diff "-u 5"',
 
1327
                                                 None, None, None)
 
1328
        self.addCleanup(diff_obj.finish)
 
1329
        self.assertEqual(['diff', '-u 5', '@old_path', '@new_path'],
 
1330
                         diff_obj.command_template)
 
1331
        self.assertEqual(['diff', '-u 5', 'old-path', 'new-path'],
 
1332
                         diff_obj._get_command('old-path', 'new-path'))
 
1333
 
 
1334
    def test_from_string_path_with_backslashes(self):
 
1335
        self.requireFeature(features.backslashdir_feature)
 
1336
        tool = 'C:\\Tools\\Diff.exe'
 
1337
        diff_obj = diff.DiffFromTool.from_string(tool, None, None, None)
 
1338
        self.addCleanup(diff_obj.finish)
 
1339
        self.assertEqual(['C:\\Tools\\Diff.exe', '@old_path', '@new_path'],
 
1340
                         diff_obj.command_template)
 
1341
        self.assertEqual(['C:\\Tools\\Diff.exe', 'old-path', 'new-path'],
 
1342
                         diff_obj._get_command('old-path', 'new-path'))
 
1343
 
 
1344
    def test_execute(self):
 
1345
        output = StringIO()
 
1346
        diff_obj = diff.DiffFromTool(['python', '-c',
 
1347
                                      'print "@old_path @new_path"'],
 
1348
                                     None, None, output)
 
1349
        self.addCleanup(diff_obj.finish)
 
1350
        diff_obj._execute('old', 'new')
 
1351
        self.assertEqual(output.getvalue().rstrip(), 'old new')
 
1352
 
 
1353
    def test_excute_missing(self):
 
1354
        diff_obj = diff.DiffFromTool(['a-tool-which-is-unlikely-to-exist'],
 
1355
                                     None, None, None)
 
1356
        self.addCleanup(diff_obj.finish)
 
1357
        e = self.assertRaises(errors.ExecutableMissing, diff_obj._execute,
 
1358
                              'old', 'new')
 
1359
        self.assertEqual('a-tool-which-is-unlikely-to-exist could not be found'
 
1360
                         ' on this machine', str(e))
 
1361
 
 
1362
    def test_prepare_files_creates_paths_readable_by_windows_tool(self):
 
1363
        self.requireFeature(AttribFeature)
 
1364
        output = StringIO()
 
1365
        tree = self.make_branch_and_tree('tree')
 
1366
        self.build_tree_contents([('tree/file', 'content')])
 
1367
        tree.add('file', 'file-id')
 
1368
        tree.commit('old tree')
 
1369
        tree.lock_read()
 
1370
        self.addCleanup(tree.unlock)
 
1371
        basis_tree = tree.basis_tree()
 
1372
        basis_tree.lock_read()
 
1373
        self.addCleanup(basis_tree.unlock)
 
1374
        diff_obj = diff.DiffFromTool(['python', '-c',
 
1375
                                      'print "@old_path @new_path"'],
 
1376
                                     basis_tree, tree, output)
 
1377
        diff_obj._prepare_files('file-id', 'file', 'file')
 
1378
        # The old content should be readonly
 
1379
        self.assertReadableByAttrib(diff_obj._root, 'old\\file',
 
1380
                                    r'R.*old\\file$')
 
1381
        # The new content should use the tree object, not a 'new' file anymore
 
1382
        self.assertEndsWith(tree.basedir, 'work/tree')
 
1383
        self.assertReadableByAttrib(tree.basedir, 'file', r'work\\tree\\file$')
 
1384
 
 
1385
    def assertReadableByAttrib(self, cwd, relpath, regex):
 
1386
        proc = subprocess.Popen(['attrib', relpath],
 
1387
                                stdout=subprocess.PIPE,
 
1388
                                cwd=cwd)
 
1389
        (result, err) = proc.communicate()
 
1390
        self.assertContainsRe(result.replace('\r\n', '\n'), regex)
 
1391
 
 
1392
    def test_prepare_files(self):
 
1393
        output = StringIO()
 
1394
        tree = self.make_branch_and_tree('tree')
 
1395
        self.build_tree_contents([('tree/oldname', 'oldcontent')])
 
1396
        self.build_tree_contents([('tree/oldname2', 'oldcontent2')])
 
1397
        tree.add('oldname', 'file-id')
 
1398
        tree.add('oldname2', 'file2-id')
 
1399
        # Earliest allowable date on FAT32 filesystems is 1980-01-01
 
1400
        tree.commit('old tree', timestamp=315532800)
 
1401
        tree.rename_one('oldname', 'newname')
 
1402
        tree.rename_one('oldname2', 'newname2')
 
1403
        self.build_tree_contents([('tree/newname', 'newcontent')])
 
1404
        self.build_tree_contents([('tree/newname2', 'newcontent2')])
 
1405
        old_tree = tree.basis_tree()
 
1406
        old_tree.lock_read()
 
1407
        self.addCleanup(old_tree.unlock)
 
1408
        tree.lock_read()
 
1409
        self.addCleanup(tree.unlock)
 
1410
        diff_obj = diff.DiffFromTool(['python', '-c',
 
1411
                                      'print "@old_path @new_path"'],
 
1412
                                     old_tree, tree, output)
 
1413
        self.addCleanup(diff_obj.finish)
 
1414
        self.assertContainsRe(diff_obj._root, 'bzr-diff-[^/]*')
 
1415
        old_path, new_path = diff_obj._prepare_files('file-id', 'oldname',
 
1416
                                                     'newname')
 
1417
        self.assertContainsRe(old_path, 'old/oldname$')
 
1418
        self.assertEqual(315532800, os.stat(old_path).st_mtime)
 
1419
        self.assertContainsRe(new_path, 'tree/newname$')
 
1420
        self.assertFileEqual('oldcontent', old_path)
 
1421
        self.assertFileEqual('newcontent', new_path)
 
1422
        if osutils.host_os_dereferences_symlinks():
 
1423
            self.assertTrue(os.path.samefile('tree/newname', new_path))
 
1424
        # make sure we can create files with the same parent directories
 
1425
        diff_obj._prepare_files('file2-id', 'oldname2', 'newname2')
 
1426
 
 
1427
 
 
1428
class TestGetTreesAndBranchesToDiffLocked(tests.TestCaseWithTransport):
 
1429
 
 
1430
    def call_gtabtd(self, path_list, revision_specs, old_url, new_url):
 
1431
        """Call get_trees_and_branches_to_diff_locked.  Overridden by
 
1432
        TestGetTreesAndBranchesToDiff.
 
1433
        """
 
1434
        return diff.get_trees_and_branches_to_diff_locked(
 
1435
            path_list, revision_specs, old_url, new_url, self.addCleanup)
 
1436
 
 
1437
    def test_basic(self):
 
1438
        tree = self.make_branch_and_tree('tree')
 
1439
        (old_tree, new_tree,
 
1440
         old_branch, new_branch,
 
1441
         specific_files, extra_trees) = self.call_gtabtd(
 
1442
             ['tree'], None, None, None)
 
1443
 
 
1444
        self.assertIsInstance(old_tree, revisiontree.RevisionTree)
 
1445
        self.assertEqual(_mod_revision.NULL_REVISION,
 
1446
                         old_tree.get_revision_id())
 
1447
        self.assertEqual(tree.basedir, new_tree.basedir)
 
1448
        self.assertEqual(tree.branch.base, old_branch.base)
 
1449
        self.assertEqual(tree.branch.base, new_branch.base)
 
1450
        self.assertIs(None, specific_files)
 
1451
        self.assertIs(None, extra_trees)
 
1452
 
 
1453
    def test_with_rev_specs(self):
 
1454
        tree = self.make_branch_and_tree('tree')
 
1455
        self.build_tree_contents([('tree/file', 'oldcontent')])
 
1456
        tree.add('file', 'file-id')
 
1457
        tree.commit('old tree', timestamp=0, rev_id="old-id")
 
1458
        self.build_tree_contents([('tree/file', 'newcontent')])
 
1459
        tree.commit('new tree', timestamp=0, rev_id="new-id")
 
1460
 
 
1461
        revisions = [revisionspec.RevisionSpec.from_string('1'),
 
1462
                     revisionspec.RevisionSpec.from_string('2')]
 
1463
        (old_tree, new_tree,
 
1464
         old_branch, new_branch,
 
1465
         specific_files, extra_trees) = self.call_gtabtd(
 
1466
            ['tree'], revisions, None, None)
 
1467
 
 
1468
        self.assertIsInstance(old_tree, revisiontree.RevisionTree)
 
1469
        self.assertEqual("old-id", old_tree.get_revision_id())
 
1470
        self.assertIsInstance(new_tree, revisiontree.RevisionTree)
 
1471
        self.assertEqual("new-id", new_tree.get_revision_id())
 
1472
        self.assertEqual(tree.branch.base, old_branch.base)
 
1473
        self.assertEqual(tree.branch.base, new_branch.base)
 
1474
        self.assertIs(None, specific_files)
 
1475
        self.assertEqual(tree.basedir, extra_trees[0].basedir)
 
1476
 
 
1477
 
 
1478
class TestGetTreesAndBranchesToDiff(TestGetTreesAndBranchesToDiffLocked):
 
1479
    """Apply the tests for get_trees_and_branches_to_diff_locked to the
 
1480
    deprecated get_trees_and_branches_to_diff function.
 
1481
    """
 
1482
 
 
1483
    def call_gtabtd(self, path_list, revision_specs, old_url, new_url):
 
1484
        return self.applyDeprecated(
 
1485
            deprecated_in((2, 2, 0)), diff.get_trees_and_branches_to_diff,
 
1486
            path_list, revision_specs, old_url, new_url)
 
1487