patchouli 0.1.0

Lightweight CLI tool for maintaing patch based forks inspired by PaperMC's paperweight
Documentation
use std::{
    fs,
    path::{absolute, Path},
};

use color_eyre::Result;

use crate::{
    config::{self, GitRepo},
    git::Git,
};

pub fn apply() -> Result<()> {
    let config = config::read()?;
    let path = absolute(Path::new(&config.name))?;
    if !path.exists() {
        fs::create_dir_all(&path)?;
    }
    let patches = absolute(Path::new("patches"))?;
    let git = Git(&path);

    checkout_from_upstream(&git, config.upstream, &config.r#ref)?;

    for file in &config.unneeded_files {
        let _ = std::fs::remove_dir_all(path.join(file));
        let _ = std::fs::remove_file(path.join(file));
    }

    git.exec(&["add", "."])?;
    git.exec(&[
        "commit",
        "-m",
        "Initial",
        "--author=Initial Source <auto@mated.null>",
        "--allow-empty",
    ])?;
    let _ = git.exec(&["tag", "-d", "base"]);
    git.exec(&["tag", "base"])?;

    apply_patches(&git, &patches)?;

    Ok(())
}

fn checkout_from_upstream(git: &Git, upstream: GitRepo, reference: &str) -> Result<()> {
    let _ = git.exec(&["init", "--quiet"]);
    git.disable_auto_gpg_signing()?;
    let _ = git.exec(&["remote", "remove", "upstream"]);
    git.exec(&["remote", "add", "upstream", &upstream.https_url().unwrap()])?;
    git.exec(&["fetch", "upstream", "--prune"])?;
    if git.exec(&["checkout", "main"]).is_err() {
        git.exec(&["checkout", "-b", "main"])?;
    }
    git.exec(&["reset", "--hard", reference])?;
    let _ = git.exec(&["gc"]);
    Ok(())
}

fn apply_patches(git: &Git, patch_dir: &Path) -> Result<()> {
    let tempdir = tempfile::tempdir()?;
    let mail_dir = tempdir.path().join("new");
    fs::create_dir_all(&mail_dir)?;

    for patch in patch_dir.read_dir()?.flatten() {
        if patch.file_name().to_str().unwrap().ends_with(".patch") {
            fs::copy(patch.path(), mail_dir.join(patch.file_name()))?;
        }
    }

    let _ = git.exec(&["am", "--abort"]);
    match git.exec(&[
        "am",
        "--3way",
        "--ignore-whitespace",
        absolute(tempdir.path())?.to_str().unwrap(),
    ]) {
        Ok(_) => {
            println!("Patches applied cleanly");
        }
        Err(_) => {
            println!("Failed to apply patches.");
            println!("Fix patches and rebuild with `patchouli rebuild`");
        }
    }
    Ok(())
}