xbp 10.46.0

XBP is a zero-config build pack that can also interact with proxies, kafka, sockets, synthetic monitors.
Documentation
//! Path / filesystem helpers for version scope resolution.

use std::path::{Path, PathBuf};

use crate::utils::collapse_project_path;

use super::git_ops::git_repository_root;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum ServiceScopeFilter {
    /// Services allowed for bumps / dirty-tree versioning.
    Versioning,
    /// Services allowed for full release publishing.
    Release,
}

pub(crate) const LAST_VERSION_SCOPE_FILE: &str = "last-version-scope";

pub(crate) fn normalized_relative_path(path: &Path) -> String {
    path.to_string_lossy().replace('\\', "/")
}

/// Repo-relative path for release ledger JSON/TOML details (always `/` separators).
///
/// Absolute Windows paths must not be persisted under `.xbp/releases/*`.
pub(crate) fn ledger_relative_path(project_root: &Path, path: &Path) -> String {
    let collapsed = collapse_project_path(project_root, &path.to_string_lossy());
    let relative = if collapsed.is_empty() || collapsed == "./" {
        path.strip_prefix(project_root)
            .map(|rel| rel.to_string_lossy().into_owned())
            .unwrap_or_else(|_| path.to_string_lossy().into_owned())
    } else {
        collapsed
    };
    relative.replace('\\', "/")
}

/// Map a list of paths to ledger-relative strings.
pub(crate) fn ledger_relative_paths(project_root: &Path, paths: &[PathBuf]) -> Vec<String> {
    paths
        .iter()
        .map(|path| ledger_relative_path(project_root, path))
        .collect()
}

pub(crate) fn same_filesystem_path(left: &Path, right: &Path) -> bool {
    if left == right {
        return true;
    }

    match (left.canonicalize(), right.canonicalize()) {
        (Ok(left), Ok(right)) => left == right,
        _ => false,
    }
}

pub(crate) fn enclosing_git_root(dir: &Path) -> Option<PathBuf> {
    for ancestor in dir.ancestors() {
        if ancestor.join(".git").exists() {
            return Some(ancestor.to_path_buf());
        }
    }

    git_repository_root(dir)
}

#[cfg(test)]
mod tests {
    use super::ledger_relative_path;
    use std::path::PathBuf;

    #[test]
    fn ledger_paths_are_repo_relative_with_forward_slashes() {
        let root = PathBuf::from(r"C:\Users\floris\Documents\GitHub\data-models");
        let absolute = root.join("CHANGELOG.md");
        let relative = ledger_relative_path(&root, &absolute);
        assert_eq!(relative, "CHANGELOG.md");

        let nested = root.join(".xbp").join("releases").join("x.toml");
        assert_eq!(
            ledger_relative_path(&root, &nested),
            ".xbp/releases/x.toml"
        );
    }
}