# Hooksmith
<pre align="center">
,"( .
////\ /
(//////--,,,,,_____ ,"
_;"""----/////_______;,, //
__________;"o,-------------......"""""`'-._/(
""'==._.__,;;;;""" ____,.-.==
"-.:______,...;---""/" " \(
'-._ `-._(" \\
'-._ '._
</pre>
<h1 align="center">Git Hook Management Made Simple</h1>
<p align="center">
<a href="https://crates.io/crates/hooksmith"><img src="https://img.shields.io/crates/v/hooksmith.svg" alt="Crates.io Version"></a>
<a href="https://docs.rs/hooksmith"><img src="https://img.shields.io/docsrs/hooksmith/latest" alt="Documentation"></a>
<a href="https://sonarcloud.io/summary/new_code?id=TomPlanche_hooksmith"><img src="https://sonarcloud.io/api/project_badges/measure?project=TomPlanche_hooksmith&metric=alert_status" alt="SonarCloud Status"></a>
<a href="https://sonarcloud.io/summary/new_code?id=TomPlanche_hooksmith"><img src="https://sonarcloud.io/api/project_badges/measure?project=TomPlanche_hooksmith&metric=sqale_rating" alt="SonarCloud SQALE Rating"></a>
<a href="https://sonarcloud.io/summary/new_code?id=TomPlanche_hooksmith"><img src="https://sonarcloud.io/api/project_badges/measure?project=TomPlanche_hooksmith&metric=security_rating" alt="SonarCloud Security Rating"></a>
<a href="https://github.com/TomPlanche/hooksmith/blob/main/LICENSE"><img src="https://img.shields.io/crates/l/hooksmith" alt="License"></a>
<a href="https://github.com/TomPlanche/hooksmith/actions/workflows/rust.yaml"><img src="https://github.com/TomPlanche/hooksmith/actions/workflows/rust.yaml/badge.svg" alt="Build Status"></a>
</p>
**Hooksmith** is a lightweight, easy-to-use tool that simplifies Git hook management. Define your hooks in a simple YAML file and let Hooksmith handle the rest.
## Table of Contents
- [Features](#features)
- [Why Hooksmith?](#why-hooksmith)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Usage](#usage)
- [Performance Monitoring](#performance-monitoring)
- [Path-based Blocks](#path-based-blocks)
- [Command Reference](#command-reference)
- [Contributing](#contributing)
- [License](#license)
## Features
- **Automatic Installation** - Set up hooks through your build scripts with `build.rs`
- **Local Testing** - Run hooks manually without triggering Git events
- **Performance Monitoring** - Track execution times for hooks and individual commands
- **Dry Run Mode** - Preview what would happen without making changes
- **Hook Validation** - Ensure your hooks comply with Git standards
- **Simple Configuration** - Define all your hooks in a clean YAML format
- **Step Progress** - See which hook and command is running at each step
- **Shell Completion** - Built-in Fish shell completions for improved productivity
- **Version Control** - Easily track hook changes with your repository
- **Error Handling** - Robust error handling with clear, actionable messages
## Why Hooksmith?
- **Minimal Dependencies** - Lightweight with only essential dependencies
- **Rust Powered** - Fast, reliable, and type-safe
- **Team Friendly** - Version control your hook configurations
- **Seamless Integration** - Works naturally with your Git workflow
- **Low Learning Curve** - Simple commands and clear documentation
## Installation
### Using Cargo
```bash
cargo install hooksmith
```
### As a Build Dependency
Add to your `Cargo.toml`:
```toml
[build-dependencies]
hooksmith = "1.16.0"
```
Create a `build.rs` file:
```rust
use std::path::Path;
fn main() {
let config_path = Path::new("hooksmith.yaml");
hooksmith::init(&config_path);
}
```
> **Note**: Hooksmith includes shell completions for Fish. After installation, they become available automatically.
### Dependencies
Hooksmith is built with minimal but powerful dependencies:
- `clap`: For robust command-line argument parsing
- `console` & `dialoguer`: For beautiful terminal interfaces
- `serde` & `serde_yaml`: For YAML configuration handling
- `thiserror`: For ergonomic error handling
## Quick Start
1. Create a `hooksmith.yaml` file in your project root:
```yaml
pre-commit:
commands:
- cargo fmt --all -- --check
- cargo clippy -- --deny warnings
pre-push:
commands:
- cargo test
```
2. Install the hooks:
```bash
hooksmith install
```
That's it! Your Git hooks are now ready to use.
## Usage
### Configuration File
Hooksmith uses a YAML configuration file (default: `hooksmith.yaml`) to define your hooks:
```yaml
# Format and lint code before committing
pre-commit:
commands:
- cargo fmt --all -- --check
- cargo clippy --workspace --all-features -- --deny warnings
# Run tests before pushing
pre-push:
commands:
- cargo test --all-features
- cargo build --verbose
# Validate commit messages
commit-msg:
commands:
# Use custom script to validate commit messages
- ./scripts/verify-commit-message.sh $1
```
#### Named Commands
You can optionally assign names to your commands for better readability and clearer output. This is especially useful for long or complex commands:
```yaml
pre-commit:
commands:
- cargo fmt --all -- --check
- clippy-linter: cargo clippy --workspace --release --all-targets --all-features -- --deny warnings -D warnings -W clippy::correctness -W clippy::suspicious -W clippy::complexity -W clippy::perf -W clippy::style -W clippy::pedantic
- typos
pre-push:
commands:
- cargo build -q
- test-suite: cargo test -q
```
Benefits of named commands:
- **Better readability**: Long commands show their purpose clearly
- **Improved monitoring**: Performance reports display meaningful names instead of truncated commands
- **Enhanced dry-run output**: See at a glance what each command does
When you use named commands, both the dry-run output and performance monitoring will display the command name followed by the actual command in parentheses.
### Common Commands
```bash
# Install all hooks defined in configuration
hooksmith install
# Run a specific hook manually
hooksmith run pre-commit
# Run a hook with performance monitoring
hooksmith run pre-commit --profile
# Uninstall all hooks or a specific one
hooksmith uninstall
hooksmith uninstall pre-commit
# Compare installed hooks with configuration
hooksmith compare
# Validate hook configuration against Git standards
hooksmith validate
```
Add `--dry-run` to any command to preview changes without applying them:
```bash
hooksmith install --dry-run
```
## Performance Monitoring
Hooksmith includes built-in performance monitoring to help you optimize your hook execution times. Use the `--profile` flag with the `run` command to see detailed timing information:
```bash
hooksmith run pre-commit --profile
```
This will show you:
- Individual command execution times (showing command names when available)
- Total hook execution time
- Overall execution time when running multiple hooks
### Example Output
When running hooks, Hooksmith shows step-by-step progress:
```
running `pre-commit`, 1/1 steps:
running `cargo fmt --all -- --check` 1/3
running `clippy-linter` 2/3
running `typos` 3/3
```
With `--profile`, a timing summary is appended:
```
running `pre-commit`, 1/1 steps:
running `cargo fmt --all -- --check` 1/3
running `clippy-linter` 2/3
running `typos` 3/3
Hook execution summary:
Hook 'pre-commit' (768ms)
cargo fmt --all -- --check: 146ms
clippy-linter: 394ms
typos: 226ms
Total: 768ms
```
### Use Cases
Performance monitoring is particularly useful for:
- **Identifying slow commands** in your hook chains
- **Optimizing CI/CD pipelines** by understanding bottlenecks
- **Debugging performance issues** in complex hook configurations
- **Tracking improvements** after optimizing your toolchain
## Path-based Blocks
Define commands that only run when files within specific paths have changed. This lets you scope expensive checks to the parts of the repository they affect.
### YAML structure
```yaml
pre-commit:
# Global commands: always run
commands:
- cargo fmt --all -- --check
# Path-based blocks: run only if matching files changed
paths:
src/:
commands:
- cargo clippy --workspace -- -D warnings
crates/api/:
working_directory: crates/api
commands:
- npm ci
- npm test
```
### How it works
- **Matching**: A block runs when any changed file path starts with its key (simple prefix match). Use paths relative to the repo root; prefer a trailing slash (e.g., `src/`).
- **Supported hooks**: Change detection is implemented for `pre-commit` and `pre-push`.
- `pre-commit`: uses `git diff --name-only --cached`.
- `pre-push`: diffs `@{u}..HEAD` when upstream exists, otherwise falls back to `HEAD~1..HEAD`.
- **Order**: All matching path-based blocks run first, then global `commands` run.
- **Working directory**: Inside a path block, `working_directory` (optional) sets the directory for those commands only. Global commands run in the current directory.
- **No matches**: If no paths match, only global commands run. Omit `commands` if you want nothing to run in that case.
- **Multiple matches**: If a file matches several prefixes, all matching blocks run. The order between blocks is not guaranteed; the order of commands within a block is preserved.
## Command Reference
| `install` | Install all hooks from configuration file |
| `run <hook>` | Run a specific hook manually |
| `run <hook> --profile` | Run a hook with performance timing information |
| `uninstall [hook]` | Uninstall all hooks or a specific one |
| `compare` | Compare installed hooks with configuration |
| `validate` | Validate hook configuration against Git standards |
### Global Options
| `--config-path <PATH>` | Specify a custom configuration file path |
| `--dry-run` | Preview changes without applying them |
| `--verbose` | Show detailed output during execution |
| `--help` | Display help information |
### Run Command Options
| `--interactive` or `-i` | Interactively select hooks to run |
| `--profile` or `-p` | Show performance timing for hook execution |
## Contributing
Contributions are welcome! Feel free to:
- Report bugs and suggest features
- Submit pull requests
- Improve documentation
- Share your use cases and feedback
## License
This project is dual-licensed under either:
- [Apache License 2.0](LICENSE-APACHE)
- [MIT License](LICENSE-MIT)
at your option.