42
votes

Is it possible to run PowerShell scripts as git hooks?

I am running git in a PowerShell prompt, which shouldn't make any difference, but I can't seem to get them to work, as the hooks are named without extensions, and PowerShell needs (AFAIK) the .ps1 extension. I am not sure if that is the issue, or something else.

10
Isn't it possible to make the script invoke the powershell script (or any other script for that matter, regardless of their extension)? - holygeek
Can you give a bit more information about git hooks. - JPBlanc
@JPBlanc: The githooks manpage. I have no idea if there is different documentation provided for the Windows version(s). - intuited
holygeek - do you have an example of firing off a PowerShell script from a bash script? I can't find any examples, and I'm not sure how to go about it. - Erick T
Erick: You should be able to call it via powershell -file someScript.ps1 args - Joey

10 Answers

38
votes

You can embed PowerShell script directly inside the hook file. Here is an example of a pre-commit hook I've used:

#!/usr/bin/env pwsh

# Verify user's Git config has appropriate email address
if ($env:GIT_AUTHOR_EMAIL -notmatch '@(non\.)?acme\.com$') {
    Write-Warning "Your Git email address '$env:GIT_AUTHOR_EMAIL' is not configured correctly."
    Write-Warning "It should end with '@acme.com' or '@non.acme.com'."
    Write-Warning "Use the command: 'git config --global user.email <[email protected]>' to set it correctly."
    exit 1
}

exit 0

This example requires PowerShell Core but as a result it will run cross-platform (assuming this file has been chmod +x on Linux/macOS).

27
votes

Rename pre-commit.sample to pre-commit in hooks folder. Then make pre-commit.ps1 powershell script file in same folder.

#!/bin/sh
c:/Windows/System32/WindowsPowerShell/v1.0/powershell.exe -ExecutionPolicy RemoteSigned -File '.git\hooks\pre-commit.ps1'
9
votes

From what I gather the only option due to Git's design here would be a bash script calling PowerShell. Unfortunate, but then again, Git didn't place any thought on non-Linux compatibility.

8
votes

Kim Ki Won's answer above didn't work for me, but it has upvotes so I'll assume it works for some people.

What worked for me was dropping the bin/sh and instead of executing using -File, executing the command directly:

c:/Windows/System32/WindowsPowerShell/v1.0/powershell.exe -ExecutionPolicy RemoteSigned -Command .\.git\hooks\pre-commit.ps1
6
votes

Here's a starting PWSH script that I've been using for my PowerShell Git Hooks since reading Keith Hill's answer. Very nice.

#!/usr/bin/env pwsh

Process {
    Write-Information -MessageData "I Ran" -InformationAction Continue
}
Begin {
    Write-Information -MessageData "Beginning" -InformationAction Continue
}
End {
    Write-Information -MessageData "Ending" -InformationAction Continue

    Exit 0
}

I should also mention I share a single copy of hooks across all my repos. My repos all live in R:\Git and I created R:\Git\Hooks and used https://git-scm.com/docs/githooks to git config core.hooksPath=R:\Git\Hooks globally. Life is good.

5
votes

I have been looking for this myself, and i found the following:

Git Powershell pre-commit hook (Source)

## Editor's note: Link is dead as of 2014-5-2.  If you have a copy, please add it.

PHP Syntax check for git pre-commit in PowerShell (Soure)

##############################################################################
#
# PHP Syntax Check for Git pre-commit hook for Windows PowerShell
#
# Author: Vojtech Kusy <[email protected]>
#
###############################################################################

### INSTRUCTIONS ###

# Place the code to file "pre-commit" (no extension) and add it to the one of 
# the following locations:
# 1) Repository hooks folder - C:\Path\To\Repository\.git\hooks
# 2) User profile template   - C:\Users\<USER>\.git\templates\hooks 
# 3) Global shared templates - C:\Program Files (x86)\Git\share\git-core\templates\hooks
# 
# The hooks from user profile or from shared templates are copied from there
# each time you create or clone new repository.

### SETTINGS ###

# Path to the php.exe
$php_exe = "C:\Program Files (x86)\Zend\ZendServer\bin\php.exe";
# Extensions of the PHP files 
$php_ext = "php|engine|theme|install|inc|module|test"
# Flag, if set to 1 git will unstage all files with errors, se to 0 to disable
$unstage_on_error = 0;

### FUNCTIONS ###

function php_syntax_check {
    param([string]$php_bin, [string]$extensions, [int]$reset) 

    $err_counter = 0;

    write-host "Pre-commit PHP syntax check:" -foregroundcolor "white"

    git diff-index --name-only --cached HEAD -- | foreach {             
        if ($_ -match ".*\.($extensions)$") {
            $file = $matches[0];
            $errors = & $php_bin -l $file           
            if ($errors -match "No syntax errors detected in $file") {
                write-host $file ": OK" -foregroundcolor "green"
            }
            else {              
                write-host $file ":" $errors -foregroundcolor "red"
                if ($reset) {
                    git reset -q HEAD $file
                    write-host "Unstaging" $file "..." -foregroundcolor "magenta"
                }
                $err_counter++
            }
        }
    }

    if ($err_counter -gt 0) {
       exit 1
    }    
}

### MAIN ###

php_syntax_check $php_exe $php_ext $unstage_on_error

The code is for a pre-commit hook, but you could modify it to do pretty much anything. Should help what you need to do!

5
votes

For the sake of completeness:

If you only have Windows PowerShell and not PowerShell Core installed then Keith Hill's neat answer doesn't work. The various answers that use a bash script to run PowerShell, passing in the path to the PowerShell script to run, are straight-forward and the way I chose to go in the end. However, I discovered there is another way:

Create two files for the git hook, say pre-commit and pre-commit.ps1. The pre-commit.ps1 file is the file that PowerShell will run. The other pre-commit file (without a file extension) is empty apart from a PowerShell interpreter directive on the first line:

#!/usr/bin/env powershell

Git will run the pre-commit file, parse the PowerShell interpreter directive and run up PowerShell, passing in the path to the pre-commit file. PowerShell will assume the file passed in should have a ".ps1" extension. It will search for pre-commit.ps1 and, since you created a file with that name and extension, PowerShell will find it and run it.

This approach is nice and simple but, in the end, I decided against it because it seemed a little "magical" and might have maintainers scratching their heads about how it works.

3
votes

This is my git hook on Windows located in .\git\hooks.

post-update

#!/bin/sh
c:/Windows/System32/WindowsPowerShell/v1.0/powershell.exe -ExecutionPolicy Bypass -Command '.\post-update.ps1'

Powershell script located in the project root folder (where you initially run git init). Powershell goes to another repository and calls pull, updating that repository.

post-update.ps1

Set-Location "E:\Websites\my_site_test"
$env:GIT_DIR = 'E:\Websites\my_site_test\.git';
$env:GIT_EXEC_PATH= 'C:\Program Files (x86)\Git/libexec/git-core';
git pull
0
votes

I didn't get Simon's answer to work, potentially because of other things in my path not being parsed properly by the windows git environment and/or using bonobo git server.

My objective was writing a pre-receive hook for a repository hosted in bonobo.

I ended up with the following shebang:

#!/c/Windows/System32/WindowsPowerShell/v1.0/powershell

Otherwise works identically:

  • Create pre-receive file with only shebang
  • Create pre-receive.ps1 in hooks directory. Powershell loads this instead.

In my case, for some cleanliness, i also used

mklink <path-to-repo>\hooks\pre-receive.ps1 c:\scripts\hooks\someLibraryScript.ps1

This allows me to keep my scripts in a central repository, of course.

EDIT: It's worth noting i did not manage to get Powershell to accept the stdin stream for the pre-receive hook. As a result, i'm still using a shell script to bootstrap powershell and pipe, rather than redirect, stdin to powershell.

In this case, i used the following shell script for my pre-receive hook:

#!/bin/sh
IFS=
echo `cat -` | powershell.exe -NoProfile -ExecutionPolicy Bypass -File "c:\scripts\hooks\pre-receive.ps1"

Powershell seems satisfied with that.

0
votes

Better solutions for pwsh in the new era

Many of the answers above are many years old, and there are now simpler and better options.

In the days of Windows PowerShell, it was not possible to use #! /usr/bin/env powershell, because it did not accept files without extensions.

The workaround was to create script files in the directory with the same name but with the extension .ps1, which was mentioned in someone else's answer. But this takes advantage of a possible undocumented internal implementation, and unexpected problems may occur.

However, in the pwsh era, running script files without extensions has been supported for cross-platform compatibility. Even on windows platforms, it is only necessary to add #! /usr/bin/env pwsh, you can write scripts directly in this file without any other additional actions.