weavatrix-worktree 0.2.1

Bounded, crash-recoverable multi-file worktree edits for source refactoring
Documentation
use std::io;

use cap_fs_ext::MetadataExt;

use crate::filesystem::FileIdentity;

pub(crate) fn validate_metadata(
    metadata: &cap_std::fs::Metadata,
    root_device: u64,
) -> io::Result<()> {
    if !metadata.is_file() {
        return Err(invalid("target is not a regular file"));
    }
    if metadata.dev() != root_device {
        return Err(invalid("target crosses the worktree filesystem"));
    }
    if metadata.nlink() != 1 {
        return Err(invalid("hard-linked targets are rejected"));
    }
    if metadata.permissions().readonly() {
        return Err(io::Error::new(
            io::ErrorKind::PermissionDenied,
            "read-only targets are rejected",
        ));
    }
    Ok(())
}

pub(crate) fn identity(metadata: &cap_std::fs::Metadata) -> FileIdentity {
    FileIdentity {
        device: metadata.dev(),
        inode: metadata.ino(),
    }
}

#[cfg(windows)]
pub(crate) fn is_link_or_reparse(metadata: &cap_std::fs::Metadata) -> bool {
    use cap_std::fs::MetadataExt;
    const FILE_ATTRIBUTE_REPARSE_POINT: u32 = 0x400;
    metadata.file_type().is_symlink()
        || metadata.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT != 0
}

#[cfg(not(windows))]
pub(crate) fn is_link_or_reparse(metadata: &cap_std::fs::Metadata) -> bool {
    metadata.file_type().is_symlink()
}

pub(crate) fn validate_artifact_name(name: &str) -> io::Result<()> {
    if name.is_empty() || name.contains(['/', '\\']) || !name.starts_with(".weavatrix-") {
        return Err(invalid("invalid transaction artifact name"));
    }
    Ok(())
}

pub(crate) fn invalid(message: impl Into<String>) -> io::Error {
    io::Error::new(io::ErrorKind::InvalidInput, message.into())
}