feat: add commit-msg-rs

This commit is contained in:
2023-02-23 00:49:37 +08:00
parent dd507b7f6e
commit 370be7df18
3 changed files with 441 additions and 0 deletions

241
commit-msg-rs/Cargo.lock generated Normal file
View File

@@ -0,0 +1,241 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "commit-msg-rs"
version = "0.1.0"
dependencies = [
"regex",
"rust_util",
]
[[package]]
name = "dirs-next"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
dependencies = [
"cfg-if",
"dirs-sys-next",
]
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "getrandom"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "proc-macro2"
version = "1.0.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_users"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
dependencies = [
"getrandom",
"redox_syscall",
"thiserror",
]
[[package]]
name = "regex"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "rust_util"
version = "0.6.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df24005feacce81f4ae340464b39c380f7e01e7225bfdef62d40cb44cb1c11d7"
dependencies = [
"lazy_static",
"libc",
"term",
"term_size",
]
[[package]]
name = "rustversion"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70"
[[package]]
name = "syn"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "term"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f"
dependencies = [
"dirs-next",
"rustversion",
"winapi",
]
[[package]]
name = "term_size"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "thiserror"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

10
commit-msg-rs/Cargo.toml Normal file
View File

@@ -0,0 +1,10 @@
[package]
name = "commit-msg-rs"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
regex = "1.7.1"
rust_util = "0.6"

190
commit-msg-rs/src/main.rs Normal file
View File

@@ -0,0 +1,190 @@
#!/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!");
}