bazaar 3.5.1

Rust implementation of the Bazaar formats and protocols
Documentation
//! Stat-packing: the base64-encoded 24-byte record dirstate stores
//! per entry as a fingerprint of the filesystem `lstat` result.

use crate::dirstate::Kind;
use base64::Engine;
use std::fs::Metadata;
#[cfg(unix)]
use std::os::unix::fs::MetadataExt;
use std::time::{SystemTime, UNIX_EPOCH};

fn epoch_secs(t: std::io::Result<SystemTime>) -> u64 {
    // `metadata.created()` is unsupported on several Linux filesystems
    // (notably ext4 without `st_birthtime`); fall back to the epoch so
    // we behave like Python's `os.stat_result` whose `st_ctime` is
    // always populated. Same fallback for `modified()` for symmetry.
    t.ok()
        .and_then(|st| st.duration_since(UNIX_EPOCH).ok())
        .map(|d| d.as_secs())
        .unwrap_or(0)
}

#[cfg(unix)]
pub fn pack_stat_metadata(metadata: &Metadata) -> String {
    pack_stat(
        metadata.len(),
        epoch_secs(metadata.modified()),
        epoch_secs(metadata.created()),
        metadata.dev(),
        metadata.ino(),
        metadata.mode(),
    )
}

#[cfg(windows)]
pub fn pack_stat_metadata(metadata: &Metadata) -> String {
    pack_stat(
        metadata.len(),
        epoch_secs(metadata.modified()),
        epoch_secs(metadata.created()),
        0,
        0,
        0,
    )
}

pub fn pack_stat(size: u64, mtime: u64, ctime: u64, dev: u64, ino: u64, mode: u32) -> String {
    let size = size & 0xFFFFFFFF;
    let mtime = mtime & 0xFFFFFFFF;
    let ctime = ctime & 0xFFFFFFFF;
    let dev = dev & 0xFFFFFFFF;
    let ino = ino & 0xFFFFFFFF;

    let packed_data = [
        (size >> 24) as u8,
        (size >> 16) as u8,
        (size >> 8) as u8,
        size as u8,
        (mtime >> 24) as u8,
        (mtime >> 16) as u8,
        (mtime >> 8) as u8,
        mtime as u8,
        (ctime >> 24) as u8,
        (ctime >> 16) as u8,
        (ctime >> 8) as u8,
        ctime as u8,
        (dev >> 24) as u8,
        (dev >> 16) as u8,
        (dev >> 8) as u8,
        dev as u8,
        (ino >> 24) as u8,
        (ino >> 16) as u8,
        (ino >> 8) as u8,
        ino as u8,
        (mode >> 24) as u8,
        (mode >> 16) as u8,
        (mode >> 8) as u8,
        mode as u8,
    ];

    base64::engine::general_purpose::STANDARD_NO_PAD.encode(packed_data)
}

/// Map a [`Metadata`] entry to the dirstate [`Kind`] it represents,
/// returning `None` for kinds dirstate cannot track (FIFOs, sockets,
/// block / char devices). Callers should surface a `BadFileKindError`
/// in that case rather than substitute a default.
pub fn stat_to_kind(metadata: &Metadata) -> Option<Kind> {
    let file_type = metadata.file_type();
    if file_type.is_dir() {
        Some(Kind::Directory)
    } else if file_type.is_file() {
        Some(Kind::File)
    } else if file_type.is_symlink() {
        Some(Kind::Symlink)
    } else {
        None
    }
}