# test_diff_tree.py -- Tests for file and tree diff utilities.
# Copyright (C) 2010 Google, Inc.
#
# Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
# General Public License as public by the Free Software Foundation; version 2.0
# or (at your option) any later version. You can redistribute it and/or
# modify it under the terms of either of these two licenses.
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# You should have received a copy of the licenses; if not, see
# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
# License, Version 2.0.
#
"""Tests for file and tree diff utilities."""
from itertools import permutations
from dulwich.diff_tree import (
CHANGE_MODIFY,
CHANGE_RENAME,
CHANGE_COPY,
CHANGE_UNCHANGED,
TreeChange,
_merge_entries,
_merge_entries_py,
tree_changes,
tree_changes_for_merge,
_count_blocks,
_count_blocks_py,
_similarity_score,
_tree_change_key,
RenameDetector,
_is_tree,
_is_tree_py
)
from dulwich.index import (
commit_tree,
)
from dulwich.object_store import (
MemoryObjectStore,
)
from dulwich.objects import (
ShaFile,
Blob,
TreeEntry,
Tree,
)
from dulwich.tests import (
TestCase,
)
from dulwich.tests.utils import (
F,
make_object,
functest_builder,
ext_functest_builder,
)
class DiffTestCase(TestCase):
def setUp(self):
super(DiffTestCase, self).setUp()
self.store = MemoryObjectStore()
self.empty_tree = self.commit_tree([])
def commit_tree(self, entries):
commit_blobs = []
for entry in entries:
if len(entry) == 2:
path, obj = entry
mode = F
else:
path, obj, mode = entry
if isinstance(obj, Blob):
self.store.add_object(obj)
sha = obj.id
else:
sha = obj
commit_blobs.append((path, sha, mode))
return self.store[commit_tree(self.store, commit_blobs)]
class TreeChangesTest(DiffTestCase):
def setUp(self):
super(TreeChangesTest, self).setUp()
self.detector = RenameDetector(self.store)
def assertMergeFails(self, merge_entries, name, mode, sha):
t = Tree()
t[name] = (mode, sha)
self.assertRaises((TypeError, ValueError), merge_entries, '', t, t)
def _do_test_merge_entries(self, merge_entries):
blob_a1 = make_object(Blob, data=b'a1')
blob_a2 = make_object(Blob, data=b'a2')
blob_b1 = make_object(Blob, data=b'b1')
blob_c2 = make_object(Blob, data=b'c2')
tree1 = self.commit_tree([(b'a', blob_a1, 0o100644),
(b'b', blob_b1, 0o100755)])
tree2 = self.commit_tree([(b'a', blob_a2, 0o100644),
(b'c', blob_c2, 0o100755)])
self.assertEqual([], merge_entries(b'', self.empty_tree,
self.empty_tree))
self.assertEqual(
[((None, None, None), (b'a', 0o100644, blob_a1.id)),
((None, None, None), (b'b', 0o100755, blob_b1.id)), ],
merge_entries(b'', self.empty_tree, tree1))
self.assertEqual(
[((None, None, None), (b'x/a', 0o100644, blob_a1.id)),
((None, None, None), (b'x/b', 0o100755, blob_b1.id)), ],
merge_entries(b'x', self.empty_tree, tree1))
self.assertEqual(
[((b'a', 0o100644, blob_a2.id), (None, None, None)),
((b'c', 0o100755, blob_c2.id), (None, None, None)), ],
merge_entries(b'', tree2, self.empty_tree))
self.assertEqual(
[((b'a', 0o100644, blob_a1.id), (b'a', 0o100644, blob_a2.id)),
((b'b', 0o100755, blob_b1.id), (None, None, None)),
((None, None, None), (b'c', 0o100755, blob_c2.id)), ],
merge_entries(b'', tree1, tree2))
self.assertEqual(
[((b'a', 0o100644, blob_a2.id), (b'a', 0o100644, blob_a1.id)),
((None, None, None), (b'b', 0o100755, blob_b1.id)),
((b'c', 0o100755, blob_c2.id), (None, None, None)), ],
merge_entries(b'', tree2, tree1))
self.assertMergeFails(merge_entries, 0xdeadbeef, 0o100644, '1' * 40)
self.assertMergeFails(merge_entries, b'a', b'deadbeef', '1' * 40)
self.assertMergeFails(merge_entries, b'a', 0o100644, 0xdeadbeef)
test_merge_entries = functest_builder(_do_test_merge_entries,
_merge_entries_py)
test_merge_entries_extension = ext_functest_builder(_do_test_merge_entries,
_merge_entries)
def _do_test_is_tree(self, is_tree):
self.assertFalse(is_tree(TreeEntry(None, None, None)))
self.assertFalse(is_tree(TreeEntry(b'a', 0o100644, b'a' * 40)))
self.assertFalse(is_tree(TreeEntry(b'a', 0o100755, b'a' * 40)))
self.assertFalse(is_tree(TreeEntry(b'a', 0o120000, b'a' * 40)))
self.assertTrue(is_tree(TreeEntry(b'a', 0o040000, b'a' * 40)))
self.assertRaises(TypeError, is_tree, TreeEntry(b'a', b'x', b'a' * 40))
self.assertRaises(AttributeError, is_tree, 1234)
test_is_tree = functest_builder(_do_test_is_tree, _is_tree_py)
test_is_tree_extension = ext_functest_builder(_do_test_is_tree, _is_tree)
def assertChangesEqual(self, expected, tree1, tree2, **kwargs):
actual = list(tree_changes(self.store, tree1.id, tree2.id, **kwargs))
self.assertEqual(expected, actual)
# For brevity, the following tests use tuples instead of TreeEntry objects.
def test_tree_changes_empty(self):
self.assertChangesEqual([], self.empty_tree, self.empty_tree)
def test_tree_changes_no_changes(self):
blob = make_object(Blob, data=b'blob')
tree = self.commit_tree([(b'a', blob), (b'b/c', blob)])
self.assertChangesEqual([], self.empty_tree, self.empty_tree)
self.assertChangesEqual([], tree, tree)
self.assertChangesEqual(
[TreeChange(CHANGE_UNCHANGED, (b'a', F, blob.id),
(b'a', F, blob.id)),
TreeChange(CHANGE_UNCHANGED, (b'b/c', F, blob.id),
(b'b/c', F, blob.id))],
tree, tree, want_unchanged=True)
def test_tree_changes_add_delete(self):
blob_a = make_object(Blob, data=b'a')
blob_b = make_object(Blob, data=b'b')
tree = self.commit_tree([(b'a', blob_a, 0o100644),
(b'x/b', blob_b, 0o100755)])
self.assertChangesEqual(
[TreeChange.add((b'a', 0o100644, blob_a.id)),
TreeChange.add((b'x/b', 0o100755, blob_b.id))],
self.empty_tree, tree)
self.assertChangesEqual(
[TreeChange.delete((b'a', 0o100644, blob_a.id)),
TreeChange.delete((b'x/b', 0o100755, blob_b.id))],
tree, self.empty_tree)
def test_tree_changes_modify_contents(self):
blob_a1 = make_object(Blob, data=b'a1')
blob_a2 = make_object(Blob, data=b'a2')
tree1 = self.commit_tree([(b'a', blob_a1)])
tree2 = self.commit_tree([(b'a', blob_a2)])
self.assertChangesEqual(
[TreeChange(CHANGE_MODIFY, (b'a', F, blob_a1.id),
(b'a', F, blob_a2.id))],
tree1, tree2)
def test_tree_changes_modify_mode(self):
blob_a = make_object(Blob, data=b'a')
tree1 = self.commit_tree([(b'a', blob_a, 0o100644)])
tree2 = self.commit_tree([(b'a', blob_a, 0o100755)])
self.assertChangesEqual(
[TreeChange(CHANGE_MODIFY, (b'a', 0o100644, blob_a.id),
(b'a', 0o100755, blob_a.id))],
tree1, tree2)
def test_tree_changes_change_type(self):
blob_a1 = make_object(Blob, data=b'a')
blob_a2 = make_object(Blob, data=b'/foo/bar')
tree1 = self.commit_tree([(b'a', blob_a1, 0o100644)])
tree2 = self.commit_tree([(b'a', blob_a2, 0o120000)])
self.assertChangesEqual(
[TreeChange.delete((b'a', 0o100644, blob_a1.id)),
TreeChange.add((b'a', 0o120000, blob_a2.id))],
tree1, tree2)
def test_tree_changes_change_type_same(self):
blob_a1 = make_object(Blob, data=b'a')
blob_a2 = make_object(Blob, data=b'/foo/bar')
tree1 = self.commit_tree([(b'a', blob_a1, 0o100644)])
tree2 = self.commit_tree([(b'a', blob_a2, 0o120000)])
self.assertChangesEqual(
[TreeChange(CHANGE_MODIFY, (b'a', 0o100644, blob_a1.id),
(b'a', 0o120000, blob_a2.id))],
tree1, tree2, change_type_same=True)
def test_tree_changes_to_tree(self):
blob_a = make_object(Blob, data=b'a')
blob_x = make_object(Blob, data=b'x')
tree1 = self.commit_tree([(b'a', blob_a)])
tree2 = self.commit_tree([(b'a/x', blob_x)])
self.assertChangesEqual(
[TreeChange.delete((b'a', F, blob_a.id)),
TreeChange.add((b'a/x', F, blob_x.id))],
tree1, tree2)
def test_tree_changes_complex(self):
blob_a_1 = make_object(Blob, data=b'a1_1')
blob_bx1_1 = make_object(Blob, data=b'bx1_1')
blob_bx2_1 = make_object(Blob, data=b'bx2_1')
blob_by1_1 = make_object(Blob, data=b'by1_1')
blob_by2_1 = make_object(Blob, data=b'by2_1')
tree1 = self.commit_tree([
(b'a', blob_a_1),
(b'b/x/1', blob_bx1_1),
(b'b/x/2', blob_bx2_1),
(b'b/y/1', blob_by1_1),
(b'b/y/2', blob_by2_1),
])
blob_a_2 = make_object(Blob, data=b'a1_2')
blob_bx1_2 = blob_bx1_1
blob_by_2 = make_object(Blob, data=b'by_2')
blob_c_2 = make_object(Blob, data=b'c_2')
tree2 = self.commit_tree([
(b'a', blob_a_2),
(b'b/x/1', blob_bx1_2),
(b'b/y', blob_by_2),
(b'c', blob_c_2),
])
self.assertChangesEqual(
[TreeChange(CHANGE_MODIFY, (b'a', F, blob_a_1.id),
(b'a', F, blob_a_2.id)),
TreeChange.delete((b'b/x/2', F, blob_bx2_1.id)),
TreeChange.add((b'b/y', F, blob_by_2.id)),
TreeChange.delete((b'b/y/1', F, blob_by1_1.id)),
TreeChange.delete((b'b/y/2', F, blob_by2_1.id)),
TreeChange.add((b'c', F, blob_c_2.id))],
tree1, tree2)
def test_tree_changes_name_order(self):
blob = make_object(Blob, data=b'a')
tree1 = self.commit_tree([(b'a', blob), (b'a.', blob), (b'a..', blob)])
# Tree order is the reverse of this, so if we used tree order, 'a..'
# would not be merged.
tree2 = self.commit_tree(
[(b'a/x', blob), (b'a./x', blob), (b'a..', blob)])
self.assertChangesEqual(
[TreeChange.delete((b'a', F, blob.id)),
TreeChange.add((b'a/x', F, blob.id)),
TreeChange.delete((b'a.', F, blob.id)),
TreeChange.add((b'a./x', F, blob.id))],
tree1, tree2)
def test_tree_changes_prune(self):
blob_a1 = make_object(Blob, data=b'a1')
blob_a2 = make_object(Blob, data=b'a2')
blob_x = make_object(Blob, data=b'x')
tree1 = self.commit_tree([(b'a', blob_a1), (b'b/x', blob_x)])
tree2 = self.commit_tree([(b'a', blob_a2), (b'b/x', blob_x)])
# Remove identical items so lookups will fail unless we prune.
subtree = self.store[tree1[b'b'][1]]
for entry in subtree.items():
del self.store[entry.sha]
del self.store[subtree.id]
self.assertChangesEqual(
[TreeChange(CHANGE_MODIFY, (b'a', F, blob_a1.id),
(b'a', F, blob_a2.id))],
tree1, tree2)
def test_tree_changes_rename_detector(self):
blob_a1 = make_object(Blob, data=b'a\nb\nc\nd\n')
blob_a2 = make_object(Blob, data=b'a\nb\nc\ne\n')
blob_b = make_object(Blob, data=b'b')
tree1 = self.commit_tree([(b'a', blob_a1), (b'b', blob_b)])
tree2 = self.commit_tree([(b'c', blob_a2), (b'b', blob_b)])
detector = RenameDetector(self.store)
self.assertChangesEqual(
[TreeChange.delete((b'a', F, blob_a1.id)),
TreeChange.add((b'c', F, blob_a2.id))],
tree1, tree2)
self.assertChangesEqual(
[TreeChange.delete((b'a', F, blob_a1.id)),
TreeChange(CHANGE_UNCHANGED, (b'b', F, blob_b.id),
(b'b', F, blob_b.id)),
TreeChange.add((b'c', F, blob_a2.id))],
tree1, tree2, want_unchanged=True)
self.assertChangesEqual(
[TreeChange(CHANGE_RENAME, (b'a', F, blob_a1.id),
(b'c', F, blob_a2.id))],
tree1, tree2, rename_detector=detector)
self.assertChangesEqual(
[TreeChange(CHANGE_RENAME, (b'a', F, blob_a1.id),
(b'c', F, blob_a2.id)),
TreeChange(CHANGE_UNCHANGED, (b'b', F, blob_b.id),
(b'b', F, blob_b.id))],
tree1, tree2, rename_detector=detector, want_unchanged=True)
def assertChangesForMergeEqual(self, expected, parent_trees, merge_tree,
**kwargs):
parent_tree_ids = [t.id for t in parent_trees]
actual = list(tree_changes_for_merge(
self.store, parent_tree_ids, merge_tree.id, **kwargs))
self.assertEqual(expected, actual)
Loading ...