Skip to main content

solar_core/tool/
vhooks.rs

1use crate::{Config, Global, SolarError, ToolTrait};
2use clap::Parser;
3use derive_getters::Getters;
4use rust_terminal::Terminal;
5use serde::{Deserialize, Serialize};
6use std::{
7    fs,
8    path::{Path, PathBuf},
9};
10
11fn default_name() -> String {
12    ".hooks".to_string()
13}
14
15#[derive(Parser, Clone, Default, PartialEq, Debug, Serialize, Deserialize, Getters)]
16pub struct Vhooks {
17    /// The working directory to use for installation.
18    #[arg(short, long, default_value = ".")]
19    #[serde(skip)]
20    destination: PathBuf,
21
22    /// Git hooks directory name.
23    #[arg(short, long, default_value = ".hooks")]
24    #[serde(default = "default_name")]
25    name: String,
26
27    /// Whether to remove all hooks when removing vhooks, or just put thim in unversioned git hooks directory.
28    #[arg(short, long, default_value = "false")]
29    #[serde(skip)]
30    remove_all: bool,
31}
32
33impl Vhooks {
34    pub fn new(destination: PathBuf, name: String, remove_all: bool) -> Self {
35        Self {
36            destination,
37            name,
38            remove_all,
39        }
40    }
41
42    pub fn move_hooks(prev: &PathBuf, new: &Path) -> Result<(), SolarError> {
43        for item in fs::read_dir(prev)? {
44            let item = item?;
45            fs::rename(item.path(), new.join(item.file_name()))?;
46        }
47        Ok(())
48    }
49
50    pub fn set_hooks_path(&self, path: PathBuf) -> Result<(), SolarError> {
51        Terminal::command()
52            .current_dir(&self.destination)
53            .piped()
54            .run(
55                "git",
56                vec![
57                    "config",
58                    "core.hooksPath",
59                    path.to_str().ok_or("Could not convert path to string.")?,
60                ],
61            )?;
62        Ok(())
63    }
64}
65
66impl ToolTrait for Vhooks {
67    fn set_dest(&mut self, dest: &Path) {
68        self.destination = dest.to_path_buf();
69    }
70
71    fn install(&mut self) -> Result<(), SolarError> {
72        // Update configuration file.
73        let config = Config::load_or_default(&self.destination);
74        let current_tool_cfg = config.vhooks().clone();
75        config.set_vhooks(Some(self.clone())).save()?;
76
77        // Ensure working directory is a git repository.
78        Global::git_init(&self.destination)?;
79
80        // Path to the versioned hooks directory.
81        let hooks_path = self.destination.join(&self.name);
82
83        // Create the new hooks directory.
84        fs::create_dir_all(&hooks_path)?;
85
86        // Set the new hooks directory as the git hooks directory.
87        self.set_hooks_path(PathBuf::from(format!("./{}", &self.name)))?;
88
89        // If there is a current installation, move the hooks from the old directory to the new one.
90        if let Some(vhooks) = current_tool_cfg {
91            let old_hooks_path = self.destination.join(vhooks.name());
92            Self::move_hooks(&old_hooks_path, &hooks_path)?;
93            fs::remove_dir_all(&old_hooks_path)?;
94        }
95
96        Ok(())
97    }
98
99    fn uninstall(&mut self) -> Result<(), SolarError> {
100        let config = Config::load_from(&self.destination)?;
101        let current_tool_cfg: Self = config
102            .vhooks()
103            .clone()
104            .ok_or("Cannot uninstall vhooks - vhooks not found in configuration.")?;
105
106        // Paths to the versioned hooks directory and default directory.
107        let hooks_path = self.destination.join(current_tool_cfg.name());
108        let default_path = self.destination.join(Global::default_git_hook_dir());
109
110        // Must be a git repository in order to set default hook directory.
111        Global::is_git(&self.destination)?;
112
113        // Git hooks folder must exist.
114        fs::create_dir_all(&default_path)?;
115
116        // If not removing hooks, move them to the default hooks directory.
117        if !self.remove_all {
118            Self::move_hooks(&hooks_path, &default_path)?;
119        }
120
121        // Set the new hooks directory as the default git hooks directory.
122        self.set_hooks_path(PathBuf::from(format!(
123            "./{}",
124            Global::default_git_hook_dir()
125                .to_str()
126                .ok_or("Could not convert path to string.")?
127        )))?;
128
129        // Remove the versioned hooks folder.
130        fs::remove_dir_all(&hooks_path)?;
131
132        // Update configuration, remove if empty.
133        let config = config.set_vhooks(None);
134        match config.is_empty() {
135            true => fs::remove_file(config.path())?,
136            false => config.save()?,
137        }
138
139        Ok(())
140    }
141}