diff --git a/.github/PRE_COMMIT_HOOK.md b/.github/PRE_COMMIT_HOOK.md index d0cf066..f619ac0 100644 --- a/.github/PRE_COMMIT_HOOK.md +++ b/.github/PRE_COMMIT_HOOK.md @@ -6,24 +6,42 @@ This repository uses a pre-commit hook to ensure code quality and prevent broken The pre-commit hook automatically runs the following checks before allowing any commit: -1. **Code Formatting** (`make format`) +1. **Badge Updates and README Check** (`make update-badges` and `make readme-check`) + + - Automatically updates README badges when problem files are modified + - Ensures README.md is up to date with current problem counts + - Automatically stages updated README.md if badges are modified + +2. **Code Formatting** (`make format`) - Formats C++ files using `clang-format` - Formats Python files using `ruff` (or `black` as fallback) - Automatically stages formatted files if changes are made -2. **Code Linting** (`make lint`) +3. **Code Linting** (`make lint`) - Lints C++ files using `clang-tidy` - Lints Python files using `ruff` (or `flake8` as fallback) -3. **Tests** (`make test:all`) +4. **Tests** (`make test:all`) - Runs all C++ tests using Google Test - Runs all Python tests using pytest ## Installation -The pre-commit hook is already installed in this repository. If you're setting up a new clone, the hook should work automatically. +The pre-commit hook needs to be installed after cloning the repository. To install the hook, run: + +```bash +make install-hooks +``` + +This will copy the pre-commit hook from the `hooks/` directory to `.git/hooks/` and make it executable. + +To uninstall the hooks (if needed), run: + +```bash +make uninstall-hooks +``` ## Manual Testing @@ -45,6 +63,18 @@ git commit --no-verify -m "your commit message" ## Troubleshooting +### Hook fails on badge updates + +- The hook automatically updates badges when problem files are modified +- If badge update fails, check that you have Python 3 installed and the required dependencies: + ```bash + pip install -r requirements.txt + ``` +- Ensure the `scripts/update_badges.py` script is executable and working: + ```bash + make update-badges + ``` + ### Hook fails on formatting - The hook automatically fixes formatting issues and stages the corrected files @@ -98,13 +128,17 @@ sudo apt-get install libgtest-dev The pre-commit hook uses the existing Makefile targets. To modify the behavior: -1. **Formatting rules**: Modify the `format-cpp` and `format-python` targets in `Makefile` -2. **Linting rules**: Modify the `lint-cpp` and `lint-python` targets in `Makefile` -3. **Test configuration**: Modify the `test:all` target in `Makefile` +1. **Badge updates**: Modify the `update-badges` target in `Makefile` or the `scripts/update_badges.py` script +2. **README checks**: Modify the `readme-check` and `readme` targets in `Makefile` +3. **Formatting rules**: Modify the `format-cpp` and `format-python` targets in `Makefile` +4. **Linting rules**: Modify the `lint-cpp` and `lint-python` targets in `Makefile` +5. **Test configuration**: Modify the `test:all` target in `Makefile` ## Benefits +- **Up-to-date documentation**: README badges are automatically updated when problems are added or modified - **Consistent code style**: All code follows the same formatting standards - **Early error detection**: Linting catches potential issues before they reach the repository - **Reliable codebase**: Tests ensure that new changes don't break existing functionality - **Team productivity**: Reduces time spent on code review for style and basic issues +- **Automated maintenance**: No need for separate CI workflows for badge updates diff --git a/.github/workflows/update-badges.yml b/.github/workflows/update-badges.yml deleted file mode 100644 index ecac212..0000000 --- a/.github/workflows/update-badges.yml +++ /dev/null @@ -1,50 +0,0 @@ ---- -name: Update Badges - -on: # yamllint disable-line rule:truthy - push: - branches: ["main"] - workflow_dispatch: # Allow manual triggering - -jobs: - update-badges: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - fetch-depth: 0 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.10" - - - name: Update badges - run: | - python3 scripts/update_badges.py - - - name: Check for changes - id: verify-changed-files - run: | - if git diff --quiet; then - echo "changed=false" >> $GITHUB_OUTPUT - else - echo "changed=true" >> $GITHUB_OUTPUT - fi - - - name: Commit and push changes - if: steps.verify-changed-files.outputs.changed == 'true' - run: | - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - git add README.md - git commit -m "docs: update problem count badges [skip ci]" - - # Get the correct branch name for pushing - BRANCH_NAME="${{ github.event_name == 'pull_request' && github.base_ref || github.ref_name }}" - echo "Pushing to branch: $BRANCH_NAME" - - # Push to the correct branch - git push origin HEAD:$BRANCH_NAME diff --git a/Makefile b/Makefile index 4b5afed..093a238 100644 --- a/Makefile +++ b/Makefile @@ -329,6 +329,25 @@ debug-gtest: fi; \ fi +# ============================================================================= +# Git Hooks +# ============================================================================= + +# Install Git hooks +.PHONY: install-hooks +install-hooks: ## Install Git hooks for code quality + @echo "Installing Git hooks..." + @cp hooks/pre-commit .git/hooks/pre-commit + @chmod +x .git/hooks/pre-commit + @echo "$(call color_green,Git hooks installed successfully.)" + +# Uninstall Git hooks +.PHONY: uninstall-hooks +uninstall-hooks: ## Uninstall Git hooks + @echo "Uninstalling Git hooks..." + @rm -f .git/hooks/pre-commit + @echo "$(call color_green,Git hooks uninstalled.)" + # ============================================================================= # Help and Documentation # ============================================================================= @@ -357,6 +376,8 @@ help: @echo " readme - Generate README from problem configurations" @echo " readme-check - Check if README is up to date" @echo " update-badges - Update README badges with current problem counts" + @echo " install-hooks - Install Git hooks for code quality" + @echo " uninstall-hooks - Uninstall Git hooks" @echo " debug-gtest - Debug Google Test configuration" @echo " help - Show this help message" @echo "" diff --git a/README.md b/README.md index 9e20cd4..23b3172 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ ### 📊 Repository Stats [![Last Commit](https://img.shields.io/github/last-commit/mathusanm6/LeetCode?style=for-the-badge&logo=git&logoColor=white&color=blue)](https://github.com/mathusanm6/LeetCode/commits/main) -[![C++ Solutions](https://img.shields.io/badge/C%2B%2B%20Solutions-3-blue?style=for-the-badge&logo=cplusplus&logoColor=white)](https://github.com/mathusanm6/LeetCode/tree/main/problems) -[![Python Solutions](https://img.shields.io/badge/Python%20Solutions-3-blue?style=for-the-badge&logo=python&logoColor=white)](https://github.com/mathusanm6/LeetCode/tree/main/problems) +[![C++ Solutions](https://img.shields.io/badge/C%2B%2B%20Solutions-4-blue?style=for-the-badge&logo=cplusplus&logoColor=white)](https://github.com/mathusanm6/LeetCode/tree/main/problems) +[![Python Solutions](https://img.shields.io/badge/Python%20Solutions-4-blue?style=for-the-badge&logo=python&logoColor=white)](https://github.com/mathusanm6/LeetCode/tree/main/problems) @@ -184,7 +184,7 @@ This repository covers a comprehensive range of algorithmic patterns and data st | # | Title | Solution | Time | Space | Difficulty | Tag | Note | |---|-------|----------|------|-------|------------|-----|------| | 1 | [Two Sum](https://leetcode.com/problems/two-sum/) | [Python](./problems/two_sum/two_sum.py), [C++](./problems/two_sum/two_sum.cc) | _O(n)_ | _O(n)_ | Easy | | | -| 217 | [Contains Duplicate](https://leetcode.com/problems/contains-duplicate/description/) | [Python](./problems/contains_duplicate/contains_duplicate.py), [C++](./problems/contains_duplicate/contains_duplicate.cc) | _O(n)_ | _O(n)_ | Easy | | | +| 217 | [Contains Duplicate](https://leetcode.com/problems/contains-duplicate/) | [Python](./problems/contains_duplicate/contains_duplicate.py), [C++](./problems/contains_duplicate/contains_duplicate.cc) | _O(n)_ | _O(n)_ | Easy | | | ## Two Pointers @@ -196,4 +196,4 @@ This repository covers a comprehensive range of algorithmic patterns and data st | # | Title | Solution | Time | Space | Difficulty | Tag | Note | |---|-------|----------|------|-------|------------|-----|------| -| 2313 | [Minimum Flips in Binary Tree to Get Result](https://leetcode.com/problems/minimum-flips-in-binary-tree-to-get-result/description/) | [Python](./problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result.py), [C++](./problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result.cc) | _O(n)_ | _O(1)_ | Hard | | _n_ is the number of nodes in the binary tree. | +| 2313 | [Minimum Flips in Binary Tree to Get Result](https://leetcode.com/problems/minimum-flips-in-binary-tree-to-get-result/) | [Python](./problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result.py), [C++](./problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result.cc) | _O(n)_ | _O(1)_ | Hard | | _n_ is the number of nodes in the binary tree. | diff --git a/hooks/README.md b/hooks/README.md new file mode 100644 index 0000000..d13f0fb --- /dev/null +++ b/hooks/README.md @@ -0,0 +1,41 @@ +# Git Hooks + +This directory contains Git hooks that can be installed to maintain code quality. + +## Available Hooks + +### pre-commit + +The pre-commit hook ensures code quality by running the following checks before allowing any commit: + +1. **Badge Updates and README Check** - Automatically updates README badges when problem files are modified +2. **Code Formatting** - Formats C++ and Python files using clang-format and ruff +3. **Code Linting** - Lints C++ and Python files using clang-tidy and ruff +4. **Tests** - Runs all tests to ensure code functionality + +## Installation + +To install the hooks, run: + +```bash +make install-hooks +``` + +To uninstall the hooks, run: + +```bash +make uninstall-hooks +``` + +## Manual Installation + +If you prefer to install manually: + +```bash +cp hooks/pre-commit .git/hooks/pre-commit +chmod +x .git/hooks/pre-commit +``` + +## More Information + +See [`../.github/PRE_COMMIT_HOOK.md`](../.github/PRE_COMMIT_HOOK.md) for detailed documentation about the pre-commit hook functionality and troubleshooting. diff --git a/hooks/pre-commit b/hooks/pre-commit new file mode 100755 index 0000000..7dfe709 --- /dev/null +++ b/hooks/pre-commit @@ -0,0 +1,210 @@ +#!/bin/bash + +# Pre-commit hook for LeetCode project +# This hook ensures code quality by running tests, formatting, and linting before allowing commits + +set -e # Exit on any error + +# Color definitions for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Function to print colored output +print_status() { + echo -e "${BLUE}[PRE-COMMIT]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[PRE-COMMIT]${NC} ✓ $1" +} + +print_warning() { + echo -e "${YELLOW}[PRE-COMMIT]${NC} ⚠ $1" +} + +print_error() { + echo -e "${RED}[PRE-COMMIT]${NC} ✗ $1" +} + +# Get the root directory of the git repository +REPO_ROOT=$(git rev-parse --show-toplevel) +cd "$REPO_ROOT" + +# Check if virtual environment exists and activate it +if [ -f "venv/bin/activate" ]; then + print_status "Activating virtual environment..." + source venv/bin/activate +elif [ -f ".venv/bin/activate" ]; then + print_status "Activating virtual environment..." + source .venv/bin/activate +fi + +print_status "Starting pre-commit checks..." + +# Check if we have any staged files to commit +if ! git diff --cached --quiet; then + print_status "Files staged for commit detected" +else + print_warning "No files staged for commit" + exit 0 +fi + +# Get list of staged files +STAGED_FILES=$(git diff --cached --name-only) +print_status "Staged files: $(echo "$STAGED_FILES" | tr '\n' ' ')" + +# Check what types of files are changed +HAS_PYTHON_FILES=false +HAS_CPP_FILES=false +HAS_PROBLEM_FILES=false +HAS_CODE_FILES=false + +while IFS= read -r file; do + case "$file" in + *.py) + HAS_PYTHON_FILES=true + HAS_CODE_FILES=true + if [[ "$file" == problems/* ]]; then + HAS_PROBLEM_FILES=true + fi + ;; + *.cc|*.h) + HAS_CPP_FILES=true + HAS_CODE_FILES=true + if [[ "$file" == problems/* ]]; then + HAS_PROBLEM_FILES=true + fi + ;; + problems/*) + HAS_PROBLEM_FILES=true + ;; + esac +done <<< "$STAGED_FILES" + +print_status "Change analysis: Python=${HAS_PYTHON_FILES}, C++=${HAS_CPP_FILES}, Problems=${HAS_PROBLEM_FILES}, Code=${HAS_CODE_FILES}" + +CURRENT_STEP=1 +TOTAL_STEPS=2 + +# Step 1: Update badges and check README +print_status "Step ${CURRENT_STEP}/${TOTAL_STEPS}: Updating badges and checking README.md..." + +# If problem files changed, automatically update badges +if [ "$HAS_PROBLEM_FILES" = true ]; then + print_status "Problem files detected, updating badges..." + if make update-badges; then + print_success "Badges updated successfully" + + # Check if badge update modified README.md + if ! git diff --quiet README.md 2>/dev/null; then + print_status "README.md was updated with new badges, staging changes..." + git add README.md + fi + else + print_error "Badge update failed" + exit 1 + fi +fi + +# Now check if README is up to date +if make readme-check; then + print_success "README.md is up to date" +else + print_error "README.md is outdated or generation failed" + print_error "Please run 'make readme' to update README.md" + exit 1 +fi +CURRENT_STEP=$((CURRENT_STEP + 1)) + +# Step 2: Format code (only if code files are changed) +if [ "$HAS_CODE_FILES" = true ]; then + print_status "Step ${CURRENT_STEP}/${TOTAL_STEPS}: Formatting code..." + if make format > /dev/null 2>&1; then + print_success "Code formatting completed" + + # Check if formatting made any changes + if ! git diff --quiet; then + print_warning "Formatting made changes to your files" + print_status "Adding formatted files to staging area..." + + # Add only the files that were originally staged and got formatted + git diff --name-only | while read -r file; do + if git diff --cached --name-only | grep -q "^$file$"; then + git add "$file" + print_status "Re-staged formatted file: $file" + fi + done + fi + else + print_error "Code formatting failed" + print_error "Please fix formatting issues and try again" + exit 1 + fi + CURRENT_STEP=$((CURRENT_STEP + 1)) + TOTAL_STEPS=$((TOTAL_STEPS + 1)) +else + print_status "Skipping code formatting (no code files changed)" +fi + +# Step 3: Run tests (only if problem files are changed) +if [ "$HAS_PROBLEM_FILES" = true ]; then + print_status "Step ${CURRENT_STEP}/${TOTAL_STEPS}: Running tests for changed problems..." + print_status "DEBUG: Current directory: $(pwd)" + print_status "DEBUG: Python path: $(which python3)" + print_status "DEBUG: Pytest path: $(which pytest)" + print_status "DEBUG: PATH: $PATH" + if make test:all; then + print_success "All tests passed" + else + print_error "Tests failed" + print_error "Please fix failing tests and try again" + print_status "Run 'make test:all' to see detailed test results" + print_status "DEBUG: Last command exit code: $?" + exit 1 + fi + CURRENT_STEP=$((CURRENT_STEP + 1)) + TOTAL_STEPS=$((TOTAL_STEPS + 1)) +else + print_status "Skipping tests (no problem files changed)" +fi + +# Step 4: Lint code (only if Python or C++ files are changed) +if [ "$HAS_PYTHON_FILES" = true ] || [ "$HAS_CPP_FILES" = true ]; then + print_status "Step ${CURRENT_STEP}/${TOTAL_STEPS}: Linting code..." + + # Run specific linting based on file types + LINT_SUCCESS=true + if [ "$HAS_PYTHON_FILES" = true ]; then + print_status "Linting Python files..." + if ! make lint-python; then + LINT_SUCCESS=false + fi + fi + + if [ "$HAS_CPP_FILES" = true ]; then + print_status "Linting C++ files..." + if ! make lint-cpp; then + LINT_SUCCESS=false + fi + fi + + if [ "$LINT_SUCCESS" = true ]; then + print_success "Code linting passed" + else + print_error "Code linting failed" + print_error "Please fix linting issues and try again" + print_status "Run 'make lint' to see detailed linting errors" + exit 1 + fi +else + print_status "Skipping linting (no Python or C++ files changed)" +fi + +# All checks passed +print_success "All pre-commit checks passed! ✨" +print_status "Proceeding with commit..." + +exit 0 diff --git a/problems/contains_duplicate/config.yml b/problems/contains_duplicate/config.yml index 82812f8..b7d042c 100644 --- a/problems/contains_duplicate/config.yml +++ b/problems/contains_duplicate/config.yml @@ -1,7 +1,7 @@ problem: number: 217 title: "Contains Duplicate" - leetcode_url: "https://leetcode.com/problems/contains-duplicate/description/" + leetcode_url: "https://leetcode.com/problems/contains-duplicate/" difficulty: "easy" tags: ["Arrays & Hashing"] diff --git a/problems/minimum_flips_in_binary_tree_to_get_result/config.yml b/problems/minimum_flips_in_binary_tree_to_get_result/config.yml index b5d0e0b..5bf0fe9 100644 --- a/problems/minimum_flips_in_binary_tree_to_get_result/config.yml +++ b/problems/minimum_flips_in_binary_tree_to_get_result/config.yml @@ -1,7 +1,7 @@ problem: number: 2313 title: "Minimum Flips in Binary Tree to Get Result" - leetcode_url: "https://leetcode.com/problems/minimum-flips-in-binary-tree-to-get-result/description/" + leetcode_url: "https://leetcode.com/problems/minimum-flips-in-binary-tree-to-get-result/" difficulty: "hard" tags: ["Trees"]