Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6559267
feat: lint messages from hash-range
JustBipin May 19, 2026
b878933
test: update unittests and integration tests
JustBipin May 19, 2026
8a2ad0a
fix(cli): addd strict constrain between cli arguments
JustBipin May 19, 2026
7f28c76
refactor(git): use null byte as delimiter for commit messages
JustBipin May 26, 2026
d9dcb3d
style: format using cargo fmt
JustBipin May 31, 2026
401e862
refactor: update git_repo and git_helpers
JustBipin May 31, 2026
9be6118
chore: better comment
JustBipin May 31, 2026
f90cf53
refactor: make to-hash string
JustBipin May 31, 2026
e7e582c
perf: complile regex patterns globally and only once
JustBipin Jun 1, 2026
69ff952
refactor: improve linter feedback and reorganize test suite
JustBipin Jun 1, 2026
16c5e49
docs: update readme for new options
JustBipin Jun 1, 2026
85edbf9
perf: use RegexSet to match IGNORED_REGEXES in single search
JustBipin Jun 1, 2026
4434feb
refactor: split project into library and binary crates
JustBipin Jun 1, 2026
755778f
docs: add descriptive rustdoc for git_commit_messsages_from_hash_range
JustBipin Jun 1, 2026
5564bd3
refactor: use git log <from>^..<to> to
JustBipin Jun 1, 2026
bb1a5c2
docs: move usage section down
JustBipin Jun 6, 2026
f6b7648
remove log redirection to stdout and stderr
JustBipin Jun 9, 2026
b547a4d
test: (#16) test all capitalization varients of 'initial commit' message
JustBipin Jun 9, 2026
880478c
refactor: remove --no-merges flag from git log
JustBipin Jun 9, 2026
42d20a4
style: replace string literals with message constants
JustBipin Jun 9, 2026
72c8104
docs: improve docstring style
JustBipin Jun 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 142 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ anyhow = "1.0"
[dev-dependencies]
assert_cmd = "2"
predicates = "3"
serial_test = "3"
Comment thread
JustBipin marked this conversation as resolved.
tempfile = "3"

# The profile that 'dist' will build with
Expand Down
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,37 @@ A Conventional Commitlint binary tool (x for executable).
## Development

Run cocox

```shell
cargo run -- "my commit message"

cargo run -- --file "/foo/bar"


# using commit hash
cargo run -- --hash xxxx-xxxx

# using range of hashes:
cargo run -- --from-hash xxxx-xxxx --to-hash xxxx-xxxx

# --to-hash points to HEAD by default
cargo run -- --from-hash xxxx-xxxx

```

## Usage:
```shell
A Conventional Commitlint binary tool

Usage: cocox [OPTIONS] <MESSAGE|--file <FILE>|--hash <HASH>|--from-hash <FROM_HASH>>

Arguments:
[MESSAGE]

Options:
--file <FILE>
--hash <HASH>
--from-hash <FROM_HASH>
--to-hash <TO_HASH> [default: HEAD]
-h, --help Print help
-V, --version Print version
```
12 changes: 11 additions & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use clap::{ArgGroup, Parser};
#[command(version)]
#[command(group(
ArgGroup::new("input")
.args(["message", "file", "hash"])
.args(["message", "file", "hash", "from_hash"])
.required(true)
.multiple(false)))]
pub struct Cli {
Expand All @@ -18,4 +18,14 @@ pub struct Cli {

#[arg(long)]
pub hash: Option<String>,

#[arg(long)]
pub from_hash: Option<String>,

#[arg(
long, requires = "from_hash",
conflicts_with_all = ["message", "file", "hash"],
default_value = "HEAD"
)]
pub to_hash: String,
}
58 changes: 40 additions & 18 deletions src/command.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use crate::cli::Cli;
use crate::git_helpers::get_commit_message_from_hash;
use crate::linter::lint_commit_message;
use crate::git_helpers::{get_commit_message_from_hash, get_commit_messages_from_hash_range};
use crate::linter::{LintOutcome, lint_commit_message};
use crate::messages::{VALIDATION_FAILED, VALIDATION_SUCCESSFUL};
use crate::utils::{is_empty, is_ignored};
use anyhow::{Context, Result};

fn read_file(file: &String) -> Result<String> {
Expand All @@ -11,26 +10,43 @@ fn read_file(file: &String) -> Result<String> {
}

fn handle_commit_message(msg: &str) {
if is_empty(msg) {
eprintln!(
"{}: Aborting commit due to empty commit message",
VALIDATION_FAILED
);
std::process::exit(1);
match lint_commit_message(msg) {
LintOutcome::Empty => {
std::process::exit(1);
}
LintOutcome::Ignored => (),
LintOutcome::Valid => {
println!("{}\n", VALIDATION_SUCCESSFUL);
}
LintOutcome::Invalid => {
eprintln!("{}\n", VALIDATION_FAILED);
std::process::exit(1);
}
}
}

if is_ignored(msg) {
return;
fn handle_multiple_commit_messages(messages: &[String]) {
let mut has_failure = false;

for msg in messages {
match lint_commit_message(msg) {
LintOutcome::Empty => {
std::process::exit(1);
}
LintOutcome::Ignored => (),
LintOutcome::Valid => (),
LintOutcome::Invalid => {
has_failure = true;
}
}
}

let success = lint_commit_message(msg);
if success {
println!("{}", VALIDATION_SUCCESSFUL);
return;
if has_failure {
eprintln!("{}", VALIDATION_FAILED);
std::process::exit(1);
}

eprintln!("{}", VALIDATION_FAILED);
std::process::exit(1);
println!("{}", VALIDATION_SUCCESSFUL);
Comment thread
JustBipin marked this conversation as resolved.
}
Comment thread
JustBipin marked this conversation as resolved.

pub fn run(args: Cli) -> Result<()> {
Expand All @@ -42,10 +58,16 @@ pub fn run(args: Cli) -> Result<()> {
let msg = read_file(file)?;
handle_commit_message(&msg);
} else if let Some(hash) = &args.hash {
// commit hash
let msg = get_commit_message_from_hash(hash)?;
handle_commit_message(&msg);
} else if let Some(from_hash) = &args.from_hash {
// commit hash range
let to_hash = &args.to_hash;
let messages = get_commit_messages_from_hash_range(from_hash, to_hash)?;
handle_multiple_commit_messages(&messages);
} else {
unreachable!("invalid option is handle by clap");
unreachable!("invalid option is handled by clap");
}

Ok(())
Expand Down
Loading