Lindenii Project Forge
Add tags and make OBJ_* an enum
# hare-git — Git library in pure Hare - Only SHA-256 is supported for now. - Loose objects and packed objects are supported.
- Tags are not supported yet.
- Read-only for now. ## Dependencies - [hare-compress](https://git.sr.ht/~sircmpwn/hare-compress) for zlib. ## Acknowledgements This was inspired by [git-walk.ha](https://git.sr.ht/~yerinalexey/git-walk.ha) but code has diverged a lot. ## Contributing Create a branch that begins with `contrib/` and push to the [main repo](https://forge.lindenii.org/hare/:/repos/hare-git/) via SSH directly. ``` git clone ssh://forge.lindenii.org/hare/:/repos/hare-git/ cd hare-git git checkout -b contrib/whatever # edit and commit stuff git push -u origin HEAD ``` ## Support [`#chat`](https://webirc.runxiyu.org/kiwiirc/#chat) on [irc.runxiyu.org](https://irc.runxiyu.org/).
use bytes; use compress::zlib;
use encoding::utf8;
use errors; use fmt; use fs; use io; use strconv; use strings; // Find the path to a loose object with the given ID, // relative to the repository root. fn loose_relpath(id: oid) (str | nomem) = { const hex = oid_string(id)?; defer free(hex); const dir = strings::bytesub(hex, 0z, 2z)!; const file = strings::bytesub(hex, 2z, strings::end)!; return fmt::asprintf("objects/{}/{}", dir, file); }; // Reads a loose object from the repository by its ID. export fn read_loose( r: repo, id: oid,
) (object | fs::error | io::error | errors::invalid | strconv::invalid | strconv::overflow | nomem) = {
) (object | fs::error | io::error | errors::invalid | strconv::invalid | strconv::overflow | nomem | utf8::invalid) = {
const rel = loose_relpath(id)?; defer free(rel); const fh = fs::open(r.root, rel)?; defer io::close(fh)!; let zr = zlib::decompress(fh)?; defer io::close(&zr.vtable)!; let buf = io::drain(&zr.vtable)?; defer free(buf); let mnul = bytes::index(buf, 0u8); if (mnul is void) { return errors::invalid; }; let nul = mnul: size; const header = buf[..nul]; const body = buf[nul + 1z ..]; let msp = bytes::index(header, ' '); if (msp is void) { return errors::invalid; }; let sp = msp: size; const ty = strings::fromutf8_unsafe(header[..sp]); const szs = strings::fromutf8_unsafe(header[sp + 1z ..]); const expect = strconv::stoz(szs)?; if (expect != len(body)) { return errors::invalid; }; if (!verify_oid(buf, id)) { return errors::invalid; }; if (ty == "blob") { const b = parse_blob(body)?; return (b: object); } else if (ty == "tree") { const t = parse_tree(body)?; return (t: object); } else if (ty == "commit") { const c = parse_commit(body)?; return (c: object);
} else if (ty == "tag") { const g = parse_tag(body)?; return (g: object);
} else { return errors::invalid; }; }; // Reads a loose object from the repository by its ID, // returning its type and raw data. export fn read_loose_typed( r: repo, id: oid,
) ((u8, []u8) | fs::error | io::error | errors::invalid | errors::noentry | strconv::invalid | strconv::overflow | nomem) = {
) ((objtype, []u8) | fs::error | io::error | errors::invalid | errors::noentry | strconv::invalid | strconv::overflow | nomem) = {
const rel = loose_relpath(id)?; defer free(rel); let fh = fs::open(r.root, rel)?; defer io::close(fh)!; let zr = zlib::decompress(fh)?; defer io::close(&zr.vtable)!; let buf = io::drain(&zr.vtable)?; defer free(buf); let mnul = bytes::index(buf, 0u8); if (mnul is void) { return errors::invalid; }; let nul = mnul: size; const header = buf[..nul]; const body = buf[nul + 1z ..]; let msp = bytes::index(header, ' '); if (msp is void) { return errors::invalid; }; let sp = msp: size; const ty = strings::fromutf8_unsafe(header[..sp]); const szs = strings::fromutf8_unsafe(header[sp + 1z ..]); const expect = strconv::stoz(szs)?; if (expect != len(body)) { return errors::invalid; };
let code: u8 = 0u8;
let code: objtype = objtype::OBJ_INVALID;
if (ty == "blob") {
code = OBJ_BLOB;
code = objtype::OBJ_BLOB;
} else if (ty == "tree") {
code = OBJ_TREE;
code = objtype::OBJ_TREE;
} else if (ty == "commit") {
code = OBJ_COMMIT;
code = objtype::OBJ_COMMIT; } else if (ty == "tag") { code = objtype::OBJ_TAG;
} else { return errors::invalid; }; let out = alloc(body...)?; return (code, out); };
use bytes; use encoding::utf8; use errors; use strconv; use strings; // A Git annotated tag object. export type tag = struct { target: oid, target_type: objtype, name: []u8, tagger: (void | ident), message: []u8, }; // Frees resources associated with a [[tag]]. export fn tag_finish(t: tag) void = { free(t.name); match (t.tagger) { case let id: ident => ident_finish(id); case void => void; }; free(t.message); }; // Parses a tag from its raw data. export fn parse_tag( body: []u8, ) (tag | errors::invalid | strconv::invalid | strconv::overflow | utf8::invalid | nomem) = { let t = tag { target = [0...], target_type = objtype::OBJ_INVALID, name = [], tagger = void, message = [], }; let have_target = false; let have_type = false; let i = 0z; for (true) { let mrel = bytes::index(body[i..], '\n'); if (mrel is void) { return errors::invalid; }; let rel = mrel: size; let line = body[i .. i + rel]; if (len(line) == 0) { i += rel + 1z; break; }; if (bytes::hasprefix(line, strings::toutf8("object "))) { const hex = strings::fromutf8(line[7..])?; match (parse_oid(hex)) { case let o: oid => t.target = o; have_target = true; case => return errors::invalid; }; } else if (bytes::hasprefix(line, strings::toutf8("type "))) { const ty = strings::fromutf8(line[5..])?; if (ty == "commit") { t.target_type = objtype::OBJ_COMMIT; } else if (ty == "tree") { t.target_type = objtype::OBJ_TREE; } else if (ty == "blob") { t.target_type = objtype::OBJ_BLOB; } else if (ty == "tag") { t.target_type = objtype::OBJ_TAG; } else { return errors::invalid; }; have_type = true; } else if (bytes::hasprefix(line, strings::toutf8("tag "))) { const name_b = line[4..]; let name = alloc(name_b...)?; free(t.name); t.name = name; } else if (bytes::hasprefix(line, strings::toutf8("tagger "))) { const per = parse_ident(line[7..])?; match (t.tagger) { case let old: ident => ident_finish(old); case void => void; }; t.tagger = per; } else if ( bytes::hasprefix(line, strings::toutf8("gpgsig ")) || bytes::hasprefix(line, strings::toutf8("gpgsig-sha256 ")) ) { i += rel + 1z; for (true) { if (i >= len(body)) { return errors::invalid; }; let mnext = bytes::index(body[i..], '\n'); if (mnext is void) { return errors::invalid; }; let next = mnext: size; if (body[i] != ' ') { break; }; i += next + 1z; }; continue; }; i += rel + 1z; }; if (!have_target || !have_type) { return errors::invalid; }; t.message = alloc(body[i..]...)?; return t; };
use bytes; use crypto::sha256; use errors; use fmt; use fs; use hash; use io; use strconv; use strings;
// Any Git object. [[tag]] will be supported in the future. export type object = (blob | tree | commit); // TODO: These should be enums instead of u8 constants. // The tag for a commit object. export def OBJ_COMMIT: u8 = 1u8; // The tag for a tree object. export def OBJ_TREE: u8 = 2u8; // The tag for a blob object. export def OBJ_BLOB: u8 = 3u8; // The tag for a tag object. export def OBJ_TAG: u8 = 4u8; // The tag for an offset delta object in packfiles. export def OBJ_OFS_DELTA: u8 = 6u8;
// Object/pack type tags. // // These are not typically used as we could represent objects with tagged // unions. However, they may be useful in scenarios where a full object is // undesirable or unavailable. export type objtype = enum u8 { OBJ_INVALID = 0u8, OBJ_COMMIT = 1u8, OBJ_TREE = 2u8, OBJ_BLOB = 3u8, OBJ_TAG = 4u8, OBJ_FUTURE = 5u8, OBJ_OFS_DELTA = 6u8, OBJ_REF_DELTA = 7u8, };
// The tag for a reference delta object in packfiles. export def OBJ_REF_DELTA: u8 = 7u8;
// Any Git object. export type object = (blob | tree | commit | tag);
// Frees resources associated with any Git object. export fn object_finish(o: object) void = { match (o) { case let b: blob => blob_finish(b); case let t: tree => tree_finish(t); case let c: commit => commit_finish(c);
case let g: tag => tag_finish(g);
case => abort("Unknown object type being freed..."); }; }; // Verifies that the given body matches the given object ID. export fn verify_oid(buf: []u8, want: oid) bool = { let st = sha256::sha256(); hash::write(&st, buf); let got: oid = [0...]; hash::sum(&st, got); hash::close(&st); if (bytes::equal(got[..], want[..])) { return true; } else { return false; }; }; // Verifies that the given body matches the given object ID. // // Adds the type and size, as a real serialized git object has. export fn verify_typed(ty: str, body: []u8, want: oid) bool = { let st = sha256::sha256(); defer hash::close(&st); if (ty == "blob") { hash::write(&st, strings::toutf8("blob")); } else if (ty == "tree") { hash::write(&st, strings::toutf8("tree")); } else if (ty == "commit") { hash::write(&st, strings::toutf8("commit"));
} else if (ty == "tag") { hash::write(&st, strings::toutf8("tag"));
} else { return false; }; hash::write(&st, strings::toutf8(" ")); let szs = strconv::ztos(len(body)); hash::write(&st, strings::toutf8(szs)); hash::write(&st, strings::toutf8("\x00")); hash::write(&st, body); let got: oid = [0...]; hash::sum(&st, got); if (bytes::equal(got[..], want[..])) { return true; } else { return false; }; }; // Reads a Git object from the repository by its ID. export fn read_object( r: repo, id: oid, ) (object | fs::error | io::error | errors::invalid | strconv::invalid | strconv::overflow | nomem) = { match (read_loose(r, id)) { case let o: object => return o; case let fe: fs::error => if (fe is errors::noentry) { void; } else { return fe; }; case let e: (io::error | errors::invalid | strconv::invalid | strconv::overflow | nomem) => return e; }; match (read_packed(r, id)) { case let o: object => return o; case let fe: fs::error => if (fe is errors::noentry) { return errors::invalid; } else { return fe; }; case let e: (io::error | errors::invalid | strconv::invalid | strconv::overflow | nomem) => return e; }; };
use compress::zlib; use crypto::sha256;
use encoding::utf8;
use endian; use errors; use fmt; use fs; use io; use strconv; use strings; def IDX_MAGIC: u32 = 0xff744f63u32; def IDX_V2: u32 = 2u32; def PACK_MAGIC: u32 = 0x5041434bu32; def PACK_V2: u32 = 2u32; type pack_loc = struct { pack_rel: str, ofs: u64, }; fn cmp_oid(a: []u8, b: oid) i32 = { for (let i = 0z; i < sha256::SZ; i += 1z) { let av = a[i]; let bv = b[i]; if (av < bv) { return -1; }; if (av > bv) { return 1; }; }; return 0; }; fn count_large_before(off32: []u8, idx: size) size = { let n = 0z; for (let i = 0z; i < idx; i += 1z) { let o32 = endian::begetu32(off32[i*4z .. i*4z + 4z]); if ((o32 & 0x8000_0000u32) != 0u32) { n += 1z; }; }; return n; }; // Reads a packed object by its ID from the given repository. export fn read_packed( r: repo, id: oid,
) (object | fs::error | io::error | errors::invalid | strconv::invalid | strconv::overflow | errors::noentry | nomem) = {
) (object | fs::error | io::error | errors::invalid | strconv::invalid | strconv::overflow | errors::noentry | utf8::invalid | nomem) = {
let loc = find_in_indexes(r, id)?; return read_from_pack_at(r, loc, id); }; fn find_in_indexes( r: repo, id: oid, ) (pack_loc | errors::noentry | fs::error | io::error | errors::invalid | nomem) = { const dir = "objects/pack"; let it = fs::iter(r.root, dir)?; defer fs::finish(it); for (true) { match (fs::next(it)) { case let de: fs::dirent => if (!strings::hassuffix(de.name, ".idx")) { continue; }; { let rel = fmt::asprintf("{}/{}", dir, de.name)?; match (idx_lookup(r, rel, id)) { case let pl: pack_loc => free(rel); return pl; case errors::noentry => free(rel); continue; case let fe: fs::error => free(rel); return fe; case let ioe: io::error => free(rel); return ioe; case let inv: errors::invalid => free(rel); return inv; case nomem => free(rel); return nomem; }; }; case done => break; case let fe: fs::error => return fe; }; }; return errors::noentry; }; fn idx_lookup( r: repo, idx_rel: const str, id: oid, ) (pack_loc | errors::noentry | fs::error | io::error | errors::invalid | nomem) = { let h = fs::open(r.root, idx_rel)?; defer io::close(h)!; let buf = io::drain(h)?; defer free(buf); if (len(buf) < 8z + 256z*4z) { return errors::invalid; }; let off = 0z; let magic = endian::begetu32(buf[off..off+4]); off += 4z; if (magic != IDX_MAGIC) { return errors::invalid; }; let ver = endian::begetu32(buf[off..off+4]); off += 4z; if (ver != IDX_V2) { return errors::invalid; }; let fanout: [256]u32 = [0...]; for (let i = 0z; i < 256z; i += 1z) { fanout[i] = endian::begetu32(buf[off..off+4]); off += 4z; }; let nobj = fanout[255]: size; let need = off + nobj * sha256::SZ + nobj * 4z + nobj * 4z + 2z * sha256::SZ; if (need > len(buf)) { return errors::invalid; }; let names_off = off; let crcs_off = names_off + nobj * sha256::SZ; let off32_off = crcs_off + nobj * 4z; let large_count = 0z; for (let i = 0z; i < nobj; i += 1z) { let o32 = endian::begetu32(buf[off32_off + i*4z .. off32_off + i*4z + 4z]); if ((o32 & 0x8000_0000u32) != 0u32) { large_count += 1z; }; }; let off64_off = off32_off + nobj * 4z; let trailer_off = off64_off + large_count * 8z; if (trailer_off + 2z * sha256::SZ > len(buf)) { return errors::invalid; }; let first = (id[0]: u8): size; let lo: size = if (first == 0u8) { yield 0z; } else { yield fanout[first - 1z]: size; }; let hi: size = fanout[first]: size; let found = false; let idx = 0z; let l = lo; let h = hi; for (l < h) { let m = l + (h - l) / 2z; let cand = buf[names_off + m*sha256::SZ .. names_off + (m+1z)*sha256::SZ]; let c = cmp_oid(cand, id); if (c == 0) { found = true; idx = m; break; } else if (c < 0) { l = m + 1z; } else { h = m; }; }; if (!found) { return errors::noentry; }; let o32 = endian::begetu32(buf[off32_off + idx*4z .. off32_off + idx*4z + 4z]); let ofs: u64 = 0u64; if ((o32 & 0x8000_0000u32) == 0u32) { ofs = (o32: u64); } else { let nlarge_before = count_large_before(buf[off32_off..], idx); let p = off64_off + nlarge_before * 8z; let o64be = endian::begetu64(buf[p .. p + 8z]); ofs = o64be; }; if (!strings::hassuffix(idx_rel, ".idx")) { return errors::invalid; }; let stem = strings::bytesub(idx_rel, 0z, len(idx_rel) - 4z)!; let packpath = fmt::asprintf("{}{}", stem, ".pack")?; return pack_loc { pack_rel = packpath, ofs = ofs }; }; fn read_from_pack_at( r: repo, loc: pack_loc, want: oid,
) (object | fs::error | io::error | errors::invalid | strconv::invalid | strconv::overflow | errors::noentry | nomem) = {
) (object | fs::error | io::error | errors::invalid | strconv::invalid | strconv::overflow | errors::noentry | utf8::invalid | nomem) = {
defer free(loc.pack_rel); let h = fs::open(r.root, loc.pack_rel)?; defer io::close(h)!; let header: [12]u8 = [0...]; match (io::readall(h, header)) { case size => void; case io::EOF => return errors::invalid; case let ioe: io::error => return ioe; }; let magic = endian::begetu32(header[..4]); let ver = endian::begetu32(header[4..8]); if (magic != PACK_MAGIC || ver != PACK_V2) { return errors::invalid; }; io::seek(h, (loc.ofs: i64), io::whence::SET)?;
let ty: u8 = 0u8;
let ty: objtype = objtype::OBJ_INVALID;
match (read_obj_header(h)) { case let t: (u8, size, size) =>
ty = t.0;
ty = (t.0: objtype);
case let ioe: io::error => return ioe; case => return errors::invalid; };
let full_ty: u8 = 0u8;
let full_ty: objtype = objtype::OBJ_INVALID;
let body: []u8 = []; defer if (len(body) != 0) { free(body); }; switch (ty) {
case OBJ_COMMIT =>
case objtype::OBJ_COMMIT =>
body = inflate_section(h)?;
full_ty = OBJ_COMMIT; case OBJ_TREE =>
full_ty = objtype::OBJ_COMMIT; case objtype::OBJ_TREE =>
body = inflate_section(h)?;
full_ty = OBJ_TREE; case OBJ_BLOB =>
full_ty = objtype::OBJ_TREE; case objtype::OBJ_BLOB =>
body = inflate_section(h)?;
full_ty = OBJ_BLOB; case OBJ_REF_DELTA =>
full_ty = objtype::OBJ_BLOB; case objtype::OBJ_TAG => body = inflate_section(h)?; full_ty = objtype::OBJ_TAG; case objtype::OBJ_REF_DELTA =>
match (resolve_ref_delta(r, h)) {
case let t: (u8, []u8) =>
case let t: (objtype, []u8) =>
full_ty = t.0; body = t.1; case let e: (fs::error | io::error | errors::invalid | errors::noentry | nomem) => return e; };
case OBJ_OFS_DELTA =>
case objtype::OBJ_OFS_DELTA =>
match (resolve_ofs_delta(r, h, loc)) {
case let t: (u8, []u8) =>
case let t: (objtype, []u8) =>
full_ty = t.0; body = t.1; case let e: (fs::error | io::error | errors::invalid | errors::noentry | nomem) => return e; }; case => return errors::invalid; };
let tystr = if (full_ty == OBJ_BLOB) {
let tystr = if (full_ty == objtype::OBJ_BLOB) {
yield "blob";
} else if (full_ty == OBJ_TREE) {
} else if (full_ty == objtype::OBJ_TREE) {
yield "tree";
} else if (full_ty == OBJ_COMMIT) {
} else if (full_ty == objtype::OBJ_COMMIT) {
yield "commit";
} else if (full_ty == objtype::OBJ_TAG) { yield "tag";
} else { yield ""; }; if (tystr == "" || !verify_typed(tystr, body, want)) { return errors::invalid; };
if (full_ty == OBJ_BLOB) {
if (full_ty == objtype::OBJ_BLOB) {
const b = parse_blob(body)?; return (b: object); };
if (full_ty == OBJ_TREE) {
if (full_ty == objtype::OBJ_TREE) {
const t = parse_tree(body)?; return (t: object); };
if (full_ty == OBJ_COMMIT) {
if (full_ty == objtype::OBJ_COMMIT) {
const c = parse_commit(body)?; return (c: object); };
if (full_ty == objtype::OBJ_TAG) { const g = parse_tag(body)?; return (g: object); };
return errors::invalid; }; fn read_obj_header(h: io::handle) ((u8, size, size) | io::error | errors::invalid) = { let consumed = 0z; let b0: [1]u8 = [0]; match (io::readall(h, b0)) { case size => void; case io::EOF => return errors::invalid; case let ioe: io::error => return ioe; }; consumed += 1z; let ty = (b0[0] >> 4) & 0x07u8; let sz: size = (b0[0] & 0x0fu8): size; let shift = 4z; if ((b0[0] & 0x80u8) != 0u8) { for (true) { let bb: [1]u8 = [0]; match (io::readall(h, bb)) { case size => void; case io::EOF => return errors::invalid; case let ioe: io::error => return ioe; }; consumed += 1z; let v = (bb[0] & 0x7fu8): size; sz += v << shift; if ((bb[0] & 0x80u8) == 0u8) { break; }; shift += 7z; }; }; return (ty, sz, consumed); }; fn inflate_section(h: io::handle) ([]u8 | io::error | nomem) = { let zr = zlib::decompress(h)?; defer io::close(&zr.vtable)!; let out = io::drain(&zr.vtable)?; return out; }; fn resolve_ref_delta( r: repo, h: io::handle,
) ((u8, []u8) | fs::error | io::error | errors::invalid | errors::noentry | strconv::invalid | strconv::overflow | nomem) = {
) ((objtype, []u8) | fs::error | io::error | errors::invalid | errors::noentry | strconv::invalid | strconv::overflow | nomem) = {
let base: oid = [0...]; match (io::readall(h, base)) { case size => void; case io::EOF => return errors::invalid; case let ioe: io::error => return ioe; }; let delta = inflate_section(h)?; defer free(delta); let bt = read_resolved_body_by_id(r, base)?; let out = apply_delta(bt.1, delta)?; return (bt.0, out); }; fn read_ofs_distance(h: io::handle) (u64 | io::error | errors::invalid) = { let b: [1]u8 = [0]; match (io::readall(h, b)) { case size => void; case io::EOF => return errors::invalid; case let ioe: io::error => return ioe; }; let dist: u64 = (b[0] & 0x7fu8): u64; if ((b[0] & 0x80u8) != 0u8) { for (true) { match (io::readall(h, b)) { case size => void; case io::EOF => return errors::invalid; case let ioe: io::error => return ioe; }; dist = ((dist + 1u64) << 7u64) + ((b[0] & 0x7fu8): u64); if ((b[0] & 0x80u8) == 0u8) { break; }; }; }; return dist; }; fn resolve_ofs_delta( r: repo, h: io::handle, loc: pack_loc,
) ((u8, []u8) | fs::error | io::error | errors::invalid | errors::noentry | strconv::invalid | strconv::overflow | nomem) = {
) ((objtype, []u8) | fs::error | io::error | errors::invalid | errors::noentry | strconv::invalid | strconv::overflow | nomem) = {
let dist = read_ofs_distance(h)?; let base_ofs: u64 = if (loc.ofs > dist) { yield loc.ofs - dist; } else { yield 0u64; }; if (base_ofs == 0u64) { return errors::invalid; }; let bt = read_resolved_body_at_ofs(r, loc.pack_rel, base_ofs)?; let delta = inflate_section(h)?; defer free(delta); let out = apply_delta(bt.1, delta)?; return (bt.0, out); }; fn read_resolved_body_by_id( r: repo, id: oid,
) ((u8, []u8) | fs::error | io::error | errors::invalid | errors::noentry | strconv::invalid | strconv::overflow | nomem) = {
) ((objtype, []u8) | fs::error | io::error | errors::invalid | errors::noentry | strconv::invalid | strconv::overflow | nomem) = {
match (find_in_indexes(r, id)) { case let pl: pack_loc => let res = read_resolved_body_at_ofs(r, pl.pack_rel, pl.ofs); free(pl.pack_rel); return res; case errors::noentry => return read_loose_typed(r, id); case let fe: fs::error => return fe; case let ioe: io::error => return ioe; case let inv: errors::invalid => return inv; case nomem => return nomem; }; }; fn read_resolved_body_at_ofs( r: repo, pack_rel: str, ofs: u64,
) ((u8, []u8) | fs::error | io::error | errors::invalid | errors::noentry | strconv::invalid | strconv::overflow | nomem) = {
) ((objtype, []u8) | fs::error | io::error | errors::invalid | errors::noentry | strconv::invalid | strconv::overflow | nomem) = {
let h = fs::open(r.root, pack_rel)?; defer io::close(h)!; let header: [12]u8 = [0...]; match (io::readall(h, header)) { case size => void; case io::EOF => return errors::invalid; case let ioe: io::error => return ioe; }; let magic = endian::begetu32(header[..4]); let ver = endian::begetu32(header[4..8]); if (magic != PACK_MAGIC || ver != PACK_V2) { return errors::invalid; }; io::seek(h, (ofs: i64), io::whence::SET)?; match (read_obj_header(h)) { case let t: (u8, size, size) =>
switch (t.0) { case OBJ_COMMIT =>
switch ((t.0: objtype)) { case objtype::OBJ_COMMIT =>
let body = inflate_section(h)?;
return (OBJ_COMMIT, body); case OBJ_TREE =>
return (objtype::OBJ_COMMIT, body); case objtype::OBJ_TREE =>
let body = inflate_section(h)?;
return (OBJ_TREE, body); case OBJ_BLOB =>
return (objtype::OBJ_TREE, body); case objtype::OBJ_BLOB =>
let body = inflate_section(h)?;
return (OBJ_BLOB, body); case OBJ_REF_DELTA =>
return (objtype::OBJ_BLOB, body); case objtype::OBJ_TAG => let body = inflate_section(h)?; return (objtype::OBJ_TAG, body); case objtype::OBJ_REF_DELTA =>
let base: oid = [0...]; match (io::readall(h, base)) { case size => void; case io::EOF => return errors::invalid; case let ioe: io::error => return ioe; }; let delta = inflate_section(h)?; defer free(delta); let bt = read_resolved_body_by_id(r, base)?; let out = apply_delta(bt.1, delta)?; return (bt.0, out);
case OBJ_OFS_DELTA =>
case objtype::OBJ_OFS_DELTA =>
let dist = read_ofs_distance(h)?; let base_ofs: u64 = if (ofs > dist) { yield ofs - dist; } else { yield 0u64; }; if (base_ofs == 0u64) { return errors::invalid; }; let delta = inflate_section(h)?; defer free(delta); let bt = read_resolved_body_at_ofs(r, pack_rel, base_ofs)?; let out = apply_delta(bt.1, delta)?; return (bt.0, out); case => return errors::invalid; }; case let ioe: io::error => return ioe; case => return errors::invalid; }; }; fn apply_delta(base: []u8, delta: []u8) ([]u8 | errors::invalid | nomem) = { let i = 0z; let srcsz = read_varint(delta, &i)?; let dstsz = read_varint(delta, &i)?; if (srcsz != len(base)) { return errors::invalid; }; let out: []u8 = alloc([0u8...], dstsz)?; let outpos = 0z; for (i < len(delta)) { let op = delta[i]; i += 1z; if ((op & 0x80u8) != 0u8) { let off = 0z; let n = 0z; if ((op & 0x01u8) != 0u8) { if (i >= len(delta)) { return errors::invalid; }; off |= (delta[i]: size); i += 1z; }; if ((op & 0x02u8) != 0u8) { if (i >= len(delta)) { return errors::invalid; }; off |= (delta[i]: size) << 8z; i += 1z; }; if ((op & 0x04u8) != 0u8) { if (i >= len(delta)) { return errors::invalid; }; off |= (delta[i]: size) << 16z; i += 1z; }; if ((op & 0x08u8) != 0u8) { if (i >= len(delta)) { return errors::invalid; }; off |= (delta[i]: size) << 24z; i += 1z; }; if ((op & 0x10u8) != 0u8) { if (i >= len(delta)) { return errors::invalid; }; n |= (delta[i]: size); i += 1z; }; if ((op & 0x20u8) != 0u8) { if (i >= len(delta)) { return errors::invalid; }; n |= (delta[i]: size) << 8z; i += 1z; }; if ((op & 0x40u8) != 0u8) { if (i >= len(delta)) { return errors::invalid; }; n |= (delta[i]: size) << 16z; i += 1z; }; if (n == 0z) { n = 0x10000z; }; if (off + n > len(base) || outpos + n > len(out)) { return errors::invalid; }; out[outpos .. outpos + n] = base[off .. off + n]; outpos += n; } else if (op != 0u8) { let n = (op: size); if (i + n > len(delta) || outpos + n > len(out)) { return errors::invalid; }; out[outpos .. outpos + n] = delta[i .. i + n]; i += n; outpos += n; } else { return errors::invalid; }; }; if (outpos != len(out)) { return errors::invalid; }; return out; }; fn read_varint(buf: []u8, ip: *size) (size | errors::invalid) = { let res = 0z; let shift = 0z; for (true) { if (*ip >= len(buf)) { return errors::invalid; }; let b = buf[*ip]; *ip += 1z; res |= ((b & 0x7fu8): size) << shift; if ((b & 0x80u8) == 0u8) { break; }; shift += 7z; }; return res; };