/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

Finish the pycurl feature.

Show diffs side-by-side

added added

removed removed

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