Skip to content
41 changes: 40 additions & 1 deletion Cargo.lock

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

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "gitkit"
version = "0.5.0"
version = "0.6.0"
edition = "2021"
description = "Standalone CLI for configuring git repos — hooks, .gitignore, and .gitattributes"
license = "MIT"
Expand All @@ -21,6 +21,7 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1"
tar = "0.4"
toml = "0.8"
regex = "1"
ureq = "2"

[dev-dependencies]
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Set up a git repo the way you actually work — one guided flow for hooks, `.git

### Demo

![Demo](assets/demo.gif)
![Demo](demo/dist/demo.gif)

---

Expand Down
Binary file removed assets/demo.gif
Binary file not shown.
14 changes: 14 additions & 0 deletions docs/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,20 @@ Running `gitkit` with no command starts the interactive wizard.
`git commit --no-verify` and `git push --no-verify` bypass the lock — see [Lock](lock.md) for why
that is accepted rather than defended against.

## Uninstall

| Command | Description |
|---|---|
| `gitkit uninstall` | Remove gitkit hooks from every repository it has touched |
| `gitkit uninstall --data` | Also remove local state under `~/.gitkit` (builds, registry) |
| `gitkit uninstall --yes` | Skip the confirmation prompt |
| `gitkit uninstall --dry-run` | Print what would be done without changing anything |

By default, `gitkit uninstall` lists every repository in the registry, shows what hooks are
installed, and asks for confirmation before removing anything. It restores any hand-written hook
that gitkit had absorbed when it first installed its dispatcher. The gitkit binary itself is never
removed — see [Installation](installation.md#uninstall) for how to remove it.

## Ignore

| Command | Description |
Expand Down
95 changes: 94 additions & 1 deletion docs/hooks.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Hooks
description: Built-in hooks (conventional commits, no-body messages, AI trailer rejection, secret detection, branch naming, invisible Unicode detection) and custom shell commands.
description: Built-in hooks (conventional commits, no-body messages, AI trailer rejection, secret detection, branch naming, invisible Unicode detection, user-defined message rules) and custom shell commands.
order: 4
---

Expand All @@ -15,6 +15,7 @@ Built-ins are embedded in the binary — no network required.
| `conventional-commits` | `commit-msg` | Validates Conventional Commits format |
| `no-body` | `commit-msg` | Rejects a commit message that has a body |
| `no-trailers` | `commit-msg` | Rejects commit messages carrying AI attribution trailers |
| `message-rules` | `commit-msg` | Validates commit messages against user-defined regex rules in `.gitmessage-rules.json` |
| `no-secrets` | `pre-commit` | Detects common secret patterns in staged changes |
| `branch-naming` | `pre-commit` | Validates branch name matches convention |
| `no-invisibles` | `pre-commit` | Rejects added lines carrying invisible Unicode characters |
Expand Down Expand Up @@ -65,6 +66,98 @@ with" line. Genuine human `Co-Authored-By:` trailers are left untouched — a
rule in a prompt is advisory, this hook is not. The commit is refused with
the offending line and its line number; it never rewrites your message.

### `message-rules`

Validates the commit message against **rules you define** in a committed
file at `.gitmessage-rules.json` in the repository root. Each rule is
a regex pattern with a direction (`must_match` or `must_not_match`), a
scope (`subject` or `whole_message`), and a message shown when the rule
fires. Because the rules file is tracked by git, it travels with the
repository and applies to everyone who clones it.

#### Configuring rules

Create `.gitmessage-rules.json` in the repository root with a JSON
array of rules:

```json
[
{
"name": "jira-prefix",
"pattern": "^[A-Z]+-\\d+",
"direction": "must_match",
"scope": "subject",
"message": "Subject must start with a JIRA ticket prefix (e.g. PROJ-123)"
}
]
```

A negative rule — forbidding something — uses `must_not_match`:

```json
[
{
"name": "no-trailer-in-subject",
"pattern": "Co-Authored-By:",
"direction": "must_not_match",
"scope": "subject",
"message": "Subject must not contain trailer lines; move Co-Authored-By to the body"
}
]
```

Commit this file to the repository so every contributor shares the same
rules.

#### Directions

- **`must_match`** — the pattern must match the scoped text. A JIRA prefix
rule is a positive match: the subject must contain `^[A-Z]+-\d+`.
- **`must_not_match`** — the pattern must not match. A forbidden-trailer
rule is a negative match: the subject must not contain `Co-Authored-By:`.
This catches what `no-trailers` misses — a trailer smuggled into the
subject line after a semicolon.

#### Scopes

- **`subject`** — only the first line of the commit message is checked.
- **`whole_message`** — the entire commit message (subject, body, trailers)
is checked.

#### Installing

```bash
gitkit hooks add message-rules
```

If no rules are configured, the command refuses with a clear message rather
than installing a hook that always passes. Patterns are validated at install
time — a regex that does not compile is rejected immediately, naming the
rule and the compile error, not deferred to commit time.

#### Regex flavour

Patterns use **Rust's `regex` crate** syntax, which is ERE-like: character
classes, alternation, grouping, anchors, and quantifiers all work. **No
lookahead, lookbehind, or backreferences** — if a pattern uses these PCRE
features, it will be rejected at configuration time with a compile error.
When in doubt, test the pattern with `rg '<pattern>'` (ripgrep uses the
same engine).

The installed hook delegates to `gitkit` itself for regex evaluation, so
the same engine that validated the pattern at install time evaluates it at
commit time — no lossy conversion to POSIX ERE.

#### Multiple rules

All rules run on every commit. The hook reports **every** failing rule, not
just the first, then exits non-zero. Rules compose — a JIRA prefix rule and
a subject-length rule are separate rules that fire independently.

Revert (`Revert "..."`), merge (`Merge branch '...'`), `fixup!` and
`squash!` commit messages are auto-generated and are always accepted
regardless of rules.

### `no-invisibles`

Rejects a commit that **adds** a line containing an invisible Unicode
Expand Down
20 changes: 19 additions & 1 deletion docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,33 @@ the update to maintain consistency.

## Uninstall

First, remove gitkit's hooks from every repository it has touched:

```bash
gitkit uninstall
```

This lists every repository in the registry, shows what hooks are installed, and asks for
confirmation before removing anything. It restores any hand-written hook that gitkit had absorbed
when it first installed its dispatcher. Add `--data` to also remove local state under `~/.gitkit`
(builds, registry).

Then remove the binary itself:

**Linux / macOS:**

```bash
rm -f ~/.local/bin/gitkit
rm -rf ~/.gitkit/ # saved builds (optional)
```

**Windows (PowerShell):**

```powershell
Remove-Item "$env:LOCALAPPDATA\gitkit\gitkit.exe" -Force
```

If gitkit was installed via `cargo install gitkit`, remove it with:

```bash
cargo uninstall gitkit
```
Loading
Loading