Repositories / ocaml-git.git

ocaml-git.git

Clone (read-only): git clone http://git.guha-anderson.com/git/ocaml-git.git

Branch

Expose commit_lookup and commit_diff

commit_lookup resolves an oid to the existing commit record. commit_diff
returns the unified-diff text for a commit against its first parent
(empty tree for a root commit), via git_diff_tree_to_tree and
git_diff_to_buf with GIT_DIFF_FORMAT_PATCH. Lets callers render commit
diffs without shelling out to the git CLI, which is needed when the repo
is owned by a different user (libgit2 doesn't enforce safe.directory).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Author
Arjun Guha <a.guha@northeastern.edu>
Date
2026-05-04 12:02:23 -0400
Commit
147d064f80c74cb4b169a177d5647f02204ab812
src/ocaml_git.ml
index 04964c6..1c98110 100644
--- a/src/ocaml_git.ml
+++ b/src/ocaml_git.ml
@@ -109,6 +109,8 @@ external raw_tree_entry : repo_handle -> string -> string -> tree_entry_raw = "k
 external raw_tree : repo_handle -> string -> string -> tree_entry_raw list = "kg_tree"
 external raw_blob : repo_handle -> string -> string = "kg_blob"
 external raw_commits : repo_handle -> string -> int -> commit_raw list = "kg_commits"
+external raw_commit_lookup : repo_handle -> string -> commit_raw = "kg_commit_lookup"
+external raw_commit_diff : repo_handle -> string -> string = "kg_commit_diff"
 external raw_status : repo_handle -> status_raw list = "kg_status"
 external raw_index : repo_handle -> index_handle = "kg_index"
 external raw_index_size : index_handle -> int = "kg_index_size"
@@ -243,6 +245,14 @@ let commits ?(limit : int = 50) (repo : t) (branch : string) : commit list =
   require_open repo;
   raw_commits repo.handle branch limit |> List.map commit_of_raw
 
+let commit_lookup (repo : t) (id : oid) : commit =
+  require_open repo;
+  raw_commit_lookup repo.handle id |> commit_of_raw
+
+let commit_diff (repo : t) (id : oid) : string =
+  require_open repo;
+  raw_commit_diff repo.handle id
+
 let index (repo : t) : index =
   require_open repo;
   { repo; handle = raw_index repo.handle; index_closed = false }
src/ocaml_git.mli
index 58a9d78..89f16a5 100644
--- a/src/ocaml_git.mli
+++ b/src/ocaml_git.mli
@@ -89,6 +89,8 @@ val tree_entry : ?commit:commit -> t -> string -> tree_entry
 val tree : ?commit:commit -> ?path:string -> t -> unit -> tree_listing
 val blob : ?commit:commit -> t -> string -> blob
 val commits : ?limit:int -> t -> string -> commit list
+val commit_lookup : t -> oid -> commit
+val commit_diff : t -> oid -> string
 
 val index : t -> index
 val index_size : index -> int
src/ocaml_git_stubs.c
index f91093b..ffcefcb 100644
--- a/src/ocaml_git_stubs.c
+++ b/src/ocaml_git_stubs.c
@@ -400,6 +400,54 @@ CAMLprim value kg_blob(value repo_v, value oid_v) {
   CAMLreturn(result);
 }
 
+CAMLprim value kg_commit_lookup(value repo_v, value oid_v) {
+  CAMLparam2(repo_v, oid_v);
+  CAMLlocal1(result);
+  git_oid oid;
+  git_commit *commit = NULL;
+  oid_from_string(&oid, oid_v);
+  check(git_commit_lookup(&commit, repo_val(repo_v), &oid), "git_commit_lookup");
+  result = copy_commit(commit);
+  git_commit_free(commit);
+  CAMLreturn(result);
+}
+
+/* Unified-diff text for a commit against its first parent (or the empty tree
+   for a root commit).  Mirrors `git show --format= <id>` output. */
+CAMLprim value kg_commit_diff(value repo_v, value oid_v) {
+  CAMLparam2(repo_v, oid_v);
+  CAMLlocal1(result);
+  git_repository *repo = repo_val(repo_v);
+  git_oid oid;
+  git_commit *commit = NULL;
+  git_commit *parent = NULL;
+  git_tree *new_tree = NULL;
+  git_tree *old_tree = NULL;
+  git_diff *diff = NULL;
+  git_diff_options opts;
+  git_buf buf = GIT_BUF_INIT;
+
+  oid_from_string(&oid, oid_v);
+  check(git_commit_lookup(&commit, repo, &oid), "git_commit_lookup");
+  check(git_commit_tree(&new_tree, commit), "git_commit_tree");
+  if (git_commit_parentcount(commit) > 0) {
+    check(git_commit_parent(&parent, commit, 0), "git_commit_parent");
+    check(git_commit_tree(&old_tree, parent), "git_commit_tree");
+  }
+  check(git_diff_options_init(&opts, GIT_DIFF_OPTIONS_VERSION), "git_diff_options_init");
+  check(git_diff_tree_to_tree(&diff, repo, old_tree, new_tree, &opts), "git_diff_tree_to_tree");
+  check(git_diff_to_buf(&buf, diff, GIT_DIFF_FORMAT_PATCH), "git_diff_to_buf");
+
+  result = caml_copy_string(buf.ptr ? buf.ptr : "");
+  git_buf_dispose(&buf);
+  git_diff_free(diff);
+  if (old_tree) git_tree_free(old_tree);
+  git_tree_free(new_tree);
+  if (parent) git_commit_free(parent);
+  git_commit_free(commit);
+  CAMLreturn(result);
+}
+
 CAMLprim value kg_commits(value repo_v, value branch_name_v, value limit_v) {
   CAMLparam3(repo_v, branch_name_v, limit_v);
   CAMLlocal3(list, commit_v, result);