Lindenii Project Forge
Login

hare-git

Git library for Hare
Commit info
ID
8bc750d61e10bcaf19e0b2cf29d951e738ab6d77
Author
Runxi Yu <me@runxiyu.org>
Author date
Sun, 14 Sep 2025 18:00:21 +0800
Committer
Runxi Yu <me@runxiyu.org>
Committer date
Sun, 14 Sep 2025 18:00:21 +0800
Actions
Add some tree and log handling functions
use fs;
use io;
use errors;
use strconv;
use encoding::utf8;

export type error = (
	fs::error |
	io::error |
	errors::invalid |
	strconv::invalid |
	strconv::overflow |
	utf8::invalid |
	nomem
);
use bytes;
use crypto::sha256;
use errors;
use fs;
use io;
use strconv;
use strings;

export type tree = struct {
	oid: oid,
	entries: []tree_entry,
};

export fn tree_finish(t: tree) void = {
	for (let entry .. t.entries) {
		tree_entry_finish(entry);
	};
	free(t.entries);
};

export type tree_entry = struct {
	mode: u32,
	name: []u8,
	oid: oid,
};

export fn tree_entry_finish(te: tree_entry) void = {
	free(te.name);
};

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

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

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;
};
use bytes;
use errors;
use fs;
use io;
use strings;

export fn resolve_ref(r: repo, refname: const str) (oid | error) = {
	{
		match (fs::open(r.root, refname)) {
		case let fh: io::handle =>
			defer io::close(fh)!;
			let b = io::drain(fh)?;
			defer free(b);

			let n = if (len(b) > 0 && b[len(b)-1] == '\n') {
				yield len(b) - 1z;
			} else {
				yield len(b);
			};

			let s = strings::fromutf8(b[..n])?;
			return parse_oid(s)?;
		case let fe: fs::error =>
			if (!(fe is errors::noentry)) {
				return fe;
			};
		};
	};

	match (fs::open(r.root, "packed-refs")) {
	case let fh: io::handle =>
		defer io::close(fh)!;

		let pr = io::drain(fh)?;
		defer free(pr);

		let want = strings::toutf8(refname);
		let i = 0z;
		for (i < len(pr)) {
			let e = match (bytes::index(pr[i..], '\n')) {
			case let j: size => yield i + j;
			case void => yield len(pr);
			};
			let line = pr[i..e];

			if (len(line) >= 1 && (line[0] == '#' || line[0] == '^')) {
				void;
			} else if (len(line) >= 66z) {
				let sp = bytes::index(line, ' ');
				if (sp is size) {
					let k = (sp: size);
					if (k == 64z && k + 1z < len(line)) {
						let name_b = line[k + 1z..];
						if (bytes::equal(name_b, want)) {
							let hexs = strings::fromutf8(line[..k])?;
							return parse_oid(hexs)?;
						};
					};
				};
			};

			i = if (e < len(pr) && pr[e] == '\n') {
				yield e + 1z;
			} else {
				yield e;
			};
		};

		return errors::invalid;

	case let fe: fs::error =>
		if (fe is errors::noentry) {
			return errors::invalid;
		};
		return fe;
	};
};

export fn head_oid(r: repo) (oid | error) = {
	let fh = fs::open(r.root, "HEAD")?;
	defer io::close(fh)!;

	let b = io::drain(fh)?;
	defer free(b);

	let n = if (len(b) > 0 && b[len(b)-1] == '\n') {
		yield len(b) - 1z;
	} else {
		yield len(b);
	};
	let line = b[..n];

	const pfx = strings::toutf8("ref: ");
	if (len(line) >= len(pfx) && bytes::hasprefix(line, pfx)) {
		let rn = strings::fromutf8(line[len(pfx)..])?;
		return resolve_ref(r, rn);
	};

	let s = strings::fromutf8(line)?;
	return parse_oid(s)?;
};
use errors;
use fs;
use io;
use strconv;

export fn recent_commits(r: repo, start: oid, limit: int)
	([]commit | error) = {
	if (limit <= 0) {
		let empty: []commit = [];
		return empty;
	};

	let out: []commit = [];
	let cur = start;

	for (let n = 0; n < limit; n += 1) {
		match (read_object(r, cur)) {
		case let c: commit =>
			append(out, c)!;
			if (len(c.parents) == 0) {
				return out;
			};
			cur = c.parents[0];
		case let b: blob =>
			blob_finish(b);
			return errors::invalid;
		case let t: tree =>
			tree_finish(t);
			return errors::invalid;
		case let e: (fs::error | io::error | errors::invalid | strconv::invalid | strconv::overflow | nomem) =>
			return (e: error);
		};
	};

	return out;
};