Lindenii Project Forge
Login

hare-git

Git library for Hare

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;
};