diff --git a/src/besafe.rs b/src/besafe.rs new file mode 100644 index 0000000..b60af72 --- /dev/null +++ b/src/besafe.rs @@ -0,0 +1,41 @@ +use crate::utils::{code_to_enum, Status}; +use std::process::{exit, Command}; + +pub fn besafe() { + let mut gs = Command::new("git"); + gs.arg("status").arg("-s"); + + let output_raw = gs.output().unwrap(); + let output = String::from_utf8_lossy(&output_raw.stdout); + let files: Vec = output + .split("\n") + .map(|i| i.to_owned()) + .filter(|i| !i.is_empty()) + .collect(); + + for file in files.iter() { + let mut file_split = file.split_whitespace(); + let code = file_split.next().expect("Failed to get file status!"); + let file_name = file_split.next().expect("Failed to get file info!"); + let status = code_to_enum(code); + if file_name.starts_with(".env") && file_name != ".env.example" { + match status { + Status::Staged => { + println!("[besafe] {} file is staged! Unstage it and add it to .gitignore before committing.", file_name); + exit(1); + }, + Status::Modified => { + println!("[besafe] {} file has changes and is not in .gitignore! Remove it and add it to .gitignore before committing.", file_name); + exit(1); + }, + Status::StagedModified => { + println!("[besafe] {} file staged! Unstage it and add it to .gitignore before committing.", file_name); + exit(1); + }, + Status::Untracked => println!("[besafe] {} file is not in .gitignore! Consider adding it.", file_name), + Status::Unknown => println!("[besafe] {} file found in git history but status cannot be determined! Make sure it doesn't contain any sensitive data before committing", file_name), + } + } + } + println!("No unsafe .env problems found!"); +} diff --git a/src/install.rs b/src/install.rs new file mode 100644 index 0000000..1defc71 --- /dev/null +++ b/src/install.rs @@ -0,0 +1,34 @@ +use std::{ + env::current_exe, + fs::{read_to_string, write}, + path::PathBuf, +}; + +const PRE_COMMIT_PATH: &str = ".git/hooks/pre-commit"; + +pub fn install_hook() { + let path = PathBuf::from(PRE_COMMIT_PATH); + let mut file = read_to_string(&path).unwrap_or(String::from("")); + + let mut current_path = current_exe() + .unwrap_or(PathBuf::new()) + .as_path() + .to_string_lossy() + .to_string(); + + if cfg!(windows) { + current_path = current_path.replace("\\", "/"); + current_path = current_path.replace(":/", "/"); + current_path = "/".to_string() + current_path.as_str(); + } + + file += format!("\n\n{}", current_path).as_str(); + + if !file.starts_with("#!/") { + file = String::from("#!/bin/sh\n\n") + &file; + } + + write(path, file).expect("Failed to install pre-commit hook!"); + + println!("Successfully installed pre-commit hook!"); +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..ef7c778 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,34 @@ +use std::process::exit; + +use besafe::besafe; +use clap::Command; +use install::install_hook; + +mod besafe; +mod install; +mod utils; + +fn main() { + let install = Command::new("install"); + + let cli = Command::new("besafe") + .version(clap::crate_version!()) + .author(clap::crate_authors!()) + .about("A simple Git pre-commit hook for preventing commiting of .env files!") + .subcommand(install); + + match cli.try_get_matches() { + Ok(matches) => match matches.subcommand_name() { + Some("install") => install_hook(), + None => { + println!("[besafe] Checking..."); + besafe(); + } + Some(_) => {} + }, + Err(_) => { + println!("[besafe] Invalid command!"); + exit(1); + } + } +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..489103f --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,18 @@ +#[derive(Debug)] +pub enum Status { + Untracked, // ?? + Staged, // A + Modified, // M + StagedModified, // AM + Unknown, +} + +pub fn code_to_enum(code: &str) -> Status { + match code { + "??" => Status::Untracked, + "A" => Status::Staged, + "M" => Status::Modified, + "AM" => Status::StagedModified, + _ => Status::Unknown, + } +}