Samoyed
A modern, fast, and secure Git hooks manager written in Rust. Samoyed is inspired by Husky with improved performance, better error handling, and enhanced security features.
You don’t have to fuss with that pesky package.json file in your projects anymore! 🤌

Test Coverage
Features
- 🚀 Fast: Built with Rust for optimal performance
- 🔒 Secure: Comprehensive path validation and security checks
- 🛡️ Robust: Detailed error handling with actionable suggestions
- 🧪 Well-tested: Comprehensive test coverage with extensive integration tests
- 🌍 Cross-platform: Supports Linux, macOS, and Windows
- 📦 Minimal dependencies: Small set of essential Rust dependencies
Installation
Samoyed is published on crates.io:
Migration Guide
Upgrading from Dual-Binary Versions (v0.1.x)
⚠️ Important: Samoyed has migrated from a dual-binary architecture (samoyed + samoyed-hook) to a unified single binary (samoyed with hook subcommand).
What Changed
- Before:
exec samoyed-hook "pre-commit" "$@" - After:
exec samoyed hook "pre-commit" "$@"
Migration Steps
-
Update Samoyed: Install the latest version
-
Re-initialize Your Hooks: This updates your
.samoyed/_/*files to use the new unified binary# Option 1: Standard re-initialization (recommended) # Safe to run multiple times, updates hooks only if needed # Option 2: Force re-initialization (explicit migration) # Explicitly overwrites all hook files to ensure complete migrationWhen to use each option:
- Use
samoyed initfor normal migration (safe, non-destructive) - Use
samoyed init -f _if you want to force complete re-creation of all hook files
- Use
-
Verify Migration: Check that your hook files now reference
samoyed hook# Should show: exec samoyed hook "$(basename "$0")" "$@"
Deprecation Timeline
- Now:
samoyed-hookbinary still works but shows deprecation warnings - September 1, 2025:
samoyed-hookbinary will be removed entirely - Recommended: Migrate immediately to avoid future issues
Troubleshooting
If you encounter issues:
- Ensure you have the latest version:
samoyed --version - Re-run
samoyed initto refresh all hook files - Check that
samoyed hook --helpworks
Quick Start
Initialize Git hooks in your repository:
This will:
- Configure Git to use
.samoyed/_as the hooks directory - Create the hooks directory structure
- Install hook files that delegate to the
samoyedbinary
Usage
Basic Commands
# Initialize hooks (one-time setup)
# Install hooks with custom directory
Architecture
Samoyed uses a three-layer architecture that provides both flexibility and performance:
Binary Component
samoyed: Unified binary for both CLI interface and hook execution
Execution Flow
sequenceDiagram
participant Git as Git
participant Hook as .samoyed/_/pre-commit
participant Runner as samoyed hook
participant Config as samoyed.toml
participant Script as .samoyed/scripts/pre-commit
participant Shell as Shell
Git->>Hook: git commit triggers
Hook->>Runner: exec samoyed hook "pre-commit" "$@"
Runner->>Config: 1. Check for [hooks] pre-commit
alt Command found in samoyed.toml
Config-->>Runner: "cargo fmt --check && cargo clippy"
Runner->>Shell: Execute command via shell
Shell-->>Runner: Exit code
else No command in samoyed.toml
Runner->>Script: 2. Look for .samoyed/scripts/pre-commit
alt Script exists
Runner->>Script: Execute script file
Script-->>Runner: Exit code
else No script found
Runner-->>Git: Exit silently (0)
end
end
Runner-->>Git: Propagate exit code
Directory Structure
When you run samoyed init, three key components are created:
1. samoyed.toml - Primary Configuration
Raison d'être: The primary configuration mechanism where you define commands for each hook.
[]
= "cargo fmt --check && cargo clippy -- -D warnings"
= "cargo test --release"
2. .samoyed/_/ - Git Hook Delegation Layer
Raison d'être: Git integration. These files tell Git "when you want to run a hook, call samoyed hook instead."
All files contain identical delegation code:
#!/usr/bin/env sh
Git's core.hooksPath=.samoyed/_ points here, so git commit → .samoyed/_/pre-commit → samoyed hook.
3. .samoyed/scripts/ - Fallback & Examples
Raison d'être: Fallback mechanism for complex scenarios and examples for users who prefer script files.
Two-Tier Lookup System
The hook runner implements a sophisticated two-tier lookup:
- Primary: Check
samoyed.tomlfor a command string - Fallback: Look for executable script in
.samoyed/scripts/
This provides maximum flexibility:
- Simple cases: Use TOML configuration for straightforward commands
- Complex cases: Use full script files for multi-line logic or complex workflows
Environment Variables
SAMOYED=0- Skip all hook execution (useful for CI/deployment)SAMOYED=1- Normal execution mode (default)SAMOYED=2- Enable debug mode with detailed script tracing
Performance Design
This architecture ensures minimal overhead:
- Git only spawns one process:
samoyed hook - No file system scanning during execution
- Direct command execution via shell when possible
- Graceful fallback with silent exit when no hooks are defined
Development
Prerequisites
- Rust 1.85+ (Rust 2024 edition)
- Git
Building
# Build debug version
# Build release version
# Run tests
# Run benchmarks
Testing
The project uses comprehensive testing with dependency injection:
# Run all tests
# Run specific test categories
# Run platform-specific tests
Code Coverage
Generate coverage reports:
Tarpaulin is configured in .tarpaulin.toml to store reports in <target/tarpaulin/coverage/>.
Contributing
- Let's discuss
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Acknowledgments
- Inspired by Husky
- Built with 🤟 🫡 in Rust