191 lines
6.5 KiB
Rust
191 lines
6.5 KiB
Rust
#!/usr/bin/env runrs
|
|
|
|
//! ```cargo
|
|
//! [dependencies]
|
|
//! regex = "1.7.1"
|
|
//! rust_util = "0.6"
|
|
//! ```
|
|
|
|
use std::{env, fs};
|
|
use std::os::unix::fs::PermissionsExt;
|
|
use std::path::PathBuf;
|
|
|
|
use rust_util::{debugging, failure_and_exit, information, success, warning};
|
|
use rust_util::util_file::get_home_path;
|
|
|
|
const BOLD: &str = "\x1B[1m";
|
|
const UNDER: &str = "\x1B[4m";
|
|
const END: &str = "\x1B[0m";
|
|
|
|
const DEFAULT_COMMIT_MSG_REGEXP: &str = "^(feat|fix|docs|style|refactor|test|chore)(\\([\\w\\-_.]+\\))?:\\s*.*";
|
|
|
|
/// Git commit message format check util
|
|
///
|
|
/// Commit format MUST be <type>(<scope>): subject
|
|
///
|
|
/// Usage:
|
|
/// `commit-msg.rs usage`
|
|
///
|
|
/// Install:
|
|
/// `commit-msg.rs install`
|
|
fn main() {
|
|
debugging!("Arguments:\n- {}", env::args().collect::<Vec<_>>().join("\n- "));
|
|
let arg1 = env::args().nth(1).unwrap_or_else(|| {
|
|
failure_and_exit!("Commit message is EMPTY!");
|
|
});
|
|
|
|
if arg1 == "usage" || arg1 == "USAGE" || arg1 == "--help" || arg1 == "-h" {
|
|
print_usage();
|
|
return;
|
|
}
|
|
|
|
let lower_arg1 = arg1.to_ascii_lowercase();
|
|
let is_install = lower_arg1 == "install";
|
|
let is_force_install = lower_arg1 == "forceinstall" || lower_arg1 == "force-install"
|
|
|| lower_arg1 == "installforce" || lower_arg1 == "install-force";
|
|
if is_install || is_force_install {
|
|
install_commit_msg(is_force_install);
|
|
return;
|
|
}
|
|
|
|
let commit_message = fs::read_to_string(arg1).unwrap_or_else(|e| {
|
|
failure_and_exit!("Read commit message failed: {}", e);
|
|
});
|
|
|
|
let re_str = get_commit_msg_regexp();
|
|
let re = match regex::Regex::new(re_str.trim()) {
|
|
Ok(re) => re,
|
|
Err(e) => failure_and_exit!("Parse regexp: {}, failed: {}", re_str, e),
|
|
};
|
|
let is_commit_message_matches = re.is_match(&commit_message);
|
|
|
|
information!("Commit message: {}{}{}", UNDER, commit_message.trim(), END);
|
|
if is_commit_message_matches {
|
|
success!("Commit message rule matches");
|
|
} else {
|
|
print_usage();
|
|
failure_and_exit!("Commit message rule is NOT matched");
|
|
}
|
|
}
|
|
|
|
fn print_usage() {
|
|
if let Some(usage) = get_commit_msg_usage() {
|
|
information!("{}",&usage);
|
|
return;
|
|
}
|
|
information!(r#"Please follow the commit spec:
|
|
Format: {b}<type>{e}(<scope>){b}: <subject>{e}
|
|
<scope> is optional
|
|
|
|
{b}feat{e}: add hat wobble
|
|
^--^ ^------------^
|
|
| |
|
|
| +-> Summary in present tense.
|
|
|
|
|
+-------> Type: chore, docs, feat, fix, refactor, style, or test.
|
|
|
|
{b}feat{e} : new feature for the user, not a new feature for build script
|
|
{b}fix{e} : bug fix for the user, not a fix to a build script
|
|
{b}docs{e} : changes to the documentation
|
|
{b}style{e} : formatting, missing semi colons, etc; no production code change
|
|
{b}refactor{e} : refactoring production code, eg. renaming a variable
|
|
{b}test{e} : adding missing tests, refactoring tests; no production code change
|
|
{b}chore{e} : updating grunt tasks etc; no production code change
|
|
|
|
Reference: {u}https://gist.github.com/joshbuchea/6f47e86d2510bce28f8e7f42ae84c716{e}
|
|
or mirror: {u}https://hatter.ink/wiki/view_raw.action?__access_token=PUBLIC&id=42{e}
|
|
"#, b = BOLD, e = END, u = UNDER);
|
|
}
|
|
|
|
fn search_git_root_path() -> Option<PathBuf> {
|
|
let mut p = PathBuf::from(".").canonicalize().unwrap_or_else(|e|
|
|
failure_and_exit!("Get current dir failed: {}", e)
|
|
);
|
|
loop {
|
|
debugging!("Check git path: {:?}", p);
|
|
if p.join(".git").is_dir() {
|
|
debugging!("Found git path: {:?}", p);
|
|
return Some(p);
|
|
}
|
|
if !p.pop() { return None; }
|
|
}
|
|
}
|
|
|
|
fn search_git_root_path_e() -> PathBuf {
|
|
search_git_root_path().unwrap_or_else(|| failure_and_exit!("Git root path not found!"))
|
|
}
|
|
|
|
fn get_home_e() -> PathBuf {
|
|
get_home_path().unwrap_or_else(|| failure_and_exit!("Get user home failed!"))
|
|
}
|
|
|
|
fn get_commit_msg_file(file: &str) -> Option<String> {
|
|
let settings_regexp = search_git_root_path_e().join("settings").join(file);
|
|
debugging!("Check file: {:?}, exists: {}", settings_regexp, settings_regexp.exists());
|
|
if settings_regexp.exists() {
|
|
information!("Found file: {:?}", settings_regexp);
|
|
return Some(fs::read_to_string(settings_regexp).unwrap());
|
|
}
|
|
|
|
let path_regexp = search_git_root_path_e().join(".git").join("hooks").join(file);
|
|
debugging!("Check file: {:?}, exists: {}", path_regexp, path_regexp.exists());
|
|
if path_regexp.exists() {
|
|
information!("Found file: {:?}", path_regexp);
|
|
return Some(fs::read_to_string(path_regexp).unwrap());
|
|
}
|
|
|
|
let home_path_regexp = get_home_e().join(&(".".to_owned() + file));
|
|
debugging!("Check file: {:?}, exists: {}", home_path_regexp, home_path_regexp.exists());
|
|
if home_path_regexp.exists() {
|
|
information!("Found file: {:?}", home_path_regexp);
|
|
return Some(fs::read_to_string(home_path_regexp).unwrap());
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
fn get_commit_msg_regexp() -> String {
|
|
get_commit_msg_file("commit-msg-regexp").unwrap_or_else(|| DEFAULT_COMMIT_MSG_REGEXP.to_owned())
|
|
}
|
|
|
|
fn get_commit_msg_usage() -> Option<String> {
|
|
get_commit_msg_file("commit-msg-regexp-usage")
|
|
}
|
|
|
|
fn copy_file_and_apply_executable_permission(from: &PathBuf, to: &PathBuf) {
|
|
information!("Copy file: {:?} to: {:?}", from, to);
|
|
let from_content = fs::read(&from).unwrap_or_else(|e|
|
|
failure_and_exit!("Read file: {:?}, failed: {}", from, e)
|
|
);
|
|
fs::write(to, from_content).unwrap_or_else(|e|
|
|
failure_and_exit!("Write file: {:?}, failed: {}", to, e)
|
|
);
|
|
fs::set_permissions(to, PermissionsExt::from_mode(0o755)).unwrap_or_else(|e|
|
|
failure_and_exit!("Apply executable permission on file: {:?}, failed: {}", to, e)
|
|
);
|
|
}
|
|
|
|
fn install_commit_msg(force: bool) {
|
|
let commit_msg_crs = get_home_e().join("bin").join("commit-msg.rs");
|
|
let commig_msg_exec_file = if commit_msg_crs.exists() {
|
|
commit_msg_crs
|
|
} else {
|
|
warning!("File {:?} NOT exists!", commit_msg_crs);
|
|
PathBuf::from(env::args().next().unwrap())
|
|
};
|
|
|
|
let git_hooks = search_git_root_path_e().join(".git").join("hooks");
|
|
if !git_hooks.exists() {
|
|
failure_and_exit!("Path {:?} NOT exists!", git_hooks);
|
|
}
|
|
let git_hooks_commit_msg = git_hooks.join("commit-msg");
|
|
if git_hooks_commit_msg.exists() && !force {
|
|
failure_and_exit!("File {:?} exists! or try {}forceinstall{}.", git_hooks_commit_msg, BOLD, END);
|
|
}
|
|
|
|
copy_file_and_apply_executable_permission(&commig_msg_exec_file, &git_hooks_commit_msg);
|
|
|
|
success!("Install commit-msg to repo succeed!");
|
|
}
|
|
|