Skip to content

Commit

Permalink
pre-commit, commit-msg hooks. format-code script. (#11)
Browse files Browse the repository at this point in the history
Require that code be formatted prior to git commit.
Based on the same scripts as in OE SDK repository.

Signed-off-by: Anand Krishnamoorthi <[email protected]>
  • Loading branch information
anakrish authored Jun 3, 2020
1 parent d88d64c commit 24fc634
Show file tree
Hide file tree
Showing 4 changed files with 376 additions and 0 deletions.
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ cmake_minimum_required(VERSION 3.1)

project(oeedger8r)

# Collect Git info
if (IS_DIRECTORY "${PROJECT_SOURCE_DIR}/.git")
# Install Git pre-commit hook
file(COPY scripts/pre-commit scripts/commit-msg
DESTINATION "${PROJECT_SOURCE_DIR}/.git/hooks")
endif ()

set(CMAKE_CXX_STANDARD 11)
add_executable(oeedger8r main.cpp parser.cpp lexer.cpp)
set_property(TARGET oeedger8r PROPERTY POSITION_INDEPENDENT_CODE on)
Expand Down
25 changes: 25 additions & 0 deletions scripts/commit-msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env bash

# Copyright (c) Open Enclave SDK contributors.
# Licensed under the MIT License.

set -o errexit

exit_() {
echo ""
echo "$1"
echo ""
echo "This hook can be skipped if needed with 'git commit --no-verify'"
echo "See '.git/hooks/commit-msg', installed from 'scripts/commit-msg'"
exit 1
}

sign_offs="$(grep '^Signed-off-by: ' "$1" || test $? = 1 )"

if [[ -z $sign_offs ]]; then
exit_ "Commit failed: please sign-off on the DCO with 'git commit -s'"
fi

if [[ -n $(echo "$sign_offs" | sort | uniq -c | sed -e '/^[ ]*1[ ]/d') ]]; then
exit_ "Commit failed: please remove duplicate Signed-off-by lines"
fi
303 changes: 303 additions & 0 deletions scripts/format-code
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
#!/usr/bin/env bash

# Copyright (c) Open Enclave SDK contributors.
# Licensed under the MIT License.

##==============================================================================
##
## Echo if verbose flag (ignores quiet flag)
##
##==============================================================================
log_verbose()
{
if [[ ${verbose} -eq 1 ]]; then
echo "$1"
fi
}

##==============================================================================
##
## Echo if whatif flag is specified but not quiet flag
##
##==============================================================================
log_whatif()
{
if [[ ${whatif} -eq 1 && ${quiet} -ne 1 ]]; then
echo "$1"
fi
}

##==============================================================================
##
## Process command-line options:
##
##==============================================================================

for opt in "$@"
do
case $opt in
-h | --help)
usage=1
;;

-v | --verbose)
verbose=1
;;

-q | --quiet)
quiet=1
;;

-w | --whatif)
whatif=1
;;

-s | --staged)
# Split into array
mapfile -t userFiles <<< "$(git diff --cached --name-only --diff-filter=ACMR)"
;;

--exclude-dirs=*)
userExcludeDirs="${opt#*=}"
;;

--include-exts=*)
userIncludeExts="${opt#*=}"
;;

--files=*)
read -ra userFiles <<< "${opt#*=}"
;;
*)
echo "$0: unknown option: $opt"
exit 1
;;
esac
done

##==============================================================================
##
## Display help
##
##==============================================================================

if [[ ${usage} -eq 1 ]]; then
cat<<EOF
OVERVIEW:
Formats all C/C++ source files based on the .clang-format rules
$ format-code [-h] [-q] [-s] [-v] [-w] [--exclude-dirs="..."] [--include-exts="..."] [--files="..."]
OPTIONS:
-h, --help Print this help message.
-q, --quiet Display only clang-format output and errors.
-s, --staged Only format files which are staged to be committed.
-v, --verbose Display verbose output.
-w, --whatif Run the script without actually modifying the files
and display the diff of expected changes, if any.
--exclude-dirs Subdirectories to exclude. If unspecified, then
./3rdparty, ./build and ./prereqs are excluded.
All subdirectories are relative to the current path.
--include-exts File extensions to include for formatting. If
unspecified, then *.h, *.c and *.cpp are included.
--files Only run the script against the specified files from
the current directory.
EXAMPLES:
To determine what lines of each file in the default configuration would be
modified by format-code, you can run from the root folder:
$ ./scripts/format-code -w
To update only all .c and .cpp files in tests/ except for tests/echo/host, you
can run from the tests folder:
tests$ ../scripts/format-code --exclude-dirs="echo/host" \
--include-exts="c cpp"
To run only against a specified set of comma separated files in the current directory:
$ ./scripts/format-code -w --files="file1 file2"
EOF
exit 0
fi

##==============================================================================
##
## Check for acceptable version of clang-format
##
##==============================================================================
check_version()
{
# shellcheck disable=SC2206
v1=(${1//./ })
# shellcheck disable=SC2206
v2=(${2//./ })
if [[ ${#v1[@]} -ne ${#v2[@]} ]]; then
echo "Cannot parse clang-format version number: $2"
exit 1
fi
for ((i=0; i<${#v1[@]}; i++));
do
# Because clang-format is not invariant across versions, this
# is an explicit equivalence check.
if [[ ${v1[$i]} -ne ${v2[$i]} ]]; then
echo "Warning: format-code prefers clang-format version $1, installed version is $2"
fi
done
}

##==============================================================================
##
## Check for installed clang-format tool
##
##==============================================================================
check_clang-format()
{
# First check explicitly for the clang-format-7 executable.
cf=$(command -v clang-format-7 2> /dev/null)

if [[ -x ${cf} ]]; then
cf="clang-format-7"
return
else
cf=$(command -v clang-format 2> /dev/null)
if [[ ! -x ${cf} ]]; then
echo "clang-format is not installed"
exit 1
fi
cf="clang-format"
fi

local required_cfver='7.0.1'
# shellcheck disable=SC2155
local cfver=$(${cf} --version | grep -o -E '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
check_version "${required_cfver}" "${cfver}"
}

check_clang-format

##==============================================================================
##
## Determine parameters for finding files to format
##
##==============================================================================

findargs='find .'

get_find_args()
{
local defaultExcludeDirs='./.git ./3rdparty ./prereqs ./build *.cquery_cached_index'
local defaultIncludeExts='h c cpp'

if [[ -z ${userExcludeDirs} ]]; then
read -r -a excludeDirs <<< "${defaultExcludeDirs}"
else
log_verbose "Using user directory exclusions: ${userExcludeDirs}"
read -r -a excludeDirs <<< "${userExcludeDirs}"
fi

for exc in "${excludeDirs[@]}"
do
findargs="${findargs} ! \( -path '${exc}' -prune \)"
done

if [[ -z ${userIncludeExts} ]]; then
# not local as this is used in get_file_list() too
read -r -a includeExts <<< "${defaultIncludeExts}"
else
log_verbose "Using user extension inclusions: ${userIncludeExts}"
read -r -a includeExts <<< "${userIncludeExts}"
fi

findargs="${findargs} \("
for ((i=0; i<${#includeExts[@]}; i++));
do
findargs="${findargs} -iname '*.${includeExts[$i]}'"
if [[ $((i + 1)) -lt ${#includeExts[@]} ]]; then
findargs="${findargs} -o"
fi
done
findargs="${findargs} \)"
}

get_find_args

log_verbose "Query for files for format:"
log_verbose "${findargs}"
log_verbose ""

##==============================================================================
##
## Call clang-format for each file to be formatted
##
##==============================================================================

filecount=0
changecount=0

cfargs="${cf} -style=file"
if [[ ${whatif} -ne 1 ]]; then
cfargs="${cfargs} -i"
fi

get_file_list()
{
if [[ -z "${userFiles[*]}" ]]; then
mapfile -t file_list < <(eval "$findargs")
if [[ ${#file_list[@]} -eq 0 ]]; then
echo "No files were found to format!"
exit 1
fi
else
log_verbose "Using user files: ${userFiles[*]}"
file_list=()
for file in "${userFiles[@]}"; do
for ext in "${includeExts[@]}"; do
if [[ ${file##*\.} == "$ext" ]]; then
file_list+=( "$file" )
log_verbose "Checking user file: ${file}"
break
fi
done
done
fi
}

get_file_list

for file in "${file_list[@]}"
do
((filecount+=1))
cf="${cfargs} $file"

log_whatif "Formatting $file ..."

if [[ ${whatif} -eq 1 ]]; then
${cf} | diff -u "$file" -
else
if [[ ${verbose} -eq 1 ]]; then
${cf}
else
${cf} > /dev/null
fi
fi

#shellcheck disable=SC2181
if [[ $? -ne 0 ]]; then
if [[ ${whatif} -eq 1 ]]; then
((changecount+=1))
else
echo "clang-format failed on file: $file."
fi
fi
done

log_whatif "${filecount} files processed, ${changecount} changed."

# If files are being edited, this count is zero so we exit with success.
exit "$changecount"
41 changes: 41 additions & 0 deletions scripts/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env bash

# Copyright (c) Open Enclave SDK contributors.
# Licensed under the MIT License.

set -o errexit

exit_() {
echo ""
echo "$1"
echo ""
exit 1
}

if [[ $(git config --get user.name | wc -w) -lt 2 ]]; then
# This heuristic avoids bad user names such as "root" or "Ubuntu"
# or a computer login name. A full name should (usually) have at
# least two words. We can change this if needed later.
exit_ "Commit failed: please fix your Git user name (see docs/Contributing.md)"
fi

if ! git diff-index --check --cached HEAD --; then
exit_ "Commit failed: please fix the conflict markers or whitespace errors"
fi

mapfile -t files < <(git diff --cached --name-only --diff-filter=ACMR)

if [[ ${#files[@]} -eq 0 ]]; then
# When 'git commit --amend' is used, the files list is empty. The
# scripts below interpret an empty file set as a directive to
# check all the files, which is slow (but used in CI). So in this
# Git hook, we just skip the following checks instead.
exit 0
fi

scripts=$(git rev-parse --show-toplevel)/scripts

# shellcheck disable=SC2154
if ! "$scripts/format-code" --quiet --whatif --files="${files[*]}"; then
exit_ "Commit failed: please run './scripts/format-code --staged' to fix the formatting"
fi

0 comments on commit 24fc634

Please sign in to comment.