-
Notifications
You must be signed in to change notification settings - Fork 0
/
lib.nix
157 lines (138 loc) · 3.94 KB
/
lib.nix
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/**
Install Nix packages as [Git hooks](https://git-scm.com/docs/githooks) from your Nix shell environment.
Heavily inspired by [a hack](https://git.clan.lol/clan/clan-core/src/commit/930923512c03179fe75e4209c27eb3da368e7766/scripts/pre-commit) to get [treefmt](https://github.com/numtide/treefmt) into a pre-commit hook.
# Installation
```shell-session
nix-shell -p npins
npins init
npins add github fricklerhandwerk git-hooks -b main
```
```nix
# default.nix
let
sources = import ./npins;
in
{
pkgs ? import sources.nixpkgs { inherit system; config = { }; overlays = [ ]; },
git-hooks ? pkgs.callPackage sources.git-hooks { },
system ? builtins.currentSystem,
}:
let
inherit (git-hooks) lib;
in
pkgs.mkShellNoCC {
shellHook = ''
# add Git hooks here
'';
}
```
*/
{ lib, writeShellApplication, git, stdenv, busybox, coreutils }:
let
wrap = hook: writeShellApplication
{
name = "pre-commit";
runtimeInputs = [ git ];
text = ''
[[ -v DEBUG ]] && set -x
readarray staged < <(git diff --name-only --cached)
[[ ''${#staged[@]} = 0 ]] && exit
unstash() {
local ret=$?
set +e
git stash pop -q
exit "$ret"
}
# check if there's an initial commit
if git rev-parse --verify HEAD >/dev/null 2>&1; then
git stash push --keep-index --message pre-commit > /dev/null
trap unstash EXIT
fi
${lib.getExe hook}
'';
};
install = hook: writeShellApplication {
name = "install";
runtimeInputs =
let
posix = if stdenv.isDarwin then coreutils else busybox;
in
[ git posix ];
text = ''
ln -s -f ${lib.getExe (wrap hook)} "$(git rev-parse --show-toplevel)"/.git/hooks/pre-commit
'';
};
in
{
/**
```
pre-commit :: Derivation -> Path
```
`pre-commit` takes a derivaton with a [pre-commit hook](https://git-scm.com/docs/githooks#_pre_commit), and returns a path to an executable that will install the hook.
The derivation must have `meta.mainProgram` set to the name of the executable in `$out/bin/` that implements the hook.
The hook is installed in the Git repository that surrounds the working directory of the Nix invocation, and will get run roughly like this:
```bash
git stash push --keep-index
hook
git stash pop
```
:::{.example}
# Add a pre-commit hook
Entering this shell environment will install a Git hook that prints `Hello, world!` to the console before each commit:
```
pkgs.mkShellNoCC {
shellHook = ''
${lib.git-hooks.pre-commit pkgs.hello}
'';
}
```
:::
*/
pre-commit = hook: lib.getExe (install hook);
/**
```
abort-on-change :: Derivation -> Derivation
```
Wrap a hook such that the commit is aborted if the hook changes staged files.
:::{.example}
# Wrap a pre-commit hook to abort on changed files
The `cursed` hook will add an empty line to each staged file.
Wrapping it in `abort-on-change` will prevent files thus changed from being committed.
```
let
cursed = pkgs.writeShellApplication {
name = "pre-commit-hook";
runtimeInputs = with pkgs; [ git ];
text = ''
for f in $(git diff --name-only --cached); do
echo >> "$f"
done
'';
};
in
pkgs.mkShellNoCC {
shellHook = ''
${with lib.git-hooks; pre-commit (wrap.abort-on-change cursed)}
'';
}
```
:::
*/
wrap.abort-on-change = hook: writeShellApplication {
name = "pre-commit-hook";
runtimeInputs = [ git ];
text = ''
${lib.getExe hook}
{
changed=$(git diff --name-only --exit-code);
status=$?;
} || true
if [ $status -ne 0 ]; then
exec 1>&2
echo Files changed by pre-commit hook:
echo "$changed"
exit $status
fi
'';
};
}