Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,30 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) (as of version 0.1.0).

## [0.4.0] `2026-05-11`

### Added
- Add new scripts (checks) for the following: eslint, prettier, tsc and vite.
- Add new lib functions add_about_info, split_about_info.
- Add a template script file (including comments) which can be used to create new scripts.

### Changed
- Improve existing scripts (checks).
- Make more dynamic the addition/removal of checks.
- Make dynamic the `about` section as well, by moving the logic from config.sh and gitcc to each one of the scripts.
- Rename `scripts/examples` directory to `scripts/prepared`
- Improve the extract_version lib function.
- Update the README.md file.

### Fixed
- Several bug fixes.


## [0.3.0] `2026-05-08`

### Changed
- Adjusted the gitcc command to run properly when the git commit is run from the host machine (no container found).
- Moved the example scripts into the scripts/examples directory.
- Adjust the gitcc command to run properly when the git commit is run from the host machine (no container found).
- Move the example scripts into the scripts/examples directory.


## [0.2.0] `2026-05-07`
Expand All @@ -26,6 +45,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Rename main script to gitcc.
- Optimize for performance.


## [0.1.0] `2025-04-20`

### Added
Expand Down
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

---

- Download the git-commit-check directory and place it within your repo (e.g. `path/to/your/repo/bin/git-commit-check`)
- Download the git-commit-check directory and place it within your repo.

- Run the following commands to copy the pre-commit hook script example and the config example files
and edit their values according to your project.
Expand All @@ -15,8 +15,21 @@ cd path/to/your/repo
cp bin/git-commit-check/hooks/pre-commit.example bin/git-commit-check/hooks/pre-commit
cp bin/git-commit-check/config.sh.example bin/git-commit-check/config.sh
```
- Run the following command to instruct git to call `gitcc` before allowing the developer to create a new commit.
- Edit the config.sh file to set the values for the checks you want to run.
- Set the ENABLED variable with the ',' separated script IDs that you want to enable.
- Create your own new script and add it to the `scripts` directory. Script naming conventions:
1. Prepend a two-digit script ID to the script name.
2. Append the .sh extension to the script name.
3. Copy the contents of the `scripts/template` script into the new script file.
4. Adjust the values in the script file to suit your needs, as described in the comments of the template script.
5. Try to keep your scripts POSIX-compliant so that they will work in all (most) SHELL. Check online here: https://www.shellcheck.net
- Alternatively, you may copy one of the existing scripts under the `scripts/examples` directory into the `scripts` directory.
- Make the pre-commit hook executable. Run the following command to instruct git to call `gitcc` before allowing the developer to create a new commit.

```
git config core.hooksPath bin/git-commit-check/hooks`
```

- Run `git commit` to test the pre-commit hook.

- Done!
35 changes: 11 additions & 24 deletions config.sh.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,50 +7,37 @@ GITCC_SCRIPTS_PATH="$GITCC_PATH/scripts"

cd "$PROJECT_PATH" || exit

# [script IDs] - Include the ids of those scripts that should be executed.
ENABLED="01,02,03,04,05"

# [about]
BACKEND_SYNTAX_VERSION=$(php -r 'echo phpversion();')
BACKEND_SYNTAX_TOOL="PHP"
FRAMEWORK_VERSION=$(php artisan --version)
FRAMEWORK_TOOL="Laravel"
BACKEND_STANDARDS_VERSION=$(./vendor/bin/phpcs '--version')
BACKEND_STANDARDS_TOOL="CodeSniffer"
BACKEND_TESTS_VERSION=$(./vendor/bin/phpunit '--version')
BACKEND_TESTS_TOOL="PHPUnit"
BACKEND_DEPENDENCIES_VERSION=$(composer '--version' 2>&1 | head -n 1)
BACKEND_DEPENDENCIES_TOOL="Composer"
FRONTEND_DEPENDENCIES_VERSION=$(node --version)
FRONTEND_DEPENDENCIES_TOOL="Node"
FRONTEND_DEPENDENCIES_VERSION_2=$(npm --version)
FRONTEND_DEPENDENCIES_TOOL_2="NPM"
# [script IDs] - Set the ids of those scripts that should be executed. To disable all scripts, set an empty string.
ENABLED="01,02,03,04,05,06,07,08,09"

# [interface]
COMPACT=0
SHOW_OUTPUT=1
SHOW_ABOUT=1
SHOW_LOG_URL=1
WIDTH=93
RED="\e[0;31m"
BRED="\e[1;31m" #Bold red
BRED="\e[1;31m" # Bold red.
GREEN="\e[0;32m"
YELLOW="\e[0;33m"
CYAN="\e[0;36m"
BOLD="\e[1m"
NC="\e[0m" #No color/modifier
NC="\e[0m" # No color/modifier.
SECTION_SYMBOL=""
HEADER_SYMBOL=➡️
MESSAGE_SYMBOL=""
LOG_SYMBOL=📋
TIME_SYMBOL=🕒
LOAD_SYMBOL=⌛
PASS_SYMBOL=🟢
#PASS_SYMBOL=🔵
WARN_SYMBOL=🟡
#WARN_SYMBOL=🟠
FAIL_SYMBOL=🔴

# [log]
LOG=1
LOG_FILE="./path/to/log-file"
LOG_FORMAT="[%s] local.%s: %s\nMessage: %s" # datetime, log level, header, message

# [log-viewer]
LOG_VIEWER=1
LOG_VIEWER_URL="https://www.example.com"
# [log link]
LOG_URL="https://www.example.com/logs"
69 changes: 31 additions & 38 deletions gitcc
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/sh
# Git Commit Check (gitcc)
# Version: 0.3.0
# Version: 0.4.0
# Author: Angelos Theodorakopoulos <angtheod@gmail.com>

# Change to the directory of the script and source the required scripts.
Expand All @@ -9,67 +9,60 @@ cd "$(dirname "$0")" || exit 1
cd "$(dirname "$0")" || exit 1
. ./lib.sh

init() {
START=$(date +%s)
HAS_ERRORS=0
HAS_SYNTAX_ERRORS=0
}

header() {
line3 "╔" "╗" $((WIDTH+8)) "═"
printf " %s${CYAN}Load\n" "$SECTION_SYMBOL"
line "Configuration" "${1:-./config.sh}"
}

if [ "$COMPACT" = 0 ]; then
printf " %s${CYAN}Load\n" "$SECTION_SYMBOL"
line "Configuration" "${1:-./config.sh}"
fi
check() {
[ -n "$ENABLED" ] && printf "\n %s${CYAN}Check${NC}\n" "$SECTION_SYMBOL"

for script in $(find "$GITCC_SCRIPTS_PATH" -type f -name '*.sh' | sort); do
_name=$(basename "$script")
_id="${_name%%_*}"
# Check if the script is enabled in the configuration and then source the script
# so that it runs in the same shell process and the variables are accessible within it.
if contains_string "$_id" "$ENABLED"; then
. "$script"
fi
done
}

about() {
[ "$COMPACT" = 1 ] && return
[ "$SHOW_ABOUT" = 0 ] && return

printf "\n %s${CYAN}About\n" "$SECTION_SYMBOL"

line "$BACKEND_SYNTAX_TOOL" "$BACKEND_SYNTAX_VERSION"
line "$FRAMEWORK_TOOL" "$(extract_version "$FRAMEWORK_VERSION")"
line "$BACKEND_STANDARDS_TOOL" "$(extract_version "$BACKEND_STANDARDS_VERSION")"
line "$BACKEND_TESTS_TOOL" "$(extract_version "$BACKEND_TESTS_VERSION")"
line "$BACKEND_DEPENDENCIES_TOOL" "$(extract_version "$BACKEND_DEPENDENCIES_VERSION")"
line "$FRONTEND_DEPENDENCIES_TOOL" "$(extract_version "$FRONTEND_DEPENDENCIES_VERSION")"
line "$FRONTEND_DEPENDENCIES_TOOL_2" "$(extract_version "$FRONTEND_DEPENDENCIES_VERSION_2")"
split_about_info | while IFS='|' read -r left right; do
line "$left" "$(extract_version "$right")"
done
}

footer() {
line3 "╚" "╝" $((WIDTH+8)) "═"

END=$(date +%s)
time=$((END-START))
[ $LOG_VIEWER = 1 ] && line3 "$LOG_SYMBOL $LOG_VIEWER_URL" "$TIME_SYMBOL ${time}sec" "$((WIDTH+7))" " "
}

check() {
[ "$COMPACT" = 0 ] && [ "$ENABLED" != "" ] && printf "\n %s${CYAN}Check${NC}\n" "$SECTION_SYMBOL"
[ "$LOG" = 1 ] && log info "══════════════" ""

# Directly run all scripts (without any extra logic such as contains_string).
#[ "$ENABLED" != "" ] && find "$GITCC_PATH/scripts" -type f -name '*.sh' | sort -h | xargs -L 1 -d '\n' sh;

if [ "$ENABLED" != "" ]; then
for script in $(find "$GITCC_SCRIPTS_PATH" -type f -name '*.sh' | sort); do
_name=$(basename "$script")
_id="${_name%%_*}"
# Check if the script is enabled in the configuration and then
# source the script so that it runs in the same shell process and the variables are accessible within it.
contains_string "$_id" "$ENABLED" && . "$script"
done
if [ "$SHOW_LOG_URL" -eq 1 ]; then
line3 "$LOG_SYMBOL $LOG_URL" "$TIME_SYMBOL ${time}sec" "$((WIDTH+7))" " "
else
line3 "" "$TIME_SYMBOL ${time}sec" "$((WIDTH+7))" " "
fi
}

init() {
START=$(date +%s)
HAS_ERRORS=0
STAGED_PHP_FILES=$(git diff --cached --name-only --diff-filter=ACMR HEAD | grep .php)
HAS_SYNTAX_ERRORS=0
}

main() {
init
header
about
check
about
footer

if [ "$HAS_ERRORS" -ne 0 ]; then
Expand Down
8 changes: 4 additions & 4 deletions hooks/pre-commit.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
# To configure git to run this script before each commit, run `git config core.hooksPath hooks`

# [pre-commit]
PROJECT_PATH="/path/to/project" # Absolute path to the project, e.g. /var/www/my-project
GITCC_PATH="path/to/gitcc" # Relative path to the project, e.g. "bin/git-commit-check"
PROJECT_PATH="/path/to/project" # Absolute path to the project, e.g. /var/www/my-project
GITCC_PATH="path/to/gitcc" # Relative path to the project, e.g. "bin/git-commit-check"
CONTAINER_ENGINE=$(which docker 2>/dev/null || which podman 2>/dev/null)
CONTAINER_NAME="container_name"
SHELL="/bin/sh"
COMMAND="$PROJECT_PATH/$GITCC_PATH/gitcc"
COMMAND="$GITCC_PATH/gitcc"

if [ -z "$CONTAINER_ENGINE" ]; then
eval "$COMMAND"
$COMMAND
else
$CONTAINER_ENGINE exec -tw "$PROJECT_PATH" "$CONTAINER_NAME" "$SHELL" -c "$COMMAND"
fi
59 changes: 42 additions & 17 deletions lib.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#!/bin/sh
cd "$PROJECT_PATH" || exit

ABOUT=""
DELIMITER="<>"

line() {
left="$1"
right="$2"
Expand Down Expand Up @@ -30,6 +33,24 @@ line3() {
printf "%s\n" "$right"
}

# Concatenate the Left/Right strings to the about section data.
add_about_info() {
ABOUT_LEFT="$1"
ABOUT_RIGHT="$2"

if [ -z "$ABOUT" ]; then
ABOUT="$ABOUT_LEFT|$ABOUT_RIGHT"
else
ABOUT="$ABOUT$DELIMITER$ABOUT_LEFT|$ABOUT_RIGHT"
fi
}

# Split the about section data into strings that can be used to generate a new line in the about section.
split_about_info() {
printf '%s\n' "$ABOUT" | tr -s "$DELIMITER" '\n'
}

# Return 1 if the 2nd argument contains the 1st argument. Return 0 otherwise.
contains_string() {
_string="$2"
_substring="$1"
Expand All @@ -44,23 +65,27 @@ contains_string() {
esac
}

# Extract the version of an application from a command output.
extract_version() {
if [ $# -eq 0 ]; then
printf "(Unknown)\n"
return 1
fi

version=$(printf "%s" "$1" | sed -n '
# Match either:
# 1. A "v" followed by version numbers after non-digit
# 2. Version numbers preceded by non-digit
# 3. Version numbers at string start
s/^.*[^0-9]v\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*$/\1/p
t
s/^.*[^0-9]\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*$/\1/p
t
s/^v\{0,1\}\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*$/\1/p
')
# Match either:
# 1. Version numbers preceded by /
# 2. A "v" followed by version numbers after non-digit
# 3. Version numbers preceded by non-digit
# 4. Version numbers at string start
s/^.*\/\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*$/\1/p
t
s/^.*[^0-9]v\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*$/\1/p
t
s/^.*[^0-9]\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*$/\1/p
t
s/^v\{0,1\}\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*$/\1/p
')

if [ -z "$version" ]; then
printf "(Unknown)\n"
Expand All @@ -70,8 +95,7 @@ extract_version() {
printf "%s" "$version"
}

# Fills a line in the terminal with $4 chars until it reaches $WIDTH in length
# (including the left and right side lengths).
# Fill a line in the terminal with $4 chars until it reaches $WIDTH in length (including the left and right side).
fill() {
left="$1"
right="$2"
Expand All @@ -88,6 +112,7 @@ fill() {
done
}

# Parse the output of npm audit and provide concise information.
parse_npm_audit() {
if [ $# -eq 0 ]; then
printf "Error: No input file specified"
Expand All @@ -104,7 +129,7 @@ parse_npm_audit() {
total=$(printf '%s' "$auditFile" | jq -r .metadata.vulnerabilities.total)
}

# Creates a Log entry according to config value LOG_FORMAT and redirects to config value LOG_FILE
# Create a Log file entry according to config value LOG_FORMAT and redirects to config value LOG_FILE
log() {
level="$1"
header="$2"
Expand All @@ -123,8 +148,6 @@ log() {
dateTime=$(date "+%Y-%m-%d %H:%M:%S")

logEntry=$(printf "$LOG_FORMAT" "$dateTime" "${level}" "${header}" "$message")
# logEntry="[${dateTime}] local.${level}: ${header}
# Message: ${message}"

# Pass log output from sed to remove control chars and color codes
printf "%s\n" "$(printf "%s" "$logEntry" | sed -e "s/\x1b\[.\{1,5\}m//g")" >> "$logFile"
Expand All @@ -135,18 +158,20 @@ message() {
titleOrText="$2"
text="$3"

if [ "$#" -eq 2 ] && [ "$COMPACT" = 0 ]; then
if [ "$#" -eq 2 ] && [ "$SHOW_OUTPUT" = 1 ]; then
printf " %s\n" "$titleOrText"
fi

if [ "$#" -eq 3 ]; then
[ "$COMPACT" = 0 ] && printf " $text\n"
[ "$SHOW_OUTPUT" = 1 ] && printf " $text\n"
[ "$LOG" = 1 ] && log "$level" "$titleOrText" "$text"
fi
}

pass() {
sek=$((sek-1))
printf "\b%s\n" "$PASS_SYMBOL"
[ -n "$1" ] && message info "${1}" "${2}"
}

warn() {
Expand Down
Loading