From 8bc750d61e10bcaf19e0b2cf29d951e738ab6d77 Mon Sep 17 00:00:00 2001 From: Runxi Yu Date: Sun, 14 Sep 2025 18:00:21 +0800 Subject: [PATCH] Add some tree and log handling functions --- git/errors.ha | 15 +++++++++++++++ git/obj_tree.ha | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++++ git/refs.ha | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++++ git/walk.ha | 36 ++++++++++++++++++++++++++++++++++++ diff --git a/git/errors.ha b/git/errors.ha new file mode 100644 index 0000000000000000000000000000000000000000..b1e3d64c870647ecb65165f3cf4d893c8e6bf94c --- /dev/null +++ b/git/errors.ha @@ -0,0 +1,15 @@ +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 +); diff --git a/git/obj_tree.ha b/git/obj_tree.ha index 4ff12cf84845c3b1374b45c3a0b38b652ea1fbca..41bd20ea740c7f4c186e487be966a60960e00061 100644 --- a/git/obj_tree.ha +++ b/git/obj_tree.ha @@ -1,6 +1,8 @@ use bytes; use crypto::sha256; use errors; +use fs; +use io; use strconv; use strings; @@ -59,3 +61,95 @@ }; 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; +}; diff --git a/git/refs.ha b/git/refs.ha new file mode 100644 index 0000000000000000000000000000000000000000..6b6d0d3f9645559fdb5e0afc29c8e6d0737255cf --- /dev/null +++ b/git/refs.ha @@ -0,0 +1,101 @@ +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)?; +}; diff --git a/git/walk.ha b/git/walk.ha new file mode 100644 index 0000000000000000000000000000000000000000..8ada45a039a0ea580dea219cd289b436e7bdfda2 --- /dev/null +++ b/git/walk.ha @@ -0,0 +1,36 @@ +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; +}; -- 2.48.1