use anyhow::Result;
use std::path::Path;
use std::time::SystemTime;
fn format_timestamp_debug(time: SystemTime) -> String {
use std::time::UNIX_EPOCH;
match time.duration_since(UNIX_EPOCH) {
Ok(duration) => {
let secs = duration.as_secs();
format!(
"{} ({})",
secs,
chrono::DateTime::<chrono::Local>::from(time).format("%Y-%m-%d %H:%M:%S")
)
}
Err(_) => "invalid".to_string(),
}
}
pub fn format_time_ago(time: SystemTime) -> String {
let elapsed = match SystemTime::now().duration_since(time) {
Ok(d) => d,
Err(_) => {
return "just now".to_string();
}
};
let seconds = elapsed.as_secs();
if seconds < 60 {
format!("{}s ago", seconds)
} else if seconds < 3600 {
let minutes = (seconds + 30) / 60; format!("{}m ago", minutes)
} else if seconds < 86400 {
let hours = (seconds + 1800) / 3600; format!("{}h ago", hours)
} else if seconds < 2592000 {
let days = (seconds + 43200) / 86400; format!("{}d ago", days)
} else if seconds < 31536000 {
let months = (seconds + 1296000) / 2592000; format!("{}mo ago", months)
} else {
let years = (seconds + 15768000) / 31536000; format!("{}y ago", years)
}
}
pub fn format_size(bytes: u64) -> String {
const KB: u64 = 1024;
const MB: u64 = KB * 1024;
const GB: u64 = MB * 1024;
if bytes >= GB {
format!("{:.1} GB", bytes as f64 / GB as f64)
} else if bytes >= MB {
format!("{:.1} MB", bytes as f64 / MB as f64)
} else if bytes >= KB {
format!("{:.1} KB", bytes as f64 / KB as f64)
} else {
format!("{} B", bytes)
}
}
pub fn get_repo_modification_time(repo_path: &Path, is_clean: bool) -> Result<SystemTime> {
if is_clean {
let commit_time = get_last_commit_time(repo_path)?;
Ok(commit_time)
} else {
let commit_time = get_last_commit_time(repo_path).unwrap_or(SystemTime::UNIX_EPOCH);
let dirty_files_time =
get_dirty_files_modification_time(repo_path).unwrap_or(SystemTime::UNIX_EPOCH);
let max_time = commit_time.max(dirty_files_time);
Ok(max_time)
}
}
fn get_last_commit_time(repo_path: &Path) -> Result<SystemTime> {
use std::process::Command;
let output = Command::new("git")
.args([
"-C",
repo_path.to_str().unwrap(),
"log",
"-1",
"--format=%ct",
])
.output()?;
if !output.status.success() {
anyhow::bail!("Failed to get last commit time");
}
let timestamp_str = String::from_utf8(output.stdout)?;
let timestamp: i64 = timestamp_str.trim().parse()?;
let system_time = SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(timestamp as u64);
Ok(system_time)
}
fn get_dirty_files_modification_time(repo_path: &Path) -> Result<SystemTime> {
use std::process::Command;
let output = Command::new("git")
.args(["-C", repo_path.to_str().unwrap(), "status", "--porcelain"])
.output()?;
if !output.status.success() {
anyhow::bail!("Failed to get dirty files");
}
let mut latest_time = SystemTime::UNIX_EPOCH;
let mut file_count = 0;
for line in String::from_utf8(output.stdout)?.lines() {
if line.len() < 4 {
continue;
}
let filename = line[3..].trim();
let file_path = repo_path.join(filename);
if let Ok(metadata) = std::fs::metadata(&file_path)
&& let Ok(modified) = metadata.modified()
{
file_count += 1;
if modified > latest_time {
latest_time = modified;
}
}
}
Ok(latest_time)
}
pub fn get_repo_size(repo_path: &Path) -> Result<u64> {
use std::fs;
let mut total_size = 0u64;
fn visit_dirs(dir: &Path, total: &mut u64) -> Result<()> {
if dir.is_dir() {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.file_name().and_then(|n| n.to_str()) == Some(".git") {
continue;
}
if path.is_dir() {
visit_dirs(&path, total)?;
} else if let Ok(metadata) = fs::metadata(&path) {
*total += metadata.len();
}
}
}
Ok(())
}
visit_dirs(repo_path, &mut total_size)?;
Ok(total_size)
}