I'm working on a pre-commit hook that uses YUI Compressor to minify any CSS and JavaScript files that have been staged for commit. After the files get minified, the minified versions automatically get staged for commit. I've read that it's generally not a good idea to automatically add machine-generated files to the commit, but I think in this case it's OK. This is what it looks like:
Output of git status
:
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: _site-wide.css
#
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: _subpage.css
#
Output of git commit -m "Updated site-wide styling"
:
1 CSS file was minified and added to the Git repository
0 JavaScript files were minified and added to the Git repository
[master 41f1815] Updated site-wide styling
2 files changed, 2 insertions(+), 2 deletions(-)
What happened here is the pre-commit hook used YUI Compressor to minify _site-wide.css
, outputting the result to site-wide.css
(no leading underscore). It then staged site-wide.css
for commit. The pre-commit hook skipped over _subpage.css
because, although it had been modified, it was not staged for commit.
Since the CSS and JavaScript files on disk might not be the same as the CSS and JavaScript files staged for commit, I run git stash -q --keep-index
before minifying the files and then run git stash pop -q
after. This pre-commit hook works fine on repositories that already have a commit, but if I put the pre-commit hook in place before the first commit is made, I get this:
Output of git status
:
# On branch master
#
# Initial commit
#
# Changes to be committed:
# (use "git rm --cached <file>..." to unstage)
#
# new file: _site-wide.css
# new file: _subpage.css
#
Output of git commit -m "Initial commit"
:
fatal: bad revision 'HEAD'
fatal: bad revision 'HEAD'
fatal: Needed a single revision
You do not have the initial commit yet
2 CSS files were minified and added to the Git repository
0 JavaScript files were minified and added to the Git repository
No stash found.
Output of git status
:
# On branch master
#
# Initial commit
#
# Changes to be committed:
# (use "git rm --cached <file>..." to unstage)
#
# new file: _site-wide.css
# new file: _subpage.css
# new file: site-wide.css
# new file: subpage.css
#
I will now paste the pre-commit hook's code. Keep in mind that in order to be flexible, I wrote this script so that it could be run in any CakePHP project, not just the ones that have a Git repository. I also wrote it so that you can force it to minify all of the CSS and JavaScript files and not just the ones staged for commit. This is done by running .git/hooks/pre-commit force
. Here is the code:
#!/bin/bash
css_files_to_ignore=(
#"_do_not_minify.css"
)
js_files_to_ignore=(
#"_do_not_minify.js"
)
if git rev-parse --git-dir > /dev/null 2>&1; then
git_repository=true
base_folder="$(git rev-parse --show-toplevel)/app/webroot"
if [ "$1" == "force" ]; then
process_unstaged_files=true
else
process_unstaged_files=false
fi
else
git_repository=false
base_folder="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/webroot"
fi
if [ -f /Applications/yuicompressor.jar ]; then
# Mac
yuicompressor_path=/Applications/yuicompressor.jar
else
# Linux
yuicompressor_path=$(command -v yui-compressor)
fi
function process_assets()
{
extension=$1
files_minified=0
for infile in $(echo "$base_folder/$extension/*.$extension")
do
# Only process files.
[[ -f $infile ]] || continue
filename=${infile##*/}
# If the filename starts with an underscore, that means that the file is
# eligible for minification.
[[ ${filename:0:1} == "_" ]] || continue
ignore_this_file=false
files_to_ignore=$extension"_files_to_ignore"
for i in $(eval echo \${$files_to_ignore[@]})
do
if [[ $i == $filename ]]; then
ignore_this_file=true
break
fi
done
if [ $git_repository == true ] && [ $process_unstaged_files == false ] && git diff --quiet --cached $infile; then
# This file is NOT staged for commit.
ignore_this_file=true
fi
if [ $ignore_this_file == false ]; then
minified_file="$base_folder/$extension/${filename:1}"
if [ ! -f "$minified_file" ] || test $infile -nt $minified_file; then
$yuicompressor_command "$infile" -o "$minified_file"
if [ $git_repository == true ] && [ $process_unstaged_files == false ]; then
git add "$minified_file"
fi
((files_minified++))
fi
fi
done
# Output a summary of what was done.
if [ $extension == "css" ]; then
file_type="CSS"
else
file_type="JavaScript"
fi
echo -n "$files_minified $file_type file"
if [ $files_minified -eq 1 ]; then
echo -n " was"
else
echo -n "s were"
fi
echo -n " minified"
if [ $git_repository == true ] && [ $process_unstaged_files == false ]; then
echo " and added to the Git repository"
else
echo
fi
}
if [ -f "$yuicompressor_path" ]; then
if [ ${yuicompressor_path: -4} == ".jar" ]; then
yuicompressor_command="java -jar $yuicompressor_path"
else
yuicompressor_command=$yuicompressor_path
fi
if [ $git_repository == true ] && [ $process_unstaged_files == false ] && ! git diff --quiet --cached; then
# The staging area is what should be processed rather than what is currently
# on disk.
git stash -q --keep-index
stashed=true
else
stashed=false
fi
process_assets css
process_assets js
if [ $stashed == true ]; then
git stash pop -q
fi
else
echo "YUI Compressor was not found. Aborting."
exit 1
fi
How can I make this work? Any help would be appreciated.
I've come to the conclusion that there is no solution to this because some Git commands simply will not work when there is no HEAD. Also, after thinking about it some more, I decided that this is kind of an edge case anyway, at least for me, because my very first commit is usually something basic such as the files for the framework that I use. So I can just implement the hook after the first commit.
I appreciate Hassek's time as well as the time of anybody else who read this. Thank you!