====== Git hooks for Windows ====== {{tag> git windows}} This article contains a few collected notes about working with git hooks in Windows environments. Client-side [[https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks|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 [[https://en.wikipedia.org/wiki/Shebang_(Unix)|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,((From https://www.visualstudiogeeks.com/DevOps/UsingPowerShellForGitHooksWithVstsGitOnWindows -- thanks Tarun!)) 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. {{:git:hooks_folder.png?nolink&500|}} 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 repository**((From https://www.visualstudiogeeks.com/DevOps/UsingPowerShellForGitHooksWithVstsGitOnWindows -- thanks Tarun!)). ===== 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 commands((From https://stackoverflow.com/questions/48864988/powershell-with-git-command-error-handling-automatically-abort-on-non-zero-exi -- thanks mklement0!)) * 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