///| Git object creation and hashing
///|
/// Helper to convert Array[Byte] to Bytes
fn array_to_bytes(arr : Array[Byte]) -> Bytes {
Bytes::from_iter(arr.iter())
}
///|
/// Helper to convert String to byte array (ASCII encoding)
fn string_to_byte_array(s : String) -> Array[Byte] {
let arr : Array[Byte] = []
for c in s {
arr.push(c.to_int().to_byte())
}
arr
}
///|
/// Create a Git object and return its ID and compressed data
/// Format: {type} {size}\0{content} -> SHA-1 -> zlib compress
pub fn create_object(
obj_type : ObjectType,
content : Bytes,
) -> (ObjectId, Bytes) {
let raw = format_object(obj_type, content)
let id = sha1(raw)
let compressed = @zlib.zlib_compress(raw)
(id, compressed)
}
///|
/// Hash object content (without compression) - optimized with incremental SHA1
pub fn hash_object_content(obj_type : ObjectType, content : Bytes) -> ObjectId {
let state = Sha1State::new()
let header = "\{obj_type.to_string()} \{content.length()}\u0000"
state.update_string(header)
state.update(content)
state.finish()
}
///|
/// Format a Git object (without compression)
fn format_object(obj_type : ObjectType, content : Bytes) -> Bytes {
let header = "\{obj_type.to_string()} \{content.length()}\u0000"
let result : Array[Byte] = []
for c in header {
result.push(c.to_int().to_byte())
}
for b in content {
result.push(b)
}
array_to_bytes(result)
}
///|
/// Create a blob object from content
pub fn create_blob(content : Bytes) -> (ObjectId, Bytes) {
create_object(ObjectType::Blob, content)
}
///|
/// Create a blob from string content
pub fn create_blob_string(content : String) -> (ObjectId, Bytes) {
create_blob(array_to_bytes(string_to_byte_array(content)))
}
///|
/// Create a tree object from entries
/// Entries must be sorted by name
pub fn create_tree(entries : Array[TreeEntry]) -> (ObjectId, Bytes) {
let content : Array[Byte] = []
for entry in entries {
// mode (as ASCII)
for c in entry.mode {
content.push(c.to_int().to_byte())
}
// space
content.push(b' ')
// name (as bytes)
for c in entry.name {
content.push(c.to_int().to_byte())
}
// null byte
content.push(b'\x00')
// 20-byte SHA
for b in entry.id.bytes {
content.push(b)
}
}
create_object(ObjectType::Tree, array_to_bytes(content))
}
///|
/// Create a commit object
pub fn create_commit(commit : Commit) -> (ObjectId, Bytes) {
let content = StringBuilder::new()
// tree
content.write_string("tree ")
content.write_string(commit.tree.to_hex())
content.write_char('\n')
// parents
for parent in commit.parents {
content.write_string("parent ")
content.write_string(parent.to_hex())
content.write_char('\n')
}
// author
content.write_string("author ")
content.write_string(commit.author)
content.write_string(" ")
content.write_string(commit.author_time.to_string())
content.write_string(" ")
content.write_string(commit.author_tz)
content.write_char('\n')
// committer
content.write_string("committer ")
content.write_string(commit.committer)
content.write_string(" ")
content.write_string(commit.commit_time.to_string())
content.write_string(" ")
content.write_string(commit.committer_tz)
content.write_char('\n')
// blank line
content.write_char('\n')
// message
content.write_string(commit.message)
create_object(
ObjectType::Commit,
array_to_bytes(string_to_byte_array(content.to_string())),
)
}
///|
/// Hash a blob without creating the compressed object - optimized with incremental SHA1
pub fn hash_blob(content : Bytes) -> ObjectId {
hash_object_content(ObjectType::Blob, content)
}
///|
/// Hash a blob from string - optimized with incremental SHA1
pub fn hash_blob_string(content : String) -> ObjectId {
let state = Sha1State::new()
// Count bytes for header (ASCII only, same as original behavior)
let byte_len = content.iter().count()
let header = "blob \{byte_len}\u0000"
state.update_string(header)
state.update_string(content)
state.finish()
}