Skip to content

Commit

Permalink
Export ${STAGED_FILES} for pre-commit hooks ⭐
Browse files Browse the repository at this point in the history
  • Loading branch information
rycus86 committed Nov 8, 2018
1 parent 993ec41 commit 2efbdcb
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 6 deletions.
9 changes: 9 additions & 0 deletions .githooks/pre-commit/list-staged-files
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/sh
# shellcheck disable=SC2153
if [ -n "${STAGED_FILES}" ]; then
echo "* Staged files:"

for STAGED_FILE in $STAGED_FILES; do
echo " - $STAGED_FILE"
done
fi
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,24 @@ Take this snippet of a project layout as an example:
└── ...
```

All hooks to be executed live under the `.githooks` top-level folder, that should be checked into the repository. Inside, we can have directories with the name of the hook (like `commit-msg` and `pre-commit` above), or a file matching the hook name (like `post-checkout` in the example). The filenames in the directory do not matter, but the ones starting with a `.` will be excluded by default. All others are executed in alphabetical order according to the [glob / LC_COLLATE](http://pubs.opengroup.org/onlinepubs/007908775/xsh/glob.html) rules. If a file is executable, it is directly invoked, otherwise it is interpreted with the `sh` shell. All parameters of the hook are passed to each of the scripts. You can use the [command line helper](https://github.com/rycus86/githooks/blob/master/docs/command-line-tool.md) tool as `git hooks list` to list all the hooks that apply to the current repository and their current state.
All hooks to be executed live under the `.githooks` top-level folder, that should be checked into the repository. Inside, we can have directories with the name of the hook (like `commit-msg` and `pre-commit` above), or a file matching the hook name (like `post-checkout` in the example). The filenames in the directory do not matter, but the ones starting with a `.` will be excluded by default. All others are executed in alphabetical order according to the [glob / LC_COLLATE](http://pubs.opengroup.org/onlinepubs/007908775/xsh/glob.html) rules. You can use the [command line helper](https://github.com/rycus86/githooks/blob/master/docs/command-line-tool.md) tool as `git hooks list` to list all the hooks that apply to the current repository and their current state.

## Execution

If a file is executable, it is directly invoked, otherwise it is interpreted with the `sh` shell. All parameters of the hook are passed to each of the scripts.

Hooks related to `commit` events will also have a `${STAGED_FILES}` environment variable set, that is the list of staged and changed files (according to `git diff --cached --diff-filter=ACMR --name-only`), one per line and where it makes sense (not `post-commit`). If you want to iterate over them, and expect spaces in paths, you might want to set `IFS` like this.

```shell
IFS="
"

for STAGED in ${STAGED_FILES}; do
...
done
```

The `ACMR` filter in the `git diff` will include staged files that are added, copied, modified or renamed.

## Supported hooks

Expand Down
26 changes: 25 additions & 1 deletion base-template.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# It allows you to have a .githooks folder per-project that contains
# its hooks to execute on various Git triggers.
#
# Version: 1810.171225-1d5d29
# Version: 1811.081158-1d386f

#####################################################
# Execute the current hook,
Expand All @@ -16,6 +16,7 @@
process_git_hook() {
are_githooks_disabled && return 0
set_main_variables
export_staged_files
check_for_updates_if_needed
execute_old_hook_if_available "$@" || return 1
execute_global_shared_hooks "$@" || return 1
Expand Down Expand Up @@ -60,6 +61,29 @@ set_main_variables() {
ACCEPT_CHANGES=
}

#####################################################
# Exports the list of staged, changed files
# when available, so hooks can use it if
# they want to.
#
# Sets the ${STAGED_FILES} variable
#
# Returns:
# None
#####################################################
export_staged_files() {
if ! echo "pre-commit prepare-commit-msg commit-msg" | grep -q "$HOOK_NAME" 2>/dev/null; then
return # we only want to do this for commit related events
fi

CHANGED_FILES=$(git diff --cached --diff-filter=ACMR --name-only)

# shellcheck disable=2181
if [ $? -eq 0 ]; then
export STAGED_FILES="$CHANGED_FILES"
fi
}

#####################################################
# Executes the old hook if we moved one
# while installing our hooks.
Expand Down
2 changes: 1 addition & 1 deletion cli.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# See the documentation in the project README for more information,
# or run the `git hooks help` command for available options.
#
# Version: 1810.171225-1d5d29
# Version: 1811.081158-1d386f

#####################################################
# Prints the command line help for usage and
Expand Down
30 changes: 27 additions & 3 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# and performs some optional setup for existing repositories.
# See the documentation in the project README for more information.
#
# Version: 1810.171225-1d5d29
# Version: 1811.081158-1d386f

# The list of hooks we can manage with this script
MANAGED_HOOK_NAMES="
Expand All @@ -23,7 +23,7 @@ BASE_TEMPLATE_CONTENT='#!/bin/sh
# It allows you to have a .githooks folder per-project that contains
# its hooks to execute on various Git triggers.
#
# Version: 1810.171225-1d5d29
# Version: 1811.081158-1d386f
#####################################################
# Execute the current hook,
Expand All @@ -35,6 +35,7 @@ BASE_TEMPLATE_CONTENT='#!/bin/sh
process_git_hook() {
are_githooks_disabled && return 0
set_main_variables
export_staged_files
check_for_updates_if_needed
execute_old_hook_if_available "$@" || return 1
execute_global_shared_hooks "$@" || return 1
Expand Down Expand Up @@ -79,6 +80,29 @@ set_main_variables() {
ACCEPT_CHANGES=
}
#####################################################
# Exports the list of staged, changed files
# when available, so hooks can use it if
# they want to.
#
# Sets the ${STAGED_FILES} variable
#
# Returns:
# None
#####################################################
export_staged_files() {
if ! echo "pre-commit prepare-commit-msg commit-msg" | grep -q "$HOOK_NAME" 2>/dev/null; then
return # we only want to do this for commit related events
fi
CHANGED_FILES=$(git diff --cached --diff-filter=ACMR --name-only)
# shellcheck disable=2181
if [ $? -eq 0 ]; then
export STAGED_FILES="$CHANGED_FILES"
fi
}
#####################################################
# Executes the old hook if we moved one
# while installing our hooks.
Expand Down Expand Up @@ -646,7 +670,7 @@ CLI_TOOL_CONTENT='#!/bin/sh
# See the documentation in the project README for more information,
# or run the `git hooks help` command for available options.
#
# Version: 1810.171225-1d5d29
# Version: 1811.081158-1d386f
#####################################################
# Prints the command line help for usage and
Expand Down
36 changes: 36 additions & 0 deletions tests/step-095.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/bin/sh
# Test:
# Direct template execution: list of staged files (simple)

mkdir -p /tmp/test095/.githooks/pre-commit &&
cd /tmp/test095 && git init ||
exit 1

echo "Test" >>sample.txt
echo "Test" >>second.txt

cat <<EOF >.githooks/pre-commit/print-changes
for STAGED in \${STAGED_FILES}; do
echo "staged: \${STAGED}" >> /tmp/test095.out
done
EOF

git add sample.txt second.txt

HOOK_NAME=pre-commit HOOK_FOLDER=$(pwd)/.git/hooks ACCEPT_CHANGES=A \
sh /var/lib/githooks/base-template.sh

if ! grep 'staged: sample.txt' /tmp/test095.out; then
echo "! Failed to find expected output (1)"
exit 1
fi

if ! grep 'staged: second.txt' /tmp/test095.out; then
echo "! Failed to find expected output (2)"
exit 1
fi

if grep -vE '(sample|second)\.txt' /tmp/test095.out; then
echo "! Unexpected additional output"
exit 1
fi
36 changes: 36 additions & 0 deletions tests/step-096.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/bin/sh
# Test:
# Direct template execution: list of staged files (special paths)

mkdir -p /tmp/test096/.githooks/pre-commit &&
cd /tmp/test096 && git init ||
exit 1

mkdir -p sub/folder\ with\ space/x
printf "Test" >>sub/test.txt
printf "Test" >>sub/folder\ with\ space/test.txt
printf "Test" >>sub/folder\ with\ space/x/test.txt
printf "Test" >>file\ with\ spaces.txt

cat <<EOF >.githooks/pre-commit/print-changes
IFS="
"
for STAGED in \${STAGED_FILES}; do
echo ">\${STAGED}< "\$(cat "\${STAGED}" | wc -c) >> /tmp/test096.out
done
EOF

git add sub f*

HOOK_NAME=pre-commit HOOK_FOLDER=$(pwd)/.git/hooks ACCEPT_CHANGES=A \
sh /var/lib/githooks/base-template.sh

if [ "$(wc -l </tmp/test096.out)" != "4" ]; then
echo "! Unexpected number of output rows"
exit 1
fi

if ! grep '>sub/folder with space/x/test.txt< 4' /tmp/test096.out; then
echo "! Failed to find expected output"
exit 1
fi
47 changes: 47 additions & 0 deletions tests/step-097.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/bin/sh
# Test:
# Direct template execution: list of staged files (hook types)

MANAGED_HOOK_NAMES="
applypatch-msg pre-applypatch post-applypatch
pre-commit prepare-commit-msg commit-msg post-commit
pre-rebase post-checkout post-merge pre-push
pre-receive update post-receive post-update
push-to-checkout pre-auto-gc post-rewrite sendemail-validate
"

mkdir -p /tmp/test097/.git/hooks &&
cd /tmp/test097 &&
git init &&
sh /var/lib/githooks/install.sh --single &&
git config githooks.autoupdate.enabled N ||
exit 1

for HOOK_TYPE in ${MANAGED_HOOK_NAMES}; do
mkdir -p ".githooks/${HOOK_TYPE}" || exit 1

cat <<EOF >".githooks/${HOOK_TYPE}/${HOOK_TYPE}" || exit 1
printf "hook=\$(basename \$0) -- " >> /tmp/test097.out
for STAGED in \${STAGED_FILES}; do
echo "\${STAGED}" >> /tmp/test097.out
done
EOF
done

echo "test" >testing.txt
git add testing.txt

ACCEPT_CHANGES=A git commit -m 'testing hooks'

cat /tmp/test097.out

if [ "$(grep -c "testing.txt" /tmp/test097.out)" != "3" ]; then
echo "! Unexpected number of output rows"
exit 1
fi

if [ "$(grep -c "hook=" /tmp/test097.out)" != "4" ]; then
echo "! Unexpected number of hooks run"
exit 1
fi

0 comments on commit 2efbdcb

Please sign in to comment.