use chrono::DateTime;
use chrono::Utc;
use quilt_uri::Namespace;
use crate::Res;
use crate::io::storage::Storage;
use crate::manifest::Manifest;
use crate::paths::DomainPaths;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Revision {
pub hash: String,
pub obtained: DateTime<Utc>,
pub message: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HistoryEntry {
pub revision: Revision,
pub published: bool,
}
pub async fn list_revisions(
paths: &DomainPaths,
storage: &(impl Storage + Sync),
namespace: &Namespace,
) -> Res<Vec<Revision>> {
let manifest_dir = paths.installed_manifests_dir(namespace);
let mut entries = storage.read_dir(&manifest_dir).await?;
let mut revisions = Vec::new();
while let Some(entry) = entries.next_entry().await? {
if !is_revision(&entry).await? {
continue;
}
let path = entry.path();
let manifest = Manifest::from_path(storage, &path).await?;
revisions.push(Revision {
hash: entry.file_name().to_string_lossy().into_owned(),
obtained: entry.metadata().await?.modified()?.into(),
message: manifest.header.message,
});
}
revisions.sort_by(|left, right| {
right
.obtained
.cmp(&left.obtained)
.then_with(|| left.hash.cmp(&right.hash))
});
Ok(revisions)
}
pub async fn count_revisions(
paths: &DomainPaths,
storage: &(impl Storage + Sync),
namespace: &Namespace,
) -> Res<usize> {
let mut entries = storage
.read_dir(paths.installed_manifests_dir(namespace))
.await?;
let mut count = 0;
while let Some(entry) = entries.next_entry().await? {
if is_revision(&entry).await? {
count += 1;
}
}
Ok(count)
}
async fn is_revision(entry: &tokio::fs::DirEntry) -> Res<bool> {
Ok(!entry.file_name().to_string_lossy().starts_with('.') && entry.file_type().await?.is_file())
}
#[cfg(test)]
mod tests {
use super::*;
use test_log::test;
use crate::flow::create;
use crate::io::storage::mocks::MockStorage;
use crate::lineage::DomainLineage;
#[test(tokio::test)]
async fn test_list_revisions_reads_the_installed_manifest() -> Res {
let (lineage, _temp_dir) = DomainLineage::from_temp_dir()?;
let (paths, _temp_dir2) = DomainPaths::from_temp_dir()?;
let storage = MockStorage::default();
let namespace: Namespace = ("demo", "sales").into();
create(
lineage,
&paths,
&storage,
namespace.clone(),
None,
Some("initial import".to_string()),
)
.await?;
let listed = list_revisions(&paths, &storage, &namespace).await?;
assert_eq!(listed.len(), 1);
assert_eq!(listed[0].message.as_deref(), Some("initial import"));
Ok(())
}
}