|
|
|
use crate::{Diff, FileTree, HashId};
|
|
|
|
use git2::Repository;
|
|
|
|
use std::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<Repository>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Commit {
|
|
|
|
/// Create a commit object and check if it exists in the repo
|
|
|
|
pub(crate) fn new(r: &Arc<Repository>, id: HashId) -> Option<Self> {
|
|
|
|
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> {
|
|
|
|
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> {
|
|
|
|
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<Commit> {
|
|
|
|
self.find()
|
|
|
|
.parents()
|
|
|
|
.map(|c| Self::new(&self.repo, c.id().into()).unwrap())
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the file tree for this commit
|
|
|
|
pub fn get_tree(&self) -> Arc<FileTree> {
|
|
|
|
FileTree::new(&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<String> {
|
|
|
|
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<git2::Diff> {
|
|
|
|
// 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::<Vec<_>>();
|
|
|
|
|
|
|
|
// 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()
|
|
|
|
}
|
|
|
|
}
|