dirmux 0.1.1

Directory and git repository command running program
Documentation
use crate::options::MergeOpts;
use crate::CommandMessage;
use crate::CommandOutput;
use crate::DirRunner;
use anyhow::Result;
use async_trait::async_trait;
use std::path::PathBuf;
use tokio::sync::mpsc::UnboundedSender;
use tokio::task;

pub struct MergeRunner {
    pub opts: MergeOpts,
}

#[async_trait]
impl DirRunner for MergeRunner {
    async fn process(
        &self,
        dir: PathBuf,
        _sender: UnboundedSender<CommandMessage>,
    ) -> Result<CommandOutput> {
        let dir_out = dir.clone();
        let opts = self.opts.clone();
        let res = task::spawn_blocking(move || git_merge(opts, &dir)).await?;

        match res {
            Ok(s) => Ok(CommandOutput {
                dir: dir_out,
                output: s,
                error: String::from(""),
            }),
            Err(e) => Ok(CommandOutput {
                dir: dir_out,
                error: e.to_string(),
                output: String::from(""),
            }),
        }
    }
}

fn git_merge(opts: MergeOpts, dir: &PathBuf) -> Result<String> {
    let repo = git2::Repository::open(dir)?;
    let head = repo.head()?;
    let (head_name, remote_ref) = match head.name() {
        Some(name) => (name, repo.branch_upstream_name(name)),
        None => return Ok(String::from("")),
    };

    let remote_ref = match remote_ref {
        Ok(remote_ref) => remote_ref,
        Err(_) => return Ok(String::from("")),
    };

    if let Some(remote_ref) = remote_ref.as_str() {
        let remote = repo.find_reference(remote_ref)?;
        let remote_annotated_commit = repo.reference_to_annotated_commit(&remote)?;
        let merge_analysis = repo.merge_analysis(&[&remote_annotated_commit])?;
        if merge_analysis.0.is_fast_forward() {
            let mut head_ref = repo.find_reference(head_name)?;
            let mut output = String::new();

            if opts.verbose {
                let head_tree = head_ref.peel_to_tree()?;
                let remote_tree = remote.peel_to_tree()?;
                let diff = repo.diff_tree_to_tree(Some(&head_tree), Some(&remote_tree), None)?;
                let diff_stats = diff.stats()?.to_buf(git2::DiffStatsFormat::FULL, 80);

                if let Some(diffoutput) = diff_stats?.as_str() {
                    output.push('\n');
                    output.push_str(diffoutput);
                }
            }

            let reflog_msg = format!(
                "Fast-Forward: Setting {} to id: {}",
                head_name,
                remote_annotated_commit.id()
            );

            if opts.dry == false {
                head_ref.set_target(remote_annotated_commit.id(), &reflog_msg)?;
                repo.set_head(&head_name)?;
                repo.checkout_head(Some(git2::build::CheckoutBuilder::default().force()))?;
            }

            output.push_str(&reflog_msg);
            output.push('\n');
            Ok(output)
        } else if merge_analysis.0.is_normal() {
            Ok(String::from("Cannot fast-forward\n"))
        } else if merge_analysis.0.is_up_to_date() {
            Ok(String::from(""))
        } else {
            Ok(String::from(""))
        }
    } else {
        return Ok(String::from(""));
    }
}