From 6d7fa8544b613e573ce0ab19da5be19c4284032b Mon Sep 17 00:00:00 2001 From: Runxi Yu Date: Sun, 21 Sep 2025 14:34:34 +0800 Subject: [PATCH] Write objects --- git/loose.ha | 146 ++++++++++++++++++++++++++++++++++++++++++++++++++++- git/obj_blob.ha | 2 +- git/obj_commit.ha | 2 +- git/obj_tag.ha | 2 +- git/obj_tree.ha | 2 +- git/object.ha | 17 +++++++++++++++++ diff --git a/git/loose.ha b/git/loose.ha index ddf6b2f7766ade91619242734fea14a666f02d42..27e1db1aff9c90a97556dc21b74a0b06c506b2ed 100644 --- a/git/loose.ha +++ b/git/loose.ha @@ -1,13 +1,16 @@ use bytes; use compress::zlib; +use crypto::sha256; use encoding::utf8; use errors; use fmt; use fs; +use hash; use io; +use memio; +use path; use strconv; use strings; -use encoding::utf8; // Find the path to a loose object with the given ID, // relative to the repository root. @@ -138,3 +141,144 @@ let out = alloc(body...)?; return (code, out); }; + +fn loose_body_slice(raw: []u8) ([]u8 | errors::invalid) = { + let mnul = bytes::index(raw, 0u8); + if (mnul is void) { + return errors::invalid; + }; + let nul = mnul: size; + if (nul + 1z > len(raw)) { + return errors::invalid; + }; + return raw[nul + 1z ..]; +}; + +// Writes a loose object given its type tag and raw body, returning the new ID. +export fn loose_write_typed( + r: repo, + ty: objtype, + body: []u8, +) (oid | fs::error | io::error | errors::invalid | nomem) = { + let tyname: const str = switch (ty) { + case objtype::OBJ_BLOB => yield "blob"; + case objtype::OBJ_TREE => yield "tree"; + case objtype::OBJ_COMMIT => yield "commit"; + case objtype::OBJ_TAG => yield "tag"; + case => + return errors::invalid; + }; + const tynameb = strings::toutf8(tyname); + const size_str = strconv::ztos(len(body)); + const sizeb = strings::toutf8(size_str); + + let rawlen = len(tynameb) + 1z + len(sizeb) + 1z + len(body); + let raw = alloc([0u8...], rawlen)?; + defer free(raw); + + let pos = 0z; + raw[pos .. pos + len(tynameb)] = tynameb; + pos += len(tynameb); + raw[pos] = ' '; + pos += 1z; + raw[pos .. pos + len(sizeb)] = sizeb; + pos += len(sizeb); + raw[pos] = 0u8; + pos += 1z; + raw[pos .. pos + len(body)] = body; + + let st = sha256::sha256(); + defer hash::close(&st); + hash::write(&st, raw); + + let id: oid = [0...]; + hash::sum(&st, id); + + let src = memio::fixed(raw); + let zw = zlib::compress(&src); + let compressed: []u8 = []; + match (io::drain(&zw.vtable)) { + case let buf: []u8 => + compressed = buf; + case let err: io::error => { + match (io::close(&zw.vtable)) { + case void => + return err; + case let cerr: io::error => + return cerr; + }; + }; + }; + defer free(compressed); + + match (io::close(&zw.vtable)) { + case void => void; + case let err: io::error => + return err; + }; + + let rel = loose_path_resolve(id)?; + defer free(rel); + + const dir = path::dirname(rel); + match (fs::mkdirs(r.root, dir, (0o755: fs::mode))) { + case void => void; + case let err: fs::error => + return err; + }; + + let file = match (fs::create(r.root, rel, (0o644: fs::mode))) { + case let fh: io::handle => + yield fh; + case let err: fs::error => + return err; + }; + + let write_res = io::writeall(file, compressed); + let close_res = io::close(file); + + match (write_res) { + case size => void; + case let err: io::error => + return err; + }; + + match (close_res) { + case void => void; + case let err: io::error => + return err; + }; + + return id; +}; + +// Writes a loose object, returning the new ID. +export fn loose_write( + r: repo, + o: object, +) (oid | fs::error | io::error | errors::invalid | nomem) = { + match (o) { + case let b: blob => + return loose_write_typed(r, objtype::OBJ_BLOB, b.data); + case let t: tree => { + let serialized = tree_serialize(t)?; + defer free(serialized); + let body = loose_body_slice(serialized)?; + return loose_write_typed(r, objtype::OBJ_TREE, body); + }; + case let c: commit => { + let serialized = commit_serialize(c)?; + defer free(serialized); + let body = loose_body_slice(serialized)?; + return loose_write_typed(r, objtype::OBJ_COMMIT, body); + }; + case let g: tag => { + let serialized = tag_serialize(g)?; + defer free(serialized); + let body = loose_body_slice(serialized)?; + return loose_write_typed(r, objtype::OBJ_TAG, body); + }; + case => + abort("Unknown object variant written to loose storage"); + }; +}; diff --git a/git/obj_blob.ha b/git/obj_blob.ha index 09ee1948a591f8cbad72d307b6890a976af4fda4..ef95b0d33ebd7a589335d4481fb85aa522abf2bd 100644 --- a/git/obj_blob.ha +++ b/git/obj_blob.ha @@ -20,7 +20,7 @@ return blob { data = data }; }; -// Serializes a blob into the on-disk format. +// Serializes a blob into the uncompressed on-disk format. export fn blob_serialize(b: blob) ([]u8 | nomem) = { const sizes = strconv::ztos(len(b.data)); const ty = strings::toutf8("blob "); diff --git a/git/obj_commit.ha b/git/obj_commit.ha index 96b68d7c9093a3b0ecbdf58d25a9410fde2ef59e..a4e29dc394ee3e63cdc502096d0415d96428c228 100644 --- a/git/obj_commit.ha +++ b/git/obj_commit.ha @@ -106,7 +106,7 @@ c.message = alloc(body[i..]...)?; return c; }; -// Serializes a commit into its on-disk format. +// Serializes a commit into its uncompressed on-disk format. export fn commit_serialize(c: commit) ([]u8 | nomem) = { const treehex = oid_stringify(c.tree)?; defer free(treehex); diff --git a/git/obj_tag.ha b/git/obj_tag.ha index db19c8237c55ab13b0bcdd4803843fcfe71ace74..1cffae70fd55cadbe50b307142903ec5d76af61e 100644 --- a/git/obj_tag.ha +++ b/git/obj_tag.ha @@ -124,7 +124,7 @@ t.message = alloc(body[i..]...)?; return t; }; -// Serializes a tag into the on-disk format. +// Serializes a commit into its uncompressed on-disk format. export fn tag_serialize(t: tag) ([]u8 | errors::invalid | nomem) = { let tyname: (const str | void) = void; switch (t.target_type) { diff --git a/git/obj_tree.ha b/git/obj_tree.ha index d44a2892f400f6e2a79f370ec90cfcede69c5c14..5ddf728c234de458e54bc0d6d40d94a0c005ec37 100644 --- a/git/obj_tree.ha +++ b/git/obj_tree.ha @@ -155,7 +155,7 @@ return errors::invalid; }; -// Serializes a tree into the on-disk format. +// Serializes a tree into its uncompressed on-disk format. export fn tree_serialize(t: tree) ([]u8 | nomem) = { let bodylen = 0z; for (let e .. t.entries) { diff --git a/git/object.ha b/git/object.ha index 270443ea9dbbdf6534f9a73bf34a4acc1d91505d..27226bb27b156b77c6f73e1d0b8c1cd5feaec13d 100644 --- a/git/object.ha +++ b/git/object.ha @@ -43,6 +43,23 @@ abort("Unknown object type being freed..."); }; }; +// Serializes an object into its on-disk representation. +export fn object_serialize(o: object) + ([]u8 | errors::invalid | nomem) = { + match (o) { + case let b: blob => + return blob_serialize(b); + case let t: tree => + return tree_serialize(t); + case let c: commit => + return commit_serialize(c); + case let g: tag => + return tag_serialize(g); + case => + abort("Unknown object type being serialized..."); + }; +}; + // Verifies that the given buffer (which must be the exact on-disk format // structured as "type size\0body") matches the given object ID. export fn object_verify_oid(buf: []u8, want: oid) bool = { -- 2.48.1