132 lines
3.1 KiB
Python
132 lines
3.1 KiB
Python
import zlib
|
|
import hashlib
|
|
from abc import ABC, abstractmethod
|
|
from libwyag import repository
|
|
|
|
|
|
class GitObject:
|
|
|
|
fmt: bytes = b""
|
|
repo = None
|
|
|
|
def __init__(self, repo, data=None):
|
|
self.repo = repo
|
|
if data is not None:
|
|
self.deserialize(data)
|
|
|
|
@abstractmethod
|
|
def serialize(self):
|
|
"""
|
|
This function MUST be implemented by subclasses.
|
|
It must read the object's contents from self.data, a byte string, and do
|
|
whatever it takes to convert it into a meaningful representation.
|
|
What exactly that means depend on each subclass.
|
|
"""
|
|
return NotImplemented
|
|
|
|
@abstractmethod
|
|
def deserialize(self, data):
|
|
return NotImplemented
|
|
|
|
|
|
class GitBlob(GitObject):
|
|
fmt = b"blob"
|
|
|
|
def serialize(self):
|
|
return self.blobdata
|
|
|
|
def deserialize(self, data):
|
|
self.blobdata = data
|
|
|
|
|
|
class GitCommit(GitObject):
|
|
pass
|
|
|
|
|
|
class GitTree(GitObject):
|
|
pass
|
|
|
|
|
|
class GitTag(GitObject):
|
|
pass
|
|
|
|
|
|
def read_object(repo, sha):
|
|
"""Read object object_id from Git repository repo. Return a
|
|
GitObject whose exact type depends on the object."""
|
|
|
|
path = repository.repo_file(repo, "objects", sha[0:2], sha[2:])
|
|
|
|
with open(path, "rb") as f:
|
|
raw = zlib.decompress(f.read())
|
|
|
|
# Read object type
|
|
x = raw.find(b" ")
|
|
fmt = raw[0:x]
|
|
|
|
# Read and validate object size
|
|
y = raw.find(b"\x00", x)
|
|
size = int(raw[x:y].decode("ascii"))
|
|
if size != len(raw) - y - 1:
|
|
raise Exception("Malformed object {0}: bad length".format(sha))
|
|
|
|
# Pick constructor
|
|
if fmt == b"commit":
|
|
c = GitCommit
|
|
elif fmt == b"tree":
|
|
c = GitTree
|
|
elif fmt == b"tag":
|
|
c = GitTag
|
|
elif fmt == b"blob":
|
|
c = GitBlob
|
|
else:
|
|
raise Exception(f"Unknown type {fmt.decode('ascii')} for object {sha}")
|
|
|
|
# Call constructor and return object
|
|
return c(repo, raw[y + 1 :])
|
|
|
|
|
|
def find_object(repo, name: str, fmt=None, follow=True):
|
|
""" Name resolution function """
|
|
pass
|
|
|
|
|
|
def write_object(obj: GitObject, actually_write=True):
|
|
# Serialize object data
|
|
data = obj.serialize()
|
|
# Add header
|
|
result = obj.fmt + b" " + str(len(data)).encode() + b"\x00" + data
|
|
# Compute hash
|
|
sha = hashlib.sha1(result).hexdigest()
|
|
|
|
if actually_write:
|
|
# Compute path
|
|
path = repository.repo_file(
|
|
obj.repo, "objects", sha[0:2], sha[2:], mkdir=actually_write
|
|
)
|
|
|
|
with open(path, "wb") as f:
|
|
# Compress and write
|
|
f.write(zlib.compress(result))
|
|
|
|
return sha
|
|
|
|
|
|
def hash_object(fd, fmt, repo=None):
|
|
data = fd.read()
|
|
|
|
# Choose constructor depending on
|
|
# object type found in header.
|
|
if fmt == b"commit":
|
|
obj = GitCommit(repo, data)
|
|
elif fmt == b"tree":
|
|
obj = GitTree(repo, data)
|
|
elif fmt == b"tag":
|
|
obj = GitTag(repo, data)
|
|
elif fmt == b"blob":
|
|
obj = GitBlob(repo, data)
|
|
else:
|
|
raise Exception("Unknown type %s!" % fmt)
|
|
|
|
return write_object(obj, repo)
|