use std::time::UNIX_EPOCH; use gix::hash::ObjectId; use crate::GitRepository; use crate::commit::impls::CommitOps; use crate::commit::types::{Commit, CommitList, Signature}; impl CommitOps for GitRepository { fn commit_commit(&self, rev: &str) -> Result { let tip = self.tip_id()?; if let Some(hit) = self.cache_get_rev::("commit", "commit", rev, tip) { return Ok(hit); } let repo = self.repo()?; let id = repo .rev_parse_single(rev.as_bytes()) .map_err(|e| crate::Error::RevParse(e.to_string()))? .detach(); let v = commit_by_id_on(&repo, id)?; self.cache_put_rev("commit", "commit", rev, tip, &v); Ok(v) } fn commit_by_id(&self, id: &ObjectId) -> Result { if let Some(hit) = self.cache_get_oid::("commit", id) { return Ok(hit); } let repo = self.repo()?; let v = commit_by_id_on(&repo, *id)?; self.cache_put_oid("commit", id, &v); Ok(v) } fn commit_branch_commit(&self, branch: &str) -> Result { crate::validate::validate_short_name("branch", branch)?; let full = format!("refs/heads/{branch}"); let repo = self.repo()?; let tip = repo .find_reference(full.as_str()) .map_err(|_| crate::Error::BranchNotExist(branch.to_string()))? .target() .try_id() .ok_or_else(|| crate::Error::BranchNotExist(branch.to_string()))? .to_owned(); if let Some(hit) = self.cache_get_rev::("commit", "branch", branch, tip) { return Ok(hit); } let v = commit_by_id_on(&repo, tip)?; self.cache_put_rev("commit", "branch", branch, tip, &v); Ok(v) } fn commit_tag_commit(&self, tag: &str) -> Result { crate::validate::validate_short_name("tag", tag)?; let full = format!("refs/tags/{tag}"); let repo = self.repo()?; let id = repo .rev_parse_single(full.as_bytes()) .map_err(|e| crate::Error::RevParse(e.to_string()))? .detach(); let tip = self.tip_id()?; if let Some(hit) = self.cache_get_rev::("commit", "tag", tag, tip) { return Ok(hit); } let v = commit_by_id_on(&repo, id)?; self.cache_put_rev("commit", "tag", tag, tip, &v); Ok(v) } fn commit_list(&self, limit: u64, skip: u64) -> Result { let tip = self.tip_id()?; let key_part = format!("{limit}.{skip}"); if let Some(hit) = self.cache_get_rev::("commit", "list", &key_part, tip) { return Ok(hit); } let repo = self.repo()?; let mut walk = repo .rev_walk([tip]) .all() .map_err(|e| crate::Error::RevParse(e.to_string()))?; let mut commits = Vec::new(); let mut skipped = 0u64; for info in walk.by_ref() { let info = info.map_err(|e| crate::Error::Find(e.to_string()))?; if skipped < skip { skipped += 1; continue; } if commits.len() as u64 >= limit { break; } commits.push(commit_by_id_on(&repo, info.id().detach())?); } let v = CommitList { commits, total: None, }; self.cache_put_rev("commit", "list", &key_part, tip, &v); Ok(v) } fn commit_parent_count(&self, id: &ObjectId) -> Result { if let Some(hit) = self.cache_get_oid::("commit_parents", id) { return Ok(hit); } let repo = self.repo()?; let c = repo .find_commit(*id) .map_err(|e| crate::Error::Find(e.to_string()))?; let v = c.parent_ids().count(); self.cache_put_oid("commit_parents", id, &v); Ok(v) } fn commit_write_merge( &self, parents: Vec, tree_id: ObjectId, message: &str, author: &Signature, committer: &Signature, ) -> Result { let repo = self.repo()?; let commit = gix::objs::Commit { tree: tree_id, parents: parents.into_iter().collect(), author: to_gix_signature(author), committer: to_gix_signature(committer), encoding: None, message: message.as_bytes().into(), extra_headers: Vec::new(), }; let id = repo .write_object(&commit) .map_err(|e| crate::Error::Odb(e.to_string()))? .detach(); Ok(id) } fn commit_write_squash( &self, parent: ObjectId, tree_id: ObjectId, message: &str, author: &Signature, committer: &Signature, ) -> Result { self.commit_write_merge(vec![parent], tree_id, message, author, committer) } } fn to_gix_signature(sig: &Signature) -> gix::actor::Signature { gix::actor::Signature { name: sig.name.clone().into(), email: sig.email.clone().into(), time: gix::date::Time { seconds: sig .when .duration_since(UNIX_EPOCH) .map(|d| d.as_secs() as i64) .unwrap_or(0), offset: sig.offset_seconds, }, } } fn commit_by_id_on(repo: &gix::Repository, id: ObjectId) -> Result { let c = repo .find_commit(id) .map_err(|e| crate::Error::Find(e.to_string()))?; let decoded = c .decode() .map_err(|e| crate::Error::Find(e.to_string()))? .into_owned() .map_err(|e| crate::Error::Find(e.to_string()))?; Ok(Commit::from_gix(id, &decoded)) }