/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_diff.py

  • Committer: Robert Collins
  • Date: 2010-05-06 11:08:10 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506110810-h3j07fh5gmw54s25
Cleaner matcher matching revised unlocking protocol.

Show diffs side-by-side

added added

removed removed

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