cuddle-please-misc 0.1.0

A release-please inspired release manager tool, built on top of cuddle, but also useful standalone, cuddle-please supports, your ci of choice, as well as gitea, github
Documentation
use std::path::{Path, PathBuf};

#[derive(Clone, Debug, PartialEq)]
pub enum VcsClient {
    Noop {},
    Git {
        source: PathBuf,
        username: String,
        email: String,
    },
}

impl VcsClient {
    pub fn new_noop() -> VcsClient {
        Self::Noop {}
    }

    pub fn new_git(
        path: &Path,
        git_username: Option<impl Into<String>>,
        git_email: Option<impl Into<String>>,
    ) -> anyhow::Result<VcsClient> {
        if !path.to_path_buf().join(".git").exists() {
            anyhow::bail!("git directory not found in: {}", path.display().to_string())
        }

        Ok(Self::Git {
            source: path.to_path_buf(),
            username: git_username
                .map(|u| u.into())
                .unwrap_or("cuddle-please".to_string()),
            email: git_email
                .map(|e| e.into())
                .unwrap_or("bot@cuddle.sh".to_string()),
        })
    }

    pub fn checkout_branch(&self) -> anyhow::Result<()> {
        match self {
            VcsClient::Noop {} => {}
            VcsClient::Git { .. } => {
                if let Err(_e) = self.exec_git(&["branch", "-D", "cuddle-please/release"]) {
                    tracing::debug!("failed to cleaned up local branch for force-push, this may be because it didn't exist before running this command");
                }
                self.exec_git(&["checkout", "-b", "cuddle-please/release"])?;
            }
        }

        Ok(())
    }

    fn exec_git(&self, args: &[&str]) -> anyhow::Result<()> {
        match self {
            VcsClient::Noop {} => {}
            VcsClient::Git {
                source,
                username,
                email,
            } => {
                let checkout_branch = std::process::Command::new("git")
                    .current_dir(source.as_path())
                    .args(&[
                        "-c",
                        &format!("user.name={}", username),
                        "-c",
                        &format!("user.email={}", email),
                    ])
                    .args(args)
                    .output()?;

                let stdout = std::str::from_utf8(&checkout_branch.stdout)?;
                let stderr = std::str::from_utf8(&checkout_branch.stderr)?;
                tracing::debug!(stdout = stdout, stderr = stderr, "git {}", args.join(" "));
            }
        }

        Ok(())
    }

    pub fn commit_and_push(&self, version: impl Into<String>, dry_run: bool) -> anyhow::Result<()> {
        match self {
            VcsClient::Noop {} => {}
            VcsClient::Git { .. } => {
                self.exec_git(&["add", "."])?;
                self.exec_git(&[
                    "commit",
                    "-m",
                    &format!("chore(release): {}", version.into()),
                ])?;

                tracing::trace!("git push -u -f origin cuddle-please/release");
                if !dry_run {
                    self.exec_git(&["push", "-u", "-f", "origin", "cuddle-please/release"])?;
                }
            }
        }

        Ok(())
    }
}