Skip to content

Commit 215b874

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 215b874

File tree

8 files changed

+290
-1
lines changed

8 files changed

+290
-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)

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,13 @@ yarn-error.log
1414

1515
.vscode/*
1616
!.vscode/settings.json
17+
18+
/Cargo.lock
19+
*.pyc
20+
__pycache__
21+
dist
22+
*egg-info*
23+
.tox
24+
25+
# pre-commit
26+
.pre-commit-cache/

.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: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,74 @@
1+
# Contributing to sentry-cli
2+
3+
## Commit Message Guidelines
4+
5+
We follow [Sentry's commit message guidelines](https://develop.sentry.dev/engineering-practices/commit-messages/). All commits must follow this format:
6+
7+
```
8+
<type>(<scope>): <subject>
9+
```
10+
11+
### Types
12+
13+
- `build`: Changes that affect the build system or external dependencies
14+
- `ci`: Changes to CI configuration files and scripts
15+
- `docs`: Documentation only changes
16+
- `feat`: A new feature
17+
- `fix`: A bug fix
18+
- `perf`: A code change that improves performance
19+
- `ref`: A code change that neither fixes a bug nor adds a feature (refactoring)
20+
- `style`: Changes that do not affect the meaning of the code (formatting, etc)
21+
- `test`: Adding missing tests or correcting existing tests
22+
- `meta`: Changes to the project metadata
23+
- `license`: Changes to licensing
24+
- `revert`: Revert a previous commit
25+
26+
### Examples
27+
28+
- `feat(api): Add new authentication endpoint`
29+
- `fix(cli): Resolve crash when uploading large files`
30+
- `docs: Update installation instructions`
31+
32+
### Setting Up Git Hooks
33+
34+
To ensure your commits follow our guidelines, we use [pre-commit](https://pre-commit.com/). To set it up:
35+
36+
#### Option 1: Quick Setup (Recommended)
37+
38+
```bash
39+
./tools/setup-commit-hooks.sh
40+
```
41+
42+
#### Option 2: Manual Setup
43+
44+
1. Install pre-commit:
45+
46+
```bash
47+
pip install pre-commit
48+
```
49+
50+
2. Install the git hooks:
51+
52+
```bash
53+
pre-commit install
54+
```
55+
56+
3. Set up the commit message template:
57+
```bash
58+
git config commit.template .gitmessage
59+
```
60+
61+
The pre-commit hooks will automatically validate your commit messages. If you need to bypass the hooks temporarily (not recommended), you can use:
62+
63+
```bash
64+
git commit --no-verify
65+
```
66+
167
# Adding new commands
68+
269
For new commands, it is recommended to use clap's [Derive API](https://docs.rs/clap/latest/clap/_derive/index.html).
370
In contrast to the [Builder API](https://docs.rs/clap/latest/clap/_tutorial/index.html), the Derive API makes it:
71+
472
- Easier to read, write, and modify commands and arguments.
573
- Easier to keep argument declaration and reading in sync.
674
- Easier to reuse shared arguments.
@@ -12,6 +80,7 @@ An existing example of how to use the Derive API is the `send-metric` command.
1280
Integration tests are written using `trycmd` crate. Consult the docs in case you need to understand how it works https://docs.rs/trycmd/latest/trycmd/.
1381

1482
The main parts to remember are:
83+
1584
- `register_test` already understands that all tests will live under `tests/integration/_cases`
1685
- use mocks for API responses
1786
- use fixtures for uploading/processing predefined data
@@ -72,3 +141,7 @@ let _assemble = mock_endpoint(
72141
.with_response_file("debug_files/post-difs-assemble.json"),
73142
);
74143
```
144+
145+
```
146+
147+
```

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,3 +149,13 @@ Also, there is a Dockerfile that builds an Alpine-based Docker image with
149149
docker build -t sentry-cli .
150150
docker run --rm -v $(pwd):/work sentry-cli --help
151151
```
152+
153+
## Contributing
154+
155+
Please see [CONTRIBUTING.md](CONTRIBUTING.md) for our contributing guidelines.
156+
157+
We use [Sentry's commit message format](https://develop.sentry.dev/engineering-practices/commit-messages/). To set up commit message validation:
158+
159+
```bash
160+
./tools/setup-commit-hooks.sh
161+
```

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)