Lindenii Project Forge
Warning: Due to various recent migrations, viewing non-HEAD refs may be broken.
/git/loose.ha (raw)
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;
// Find the path to a loose object with the given ID,
// relative to the repository root.
fn loose_path_resolve(id: oid) (str | nomem) = {
const hex = oid_stringify(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 loose_read(
r: repo,
id: oid,
) (object | fs::error | io::error | errors::invalid | strconv::invalid | strconv::overflow | nomem | utf8::invalid) = {
const rel = loose_path_resolve(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(header[..sp])?;
const szs = strings::fromutf8(header[sp + 1z ..])?;
const expect = strconv::stoz(szs)?;
if (expect != len(body)) {
return errors::invalid;
};
if (!object_verify_oid(buf, id)) {
return errors::invalid;
};
if (ty == "blob") {
const b = blob_parse(body)?;
return (b: object);
} else if (ty == "tree") {
const t = tree_parse(body)?;
return (t: object);
} else if (ty == "commit") {
const c = commit_parse(body)?;
return (c: object);
} else if (ty == "tag") {
const g = tag_parse(body)?;
return (g: object);
};
return errors::invalid;
};
// Reads a loose object from the repository by its ID,
// returning its type and raw data.
export fn loose_read_typed(
r: repo,
id: oid,
) ((objtype, []u8) | fs::error | io::error | errors::invalid | errors::noentry | strconv::invalid | strconv::overflow | nomem) = {
const rel = loose_path_resolve(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(header[..sp])?;
const szs = strings::fromutf8(header[sp + 1z ..])?;
const expect = strconv::stoz(szs)?;
if (expect != len(body)) {
return errors::invalid;
};
let code: objtype = objtype::OBJ_INVALID;
if (ty == "blob") {
code = objtype::OBJ_BLOB;
} else if (ty == "tree") {
code = objtype::OBJ_TREE;
} else if (ty == "commit") {
code = objtype::OBJ_COMMIT;
} else if (ty == "tag") {
code = objtype::OBJ_TAG;
} else {
return errors::invalid;
};
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");
};
};