81edfe7
//! Commit module usecase: implements CommitOps for GitRepository on top of gix.
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<Commit, crate::Error> {
let tip = self.tip_id()?;
if let Some(hit) = self.cache_get_rev::<Commit>("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<Commit, crate::Error> {
if let Some(hit) = self.cache_get_oid::<Commit>("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<Commit, crate::Error> {
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>("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<Commit, crate::Error> {
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>("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<CommitList, crate::Error> {
let tip = self.tip_id()?;
let key_part = format!("{limit}.{skip}");
if let Some(hit) = self.cache_get_rev::<CommitList>("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<usize, crate::Error> {
if let Some(hit) = self.cache_get_oid::<usize>("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<ObjectId>,
tree_id: ObjectId,
message: &str,
author: &Signature,
committer: &Signature,
) -> Result<ObjectId, crate::Error> {
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<ObjectId, crate::Error> {
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<Commit, crate::Error> {
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))
}