79
    """Simple file-like object that allows writes with any type and records."""
 
 
82
        self.write_record = []
 
 
84
    def write(self, data):
 
 
85
        self.write_record.append(data)
 
 
87
    def check_types(self, testcase, expected_type):
 
 
89
            any(not isinstance(o, expected_type) for o in self.write_record),
 
 
90
            "Not all writes of type %s: %r" % (
 
 
91
                expected_type.__name__, self.write_record))
 
 
94
class TestDiffOptions(tests.TestCase):
 
 
96
    def test_unified_added(self):
 
 
97
        """Check for default style '-u' only if no other style specified
 
 
100
        # Verify that style defaults to unified, id est '-u' appended
 
 
101
        # to option list, in the absence of an alternative style.
 
 
102
        self.assertEqual(['-a', '-u'], diff.default_style_unified(['-a']))
 
 
105
class TestDiffOptionsScenarios(tests.TestCase):
 
 
107
    scenarios = [(s, dict(style=s)) for s in diff.style_option_list]
 
 
108
    style = None  # Set by load_tests_apply_scenarios from scenarios
 
 
110
    def test_unified_not_added(self):
 
 
111
        # Verify that for all valid style options, '-u' is not
 
 
112
        # appended to option list.
 
 
113
        ret_opts = diff.default_style_unified(diff_opts=["%s" % (self.style,)])
 
 
114
        self.assertEqual(["%s" % (self.style,)], ret_opts)
 
83
117
class TestDiff(tests.TestCase):
 
85
119
    def test_add_nl(self):
 
86
120
        """diff generates a valid diff for patches that add a newline"""
 
87
 
        lines = udiff_lines(['boo'], ['boo\n'])
 
 
121
        lines = udiff_lines([b'boo'], [b'boo\n'])
 
88
122
        self.check_patch(lines)
 
89
 
        self.assertEquals(lines[4], '\\ No newline at end of file\n')
 
90
 
            ## "expected no-nl, got %r" % lines[4]
 
 
123
        self.assertEqual(lines[4], b'\\ No newline at end of file\n')
 
 
124
        ## "expected no-nl, got %r" % lines[4]
 
92
126
    def test_add_nl_2(self):
 
93
127
        """diff generates a valid diff for patches that change last line and
 
96
 
        lines = udiff_lines(['boo'], ['goo\n'])
 
 
130
        lines = udiff_lines([b'boo'], [b'goo\n'])
 
97
131
        self.check_patch(lines)
 
98
 
        self.assertEquals(lines[4], '\\ No newline at end of file\n')
 
99
 
            ## "expected no-nl, got %r" % lines[4]
 
 
132
        self.assertEqual(lines[4], b'\\ No newline at end of file\n')
 
 
133
        ## "expected no-nl, got %r" % lines[4]
 
101
135
    def test_remove_nl(self):
 
102
136
        """diff generates a valid diff for patches that change last line and
 
105
 
        lines = udiff_lines(['boo\n'], ['boo'])
 
 
139
        lines = udiff_lines([b'boo\n'], [b'boo'])
 
106
140
        self.check_patch(lines)
 
107
 
        self.assertEquals(lines[5], '\\ No newline at end of file\n')
 
108
 
            ## "expected no-nl, got %r" % lines[5]
 
 
141
        self.assertEqual(lines[5], b'\\ No newline at end of file\n')
 
 
142
        ## "expected no-nl, got %r" % lines[5]
 
110
144
    def check_patch(self, lines):
 
111
 
        self.assert_(len(lines) > 1)
 
112
 
            ## "Not enough lines for a file header for patch:\n%s" % "".join(lines)
 
113
 
        self.assert_(lines[0].startswith ('---'))
 
114
 
            ## 'No orig line for patch:\n%s' % "".join(lines)
 
115
 
        self.assert_(lines[1].startswith ('+++'))
 
116
 
            ## 'No mod line for patch:\n%s' % "".join(lines)
 
117
 
        self.assert_(len(lines) > 2)
 
118
 
            ## "No hunks for patch:\n%s" % "".join(lines)
 
119
 
        self.assert_(lines[2].startswith('@@'))
 
120
 
            ## "No hunk header for patch:\n%s" % "".join(lines)
 
121
 
        self.assert_('@@' in lines[2][2:])
 
122
 
            ## "Unterminated hunk header for patch:\n%s" % "".join(lines)
 
 
145
        self.assertTrue(len(lines) > 1)
 
 
146
        ## "Not enough lines for a file header for patch:\n%s" % "".join(lines)
 
 
147
        self.assertTrue(lines[0].startswith(b'---'))
 
 
148
        ## 'No orig line for patch:\n%s' % "".join(lines)
 
 
149
        self.assertTrue(lines[1].startswith(b'+++'))
 
 
150
        ## 'No mod line for patch:\n%s' % "".join(lines)
 
 
151
        self.assertTrue(len(lines) > 2)
 
 
152
        ## "No hunks for patch:\n%s" % "".join(lines)
 
 
153
        self.assertTrue(lines[2].startswith(b'@@'))
 
 
154
        ## "No hunk header for patch:\n%s" % "".join(lines)
 
 
155
        self.assertTrue(b'@@' in lines[2][2:])
 
 
156
        ## "Unterminated hunk header for patch:\n%s" % "".join(lines)
 
124
158
    def test_binary_lines(self):
 
126
 
        uni_lines = [1023 * 'a' + '\x00']
 
127
 
        self.assertRaises(errors.BinaryFile, udiff_lines, uni_lines , empty)
 
 
160
        uni_lines = [1023 * b'a' + b'\x00']
 
 
161
        self.assertRaises(errors.BinaryFile, udiff_lines, uni_lines, empty)
 
128
162
        self.assertRaises(errors.BinaryFile, udiff_lines, empty, uni_lines)
 
129
 
        udiff_lines(uni_lines , empty, allow_binary=True)
 
 
163
        udiff_lines(uni_lines, empty, allow_binary=True)
 
130
164
        udiff_lines(empty, uni_lines, allow_binary=True)
 
132
166
    def test_external_diff(self):
 
133
 
        lines = external_udiff_lines(['boo\n'], ['goo\n'])
 
 
167
        lines = external_udiff_lines([b'boo\n'], [b'goo\n'])
 
134
168
        self.check_patch(lines)
 
135
 
        self.assertEqual('\n', lines[-1])
 
 
169
        self.assertEqual(b'\n', lines[-1])
 
137
171
    def test_external_diff_no_fileno(self):
 
138
172
        # Make sure that we can handle not having a fileno, even
 
139
173
        # if the diff is large
 
140
 
        lines = external_udiff_lines(['boo\n']*10000,
 
 
174
        lines = external_udiff_lines([b'boo\n'] * 10000,
 
142
176
                                     use_stringio=True)
 
143
177
        self.check_patch(lines)
 
145
179
    def test_external_diff_binary_lang_c(self):
 
147
180
        for lang in ('LANG', 'LC_ALL', 'LANGUAGE'):
 
148
 
            old_env[lang] = osutils.set_or_unset_env(lang, 'C')
 
150
 
            lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
 
151
 
            # Older versions of diffutils say "Binary files", newer
 
152
 
            # versions just say "Files".
 
153
 
            self.assertContainsRe(lines[0],
 
154
 
                                  '(Binary f|F)iles old and new differ\n')
 
155
 
            self.assertEquals(lines[1:], ['\n'])
 
157
 
            for lang, old_val in old_env.iteritems():
 
158
 
                osutils.set_or_unset_env(lang, old_val)
 
 
181
            self.overrideEnv(lang, 'C')
 
 
182
        lines = external_udiff_lines([b'\x00foobar\n'], [b'foo\x00bar\n'])
 
 
183
        # Older versions of diffutils say "Binary files", newer
 
 
184
        # versions just say "Files".
 
 
185
        self.assertContainsRe(
 
 
186
            lines[0], b'(Binary f|F)iles old and new differ\n')
 
 
187
        self.assertEqual(lines[1:], [b'\n'])
 
160
189
    def test_no_external_diff(self):
 
161
190
        """Check that NoDiff is raised when diff is not available"""
 
162
 
        # Use os.environ['PATH'] to make sure no 'diff' command is available
 
163
 
        orig_path = os.environ['PATH']
 
165
 
            os.environ['PATH'] = ''
 
166
 
            self.assertRaises(errors.NoDiff, diff.external_diff,
 
167
 
                              'old', ['boo\n'], 'new', ['goo\n'],
 
168
 
                              StringIO(), diff_opts=['-u'])
 
170
 
            os.environ['PATH'] = orig_path
 
 
191
        # Make sure no 'diff' command is available
 
 
192
        # XXX: Weird, using None instead of '' breaks the test -- vila 20101216
 
 
193
        self.overrideEnv('PATH', '')
 
 
194
        self.assertRaises(errors.NoDiff, diff.external_diff,
 
 
195
                          b'old', [b'boo\n'], b'new', [b'goo\n'],
 
 
196
                          BytesIO(), diff_opts=['-u'])
 
172
198
    def test_internal_diff_default(self):
 
173
199
        # Default internal diff encoding is utf8
 
175
 
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
 
176
 
                           u'new_\xe5', ['new_text\n'], output)
 
 
201
        diff.internal_diff(u'old_\xb5', [b'old_text\n'],
 
 
202
                           u'new_\xe5', [b'new_text\n'], output)
 
177
203
        lines = output.getvalue().splitlines(True)
 
178
204
        self.check_patch(lines)
 
179
 
        self.assertEquals(['--- old_\xc2\xb5\n',
 
180
 
                           '+++ new_\xc3\xa5\n',
 
 
205
        self.assertEqual([b'--- old_\xc2\xb5\n',
 
 
206
                          b'+++ new_\xc3\xa5\n',
 
 
207
                          b'@@ -1,1 +1,1 @@\n',
 
188
213
    def test_internal_diff_utf8(self):
 
190
 
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
 
191
 
                           u'new_\xe5', ['new_text\n'], output,
 
 
215
        diff.internal_diff(u'old_\xb5', [b'old_text\n'],
 
 
216
                           u'new_\xe5', [b'new_text\n'], output,
 
192
217
                           path_encoding='utf8')
 
193
218
        lines = output.getvalue().splitlines(True)
 
194
219
        self.check_patch(lines)
 
195
 
        self.assertEquals(['--- old_\xc2\xb5\n',
 
196
 
                           '+++ new_\xc3\xa5\n',
 
 
220
        self.assertEqual([b'--- old_\xc2\xb5\n',
 
 
221
                          b'+++ new_\xc3\xa5\n',
 
 
222
                          b'@@ -1,1 +1,1 @@\n',
 
204
228
    def test_internal_diff_iso_8859_1(self):
 
206
 
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
 
207
 
                           u'new_\xe5', ['new_text\n'], output,
 
 
230
        diff.internal_diff(u'old_\xb5', [b'old_text\n'],
 
 
231
                           u'new_\xe5', [b'new_text\n'], output,
 
208
232
                           path_encoding='iso-8859-1')
 
209
233
        lines = output.getvalue().splitlines(True)
 
210
234
        self.check_patch(lines)
 
211
 
        self.assertEquals(['--- old_\xb5\n',
 
 
235
        self.assertEqual([b'--- old_\xb5\n',
 
 
237
                          b'@@ -1,1 +1,1 @@\n',
 
220
243
    def test_internal_diff_no_content(self):
 
222
245
        diff.internal_diff(u'old', [], u'new', [], output)
 
223
 
        self.assertEqual('', output.getvalue())
 
 
246
        self.assertEqual(b'', output.getvalue())
 
225
248
    def test_internal_diff_no_changes(self):
 
227
 
        diff.internal_diff(u'old', ['text\n', 'contents\n'],
 
228
 
                           u'new', ['text\n', 'contents\n'],
 
 
250
        diff.internal_diff(u'old', [b'text\n', b'contents\n'],
 
 
251
                           u'new', [b'text\n', b'contents\n'],
 
230
 
        self.assertEqual('', output.getvalue())
 
 
253
        self.assertEqual(b'', output.getvalue())
 
232
255
    def test_internal_diff_returns_bytes(self):
 
234
 
        output = StringIO.StringIO()
 
235
 
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
 
236
 
                            u'new_\xe5', ['new_text\n'], output)
 
237
 
        self.failUnless(isinstance(output.getvalue(), str),
 
238
 
            'internal_diff should return bytestrings')
 
 
257
        diff.internal_diff(u'old_\xb5', [b'old_text\n'],
 
 
258
                           u'new_\xe5', [b'new_text\n'], output)
 
 
259
        output.check_types(self, bytes)
 
 
261
    def test_internal_diff_default_context(self):
 
 
263
        diff.internal_diff('old', [b'same_text\n', b'same_text\n', b'same_text\n',
 
 
264
                                   b'same_text\n', b'same_text\n', b'old_text\n'],
 
 
265
                           'new', [b'same_text\n', b'same_text\n', b'same_text\n',
 
 
266
                                   b'same_text\n', b'same_text\n', b'new_text\n'], output)
 
 
267
        lines = output.getvalue().splitlines(True)
 
 
268
        self.check_patch(lines)
 
 
269
        self.assertEqual([b'--- old\n',
 
 
271
                          b'@@ -3,4 +3,4 @@\n',
 
 
280
    def test_internal_diff_no_context(self):
 
 
282
        diff.internal_diff('old', [b'same_text\n', b'same_text\n', b'same_text\n',
 
 
283
                                   b'same_text\n', b'same_text\n', b'old_text\n'],
 
 
284
                           'new', [b'same_text\n', b'same_text\n', b'same_text\n',
 
 
285
                                   b'same_text\n', b'same_text\n', b'new_text\n'], output,
 
 
287
        lines = output.getvalue().splitlines(True)
 
 
288
        self.check_patch(lines)
 
 
289
        self.assertEqual([b'--- old\n',
 
 
291
                          b'@@ -6,1 +6,1 @@\n',
 
 
297
    def test_internal_diff_more_context(self):
 
 
299
        diff.internal_diff('old', [b'same_text\n', b'same_text\n', b'same_text\n',
 
 
300
                                   b'same_text\n', b'same_text\n', b'old_text\n'],
 
 
301
                           'new', [b'same_text\n', b'same_text\n', b'same_text\n',
 
 
302
                                   b'same_text\n', b'same_text\n', b'new_text\n'], output,
 
 
304
        lines = output.getvalue().splitlines(True)
 
 
305
        self.check_patch(lines)
 
 
306
        self.assertEqual([b'--- old\n',
 
 
308
                          b'@@ -2,5 +2,5 @@\n',
 
241
319
class TestDiffFiles(tests.TestCaseInTempDir):
 
243
321
    def test_external_diff_binary(self):
 
244
322
        """The output when using external diff should use diff's i18n error"""
 
 
323
        for lang in ('LANG', 'LC_ALL', 'LANGUAGE'):
 
 
324
            self.overrideEnv(lang, 'C')
 
245
325
        # Make sure external_diff doesn't fail in the current LANG
 
246
 
        lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
 
 
326
        lines = external_udiff_lines([b'\x00foobar\n'], [b'foo\x00bar\n'])
 
248
328
        cmd = ['diff', '-u', '--binary', 'old', 'new']
 
249
 
        open('old', 'wb').write('\x00foobar\n')
 
250
 
        open('new', 'wb').write('foo\x00bar\n')
 
 
329
        with open('old', 'wb') as f:
 
 
330
            f.write(b'\x00foobar\n')
 
 
331
        with open('new', 'wb') as f:
 
 
332
            f.write(b'foo\x00bar\n')
 
251
333
        pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
 
252
 
                                     stdin=subprocess.PIPE)
 
 
334
                                stdin=subprocess.PIPE)
 
253
335
        out, err = pipe.communicate()
 
254
 
        # Diff returns '2' on Binary files.
 
255
 
        self.assertEqual(2, pipe.returncode)
 
256
336
        # We should output whatever diff tells us, plus a trailing newline
 
257
 
        self.assertEqual(out.splitlines(True) + ['\n'], lines)
 
260
 
class TestShowDiffTreesHelper(tests.TestCaseWithTransport):
 
261
 
    """Has a helper for running show_diff_trees"""
 
263
 
    def get_diff(self, tree1, tree2, specific_files=None, working_tree=None):
 
265
 
        if working_tree is not None:
 
266
 
            extra_trees = (working_tree,)
 
269
 
        diff.show_diff_trees(tree1, tree2, output,
 
270
 
                             specific_files=specific_files,
 
271
 
                             extra_trees=extra_trees, old_label='old/',
 
273
 
        return output.getvalue()
 
276
 
class TestDiffDates(TestShowDiffTreesHelper):
 
 
337
        self.assertEqual(out.splitlines(True) + [b'\n'], lines)
 
 
340
def get_diff_as_string(tree1, tree2, specific_files=None, working_tree=None):
 
 
342
    if working_tree is not None:
 
 
343
        extra_trees = (working_tree,)
 
 
346
    diff.show_diff_trees(tree1, tree2, output,
 
 
347
                         specific_files=specific_files,
 
 
348
                         extra_trees=extra_trees, old_label='old/',
 
 
350
    return output.getvalue()
 
 
353
class TestDiffDates(tests.TestCaseWithTransport):
 
279
356
        super(TestDiffDates, self).setUp()
 
280
357
        self.wt = self.make_branch_and_tree('.')
 
281
358
        self.b = self.wt.branch
 
282
359
        self.build_tree_contents([
 
283
 
            ('file1', 'file1 contents at rev 1\n'),
 
284
 
            ('file2', 'file2 contents at rev 1\n')
 
 
360
            ('file1', b'file1 contents at rev 1\n'),
 
 
361
            ('file2', b'file2 contents at rev 1\n')
 
286
363
        self.wt.add(['file1', 'file2'])
 
288
365
            message='Revision 1',
 
289
 
            timestamp=1143849600, # 2006-04-01 00:00:00 UTC
 
 
366
            timestamp=1143849600,  # 2006-04-01 00:00:00 UTC
 
292
 
        self.build_tree_contents([('file1', 'file1 contents at rev 2\n')])
 
 
369
        self.build_tree_contents([('file1', b'file1 contents at rev 2\n')])
 
294
371
            message='Revision 2',
 
295
 
            timestamp=1143936000, # 2006-04-02 00:00:00 UTC
 
 
372
            timestamp=1143936000,  # 2006-04-02 00:00:00 UTC
 
298
 
        self.build_tree_contents([('file2', 'file2 contents at rev 3\n')])
 
 
375
        self.build_tree_contents([('file2', b'file2 contents at rev 3\n')])
 
300
377
            message='Revision 3',
 
301
 
            timestamp=1144022400, # 2006-04-03 00:00:00 UTC
 
 
378
            timestamp=1144022400,  # 2006-04-03 00:00:00 UTC
 
304
381
        self.wt.remove(['file2'])
 
306
383
            message='Revision 4',
 
307
 
            timestamp=1144108800, # 2006-04-04 00:00:00 UTC
 
 
384
            timestamp=1144108800,  # 2006-04-04 00:00:00 UTC
 
310
387
        self.build_tree_contents([
 
311
 
            ('file1', 'file1 contents in working tree\n')
 
 
388
            ('file1', b'file1 contents in working tree\n')
 
313
390
        # set the date stamps for files in the working tree to known values
 
314
 
        os.utime('file1', (1144195200, 1144195200)) # 2006-04-05 00:00:00 UTC
 
 
391
        os.utime('file1', (1144195200, 1144195200))  # 2006-04-05 00:00:00 UTC
 
316
393
    def test_diff_rev_tree_working_tree(self):
 
317
 
        output = self.get_diff(self.wt.basis_tree(), self.wt)
 
 
394
        output = get_diff_as_string(self.wt.basis_tree(), self.wt)
 
318
395
        # note that the date for old/file1 is from rev 2 rather than from
 
319
396
        # the basis revision (rev 4)
 
320
 
        self.assertEqualDiff(output, '''\
 
 
397
        self.assertEqualDiff(output, b'''\
 
321
398
=== modified file 'file1'
 
322
399
--- old/file1\t2006-04-02 00:00:00 +0000
 
323
400
+++ new/file1\t2006-04-05 00:00:00 +0000
 
 
393
470
        self.wt.add(['dir1', 'dir2'])
 
394
471
        self.wt.rename_one('file1', 'dir1/file1')
 
395
 
        old_tree = self.b.repository.revision_tree('rev-1')
 
396
 
        new_tree = self.b.repository.revision_tree('rev-4')
 
397
 
        out = self.get_diff(old_tree, new_tree, specific_files=['dir1'],
 
398
 
                            working_tree=self.wt)
 
399
 
        self.assertContainsRe(out, 'file1\t')
 
400
 
        out = self.get_diff(old_tree, new_tree, specific_files=['dir2'],
 
401
 
                            working_tree=self.wt)
 
402
 
        self.assertNotContainsRe(out, 'file1\t')
 
406
 
class TestShowDiffTrees(TestShowDiffTreesHelper):
 
 
472
        old_tree = self.b.repository.revision_tree(b'rev-1')
 
 
473
        new_tree = self.b.repository.revision_tree(b'rev-4')
 
 
474
        out = get_diff_as_string(old_tree, new_tree, specific_files=['dir1'],
 
 
475
                                 working_tree=self.wt)
 
 
476
        self.assertContainsRe(out, b'file1\t')
 
 
477
        out = get_diff_as_string(old_tree, new_tree, specific_files=['dir2'],
 
 
478
                                 working_tree=self.wt)
 
 
479
        self.assertNotContainsRe(out, b'file1\t')
 
 
482
class TestShowDiffTrees(tests.TestCaseWithTransport):
 
407
483
    """Direct tests for show_diff_trees"""
 
409
485
    def test_modified_file(self):
 
410
486
        """Test when a file is modified."""
 
411
487
        tree = self.make_branch_and_tree('tree')
 
412
 
        self.build_tree_contents([('tree/file', 'contents\n')])
 
413
 
        tree.add(['file'], ['file-id'])
 
414
 
        tree.commit('one', rev_id='rev-1')
 
 
488
        self.build_tree_contents([('tree/file', b'contents\n')])
 
 
489
        tree.add(['file'], [b'file-id'])
 
 
490
        tree.commit('one', rev_id=b'rev-1')
 
416
 
        self.build_tree_contents([('tree/file', 'new contents\n')])
 
417
 
        d = self.get_diff(tree.basis_tree(), tree)
 
418
 
        self.assertContainsRe(d, "=== modified file 'file'\n")
 
419
 
        self.assertContainsRe(d, '--- old/file\t')
 
420
 
        self.assertContainsRe(d, '\\+\\+\\+ new/file\t')
 
421
 
        self.assertContainsRe(d, '-contents\n'
 
 
492
        self.build_tree_contents([('tree/file', b'new contents\n')])
 
 
493
        d = get_diff_as_string(tree.basis_tree(), tree)
 
 
494
        self.assertContainsRe(d, b"=== modified file 'file'\n")
 
 
495
        self.assertContainsRe(d, b'--- old/file\t')
 
 
496
        self.assertContainsRe(d, b'\\+\\+\\+ new/file\t')
 
 
497
        self.assertContainsRe(d, b'-contents\n'
 
 
498
                                 b'\\+new contents\n')
 
424
500
    def test_modified_file_in_renamed_dir(self):
 
425
501
        """Test when a file is modified in a renamed directory."""
 
426
502
        tree = self.make_branch_and_tree('tree')
 
427
503
        self.build_tree(['tree/dir/'])
 
428
 
        self.build_tree_contents([('tree/dir/file', 'contents\n')])
 
429
 
        tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
 
430
 
        tree.commit('one', rev_id='rev-1')
 
 
504
        self.build_tree_contents([('tree/dir/file', b'contents\n')])
 
 
505
        tree.add(['dir', 'dir/file'], [b'dir-id', b'file-id'])
 
 
506
        tree.commit('one', rev_id=b'rev-1')
 
432
508
        tree.rename_one('dir', 'other')
 
433
 
        self.build_tree_contents([('tree/other/file', 'new contents\n')])
 
434
 
        d = self.get_diff(tree.basis_tree(), tree)
 
435
 
        self.assertContainsRe(d, "=== renamed directory 'dir' => 'other'\n")
 
436
 
        self.assertContainsRe(d, "=== modified file 'other/file'\n")
 
 
509
        self.build_tree_contents([('tree/other/file', b'new contents\n')])
 
 
510
        d = get_diff_as_string(tree.basis_tree(), tree)
 
 
511
        self.assertContainsRe(d, b"=== renamed directory 'dir' => 'other'\n")
 
 
512
        self.assertContainsRe(d, b"=== modified file 'other/file'\n")
 
437
513
        # XXX: This is technically incorrect, because it used to be at another
 
438
514
        # location. What to do?
 
439
 
        self.assertContainsRe(d, '--- old/dir/file\t')
 
440
 
        self.assertContainsRe(d, '\\+\\+\\+ new/other/file\t')
 
441
 
        self.assertContainsRe(d, '-contents\n'
 
 
515
        self.assertContainsRe(d, b'--- old/dir/file\t')
 
 
516
        self.assertContainsRe(d, b'\\+\\+\\+ new/other/file\t')
 
 
517
        self.assertContainsRe(d, b'-contents\n'
 
 
518
                                 b'\\+new contents\n')
 
444
520
    def test_renamed_directory(self):
 
445
521
        """Test when only a directory is only renamed."""
 
446
522
        tree = self.make_branch_and_tree('tree')
 
447
523
        self.build_tree(['tree/dir/'])
 
448
 
        self.build_tree_contents([('tree/dir/file', 'contents\n')])
 
449
 
        tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
 
450
 
        tree.commit('one', rev_id='rev-1')
 
 
524
        self.build_tree_contents([('tree/dir/file', b'contents\n')])
 
 
525
        tree.add(['dir', 'dir/file'], [b'dir-id', b'file-id'])
 
 
526
        tree.commit('one', rev_id=b'rev-1')
 
452
528
        tree.rename_one('dir', 'newdir')
 
453
 
        d = self.get_diff(tree.basis_tree(), tree)
 
 
529
        d = get_diff_as_string(tree.basis_tree(), tree)
 
454
530
        # Renaming a directory should be a single "you renamed this dir" even
 
455
531
        # when there are files inside.
 
456
 
        self.assertEqual(d, "=== renamed directory 'dir' => 'newdir'\n")
 
 
532
        self.assertEqual(d, b"=== renamed directory 'dir' => 'newdir'\n")
 
458
534
    def test_renamed_file(self):
 
459
535
        """Test when a file is only renamed."""
 
460
536
        tree = self.make_branch_and_tree('tree')
 
461
 
        self.build_tree_contents([('tree/file', 'contents\n')])
 
462
 
        tree.add(['file'], ['file-id'])
 
463
 
        tree.commit('one', rev_id='rev-1')
 
 
537
        self.build_tree_contents([('tree/file', b'contents\n')])
 
 
538
        tree.add(['file'], [b'file-id'])
 
 
539
        tree.commit('one', rev_id=b'rev-1')
 
465
541
        tree.rename_one('file', 'newname')
 
466
 
        d = self.get_diff(tree.basis_tree(), tree)
 
467
 
        self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
 
 
542
        d = get_diff_as_string(tree.basis_tree(), tree)
 
 
543
        self.assertContainsRe(d, b"=== renamed file 'file' => 'newname'\n")
 
468
544
        # We shouldn't have a --- or +++ line, because there is no content
 
470
 
        self.assertNotContainsRe(d, '---')
 
 
546
        self.assertNotContainsRe(d, b'---')
 
472
548
    def test_renamed_and_modified_file(self):
 
473
549
        """Test when a file is only renamed."""
 
474
550
        tree = self.make_branch_and_tree('tree')
 
475
 
        self.build_tree_contents([('tree/file', 'contents\n')])
 
476
 
        tree.add(['file'], ['file-id'])
 
477
 
        tree.commit('one', rev_id='rev-1')
 
 
551
        self.build_tree_contents([('tree/file', b'contents\n')])
 
 
552
        tree.add(['file'], [b'file-id'])
 
 
553
        tree.commit('one', rev_id=b'rev-1')
 
479
555
        tree.rename_one('file', 'newname')
 
480
 
        self.build_tree_contents([('tree/newname', 'new contents\n')])
 
481
 
        d = self.get_diff(tree.basis_tree(), tree)
 
482
 
        self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
 
483
 
        self.assertContainsRe(d, '--- old/file\t')
 
484
 
        self.assertContainsRe(d, '\\+\\+\\+ new/newname\t')
 
485
 
        self.assertContainsRe(d, '-contents\n'
 
 
556
        self.build_tree_contents([('tree/newname', b'new contents\n')])
 
 
557
        d = get_diff_as_string(tree.basis_tree(), tree)
 
 
558
        self.assertContainsRe(d, b"=== renamed file 'file' => 'newname'\n")
 
 
559
        self.assertContainsRe(d, b'--- old/file\t')
 
 
560
        self.assertContainsRe(d, b'\\+\\+\\+ new/newname\t')
 
 
561
        self.assertContainsRe(d, b'-contents\n'
 
 
562
                                 b'\\+new contents\n')
 
489
564
    def test_internal_diff_exec_property(self):
 
490
565
        tree = self.make_branch_and_tree('tree')
 
492
567
        tt = transform.TreeTransform(tree)
 
493
 
        tt.new_file('a', tt.root, 'contents\n', 'a-id', True)
 
494
 
        tt.new_file('b', tt.root, 'contents\n', 'b-id', False)
 
495
 
        tt.new_file('c', tt.root, 'contents\n', 'c-id', True)
 
496
 
        tt.new_file('d', tt.root, 'contents\n', 'd-id', False)
 
497
 
        tt.new_file('e', tt.root, 'contents\n', 'control-e-id', True)
 
498
 
        tt.new_file('f', tt.root, 'contents\n', 'control-f-id', False)
 
 
568
        tt.new_file('a', tt.root, [b'contents\n'], b'a-id', True)
 
 
569
        tt.new_file('b', tt.root, [b'contents\n'], b'b-id', False)
 
 
570
        tt.new_file('c', tt.root, [b'contents\n'], b'c-id', True)
 
 
571
        tt.new_file('d', tt.root, [b'contents\n'], b'd-id', False)
 
 
572
        tt.new_file('e', tt.root, [b'contents\n'], b'control-e-id', True)
 
 
573
        tt.new_file('f', tt.root, [b'contents\n'], b'control-f-id', False)
 
500
 
        tree.commit('one', rev_id='rev-1')
 
 
575
        tree.commit('one', rev_id=b'rev-1')
 
502
577
        tt = transform.TreeTransform(tree)
 
503
 
        tt.set_executability(False, tt.trans_id_file_id('a-id'))
 
504
 
        tt.set_executability(True, tt.trans_id_file_id('b-id'))
 
505
 
        tt.set_executability(False, tt.trans_id_file_id('c-id'))
 
506
 
        tt.set_executability(True, tt.trans_id_file_id('d-id'))
 
 
578
        tt.set_executability(False, tt.trans_id_file_id(b'a-id'))
 
 
579
        tt.set_executability(True, tt.trans_id_file_id(b'b-id'))
 
 
580
        tt.set_executability(False, tt.trans_id_file_id(b'c-id'))
 
 
581
        tt.set_executability(True, tt.trans_id_file_id(b'd-id'))
 
508
583
        tree.rename_one('c', 'new-c')
 
509
584
        tree.rename_one('d', 'new-d')
 
511
 
        d = self.get_diff(tree.basis_tree(), tree)
 
513
 
        self.assertContainsRe(d, r"file 'a'.*\(properties changed:"
 
515
 
        self.assertContainsRe(d, r"file 'b'.*\(properties changed:"
 
517
 
        self.assertContainsRe(d, r"file 'c'.*\(properties changed:"
 
519
 
        self.assertContainsRe(d, r"file 'd'.*\(properties changed:"
 
521
 
        self.assertNotContainsRe(d, r"file 'e'")
 
522
 
        self.assertNotContainsRe(d, r"file 'f'")
 
 
586
        d = get_diff_as_string(tree.basis_tree(), tree)
 
 
588
        self.assertContainsRe(d, br"file 'a'.*\(properties changed:"
 
 
590
        self.assertContainsRe(d, br"file 'b'.*\(properties changed:"
 
 
592
        self.assertContainsRe(d, br"file 'c'.*\(properties changed:"
 
 
594
        self.assertContainsRe(d, br"file 'd'.*\(properties changed:"
 
 
596
        self.assertNotContainsRe(d, br"file 'e'")
 
 
597
        self.assertNotContainsRe(d, br"file 'f'")
 
525
599
    def test_binary_unicode_filenames(self):
 
526
600
        """Test that contents of files are *not* encoded in UTF-8 when there
 
527
601
        is a binary file in the diff.
 
529
603
        # See https://bugs.launchpad.net/bugs/110092.
 
530
 
        self.requireFeature(tests.UnicodeFilenameFeature)
 
 
604
        self.requireFeature(features.UnicodeFilenameFeature)
 
532
 
        # This bug isn't triggered with cStringIO.
 
533
 
        from StringIO import StringIO
 
534
606
        tree = self.make_branch_and_tree('tree')
 
535
607
        alpha, omega = u'\u03b1', u'\u03c9'
 
536
608
        alpha_utf8, omega_utf8 = alpha.encode('utf8'), omega.encode('utf8')
 
537
609
        self.build_tree_contents(
 
538
 
            [('tree/' + alpha, chr(0)),
 
 
610
            [('tree/' + alpha, b'\0'),
 
539
611
             ('tree/' + omega,
 
540
 
              ('The %s and the %s\n' % (alpha_utf8, omega_utf8)))])
 
541
 
        tree.add([alpha], ['file-id'])
 
542
 
        tree.add([omega], ['file-id-2'])
 
543
 
        diff_content = StringIO()
 
 
612
              (b'The %s and the %s\n' % (alpha_utf8, omega_utf8)))])
 
 
613
        tree.add([alpha], [b'file-id'])
 
 
614
        tree.add([omega], [b'file-id-2'])
 
 
615
        diff_content = StubO()
 
544
616
        diff.show_diff_trees(tree.basis_tree(), tree, diff_content)
 
545
 
        d = diff_content.getvalue()
 
546
 
        self.assertContainsRe(d, r"=== added file '%s'" % alpha_utf8)
 
547
 
        self.assertContainsRe(d, "Binary files a/%s.*and b/%s.* differ\n"
 
 
617
        diff_content.check_types(self, bytes)
 
 
618
        d = b''.join(diff_content.write_record)
 
 
619
        self.assertContainsRe(d, br"=== added file '%s'" % alpha_utf8)
 
 
620
        self.assertContainsRe(d, b"Binary files a/%s.*and b/%s.* differ\n"
 
548
621
                              % (alpha_utf8, alpha_utf8))
 
549
 
        self.assertContainsRe(d, r"=== added file '%s'" % omega_utf8)
 
550
 
        self.assertContainsRe(d, r"--- a/%s" % (omega_utf8,))
 
551
 
        self.assertContainsRe(d, r"\+\+\+ b/%s" % (omega_utf8,))
 
 
622
        self.assertContainsRe(d, br"=== added file '%s'" % omega_utf8)
 
 
623
        self.assertContainsRe(d, br"--- a/%s" % (omega_utf8,))
 
 
624
        self.assertContainsRe(d, br"\+\+\+ b/%s" % (omega_utf8,))
 
553
626
    def test_unicode_filename(self):
 
554
627
        """Test when the filename are unicode."""
 
555
 
        self.requireFeature(tests.UnicodeFilenameFeature)
 
 
628
        self.requireFeature(features.UnicodeFilenameFeature)
 
557
630
        alpha, omega = u'\u03b1', u'\u03c9'
 
558
631
        autf8, outf8 = alpha.encode('utf8'), omega.encode('utf8')
 
560
633
        tree = self.make_branch_and_tree('tree')
 
561
 
        self.build_tree_contents([('tree/ren_'+alpha, 'contents\n')])
 
562
 
        tree.add(['ren_'+alpha], ['file-id-2'])
 
563
 
        self.build_tree_contents([('tree/del_'+alpha, 'contents\n')])
 
564
 
        tree.add(['del_'+alpha], ['file-id-3'])
 
565
 
        self.build_tree_contents([('tree/mod_'+alpha, 'contents\n')])
 
566
 
        tree.add(['mod_'+alpha], ['file-id-4'])
 
568
 
        tree.commit('one', rev_id='rev-1')
 
570
 
        tree.rename_one('ren_'+alpha, 'ren_'+omega)
 
571
 
        tree.remove('del_'+alpha)
 
572
 
        self.build_tree_contents([('tree/add_'+alpha, 'contents\n')])
 
573
 
        tree.add(['add_'+alpha], ['file-id'])
 
574
 
        self.build_tree_contents([('tree/mod_'+alpha, 'contents_mod\n')])
 
576
 
        d = self.get_diff(tree.basis_tree(), tree)
 
 
634
        self.build_tree_contents([('tree/ren_' + alpha, b'contents\n')])
 
 
635
        tree.add(['ren_' + alpha], [b'file-id-2'])
 
 
636
        self.build_tree_contents([('tree/del_' + alpha, b'contents\n')])
 
 
637
        tree.add(['del_' + alpha], [b'file-id-3'])
 
 
638
        self.build_tree_contents([('tree/mod_' + alpha, b'contents\n')])
 
 
639
        tree.add(['mod_' + alpha], [b'file-id-4'])
 
 
641
        tree.commit('one', rev_id=b'rev-1')
 
 
643
        tree.rename_one('ren_' + alpha, 'ren_' + omega)
 
 
644
        tree.remove('del_' + alpha)
 
 
645
        self.build_tree_contents([('tree/add_' + alpha, b'contents\n')])
 
 
646
        tree.add(['add_' + alpha], [b'file-id'])
 
 
647
        self.build_tree_contents([('tree/mod_' + alpha, b'contents_mod\n')])
 
 
649
        d = get_diff_as_string(tree.basis_tree(), tree)
 
577
650
        self.assertContainsRe(d,
 
578
 
                "=== renamed file 'ren_%s' => 'ren_%s'\n"%(autf8, outf8))
 
579
 
        self.assertContainsRe(d, "=== added file 'add_%s'"%autf8)
 
580
 
        self.assertContainsRe(d, "=== modified file 'mod_%s'"%autf8)
 
581
 
        self.assertContainsRe(d, "=== removed file 'del_%s'"%autf8)
 
 
651
                              b"=== renamed file 'ren_%s' => 'ren_%s'\n" % (autf8, outf8))
 
 
652
        self.assertContainsRe(d, b"=== added file 'add_%s'" % autf8)
 
 
653
        self.assertContainsRe(d, b"=== modified file 'mod_%s'" % autf8)
 
 
654
        self.assertContainsRe(d, b"=== removed file 'del_%s'" % autf8)
 
 
656
    def test_unicode_filename_path_encoding(self):
 
 
657
        """Test for bug #382699: unicode filenames on Windows should be shown
 
 
660
        self.requireFeature(features.UnicodeFilenameFeature)
 
 
661
        # The word 'test' in Russian
 
 
662
        _russian_test = u'\u0422\u0435\u0441\u0442'
 
 
663
        directory = _russian_test + u'/'
 
 
664
        test_txt = _russian_test + u'.txt'
 
 
665
        u1234 = u'\u1234.txt'
 
 
667
        tree = self.make_branch_and_tree('.')
 
 
668
        self.build_tree_contents([
 
 
669
            (test_txt, b'foo\n'),
 
 
673
        tree.add([test_txt, u1234, directory])
 
 
676
        diff.show_diff_trees(tree.basis_tree(), tree, sio,
 
 
677
                             path_encoding='cp1251')
 
 
679
        output = subst_dates(sio.getvalue())
 
 
681
=== added directory '%(directory)s'
 
 
682
=== added file '%(test_txt)s'
 
 
683
--- a/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
 
684
+++ b/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
 
688
=== added file '?.txt'
 
 
689
--- a/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
 
690
+++ b/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
 
694
''' % {b'directory': _russian_test.encode('cp1251'),
 
 
695
            b'test_txt': test_txt.encode('cp1251'),
 
 
697
        self.assertEqualDiff(output, shouldbe)
 
584
700
class DiffWasIs(diff.DiffPath):
 
586
 
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
 
587
 
        self.to_file.write('was: ')
 
588
 
        self.to_file.write(self.old_tree.get_file(file_id).read())
 
589
 
        self.to_file.write('is: ')
 
590
 
        self.to_file.write(self.new_tree.get_file(file_id).read())
 
 
702
    def diff(self, old_path, new_path, old_kind, new_kind):
 
 
703
        self.to_file.write(b'was: ')
 
 
704
        self.to_file.write(self.old_tree.get_file(old_path).read())
 
 
705
        self.to_file.write(b'is: ')
 
 
706
        self.to_file.write(self.new_tree.get_file(new_path).read())
 
594
709
class TestDiffTree(tests.TestCaseWithTransport):
 
 
601
716
        self.new_tree = self.make_branch_and_tree('new-tree')
 
602
717
        self.new_tree.lock_write()
 
603
718
        self.addCleanup(self.new_tree.unlock)
 
604
 
        self.differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
 
 
719
        self.differ = diff.DiffTree(self.old_tree, self.new_tree, BytesIO())
 
606
721
    def test_diff_text(self):
 
607
722
        self.build_tree_contents([('old-tree/olddir/',),
 
608
 
                                  ('old-tree/olddir/oldfile', 'old\n')])
 
 
723
                                  ('old-tree/olddir/oldfile', b'old\n')])
 
609
724
        self.old_tree.add('olddir')
 
610
 
        self.old_tree.add('olddir/oldfile', 'file-id')
 
 
725
        self.old_tree.add('olddir/oldfile', b'file-id')
 
611
726
        self.build_tree_contents([('new-tree/newdir/',),
 
612
 
                                  ('new-tree/newdir/newfile', 'new\n')])
 
 
727
                                  ('new-tree/newdir/newfile', b'new\n')])
 
613
728
        self.new_tree.add('newdir')
 
614
 
        self.new_tree.add('newdir/newfile', 'file-id')
 
615
 
        differ = diff.DiffText(self.old_tree, self.new_tree, StringIO())
 
616
 
        differ.diff_text('file-id', None, 'old label', 'new label')
 
618
 
            '--- old label\n+++ new label\n@@ -1,1 +0,0 @@\n-old\n\n',
 
619
 
            differ.to_file.getvalue())
 
620
 
        differ.to_file.seek(0)
 
621
 
        differ.diff_text(None, 'file-id', 'old label', 'new label')
 
623
 
            '--- old label\n+++ new label\n@@ -0,0 +1,1 @@\n+new\n\n',
 
624
 
            differ.to_file.getvalue())
 
625
 
        differ.to_file.seek(0)
 
626
 
        differ.diff_text('file-id', 'file-id', 'old label', 'new label')
 
628
 
            '--- old label\n+++ new label\n@@ -1,1 +1,1 @@\n-old\n+new\n\n',
 
 
729
        self.new_tree.add('newdir/newfile', b'file-id')
 
 
730
        differ = diff.DiffText(self.old_tree, self.new_tree, BytesIO())
 
 
731
        differ.diff_text('olddir/oldfile', None, 'old label', 'new label')
 
 
733
            b'--- old label\n+++ new label\n@@ -1,1 +0,0 @@\n-old\n\n',
 
 
734
            differ.to_file.getvalue())
 
 
735
        differ.to_file.seek(0)
 
 
736
        differ.diff_text(None, 'newdir/newfile',
 
 
737
                         'old label', 'new label')
 
 
739
            b'--- old label\n+++ new label\n@@ -0,0 +1,1 @@\n+new\n\n',
 
 
740
            differ.to_file.getvalue())
 
 
741
        differ.to_file.seek(0)
 
 
742
        differ.diff_text('olddir/oldfile', 'newdir/newfile',
 
 
743
                         'old label', 'new label')
 
 
745
            b'--- old label\n+++ new label\n@@ -1,1 +1,1 @@\n-old\n+new\n\n',
 
629
746
            differ.to_file.getvalue())
 
631
748
    def test_diff_deletion(self):
 
632
 
        self.build_tree_contents([('old-tree/file', 'contents'),
 
633
 
                                  ('new-tree/file', 'contents')])
 
634
 
        self.old_tree.add('file', 'file-id')
 
635
 
        self.new_tree.add('file', 'file-id')
 
 
749
        self.build_tree_contents([('old-tree/file', b'contents'),
 
 
750
                                  ('new-tree/file', b'contents')])
 
 
751
        self.old_tree.add('file', b'file-id')
 
 
752
        self.new_tree.add('file', b'file-id')
 
636
753
        os.unlink('new-tree/file')
 
637
754
        self.differ.show_diff(None)
 
638
 
        self.assertContainsRe(self.differ.to_file.getvalue(), '-contents')
 
 
755
        self.assertContainsRe(self.differ.to_file.getvalue(), b'-contents')
 
640
757
    def test_diff_creation(self):
 
641
 
        self.build_tree_contents([('old-tree/file', 'contents'),
 
642
 
                                  ('new-tree/file', 'contents')])
 
643
 
        self.old_tree.add('file', 'file-id')
 
644
 
        self.new_tree.add('file', 'file-id')
 
 
758
        self.build_tree_contents([('old-tree/file', b'contents'),
 
 
759
                                  ('new-tree/file', b'contents')])
 
 
760
        self.old_tree.add('file', b'file-id')
 
 
761
        self.new_tree.add('file', b'file-id')
 
645
762
        os.unlink('old-tree/file')
 
646
763
        self.differ.show_diff(None)
 
647
 
        self.assertContainsRe(self.differ.to_file.getvalue(), '\+contents')
 
 
764
        self.assertContainsRe(self.differ.to_file.getvalue(), br'\+contents')
 
649
766
    def test_diff_symlink(self):
 
650
 
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
 
767
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, BytesIO())
 
651
768
        differ.diff_symlink('old target', None)
 
652
 
        self.assertEqual("=== target was 'old target'\n",
 
 
769
        self.assertEqual(b"=== target was 'old target'\n",
 
653
770
                         differ.to_file.getvalue())
 
655
 
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
 
772
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, BytesIO())
 
656
773
        differ.diff_symlink(None, 'new target')
 
657
 
        self.assertEqual("=== target is 'new target'\n",
 
 
774
        self.assertEqual(b"=== target is 'new target'\n",
 
658
775
                         differ.to_file.getvalue())
 
660
 
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
 
777
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, BytesIO())
 
661
778
        differ.diff_symlink('old target', 'new target')
 
662
 
        self.assertEqual("=== target changed 'old target' => 'new target'\n",
 
 
779
        self.assertEqual(b"=== target changed 'old target' => 'new target'\n",
 
663
780
                         differ.to_file.getvalue())
 
665
782
    def test_diff(self):
 
666
783
        self.build_tree_contents([('old-tree/olddir/',),
 
667
 
                                  ('old-tree/olddir/oldfile', 'old\n')])
 
 
784
                                  ('old-tree/olddir/oldfile', b'old\n')])
 
668
785
        self.old_tree.add('olddir')
 
669
 
        self.old_tree.add('olddir/oldfile', 'file-id')
 
 
786
        self.old_tree.add('olddir/oldfile', b'file-id')
 
670
787
        self.build_tree_contents([('new-tree/newdir/',),
 
671
 
                                  ('new-tree/newdir/newfile', 'new\n')])
 
 
788
                                  ('new-tree/newdir/newfile', b'new\n')])
 
672
789
        self.new_tree.add('newdir')
 
673
 
        self.new_tree.add('newdir/newfile', 'file-id')
 
674
 
        self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
 
 
790
        self.new_tree.add('newdir/newfile', b'file-id')
 
 
791
        self.differ.diff('olddir/oldfile', 'newdir/newfile')
 
675
792
        self.assertContainsRe(
 
676
793
            self.differ.to_file.getvalue(),
 
677
 
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
 
678
 
             ' \@\@\n-old\n\+new\n\n')
 
 
794
            br'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
 
 
795
            br' \@\@\n-old\n\+new\n\n')
 
680
797
    def test_diff_kind_change(self):
 
681
 
        self.requireFeature(tests.SymlinkFeature)
 
 
798
        self.requireFeature(features.SymlinkFeature)
 
682
799
        self.build_tree_contents([('old-tree/olddir/',),
 
683
 
                                  ('old-tree/olddir/oldfile', 'old\n')])
 
 
800
                                  ('old-tree/olddir/oldfile', b'old\n')])
 
684
801
        self.old_tree.add('olddir')
 
685
 
        self.old_tree.add('olddir/oldfile', 'file-id')
 
 
802
        self.old_tree.add('olddir/oldfile', b'file-id')
 
686
803
        self.build_tree(['new-tree/newdir/'])
 
687
804
        os.symlink('new', 'new-tree/newdir/newfile')
 
688
805
        self.new_tree.add('newdir')
 
689
 
        self.new_tree.add('newdir/newfile', 'file-id')
 
690
 
        self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
 
 
806
        self.new_tree.add('newdir/newfile', b'file-id')
 
 
807
        self.differ.diff('olddir/oldfile', 'newdir/newfile')
 
691
808
        self.assertContainsRe(
 
692
809
            self.differ.to_file.getvalue(),
 
693
 
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+0,0'
 
 
810
            br'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+0,0'
 
695
812
        self.assertContainsRe(self.differ.to_file.getvalue(),
 
696
 
                              "=== target is u'new'\n")
 
 
813
                              b"=== target is 'new'\n")
 
698
815
    def test_diff_directory(self):
 
699
816
        self.build_tree(['new-tree/new-dir/'])
 
700
 
        self.new_tree.add('new-dir', 'new-dir-id')
 
701
 
        self.differ.diff('new-dir-id', None, 'new-dir')
 
702
 
        self.assertEqual(self.differ.to_file.getvalue(), '')
 
 
817
        self.new_tree.add('new-dir', b'new-dir-id')
 
 
818
        self.differ.diff(None, 'new-dir')
 
 
819
        self.assertEqual(self.differ.to_file.getvalue(), b'')
 
704
821
    def create_old_new(self):
 
705
822
        self.build_tree_contents([('old-tree/olddir/',),
 
706
 
                                  ('old-tree/olddir/oldfile', 'old\n')])
 
 
823
                                  ('old-tree/olddir/oldfile', b'old\n')])
 
707
824
        self.old_tree.add('olddir')
 
708
 
        self.old_tree.add('olddir/oldfile', 'file-id')
 
 
825
        self.old_tree.add('olddir/oldfile', b'file-id')
 
709
826
        self.build_tree_contents([('new-tree/newdir/',),
 
710
 
                                  ('new-tree/newdir/newfile', 'new\n')])
 
 
827
                                  ('new-tree/newdir/newfile', b'new\n')])
 
711
828
        self.new_tree.add('newdir')
 
712
 
        self.new_tree.add('newdir/newfile', 'file-id')
 
 
829
        self.new_tree.add('newdir/newfile', b'file-id')
 
714
831
    def test_register_diff(self):
 
715
832
        self.create_old_new()
 
716
833
        old_diff_factories = diff.DiffTree.diff_factories
 
717
 
        diff.DiffTree.diff_factories=old_diff_factories[:]
 
 
834
        diff.DiffTree.diff_factories = old_diff_factories[:]
 
718
835
        diff.DiffTree.diff_factories.insert(0, DiffWasIs.from_diff_tree)
 
720
 
            differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
 
 
837
            differ = diff.DiffTree(self.old_tree, self.new_tree, BytesIO())
 
722
839
            diff.DiffTree.diff_factories = old_diff_factories
 
723
 
        differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
 
 
840
        differ.diff('olddir/oldfile', 'newdir/newfile')
 
724
841
        self.assertNotContainsRe(
 
725
842
            differ.to_file.getvalue(),
 
726
 
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
 
727
 
             ' \@\@\n-old\n\+new\n\n')
 
 
843
            br'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
 
 
844
            br' \@\@\n-old\n\+new\n\n')
 
728
845
        self.assertContainsRe(differ.to_file.getvalue(),
 
729
 
                              'was: old\nis: new\n')
 
 
846
                              b'was: old\nis: new\n')
 
731
848
    def test_extra_factories(self):
 
732
849
        self.create_old_new()
 
733
 
        differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO(),
 
 
850
        differ = diff.DiffTree(self.old_tree, self.new_tree, BytesIO(),
 
734
851
                               extra_factories=[DiffWasIs.from_diff_tree])
 
735
 
        differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
 
 
852
        differ.diff('olddir/oldfile', 'newdir/newfile')
 
736
853
        self.assertNotContainsRe(
 
737
854
            differ.to_file.getvalue(),
 
738
 
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
 
739
 
             ' \@\@\n-old\n\+new\n\n')
 
 
855
            br'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
 
 
856
            br' \@\@\n-old\n\+new\n\n')
 
740
857
        self.assertContainsRe(differ.to_file.getvalue(),
 
741
 
                              'was: old\nis: new\n')
 
 
858
                              b'was: old\nis: new\n')
 
743
860
    def test_alphabetical_order(self):
 
744
861
        self.build_tree(['new-tree/a-file'])
 
 
764
881
        b = ''.join([unichr(i) for i in range(4300, 4800, 2)])
 
765
882
        sm = self._PatienceSequenceMatcher(None, a, b)
 
766
883
        mb = sm.get_matching_blocks()
 
767
 
        self.assertEquals(35, len(mb))
 
 
884
        self.assertEqual(35, len(mb))
 
769
886
    def test_unique_lcs(self):
 
770
887
        unique_lcs = self._unique_lcs
 
771
 
        self.assertEquals(unique_lcs('', ''), [])
 
772
 
        self.assertEquals(unique_lcs('', 'a'), [])
 
773
 
        self.assertEquals(unique_lcs('a', ''), [])
 
774
 
        self.assertEquals(unique_lcs('a', 'a'), [(0,0)])
 
775
 
        self.assertEquals(unique_lcs('a', 'b'), [])
 
776
 
        self.assertEquals(unique_lcs('ab', 'ab'), [(0,0), (1,1)])
 
777
 
        self.assertEquals(unique_lcs('abcde', 'cdeab'), [(2,0), (3,1), (4,2)])
 
778
 
        self.assertEquals(unique_lcs('cdeab', 'abcde'), [(0,2), (1,3), (2,4)])
 
779
 
        self.assertEquals(unique_lcs('abXde', 'abYde'), [(0,0), (1,1),
 
781
 
        self.assertEquals(unique_lcs('acbac', 'abc'), [(2,1)])
 
 
888
        self.assertEqual(unique_lcs('', ''), [])
 
 
889
        self.assertEqual(unique_lcs('', 'a'), [])
 
 
890
        self.assertEqual(unique_lcs('a', ''), [])
 
 
891
        self.assertEqual(unique_lcs('a', 'a'), [(0, 0)])
 
 
892
        self.assertEqual(unique_lcs('a', 'b'), [])
 
 
893
        self.assertEqual(unique_lcs('ab', 'ab'), [(0, 0), (1, 1)])
 
 
894
        self.assertEqual(unique_lcs('abcde', 'cdeab'),
 
 
895
                         [(2, 0), (3, 1), (4, 2)])
 
 
896
        self.assertEqual(unique_lcs('cdeab', 'abcde'),
 
 
897
                         [(0, 2), (1, 3), (2, 4)])
 
 
898
        self.assertEqual(unique_lcs('abXde', 'abYde'), [(0, 0), (1, 1),
 
 
900
        self.assertEqual(unique_lcs('acbac', 'abc'), [(2, 1)])
 
783
902
    def test_recurse_matches(self):
 
784
903
        def test_one(a, b, matches):
 
785
904
            test_matches = []
 
786
905
            self._recurse_matches(
 
787
906
                a, b, 0, 0, len(a), len(b), test_matches, 10)
 
788
 
            self.assertEquals(test_matches, matches)
 
 
907
            self.assertEqual(test_matches, matches)
 
790
909
        test_one(['a', '', 'b', '', 'c'], ['a', 'a', 'b', 'c', 'c'],
 
791
910
                 [(0, 0), (2, 2), (4, 4)])
 
 
896
1016
    def test_opcodes(self):
 
897
1017
        def chk_ops(a, b, expected_codes):
 
898
1018
            s = self._PatienceSequenceMatcher(None, a, b)
 
899
 
            self.assertEquals(expected_codes, s.get_opcodes())
 
 
1019
            self.assertEqual(expected_codes, s.get_opcodes())
 
901
1021
        chk_ops('', '', [])
 
902
1022
        chk_ops([], [], [])
 
903
 
        chk_ops('abc', '', [('delete', 0,3, 0,0)])
 
904
 
        chk_ops('', 'abc', [('insert', 0,0, 0,3)])
 
905
 
        chk_ops('abcd', 'abcd', [('equal',    0,4, 0,4)])
 
906
 
        chk_ops('abcd', 'abce', [('equal',   0,3, 0,3),
 
907
 
                                 ('replace', 3,4, 3,4)
 
909
 
        chk_ops('eabc', 'abce', [('delete', 0,1, 0,0),
 
913
 
        chk_ops('eabce', 'abce', [('delete', 0,1, 0,0),
 
916
 
        chk_ops('abcde', 'abXde', [('equal',   0,2, 0,2),
 
917
 
                                   ('replace', 2,3, 2,3),
 
 
1023
        chk_ops('abc', '', [('delete', 0, 3, 0, 0)])
 
 
1024
        chk_ops('', 'abc', [('insert', 0, 0, 0, 3)])
 
 
1025
        chk_ops('abcd', 'abcd', [('equal', 0, 4, 0, 4)])
 
 
1026
        chk_ops('abcd', 'abce', [('equal', 0, 3, 0, 3),
 
 
1027
                                 ('replace', 3, 4, 3, 4)
 
 
1029
        chk_ops('eabc', 'abce', [('delete', 0, 1, 0, 0),
 
 
1030
                                 ('equal', 1, 4, 0, 3),
 
 
1031
                                 ('insert', 4, 4, 3, 4)
 
 
1033
        chk_ops('eabce', 'abce', [('delete', 0, 1, 0, 0),
 
 
1034
                                  ('equal', 1, 5, 0, 4)
 
920
 
        chk_ops('abcde', 'abXYZde', [('equal',   0,2, 0,2),
 
921
 
                                     ('replace', 2,3, 2,5),
 
 
1036
        chk_ops('abcde', 'abXde', [('equal', 0, 2, 0, 2),
 
 
1037
                                   ('replace', 2, 3, 2, 3),
 
 
1038
                                   ('equal', 3, 5, 3, 5)
 
 
1040
        chk_ops('abcde', 'abXYZde', [('equal', 0, 2, 0, 2),
 
 
1041
                                     ('replace', 2, 3, 2, 5),
 
 
1042
                                     ('equal', 3, 5, 5, 7)
 
 
1044
        chk_ops('abde', 'abXYZde', [('equal', 0, 2, 0, 2),
 
 
1045
                                    ('insert', 2, 2, 2, 5),
 
 
1046
                                    ('equal', 2, 4, 5, 7)
 
924
 
        chk_ops('abde', 'abXYZde', [('equal',  0,2, 0,2),
 
925
 
                                    ('insert', 2,2, 2,5),
 
928
1048
        chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
 
929
 
                [('equal',  0,6,  0,6),
 
930
 
                 ('insert', 6,6,  6,11),
 
931
 
                 ('equal',  6,16, 11,21)
 
 
1049
                [('equal', 0, 6, 0, 6),
 
 
1050
                 ('insert', 6, 6, 6, 11),
 
 
1051
                 ('equal', 6, 16, 11, 21)
 
936
 
                , 'how are you today?\n'],
 
938
 
                , 'how are you today?\n'],
 
939
 
                [('equal',  0,1, 0,1),
 
940
 
                 ('delete', 1,2, 1,1),
 
 
1054
            ['hello there\n', 'world\n', 'how are you today?\n'],
 
 
1055
            ['hello there\n', 'how are you today?\n'],
 
 
1056
            [('equal', 0, 1, 0, 1),
 
 
1057
             ('delete', 1, 2, 1, 1),
 
 
1058
             ('equal', 2, 3, 1, 2),
 
943
1060
        chk_ops('aBccDe', 'abccde',
 
944
 
                [('equal',   0,1, 0,1),
 
945
 
                 ('replace', 1,5, 1,5),
 
 
1061
                [('equal', 0, 1, 0, 1),
 
 
1062
                 ('replace', 1, 5, 1, 5),
 
 
1063
                 ('equal', 5, 6, 5, 6),
 
948
1065
        chk_ops('aBcDec', 'abcdec',
 
949
 
                [('equal',   0,1, 0,1),
 
950
 
                 ('replace', 1,2, 1,2),
 
952
 
                 ('replace', 3,4, 3,4),
 
 
1066
                [('equal', 0, 1, 0, 1),
 
 
1067
                 ('replace', 1, 2, 1, 2),
 
 
1068
                 ('equal', 2, 3, 2, 3),
 
 
1069
                 ('replace', 3, 4, 3, 4),
 
 
1070
                 ('equal', 4, 6, 4, 6),
 
955
1072
        chk_ops('aBcdEcdFg', 'abcdecdfg',
 
956
 
                [('equal',   0,1, 0,1),
 
957
 
                 ('replace', 1,8, 1,8),
 
 
1073
                [('equal', 0, 1, 0, 1),
 
 
1074
                 ('replace', 1, 8, 1, 8),
 
 
1075
                 ('equal', 8, 9, 8, 9)
 
960
1077
        chk_ops('aBcdEeXcdFg', 'abcdecdfg',
 
961
 
                [('equal',   0,1, 0,1),
 
962
 
                 ('replace', 1,2, 1,2),
 
964
 
                 ('delete', 4,5, 4,4),
 
966
 
                 ('delete', 6,7, 5,5),
 
968
 
                 ('replace', 9,10, 7,8),
 
969
 
                 ('equal',   10,11, 8,9)
 
 
1078
                [('equal', 0, 1, 0, 1),
 
 
1079
                 ('replace', 1, 2, 1, 2),
 
 
1080
                 ('equal', 2, 4, 2, 4),
 
 
1081
                 ('delete', 4, 5, 4, 4),
 
 
1082
                 ('equal', 5, 6, 4, 5),
 
 
1083
                 ('delete', 6, 7, 5, 5),
 
 
1084
                 ('equal', 7, 9, 5, 7),
 
 
1085
                 ('replace', 9, 10, 7, 8),
 
 
1086
                 ('equal', 10, 11, 8, 9)
 
972
1089
    def test_grouped_opcodes(self):
 
973
1090
        def chk_ops(a, b, expected_codes, n=3):
 
974
1091
            s = self._PatienceSequenceMatcher(None, a, b)
 
975
 
            self.assertEquals(expected_codes, list(s.get_grouped_opcodes(n)))
 
 
1092
            self.assertEqual(expected_codes, list(s.get_grouped_opcodes(n)))
 
977
1094
        chk_ops('', '', [])
 
978
1095
        chk_ops([], [], [])
 
979
 
        chk_ops('abc', '', [[('delete', 0,3, 0,0)]])
 
980
 
        chk_ops('', 'abc', [[('insert', 0,0, 0,3)]])
 
 
1096
        chk_ops('abc', '', [[('delete', 0, 3, 0, 0)]])
 
 
1097
        chk_ops('', 'abc', [[('insert', 0, 0, 0, 3)]])
 
981
1098
        chk_ops('abcd', 'abcd', [])
 
982
 
        chk_ops('abcd', 'abce', [[('equal',   0,3, 0,3),
 
983
 
                                  ('replace', 3,4, 3,4)
 
985
 
        chk_ops('eabc', 'abce', [[('delete', 0,1, 0,0),
 
 
1099
        chk_ops('abcd', 'abce', [[('equal', 0, 3, 0, 3),
 
 
1100
                                  ('replace', 3, 4, 3, 4)
 
 
1102
        chk_ops('eabc', 'abce', [[('delete', 0, 1, 0, 0),
 
 
1103
                                  ('equal', 1, 4, 0, 3),
 
 
1104
                                  ('insert', 4, 4, 3, 4)
 
989
1106
        chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
 
990
 
                [[('equal',  3,6, 3,6),
 
991
 
                  ('insert', 6,6, 6,11),
 
992
 
                  ('equal',  6,9, 11,14)
 
 
1107
                [[('equal', 3, 6, 3, 6),
 
 
1108
                  ('insert', 6, 6, 6, 11),
 
 
1109
                  ('equal', 6, 9, 11, 14)
 
994
1111
        chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
 
995
 
                [[('equal',  2,6, 2,6),
 
996
 
                  ('insert', 6,6, 6,11),
 
997
 
                  ('equal',  6,10, 11,15)
 
 
1112
                [[('equal', 2, 6, 2, 6),
 
 
1113
                  ('insert', 6, 6, 6, 11),
 
 
1114
                  ('equal', 6, 10, 11, 15)
 
999
1116
        chk_ops('Xabcdef', 'abcdef',
 
1000
 
                [[('delete', 0,1, 0,0),
 
 
1117
                [[('delete', 0, 1, 0, 0),
 
 
1118
                  ('equal', 1, 4, 0, 3)
 
1003
1120
        chk_ops('abcdef', 'abcdefX',
 
1004
 
                [[('equal',  3,6, 3,6),
 
1005
 
                  ('insert', 6,6, 6,7)
 
 
1121
                [[('equal', 3, 6, 3, 6),
 
 
1122
                  ('insert', 6, 6, 6, 7)
 
1009
1125
    def test_multiple_ranges(self):
 
1010
1126
        # There was an earlier bug where we used a bad set of ranges,
 
1011
1127
        # this triggers that specific bug, to make sure it doesn't regress
 
 
1356
1468
        self.assertContainsRe(result.replace('\r\n', '\n'), regex)
 
1358
1470
    def test_prepare_files(self):
 
1360
1472
        tree = self.make_branch_and_tree('tree')
 
1361
 
        self.build_tree_contents([('tree/oldname', 'oldcontent')])
 
1362
 
        self.build_tree_contents([('tree/oldname2', 'oldcontent2')])
 
1363
 
        tree.add('oldname', 'file-id')
 
1364
 
        tree.add('oldname2', 'file2-id')
 
 
1473
        self.build_tree_contents([('tree/oldname', b'oldcontent')])
 
 
1474
        self.build_tree_contents([('tree/oldname2', b'oldcontent2')])
 
 
1475
        tree.add('oldname', b'file-id')
 
 
1476
        tree.add('oldname2', b'file2-id')
 
1365
1477
        # Earliest allowable date on FAT32 filesystems is 1980-01-01
 
1366
1478
        tree.commit('old tree', timestamp=315532800)
 
1367
1479
        tree.rename_one('oldname', 'newname')
 
1368
1480
        tree.rename_one('oldname2', 'newname2')
 
1369
 
        self.build_tree_contents([('tree/newname', 'newcontent')])
 
1370
 
        self.build_tree_contents([('tree/newname2', 'newcontent2')])
 
 
1481
        self.build_tree_contents([('tree/newname', b'newcontent')])
 
 
1482
        self.build_tree_contents([('tree/newname2', b'newcontent2')])
 
1371
1483
        old_tree = tree.basis_tree()
 
1372
1484
        old_tree.lock_read()
 
1373
1485
        self.addCleanup(old_tree.unlock)
 
1374
1486
        tree.lock_read()
 
1375
1487
        self.addCleanup(tree.unlock)
 
1376
 
        diff_obj = diff.DiffFromTool(['python', '-c',
 
 
1488
        diff_obj = diff.DiffFromTool([sys.executable, '-c',
 
1377
1489
                                      'print "@old_path @new_path"'],
 
1378
1490
                                     old_tree, tree, output)
 
1379
1491
        self.addCleanup(diff_obj.finish)
 
1380
 
        self.assertContainsRe(diff_obj._root, 'bzr-diff-[^/]*')
 
1381
 
        old_path, new_path = diff_obj._prepare_files('file-id', 'oldname',
 
 
1492
        self.assertContainsRe(diff_obj._root, 'brz-diff-[^/]*')
 
 
1493
        old_path, new_path = diff_obj._prepare_files(
 
 
1494
            'oldname', 'newname')
 
1383
1495
        self.assertContainsRe(old_path, 'old/oldname$')
 
1384
1496
        self.assertEqual(315532800, os.stat(old_path).st_mtime)
 
1385
1497
        self.assertContainsRe(new_path, 'tree/newname$')
 
1386
 
        self.assertFileEqual('oldcontent', old_path)
 
1387
 
        self.assertFileEqual('newcontent', new_path)
 
 
1498
        self.assertFileEqual(b'oldcontent', old_path)
 
 
1499
        self.assertFileEqual(b'newcontent', new_path)
 
1388
1500
        if osutils.host_os_dereferences_symlinks():
 
1389
1501
            self.assertTrue(os.path.samefile('tree/newname', new_path))
 
1390
1502
        # make sure we can create files with the same parent directories
 
1391
 
        diff_obj._prepare_files('file2-id', 'oldname2', 'newname2')
 
 
1503
        diff_obj._prepare_files('oldname2', 'newname2')
 
 
1506
class TestDiffFromToolEncodedFilename(tests.TestCaseWithTransport):
 
 
1508
    def test_encodable_filename(self):
 
 
1509
        # Just checks file path for external diff tool.
 
 
1510
        # We cannot change CPython's internal encoding used by os.exec*.
 
 
1511
        diffobj = diff.DiffFromTool(['dummy', '@old_path', '@new_path'],
 
 
1513
        for _, scenario in EncodingAdapter.encoding_scenarios:
 
 
1514
            encoding = scenario['encoding']
 
 
1515
            dirname = scenario['info']['directory']
 
 
1516
            filename = scenario['info']['filename']
 
 
1518
            self.overrideAttr(diffobj, '_fenc', lambda: encoding)
 
 
1519
            relpath = dirname + u'/' + filename
 
 
1520
            fullpath = diffobj._safe_filename('safe', relpath)
 
 
1521
            self.assertEqual(fullpath,
 
 
1522
                             fullpath.encode(encoding).decode(encoding))
 
 
1523
            self.assertTrue(fullpath.startswith(diffobj._root + '/safe'))
 
 
1525
    def test_unencodable_filename(self):
 
 
1526
        diffobj = diff.DiffFromTool(['dummy', '@old_path', '@new_path'],
 
 
1528
        for _, scenario in EncodingAdapter.encoding_scenarios:
 
 
1529
            encoding = scenario['encoding']
 
 
1530
            dirname = scenario['info']['directory']
 
 
1531
            filename = scenario['info']['filename']
 
 
1533
            if encoding == 'iso-8859-1':
 
 
1534
                encoding = 'iso-8859-2'
 
 
1536
                encoding = 'iso-8859-1'
 
 
1538
            self.overrideAttr(diffobj, '_fenc', lambda: encoding)
 
 
1539
            relpath = dirname + u'/' + filename
 
 
1540
            fullpath = diffobj._safe_filename('safe', relpath)
 
 
1541
            self.assertEqual(fullpath,
 
 
1542
                             fullpath.encode(encoding).decode(encoding))
 
 
1543
            self.assertTrue(fullpath.startswith(diffobj._root + '/safe'))
 
1394
1546
class TestGetTreesAndBranchesToDiffLocked(tests.TestCaseWithTransport):
 
1396
1548
    def call_gtabtd(self, path_list, revision_specs, old_url, new_url):
 
1397
 
        """Call get_trees_and_branches_to_diff_locked.  Overridden by
 
1398
 
        TestGetTreesAndBranchesToDiff.
 
 
1549
        """Call get_trees_and_branches_to_diff_locked."""
 
1400
1550
        return diff.get_trees_and_branches_to_diff_locked(
 
1401
1551
            path_list, revision_specs, old_url, new_url, self.addCleanup)