From 6ed60c4a72d796d3de06addb48f957a3d7674ff8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Christoph=20R=C3=BC=C3=9Fler?=
 <christoph.ruessler@mailbox.org>
Date: Sat, 17 May 2025 10:07:43 +0200
Subject: [PATCH 1/2] Implement From<gix::ObjectId> for CommitId

---
 asyncgit/src/sync/commits_info.rs | 9 +++++++++
 asyncgit/src/sync/logwalker.rs    | 5 +----
 2 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/asyncgit/src/sync/commits_info.rs b/asyncgit/src/sync/commits_info.rs
index 111a2b9bce..e2835320d4 100644
--- a/asyncgit/src/sync/commits_info.rs
+++ b/asyncgit/src/sync/commits_info.rs
@@ -84,6 +84,15 @@ impl From<Oid> for CommitId {
 	}
 }
 
+impl From<gix::ObjectId> for CommitId {
+	fn from(object_id: gix::ObjectId) -> Self {
+		#[allow(clippy::expect_used)]
+		let oid = Oid::from_bytes(object_id.as_bytes()).expect("`Oid::from_bytes(object_id.as_bytes())` is expected to never fail");
+
+		Self::new(oid)
+	}
+}
+
 ///
 #[derive(Debug, Clone)]
 pub struct CommitInfo {
diff --git a/asyncgit/src/sync/logwalker.rs b/asyncgit/src/sync/logwalker.rs
index b42447642c..3865368b0d 100644
--- a/asyncgit/src/sync/logwalker.rs
+++ b/asyncgit/src/sync/logwalker.rs
@@ -161,10 +161,7 @@ impl<'a> LogWalkerWithoutFilter<'a> {
 		let mut count = 0_usize;
 
 		while let Some(Ok(info)) = self.walk.next() {
-			let bytes = info.id.as_bytes();
-			let commit_id: CommitId = Oid::from_bytes(bytes)?.into();
-
-			out.push(commit_id);
+			out.push(info.id.into());
 
 			count += 1;
 

From d8eb1f353f03c0cb4b1eccd536e930832de37729 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Christoph=20R=C3=BC=C3=9Fler?=
 <christoph.ruessler@mailbox.org>
Date: Sat, 17 May 2025 17:59:54 +0200
Subject: [PATCH 2/2] Use gitoxide in get_commits_info and get_commit_info

---
 Cargo.lock                        |  13 ++++
 asyncgit/Cargo.toml               |   1 +
 asyncgit/src/error.rs             |  10 +++
 asyncgit/src/sync/commits_info.rs | 114 +++++++++++++++++++-----------
 4 files changed, 98 insertions(+), 40 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index ea206001a1..920324e33a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1224,6 +1224,7 @@ dependencies = [
  "gix-hashtable",
  "gix-index",
  "gix-lock",
+ "gix-mailmap",
  "gix-object",
  "gix-odb",
  "gix-pack",
@@ -1486,6 +1487,18 @@ dependencies = [
  "thiserror 2.0.12",
 ]
 
+[[package]]
+name = "gix-mailmap"
+version = "0.26.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff80d086d2684d30c5785cc37eba9d2cf817cfb33797ed999db9a359d16ab393"
+dependencies = [
+ "bstr",
+ "gix-actor",
+ "gix-date",
+ "thiserror 2.0.12",
+]
+
 [[package]]
 name = "gix-object"
 version = "0.48.0"
diff --git a/asyncgit/Cargo.toml b/asyncgit/Cargo.toml
index 7966e82e5c..6f2d5b1027 100644
--- a/asyncgit/Cargo.toml
+++ b/asyncgit/Cargo.toml
@@ -22,6 +22,7 @@ git2-hooks = { path = "../git2-hooks", version = ">=0.4" }
 gix = { version = "0.71.0", default-features = false, features = [
     "max-performance",
     "revision",
+    "mailmap"
 ] }
 log = "0.4"
 # git2 = { path = "../../extern/git2-rs", features = ["vendored-openssl"]}
diff --git a/asyncgit/src/error.rs b/asyncgit/src/error.rs
index 2113e93d6e..1578ed1e50 100644
--- a/asyncgit/src/error.rs
+++ b/asyncgit/src/error.rs
@@ -113,6 +113,16 @@ pub enum Error {
 	#[error("gix::revision::walk error: {0}")]
 	GixRevisionWalk(#[from] gix::revision::walk::Error),
 
+	///
+	#[error("gix::objs::decode::Error error: {0}")]
+	GixObjsDecode(#[from] gix::objs::decode::Error),
+
+	///
+	#[error("gix::object::find::existing::with_conversion::Error error: {0}")]
+	GixObjectFindExistingWithConversionError(
+		#[from] gix::object::find::existing::with_conversion::Error,
+	),
+
 	///
 	#[error("amend error: config commit.gpgsign=true detected.\ngpg signing is not supported for amending non-last commits")]
 	SignAmendNonLastCommit,
diff --git a/asyncgit/src/sync/commits_info.rs b/asyncgit/src/sync/commits_info.rs
index e2835320d4..ca20f6b4e5 100644
--- a/asyncgit/src/sync/commits_info.rs
+++ b/asyncgit/src/sync/commits_info.rs
@@ -1,11 +1,8 @@
 use std::fmt::Display;
 
 use super::RepoPath;
-use crate::{
-	error::Result,
-	sync::{commit_details::get_author_of_commit, repository::repo},
-};
-use git2::{Commit, Error, Oid};
+use crate::{error::Result, sync::repository::repo};
+use git2::Oid;
 use scopetime::scope_time;
 use unicode_truncate::UnicodeTruncateStr;
 
@@ -93,6 +90,12 @@ impl From<gix::ObjectId> for CommitId {
 	}
 }
 
+impl From<CommitId> for gix::ObjectId {
+	fn from(id: CommitId) -> Self {
+		Self::from_bytes_or_panic(id.0.as_bytes())
+	}
+}
+
 ///
 #[derive(Debug, Clone)]
 pub struct CommitInfo {
@@ -114,34 +117,36 @@ pub fn get_commits_info(
 ) -> Result<Vec<CommitInfo>> {
 	scope_time!("get_commits_info");
 
-	let repo = repo(repo_path)?;
-	let mailmap = repo.mailmap()?;
-
-	let commits = ids
-		.iter()
-		.map(|id| repo.find_commit((*id).into()))
-		.collect::<std::result::Result<Vec<Commit>, Error>>()?
-		.into_iter();
-
-	let res = commits
-		.map(|c: Commit| {
-			let message = get_message(&c, Some(message_length_limit));
-			let author = get_author_of_commit(&c, &mailmap)
-				.name()
-				.map_or_else(
-					|| String::from("<unknown>"),
-					String::from,
-				);
-			CommitInfo {
+	let repo: gix::Repository =
+				gix::ThreadSafeRepository::discover_with_environment_overrides(repo_path.gitpath())
+						.map(Into::into)?;
+	let mailmap = repo.open_mailmap();
+
+	ids.iter()
+		.map(|id| -> Result<_> {
+			let commit = repo.find_commit(*id)?;
+			let commit_ref = commit.decode()?;
+
+			let message = gix_get_message(
+				&commit_ref,
+				Some(message_length_limit),
+			);
+
+			let author = commit_ref.author();
+
+			let author = mailmap.try_resolve(author).map_or_else(
+				|| author.name.into(),
+				|signature| signature.name,
+			);
+
+			Ok(CommitInfo {
 				message,
-				author,
-				time: c.time().seconds(),
-				id: CommitId(c.id()),
-			}
+				author: author.to_string(),
+				time: commit_ref.time().seconds,
+				id: *id,
+			})
 		})
-		.collect::<Vec<_>>();
-
-	Ok(res)
+		.collect()
 }
 
 ///
@@ -151,24 +156,35 @@ pub fn get_commit_info(
 ) -> Result<CommitInfo> {
 	scope_time!("get_commit_info");
 
-	let repo = repo(repo_path)?;
-	let mailmap = repo.mailmap()?;
+	let repo: gix::Repository =
+				gix::ThreadSafeRepository::discover_with_environment_overrides(repo_path.gitpath())
+						.map(Into::into)?;
+	let mailmap = repo.open_mailmap();
+
+	let commit = repo.find_commit(*commit_id)?;
+	let commit_ref = commit.decode()?;
+
+	let message = gix_get_message(&commit_ref, None);
 
-	let commit = repo.find_commit((*commit_id).into())?;
-	let author = get_author_of_commit(&commit, &mailmap);
+	let author = commit_ref.author();
+
+	let author = mailmap.try_resolve(author).map_or_else(
+		|| author.name.into(),
+		|signature| signature.name,
+	);
 
 	Ok(CommitInfo {
-		message: commit.message().unwrap_or("").into(),
-		author: author.name().unwrap_or("<unknown>").into(),
-		time: commit.time().seconds(),
-		id: CommitId(commit.id()),
+		message,
+		author: author.to_string(),
+		time: commit_ref.time().seconds,
+		id: commit.id().detach().into(),
 	})
 }
 
 /// if `message_limit` is set the message will be
 /// limited to the first line and truncated to fit
 pub fn get_message(
-	c: &Commit,
+	c: &git2::Commit,
 	message_limit: Option<usize>,
 ) -> String {
 	let msg = String::from_utf8_lossy(c.message_bytes());
@@ -183,6 +199,24 @@ pub fn get_message(
 	)
 }
 
+/// if `message_limit` is set the message will be
+/// limited to the first line and truncated to fit
+pub fn gix_get_message(
+	commit_ref: &gix::objs::CommitRef,
+	message_limit: Option<usize>,
+) -> String {
+	let message = commit_ref.message.to_string();
+	let message = message.trim();
+
+	message_limit.map_or_else(
+		|| message.to_string(),
+		|limit| {
+			let message = message.lines().next().unwrap_or_default();
+			message.unicode_truncate(limit).0.to_string()
+		},
+	)
+}
+
 #[cfg(test)]
 mod tests {
 	use super::get_commits_info;