~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_diff.py

  • Committer: Vincent Ladeuil
  • Date: 2010-02-11 09:27:55 UTC
  • mfrom: (5017.3.46 test-servers)
  • mto: This revision was merged to the branch mainline in revision 5030.
  • Revision ID: v.ladeuil+lp@free.fr-20100211092755-3vvu4vbwiwjjte3s
Move tests servers from bzrlib.transport to bzrlib.tests.test_server

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