diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 00000000..5010f84c --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -euo pipefail + +repo_root="$(git rev-parse --show-toplevel)" +"${repo_root}/bin/format-staged-files.sh" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f17d1828..947aee87 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,6 +38,28 @@ Tips for git management are found on the corresponding [wiki page](https://githu Tips for code formatting are found on the corresponding [wiki page](https://github.com/JETSCAPE/X-SCAPE/wiki/Doc.Policy.CodeFormatting). +### Optional Local git Hooks + +Enable a local formatter hook that runs on staged files before commit. +This is useful for catching formatting issues early, but remains optional. + +Setup: +``` +./bin/setup-hooks.sh +``` + +Notes: +- The setup-hooks script only needs to be run once per clone. +- The hook formats CMake files and C++ files in `src/` and `examples/`. +- The hook only checks staged files. +- Requires `clang-format` (v14) and `cmake-format` (v0.6.13) on PATH. + - Ubuntu / Debian: `sudo apt install clang-format-14 cmake-format` + - Package manager commands for other distributions may differ. + - `pip3 install clang-format==14.0.6 cmakelang` can be used. + - If using pip3, add the pip bin directory to the PATH, e.g. `export PATH="$HOME/.local/bin:$PATH"`. +- Skip hooks for one commit with `git commit --no-verify`. +- To disable permanently, unset hooks with `git config --unset core.hooksPath`. + ## Unit Tests Please also see the [Unit Tests](https://github.com/JETSCAPE/X-SCAPE/wiki/Doc.Policy.UnitTests) page for details about writing unit tests to accompany your code contributions. diff --git a/bin/codebase-format-helper.bash b/bin/codebase-format-helper.bash index 322031a6..4943b3d1 100755 --- a/bin/codebase-format-helper.bash +++ b/bin/codebase-format-helper.bash @@ -123,17 +123,11 @@ function look_for_files_to_format() ) elif [[ ${language} = 'CMake' ]]; then FILES_TO_FORMAT=( - "${base_dir}"/**/CMakeLists.txt - "${base_dir}"/**/*.cmake + "${base_dir}"/CMakeLists.txt + "${base_dir}"/{src,examples}/**/CMakeLists.txt + "${base_dir}"/{src,examples}/**/*.cmake + "${base_dir}"/cmakemodules/**/*.cmake ) - for index in "${!FILES_TO_FORMAT[@]}"; do - if [[ ${FILES_TO_FORMAT[index]} =~ ^${base_dir}/3rdparty/(Cuba[^/]*/|CMakeLists.txt$) ]]; then - continue - elif [[ ${FILES_TO_FORMAT[index]} =~ ^${base_dir}/(3rdparty|build[^/]*)/ ]]; then - unset -v 'FILES_TO_FORMAT[index]' - fi - done - FILES_TO_FORMAT=("${FILES_TO_FORMAT[@]}") fi } diff --git a/bin/format-staged-files.sh b/bin/format-staged-files.sh new file mode 100755 index 00000000..971d005f --- /dev/null +++ b/bin/format-staged-files.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash + +set -euo pipefail + +if ! command -v clang-format >/dev/null 2>&1 || ! command -v cmake-format >/dev/null 2>&1; then + cat >&2 <<'EOF' +Formatter tools not found. Install them: + Ubuntu/Debian: sudo apt install clang-format-14 cmake-format +EOF + exit 1 +fi + +repo_root="$(git rev-parse --show-toplevel)" +cd "${repo_root}" + +mapfile -t staged_files < <(git diff --cached --name-only --diff-filter=ACMR 2>/dev/null || true) +if [[ ${#staged_files[@]} -eq 0 ]]; then + exit 0 +fi + +cpp_ext='(h|hpp|c|cc|cpp)$' +cmake_re='(^|/)CMakeLists\.txt$|\.cmake$' + +selected_files=() +for f in "${staged_files[@]}"; do + # Skip build folder and external packages + if [[ "${f}" == build/* || "${f}" == external_packages/* ]]; then + continue + fi + + # Include files matching CMakeLists.txt or .cmake + if [[ "${f}" =~ ${cmake_re} ]]; then + selected_files+=("${f}") + continue + fi + + # Include source files from src/ and examples/ + if [[ "${f}" =~ \.${cpp_ext} ]] && [[ "${f}" == src/* || "${f}" == examples/* ]]; then + selected_files+=("${f}") + fi +done + +if [[ ${#selected_files[@]} -eq 0 ]]; then + exit 0 +fi + +# format the selected files +for f in "${selected_files[@]}"; do + if [[ "${f}" =~ ${cmake_re} ]]; then + cmake-format -i "${f}" + else + clang-format -i -style=file "${f}" + fi +done + +# restage if changed +if ! git diff --quiet -- "${selected_files[@]}"; then + git add -- "${selected_files[@]}" + cat >&2 <<'EOF' +Formatting hook changed and restaged files. +EOF +fi + +exit 0 diff --git a/bin/setup-hooks.sh b/bin/setup-hooks.sh new file mode 100755 index 00000000..3f7b6322 --- /dev/null +++ b/bin/setup-hooks.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +set -euo pipefail + +if [[ ! -d .git ]]; then + echo "Error: Run this script from the repository's root directory." >&2 + exit 1 +fi + +if [[ ! -x .githooks/pre-commit ]]; then + echo "Error: Expected hook .githooks/pre-commit not found." >&2 + exit 1 +fi + +git config core.hooksPath .githooks +echo "Installed optional formatting hook." + +if ! command -v clang-format >/dev/null 2>&1 || ! command -v cmake-format >/dev/null 2>&1; then + cat >&2 <<'EOF' +Warning: Formatter utilities not found. Install them with your package manager: + Ubuntu / Debian: + sudo apt install clang-format-14 cmake-format + Package manager commands for other distributions may differ. + pip3 install clang-format==14.0.6 cmakelang can be used. + If using pip3, add the pip bin directory to the PATH, e.g. export PATH="$HOME/.local/bin:$PATH". + After installing the formatter utilities, re-run this setup script. +EOF + exit 1 +fi + +echo "clang-format and cmake-format are installed." +