Git hooks for Windows
This article contains a few collected notes about working with git hooks in Windows environments.
Client-side git hooks are locally-installed scripts that run at certain points in your git workflow. For example:
- A pre-commit hook runs automatically when you run
git commit
, even before a commit message is required. - A post-merge runs automatically after a successful
git merge
.
Hook scripts are stored in the .git/hooks
folder in the root of your repository – to use a hook script, you just need to make sure it's present in the folder in an appropriately named file name. If .git/hooks
contains a file named pre-commit
, git will automatically attempt to execute it as a script file when you run git commit
.
A default repo installation includes sample git hook scripts, suffixed .sample
– to use the sample pre-commit hook, you would just rename file pre-commit.sample
to pre-commit
(no extension).
Shebang
Hook scripts, like other *nix shell scripts start with a shebang. This initial line tells the system which script interpreter to use to execute the file. In a *nix system this might be e.g.:
#! /bin/sh
(Bourne shell)#! /bin/bash
(Bash shell)
but obviously /bin/sh
and /bin/bash
aren't valid Windows paths.
Instead,1) you need to include the path to the relevant script interpreter on your local Windows system, e.g.
#! C:/Program\ Files/Git/usr/bin/sh.exe
specifies the location of the sh
interpreter bundled with my Git for Windows install. Notice the forward slash path separator and the backward slash escape character (used to prevent git from interpreting the path as stopping at C:/Program
).
Version control
The contents of .git/hooks
are excluded from version control and aren't cloned from a source repo. For this reason I prefer to:
- include a
HookScripts
folder somewhere inside the “normal” repo contents (e.g.shared/HookScripts
) and store hook scripts there - define hook behaviour separate script files, also in the
HookScripts
folder, and have the actual hook scripts call the separate scripts.
The HookScripts
folder is then version controlled like any other folder in the repo. To install a hook script in your local repo (e.g. pre-commit
in the screenshot), copy it into (or soft link it in) your local .git/hooks
folder. If you copy it, you'll need to modify the shebang for your local git install.
The “official” way to manage non-standard hook script location is to use the core.hooksPath
git config option, but many users seem to report difficulties with this approach, particularly in Windows systems.
PowerShell
In a Windows system, writing hook scripts in PowerShell is convenient. In the screenshot above, the two .ps1
PowerShell scripts files provide actions available for use in a hook script – the pre-commit
hook in the same folder then need only contain calls to the necessary scripts, e.g.:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File ".\shared\HookScripts\Sort-SemanticModels.ps1"
The path to the script file is relative to the root of the repository2).
pre-commit changes
A common use case for pre-commit hooks is to make additional standard changes to files before committing them, e.g. applying format conventions, linting code etc.
Bear in mind that changes are made in a git repo by being first staged (git add
), and then committed (git commit
). A pre-commit hook runs on git commit
, so any additional changes made to files by the hook script must also be staged if they are to be included in the commit.
In PowerShell, I:
- define an
Invoke-Utility
cmdlet to invoke Windows commands3) - call it with
git add
for any file modified in the hook script.
Function Invoke-Utility { $exe, $argsForExe = $Args $ErrorActionPreference = 'Continue' # to prevent 2> redirections from triggering a terminating error. try { & $exe $argsForExe } catch { Throw } # catch triggered ONLY if $exe not found, not for errors reported by $exe itself if ($LASTEXITCODE) { Throw "$exe indicated failure (exit code $LASTEXITCODE; full command: $Args)." } } # modify some file $filePath # ... # re-stage file after modifying Invoke-Utility git add $filePath