use crate::{files::FileTree, Diff, HashId}; use git2::Repository; use std::{ fmt::{self, Debug, Formatter}, sync::Arc, }; /// Represent a commit on a repository /// /// When creating a commit object, it is guaranteed that it exists in /// the repository. #[derive(Clone)] pub struct Commit { pub id: HashId, repo: Arc, } impl Debug for Commit { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "{}", self.id) } } impl Commit { /// Create a commit object and check if it exists in the repo pub(crate) fn new(r: &Arc, id: HashId) -> Option { r.find_commit(id.to_oid()).ok().map(|_| Self { id, repo: Arc::clone(r), }) } /// Get a utf-8 string representation of the commit ID pub fn id_str(&self) -> String { self.id.to_string() } /// Get the summary line as a utf-8 string pub fn summary(&self) -> String { self.find().summary().unwrap().into() } /// Get the number of parents pub fn parent_count(&self) -> usize { self.repo .find_commit(self.id.to_oid()) .unwrap() .parent_count() } /// Return the first parent, if it exists pub fn first_parent(&self) -> Option { self.find() .parent(0) .ok() .and_then(|c| Self::new(&self.repo, c.id().into())) } /// Get a specific parent, if it exists pub fn parent(&self, num: usize) -> Option { self.find() .parent(num) .ok() .and_then(|c| Self::new(&self.repo, c.id().into())) } /// Get the set of parents as a vector /// /// Use this function if you suspect a commit has more than one /// parent. pub fn parents(&self) -> Vec { self.find() .parents() .map(|c| Self::new(&self.repo, c.id().into()).unwrap()) .collect() } /// Get the file tree for this commit pub fn tree(&self) -> FileTree { FileTree::new(Arc::clone(&self.repo), self.id.clone()) } /// Get the list of paths in the repository touched by this commit /// /// Using this function directly is often not what you want. /// Instead, use the `get_history(...)` function on `FileTree`, /// which uses this function. pub fn get_paths(&self) -> Vec { self.get_diff() .map(|d| Diff::from(d)) .map_or(vec![], |d| d.get_paths()) } /// Utility function to get a merged diff from all parents fn get_diff(&self) -> Option { // Get all diffs with parents let stree = self.find().tree().unwrap(); let mut vec = self .parents() .into_iter() .filter_map(|p| { self.repo .diff_tree_to_tree(Some(&stree), Some(p.find().tree().as_ref().unwrap()), None) .ok() }) .collect::>(); // If there are no parents if vec.len() == 0 { vec = vec![self.repo.diff_tree_to_tree(Some(&stree), None, None).ok()?]; } // Take the first and merge onto let first = vec.remove(0); Some(vec.iter().fold(first, |mut acc, diff| { acc.merge(diff).unwrap(); acc })) } fn find(&self) -> git2::Commit { self.repo.find_commit(self.id.to_oid()).unwrap() } }