Skip to content

Commit

Permalink
Uncrustify: Faster pre-commit hook to fix code style
Browse files Browse the repository at this point in the history
Run multiple instances of Uncrustify in parallel to speed up fixing the
code style for many files at once.
  • Loading branch information
shoogle committed Apr 10, 2023
1 parent 85c9982 commit 0400985
Show file tree
Hide file tree
Showing 7 changed files with 222 additions and 43 deletions.
14 changes: 11 additions & 3 deletions hooks/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# MuseScore
# Music Composition & Notation
#
# Copyright (C) 2021 MuseScore BVBA and others
# Copyright (C) 2023 MuseScore BVBA and others
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3 as
Expand All @@ -18,6 +18,14 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
HOOKSDIR=./.git/hooks/

cp ./hooks/pre-commit "$HOOKSDIR/pre-commit"
cd "${BASH_SOURCE%/*}/.." # make paths relative to repository root

hooks=(
# Alphabetical order please!
pre-commit
)

for file in "${hooks[@]}"; do
cp "hooks/${file}" ".git/hooks/${file}"
done
151 changes: 124 additions & 27 deletions hooks/pre-commit
Original file line number Diff line number Diff line change
@@ -1,29 +1,126 @@
#!/usr/bin/env bash

echo "Uncrustifying staged files..."

RELPATH=../..
staged=$(git diff --name-only --cached)

cd ./tools/codestyle/
while IFS= read -r file; do
ext="${file##*.}"
if [ $ext != "cpp" ] && [ $ext != "h" ]; then
continue
fi
cp "$RELPATH/$file" "$RELPATH/$file.bak"
if $( ./uncrustify_run_file.sh "$RELPATH/$file" ); then
mv "$RELPATH/$file.uncrustify" "$RELPATH/$file"
rm "$RELPATH/$file.bak"
git add "$RELPATH/$file"
else
echo $?
mv "$RELPATH/$file.bak" "$RELPATH/$file"
rm "$RELPATH/$file.uncrustify"
echo "Uncrustify failed for $file"
fi
done <<< "$staged"

echo "Finished uncrustifying"

exit 0
((${BASH_VERSION%%.*} >= 4)) || { echo >&2 "$0: Error: Please upgrade Bash."; exit 1; }

set -euo pipefail

source "tools/codestyle/globals.source"

function path_under_any_of_dirs()
{
local path="$1" dir
shift
for dir in "$@"; do
if [[ "${path}" == "${dir}/"* ]]; then
return 0
fi
done
return 1
}

# Files with staged changes that are required to follow the coding style.
function get_staged_files()
{
local tidy_globs=() dir ext
for dir in "${TIDY_DIRS[@]}"; do
for ext in "${TIDY_EXTENSIONS[@]}"; do
tidy_globs+=("${dir}/*.${ext}")
done
done

local untidy_dirs=() untidy_path
while IFS= read -r -d '' untidy_path; do
untidy_dirs+=("${untidy_path%/${UNTIDY_FILE}}")
done < <(git ls-files -z -- "*/${UNTIDY_FILE}")

STAGED_FILES=()

local file
while IFS= read -r -d '' file; do
if ! path_under_any_of_dirs "${file}" "${untidy_dirs[@]}"; then
STAGED_FILES+=("${file}")
fi
done < <(git diff -z --name-only --cached -- "${tidy_globs[@]}")

NUM_STAGED="${#STAGED_FILES[@]}"
}

# Files from STAGED_FILES that also have unstaged changes. Beware, if
# STAGED_FILES is empty then this will return all unstaged files.
function get_unstaged_files()
{
UNSTAGED_FILES=()

while IFS= read -r -d '' unstaged_file; do
UNSTAGED_FILES+=("${unstaged_file}")
done < <(git diff -z --name-only -- "${STAGED_FILES[@]}")

NUM_UNSTAGED="${#UNSTAGED_FILES[@]}"
}

exit_status=0

get_staged_files

if ((${NUM_STAGED} == 0)); then
echo >&2 "$0: Nothing to do."
exit 0
fi

get_unstaged_files

echo >&2 "$0: Tidying staged files..."

# Avoid overwriting unstaged changes.
for file in "${UNSTAGED_FILES[@]}"; do
mv "${file}" "${file}.unstaged"
git checkout -- "${file}"
done

# Begin tidying the staged version of each file.
STAGED_PIDS=()
for file in "${STAGED_FILES[@]}"; do
"tools/codestyle/tidy_file.sh" "${file}" & # Run in background. Must not use Git.
STAGED_PIDS+=( $! ) # store process ID
done

# Begin tidying the unstaged versions to reduce diff with staged versions.
UNSTAGED_PIDS=()
for file in "${UNSTAGED_FILES[@]}"; do
"tools/codestyle/tidy_file.sh" "${file}.unstaged" & # Run in background. Must not use Git.
UNSTAGED_PIDS+=( $! ) # store process ID
done

# Once the staged files are tidy, re-stage them.
for idx in "${!STAGED_PIDS[@]}"; do
# Wait for background process to finish
if wait "${STAGED_PIDS[${idx}]}"; then
# Process was successful
file="${STAGED_FILES[${idx}]}"
git add -- "${file}" # Run in foreground to avoid git index conflicts.
else
# Process failed
exit_status=$?
fi
done

# Once the unstaged files are tidy, restore them.
for idx in "${!UNSTAGED_PIDS[@]}"; do
# Wait for background process to finish
if wait "${UNSTAGED_PIDS[${idx}]}"; then
# Process was successful
file="${UNSTAGED_FILES[${idx}]}"
mv "${file}.unstaged" "${file}"
else
# Process failed
exit_status=$?
fi
done

if ((${exit_status} == 0)); then
echo >&2 "$0: Tidying complete!"
else
echo >&2 "$0: Errors occured."
fi

exit ${exit_status}
14 changes: 11 additions & 3 deletions hooks/uninstall.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# MuseScore
# Music Composition & Notation
#
# Copyright (C) 2021 MuseScore BVBA and others
# Copyright (C) 2023 MuseScore BVBA and others
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3 as
Expand All @@ -18,6 +18,14 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
HOOKSDIR=./.git/hooks/

rm "$HOOKSDIR/pre-commit"
cd "${BASH_SOURCE%/*}/.." # make paths relative to repository root

hooks=(
# Alphabetical order please!
pre-commit
)

for file in "${hooks[@]}"; do
rm ".git/hooks/${file}"
done
21 changes: 21 additions & 0 deletions tools/codestyle/globals.source
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Source this file in Bash scripts to determine where to enforce the coding style.

# Files with these extensions follow the coding style.
readonly TIDY_EXTENSIONS=(
# Alphabetical order please!
c
cpp
h
hpp
m
mm
)

# Files in these directories follow the coding style.
readonly TIDY_DIRS=(
# Alphabetical order please!
src
)

# Files in containing directory and subdirs don't follow coding style.
readonly UNTIDY_FILE='.untidy'
48 changes: 48 additions & 0 deletions tools/codestyle/tidy_file.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env bash

((${BASH_VERSION%%.*} >= 4)) || { echo >&2 "$0: Error: Please upgrade Bash."; exit 1; }

set -euo pipefail

# Call the right command to tidy a file based on purely on its extension.
# For best performance, filter the list of files prior to calling this script.

# Editors, don't use `git` in this script as it can cause conflicts reading
# the git index when multiple instances of this script are run in parallel.

HERE="${BASH_SOURCE%/*}" # path to dir that contains this script

function uncrustify_file()
{
local file="$1" lang="$2" status
if ! uncrustify -c "${HERE}/uncrustify_musescore.cfg" --no-backup -l "${lang}" "${file}"; then
status=$?
rm -f "${file}.uncrustify" # remove possible temporary file
return ${status}
fi
}

if (($# != 1)); then
echo >&2 "$0: Error: Too many arguments. Please specify a single file."
exit 255 # make xargs exit early and ensure non-zero status on macOS
fi

file="$1"
base="${file%.unstaged}" # remove possible '.unstaged' extension (see hooks/pre-commit)
ext="${base##*.}"

set +e
case "${ext,,}" in
c) uncrustify_file "${file}" 'CPP' ;;
h|cpp|hpp) uncrustify_file "${file}" 'CPP' ;;
m) uncrustify_file "${file}" 'OC' ;;
mm) uncrustify_file "${file}" 'OC+' ;;
*) echo >&2 "Skipping: ${file}" ;;
esac
status=$?
set -e

if ((${status} != 0)); then
echo >&2 "$0: Error ${status} for ${file}"
exit 255 # make xargs exit early and ensure non-zero status on macOS
fi
8 changes: 2 additions & 6 deletions tools/codestyle/uncrustify_run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,11 @@ cd "${BASH_SOURCE%/*}/../.."

HERE="tools/codestyle" # path to dir that contains this script

SRC_DIRS=(
# Alphabetical order please!
src
)
source "${HERE}/globals.source"

START_TIME=$(date +%s)

for dir in "${SRC_DIRS[@]}"
do
for dir in "${TIDY_DIRS[@]}"; do
"${HERE}/uncrustify_run_dir.sh" "${dir}"
done

Expand Down
9 changes: 5 additions & 4 deletions tools/codestyle/uncrustify_run_dir.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
HERE="$(dirname ${BASH_SOURCE[0]})"
DIR="${1-.}" # use $1 or "." (current dir) if $1 is not defined

IGNORE_FILE=.untidy
source "${HERE}/globals.source"

SCAN_BIN_DIR=""

if [[ "$OSTYPE" == "linux-gnu"* ]]; then
Expand All @@ -42,11 +43,11 @@ SCAN_BIN=$HERE/scan_files/bin/${SCAN_BIN_DIR}/scan_files

START_TIME=$(date +%s)

$SCAN_BIN -d $DIR -i $IGNORE_FILE -e cpp,c,cc,hpp,h | xargs -n 1 -P 16 uncrustify -c "${HERE}/uncrustify_musescore.cfg" --no-backup -l CPP
$SCAN_BIN -d $DIR -i $IGNORE_FILE -e mm | xargs -n 1 -P 16 uncrustify -c "${HERE}/uncrustify_musescore.cfg" --no-backup -l OC+
$SCAN_BIN -d $DIR -i $UNTIDY_FILE -e cpp,c,cc,hpp,h | xargs -n 1 -P 16 uncrustify -c "${HERE}/uncrustify_musescore.cfg" --no-backup -l CPP
$SCAN_BIN -d $DIR -i $UNTIDY_FILE -e mm | xargs -n 1 -P 16 uncrustify -c "${HERE}/uncrustify_musescore.cfg" --no-backup -l OC+

END_TIME=$(date +%s)
DIFF_TIME=$(( $END_TIME - $START_TIME ))
echo ""
echo "time: $DIFF_TIME sec, complete: $DIR"
echo ""
echo ""

0 comments on commit 0400985

Please sign in to comment.