Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(pre-commit): add hook to enforce branch name validation rules #293

Merged
merged 5 commits into from
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 25 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,32 @@ repos:
rev: v5.0.0
hooks:
- id: trailing-whitespace
stages: [pre-commit, manual]
- id: end-of-file-fixer
stages: [pre-commit, manual]
- repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook
rev: v9.20.0
hooks:
- id: commitlint
stages: [commit-msg]
additional_dependencies: ['@commitlint/config-conventional']
- id: commitlint
stages: [commit-msg]

# Local repository for a custom hook
- repo: local
hooks:
- id: check-branch-name
name: 'Check Branch Name'
entry: ./scripts/check-current-branch.sh
stages: [pre-commit, pre-push, manual]
language: system
files: /dev/null
always_run: true
verbose: true
- repo: local
hooks:
- id: post-checkout-check
name: 'Check Branch Name'
entry: ./scripts/check-current-branch.sh
stages: [post-checkout]
language: system
always_run: true
verbose: true
2 changes: 1 addition & 1 deletion QA.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

We are starting to introduce pre-commit and adding checks to it and enabling GitHub Actions.

Locally, start by installing pre-commit package and running `pre-commit install --install-hooks`. It will ensure checksare being run before each commit is made.
Locally, start by installing pre-commit package and running `scripts/install-repo-hooks.sh` - it will ensure checks are being run before each commit is made and in other situations.

Autolinting commits (made after running `pre-commit run -a` and fixing all files with new checks) are to be recorded in `.git-blame-ignore-revs` and that file can be used with git blame and git config snippet like this (or command-line `--ignore-revs-file`) to skip such commits when examining history.

Expand Down
107 changes: 107 additions & 0 deletions scripts/check-branch-name.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#!/bin/sh

# Define allowed prefixes including release
allowed_prefixes="feat|fix|hotfix|chore|refactor|test|spike|prototype|release|docs"

# Define the JIRA ticket pattern (CSN-XXXX, any number of digits)
jira_ticket_pattern="CSN-[0-9]+"

# Define the valid release version pattern (release/vX.Y.Z or release/vX.Y.Z-beta)
release_version_pattern="release/v[0-9]+\.[0-9]+\.[0-9]+(-[a-z0-9]+)?"

# Get the current branch name
branch_name="$1"

# Define the valid hotfix version pattern (hotfix/vX.Y.Z)
hotfix_version_pattern="hotfix/v[0-9]+\.[0-9]+\.[0-9]+"

# If branch_name is empty or you are in a detached HEAD state where HEAD is returned
if [ -z "$branch_name" ] || [ "$branch_name" = "HEAD" ]; then
printf "❌ ERROR: Unable to determine branch name.\n"
exit 1
fi

# Check if it's a valid release branch (release/vX.Y.Z)
if echo "$branch_name" | grep -Eq "^$release_version_pattern$"; then
printf "✅ Branch name is valid (Release branch): '%s'\n" "$branch_name"
exit 0
fi

# Validate hotfix branches
if echo "$branch_name" | grep -Eq "^hotfix/"; then
if echo "$branch_name" | grep -Eq "^$hotfix_version_pattern-$jira_ticket_pattern-[a-z0-9]+(-[a-z0-9]+)*$"; then
printf "✅ Branch name is valid (Hotfix branch): '%s'\n" "$branch_name"
exit 0
else
printf "❌ ERROR: Hotfix branch name '%s' is invalid. Expected format is 'hotfix/vX.Y.Z-CSN-XXXX-description' where 'XXXX' can be any number of digits.\n" "$branch_name"
printf "🔹 Suggested fix: 'hotfix/v1.2.3-CSN-1234-fix-login-issue'\n"
printf "👉 Branch name should follow the format: 'hotfix/vX.Y.Z-CSN-XXXX-description'\n"
exit 1
fi
fi

# ❌ **If it starts with `release/` but is NOT valid, show a specific error**
if echo "$branch_name" | grep -Eq "^release/"; then
printf "❌ ERROR: Branch name '%s' is invalid - Release branches must follow the format 'release/vX.Y.Z' or 'release/vX.Y.Z-beta'\n" "$branch_name" >&2
printf "🔹 Suggested fix: 'release/v1.2.3'\n"
printf "👉 Branch name should follow the format: 'release/vX.Y.Z' or 'release/vX.Y.Z-beta'\n"
exit 1
fi

# Extract JIRA ticket (always uppercase CSN-XXXX)
jira_ticket=$(echo "$branch_name" | grep -o "$jira_ticket_pattern")

# Extract the description (everything after `CSN-XXXX-`)
branch_description=$(echo "$branch_name" | sed -E "s/^($allowed_prefixes)\/$jira_ticket_pattern-//g")

# Ensure description is lowercase (ignore the `CSN-XXXX` part)
branch_description_lower=$(echo "$branch_description" | tr '[:upper:]' '[:lower:]')

# Suggest a valid branch name (default to feat/CSN-1234-description if missing details)
if [ -n "$jira_ticket" ] && [ -n "$branch_description" ]; then
suggested_branch_name="feat/${jira_ticket}-$(echo "$branch_description_lower" | tr '_' '-' | tr ' ' '-' | tr -d '[^a-z0-9-]')"
else
suggested_branch_name="feat/CSN-1234-description"
fi

# Function to display errors properly (single-line for Git popups)
show_error() {
# Print error message on the first line
printf "❌ ERROR: %s\n" "$1" >&2

# Print suggested fix on a new line
printf "🔹 Suggested fix: '%s'\n" "$suggested_branch_name" >&2

# Print additional information (e.g., branch name format)
printf "👉 Branch name should follow the format: '<prefix>/CSN-XXXX-description'\n" >&2

exit 1
}

# Check if the branch name starts with an allowed prefix
if ! echo "$branch_name" | grep -Eq "^($allowed_prefixes)/"; then
show_error "Branch name '$branch_name' is invalid - Must start with one of: $allowed_prefixes/"
fi

# Check if the branch name contains a valid JIRA ticket (CSN-XXXX)
if ! echo "$branch_name" | grep -Eq "/$jira_ticket_pattern-"; then
show_error "Branch name '$branch_name' is invalid - Must include a JIRA ticket like 'CSN-XXXX' (e.g., CSN-1234, CSN-98765)"
fi

# Check for uppercase letters in the description (ignore the JIRA ticket itself)
if [ "$branch_description" != "$branch_description_lower" ]; then
show_error "Branch name '$branch_name' contains uppercase letters in the description - Use only lowercase letters, numbers, and hyphens"
fi

# Ensure kebab-case (no underscores, spaces, or special characters other than '-')
if echo "$branch_description" | grep -Eq "[^a-z0-9-]"; then
show_error "Branch name '$branch_name' contains invalid characters in the description - Only lowercase letters, numbers, and hyphens are allowed"
fi

# Ensure proper formatting (e.g., feat/CSN-1234-description)
if ! echo "$branch_name" | grep -Eq "^($allowed_prefixes)/$jira_ticket_pattern-[a-z0-9]+(-[a-z0-9]+)*$"; then
show_error "Branch name '$branch_name' is invalid - Format should be '<prefix>/CSN-XXXX-description' - Example: 'feat/CSN-1234-add-new-feature'"
fi

printf "✅ Branch name is valid: '%s'\n" "$branch_name"
exit 0
6 changes: 6 additions & 0 deletions scripts/check-current-branch.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/sh

# Get the current branch name
branch_name="${GITHUB_HEAD_REF:-$(git branch --show-current)}"

scripts/check-branch-name.sh "$branch_name"
7 changes: 7 additions & 0 deletions scripts/install-repo-hooks.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash
python3 -m pip install pre-commit
pre-commit install --install-hooks
pre-commit install -t commit-msg
pre-commit install -t post-checkout
pre-commit install -t pre-commit
pre-commit install -t pre-push