Lindenii Project Forge
Login

hare-git

Git library for Hare
Commit info
ID
3d0a34dabf433f2334b8fd965980b07bb2936147
Author
Runxi Yu <me@runxiyu.org>
Author date
Sun, 14 Sep 2025 03:59:14 +0800
Committer
Runxi Yu <me@runxiyu.org>
Committer date
Sun, 14 Sep 2025 03:59:14 +0800
Actions
Basic commit and ident parsing
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 errors;
use strconv;
use strings;

export type commit = struct {
	oid: oid,
	tree: oid,
	parents: []oid,
	author: person,
	committer: person,
	author: ident,
	committer: ident,
	message: []u8,
	// other raw headers?
};

export fn commit_finish(c: commit) void = {
	free(c.parents);
	person_finish(c.author);
	person_finish(c.committer);
	ident_finish(c.author);
	ident_finish(c.committer);
	free(c.message);
};

export fn parse_commit(
	id: oid,
	body: []u8,
) (commit | errors::invalid | strconv::invalid | strconv::overflow | nomem) = {
	let c = commit {
		oid = id,
		tree = [0...],
		parents = [],
		author = ident { name = [], email = [], when = 0, ofs = 0 },
		committer = ident { name = [], email = [], when = 0, ofs = 0 },
		message = [],
	};

	let i = 0z;
	for (true) {
		let mrel = bytes::index(body[i..], '\n');
		if (mrel is void) {
			return errors::invalid;
		};
		let rel = mrel: size;

		const line = body[i .. i + rel];

		if (len(line) == 0) {
			i += rel + 1z;
			break;
		};

		if (bytes::hasprefix(line, strings::toutf8("tree "))) {
			const hex = strings::fromutf8_unsafe(line[5..]);
			match (parse_oid(hex)) {
			case let o: oid =>
				c.tree = o;
			case nomem =>
				return nomem;
			case =>
				return errors::invalid;
			};
		} else if (bytes::hasprefix(line, strings::toutf8("parent "))) {
			const hex = strings::fromutf8_unsafe(line[7..]);
			match (parse_oid(hex)) {
			case let o: oid =>
				append(c.parents, o)!;
			case nomem =>
				return nomem;
			case =>
				return errors::invalid;
			};
		} else if (bytes::hasprefix(line, strings::toutf8("author "))) {
			const per = parse_ident(line[7..])?;
			ident_finish(c.author);
			c.author = per;
		} else if (bytes::hasprefix(line, strings::toutf8("committer "))) {
			const per = parse_ident(line[10..])?;
			ident_finish(c.committer);
			c.committer = per;
		} else if (
			bytes::hasprefix(line, strings::toutf8("gpgsig ")) ||
			bytes::hasprefix(line, strings::toutf8("gpgsig-sha256 "))
		) {
			i += rel + 1z;
			for (true) {
				if (i >= len(body)) {
					return errors::invalid;
				};
				let mnext = bytes::index(body[i..], '\n');
				if (mnext is void) {
					return errors::invalid;
				};
				let next = mnext: size;
				if (body[i] != ' ') {
					break;
				};
				i += next + 1z;
			};
			continue;
		};

		i += rel + 1z;
	};

	c.message = alloc(body[i..]...)?;
	return c;
};

export type person = struct {
	name: []u8,
	email: []u8,
	when: i64,
	ofs: i32,
};

export fn person_finish(p: person) void = {
	free(p.name);
	free(p.email);
};