Skip to content

Make hook installation optional #261

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 14 commits into from
Closed
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
87 changes: 24 additions & 63 deletions modules/pre-commit.nix
Original file line number Diff line number Diff line change
Expand Up @@ -281,12 +281,22 @@ in
defaultText = "<derivation>";
};

environmentSetupScript =
mkOption {
type = types.str;
description = lib.mdDoc
''
A bash snippet that provides functions for updating the config file and installing the hook scripts.
'';
readOnly = true;
};

installationScript =
mkOption {
type = types.str;
description = lib.mdDoc
''
A bash snippet that installs nix-pre-commit-hooks in the current directory
A bash snippet that installs nix-pre-commit-hooks in the current directory.
'';
readOnly = true;
};
Expand Down Expand Up @@ -372,70 +382,21 @@ in
default_stages = cfg.default_stages;
};

installationScript =
environmentSetupScript =
''
export PATH=${cfg.package}/bin:$PATH
if ! type -t git >/dev/null; then
# This happens in pure shells, including lorri
echo 1>&2 "WARNING: pre-commit-hooks.nix: git command not found; skipping installation."
elif ! ${git}/bin/git rev-parse --git-dir &> /dev/null; then
echo 1>&2 "WARNING: pre-commit-hooks.nix: .git not found; skipping installation."
else
GIT_WC=`${git}/bin/git rev-parse --show-toplevel`

# These update procedures compare before they write, to avoid
# filesystem churn. This improves performance with watch tools like lorri
# and prevents installation loops by via lorri.

if readlink "''${GIT_WC}/.pre-commit-config.yaml" >/dev/null \
&& [[ $(readlink "''${GIT_WC}/.pre-commit-config.yaml") == ${configFile} ]]; then
echo 1>&2 "pre-commit-hooks.nix: hooks up to date"
else
echo 1>&2 "pre-commit-hooks.nix: updating $PWD repo"

[ -L .pre-commit-config.yaml ] && unlink .pre-commit-config.yaml

if [ -e "''${GIT_WC}/.pre-commit-config.yaml" ]; then
echo 1>&2 "pre-commit-hooks.nix: WARNING: Refusing to install because of pre-existing .pre-commit-config.yaml"
echo 1>&2 " 1. Translate .pre-commit-config.yaml contents to the new syntax in your Nix file"
echo 1>&2 " see https://github.com/cachix/pre-commit-hooks.nix#getting-started"
echo 1>&2 " 2. remove .pre-commit-config.yaml"
echo 1>&2 " 3. add .pre-commit-config.yaml to .gitignore"
else
ln -fs ${configFile} "''${GIT_WC}/.pre-commit-config.yaml"
# Remove any previously installed hooks (since pre-commit itself has no convergent design)
hooks="pre-commit pre-merge-commit pre-push prepare-commit-msg commit-msg post-checkout post-commit"
for hook in $hooks; do
pre-commit uninstall -t $hook
done
# Add hooks for configured stages (only) ...
if [ ! -z "${concatStringsSep " " install_stages}" ]; then
for stage in ${concatStringsSep " " install_stages}; do
if [[ "$stage" == "manual" ]]; then
continue
fi
case $stage in
# if you amend these switches please also review $hooks above
commit | merge-commit | push)
stage="pre-"$stage
pre-commit install -t $stage
;;
prepare-commit-msg | commit-msg | post-checkout | post-commit)
pre-commit install -t $stage
;;
*)
echo 1>&2 "ERROR: pre-commit-hooks.nix: either $stage is not a valid stage or pre-commit-hooks.nix doesn't yet support it."
exit 1
;;
esac
done
# ... or default 'pre-commit' hook
else
pre-commit install
fi
fi
fi
fi
_pre_commit_hooks_nix_git=${git}/bin/git
_pre_commit_hooks_nix_config=${configFile}
_pre_commit_hooks_nix_install_stages='${concatStringsSep " " install_stages}'

source ${../src/pre-commit-install.sh}
'';

installationScript =
''
${cfg.environmentSetupScript}

_pre_commit_hooks_nix_install_main
'';
};
}
3 changes: 3 additions & 0 deletions nix/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ let
src = ../.;
hooks = {
shellcheck.enable = true;
# `types_or = [ ... something ... ]` doesn't pick up our .sh file
shellcheck.types_or = nixpkgs.lib.mkForce [ ];
Comment on lines +25 to +26
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't have been necessary, but that's a different problem; not for this PR.


nixpkgs-fmt.enable = true;
};
};
Expand Down
123 changes: 123 additions & 0 deletions src/pre-commit-install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# shellcheck shell=bash

# This file provides utilities that are used by the `shellHook` among other things.
# They are meant to be loaded into interactive shells, so use an underscore prefix
# to avoid polluting the command tab completion.

# PUBLIC API
#
# We make a reasonable effort to keep the functions marked (public) stable.
# Functions marked (private) are not guaranteed to be stable for reasons including:
# - dependency on global variables that are not part of the public API
# - likely to be changed in the future to improve behavior or implementation details
# The public API uses the following global variables, which are checked here:
(
: "${_pre_commit_hooks_nix_git:?}"
: "${_pre_commit_hooks_nix_config:?}"
: "${_pre_commit_hooks_nix_install_stages:?}"
) || {
echo >&2 "ERROR: pre-commit-hooks.nix: missing global variables. Please initialize them in your shellHook."
}

# (public)
#
# Install the hooks and the config file.
_pre_commit_hooks_nix_install_main() {
if _pre_commit_hooks_nix_local_config_file="$(_pre_commit_hooks_nix_find_git_toplevel "skipping installation.")/.pre-commit-config.yaml"; then
_pre_commit_hooks_nix_ensure_config_file_up_to_date && _pre_commit_hooks_nix_install_stages
fi
}

# (public)
#
# Only update the config file.
_pre_commit_hooks_nix_install_config_file_only() {
if _pre_commit_hooks_nix_local_config_file="$(_pre_commit_hooks_nix_find_git_toplevel "not updating local config file link.")/.pre-commit-config.yaml"; then
_pre_commit_hooks_nix_ensure_config_file_up_to_date
fi
}

# (private)
#
# Find the root of the worktree.
_pre_commit_hooks_nix_find_git_toplevel() {
local msg="$1"
if ! type -t git >/dev/null; then
# This happens in pure shells, including lorri
echo 1>&2 "WARNING: pre-commit-hooks.nix: git command not found; $msg"
return 1
elif ! $_pre_commit_hooks_nix_git rev-parse --git-dir &> /dev/null; then
echo 1>&2 "WARNING: pre-commit-hooks.nix: .git not found; $msg"
return 1
else
$_pre_commit_hooks_nix_git rev-parse --show-toplevel
fi
}

# (private)
_pre_commit_hooks_nix_is_config_up_to_date() {
readlink "${_pre_commit_hooks_nix_local_config_file}" >/dev/null \
&& [[ "$(readlink "${_pre_commit_hooks_nix_local_config_file}")" == "$_pre_commit_hooks_nix_config" ]]
}

# (private)
_pre_commit_hooks_nix_ensure_config_file_up_to_date() {
# These update procedures compare before they write, to avoid
# filesystem churn. This improves performance with watch tools like lorri
# and prevents installation loops by via lorri.
if _pre_commit_hooks_nix_is_config_up_to_date; then
echo 1>&2 "pre-commit-hooks.nix: hooks up to date"
return 0;
fi

echo 1>&2 "pre-commit-hooks.nix: updating $PWD repo"

[ -L .pre-commit-config.yaml ] && unlink .pre-commit-config.yaml

if [ -e "${_pre_commit_hooks_nix_local_config_file}" ]; then
echo 1>&2 "pre-commit-hooks.nix: WARNING: Refusing to install because of pre-existing .pre-commit-config.yaml"
echo 1>&2 " 1. Translate .pre-commit-config.yaml contents to the new syntax in your Nix file"
echo 1>&2 " see https://github.com/cachix/pre-commit-hooks.nix#getting-started"
echo 1>&2 " 2. remove .pre-commit-config.yaml"
echo 1>&2 " 3. add .pre-commit-config.yaml to .gitignore"
return 1;
fi

ln -fs "$_pre_commit_hooks_nix_config" "${_pre_commit_hooks_nix_local_config_file}"
}

# (private)
#
# Perform the installation of the hooks. Relies on some global variables.
_pre_commit_hooks_nix_install_stages() {
# Remove any previously installed hooks (since pre-commit itself has no convergent design)
hooks="pre-commit pre-merge-commit pre-push prepare-commit-msg commit-msg post-checkout post-commit"
for hook in $hooks; do
pre-commit uninstall -t "$hook"
done
# Add hooks for configured stages (only) ...
if [ -n "$_pre_commit_hooks_nix_install_stages" ]; then
for stage in $_pre_commit_hooks_nix_install_stages; do
if [[ "$stage" == "manual" ]]; then
continue
fi
case $stage in
# if you amend these switches please also review $hooks above
commit | merge-commit | push)
stage="pre-$stage"
pre-commit install -t "$stage"
;;
prepare-commit-msg | commit-msg | post-checkout | post-commit)
pre-commit install -t "$stage"
;;
*)
echo 1>&2 "ERROR: pre-commit-hooks.nix: either $stage is not a valid stage or pre-commit-hooks.nix doesn't yet support it."
exit 1
;;
esac
done
# ... or default 'pre-commit' hook
else
pre-commit install
fi
}