Lindenii Project Forge
Warning: Due to various recent migrations, viewing non-HEAD refs may be broken.
/git/obj_tree.ha (raw)
use bytes;
use crypto::sha256;
use errors;
use fs;
use io;
use strconv;
use strings;
// A Git tree object.
export type tree = struct {
oid: oid,
entries: []tree_entry,
};
// Frees resources associated with a [[tree]].
export fn tree_finish(t: tree) void = {
for (let entry .. t.entries) {
tree_entry_finish(entry);
};
free(t.entries);
};
// A single entry in a Git tree. In general, the oid
// either refers to a blob (file) or another tree (directory).
export type tree_entry = struct {
mode: u32,
name: []u8,
oid: oid,
};
// Frees resources associated with a [[tree_entry]].
export fn tree_entry_finish(te: tree_entry) void = {
free(te.name);
};
// Parses a tree from its raw data and object ID.
export fn parse_tree(id: oid, body: []u8) (tree | errors::invalid | strconv::invalid | strconv::overflow | nomem) = {
let entries: []tree_entry = [];
let i = 0z;
for (i < len(body)) {
const sp = match (bytes::index(body[i..], ' ')) {
case let j: size => yield j;
case void => return errors::invalid;
};
const mode_b = body[i .. i+sp];
i += sp + 1z;
const nul = match (bytes::index(body[i..], 0u8)) {
case let j: size => yield j;
case void => return errors::invalid;
};
const name_b = body[i .. i+nul];
i += nul + 1z;
if (i + sha256::SZ > len(body)) return errors::invalid;
let child: oid = [0...];
child[..] = body[i .. i+sha256::SZ];
i += sha256::SZ;
const mode_s = strings::fromutf8_unsafe(mode_b);
const mode = strconv::stou32(mode_s, strconv::base::OCT)?;
const name = alloc(name_b...)?;
append(entries, tree_entry { mode = mode, name = name, oid = child })!;
};
return tree { oid = id, entries = entries };
};
// Looks up a tree entry by name.
fn tree_entry_by_name_raw(t: *const tree, name: []const u8) (*const tree_entry | void) = {
for (let i = 0z; i < len(t.entries); i += 1z) {
if (bytes::equal(t.entries[i].name, name)) {
return &t.entries[i];
};
};
return void;
};
// Recursively looks up a tree or blob at the given path,
export fn tree_at_path(
r: repo,
root: *const tree,
path: const []u8,
) (tree | blob | errors::invalid | fs::error | io::error | strconv::invalid | strconv::overflow | nomem) = {
if (len(path) == 0) {
match (read_object(r, root.oid)) {
case let t: tree =>
return t;
case =>
return errors::invalid;
};
};
let cur: tree = *root;
let owned_cur = false;
defer if (owned_cur) {
tree_finish(cur);
};
let i = 0z;
for (i < len(path)) {
let j = match (bytes::index(path[i..], '/')) {
case let k: size => yield i + k;
case void => yield len(path);
};
if (j == i) {
return errors::invalid;
};
let comp = path[i..j];
let entp = tree_entry_by_name_raw(&cur, comp);
let ent: *const tree_entry = match (entp) {
case let p: *const tree_entry =>
yield p;
case void =>
return errors::invalid;
};
let last = (j == len(path));
if (last) {
match (read_object(r, ent.oid)) {
case let t: tree =>
if (owned_cur) {
tree_finish(cur);
};
return t;
case let b: blob =>
if (owned_cur) {
tree_finish(cur);
};
return b;
case =>
if (owned_cur) {
tree_finish(cur);
};
return errors::invalid;
};
} else {
match (read_object(r, ent.oid)) {
case let t: tree =>
if (owned_cur) {
tree_finish(cur);
};
cur = t;
owned_cur = true;
case =>
if (owned_cur) {
tree_finish(cur);
};
return errors::invalid;
};
i = j + 1z;
if (i >= len(path)) {
return errors::invalid;
};
};
};
return errors::invalid;
};