From efc82a5ed7e055bd25094cb628bc8782bb884b1e Mon Sep 17 00:00:00 2001 From: Abhinay Kukkadapu Date: Fri, 3 Apr 2026 10:19:59 -0700 Subject: [PATCH] Add lintrunner pre-commit hook and consolidate git hooks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Run lintrunner automatically on every commit via .githooks/pre-commit. Hooks are enabled by default through install_executorch.sh using core.hooksPath. Add a pre-push passthrough that delegates to .git/hooks/pre-push so backend-specific hooks (e.g., ARM) still work. Remove redundant ARM pre-commit hook. Test Plan: Tested all pre-commit branches (lintrunner missing, toml missing, first init, hash match, toml changed, init failure, first commit, lint pass, lint fail, sha256sum/shasum parity) — 18/18 passed. --- .githooks/README.md | 60 ++++++++++++++++-------------- .githooks/install.sh | 23 ------------ .githooks/pre-commit | 66 ++++++++++++++++++++++++++++++++- .githooks/pre-push | 9 +++++ CONTRIBUTING.md | 12 ++++++ backends/arm/README.md | 8 ++-- backends/arm/scripts/pre-commit | 22 ----------- install_executorch.sh | 5 +++ 8 files changed, 129 insertions(+), 76 deletions(-) delete mode 100755 .githooks/install.sh create mode 100755 .githooks/pre-push delete mode 100755 backends/arm/scripts/pre-commit diff --git a/.githooks/README.md b/.githooks/README.md index cf79397337c..796274c2e48 100644 --- a/.githooks/README.md +++ b/.githooks/README.md @@ -1,57 +1,63 @@ # Git Hooks -This directory contains Git hooks for the ExecuTorch repository. +This directory contains Git hooks for the ExecuTorch repository. It is used as +`core.hooksPath`, so git looks here instead of `.git/hooks/`. -## Pre-commit Hook +## Hooks -The pre-commit hook automatically updates the PyTorch commit pin in `.ci/docker/ci_commit_pins/pytorch.txt` whenever `torch_pin.py` is modified. +### pre-commit -### How It Works +Runs on every commit: -1. When you commit changes to `torch_pin.py`, the hook detects the change -2. It parses the `NIGHTLY_VERSION` field (e.g., `dev20251004`) -3. Converts it to a date string (e.g., `2025-10-04`) -4. Fetches the corresponding commit hash from the PyTorch nightly branch at https://github.com/pytorch/pytorch/tree/nightly -5. Updates `.ci/docker/ci_commit_pins/pytorch.txt` with the new commit hash -6. Automatically stages the updated file for commit +1. **torch_pin sync** — when `torch_pin.py` is staged, updates the PyTorch commit + pin in `.ci/docker/ci_commit_pins/pytorch.txt` and syncs grafted c10 files. +2. **lintrunner** — runs `lintrunner -a --revision HEAD^ --skip MYPY` on changed + files. Auto-fixes formatting and blocks on lint errors. Soft-fails if lintrunner + is not installed. Runs `lintrunner init` automatically when `.lintrunner.toml` + changes. -### Installation +### pre-push -To install the Git hooks, run: +Delegates to `.git/hooks/pre-push` if one exists. This allows backend-specific +pre-push hooks (e.g., ARM's license and commit message checks) to work alongside +the repo-wide hooks. + +## Installation + +Hooks are installed automatically by `./install_executorch.sh`. + +To install manually: ```bash -.githooks/install.sh +git config core.hooksPath .githooks ``` -This will copy the pre-commit hook to `.git/hooks/` and make it executable. +### ARM backend pre-push -### Manual Usage - -You can also run the update script manually at any time: +ARM contributors should additionally install the ARM-specific pre-push hook: ```bash -python .github/scripts/update_pytorch_pin.py +cp backends/arm/scripts/pre-push .git/hooks/ ``` -### Uninstalling +## Bypassing -To remove the pre-commit hook: +To skip hooks for a single commit or push: ```bash -rm .git/hooks/pre-commit +git commit --no-verify +git push --no-verify ``` ## Troubleshooting -If the hook fails during a commit: +If the torch_pin hook fails: 1. Check that Python 3 is available in your PATH 2. Ensure you have internet connectivity to fetch commits from GitHub 3. Verify that the `NIGHTLY_VERSION` in `torch_pin.py` is in the correct format (`devYYYYMMDD`) -4. Make sure the corresponding nightly release exists in the PyTorch nightly branch -You can run the script manually to see detailed error messages: +If lintrunner fails: -```bash -python .github/scripts/update_pytorch_pin.py -``` +1. Run `lintrunner init` to install linter tools +2. Check that your virtual environment is activated diff --git a/.githooks/install.sh b/.githooks/install.sh deleted file mode 100755 index b79f750177b..00000000000 --- a/.githooks/install.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash - -# Script to install Git hooks from .githooks directory - -set -e - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -GIT_DIR="$(git rev-parse --git-dir)" -HOOKS_DIR="${GIT_DIR}/hooks" - -echo "Installing Git hooks..." - -# Install pre-commit hook -echo "📦 Installing pre-commit hook..." -cp "${SCRIPT_DIR}/pre-commit" "${HOOKS_DIR}/pre-commit" -chmod +x "${HOOKS_DIR}/pre-commit" -echo "✅ pre-commit hook installed" - -echo "" -echo "🎉 Git hooks installed successfully!" -echo "" -echo "The pre-commit hook will automatically update .ci/docker/ci_commit_pins/pytorch.txt" -echo "whenever you commit changes to torch_pin.py" diff --git a/.githooks/pre-commit b/.githooks/pre-commit index f29342da67e..da2aaf27ee2 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -27,4 +27,68 @@ if git diff --cached --name-only | grep -q "^torch_pin.py$"; then fi fi -exit 0 +# --- lintrunner --- + +if ! command -v lintrunner >/dev/null 2>&1; then + echo "Warning: lintrunner not found. Skipping lint checks." + echo "Install with: pip install lintrunner lintrunner-adapters && lintrunner init" + exit 0 +fi + +if [ ! -f .lintrunner.toml ]; then + echo "Warning: .lintrunner.toml not found. Skipping lint checks." + exit 0 +fi + +git_dir=$(git rev-parse --git-dir) + +# Portable hash: sha256sum (Linux) or shasum (macOS) +if command -v sha256sum >/dev/null 2>&1; then + toml_hash=$(sha256sum .lintrunner.toml | cut -d' ' -f1) +else + toml_hash=$(shasum -a 256 .lintrunner.toml | cut -d' ' -f1) +fi +stored_hash="" +[ -f "${git_dir}/.lintrunner_init_hash" ] && stored_hash=$(cat "${git_dir}/.lintrunner_init_hash") + +if [ "${toml_hash}" != "${stored_hash}" ]; then + echo "Running lintrunner init..." + if lintrunner init; then + echo "${toml_hash}" > "${git_dir}/.lintrunner_init_hash" + else + echo "lintrunner init failed. Run 'lintrunner init' manually." + exit 1 + fi +fi + +staged_files=$(git diff --cached --name-only --diff-filter=ACMR) + +# Use HEAD^ if it exists (skip on initial commit) +revision_flag="--revision HEAD^" +if ! git rev-parse HEAD^ >/dev/null 2>&1; then + revision_flag="" +fi + +lintrunner -a $revision_flag --skip MYPY +lint_status=$? + +# Check if lintrunner modified any staged files. If so, block the commit +# so the user can review the changes before committing. +files_modified=0 +while IFS= read -r path; do + [ -z "${path}" ] && continue + if ! git diff --quiet -- "${path}" 2>/dev/null; then + files_modified=1 + break + fi +done <<< "${staged_files}" + +if [ $files_modified -eq 1 ]; then + echo "Lintrunner modified files. Review with 'git diff', then 'git add -u && git commit'." + exit 1 +fi + +if [ $lint_status -ne 0 ]; then + echo "Lint errors found. Fix them and try again, or use 'git commit --no-verify' to skip." + exit 1 +fi diff --git a/.githooks/pre-push b/.githooks/pre-push new file mode 100755 index 00000000000..ef2839436a8 --- /dev/null +++ b/.githooks/pre-push @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +# Delegate to local pre-push hook if present (e.g., ARM backend). +# .githooks/ is set as core.hooksPath, so git won't look in .git/hooks/ +# automatically. This passthrough ensures local hooks still run. +local_hook="$(git rev-parse --git-dir)/hooks/pre-push" +if [ -x "$local_hook" ]; then + "$local_hook" "$@" +fi diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ff0a9834936..73617d5a6aa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -206,6 +206,18 @@ lintrunner init Then run `lintrunner` from the root of the repo to see its suggestions, or run `lintrunner -a` to automatically apply the suggestions. +### Git Hooks + +A pre-commit hook runs lintrunner automatically on every commit. Install it with: + +``` +git config core.hooksPath .githooks +``` + +This is also done automatically by `./install_executorch.sh`. If lintrunner +auto-fixes files, the commit will be blocked so you can review the changes with +`git diff` before re-committing. + ### Python Style ExecuTorch Python code follows the style used by the PyTorch core project. diff --git a/backends/arm/README.md b/backends/arm/README.md index 7bc6a24ccaf..cd24cb0d2f7 100644 --- a/backends/arm/README.md +++ b/backends/arm/README.md @@ -247,12 +247,14 @@ To run these tests, you need to install the required dependencies by running the Please note that installing model test dependencies is a standalone process. When using the `--setup-test-dependency` flag, the script will install only the necessary dependencies for model tests, skipping all other setup procedures. -## Using pre-commit +## Using git hooks -A pre-commit script is available in the backend to help developers. Follow the steps below to enable it: +The repo-wide pre-commit hook (lintrunner + torch_pin sync) is installed automatically +by `./install_executorch.sh`. To install the Arm-specific pre-push hook (license checks, +commit message format, docgen): ``` -cp backends/arm/scripts/pre-commit .git/hooks/ +cp backends/arm/scripts/pre-push .git/hooks/ ``` ## Notes on model specific and optional passes diff --git a/backends/arm/scripts/pre-commit b/backends/arm/scripts/pre-commit deleted file mode 100755 index 9839ac0bfd9..00000000000 --- a/backends/arm/scripts/pre-commit +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash -# Copyright 2025-2026 Arm Limited and/or its affiliates. -# -# This source code is licensed under the BSD-style license found in the -# LICENSE file in the root directory of this source tree. - -# Check 1: If commit header contains WIP, everything is ok -git rev-list --format=%s --max-count=1 HEAD | grep -q WIP && exit 0 - -# Check 2: lintunner on latest patch. -lintrunner -a --revision 'HEAD^' --skip MYPY - -# Re-stage only paths that were already staged before lintrunner ran. -# This preserves lint fixes for files the user intended to commit, and avoids -# accidentally staging unrelated working-tree changes during commit --amend. -staged_files=$(git diff --cached --name-only --diff-filter=ACMR) -while IFS= read -r path; do - [ -z "${path}" ] && continue - if [ -f "${path}" ]; then - git add -- "${path}" || true - fi -done <<< "${staged_files}" diff --git a/install_executorch.sh b/install_executorch.sh index 931728296e1..3289fc7c5b0 100755 --- a/install_executorch.sh +++ b/install_executorch.sh @@ -10,3 +10,8 @@ # so we avoid repeated symlink segments in downstream CMake paths. cd -- "$( realpath "$( dirname -- "${BASH_SOURCE[0]}" )" )" &> /dev/null || /bin/true ./run_python_script.sh ./install_executorch.py "$@" + +# Install git hooks if inside a git repo +if git rev-parse --git-dir > /dev/null 2>&1; then + git config core.hooksPath .githooks +fi