diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..e9967dc --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,8 @@ +# On Windows MSVC, statically link the C runtime so that the resulting EXE does +# not depend on the vcruntime DLL. +[target.x86_64-pc-windows-msvc] +rustflags = ["-C", "target-feature=+crt-static"] +[target.i686-pc-windows-msvc] +rustflags = ["-C", "target-feature=+crt-static"] +[target.aarch64-pc-windows-msvc] +rustflags = ["-C", "target-feature=+crt-static"] diff --git a/.husky/commit-msg b/.husky/commit-msg deleted file mode 100644 index bdfc2a0..0000000 --- a/.husky/commit-msg +++ /dev/null @@ -1 +0,0 @@ -pnpm exec commit $1 diff --git a/.husky/pre-commit b/.husky/pre-commit deleted file mode 100644 index 8ba25f3..0000000 --- a/.husky/pre-commit +++ /dev/null @@ -1 +0,0 @@ -pnpm exec lint-staged --concurrent false diff --git a/.npmrc b/.npmrc deleted file mode 100644 index 009aa06..0000000 --- a/.npmrc +++ /dev/null @@ -1,2 +0,0 @@ -shamefully-hoist=true -auto-install-peers=true diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 8372aa1..0000000 --- a/.prettierignore +++ /dev/null @@ -1,2 +0,0 @@ -CHANGELOG.md -config.json diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index b2095be..0000000 --- a/.prettierrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "semi": false, - "singleQuote": true -} diff --git a/.releaserc b/.releaserc index 9c3dde7..95f8c1d 100644 --- a/.releaserc +++ b/.releaserc @@ -20,11 +20,11 @@ { "assets": [ { - "path": "artifacts/git-commit-analytics_win.zip", + "path": "artifacts/git-commit-analytics_v*_win.zip", "label": "Windows Build" }, { - "path": "artifacts/git-commit-analytics_mac.zip", + "path": "artifacts/git-commit-analytics_v*_mac.zip", "label": "macOS Build" } ] diff --git a/.vscode/settings.json b/.vscode/settings.json index 0b66043..205f8cd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,6 +6,7 @@ "source.fixAll.prettier": "always" }, "cSpell.words": [ + "analyticsjs", "chrono", "codesign", "postject", diff --git a/CHANGELOG.md b/CHANGELOG.md index 0953a68..5512330 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,27 @@ +## [2.0.3](https://github.com/analyticsjs/git-commit-analytics/compare/v2.0.2...v2.0.3) (2025-05-01) + + +### Bug Fixes + +* add git availability check with download link ([e2f2ebd](https://github.com/analyticsjs/git-commit-analytics/commit/e2f2ebd43f5df619260dc4935f8b2db2152628ea)) +* **config:** allow optional config fields with default values ([30fac51](https://github.com/analyticsjs/git-commit-analytics/commit/30fac513f72902e12ec575ba46ae3e974ce3cf0f)) +* prevent window from closing immediately on crash by waiting for keypress ([699b999](https://github.com/analyticsjs/git-commit-analytics/commit/699b999a324b6a563636466ed705894c135d0e7b)) + +## [2.0.2](https://github.com/analyticsjs/git-commit-analytics/compare/v2.0.1...v2.0.2) (2025-04-28) + + +### Bug Fixes + +* **report:** deduplicate commit messages within each group in the report ([e669454](https://github.com/analyticsjs/git-commit-analytics/commit/e669454aaedbf02a2b2db495a9da0b3de3a5908c)) +* **windows:** statically link MSVC CRT to fix missing vcruntime140.dll issue ([097997e](https://github.com/analyticsjs/git-commit-analytics/commit/097997e740a1a333ebd51e16f7a65fd3e9e69c96)) + +## [2.0.1](https://github.com/analyticsjs/git-commit-analytics/compare/v2.0.0...v2.0.1) (2025-04-28) + + +### Bug Fixes + +* **release:** upload built artifacts with versioned filenames to GitHub assets ([98616fc](https://github.com/analyticsjs/git-commit-analytics/commit/98616fcdb6eff5cb75b4dbc1ae4f2143fe86692d)) + # [2.0.0](https://github.com/analyticsjs/git-commit-analytics/compare/v1.5.1...v2.0.0) (2025-04-27) diff --git a/Cargo.toml b/Cargo.toml index a094a08..cc333f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["chengpeiquan "] -description = "A tool to analyze your git repository's commit log. I can help you generate daily/weekly or longer work reports." +description = "A tool for analyzing git commit logs and generating daily, weekly, or custom work reports." edition = "2021" license = "MIT" name = "git-commit-analytics" diff --git a/README.md b/README.md index b04c69f..9c6a11a 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,15 @@ English | [简体中文](https://github.com/analyticsjs/git-commit-analytics/blob/main/README.zh-CN.md) -A tool to analyze your git repository's commit log. It can help you generate daily/weekly or longer work reports. +A tool for analyzing git commit logs and generating daily, weekly, or custom work reports. -![git-commit-analytics](https://cdn.chengpeiquan.com/img/2022/01/20220103021254.gif) +![git-commit-analytics](https://cdn.chengpeiquan.com/img/2025/05/202505020245534.gif) ## 🚀 Download This is a client tool, so you need to download the program to use it. See: [The Latest Release](https://github.com/analyticsjs/git-commit-analytics/releases/latest) to download. -Note: - -- As the latest version uses a new packaging tool, Windows device testing is currently missing. If you cannot run it, please download the [v1.4.1](https://github.com/analyticsjs/git-commit-analytics/releases/tag/v1.4.1) version of the product -- The latest version fixes the problem that macOS cannot run the build product, but if you still encounter problems, you can choose to clone the code of this repository and use it through `pnpm i` and `pnpm dev`. +> Note: This tool requires Git to be installed and properly configured in your system's PATH. Please make sure Git is installed before running the program. ## ⚡ Usage @@ -26,28 +23,33 @@ You need to create a `config.json` at the same folder with the program, and writ ```json { "lang": "en", - "authors": ["chengpeiquan"], - "dateRange": ["2021-12-01", "2022-01-31"], - "repos": ["D:\\Git\\git-commit-analytics"], + "authors": ["my-name"], + "dateRange": ["2025-04-01", "2025-05-01"], + "repos": ["/path/to/my-project-folder"], "format": { - "git-commit-analytics": "Git Commit Analytics" + "my-project-folder": "My Awesome Project" }, "includes": ["feat", "fix", "docs", "style", "refactor", "test", "chore"], - "excludes": ["typo", "backup", "progress"] + "excludes": ["typo", "backup"] } ``` +**NOTE:** Please configure the repos path according to your operating system. For example: + +- On Windows, you must use backslashes (\), e.g., `D:\\path\\to\\folder-name` +- On macOS, use forward slashes (/), e.g., `/path/to/folder-name` + The configuration items are described as follows: -| key | type | description | -| :-------: | :-----------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| lang | string | Set program default language, support `en` (English) and `zh` (Simplified Chinese). | -| authors | string[] | Filter the author name of commits, support multiple author names, for you may have different names in different repos. | -| dateRange | [string, string] | Fill in [start date, end date], support the legal time format, and count from the start date `00:00:00` to the end date `23:59:59`(If not configured, the default day to run the program). | -| repos | string[] | The Git repo folder on your computer, need to be switched to the branch you want to count.
你电脑里的 Git 仓库文件夹,需要提前切换到你要统计的分支。 | -| format | { [key: string]: string } | Format your folder name as the project name. | -| includes | string[] | The commit message prefix to be included in the statistics. | -| excludes | string[] | In the statistical results, exclude commit messages that contain these keywords. | +| key | type | description | +| :-------: | :-----------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| lang | string | Set program default language, support `en` (English, default) and `zh` (Simplified Chinese). | +| authors | string[] | Filter commit authors. Supports multiple author names, useful if you use different names in different repositories. | +| dateRange | [string, string] | Specify [start date, end date]. Supports valid date formats. The statistics cover from `00:00:00` of the start date to `23:59:59` of the end date. If not set, defaults to the current day. | +| repos | string[] | The Git repository folders on your computer. Please switch to the branch you want to analyze in advance. | +| format | { [key: string]: string } | Format your folder names as project names. | +| includes | string[] | Commit message prefixes to include in the statistics. | +| excludes | string[] | Exclude commit messages containing these keywords from the results. | Among them, `authors` / `includes` / `excludes` will be created as regular expressions to match data. diff --git a/README.zh-CN.md b/README.zh-CN.md index 2c7a392..a919c8e 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -2,18 +2,15 @@ [English](https://github.com/analyticsjs/git-commit-analytics/blob/main/README.md) | 简体中文 -一个可以分析你的 Git 仓库 commit 记录的工具。它可以帮你生成一份工作日报 / 周报,或者你需要的更长时间范围的工作报告。 +一款用于分析 Git 提交日志并生成每日、每周或自定义工作报告的工具。 -![git-commit-analytics](https://cdn.chengpeiquan.com/img/2022/01/20220103021254.gif) +![git-commit-analytics](https://cdn.chengpeiquan.com/img/2025/05/202505020245534.gif) ## 🚀 下载安装 这是一个客户端工具,所以你需要下载程序去使用它,点击 [最新版本](https://github.com/analyticsjs/git-commit-analytics/releases/latest) 去下载客户端。 -注: - -- 由于最新版使用了新的打包工具,目前缺少 Windows 设备测试,如果无法运行,请下载 [v1.4.1](https://github.com/analyticsjs/git-commit-analytics/releases/tag/v1.4.1) 版本的产物 -- 最新版本修复了 macOS 无法运行构建产物的问题,但如果依然遇到问题,可以选择克隆本仓库的代码,并通过 `pnpm i` 和 `pnpm dev` 使用。 +> 注意: 本工具依赖于已安装并配置好的 Git,请确保在运行前已正确安装 Git 并将其添加到环境变量中。 ## ⚡ 使用方法 @@ -26,22 +23,27 @@ ```json { "lang": "en", - "authors": ["chengpeiquan"], - "dateRange": ["2021-12-01", "2022-01-31"], - "repos": ["D:\\Git\\git-commit-analytics"], + "authors": ["my-name"], + "dateRange": ["2025-04-01", "2025-05-01"], + "repos": ["/path/to/my-project-folder"], "format": { - "git-commit-analytics": "Git Commit Analytics" + "my-project-folder": "My Awesome Project" }, "includes": ["feat", "fix", "docs", "style", "refactor", "test", "chore"], - "excludes": ["typo", "backup", "progress"] + "excludes": ["typo", "backup"] } ``` +**提醒:** 请根据操作系统正确配置 repos 字段的路径。例如: + +- 在 Windows 系统中,路径必须使用反斜杠(\),如:`D:\\path\\to\\folder-name` +- 在 macOS 下,请使用正斜杠(/),如:`/path/to/folder-name` + 配置项说明如下: | key | type | description | | :-------: | :-----------------------: | :------------------------------------------------------------------------------------------------------------------------------------------- | -| lang | string | 设置软件的默认语言,支持 `en` (英语)和 `zh` (简体中文)。 | +| lang | string | 设置软件的默认语言,支持 `en` (英语,默认值)和 `zh` (简体中文)。 | | authors | string[] | 筛选 commit 的作者名称,支持多个作者名称,用于你在不同的仓库可能有不同的名字。 | | dateRange | [string, string] | 填写 [开始日期, 结束日期] , 支持合法的时间格式,会从开始日期的 `00:00:00` 统计到截止日期的 `23:59:59` (如果不配置则默认运行程序的当天)。 | | repos | string[] | 你电脑里的 Git 仓库文件夹,需要提前切换到你要统计的分支。 | @@ -60,7 +62,7 @@ | type | description | | :------: | :---------: | | feat | 功能开发 | -| fix | BUG修复 | +| fix | BUG 修复 | | docs | 完善文档 | | style | 优化样式 | | refactor | 代码重构 | diff --git a/src/config/schema.rs b/src/config/schema.rs index 8e4b2dd..4ce0ad1 100644 --- a/src/config/schema.rs +++ b/src/config/schema.rs @@ -6,6 +6,7 @@ use std::{collections::HashMap, fs}; #[derive(Debug, Deserialize)] pub struct ConfigFile { /// Language setting: 'en' or 'zh' + #[serde(default)] pub lang: String, /// List of author names @@ -14,7 +15,7 @@ pub struct ConfigFile { /// Date range: [start_date, end_date] /// Vec here represents a fixed-length array of 2 strings - #[serde(rename = "dateRange")] + #[serde(rename = "dateRange", default)] pub date_range: Vec, /// List of Git repository paths @@ -23,12 +24,15 @@ pub struct ConfigFile { /// Repository name formatting map /// HashMap is equivalent to Record in TypeScript /// or { [key: string]: string } + #[serde(default)] pub format: HashMap, /// List of commit types to include + #[serde(default)] pub includes: Vec, /// List of keywords to exclude + #[serde(default)] pub excludes: Vec, } @@ -38,7 +42,10 @@ impl ConfigFile { let content = fs::read_to_string(path)?; // Parsing JSON - let config: ConfigFile = serde_json::from_str(&content)?; + let mut config: ConfigFile = serde_json::from_str(&content)?; + + // Allow missing fields + config.fill_defaults(); // Validate the config config.validate()?; @@ -46,12 +53,45 @@ impl ConfigFile { Ok(config) } - fn validate(&self) -> Result<(), ConfigError> { + fn fill_defaults(&mut self) { + use chrono::Local; + + // lang default en + if self.lang.is_empty() { + self.lang = "en".to_string(); + } + + // dateRange default today if self.date_range.len() != 2 { - return Err(ConfigError::InvalidConfig( - "date_range must contain exactly 2 dates".to_string(), - )); + let today = Local::now().format("%Y-%m-%d").to_string(); + self.date_range = vec![today.clone(), today]; + } + + // includes default the day the program is running + if self.includes.is_empty() { + self.includes = vec![ + "feat".into(), + "fix".into(), + "docs".into(), + "style".into(), + "refactor".into(), + "test".into(), + "chore".into(), + ]; + } + + // excludes default empty + if self.excludes.is_empty() { + self.excludes = vec![]; + } + + // format default empty + if self.format.is_empty() { + self.format = std::collections::HashMap::new(); } + } + + fn validate(&self) -> Result<(), ConfigError> { if self.authors.is_empty() { return Err(ConfigError::InvalidConfig( "authors list cannot be empty".to_string(), diff --git a/src/config/types.rs b/src/config/types.rs index c0720bc..a58e185 100644 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -97,9 +97,11 @@ impl fmt::Display for Config { for inc in &self.includes { writeln!(f, " - {}", inc)?; } - writeln!(f, "{}:", t("excludes"))?; - for exc in &self.excludes { - writeln!(f, " - {}", exc)?; + if !self.excludes.is_empty() { + writeln!(f, "{}:", t("excludes"))?; + for exc in &self.excludes { + writeln!(f, " - {}", exc)?; + } } writeln!(f, "")?; writeln!(f, "--------------------------------")?; diff --git a/src/i18n/messages/en.rs b/src/i18n/messages/en.rs index d798aa2..d202cd9 100644 --- a/src/i18n/messages/en.rs +++ b/src/i18n/messages/en.rs @@ -1,41 +1,45 @@ use phf::phf_map; pub static MESSAGES: phf::Map<&'static str, &'static str> = phf_map! { - // Configuration file I/O - "config_loaded" => "Configuration loaded successfully", - "failed_print_config" => "Failed to print configuration: {}", - "failed_parse_config" => "Failed to parse config: {}", - "global_config_not_initialized" => "Global configuration not initialized", + // Git + "err_git_not_found" => "Git is not installed or not in system PATH. Please install Git first.\nVisit https://git-scm.com/downloads to download.", + "err_git_not_working" => "Git is installed but not working properly. Please check your Git installation.\nTo reinstall, visit https://git-scm.com/downloads .", - // Configuration file details - "config_details" => "---- Configuration Details -----", - "language" => "Language", - "authors" => "Authors", - "date_range" => "Date Range", - "repos" => "Repositories", - "includes" => "Includes", - "excludes" => "Excludes", - "format" => "Format", + // Configuration file I/O + "config_loaded" => "Configuration loaded successfully", + "failed_print_config" => "Failed to print configuration: {}", + "failed_parse_config" => "Failed to parse config: {}", + "global_config_not_initialized" => "Global configuration not initialized", - // Keypress - "wait_for_key" => "Press any key to continue...", - "press_to_exit" => "Press any key to exit...", + // Configuration file details + "config_details" => "---- Configuration Details -----", + "language" => "Language", + "authors" => "Authors", + "date_range" => "Date Range", + "repos" => "Repositories", + "includes" => "Includes", + "excludes" => "Excludes", + "format" => "Format", - // Commit categories - "commit_category_features" => "Features", - "commit_category_bug_fixes" => "Bug Fixes", - "commit_category_docs" => "Documentation", - "commit_category_style" => "Optimized Style", - "commit_category_refactor" => "Refactored", - "commit_category_test" => "Test Cases", - "commit_category_chores" => "Chores", + // Keypress + "wait_for_key" => "Press any key to continue...", + "press_to_exit" => "Press any key to exit...", - // Git repository error messages - "err_repo_not_found" => "Repo path does not exist or is not a directory: {}", - "err_git_log_failed" => "git log failed in directory: {}\nstderr: {}", + // Commit categories + "commit_category_features" => "Features", + "commit_category_bug_fixes" => "Bug Fixes", + "commit_category_docs" => "Documentation", + "commit_category_style" => "Optimized Style", + "commit_category_refactor" => "Refactored", + "commit_category_test" => "Test Cases", + "commit_category_chores" => "Chores", - // Save report - "no_report_generated" => "No report generated", - "err_save_report_failed" => "Failed to save report: {}", - "report_saved" => "Report saved to {}", + // Git repository error messages + "err_repo_not_found" => "Repo path does not exist or is not a directory: {}", + "err_git_log_failed" => "git log failed in directory: {}\nstderr: {}", + + // Save report + "no_report_generated" => "No report generated", + "err_save_report_failed" => "Failed to save report: {}", + "report_saved" => "Report saved to {}", }; diff --git a/src/i18n/messages/zh.rs b/src/i18n/messages/zh.rs index d2060cd..644407c 100644 --- a/src/i18n/messages/zh.rs +++ b/src/i18n/messages/zh.rs @@ -1,41 +1,45 @@ use phf::phf_map; pub static MESSAGES: phf::Map<&'static str, &'static str> = phf_map! { - // Configuration file I/O - "config_loaded" => "配置加载成功", - "failed_print_config" => "打印配置失败:{}", - "failed_parse_config" => "解析配置失败:{}", - "global_config_not_initialized" => "全局配置未初始化", + // Git + "err_git_not_found" => "Git 未安装或未添加到系统 PATH 中,请先安装 Git。\n访问 https://git-scm.com/downloads 下载安装。", + "err_git_not_working" => "Git 已安装但无法正常工作,请检查 Git 安装状态。\n如需重新安装,请访问 https://git-scm.com/downloads 。", - // Configuration file details - "config_details" => "----------- 配置详情 -----------", - "language" => "语言", - "authors" => "作者", - "date_range" => "日期范围", - "repos" => "仓库", - "includes" => "包含", - "excludes" => "排除", - "format" => "格式", + // Configuration file I/O + "config_loaded" => "配置加载成功", + "failed_print_config" => "打印配置失败:{}", + "failed_parse_config" => "解析配置失败:{}", + "global_config_not_initialized" => "全局配置未初始化", - // Keypress - "wait_for_key" => "按任意键继续...", - "press_to_exit" => "按任意键退出...", + // Configuration file details + "config_details" => "----------- 配置详情 -----------", + "language" => "语言", + "authors" => "作者", + "date_range" => "日期范围", + "repos" => "仓库", + "includes" => "包含", + "excludes" => "排除", + "format" => "格式", - // Commit categories - "commit_category_features" => "功能开发", - "commit_category_bug_fixes" => "BUG修复", - "commit_category_docs" => "完善文档", - "commit_category_style" => "优化样式", - "commit_category_refactor" => "代码重构", - "commit_category_test" => "测试用例", - "commit_category_chores" => "其他优化", + // Keypress + "wait_for_key" => "按任意键继续...", + "press_to_exit" => "按任意键退出...", - // Git repository error messages - "err_repo_not_found" => "仓库路径不存在或不是目录:{}", - "err_git_log_failed" => "git log 执行失败,目录:{}\n错误信息:{}", + // Commit categories + "commit_category_features" => "功能开发", + "commit_category_bug_fixes" => "BUG修复", + "commit_category_docs" => "完善文档", + "commit_category_style" => "优化样式", + "commit_category_refactor" => "代码重构", + "commit_category_test" => "测试用例", + "commit_category_chores" => "其他优化", - // Save report - "no_report_generated" => "未生成报告", - "err_save_report_failed" => "保存报告失败:{}", - "report_saved" => "报告已保存到:{}", + // Git repository error messages + "err_repo_not_found" => "仓库路径不存在或不是目录:{}", + "err_git_log_failed" => "git log 执行失败,目录:{}\n错误信息:{}", + + // Save report + "no_report_generated" => "未生成报告", + "err_save_report_failed" => "保存报告失败:{}", + "report_saved" => "报告已保存到:{}", }; diff --git a/src/main.rs b/src/main.rs index eeac073..6c4e5b4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,11 +2,15 @@ mod config; mod i18n; mod utils; -use std::{collections::HashMap, path::PathBuf}; +use std::{ + collections::{HashMap, HashSet}, + path::PathBuf, +}; use config::{init_config, print_config}; use i18n::t; use utils::{ + check_git::check_git_available, filter_logs::filter_logs, format_log::{format_log, LogInfo}, get_repo_logs::get_repo_logs, @@ -22,10 +26,16 @@ fn run(root_path: PathBuf) -> Result<(), Box> { // such as in the i18n module. let config = init_config(&root_path)?; + // Check if git is available + check_git_available()?; + print_config(); let mut result: HashMap>> = HashMap::new(); + // Deduplicate logs by repoName and typeName + let mut dedup_map: HashMap>> = HashMap::new(); + for repo_dir in &config.repos { // Get repo name let repo_name = @@ -60,12 +70,23 @@ fn run(root_path: PathBuf) -> Result<(), Box> { for log in filtered_logs { let log_info = format_log(&log); let type_name = log_info.type_name.clone(); - result + + // get HashSet of type_name in repo_name, if not exist, create a new one + let type_set = dedup_map .entry(repo_name.clone()) .or_default() - .entry(type_name) - .or_default() - .push(log_info); + .entry(type_name.clone()) + .or_default(); + + // if message not in type_set, push to result + if type_set.insert(log_info.message.clone()) { + result + .entry(repo_name.clone()) + .or_default() + .entry(type_name) + .or_default() + .push(log_info); + } } save_report_markdown(&result, &root_path)?; @@ -105,6 +126,7 @@ fn main() { // explicitly with `eprintln!` ensures that the Display output is used. if let Err(e) = run(root_path) { eprintln!("{}", e); // Use Display format to output the error + exit_on_keypress(Some(t("press_to_exit"))); std::process::exit(1); } } diff --git a/src/utils/check_git.rs b/src/utils/check_git.rs new file mode 100644 index 0000000..487e1da --- /dev/null +++ b/src/utils/check_git.rs @@ -0,0 +1,12 @@ +use crate::i18n::t; +use std::process::Command; + +pub fn check_git_available() -> anyhow::Result<()> { + let output = Command::new("git").arg("--version").output(); + + match output { + Ok(output) if output.status.success() => Ok(()), + Ok(_) => anyhow::bail!("\n{}\n", t("err_git_not_working")), + Err(_) => anyhow::bail!("\n{}\n", t("err_git_not_found")), + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 76066a8..87765bb 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,3 +1,4 @@ +pub mod check_git; pub mod filter_logs; pub mod format_commit; pub mod format_log;