homeboy 0.76.0

CLI for multi-component deployment and development workflow automation
Documentation
use crate::engine::temp;
use crate::Error;
use std::collections::{BTreeMap, BTreeSet};
use std::path::{Path, PathBuf};

pub struct SandboxDir {
    path: PathBuf,
}

impl SandboxDir {
    pub fn path(&self) -> &Path {
        &self.path
    }
}

impl Drop for SandboxDir {
    fn drop(&mut self) {
        let _ = std::fs::remove_dir_all(&self.path);
    }
}

pub fn clone_tree(src: &Path) -> crate::Result<SandboxDir> {
    let temp = temp::runtime_temp_dir("homeboy-refactor-ci")?;
    copy_dir_recursive(src, &temp)?;
    Ok(SandboxDir { path: temp })
}

pub fn copy_changed_files(
    src_root: &Path,
    dst_root: &Path,
    changed_files: &[String],
) -> crate::Result<()> {
    for file in changed_files {
        let src = src_root.join(file);
        let dst = dst_root.join(file);

        if let Some(parent) = dst.parent() {
            std::fs::create_dir_all(parent).map_err(|e| {
                Error::internal_io(e.to_string(), Some(format!("create parent for {}", file)))
            })?;
        }

        std::fs::copy(&src, &dst).map_err(|e| {
            Error::internal_io(e.to_string(), Some(format!("copy changed file {}", file)))
        })?;
    }

    Ok(())
}

pub fn snapshot_tree(root: &str) -> crate::Result<BTreeMap<String, u64>> {
    let root_path = Path::new(root);
    let mut files = BTreeMap::new();
    snapshot_tree_recursive(root_path, root_path, &mut files)?;
    Ok(files)
}

pub fn diff_tree_snapshots(
    before: &BTreeMap<String, u64>,
    after: &BTreeMap<String, u64>,
) -> Vec<String> {
    let mut changed = BTreeSet::new();

    for (file, size) in after {
        if before.get(file) != Some(size) {
            changed.insert(file.clone());
        }
    }

    for file in before.keys() {
        if !after.contains_key(file) {
            changed.insert(file.clone());
        }
    }

    changed.into_iter().collect()
}

fn snapshot_tree_recursive(
    root: &Path,
    dir: &Path,
    files: &mut BTreeMap<String, u64>,
) -> crate::Result<()> {
    for entry in std::fs::read_dir(dir)
        .map_err(|e| Error::internal_io(e.to_string(), Some("read sandbox dir".to_string())))?
    {
        let entry = entry.map_err(|e| {
            Error::internal_io(e.to_string(), Some("read sandbox entry".to_string()))
        })?;
        let path = entry.path();

        if path.is_dir() {
            snapshot_tree_recursive(root, &path, files)?;
            continue;
        }

        let relative = path
            .strip_prefix(root)
            .map_err(|e| {
                Error::internal_io(e.to_string(), Some("strip sandbox prefix".to_string()))
            })?
            .to_string_lossy()
            .replace('\\', "/");
        let metadata = std::fs::metadata(&path).map_err(|e| {
            Error::internal_io(e.to_string(), Some("stat sandbox file".to_string()))
        })?;
        files.insert(relative, metadata.len());
    }

    Ok(())
}

fn copy_dir_recursive(src: &Path, dst: &Path) -> crate::Result<()> {
    std::fs::create_dir_all(dst)
        .map_err(|e| Error::internal_io(e.to_string(), Some("create sandbox dir".to_string())))?;

    for entry in std::fs::read_dir(src)
        .map_err(|e| Error::internal_io(e.to_string(), Some("read source dir".to_string())))?
    {
        let entry = entry
            .map_err(|e| Error::internal_io(e.to_string(), Some("read dir entry".to_string())))?;
        let src_path = entry.path();
        let dst_path = dst.join(entry.file_name());

        if src_path.is_dir() {
            if entry.file_name() == ".git" {
                continue;
            }
            copy_dir_recursive(&src_path, &dst_path)?;
        } else {
            std::fs::copy(&src_path, &dst_path).map_err(|e| {
                Error::internal_io(e.to_string(), Some("copy sandbox file".to_string()))
            })?;
        }
    }

    Ok(())
}