use crate::cli::UI;
use crate::ops::utils::{format_timestamp, short_oid};
use anyhow::Result;
use std::path::Path;
pub struct LogOptions<'a> {
pub max_count: usize,
pub oneline: bool,
pub author_filter: Option<&'a str>,
pub since: Option<&'a str>,
pub until: Option<&'a str>,
pub all: bool,
pub verbose: bool,
}
pub fn execute(path: &Path, opts: &LogOptions, ui: &UI) -> Result<()> {
let repo = crate::ops::open_repo(path)?;
let mut revwalk = repo.revwalk()?;
if opts.all {
revwalk.push_glob("refs/*")?;
} else {
revwalk.push_head()?;
}
revwalk.set_sorting(git2::Sort::TIME)?;
let since_secs = opts.since.and_then(parse_date_approx);
let until_secs = opts.until.and_then(parse_date_approx);
let mut shown = 0;
for oid in revwalk {
if shown >= opts.max_count {
break;
}
let oid = oid?;
let commit = repo.find_commit(oid)?;
let commit_time = commit.time().seconds();
if let Some(s) = since_secs {
if commit_time < s {
continue;
}
}
if let Some(u) = until_secs {
if commit_time > u {
continue;
}
}
if let Some(author_pat) = opts.author_filter {
let author = commit.author();
let name = author.name().unwrap_or("");
let email = author.email().unwrap_or("");
let pat_lower = author_pat.to_lowercase();
if !name.to_lowercase().contains(&pat_lower)
&& !email.to_lowercase().contains(&pat_lower)
{
continue;
}
}
if opts.oneline {
let short_id = short_oid(&oid);
let msg = commit.summary().unwrap_or("");
ui.log_oneline(&short_id, msg);
} else {
let short_id = short_oid(&oid);
let summary = commit.summary().unwrap_or("");
let author = commit.author();
let author_str = format!(
"{} <{}>",
author.name().unwrap_or(""),
author.email().unwrap_or("")
);
let time = commit.time();
let time_ago = format_timestamp(time.seconds(), time.offset_minutes());
let is_last = shown + 1 >= opts.max_count;
ui.log_entry(&short_id, summary, &author_str, &time_ago, is_last);
}
if opts.verbose {
let parent = commit.parent(0).ok();
let parent_tree = parent.as_ref().and_then(|p| p.tree().ok());
let current_tree = commit.tree().ok();
if let (Some(t1), Some(t2)) = (parent_tree, current_tree) {
let mut diff_opts = git2::DiffOptions::new();
let diff = repo.diff_tree_to_tree(Some(&t1), Some(&t2), Some(&mut diff_opts))?;
crate::ops::diff::display_diff(&diff, ui)?;
}
}
shown += 1;
}
Ok(())
}
pub fn execute_compact(
path: &Path,
max_count: usize,
author_filter: Option<&str>,
since: Option<&str>,
until: Option<&str>,
all: bool,
) -> Result<String> {
use crate::cli::compact::truncate_line;
let repo = crate::ops::open_repo(path)?;
let mut revwalk = repo.revwalk()?;
if all {
revwalk.push_glob("refs/*")?;
} else {
revwalk.push_head()?;
}
revwalk.set_sorting(git2::Sort::TIME)?;
let since_secs = since.and_then(parse_date_approx);
let until_secs = until.and_then(parse_date_approx);
let limit = if max_count > 100 { 10 } else { max_count };
let mut lines = Vec::new();
let mut shown = 0;
for oid in revwalk {
if shown >= limit {
break;
}
let oid = oid?;
let commit = repo.find_commit(oid)?;
let commit_time = commit.time().seconds();
if let Some(s) = since_secs {
if commit_time < s {
continue;
}
}
if let Some(u) = until_secs {
if commit_time > u {
continue;
}
}
if let Some(author_pat) = author_filter {
let author = commit.author();
let name = author.name().unwrap_or("");
let email = author.email().unwrap_or("");
let pat_lower = author_pat.to_lowercase();
if !name.to_lowercase().contains(&pat_lower)
&& !email.to_lowercase().contains(&pat_lower)
{
continue;
}
}
let short_id = short_oid(&oid);
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 line = format!("{} {} ({}) <{}>", short_id, summary, timestamp, author_name);
lines.push(truncate_line(&line, 120));
shown += 1;
}
Ok(lines.join("\n"))
}
fn parse_date_approx(s: &str) -> Option<i64> {
let parts: Vec<&str> = s.split('-').collect();
if parts.len() == 3 {
let year: i64 = parts[0].parse().ok()?;
let month: u32 = parts[1].parse().ok()?;
let day: u32 = parts[2].parse().ok()?;
if !(1..=12).contains(&month) || !(1..=31).contains(&day) {
return None;
}
let mut total_days: i64 = 0;
for y in 1970..year {
total_days += if y % 4 == 0 && (y % 100 != 0 || y % 400 == 0) {
366
} else {
365
};
}
let is_leap = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
let month_days = [
31,
if is_leap { 29 } else { 28 },
31,
30,
31,
30,
31,
31,
30,
31,
30,
31,
];
for &md in month_days.iter().take(month as usize - 1) {
total_days += md as i64;
}
total_days += (day - 1) as i64;
return Some(total_days * 86400);
}
None
}