Lindenii Project Forge
Basic loose object parsing
/hare-git
use bytes; use errors; use strings; use strconv;
export type ident = struct { name: []u8, email: []u8, when: i64, ofs: i32, }; export fn ident_finish(p: ident) void = { free(p.name); free(p.email); }; fn parse_ident( line: []u8, ) (ident | errors::invalid | strconv::invalid | strconv::overflow | nomem) = { let mlt = bytes::index(line, '<'); if (mlt is void) { return errors::invalid; }; let lt = mlt: size; let mgt_rel = bytes::index(line[lt + 1z..], '>'); if (mgt_rel is void) { return errors::invalid; }; let gt_rel = mgt_rel: size; const gt = lt + 1z + gt_rel; const name_b = line[..lt]; const email_b = line[lt + 1z .. gt]; let rest = line[gt + 1z..]; if (len(rest) == 0 || rest[0] != ' ') { return errors::invalid; }; rest = rest[1..]; let msp = bytes::index(rest, ' '); if (msp is void) { return errors::invalid; }; let sp = msp: size; const when_s = strings::fromutf8_unsafe(rest[..sp]); const tz_b = rest[sp + 1z..]; if (len(tz_b) < 5) { return errors::invalid; }; const when = strconv::stoi64(when_s)?; let sign: i32 = 1; if (tz_b[0] == '-') { sign = -1; }; const hh = strconv::stou32(strings::fromutf8_unsafe(tz_b[1..3]), strconv::base::DEC)?; const mm = strconv::stou32(strings::fromutf8_unsafe(tz_b[3..5]), strconv::base::DEC)?; const mins: i32 = (hh: i32) * 60 + (mm: i32); const ofs: i32 = sign * mins; let name = alloc(name_b...)?; let email = alloc(email_b...)?; return ident { name = name, email = email, when = when, ofs = ofs }; };
use bytes; use compress::zlib; use errors; use fmt; use fs; use io; use strconv; use strings; 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); }; export fn read_loose( r: repo, id: oid, ) (object | fs::error | io::error | errors::invalid | strconv::invalid | strconv::overflow | nomem) = { 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(id, body)?; return (b: object); } else if (ty == "tree") { const t = parse_tree(id, body)?; return (t: object); } else if (ty == "commit") { const c = parse_commit(id, body)?; return (c: object); } else { return errors::invalid; }; };
use bytes; use crypto::sha256; use errors; use fs; use hash; use io; use strconv;
export type object = (blob | tree | commit); 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 b: blob => blob_finish(b); case let t: tree => tree_finish(t); case let c: commit => commit_finish(c); }; }; 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;
}; };
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; // fallthrough to the packfile thing } 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 errors; use fs; use io; use strconv; export fn read_packed( r: repo, id: oid, ) (object | fs::error | io::error | errors::invalid | strconv::invalid | strconv::overflow | errors::noentry | nomem) = { return errors::noentry; };
use fmt; use fs; use getopt; use io; use os; use strings; use git; fn usage(cmd: *getopt::command) void = { getopt::printusage(os::stderr, os::args[0], cmd.help)!; }; fn print_blob(b: git::blob) void = { io::write(os::stdout, b.data)!; }; fn print_tree(t: git::tree) void = { for (let i = 0z; i < len(t.entries); i += 1z) { const ent = t.entries[i]; const hex = git::oid_string(ent.oid)!; defer free(hex); const name = strings::fromutf8_unsafe(ent.name); fmt::printfln("{:o} {} {}", ent.mode, hex, name)!; }; }; fn print_ident(label: str, id: git::ident) void = { const name = strings::fromutf8_unsafe(id.name); const email = strings::fromutf8_unsafe(id.email); fmt::printfln("{} {} <{}> {} {}", label, name, email, id.when, id.ofs)!; }; fn print_commit(c: git::commit) void = { const treehex = git::oid_string(c.tree)!; defer free(treehex); fmt::printfln("tree {}", treehex)!; for (let i = 0z; i < len(c.parents); i += 1z) { const phex = git::oid_string(c.parents[i])!; defer free(phex); fmt::printfln("parent {}", phex)!; }; print_ident("author", c.author); print_ident("committer", c.committer); fmt::println("")!; const msg = strings::fromutf8_unsafe(c.message); fmt::println(msg)!; }; export fn main() void = { let repo_path: (str | void) = void; let cmd = getopt::parse( os::args, "Print a Git object by OID", ('r', "repo", "PATH to repo or .git"), "OID", ); defer getopt::finish(&cmd); for (let opt .. cmd.opts) { switch (opt.0) { case 'r' => repo_path = opt.1; case => abort(); }; }; if (len(cmd.args) != 1) { usage(&cmd); os::exit(2); }; if (repo_path is void) { usage(&cmd); os::exit(2); }; const rp = repo_path: str; let r = match (git::open(rp)) { case let rr: git::repo => yield rr; case let fe: fs::error => fmt::errorfln("open repo: {}", fs::strerror(fe))!; os::exit(1); }; defer git::close(r); const oidhex = cmd.args[0]; let id = match (git::parse_oid(oidhex)) { case let o: git::oid => yield o; case => fmt::errorfln("invalid oid: {}", oidhex)!; os::exit(1); }; let obj = match (git::read_object(r, id)) { case let o: git::object => yield o; case let fe: fs::error => fmt::errorfln("fs error: {}", fs::strerror(fe))!; os::exit(1); case let ioe: io::error => fmt::errorfln("io error: {}", io::strerror(ioe))!; os::exit(1); case => fmt::errorfln("not a valid object: {}", oidhex)!; os::exit(1); }; match (obj) { case let b: git::blob => print_blob(b); git::blob_finish(b); case let t: git::tree => print_tree(t); git::tree_finish(t); case let c: git::commit => print_commit(c); git::commit_finish(c); }; };