Statistics
| Revision:

gvsig-scripting / org.gvsig.scripting / trunk / org.gvsig.scripting / org.gvsig.scripting.app / org.gvsig.scripting.app.mainplugin / src / main / resources-plugin / scripting / lib / dulwich / tests / test_diff_tree.py @ 959

History | View | Annotate | Download (40.2 KB)

1
# test_diff_tree.py -- Tests for file and tree diff utilities.
2
# Copyright (C) 2010 Google, Inc.
3
#
4
# Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
5
# General Public License as public by the Free Software Foundation; version 2.0
6
# or (at your option) any later version. You can redistribute it and/or
7
# modify it under the terms of either of these two licenses.
8
#
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
# See the License for the specific language governing permissions and
13
# limitations under the License.
14
#
15
# You should have received a copy of the licenses; if not, see
16
# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
17
# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
18
# License, Version 2.0.
19
#
20

    
21
"""Tests for file and tree diff utilities."""
22

    
23
from itertools import permutations
24
from dulwich.diff_tree import (
25
    CHANGE_MODIFY,
26
    CHANGE_RENAME,
27
    CHANGE_COPY,
28
    CHANGE_UNCHANGED,
29
    TreeChange,
30
    _merge_entries,
31
    _merge_entries_py,
32
    tree_changes,
33
    tree_changes_for_merge,
34
    _count_blocks,
35
    _count_blocks_py,
36
    _similarity_score,
37
    _tree_change_key,
38
    RenameDetector,
39
    _is_tree,
40
    _is_tree_py
41
    )
42
from dulwich.index import (
43
    commit_tree,
44
    )
45
from dulwich.object_store import (
46
    MemoryObjectStore,
47
    )
48
from dulwich.objects import (
49
    ShaFile,
50
    Blob,
51
    TreeEntry,
52
    Tree,
53
    )
54
from dulwich.tests import (
55
    TestCase,
56
    )
57
from dulwich.tests.utils import (
58
    F,
59
    make_object,
60
    functest_builder,
61
    ext_functest_builder,
62
    )
63

    
64

    
65
class DiffTestCase(TestCase):
66

    
67
    def setUp(self):
68
        super(DiffTestCase, self).setUp()
69
        self.store = MemoryObjectStore()
70
        self.empty_tree = self.commit_tree([])
71

    
72
    def commit_tree(self, entries):
73
        commit_blobs = []
74
        for entry in entries:
75
            if len(entry) == 2:
76
                path, obj = entry
77
                mode = F
78
            else:
79
                path, obj, mode = entry
80
            if isinstance(obj, Blob):
81
                self.store.add_object(obj)
82
                sha = obj.id
83
            else:
84
                sha = obj
85
            commit_blobs.append((path, sha, mode))
86
        return self.store[commit_tree(self.store, commit_blobs)]
87

    
88

    
89
class TreeChangesTest(DiffTestCase):
90

    
91
    def setUp(self):
92
        super(TreeChangesTest, self).setUp()
93
        self.detector = RenameDetector(self.store)
94

    
95
    def assertMergeFails(self, merge_entries, name, mode, sha):
96
        t = Tree()
97
        t[name] = (mode, sha)
98
        self.assertRaises((TypeError, ValueError), merge_entries, '', t, t)
99

    
100
    def _do_test_merge_entries(self, merge_entries):
101
        blob_a1 = make_object(Blob, data=b'a1')
102
        blob_a2 = make_object(Blob, data=b'a2')
103
        blob_b1 = make_object(Blob, data=b'b1')
104
        blob_c2 = make_object(Blob, data=b'c2')
105
        tree1 = self.commit_tree([(b'a', blob_a1, 0o100644),
106
                                  (b'b', blob_b1, 0o100755)])
107
        tree2 = self.commit_tree([(b'a', blob_a2, 0o100644),
108
                                  (b'c', blob_c2, 0o100755)])
109

    
110
        self.assertEqual([], merge_entries(b'', self.empty_tree,
111
                                           self.empty_tree))
112
        self.assertEqual(
113
            [((None, None, None), (b'a', 0o100644, blob_a1.id)),
114
             ((None, None, None), (b'b', 0o100755, blob_b1.id)), ],
115
            merge_entries(b'', self.empty_tree, tree1))
116
        self.assertEqual(
117
            [((None, None, None), (b'x/a', 0o100644, blob_a1.id)),
118
             ((None, None, None), (b'x/b', 0o100755, blob_b1.id)), ],
119
            merge_entries(b'x', self.empty_tree, tree1))
120

    
121
        self.assertEqual(
122
            [((b'a', 0o100644, blob_a2.id), (None, None, None)),
123
             ((b'c', 0o100755, blob_c2.id), (None, None, None)), ],
124
            merge_entries(b'', tree2, self.empty_tree))
125

    
126
        self.assertEqual(
127
            [((b'a', 0o100644, blob_a1.id), (b'a', 0o100644, blob_a2.id)),
128
             ((b'b', 0o100755, blob_b1.id), (None, None, None)),
129
             ((None, None, None), (b'c', 0o100755, blob_c2.id)), ],
130
            merge_entries(b'', tree1, tree2))
131

    
132
        self.assertEqual(
133
            [((b'a', 0o100644, blob_a2.id), (b'a', 0o100644, blob_a1.id)),
134
             ((None, None, None), (b'b', 0o100755, blob_b1.id)),
135
             ((b'c', 0o100755, blob_c2.id), (None, None, None)), ],
136
            merge_entries(b'', tree2, tree1))
137

    
138
        self.assertMergeFails(merge_entries, 0xdeadbeef, 0o100644, '1' * 40)
139
        self.assertMergeFails(merge_entries, b'a', b'deadbeef', '1' * 40)
140
        self.assertMergeFails(merge_entries, b'a', 0o100644, 0xdeadbeef)
141

    
142
    test_merge_entries = functest_builder(_do_test_merge_entries,
143
                                          _merge_entries_py)
144
    test_merge_entries_extension = ext_functest_builder(_do_test_merge_entries,
145
                                                        _merge_entries)
146

    
147
    def _do_test_is_tree(self, is_tree):
148
        self.assertFalse(is_tree(TreeEntry(None, None, None)))
149
        self.assertFalse(is_tree(TreeEntry(b'a', 0o100644, b'a' * 40)))
150
        self.assertFalse(is_tree(TreeEntry(b'a', 0o100755, b'a' * 40)))
151
        self.assertFalse(is_tree(TreeEntry(b'a', 0o120000, b'a' * 40)))
152
        self.assertTrue(is_tree(TreeEntry(b'a', 0o040000, b'a' * 40)))
153
        self.assertRaises(TypeError, is_tree, TreeEntry(b'a', b'x', b'a' * 40))
154
        self.assertRaises(AttributeError, is_tree, 1234)
155

    
156
    test_is_tree = functest_builder(_do_test_is_tree, _is_tree_py)
157
    test_is_tree_extension = ext_functest_builder(_do_test_is_tree, _is_tree)
158

    
159
    def assertChangesEqual(self, expected, tree1, tree2, **kwargs):
160
        actual = list(tree_changes(self.store, tree1.id, tree2.id, **kwargs))
161
        self.assertEqual(expected, actual)
162

    
163
    # For brevity, the following tests use tuples instead of TreeEntry objects.
164

    
165
    def test_tree_changes_empty(self):
166
        self.assertChangesEqual([], self.empty_tree, self.empty_tree)
167

    
168
    def test_tree_changes_no_changes(self):
169
        blob = make_object(Blob, data=b'blob')
170
        tree = self.commit_tree([(b'a', blob), (b'b/c', blob)])
171
        self.assertChangesEqual([], self.empty_tree, self.empty_tree)
172
        self.assertChangesEqual([], tree, tree)
173
        self.assertChangesEqual(
174
            [TreeChange(CHANGE_UNCHANGED, (b'a', F, blob.id), (b'a', F, blob.id)),
175
             TreeChange(CHANGE_UNCHANGED, (b'b/c', F, blob.id),
176
                        (b'b/c', F, blob.id))],
177
            tree, tree, want_unchanged=True)
178

    
179
    def test_tree_changes_add_delete(self):
180
        blob_a = make_object(Blob, data=b'a')
181
        blob_b = make_object(Blob, data=b'b')
182
        tree = self.commit_tree([(b'a', blob_a, 0o100644),
183
                                 (b'x/b', blob_b, 0o100755)])
184
        self.assertChangesEqual(
185
            [TreeChange.add((b'a', 0o100644, blob_a.id)),
186
             TreeChange.add((b'x/b', 0o100755, blob_b.id))],
187
            self.empty_tree, tree)
188
        self.assertChangesEqual(
189
            [TreeChange.delete((b'a', 0o100644, blob_a.id)),
190
             TreeChange.delete((b'x/b', 0o100755, blob_b.id))],
191
            tree, self.empty_tree)
192

    
193
    def test_tree_changes_modify_contents(self):
194
        blob_a1 = make_object(Blob, data=b'a1')
195
        blob_a2 = make_object(Blob, data=b'a2')
196
        tree1 = self.commit_tree([(b'a', blob_a1)])
197
        tree2 = self.commit_tree([(b'a', blob_a2)])
198
        self.assertChangesEqual(
199
            [TreeChange(CHANGE_MODIFY, (b'a', F, blob_a1.id),
200
                        (b'a', F, blob_a2.id))],
201
            tree1, tree2)
202

    
203
    def test_tree_changes_modify_mode(self):
204
        blob_a = make_object(Blob, data=b'a')
205
        tree1 = self.commit_tree([(b'a', blob_a, 0o100644)])
206
        tree2 = self.commit_tree([(b'a', blob_a, 0o100755)])
207
        self.assertChangesEqual(
208
            [TreeChange(CHANGE_MODIFY, (b'a', 0o100644, blob_a.id),
209
                        (b'a', 0o100755, blob_a.id))],
210
            tree1, tree2)
211

    
212
    def test_tree_changes_change_type(self):
213
        blob_a1 = make_object(Blob, data=b'a')
214
        blob_a2 = make_object(Blob, data=b'/foo/bar')
215
        tree1 = self.commit_tree([(b'a', blob_a1, 0o100644)])
216
        tree2 = self.commit_tree([(b'a', blob_a2, 0o120000)])
217
        self.assertChangesEqual(
218
            [TreeChange.delete((b'a', 0o100644, blob_a1.id)),
219
             TreeChange.add((b'a', 0o120000, blob_a2.id))],
220
            tree1, tree2)
221

    
222
    def test_tree_changes_to_tree(self):
223
        blob_a = make_object(Blob, data=b'a')
224
        blob_x = make_object(Blob, data=b'x')
225
        tree1 = self.commit_tree([(b'a', blob_a)])
226
        tree2 = self.commit_tree([(b'a/x', blob_x)])
227
        self.assertChangesEqual(
228
            [TreeChange.delete((b'a', F, blob_a.id)),
229
             TreeChange.add((b'a/x', F, blob_x.id))],
230
            tree1, tree2)
231

    
232
    def test_tree_changes_complex(self):
233
        blob_a_1 = make_object(Blob, data=b'a1_1')
234
        blob_bx1_1 = make_object(Blob, data=b'bx1_1')
235
        blob_bx2_1 = make_object(Blob, data=b'bx2_1')
236
        blob_by1_1 = make_object(Blob, data=b'by1_1')
237
        blob_by2_1 = make_object(Blob, data=b'by2_1')
238
        tree1 = self.commit_tree([
239
            (b'a', blob_a_1),
240
            (b'b/x/1', blob_bx1_1),
241
            (b'b/x/2', blob_bx2_1),
242
            (b'b/y/1', blob_by1_1),
243
            (b'b/y/2', blob_by2_1),
244
        ])
245

    
246
        blob_a_2 = make_object(Blob, data=b'a1_2')
247
        blob_bx1_2 = blob_bx1_1
248
        blob_by_2 = make_object(Blob, data=b'by_2')
249
        blob_c_2 = make_object(Blob, data=b'c_2')
250
        tree2 = self.commit_tree([
251
            (b'a', blob_a_2),
252
            (b'b/x/1', blob_bx1_2),
253
            (b'b/y', blob_by_2),
254
            (b'c', blob_c_2),
255
        ])
256

    
257
        self.assertChangesEqual(
258
            [TreeChange(CHANGE_MODIFY, (b'a', F, blob_a_1.id),
259
                        (b'a', F, blob_a_2.id)),
260
             TreeChange.delete((b'b/x/2', F, blob_bx2_1.id)),
261
             TreeChange.add((b'b/y', F, blob_by_2.id)),
262
             TreeChange.delete((b'b/y/1', F, blob_by1_1.id)),
263
             TreeChange.delete((b'b/y/2', F, blob_by2_1.id)),
264
             TreeChange.add((b'c', F, blob_c_2.id))],
265
            tree1, tree2)
266

    
267
    def test_tree_changes_name_order(self):
268
        blob = make_object(Blob, data=b'a')
269
        tree1 = self.commit_tree([(b'a', blob), (b'a.', blob), (b'a..', blob)])
270
        # Tree order is the reverse of this, so if we used tree order, 'a..'
271
        # would not be merged.
272
        tree2 = self.commit_tree([(b'a/x', blob), (b'a./x', blob), (b'a..', blob)])
273

    
274
        self.assertChangesEqual(
275
            [TreeChange.delete((b'a', F, blob.id)),
276
             TreeChange.add((b'a/x', F, blob.id)),
277
             TreeChange.delete((b'a.', F, blob.id)),
278
             TreeChange.add((b'a./x', F, blob.id))],
279
            tree1, tree2)
280

    
281
    def test_tree_changes_prune(self):
282
        blob_a1 = make_object(Blob, data=b'a1')
283
        blob_a2 = make_object(Blob, data=b'a2')
284
        blob_x = make_object(Blob, data=b'x')
285
        tree1 = self.commit_tree([(b'a', blob_a1), (b'b/x', blob_x)])
286
        tree2 = self.commit_tree([(b'a', blob_a2), (b'b/x', blob_x)])
287
        # Remove identical items so lookups will fail unless we prune.
288
        subtree = self.store[tree1[b'b'][1]]
289
        for entry in subtree.items():
290
            del self.store[entry.sha]
291
        del self.store[subtree.id]
292

    
293
        self.assertChangesEqual(
294
            [TreeChange(CHANGE_MODIFY, (b'a', F, blob_a1.id),
295
                        (b'a', F, blob_a2.id))],
296
            tree1, tree2)
297

    
298
    def test_tree_changes_rename_detector(self):
299
        blob_a1 = make_object(Blob, data=b'a\nb\nc\nd\n')
300
        blob_a2 = make_object(Blob, data=b'a\nb\nc\ne\n')
301
        blob_b = make_object(Blob, data=b'b')
302
        tree1 = self.commit_tree([(b'a', blob_a1), (b'b', blob_b)])
303
        tree2 = self.commit_tree([(b'c', blob_a2), (b'b', blob_b)])
304
        detector = RenameDetector(self.store)
305

    
306
        self.assertChangesEqual(
307
            [TreeChange.delete((b'a', F, blob_a1.id)),
308
             TreeChange.add((b'c', F, blob_a2.id))],
309
            tree1, tree2)
310
        self.assertChangesEqual(
311
            [TreeChange.delete((b'a', F, blob_a1.id)),
312
             TreeChange(CHANGE_UNCHANGED, (b'b', F, blob_b.id),
313
                        (b'b', F, blob_b.id)),
314
             TreeChange.add((b'c', F, blob_a2.id))],
315
            tree1, tree2, want_unchanged=True)
316
        self.assertChangesEqual(
317
            [TreeChange(CHANGE_RENAME, (b'a', F, blob_a1.id),
318
                        (b'c', F, blob_a2.id))],
319
            tree1, tree2, rename_detector=detector)
320
        self.assertChangesEqual(
321
            [TreeChange(CHANGE_RENAME, (b'a', F, blob_a1.id),
322
                        (b'c', F, blob_a2.id)),
323
             TreeChange(CHANGE_UNCHANGED, (b'b', F, blob_b.id),
324
                        (b'b', F, blob_b.id))],
325
            tree1, tree2, rename_detector=detector, want_unchanged=True)
326

    
327
    def assertChangesForMergeEqual(self, expected, parent_trees, merge_tree,
328
                                   **kwargs):
329
        parent_tree_ids = [t.id for t in parent_trees]
330
        actual = list(tree_changes_for_merge(
331
          self.store, parent_tree_ids, merge_tree.id, **kwargs))
332
        self.assertEqual(expected, actual)
333

    
334
        parent_tree_ids.reverse()
335
        expected = [list(reversed(cs)) for cs in expected]
336
        actual = list(tree_changes_for_merge(
337
          self.store, parent_tree_ids, merge_tree.id, **kwargs))
338
        self.assertEqual(expected, actual)
339

    
340
    def test_tree_changes_for_merge_add_no_conflict(self):
341
        blob = make_object(Blob, data=b'blob')
342
        parent1 = self.commit_tree([])
343
        parent2 = merge = self.commit_tree([(b'a', blob)])
344
        self.assertChangesForMergeEqual([], [parent1, parent2], merge)
345
        self.assertChangesForMergeEqual([], [parent2, parent2], merge)
346

    
347
    def test_tree_changes_for_merge_add_modify_conflict(self):
348
        blob1 = make_object(Blob, data=b'1')
349
        blob2 = make_object(Blob, data=b'2')
350
        parent1 = self.commit_tree([])
351
        parent2 = self.commit_tree([(b'a', blob1)])
352
        merge = self.commit_tree([(b'a', blob2)])
353
        self.assertChangesForMergeEqual(
354
            [[TreeChange.add((b'a', F, blob2.id)),
355
              TreeChange(CHANGE_MODIFY, (b'a', F, blob1.id), (b'a', F, blob2.id))]],
356
            [parent1, parent2], merge)
357

    
358
    def test_tree_changes_for_merge_modify_modify_conflict(self):
359
        blob1 = make_object(Blob, data=b'1')
360
        blob2 = make_object(Blob, data=b'2')
361
        blob3 = make_object(Blob, data=b'3')
362
        parent1 = self.commit_tree([(b'a', blob1)])
363
        parent2 = self.commit_tree([(b'a', blob2)])
364
        merge = self.commit_tree([(b'a', blob3)])
365
        self.assertChangesForMergeEqual(
366
            [[TreeChange(CHANGE_MODIFY, (b'a', F, blob1.id), (b'a', F, blob3.id)),
367
              TreeChange(CHANGE_MODIFY, (b'a', F, blob2.id), (b'a', F, blob3.id))]],
368
            [parent1, parent2], merge)
369

    
370
    def test_tree_changes_for_merge_modify_no_conflict(self):
371
        blob1 = make_object(Blob, data=b'1')
372
        blob2 = make_object(Blob, data=b'2')
373
        parent1 = self.commit_tree([(b'a', blob1)])
374
        parent2 = merge = self.commit_tree([(b'a', blob2)])
375
        self.assertChangesForMergeEqual([], [parent1, parent2], merge)
376

    
377
    def test_tree_changes_for_merge_delete_delete_conflict(self):
378
        blob1 = make_object(Blob, data=b'1')
379
        blob2 = make_object(Blob, data=b'2')
380
        parent1 = self.commit_tree([(b'a', blob1)])
381
        parent2 = self.commit_tree([(b'a', blob2)])
382
        merge = self.commit_tree([])
383
        self.assertChangesForMergeEqual(
384
            [[TreeChange.delete((b'a', F, blob1.id)),
385
              TreeChange.delete((b'a', F, blob2.id))]],
386
            [parent1, parent2], merge)
387

    
388
    def test_tree_changes_for_merge_delete_no_conflict(self):
389
        blob = make_object(Blob, data=b'blob')
390
        has = self.commit_tree([(b'a', blob)])
391
        doesnt_have = self.commit_tree([])
392
        self.assertChangesForMergeEqual([], [has, has], doesnt_have)
393
        self.assertChangesForMergeEqual([], [has, doesnt_have], doesnt_have)
394

    
395
    def test_tree_changes_for_merge_octopus_no_conflict(self):
396
        r = list(range(5))
397
        blobs = [make_object(Blob, data=bytes(i)) for i in r]
398
        parents = [self.commit_tree([(b'a', blobs[i])]) for i in r]
399
        for i in r:
400
            # Take the SHA from each of the parents.
401
            self.assertChangesForMergeEqual([], parents, parents[i])
402

    
403
    def test_tree_changes_for_merge_octopus_modify_conflict(self):
404
        # Because the octopus merge strategy is limited, I doubt it's possible
405
        # to create this with the git command line. But the output is well-
406
        # defined, so test it anyway.
407
        r = list(range(5))
408
        parent_blobs = [make_object(Blob, data=bytes(i)) for i in r]
409
        merge_blob = make_object(Blob, data=b'merge')
410
        parents = [self.commit_tree([(b'a', parent_blobs[i])]) for i in r]
411
        merge = self.commit_tree([(b'a', merge_blob)])
412
        expected = [[TreeChange(CHANGE_MODIFY, (b'a', F, parent_blobs[i].id),
413
                                (b'a', F, merge_blob.id)) for i in r]]
414
        self.assertChangesForMergeEqual(expected, parents, merge)
415

    
416
    def test_tree_changes_for_merge_octopus_delete(self):
417
        blob1 = make_object(Blob, data=b'1')
418
        blob2 = make_object(Blob, data=b'3')
419
        parent1 = self.commit_tree([(b'a', blob1)])
420
        parent2 = self.commit_tree([(b'a', blob2)])
421
        parent3 = merge = self.commit_tree([])
422
        self.assertChangesForMergeEqual([], [parent1, parent1, parent1], merge)
423
        self.assertChangesForMergeEqual([], [parent1, parent1, parent3], merge)
424
        self.assertChangesForMergeEqual([], [parent1, parent3, parent3], merge)
425
        self.assertChangesForMergeEqual(
426
            [[TreeChange.delete((b'a', F, blob1.id)),
427
              TreeChange.delete((b'a', F, blob2.id)),
428
              None]],
429
            [parent1, parent2, parent3], merge)
430

    
431
    def test_tree_changes_for_merge_add_add_same_conflict(self):
432
        blob = make_object(Blob, data=b'a\nb\nc\nd\n')
433
        parent1 = self.commit_tree([(b'a', blob)])
434
        parent2 = self.commit_tree([])
435
        merge = self.commit_tree([(b'b', blob)])
436
        add = TreeChange.add((b'b', F, blob.id))
437
        self.assertChangesForMergeEqual([[add, add]], [parent1, parent2], merge)
438

    
439
    def test_tree_changes_for_merge_add_exact_rename_conflict(self):
440
        blob = make_object(Blob, data=b'a\nb\nc\nd\n')
441
        parent1 = self.commit_tree([(b'a', blob)])
442
        parent2 = self.commit_tree([])
443
        merge = self.commit_tree([(b'b', blob)])
444
        self.assertChangesForMergeEqual(
445
            [[TreeChange(CHANGE_RENAME, (b'a', F, blob.id), (b'b', F, blob.id)),
446
              TreeChange.add((b'b', F, blob.id))]],
447
            [parent1, parent2], merge, rename_detector=self.detector)
448

    
449
    def test_tree_changes_for_merge_add_content_rename_conflict(self):
450
        blob1 = make_object(Blob, data=b'a\nb\nc\nd\n')
451
        blob2 = make_object(Blob, data=b'a\nb\nc\ne\n')
452
        parent1 = self.commit_tree([(b'a', blob1)])
453
        parent2 = self.commit_tree([])
454
        merge = self.commit_tree([(b'b', blob2)])
455
        self.assertChangesForMergeEqual(
456
            [[TreeChange(CHANGE_RENAME, (b'a', F, blob1.id), (b'b', F, blob2.id)),
457
              TreeChange.add((b'b', F, blob2.id))]],
458
            [parent1, parent2], merge, rename_detector=self.detector)
459

    
460
    def test_tree_changes_for_merge_modify_rename_conflict(self):
461
        blob1 = make_object(Blob, data=b'a\nb\nc\nd\n')
462
        blob2 = make_object(Blob, data=b'a\nb\nc\ne\n')
463
        parent1 = self.commit_tree([(b'a', blob1)])
464
        parent2 = self.commit_tree([(b'b', blob1)])
465
        merge = self.commit_tree([(b'b', blob2)])
466
        self.assertChangesForMergeEqual(
467
            [[TreeChange(CHANGE_RENAME, (b'a', F, blob1.id), (b'b', F, blob2.id)),
468
              TreeChange(CHANGE_MODIFY, (b'b', F, blob1.id), (b'b', F, blob2.id))]],
469
            [parent1, parent2], merge, rename_detector=self.detector)
470

    
471

    
472
class RenameDetectionTest(DiffTestCase):
473

    
474
    def _do_test_count_blocks(self, count_blocks):
475
        blob = make_object(Blob, data=b'a\nb\na\n')
476
        self.assertEqual({hash(b'a\n'): 4, hash(b'b\n'): 2}, count_blocks(blob))
477

    
478
    test_count_blocks = functest_builder(_do_test_count_blocks,
479
                                         _count_blocks_py)
480
    test_count_blocks_extension = ext_functest_builder(_do_test_count_blocks,
481
                                                       _count_blocks)
482

    
483
    def _do_test_count_blocks_no_newline(self, count_blocks):
484
        blob = make_object(Blob, data=b'a\na')
485
        self.assertEqual({hash(b'a\n'): 2, hash(b'a'): 1}, _count_blocks(blob))
486

    
487
    test_count_blocks_no_newline = functest_builder(
488
        _do_test_count_blocks_no_newline, _count_blocks_py)
489
    test_count_blocks_no_newline_extension = ext_functest_builder(
490
        _do_test_count_blocks_no_newline, _count_blocks)
491

    
492
    def _do_test_count_blocks_chunks(self, count_blocks):
493
        blob = ShaFile.from_raw_chunks(Blob.type_num, [b'a\nb', b'\na\n'])
494
        self.assertEqual({hash(b'a\n'): 4, hash(b'b\n'): 2}, _count_blocks(blob))
495

    
496
    test_count_blocks_chunks = functest_builder(_do_test_count_blocks_chunks,
497
                                                _count_blocks_py)
498
    test_count_blocks_chunks_extension = ext_functest_builder(
499
        _do_test_count_blocks_chunks, _count_blocks)
500

    
501
    def _do_test_count_blocks_long_lines(self, count_blocks):
502
        a = b'a' * 64
503
        data = a + b'xxx\ny\n' + a + b'zzz\n'
504
        blob = make_object(Blob, data=data)
505
        self.assertEqual({hash(b'a' * 64): 128, hash(b'xxx\n'): 4, hash(b'y\n'): 2,
506
                          hash(b'zzz\n'): 4},
507
                         _count_blocks(blob))
508

    
509
    test_count_blocks_long_lines = functest_builder(
510
        _do_test_count_blocks_long_lines, _count_blocks_py)
511
    test_count_blocks_long_lines_extension = ext_functest_builder(
512
        _do_test_count_blocks_long_lines, _count_blocks)
513

    
514
    def assertSimilar(self, expected_score, blob1, blob2):
515
        self.assertEqual(expected_score, _similarity_score(blob1, blob2))
516
        self.assertEqual(expected_score, _similarity_score(blob2, blob1))
517

    
518
    def test_similarity_score(self):
519
        blob0 = make_object(Blob, data=b'')
520
        blob1 = make_object(Blob, data=b'ab\ncd\ncd\n')
521
        blob2 = make_object(Blob, data=b'ab\n')
522
        blob3 = make_object(Blob, data=b'cd\n')
523
        blob4 = make_object(Blob, data=b'cd\ncd\n')
524

    
525
        self.assertSimilar(100, blob0, blob0)
526
        self.assertSimilar(0, blob0, blob1)
527
        self.assertSimilar(33, blob1, blob2)
528
        self.assertSimilar(33, blob1, blob3)
529
        self.assertSimilar(66, blob1, blob4)
530
        self.assertSimilar(0, blob2, blob3)
531
        self.assertSimilar(50, blob3, blob4)
532

    
533
    def test_similarity_score_cache(self):
534
        blob1 = make_object(Blob, data=b'ab\ncd\n')
535
        blob2 = make_object(Blob, data=b'ab\n')
536

    
537
        block_cache = {}
538
        self.assertEqual(
539
            50, _similarity_score(blob1, blob2, block_cache=block_cache))
540
        self.assertEqual(set([blob1.id, blob2.id]), set(block_cache))
541

    
542
        def fail_chunks():
543
            self.fail('Unexpected call to as_raw_chunks()')
544

    
545
        blob1.as_raw_chunks = blob2.as_raw_chunks = fail_chunks
546
        blob1.raw_length = lambda: 6
547
        blob2.raw_length = lambda: 3
548
        self.assertEqual(
549
            50, _similarity_score(blob1, blob2, block_cache=block_cache))
550

    
551
    def test_tree_entry_sort(self):
552
        sha = 'abcd' * 10
553
        expected_entries = [
554
            TreeChange.add(TreeEntry(b'aaa', F, sha)),
555
            TreeChange(CHANGE_COPY, TreeEntry(b'bbb', F, sha),
556
                       TreeEntry(b'aab', F, sha)),
557
            TreeChange(CHANGE_MODIFY, TreeEntry(b'bbb', F, sha),
558
                       TreeEntry(b'bbb', F, b'dabc' * 10)),
559
            TreeChange(CHANGE_RENAME, TreeEntry(b'bbc', F, sha),
560
                       TreeEntry(b'ddd', F, sha)),
561
            TreeChange.delete(TreeEntry(b'ccc', F, sha)),
562
        ]
563

    
564
        for perm in permutations(expected_entries):
565
            self.assertEqual(expected_entries,
566
                             sorted(perm, key=_tree_change_key))
567

    
568
    def detect_renames(self, tree1, tree2, want_unchanged=False, **kwargs):
569
        detector = RenameDetector(self.store, **kwargs)
570
        return detector.changes_with_renames(tree1.id, tree2.id,
571
                                             want_unchanged=want_unchanged)
572

    
573
    def test_no_renames(self):
574
        blob1 = make_object(Blob, data=b'a\nb\nc\nd\n')
575
        blob2 = make_object(Blob, data=b'a\nb\ne\nf\n')
576
        blob3 = make_object(Blob, data=b'a\nb\ng\nh\n')
577
        tree1 = self.commit_tree([(b'a', blob1), (b'b', blob2)])
578
        tree2 = self.commit_tree([(b'a', blob1), (b'b', blob3)])
579
        self.assertEqual(
580
            [TreeChange(CHANGE_MODIFY, (b'b', F, blob2.id), (b'b', F, blob3.id))],
581
            self.detect_renames(tree1, tree2))
582

    
583
    def test_exact_rename_one_to_one(self):
584
        blob1 = make_object(Blob, data=b'1')
585
        blob2 = make_object(Blob, data=b'2')
586
        tree1 = self.commit_tree([(b'a', blob1), (b'b', blob2)])
587
        tree2 = self.commit_tree([(b'c', blob1), (b'd', blob2)])
588
        self.assertEqual(
589
            [TreeChange(CHANGE_RENAME, (b'a', F, blob1.id), (b'c', F, blob1.id)),
590
             TreeChange(CHANGE_RENAME, (b'b', F, blob2.id), (b'd', F, blob2.id))],
591
            self.detect_renames(tree1, tree2))
592

    
593
    def test_exact_rename_split_different_type(self):
594
        blob = make_object(Blob, data=b'/foo')
595
        tree1 = self.commit_tree([(b'a', blob, 0o100644)])
596
        tree2 = self.commit_tree([(b'a', blob, 0o120000)])
597
        self.assertEqual(
598
            [TreeChange.add((b'a', 0o120000, blob.id)),
599
             TreeChange.delete((b'a', 0o100644, blob.id))],
600
            self.detect_renames(tree1, tree2))
601

    
602
    def test_exact_rename_and_different_type(self):
603
        blob1 = make_object(Blob, data=b'1')
604
        blob2 = make_object(Blob, data=b'2')
605
        tree1 = self.commit_tree([(b'a', blob1)])
606
        tree2 = self.commit_tree([(b'a', blob2, 0o120000), (b'b', blob1)])
607
        self.assertEqual(
608
            [TreeChange.add((b'a', 0o120000, blob2.id)),
609
             TreeChange(CHANGE_RENAME, (b'a', F, blob1.id), (b'b', F, blob1.id))],
610
            self.detect_renames(tree1, tree2))
611

    
612
    def test_exact_rename_one_to_many(self):
613
        blob = make_object(Blob, data=b'1')
614
        tree1 = self.commit_tree([(b'a', blob)])
615
        tree2 = self.commit_tree([(b'b', blob), (b'c', blob)])
616
        self.assertEqual(
617
            [TreeChange(CHANGE_RENAME, (b'a', F, blob.id), (b'b', F, blob.id)),
618
             TreeChange(CHANGE_COPY, (b'a', F, blob.id), (b'c', F, blob.id))],
619
            self.detect_renames(tree1, tree2))
620

    
621
    def test_exact_rename_many_to_one(self):
622
        blob = make_object(Blob, data=b'1')
623
        tree1 = self.commit_tree([(b'a', blob), (b'b', blob)])
624
        tree2 = self.commit_tree([(b'c', blob)])
625
        self.assertEqual(
626
            [TreeChange(CHANGE_RENAME, (b'a', F, blob.id), (b'c', F, blob.id)),
627
             TreeChange.delete((b'b', F, blob.id))],
628
            self.detect_renames(tree1, tree2))
629

    
630
    def test_exact_rename_many_to_many(self):
631
        blob = make_object(Blob, data=b'1')
632
        tree1 = self.commit_tree([(b'a', blob), (b'b', blob)])
633
        tree2 = self.commit_tree([(b'c', blob), (b'd', blob), (b'e', blob)])
634
        self.assertEqual(
635
            [TreeChange(CHANGE_RENAME, (b'a', F, blob.id), (b'c', F, blob.id)),
636
             TreeChange(CHANGE_COPY, (b'a', F, blob.id), (b'e', F, blob.id)),
637
             TreeChange(CHANGE_RENAME, (b'b', F, blob.id), (b'd', F, blob.id))],
638
            self.detect_renames(tree1, tree2))
639

    
640
    def test_exact_copy_modify(self):
641
        blob1 = make_object(Blob, data=b'a\nb\nc\nd\n')
642
        blob2 = make_object(Blob, data=b'a\nb\nc\ne\n')
643
        tree1 = self.commit_tree([(b'a', blob1)])
644
        tree2 = self.commit_tree([(b'a', blob2), (b'b', blob1)])
645
        self.assertEqual(
646
            [TreeChange(CHANGE_MODIFY, (b'a', F, blob1.id), (b'a', F, blob2.id)),
647
             TreeChange(CHANGE_COPY, (b'a', F, blob1.id), (b'b', F, blob1.id))],
648
            self.detect_renames(tree1, tree2))
649

    
650
    def test_exact_copy_change_mode(self):
651
        blob = make_object(Blob, data=b'a\nb\nc\nd\n')
652
        tree1 = self.commit_tree([(b'a', blob)])
653
        tree2 = self.commit_tree([(b'a', blob, 0o100755), (b'b', blob)])
654
        self.assertEqual(
655
            [TreeChange(CHANGE_MODIFY, (b'a', F, blob.id),
656
                        (b'a', 0o100755, blob.id)),
657
             TreeChange(CHANGE_COPY, (b'a', F, blob.id), (b'b', F, blob.id))],
658
            self.detect_renames(tree1, tree2))
659

    
660
    def test_rename_threshold(self):
661
        blob1 = make_object(Blob, data=b'a\nb\nc\n')
662
        blob2 = make_object(Blob, data=b'a\nb\nd\n')
663
        tree1 = self.commit_tree([(b'a', blob1)])
664
        tree2 = self.commit_tree([(b'b', blob2)])
665
        self.assertEqual(
666
            [TreeChange(CHANGE_RENAME, (b'a', F, blob1.id), (b'b', F, blob2.id))],
667
            self.detect_renames(tree1, tree2, rename_threshold=50))
668
        self.assertEqual(
669
            [TreeChange.delete((b'a', F, blob1.id)),
670
             TreeChange.add((b'b', F, blob2.id))],
671
            self.detect_renames(tree1, tree2, rename_threshold=75))
672

    
673
    def test_content_rename_max_files(self):
674
        blob1 = make_object(Blob, data=b'a\nb\nc\nd')
675
        blob4 = make_object(Blob, data=b'a\nb\nc\ne\n')
676
        blob2 = make_object(Blob, data=b'e\nf\ng\nh\n')
677
        blob3 = make_object(Blob, data=b'e\nf\ng\ni\n')
678
        tree1 = self.commit_tree([(b'a', blob1), (b'b', blob2)])
679
        tree2 = self.commit_tree([(b'c', blob3), (b'd', blob4)])
680
        self.assertEqual(
681
            [TreeChange(CHANGE_RENAME, (b'a', F, blob1.id), (b'd', F, blob4.id)),
682
             TreeChange(CHANGE_RENAME, (b'b', F, blob2.id), (b'c', F, blob3.id))],
683
            self.detect_renames(tree1, tree2))
684
        self.assertEqual(
685
            [TreeChange.delete((b'a', F, blob1.id)),
686
             TreeChange.delete((b'b', F, blob2.id)),
687
             TreeChange.add((b'c', F, blob3.id)),
688
             TreeChange.add((b'd', F, blob4.id))],
689
            self.detect_renames(tree1, tree2, max_files=1))
690

    
691
    def test_content_rename_one_to_one(self):
692
        b11 = make_object(Blob, data=b'a\nb\nc\nd\n')
693
        b12 = make_object(Blob, data=b'a\nb\nc\ne\n')
694
        b21 = make_object(Blob, data=b'e\nf\ng\n\h')
695
        b22 = make_object(Blob, data=b'e\nf\ng\n\i')
696
        tree1 = self.commit_tree([(b'a', b11), (b'b', b21)])
697
        tree2 = self.commit_tree([(b'c', b12), (b'd', b22)])
698
        self.assertEqual(
699
            [TreeChange(CHANGE_RENAME, (b'a', F, b11.id), (b'c', F, b12.id)),
700
             TreeChange(CHANGE_RENAME, (b'b', F, b21.id), (b'd', F, b22.id))],
701
            self.detect_renames(tree1, tree2))
702

    
703
    def test_content_rename_one_to_one_ordering(self):
704
        blob1 = make_object(Blob, data=b'a\nb\nc\nd\ne\nf\n')
705
        blob2 = make_object(Blob, data=b'a\nb\nc\nd\ng\nh\n')
706
        # 6/10 match to blob1, 8/10 match to blob2
707
        blob3 = make_object(Blob, data=b'a\nb\nc\nd\ng\ni\n')
708
        tree1 = self.commit_tree([(b'a', blob1), (b'b', blob2)])
709
        tree2 = self.commit_tree([(b'c', blob3)])
710
        self.assertEqual(
711
            [TreeChange.delete((b'a', F, blob1.id)),
712
             TreeChange(CHANGE_RENAME, (b'b', F, blob2.id), (b'c', F, blob3.id))],
713
            self.detect_renames(tree1, tree2))
714

    
715
        tree3 = self.commit_tree([(b'a', blob2), (b'b', blob1)])
716
        tree4 = self.commit_tree([(b'c', blob3)])
717
        self.assertEqual(
718
            [TreeChange(CHANGE_RENAME, (b'a', F, blob2.id), (b'c', F, blob3.id)),
719
             TreeChange.delete((b'b', F, blob1.id))],
720
            self.detect_renames(tree3, tree4))
721

    
722
    def test_content_rename_one_to_many(self):
723
        blob1 = make_object(Blob, data=b'aa\nb\nc\nd\ne\n')
724
        blob2 = make_object(Blob, data=b'ab\nb\nc\nd\ne\n')  # 8/11 match
725
        blob3 = make_object(Blob, data=b'aa\nb\nc\nd\nf\n')  # 9/11 match
726
        tree1 = self.commit_tree([(b'a', blob1)])
727
        tree2 = self.commit_tree([(b'b', blob2), (b'c', blob3)])
728
        self.assertEqual(
729
            [TreeChange(CHANGE_COPY, (b'a', F, blob1.id), (b'b', F, blob2.id)),
730
             TreeChange(CHANGE_RENAME, (b'a', F, blob1.id), (b'c', F, blob3.id))],
731
            self.detect_renames(tree1, tree2))
732

    
733
    def test_content_rename_many_to_one(self):
734
        blob1 = make_object(Blob, data=b'a\nb\nc\nd\n')
735
        blob2 = make_object(Blob, data=b'a\nb\nc\ne\n')
736
        blob3 = make_object(Blob, data=b'a\nb\nc\nf\n')
737
        tree1 = self.commit_tree([(b'a', blob1), (b'b', blob2)])
738
        tree2 = self.commit_tree([(b'c', blob3)])
739
        self.assertEqual(
740
            [TreeChange(CHANGE_RENAME, (b'a', F, blob1.id), (b'c', F, blob3.id)),
741
             TreeChange.delete((b'b', F, blob2.id))],
742
            self.detect_renames(tree1, tree2))
743

    
744
    def test_content_rename_many_to_many(self):
745
        blob1 = make_object(Blob, data=b'a\nb\nc\nd\n')
746
        blob2 = make_object(Blob, data=b'a\nb\nc\ne\n')
747
        blob3 = make_object(Blob, data=b'a\nb\nc\nf\n')
748
        blob4 = make_object(Blob, data=b'a\nb\nc\ng\n')
749
        tree1 = self.commit_tree([(b'a', blob1), (b'b', blob2)])
750
        tree2 = self.commit_tree([(b'c', blob3), (b'd', blob4)])
751
        # TODO(dborowitz): Distribute renames rather than greedily choosing
752
        # copies.
753
        self.assertEqual(
754
            [TreeChange(CHANGE_RENAME, (b'a', F, blob1.id), (b'c', F, blob3.id)),
755
             TreeChange(CHANGE_COPY, (b'a', F, blob1.id), (b'd', F, blob4.id)),
756
             TreeChange.delete((b'b', F, blob2.id))],
757
            self.detect_renames(tree1, tree2))
758

    
759
    def test_content_rename_with_more_deletions(self):
760
        blob1 = make_object(Blob, data=b'')
761
        tree1 = self.commit_tree([(b'a', blob1), (b'b', blob1), (b'c', blob1),
762
                                  (b'd', blob1)])
763
        tree2 = self.commit_tree([(b'e', blob1), (b'f', blob1), (b'g', blob1)])
764
        self.maxDiff = None
765
        self.assertEqual(
766
          [TreeChange(CHANGE_RENAME, (b'a', F, blob1.id), (b'e', F, blob1.id)),
767
           TreeChange(CHANGE_RENAME, (b'b', F, blob1.id), (b'f', F, blob1.id)),
768
           TreeChange(CHANGE_RENAME, (b'c', F, blob1.id), (b'g', F, blob1.id)),
769
           TreeChange.delete((b'd', F, blob1.id))],
770
          self.detect_renames(tree1, tree2))
771

    
772
    def test_content_rename_gitlink(self):
773
        blob1 = make_object(Blob, data=b'blob1')
774
        blob2 = make_object(Blob, data=b'blob2')
775
        link1 = b'1' * 40
776
        link2 = b'2' * 40
777
        tree1 = self.commit_tree([(b'a', blob1), (b'b', link1, 0o160000)])
778
        tree2 = self.commit_tree([(b'c', blob2), (b'd', link2, 0o160000)])
779
        self.assertEqual(
780
            [TreeChange.delete((b'a', 0o100644, blob1.id)),
781
             TreeChange.delete((b'b', 0o160000, link1)),
782
             TreeChange.add((b'c', 0o100644, blob2.id)),
783
             TreeChange.add((b'd', 0o160000, link2))],
784
            self.detect_renames(tree1, tree2))
785

    
786
    def test_exact_rename_swap(self):
787
        blob1 = make_object(Blob, data=b'1')
788
        blob2 = make_object(Blob, data=b'2')
789
        tree1 = self.commit_tree([(b'a', blob1), (b'b', blob2)])
790
        tree2 = self.commit_tree([(b'a', blob2), (b'b', blob1)])
791
        self.assertEqual(
792
            [TreeChange(CHANGE_MODIFY, (b'a', F, blob1.id), (b'a', F, blob2.id)),
793
             TreeChange(CHANGE_MODIFY, (b'b', F, blob2.id), (b'b', F, blob1.id))],
794
            self.detect_renames(tree1, tree2))
795
        self.assertEqual(
796
            [TreeChange(CHANGE_RENAME, (b'a', F, blob1.id), (b'b', F, blob1.id)),
797
             TreeChange(CHANGE_RENAME, (b'b', F, blob2.id), (b'a', F, blob2.id))],
798
            self.detect_renames(tree1, tree2, rewrite_threshold=50))
799

    
800
    def test_content_rename_swap(self):
801
        blob1 = make_object(Blob, data=b'a\nb\nc\nd\n')
802
        blob2 = make_object(Blob, data=b'e\nf\ng\nh\n')
803
        blob3 = make_object(Blob, data=b'a\nb\nc\ne\n')
804
        blob4 = make_object(Blob, data=b'e\nf\ng\ni\n')
805
        tree1 = self.commit_tree([(b'a', blob1), (b'b', blob2)])
806
        tree2 = self.commit_tree([(b'a', blob4), (b'b', blob3)])
807
        self.assertEqual(
808
            [TreeChange(CHANGE_RENAME, (b'a', F, blob1.id), (b'b', F, blob3.id)),
809
             TreeChange(CHANGE_RENAME, (b'b', F, blob2.id), (b'a', F, blob4.id))],
810
            self.detect_renames(tree1, tree2, rewrite_threshold=60))
811

    
812
    def test_rewrite_threshold(self):
813
        blob1 = make_object(Blob, data=b'a\nb\nc\nd\n')
814
        blob2 = make_object(Blob, data=b'a\nb\nc\ne\n')
815
        blob3 = make_object(Blob, data=b'a\nb\nf\ng\n')
816

    
817
        tree1 = self.commit_tree([(b'a', blob1)])
818
        tree2 = self.commit_tree([(b'a', blob3), (b'b', blob2)])
819

    
820
        no_renames = [
821
            TreeChange(CHANGE_MODIFY, (b'a', F, blob1.id), (b'a', F, blob3.id)),
822
            TreeChange(CHANGE_COPY, (b'a', F, blob1.id), (b'b', F, blob2.id))]
823
        self.assertEqual(
824
            no_renames, self.detect_renames(tree1, tree2))
825
        self.assertEqual(
826
            no_renames, self.detect_renames(tree1, tree2, rewrite_threshold=40))
827
        self.assertEqual(
828
            [TreeChange.add((b'a', F, blob3.id)),
829
             TreeChange(CHANGE_RENAME, (b'a', F, blob1.id), (b'b', F, blob2.id))],
830
            self.detect_renames(tree1, tree2, rewrite_threshold=80))
831

    
832
    def test_find_copies_harder_exact(self):
833
        blob = make_object(Blob, data=b'blob')
834
        tree1 = self.commit_tree([(b'a', blob)])
835
        tree2 = self.commit_tree([(b'a', blob), (b'b', blob)])
836
        self.assertEqual([TreeChange.add((b'b', F, blob.id))],
837
                         self.detect_renames(tree1, tree2))
838
        self.assertEqual(
839
            [TreeChange(CHANGE_COPY, (b'a', F, blob.id), (b'b', F, blob.id))],
840
            self.detect_renames(tree1, tree2, find_copies_harder=True))
841

    
842
    def test_find_copies_harder_content(self):
843
        blob1 = make_object(Blob, data=b'a\nb\nc\nd\n')
844
        blob2 = make_object(Blob, data=b'a\nb\nc\ne\n')
845
        tree1 = self.commit_tree([(b'a', blob1)])
846
        tree2 = self.commit_tree([(b'a', blob1), (b'b', blob2)])
847
        self.assertEqual([TreeChange.add((b'b', F, blob2.id))],
848
                         self.detect_renames(tree1, tree2))
849
        self.assertEqual(
850
            [TreeChange(CHANGE_COPY, (b'a', F, blob1.id), (b'b', F, blob2.id))],
851
            self.detect_renames(tree1, tree2, find_copies_harder=True))
852

    
853
    def test_find_copies_harder_with_rewrites(self):
854
        blob_a1 = make_object(Blob, data=b'a\nb\nc\nd\n')
855
        blob_a2 = make_object(Blob, data=b'f\ng\nh\ni\n')
856
        blob_b2 = make_object(Blob, data=b'a\nb\nc\ne\n')
857
        tree1 = self.commit_tree([(b'a', blob_a1)])
858
        tree2 = self.commit_tree([(b'a', blob_a2), (b'b', blob_b2)])
859
        self.assertEqual(
860
            [TreeChange(CHANGE_MODIFY, (b'a', F, blob_a1.id),
861
                        (b'a', F, blob_a2.id)),
862
             TreeChange(CHANGE_COPY, (b'a', F, blob_a1.id), (b'b', F, blob_b2.id))],
863
            self.detect_renames(tree1, tree2, find_copies_harder=True))
864
        self.assertEqual(
865
            [TreeChange.add((b'a', F, blob_a2.id)),
866
             TreeChange(CHANGE_RENAME, (b'a', F, blob_a1.id),
867
                        (b'b', F, blob_b2.id))],
868
            self.detect_renames(tree1, tree2, rewrite_threshold=50,
869
                                find_copies_harder=True))
870

    
871
    def test_reuse_detector(self):
872
        blob = make_object(Blob, data=b'blob')
873
        tree1 = self.commit_tree([(b'a', blob)])
874
        tree2 = self.commit_tree([(b'b', blob)])
875
        detector = RenameDetector(self.store)
876
        changes = [TreeChange(CHANGE_RENAME, (b'a', F, blob.id),
877
                              (b'b', F, blob.id))]
878
        self.assertEqual(changes,
879
                         detector.changes_with_renames(tree1.id, tree2.id))
880
        self.assertEqual(changes,
881
                         detector.changes_with_renames(tree1.id, tree2.id))
882

    
883
    def test_want_unchanged(self):
884
        blob_a1 = make_object(Blob, data=b'a\nb\nc\nd\n')
885
        blob_b = make_object(Blob, data=b'b')
886
        blob_c2 = make_object(Blob, data=b'a\nb\nc\ne\n')
887
        tree1 = self.commit_tree([(b'a', blob_a1), (b'b', blob_b)])
888
        tree2 = self.commit_tree([(b'c', blob_c2), (b'b', blob_b)])
889
        self.assertEqual(
890
            [TreeChange(CHANGE_RENAME, (b'a', F, blob_a1.id),
891
                        (b'c', F, blob_c2.id))],
892
            self.detect_renames(tree1, tree2))
893
        self.assertEqual(
894
            [TreeChange(CHANGE_RENAME, (b'a', F, blob_a1.id),
895
                        (b'c', F, blob_c2.id)),
896
             TreeChange(CHANGE_UNCHANGED, (b'b', F, blob_b.id),
897
                        (b'b', F, blob_b.id))],
898
            self.detect_renames(tree1, tree2, want_unchanged=True))