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_commit.ha (raw)

use bytes;
use encoding::utf8;
use errors;
use strconv;
use strings;

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

// Frees resources associated with a [[commit]].
export fn commit_finish(c: commit) void = {
	free(c.parents);
	ident_finish(c.author);
	ident_finish(c.committer);
	free(c.message);
};

// Parses a commit from its raw data and object ID.
export fn commit_parse(
	body: []u8,
) (commit | errors::invalid | strconv::invalid | strconv::overflow | utf8::invalid | nomem) = {
	let c = commit {
		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(line[5..])?;
			match (oid_parse(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(line[7..])?;
			match (oid_parse(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 = ident_parse(line[7..])?;
			ident_finish(c.author);
			c.author = per;
		} else if (bytes::hasprefix(line, strings::toutf8("committer "))) {
			const per = ident_parse(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;
};

// Serializes a commit into its uncompressed on-disk format.
export fn commit_serialize(c: commit) ([]u8 | nomem) = {
	const treehex = oid_stringify(c.tree)?;
	defer free(treehex);

	let parenthex: []const str = [];
	for (let i = 0z; i < len(c.parents); i += 1z) {
		const hex = oid_stringify(c.parents[i])?;
		append(parenthex, hex)!;
	};

	let authorb = ident_serialize(c.author)?;
	defer free(authorb);

	let committerb = ident_serialize(c.committer)?;
	defer free(committerb);

	let bodylen = 0z;
	bodylen += 5z + len(treehex) + 1z;
	for (let i = 0z; i < len(parenthex); i += 1z) {
		bodylen += 7z + len(parenthex[i]) + 1z;
	};
	bodylen += 7z + len(authorb) + 1z;
	bodylen += 10z + len(committerb) + 1z;
	bodylen += 1z + len(c.message);

	const sizes = strconv::ztos(bodylen);
	const ty = strings::toutf8("commit ");
	const sizesb = strings::toutf8(sizes);
	let hlen = len(ty) + len(sizesb) + 1z;

	let out = alloc([0u8...], hlen + bodylen)?;
	let pos = 0z;

	out[pos .. pos + len(ty)] = ty;
	pos += len(ty);
	out[pos .. pos + len(sizesb)] = sizesb;
	pos += len(sizesb);
	out[pos] = 0u8;
	pos += 1z;

	{
		const pre = strings::toutf8("tree ");
		out[pos .. pos + len(pre)] = pre; pos += len(pre);
		const hb = strings::toutf8(treehex);
		out[pos .. pos + len(hb)] = hb; pos += len(hb);
		out[pos] = '\n'; pos += 1z;
	};

	for (let i = 0z; i < len(parenthex); i += 1z) {
		const pre = strings::toutf8("parent ");
		out[pos .. pos + len(pre)] = pre; pos += len(pre);
		const hb = strings::toutf8(parenthex[i]);
		out[pos .. pos + len(hb)] = hb; pos += len(hb);
		out[pos] = '\n'; pos += 1z;
	};

	{
		const pre = strings::toutf8("author ");
		out[pos .. pos + len(pre)] = pre; pos += len(pre);
		out[pos .. pos + len(authorb)] = authorb; pos += len(authorb);
		out[pos] = '\n'; pos += 1z;
	};

	{
		const pre = strings::toutf8("committer ");
		out[pos .. pos + len(pre)] = pre; pos += len(pre);
		out[pos .. pos + len(committerb)] = committerb; pos += len(committerb);
		out[pos] = '\n'; pos += 1z;
	};

	out[pos] = '\n';
	pos += 1z;
	out[pos .. pos + len(c.message)] = c.message;

	for (let i = 0z; i < len(parenthex); i += 1z) {
		free(parenthex[i]);
	};

	return out;
};