quilt-rs 0.46.0

Rust library for versioning data packages: content-addressed manifests, local domains, and S3 remotes.
use std::collections::BTreeMap;
use std::fmt;
use std::path::PathBuf;

use serde::Serialize;

use crate::manifest::ManifestRow;
use crate::quiltignore;

/// Describes modified states of a file
#[derive(Debug, Clone, PartialEq)]
pub enum Change {
    Modified(ManifestRow), // modified to what
    Added(ManifestRow),    // added what
    Removed(ManifestRow),  // removed what
}

/// Map of all changed files
pub type ChangeSet = BTreeMap<PathBuf, Change>;

/// State of the local package relative to the remote package
/// `Serialize` is the machine-readable form and a stable contract: these
/// strings reach `quilt --json` and `QuiltSync`'s frontend, so changing one is a
/// breaking change. `Display` below is for humans and may be reworded freely —
/// the two happen to agree today, which is why they must be kept apart.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum UpstreamState {
    #[default]
    UpToDate,
    Behind,
    Ahead,
    Diverged,
    /// Local-only package: either no remote configured, or remote set but never pushed
    Local,
    Error,
}

impl fmt::Display for UpstreamState {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let s = match self {
            UpstreamState::Ahead => "ahead",
            UpstreamState::Behind => "behind",
            UpstreamState::Diverged => "diverged",
            UpstreamState::UpToDate => "up_to_date",
            UpstreamState::Local => "local",
            UpstreamState::Error => "error",
        };
        f.write_str(s)
    }
}

/// Status of the package and working directory of the package
#[derive(Clone, Debug, Default)]
pub struct InstalledPackageStatus {
    /// Current commit vs upstream state
    pub upstream_state: UpstreamState,
    /// File changes vs current commit (visible + junky files only)
    pub changes: ChangeSet,
    /// Files matched by an ignore rule — (path, the rule and its source, size in bytes)
    pub ignored_files: Vec<(PathBuf, quiltignore::IgnoredBy, u64)>,
    /// Folders matched by an ignore rule — (path, the rule and its source).
    /// Nothing inside them is walked, so they carry no size and no
    /// folder nested in one is listed.
    pub ignored_dirs: Vec<(PathBuf, quiltignore::IgnoredBy)>,
    /// Files in changes that are also flagged as junk — (path, suggested pattern)
    pub junky_changes: Vec<(PathBuf, String)>,
    /// This status was read against the remote's `latest` tip fetched just
    /// now, including a remote with no `latest` tag for a package never
    /// published: that is an answer, not an outage. `false` when the package has no remote, when the remote could not
    /// be reached and `upstream_state` fell back to the last-known tip, and
    /// for any status not built by [`InstalledPackage::status`](crate::InstalledPackage::status).
    pub latest_refreshed: bool,
    /// The remote's `latest` hash this status was computed against: fetched
    /// just now when [`Self::latest_refreshed`], else the last-known tip.
    /// Empty when none is known. Carried with the verdict so a caller never
    /// pairs it with another read's tip.
    pub latest_hash: String,
}

impl InstalledPackageStatus {
    #[must_use]
    pub fn new(upstream_state: UpstreamState, changes: ChangeSet) -> Self {
        Self {
            upstream_state,
            changes,
            ignored_files: Vec::new(),
            ignored_dirs: Vec::new(),
            junky_changes: Vec::new(),
            latest_refreshed: false,
            latest_hash: String::new(),
        }
    }

    #[must_use]
    pub fn error() -> Self {
        Self {
            upstream_state: UpstreamState::Error,
            ..Default::default()
        }
    }

    #[must_use]
    pub fn local() -> Self {
        Self {
            upstream_state: UpstreamState::Local,
            ..Default::default()
        }
    }
}