use crate::ops::utils::{format_timestamp, short_oid};
use anyhow::Result;
use git2::{Commit, Repository};
use std::path::Path;
pub fn execute(path: &Path, object: &str, stat_only: bool) -> Result<()> {
let repo = crate::ops::open_repo(path)?;
let obj = repo.revparse_single(object)?;
if obj.kind() == Some(git2::ObjectType::Tag) {
let tag = obj.peel(git2::ObjectType::Tag)?;
let tag = tag
.as_tag()
.ok_or_else(|| anyhow::anyhow!("expected tag object"))?;
println!("tag {}", tag.name().unwrap_or(""));
if let Some(tagger) = tag.tagger() {
println!(
"Tagger: {} <{}>",
tagger.name().unwrap_or(""),
tagger.email().unwrap_or("")
);
}
if let Some(msg) = tag.message() {
println!();
for line in msg.lines() {
println!(" {}", line);
}
}
println!();
}
let commit = obj.peel_to_commit()?;
show_commit(&repo, &commit, stat_only)?;
Ok(())
}
pub fn execute_compact(path: &Path, object: &str) -> Result<String> {
use crate::cli::compact::CompactDiffFormatter;
let repo = crate::ops::open_repo(path)?;
let obj = repo.revparse_single(object)?;
let commit = obj.peel_to_commit()?;
let short_id = short_oid(&commit.id());
let summary = commit.summary().unwrap_or("");
let author = commit.author();
let author_name = author.name().unwrap_or("");
let time = commit.time();
let timestamp = format_timestamp(time.seconds(), time.offset_minutes());
let mut output = format!(
"{} {} ({}) <{}>\n",
short_id, summary, timestamp, author_name
);
let parent_tree = commit.parent(0).ok().and_then(|p| p.tree().ok());
let tree = commit.tree()?;
let diff = repo.diff_tree_to_tree(parent_tree.as_ref(), Some(&tree), None)?;
let stats = diff.stats()?;
let stat_buf = stats.to_buf(git2::DiffStatsFormat::SHORT, 80)?;
output.push_str(stat_buf.as_str().unwrap_or(""));
let formatter = std::cell::RefCell::new(CompactDiffFormatter::new());
diff.print(git2::DiffFormat::Patch, |_delta, _hunk, line| {
let mut fmt = formatter.borrow_mut();
let content = std::str::from_utf8(line.content()).unwrap_or("");
match line.origin() {
'F' => {
if let Some(path) = _delta
.new_file()
.path()
.or_else(|| _delta.old_file().path())
{
fmt.begin_file(&path.display().to_string());
}
}
'H' => {
fmt.begin_hunk(content);
}
'+' | '-' | ' ' => {
fmt.add_line(line.origin(), content);
}
_ => {}
}
true
})?;
let compact_diff = formatter.into_inner().finish();
output.push_str(&compact_diff);
Ok(output.trim_end().to_string())
}
fn show_commit(repo: &Repository, commit: &Commit, stat_only: bool) -> Result<()> {
println!("commit {}", commit.id());
if commit.parent_count() > 1 {
let parents: Vec<String> = (0..commit.parent_count())
.filter_map(|i| commit.parent_id(i).ok())
.map(|id| short_oid(&id))
.collect();
println!("Merge: {}", parents.join(" "));
}
let author = commit.author();
println!(
"Author: {} <{}>",
author.name().unwrap_or(""),
author.email().unwrap_or("")
);
let time = commit.time();
println!(
"Date: {}",
format_timestamp(time.seconds(), time.offset_minutes())
);
println!();
if let Some(msg) = commit.message() {
for line in msg.lines() {
println!(" {}", line);
}
println!();
}
let parent_tree = commit.parent(0).ok().and_then(|p| p.tree().ok());
let tree = commit.tree()?;
let diff = repo.diff_tree_to_tree(parent_tree.as_ref(), Some(&tree), None)?;
if stat_only {
let stats = diff.stats()?;
let buf = stats.to_buf(git2::DiffStatsFormat::FULL, 80)?;
print!("{}", buf.as_str().unwrap_or(""));
} else {
diff.print(git2::DiffFormat::Patch, |_delta, _hunk, line| {
let prefix = match line.origin() {
'+' => "+",
'-' => "-",
' ' => " ",
_ => "",
};
print!(
"{}{}",
prefix,
std::str::from_utf8(line.content()).unwrap_or("")
);
true
})?;
}
Ok(())
}