git-smash 0.1.0

Smash staged changes into previous commits
#![allow(clippy::use_self)]
use crate::args::Args;
use crate::errors::*;
use crate::git::{git_check_version, git_version, GitConfigBuilder};

use std::str::FromStr;

use strum_macros::{Display, EnumString};

pub const DEFAULT_LIST_FORMAT: &str =
    "%C(yellow)%h%C(reset) [%(smash:source)] %s %C(cyan)<%an>%C(reset) %C(green)(%cr)%C(reset)%C(auto)%d%C(reset)";
pub const DEFAULT_FORMAT_SOURCE_FILES: &str = "%C(green)F%C(reset)";
pub const DEFAULT_FORMAT_SOURCE_BLAME: &str = "%C(red)B%C(reset)";
pub const DEFAULT_FORMAT_SOURCE_RECENT: &str = "%C(magenta)R%C(reset)️️";

#[derive(Debug, PartialEq, Eq, Display, EnumString)]
#[strum(serialize_all = "snake_case")]
pub enum DisplayMode {
    Smash,
    List,
    Select,
}

pub enum CommitRange {
    Local,
    All,
    Range(String),
}

pub enum FixupMode {
    Fixup,
    Amend,
    Reword,
}

impl FixupMode {
    pub fn to_cli_option(&self, target: &str) -> String {
        match self {
            FixupMode::Fixup => format!("--fixup={}", target),
            FixupMode::Amend => format!("--fixup=amend:{}", target),
            FixupMode::Reword => format!("--fixup=reword:{}", target),
        }
    }
}

pub struct Config {
    pub mode: DisplayMode,
    pub range: CommitRange,
    pub format: String,
    pub max_count: u32,
    pub auto_rebase: bool,
    pub interactive: bool,
    pub blame: bool,
    pub files: bool,
    pub recent: u32,
    pub commit: Option<String>,
    pub source_label_files: String,
    pub source_label_blame: String,
    pub source_label_recent: String,
    pub fixup_mode: FixupMode,
}

impl Config {
    #[allow(clippy::cognitive_complexity)]
    pub fn load(args: &Args) -> Result<Self> {
        let git_version = git_version().context("failed to get git version")?;

        let config = Self {
            mode: if args.list {
                DisplayMode::List
            } else if args.select {
                DisplayMode::Select
            } else if let Some(mode) = GitConfigBuilder::new("smash.mode").get()? {
                DisplayMode::from_str(&mode)
                    .with_context(|| format!("failed to parse smash.mode '{}'", mode))?
            } else {
                DisplayMode::Smash
            },
            range: if args.local {
                CommitRange::Local
            } else if args.all {
                CommitRange::All
            } else if let Some(range) = &args.range {
                CommitRange::Range(range.into())
            } else if let Some(range) = GitConfigBuilder::new("smash.range").get()? {
                match range.as_str() {
                    "local" => CommitRange::Local,
                    "all" => CommitRange::All,
                    range => CommitRange::Range(range.into()),
                }
            } else {
                CommitRange::All
            },
            format: if let Some(format) = &args.format {
                format.into()
            } else {
                GitConfigBuilder::new("smash.format")
                    .get()?
                    .unwrap_or_else(|| DEFAULT_LIST_FORMAT.into())
            },
            max_count: if let Some(max_count) = args.max_count {
                max_count
            } else {
                GitConfigBuilder::new("smash.maxCommitCount")
                    .with_type("int")
                    .with_default("0")
                    .get_as_int()?
                    .unwrap_or(0)
            },
            auto_rebase: if args.rebase {
                true
            } else if args.no_rebase {
                false
            } else {
                GitConfigBuilder::new("smash.autorebase")
                    .with_type("bool")
                    .with_default("true")
                    .get_as_bool()?
                    .unwrap_or(true)
            },
            interactive: if args.interactive {
                true
            } else {
                GitConfigBuilder::new("smash.interactive")
                    .with_type("bool")
                    .with_default("false")
                    .get_as_bool()?
                    .unwrap_or(false)
            },
            blame: if args.blame {
                true
            } else if args.no_blame {
                false
            } else {
                GitConfigBuilder::new("smash.blame")
                    .with_type("bool")
                    .with_default("true")
                    .get_as_bool()?
                    .unwrap_or(true)
            },
            files: if args.files {
                true
            } else if args.no_files {
                false
            } else {
                GitConfigBuilder::new("smash.files")
                    .with_type("bool")
                    .with_default("true")
                    .get_as_bool()?
                    .unwrap_or(true)
            },
            recent: if let Some(recent) = args.recent {
                recent
            } else {
                GitConfigBuilder::new("smash.recent")
                    .with_type("int")
                    .with_default("0")
                    .get_as_int()?
                    .unwrap_or(0)
            },
            source_label_files: GitConfigBuilder::new("smash.filesSourceFormat")
                .with_default(DEFAULT_FORMAT_SOURCE_FILES)
                .get()?
                .unwrap_or_else(|| DEFAULT_FORMAT_SOURCE_FILES.into()),
            source_label_blame: GitConfigBuilder::new("smash.blameSourceFormat")
                .with_default(DEFAULT_FORMAT_SOURCE_BLAME)
                .get()?
                .unwrap_or_else(|| DEFAULT_FORMAT_SOURCE_BLAME.into()),
            source_label_recent: GitConfigBuilder::new("smash.recentSourceFormat")
                .with_default(DEFAULT_FORMAT_SOURCE_RECENT)
                .get()?
                .unwrap_or_else(|| DEFAULT_FORMAT_SOURCE_RECENT.into()),
            commit: args.commit.clone(),
            fixup_mode: if args.amend {
                git_check_version(&git_version, ">=2.33", "--amend")?;
                FixupMode::Amend
            } else if args.reword {
                git_check_version(&git_version, ">=2.33", "--reword")?;
                FixupMode::Reword
            } else {
                FixupMode::Fixup
            },
        };

        Ok(config)
    }
}