Răsfoiți Sursa

Add commit_tree_changes.

Jelmer Vernooij 7 ani în urmă
părinte
comite
51eb7e1d9a
3 a modificat fișierele cu 125 adăugiri și 0 ștergeri
  1. 3 0
      NEWS
  2. 48 0
      dulwich/object_store.py
  3. 74 0
      dulwich/tests/test_object_store.py

+ 3 - 0
NEWS

@@ -17,6 +17,9 @@
   * The server now supports providing symrefs.
     (Jelmer Vernooij, #485)
 
+  * Add ``dulwich.object_store.commit_tree_changes`` to incrementally
+    commit changes to a tree structure. (Jelmer Vernooij)
+
 0.18.2	2017-08-01
 
  TEST FIXES

+ 48 - 0
dulwich/object_store.py

@@ -1126,3 +1126,51 @@ class ObjectStoreGraphWalker(object):
         return None
 
     __next__ = next
+
+
+def commit_tree_changes(object_store, tree, changes):
+    """Commit a specified set of changes to a tree structure.
+
+    This will apply a set of changes on top of an existing tree, storing new
+    objects in object_store.
+
+    changes are a list of tuples with (path, mode, object_sha).
+    Paths can be both blobs and trees. See the mode and
+    object sha to None deletes the path.
+
+    This method works especially well if there are only a small
+    number of changes to a big tree. For a large number of changes
+    to a large tree, use e.g. commit_tree.
+
+    :param object_store: Object store to store new objects in
+        and retrieve old ones from.
+    :param tree: Original tree root
+    :param changes: changes to apply
+    :return: New tree root object
+    """
+    # TODO(jelmer): Save up the objects and add them using .add_objects
+    # rather than with individual calls to .add_object.
+    nested_changes = {}
+    for (path, new_mode, new_sha) in changes:
+        try:
+            (dirname, subpath) = path.split(b'/', 1)
+        except ValueError:
+            if new_sha is None:
+                del tree[path]
+            else:
+                tree[path] = (new_mode, new_sha)
+        else:
+            nested_changes.setdefault(dirname, []).append(
+                (subpath, new_mode, new_sha))
+    for name, subchanges in nested_changes.items():
+        try:
+            orig_subtree = object_store[tree[name][1]]
+        except KeyError:
+            orig_subtree = Tree()
+        subtree = commit_tree_changes(object_store, orig_subtree, subchanges)
+        if len(subtree) == 0:
+            del tree[name]
+        else:
+            tree[name] = (stat.S_IFDIR, subtree.id)
+    object_store.add_object(tree)
+    return tree

+ 74 - 0
dulwich/tests/test_object_store.py

@@ -25,6 +25,7 @@ from contextlib import closing
 from io import BytesIO
 import os
 import shutil
+import stat
 import tempfile
 
 from dulwich.index import (
@@ -43,6 +44,7 @@ from dulwich.object_store import (
     DiskObjectStore,
     MemoryObjectStore,
     ObjectStoreGraphWalker,
+    commit_tree_changes,
     tree_lookup_path,
     )
 from dulwich.pack import (
@@ -493,3 +495,75 @@ class ObjectStoreGraphWalkerTests(TestCase):
                          sorted(walk))
         self.assertLess(walk.index(b"a" * 40), walk.index(b"c" * 40))
         self.assertLess(walk.index(b"b" * 40), walk.index(b"d" * 40))
+
+
+class CommitTreeChangesTests(TestCase):
+
+    def setUp(self):
+        super(CommitTreeChangesTests, self).setUp()
+        self.store = MemoryObjectStore()
+        self.blob_a = make_object(Blob, data=b'a')
+        self.blob_b = make_object(Blob, data=b'b')
+        self.blob_c = make_object(Blob, data=b'c')
+        for blob in [self.blob_a, self.blob_b, self.blob_c]:
+            self.store.add_object(blob)
+
+        blobs = [
+          (b'a', self.blob_a.id, 0o100644),
+          (b'ad/b', self.blob_b.id, 0o100644),
+          (b'ad/bd/c', self.blob_c.id, 0o100755),
+          (b'ad/c', self.blob_c.id, 0o100644),
+          (b'c', self.blob_c.id, 0o100644),
+          ]
+        self.tree_id = commit_tree(self.store, blobs)
+
+    def test_no_changes(self):
+        self.assertEqual(
+                self.store[self.tree_id],
+                commit_tree_changes(self.store, self.store[self.tree_id], []))
+
+    def test_add_blob(self):
+        blob_d = make_object(Blob, data=b'd')
+        new_tree = commit_tree_changes(
+                self.store, self.store[self.tree_id], [
+                    (b'd', 0o100644, blob_d.id)])
+        self.assertEqual(
+            new_tree[b'd'],
+            (33188, 'c59d9b6344f1af00e504ba698129f07a34bbed8d'))
+
+    def test_add_blob_in_dir(self):
+        blob_d = make_object(Blob, data=b'd')
+        new_tree = commit_tree_changes(
+                self.store, self.store[self.tree_id], [
+                    (b'e/f/d', 0o100644, blob_d.id)])
+        self.assertEqual(
+            new_tree.items(), [
+                TreeEntry(path=b'a', mode=stat.S_IFREG|0o100644,
+                          sha=self.blob_a.id),
+                TreeEntry(path=b'ad', mode=stat.S_IFDIR,
+                          sha='0e2ce2cd7725ff4817791be31ccd6e627e801f4a'),
+                TreeEntry(path=b'c', mode=stat.S_IFREG|0o100644,
+                          sha=self.blob_c.id),
+                TreeEntry(path=b'e', mode=stat.S_IFDIR,
+                          sha='6ab344e288724ac2fb38704728b8896e367ed108')
+                ])
+        e_tree = self.store[new_tree[b'e'][1]]
+        self.assertEqual(
+            e_tree.items(), [
+                TreeEntry(path=b'f', mode=stat.S_IFDIR,
+                          sha='24d2c94d8af232b15a0978c006bf61ef4479a0a5')
+                ])
+        f_tree = self.store[e_tree[b'f'][1]]
+        self.assertEqual(
+            f_tree.items(), [
+                TreeEntry(path=b'd', mode=stat.S_IFREG|0o100644,
+                    sha=blob_d.id)
+                ])
+
+    def test_delete_blob(self):
+        new_tree = commit_tree_changes(
+                self.store, self.store[self.tree_id], [
+                    (b'ad/bd/c', None, None)])
+        self.assertEqual(set(new_tree), {b'a', b'ad', b'c'})
+        ad_tree = self.store[new_tree[b'ad'][1]]
+        self.assertEqual(set(ad_tree), {b'b', b'c'})