use crate::snapshot::{EntryKind, Manifest, Root, SnapshotError, hash_file};
use std::io::ErrorKind;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PathStatus {
Unchanged,
Modified,
Missing,
TypeChanged,
Created,
Unreadable,
}
impl PathStatus {
pub fn as_str(self) -> &'static str {
match self {
Self::Unchanged => "unchanged",
Self::Modified => "modified",
Self::Missing => "missing",
Self::TypeChanged => "type-changed",
Self::Created => "created",
Self::Unreadable => "unreadable",
}
}
}
#[derive(Debug)]
pub struct DiffLine {
pub path: PathBuf,
pub status: PathStatus,
}
#[derive(Debug)]
pub struct DiffReport {
pub lines: Vec<DiffLine>,
pub partial: bool,
}
pub fn diff_manifest(m: &Manifest) -> Result<DiffReport, SnapshotError> {
let partial = m.truncated;
if m.root == Root::Absent {
let status = if m.path.symlink_metadata().is_ok() {
PathStatus::Created
} else {
PathStatus::Unchanged
};
return Ok(DiffReport {
lines: vec![DiffLine {
path: m.path.clone(),
status,
}],
partial,
});
}
let root_kind = m
.entries
.iter()
.find(|e| e.rel.as_os_str().is_empty())
.map(|e| &e.kind);
if let Some(kind) = root_kind {
let root_status = status_of_entry(&m.path, kind)?;
if matches!(root_status, PathStatus::TypeChanged | PathStatus::Missing) {
return Ok(DiffReport {
lines: vec![DiffLine {
path: m.path.clone(),
status: root_status,
}],
partial,
});
}
}
let mut lines = Vec::with_capacity(m.entries.len());
for entry in &m.entries {
let abs = if entry.rel.as_os_str().is_empty() {
m.path.clone()
} else {
m.path.join(&entry.rel)
};
let status = status_of_entry(&abs, &entry.kind)?;
lines.push(DiffLine { path: abs, status });
}
Ok(DiffReport { lines, partial })
}
fn status_of_entry(abs: &Path, kind: &EntryKind) -> Result<PathStatus, SnapshotError> {
let meta = match abs.symlink_metadata() {
Ok(meta) => meta,
Err(e) if e.kind() == ErrorKind::NotFound => return Ok(PathStatus::Missing),
Err(_) => return Ok(PathStatus::Unreadable),
};
let ft = meta.file_type();
let status = match kind {
EntryKind::File { hash, .. } => {
if !ft.is_file() {
PathStatus::TypeChanged
} else {
match hash_file(abs) {
Ok(h) if &h == hash => PathStatus::Unchanged,
Ok(_) => PathStatus::Modified,
Err(_) => PathStatus::Unreadable,
}
}
}
EntryKind::Dir { .. } => {
if ft.is_dir() {
PathStatus::Unchanged
} else {
PathStatus::TypeChanged
}
}
EntryKind::Symlink { target } => {
if !ft.is_symlink() {
PathStatus::TypeChanged
} else {
match std::fs::read_link(abs) {
Ok(t) if &t == target => PathStatus::Unchanged,
Ok(_) => PathStatus::Modified,
Err(_) => PathStatus::Unreadable,
}
}
}
EntryKind::Fifo { .. } => {
#[cfg(unix)]
{
use std::os::unix::fs::FileTypeExt;
if ft.is_fifo() {
PathStatus::Unchanged
} else {
PathStatus::TypeChanged
}
}
#[cfg(not(unix))]
{
PathStatus::TypeChanged
}
}
};
Ok(status)
}