use git2::StatusOptions;
use time::OffsetDateTime;
use crate::{error::Error, util::convert_git2_time, GitRepository};
#[derive(Debug)]
pub struct Commit {
pub id: String,
pub date: OffsetDateTime,
pub author_name: String,
pub author_email: String,
pub committer_name: String,
pub committer_email: String,
pub message: String,
}
impl Commit {
pub fn subject(&self) -> String {
if let Some(line) = self.message.lines().next() {
return line.to_string();
}
unreachable!()
}
}
impl<'repo> TryFrom<&'repo git2::Commit<'repo>> for Commit {
type Error = Error;
fn try_from(c: &'repo git2::Commit) -> Result<Self, Self::Error> {
Ok(Commit {
id: c.id().to_string(),
date: convert_git2_time(c.time())?,
author_name: c
.author()
.name()
.ok_or(Error::msg("non UTF8 author name"))?
.to_string(),
author_email: c
.author()
.email()
.ok_or(Error::msg("non UTF8 author email"))?
.to_string(),
committer_name: c
.committer()
.name()
.ok_or(Error::msg("non UTF8 committer name"))?
.to_string(),
committer_email: c
.committer()
.email()
.ok_or(Error::msg("non UTF8 committer email"))?
.to_string(),
message: c
.message()
.ok_or(Error::msg("non UTF8 message"))?
.to_string(),
})
}
}
pub fn commit_log(repo: &GitRepository) -> Result<Vec<Commit>, Error> {
let mut revwalk = repo.revwalk()?;
revwalk.push_head()?;
let mut commits: Vec<_> = vec![];
for oid_res in revwalk {
match oid_res {
Ok(oid) => {
let obj = repo.find_object(oid, None).unwrap();
if let Some(c) = obj.as_commit() {
commits.push(c.try_into()?);
}
}
Err(err) => return Err(Error::msg(format!("{err} (revwalk)").as_str())),
}
}
Ok(commits)
}
pub fn commit_to_head(repo: &GitRepository, message: &str) -> Result<Commit, Error> {
let mut status_opts = StatusOptions::new();
status_opts.show(git2::StatusShow::Index);
let has_no_changes = repo.statuses(Some(&mut status_opts))?.is_empty();
if has_no_changes {
return Err(Error::msg("nothing to commit"));
}
let sig = repo.signature()?;
let update_ref = Some("HEAD");
let head = repo.head()?;
let head_commit = head.peel_to_commit()?;
let tree = {
let mut index = repo.index()?;
let tree_id = index.write_tree()?;
repo.find_tree(tree_id)?
};
let commit_id = repo.commit(update_ref, &sig, &sig, message, &tree, &[&head_commit])?;
let commit_obj = repo.find_object(commit_id, None)?;
let commit = commit_obj.as_commit().unwrap();
commit.try_into()
}
#[cfg(test)]
mod tests {
use crate::repo::discover_repo;
use super::*;
#[test]
fn test_commit_log() {
let cwd = std::env::current_dir().unwrap();
let repo = discover_repo(&cwd).unwrap();
let commits = commit_log(&repo).unwrap();
for c in commits {
eprintln!("commit {}", c.id);
eprintln!("Date: {}", c.date);
eprintln!("Author: {} <{}>", c.author_name, c.author_email);
eprintln!("Committer: {} <{}>", c.committer_name, c.committer_email);
eprintln!("{}", c.message);
eprintln!();
}
}
#[test]
fn test_commit_do() {
let cwd = std::env::current_dir().unwrap();
let _repo = discover_repo(&cwd).unwrap();
}
}