Skip to content

Commit b46a6ab

Browse files
meta(dev): Add commit message guidelines and pre-commit validation
Co-authored-by: daniel.szoke <daniel.szoke@sentry.io>
1 parent 624d942 commit b46a6ab

File tree

6 files changed

+205
-1
lines changed

6 files changed

+205
-1
lines changed

.cursor/rules/sentry-cli-project.mdc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ This is **sentry-cli**, a command-line utility for working with Sentry. It's pri
2121

2222
- `src/` - Core Rust source code with command modules and utilities
2323
- `js/` - JavaScript wrapper and npm package code
24-
- `scripts/` - Build and utility scripts
24+
- `scripts/` - Build, installation, and deployment scripts
25+
- `tools/` - Development tooling and utilities (commit hooks, validation scripts)
2526
- `tests/integration/` - Integration tests using `.trycmd` format
2627
- `npm-binary-distributions/` - Platform-specific binary packages
2728
- `.github/workflows/` - CI/CD workflows (follows reusable workflow pattern)

.gitmessage

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
# Commit Message Format
3+
# <type>(<scope>): <subject>
4+
#
5+
# <body - explain what and why, not how>
6+
#
7+
# <footer - link issues: "Fixes #123", breaking: "BREAKING CHANGE: desc">
8+
#
9+
# Example: feat(api): Add user authentication endpoint
10+
# Types: feat, fix, docs, style, ref, test, ci, build, perf, meta, license, revert
11+
# Subject: Capitalize, imperative mood, no period, max 70 chars
12+
# Body: Separate from subject with blank line
13+
# Details: https://develop.sentry.dev/engineering-practices/commit-messages/

.pre-commit-config.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# pre-commit configuration for sentry-cli
2+
# See https://pre-commit.com for more information
3+
default_stages: [pre-commit]
4+
fail_fast: false
5+
6+
repos:
7+
# Commit message validation
8+
- repo: local
9+
hooks:
10+
- id: validate-commit-msg
11+
name: Validate commit message
12+
entry: ./tools/validate-commit-msg.py
13+
language: system
14+
stages: [commit-msg]
15+
always_run: true
16+
17+
# Standard hooks for code quality
18+
- repo: https://github.com/pre-commit/pre-commit-hooks
19+
rev: v5.0.0
20+
hooks:
21+
- id: trailing-whitespace
22+
- id: end-of-file-fixer
23+
- id: check-yaml
24+
- id: check-added-large-files
25+
- id: check-merge-conflict
26+
- id: mixed-line-ending
27+
args: [--fix=lf]

CONTRIBUTING.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
# Pre-commit hooks
2+
3+
Pre-commit hooks provide some local validation of your commits. Please set up the hooks with the following script:
4+
5+
```bash
6+
./tools/setup-commit-hooks.sh
7+
```
8+
19
# Adding new commands
210
For new commands, it is recommended to use clap's [Derive API](https://docs.rs/clap/latest/clap/_derive/index.html).
311
In contrast to the [Builder API](https://docs.rs/clap/latest/clap/_tutorial/index.html), the Derive API makes it:

tools/setup-commit-hooks.sh

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#!/bin/bash
2+
# Script to set up commit message validation hooks for sentry-cli
3+
4+
set -e
5+
6+
echo "Setting up commit hooks for sentry-cli..."
7+
8+
# Check if pre-commit is installed
9+
if ! command -v pre-commit &> /dev/null; then
10+
echo "❌ pre-commit is not installed."
11+
echo ""
12+
echo "Please install pre-commit using one of these methods:"
13+
echo " - pip install pre-commit"
14+
echo " - brew install pre-commit (macOS)"
15+
echo " - pipx install pre-commit"
16+
echo ""
17+
echo "Then run this script again."
18+
exit 1
19+
fi
20+
21+
# Install the git hooks
22+
echo "Installing pre-commit hooks..."
23+
pre-commit install
24+
pre-commit install --hook-type commit-msg
25+
26+
# Always set up the commit message template
27+
echo "Setting up commit message template..."
28+
git config commit.template .gitmessage
29+
echo "✅ Commit message template configured"
30+
31+
echo ""
32+
echo "✅ Setup complete!"
33+
echo ""
34+
echo "Your commits will now be validated against Sentry's commit message format."
35+
echo "Format: <type>(<scope>): <subject>"
36+
echo "Example: feat(cli): Add new authentication feature"
37+
echo ""
38+
echo "For more details: https://develop.sentry.dev/engineering-practices/commit-messages/"

tools/validate-commit-msg.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Validates commit messages according to Sentry's commit message guidelines.
4+
https://develop.sentry.dev/engineering-practices/commit-messages/
5+
"""
6+
7+
import re
8+
import sys
9+
10+
11+
def validate_commit_message(message):
12+
"""
13+
Validates a commit message according to Sentry's format:
14+
<type>(<scope>): <subject>
15+
16+
Returns tuple of (is_valid, error_message)
17+
"""
18+
# Valid commit types
19+
valid_types = [
20+
"build",
21+
"ci",
22+
"docs",
23+
"feat",
24+
"fix",
25+
"perf",
26+
"ref",
27+
"style",
28+
"test",
29+
"meta",
30+
"license",
31+
"revert",
32+
]
33+
34+
# Skip validation for merge commits
35+
if message.startswith("Merge"):
36+
return True, None
37+
38+
# Parse the first line (header)
39+
lines = message.strip().split("\n")
40+
41+
header = lines[0].strip()
42+
43+
# Pattern for the header: type(scope): subject or type: subject
44+
pattern = r"^(?P<type>[a-z]+)(?:\((?P<scope>[^)]+)\))?: (?P<subject>.+)$"
45+
match = re.match(pattern, header)
46+
47+
if not match:
48+
return (
49+
False,
50+
"Invalid format. Must be: <type>(<scope>): <subject> or <type>: <subject>",
51+
)
52+
53+
commit_type = match.group("type")
54+
scope = match.group("scope")
55+
subject = match.group("subject")
56+
57+
# Validate type
58+
if commit_type not in valid_types:
59+
return (
60+
False,
61+
f"Invalid type '{commit_type}'. Must be one of: {', '.join(valid_types)}",
62+
)
63+
64+
# Validate scope (if present)
65+
if scope and not scope.islower():
66+
return False, f"Scope '{scope}' must be lowercase"
67+
68+
# Validate subject
69+
if not subject:
70+
return False, "Subject cannot be empty"
71+
72+
# Check first letter is capitalized
73+
if subject[0].islower():
74+
return False, "Subject must start with a capital letter"
75+
76+
# Check for trailing period
77+
if subject.endswith("."):
78+
return False, "Subject must not end with a period"
79+
80+
# Check header length (max 70 characters)
81+
if len(header) > 70:
82+
return False, f"Header is {len(header)} characters, must be 70 or less"
83+
84+
return True, None
85+
86+
87+
def main():
88+
"""Main entry point for the commit message validator."""
89+
# Read commit message from file (provided by git)
90+
if len(sys.argv) < 2:
91+
print("Error: No commit message file provided")
92+
sys.exit(1)
93+
94+
commit_msg_file = sys.argv[1]
95+
96+
try:
97+
with open(commit_msg_file, "r", encoding="utf-8") as f:
98+
commit_message = f.read()
99+
except Exception as e:
100+
print(f"Error reading commit message file: {e}")
101+
sys.exit(1)
102+
103+
# Validate the commit message
104+
is_valid, error_msg = validate_commit_message(commit_message)
105+
106+
if not is_valid:
107+
print(f"❌ Commit message validation failed:\n{error_msg}")
108+
print("\nCommit message format: <type>(<scope>): <subject>")
109+
print("Example: feat(api): Add new authentication endpoint")
110+
print(
111+
"\nSee https://develop.sentry.dev/engineering-practices/commit-messages/ for details"
112+
)
113+
sys.exit(1)
114+
115+
116+
if __name__ == "__main__":
117+
main()

0 commit comments

Comments
 (0)