Lindenii Project Forge
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;
};