From 3d0a34dabf433f2334b8fd965980b07bb2936147 Mon Sep 17 00:00:00 2001 From: Runxi Yu Date: Sun, 14 Sep 2025 03:59:14 +0800 Subject: [PATCH] Basic commit and ident parsing --- git/ident.ha | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++ git/obj_commit.ha | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++--- git/person.ha | 12 ------------ diff --git a/git/ident.ha b/git/ident.ha new file mode 100644 index 0000000000000000000000000000000000000000..9560e4eec8677f9af21fa8c8f5a88544793a4c06 --- /dev/null +++ b/git/ident.ha @@ -0,0 +1,66 @@ +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 }; +}; diff --git a/git/obj_commit.ha b/git/obj_commit.ha index 7f47eb6c62fc6d5dd906e4b3501165e9ef45526e..3efc2c29337e8362e939b77f1fb717c73476b6a4 100644 --- a/git/obj_commit.ha +++ b/git/obj_commit.ha @@ -1,17 +1,107 @@ +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; +}; + diff --git a/git/person.ha b/git/person.ha deleted file mode 100644 index 5f981a8e9053ff9e494ad5e802409777cef1494a..0000000000000000000000000000000000000000 --- a/git/person.ha +++ /dev/null @@ -1,12 +0,0 @@ -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); -}; - -- 2.48.1