git-workon 0.1.0

A git plugin for managing worktrees
use miette::{Result, WrapErr};
use workon::{
    copy_untracked, get_repo, workon_root, CopyOptions, WorkonConfig, WorktreeDescriptor,
};

use crate::cli::CopyUntracked;
use crate::output;

use super::Run;

impl Run for CopyUntracked {
    fn run(&self) -> Result<Option<WorktreeDescriptor>> {
        let repo = get_repo(None)?;
        let config = WorkonConfig::new(&repo)?;

        let root = workon_root(&repo)?;
        let from_path = root.join(&self.from);
        let to_path = root.join(&self.to);

        if !from_path.exists() {
            return Err(miette::miette!(
                "Source worktree '{}' does not exist at {:?}",
                self.from,
                from_path
            ));
        }
        if !to_path.exists() {
            return Err(miette::miette!(
                "Destination worktree '{}' does not exist at {:?}",
                self.to,
                to_path
            ));
        }

        let patterns = determine_patterns(self, &config)?;
        let excludes = determine_excludes(self, &config)?;
        let include_ignored =
            config.copy_include_ignored(Some(self.include_ignored).filter(|&v| v))?;

        let json_mode = output::is_json_mode();
        let pb = output::create_spinner();
        pb.set_message("Copying files...");

        let show_skipped = log::log_enabled!(log::Level::Debug);
        let mut count = 0usize;
        let pb_copied = pb.clone();
        let pb_skipped = pb.clone();
        let copied = copy_untracked(
            &from_path,
            &to_path,
            CopyOptions {
                patterns: &patterns,
                excludes: &excludes,
                force: self.force,
                include_ignored,
                on_copied: Box::new(move |rel_path| {
                    if !json_mode {
                        count += 1;
                        pb_copied.println(format!(
                            "      {} {}",
                            output::style::green_bold("Copied"),
                            rel_path.display()
                        ));
                        pb_copied.set_message(format!("Copying files... ({} copied)", count));
                    }
                }),
                on_skipped: Box::new(move |reason, rel_path| {
                    if show_skipped && !json_mode {
                        pb_skipped.println(format!(
                            "      {} {} ({})",
                            output::style::dim("Skipped"),
                            rel_path.display(),
                            reason,
                        ));
                    }
                }),
            },
        )
        .wrap_err(format!(
            "Failed to copy files from '{}' to '{}'",
            self.from, self.to
        ))?;

        pb.finish_and_clear();
        if !json_mode {
            println!("\nCopied {} file(s)", copied.len());
        }

        Ok(Some(WorktreeDescriptor::new(&repo, &self.to)?))
    }
}

/// Determine which patterns to use for copying
///
/// Priority: --pattern flag > config > [] (empty = copy all untracked)
fn determine_patterns(cmd: &CopyUntracked, config: &WorkonConfig) -> Result<Vec<String>> {
    if let Some(pattern) = &cmd.pattern {
        return Ok(vec![pattern.clone()]);
    }
    Ok(config.copy_patterns()?)
}

/// Determine which excludes to use for copying
///
/// CLI excludes are additive with config excludes.
fn determine_excludes(cmd: &CopyUntracked, config: &WorkonConfig) -> Result<Vec<String>> {
    let mut excludes = config.copy_excludes()?;
    excludes.extend(cmd.exclude.iter().cloned());
    Ok(excludes)
}