use anyhow::{Context, Result};
use gix::bstr::ByteSlice;
use gix::Repository;
pub(crate) fn is_dirty(repo: &Repository) -> Result<bool> {
if repo.workdir().is_none() {
return Ok(false); }
let index = repo.open_index().context("open git index")?;
let workdir = repo.workdir().expect("checked above");
for entry in index.entries() {
let path_bytes = entry.path(&index);
let abs_path = {
let s = path_bytes.to_os_str_lossy();
workdir.join(std::path::Path::new(s.as_ref() as &std::ffi::OsStr))
};
match std::fs::metadata(&abs_path) {
Err(_) => return Ok(true), Ok(meta) => {
let cached_secs = entry.stat.mtime.secs;
let disk_secs = meta
.modified()
.ok()
.and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
.map(|d| d.as_secs() as u32);
if disk_secs != Some(cached_secs)
|| meta.len() as u32 != entry.stat.size
{
return Ok(true); }
}
}
}
let head_tree = match repo.head().ok().and_then(|mut h| h.peel_to_commit().ok()) {
Some(commit) => commit.tree().context("HEAD tree")?,
None => return Ok(false), };
let mut head_blobs: std::collections::HashMap<Vec<u8>, gix::ObjectId> =
std::collections::HashMap::new();
for entry_result in head_tree.iter() {
let te = entry_result.context("HEAD tree entry")?;
head_blobs.insert(te.filename().to_vec(), te.object_id());
}
for entry in index.entries() {
let path = entry.path(&index).to_owned();
match head_blobs.get(path.as_slice()) {
None => return Ok(true), Some(&head_oid) if head_oid != entry.id => return Ok(true), _ => {}
}
}
let index_paths: std::collections::HashSet<Vec<u8>> = index
.entries()
.iter()
.map(|e| e.path(&index).to_vec())
.collect();
if head_blobs.keys().any(|p| !index_paths.contains(p.as_slice())) {
return Ok(true);
}
Ok(false)
}