diff --git a/git/db.py b/git/db.py index cacd030d0..bd68a5157 100644 --- a/git/db.py +++ b/git/db.py @@ -5,10 +5,14 @@ __all__ = ["GitCmdObjectDB", "GitDB"] -from gitdb.base import OInfo, OStream +from subprocess import PIPE + +from gitdb.base import IStream, OInfo, OStream from gitdb.db import GitDB, LooseObjectDB from gitdb.exc import BadObject +from gitdb.fun import stream_copy +from git.compat import force_text from git.util import bin_to_hex, hex_to_bin from git.exc import GitCommandError @@ -46,6 +50,25 @@ def stream(self, binsha: bytes) -> OStream: hexsha, typename, size, stream = self._git.stream_object_data(bin_to_hex(binsha)) return OStream(hex_to_bin(hexsha), typename, size, stream) + def store(self, istream: IStream) -> IStream: + """Store an object using git itself.""" + if istream.binsha is not None or self.ostream() is not None: + return super().store(istream) + + proc = self._git.hash_object( + "-t", force_text(istream.type), "-w", "--stdin", "--literally", as_process=True, istream=PIPE + ) + assert proc.stdin is not None + try: + stream_copy(istream.read, proc.stdin.write, istream.size, self.stream_chunk_size) + finally: + proc.stdin.close() + assert proc.stdout is not None + hexsha = proc.stdout.read().strip() + proc.wait() + istream.binsha = hex_to_bin(hexsha) + return istream + # { Interface def partial_to_complete_sha_hex(self, partial_hexsha: str) -> bytes: diff --git a/test/test_db.py b/test/test_db.py index 72d63b44b..46580d84b 100644 --- a/test/test_db.py +++ b/test/test_db.py @@ -3,16 +3,30 @@ # This module is part of GitPython and is released under the # 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/ +from io import BytesIO import os.path as osp +from unittest import mock + +from gitdb import IStream +from gitdb.db import LooseObjectDB +from gitdb.typ import str_blob_type from git.db import GitCmdObjectDB from git.exc import BadObject from git.util import bin_to_hex -from test.lib import TestBase +from test.lib import TestBase, with_rw_repo class TestDB(TestBase): + @with_rw_repo("HEAD") + def test_store_uses_hash_object(self, rw_repo): + data = b"hello world" + with mock.patch.object(LooseObjectDB, "store", side_effect=AssertionError("unexpected loose-object write")): + istream = rw_repo.odb.store(IStream(str_blob_type, len(data), BytesIO(data))) + + assert rw_repo.odb.stream(istream.binsha).read() == data + def test_base(self): gdb = GitCmdObjectDB(osp.join(self.rorepo.git_dir, "objects"), self.rorepo.git) diff --git a/test/test_refs.py b/test/test_refs.py index 9a3f58c7b..32c8fbe34 100644 --- a/test/test_refs.py +++ b/test/test_refs.py @@ -26,7 +26,7 @@ from git.exc import UnsafeOptionError from git.objects.tag import TagObject import git.refs as refs -from git.util import Actor +from git.util import Actor, rmtree from test.lib import TestBase, requires_symlinks, with_rw_repo, PathLikeMock @@ -43,6 +43,7 @@ def _repo_with_initial_commit(self, base_dir): yield repo finally: repo.git.clear_cache() + rmtree(repo_dir) def test_from_path(self): # Should be able to create any reference directly.