|
| 1 | +use anyhow::{Context, Result}; |
| 2 | +use clap::{Subcommand, ValueEnum}; |
| 3 | +use std::process::Command; |
| 4 | + |
| 5 | +use crate::utils::confirm; |
| 6 | + |
| 7 | +#[derive(Subcommand)] |
| 8 | +pub enum ConfigCommand { |
| 9 | + /// Apply a curated git config preset |
| 10 | + Apply { |
| 11 | + preset: Preset, |
| 12 | + #[arg(short, long)] |
| 13 | + yes: bool, |
| 14 | + #[arg(long)] |
| 15 | + dry_run: bool, |
| 16 | + }, |
| 17 | +} |
| 18 | + |
| 19 | +#[derive(ValueEnum, Clone)] |
| 20 | +pub enum Preset { |
| 21 | + /// push.autoSetupRemote, help.autocorrect, diff.algorithm |
| 22 | + Defaults, |
| 23 | + /// merge.conflictstyle zdiff3, rerere.enabled (terminal-focused) |
| 24 | + Advanced, |
| 25 | + /// core.pager delta (installs git-delta if needed) |
| 26 | + Delta, |
| 27 | +} |
| 28 | + |
| 29 | +pub fn run(cmd: ConfigCommand) -> Result<()> { |
| 30 | + let ConfigCommand::Apply { |
| 31 | + preset, |
| 32 | + yes, |
| 33 | + dry_run, |
| 34 | + } = cmd; |
| 35 | + match preset { |
| 36 | + Preset::Defaults => apply_defaults(dry_run), |
| 37 | + Preset::Advanced => apply_advanced(dry_run), |
| 38 | + Preset::Delta => apply_delta(yes, dry_run), |
| 39 | + } |
| 40 | +} |
| 41 | + |
| 42 | +type GitConfigs = &'static [(&'static str, &'static str)]; |
| 43 | + |
| 44 | +const DEFAULTS: GitConfigs = &[ |
| 45 | + ("push.autoSetupRemote", "true"), |
| 46 | + ("help.autocorrect", "prompt"), |
| 47 | + ("diff.algorithm", "histogram"), |
| 48 | +]; |
| 49 | + |
| 50 | +const ADVANCED: GitConfigs = &[ |
| 51 | + ("merge.conflictstyle", "zdiff3"), |
| 52 | + ("rerere.enabled", "true"), |
| 53 | +]; |
| 54 | + |
| 55 | +const DELTA_CONFIGS: GitConfigs = &[ |
| 56 | + ("core.pager", "delta"), |
| 57 | + ("interactive.diffFilter", "delta --color-only"), |
| 58 | + ("delta.navigate", "true"), |
| 59 | + ("delta.side-by-side", "true"), |
| 60 | +]; |
| 61 | + |
| 62 | +fn apply_defaults(dry_run: bool) -> Result<()> { |
| 63 | + apply_configs(DEFAULTS, dry_run) |
| 64 | +} |
| 65 | + |
| 66 | +fn apply_advanced(dry_run: bool) -> Result<()> { |
| 67 | + println!( |
| 68 | + "Warning: merge.conflictstyle=zdiff3 may cause issues with GitHub Desktop and GUI merge tools." |
| 69 | + ); |
| 70 | + apply_configs(ADVANCED, dry_run) |
| 71 | +} |
| 72 | + |
| 73 | +fn apply_delta(yes: bool, dry_run: bool) -> Result<()> { |
| 74 | + if !delta_installed() { |
| 75 | + if !confirm( |
| 76 | + "git-delta is not installed. Install via `cargo install git-delta`?", |
| 77 | + yes, |
| 78 | + ) { |
| 79 | + println!("Aborted."); |
| 80 | + return Ok(()); |
| 81 | + } |
| 82 | + if !dry_run { |
| 83 | + install_delta()?; |
| 84 | + } else { |
| 85 | + println!("[dry-run] Would run: cargo install git-delta"); |
| 86 | + } |
| 87 | + } |
| 88 | + println!( |
| 89 | + "Note: delta.side-by-side=true may look wrong in narrow terminals. \ |
| 90 | + Disable with: git config --global delta.side-by-side false" |
| 91 | + ); |
| 92 | + apply_configs(DELTA_CONFIGS, dry_run) |
| 93 | +} |
| 94 | + |
| 95 | +fn apply_configs(configs: GitConfigs, dry_run: bool) -> Result<()> { |
| 96 | + for (key, value) in configs { |
| 97 | + if dry_run { |
| 98 | + println!("[dry-run] git config --global {key} {value}"); |
| 99 | + } else { |
| 100 | + git_config_set(key, value)?; |
| 101 | + println!("Set {key} = {value}"); |
| 102 | + } |
| 103 | + } |
| 104 | + Ok(()) |
| 105 | +} |
| 106 | + |
| 107 | +fn git_config_set(key: &str, value: &str) -> Result<()> { |
| 108 | + let status = Command::new("git") |
| 109 | + .args(["config", "--global", key, value]) |
| 110 | + .status() |
| 111 | + .with_context(|| format!("Failed to run git config for '{key}'"))?; |
| 112 | + anyhow::ensure!(status.success(), "git config --global {key} {value} failed"); |
| 113 | + Ok(()) |
| 114 | +} |
| 115 | + |
| 116 | +fn delta_installed() -> bool { |
| 117 | + Command::new("delta") |
| 118 | + .arg("--version") |
| 119 | + .output() |
| 120 | + .map(|o| o.status.success()) |
| 121 | + .unwrap_or(false) |
| 122 | +} |
| 123 | + |
| 124 | +fn install_delta() -> Result<()> { |
| 125 | + println!("Installing git-delta..."); |
| 126 | + let status = Command::new("cargo") |
| 127 | + .args(["install", "git-delta"]) |
| 128 | + .status() |
| 129 | + .context("Failed to run cargo install git-delta")?; |
| 130 | + anyhow::ensure!(status.success(), "cargo install git-delta failed"); |
| 131 | + Ok(()) |
| 132 | +} |
0 commit comments