~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_diff.py

  • Committer: Patch Queue Manager
  • Date: 2014-09-22 19:42:30 UTC
  • mfrom: (6597.2.1 bzr)
  • Revision ID: pqm@pqm.ubuntu.com-20140922194230-y32j0sq621bxhp7c
(richard-wilbur) Split diff format option parser into a separate function,
 update to include all format options for GNU diff v3.2,
 and test parser.  Fixes lp:1370435 (Richard Wilbur)

Show diffs side-by-side

added added

removed removed

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