49

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.

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Erick T
  • 7,009
  • 9
  • 50
  • 85
  • Isn't it possible to make the script invoke the powershell script (or any other script for that matter, regardless of their extension)? – holygeek Apr 12 '11 at 01:32
  • 1
    Can you give a bit more information about git hooks. – JPBlanc Apr 12 '11 at 04:45
  • @JPBlanc: [The `githooks` manpage.](http://www.kernel.org/pub/software/scm/git/docs/githooks.html) I have no idea if there is different documentation provided for the Windows version(s). – intuited Apr 12 '11 at 06:09
  • 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 Apr 13 '11 at 04:41
  • Erick: You should be able to call it via `powershell -file someScript.ps1 args` – Joey Oct 04 '11 at 10:39
  • Great question and anyone looking reading here after July 2019 should skip on past the accepted answer and look at the more recently popular answer that make this a snap. SO should have some way to remove the accepted answer or at least have the community answers appear ahead of that green checkmark. Time can change the answer to a great question and SO should allow this. – No Refunds No Returns Jul 11 '19 at 15:22

10 Answers10

45

Since powershell 7.2 the script must have .ps1 extension on Windows. So this answer will not work.


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 <name@acme.com>' 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).

Roman Mahotskyi
  • 4,576
  • 5
  • 35
  • 68
Keith Hill
  • 194,368
  • 42
  • 353
  • 369
  • 4
    Starting in October 2018, this should be the Accepted Answer. Thanks Keith. – No Refunds No Returns Oct 27 '18 at 06:12
  • 3
    Great answer! This `#!/usr/bin/env pwsh` "trick" works also for `git rebase --exec` and `git bisect run`. Unfortunately I noticed that `pwsh` doesn't exit with error code when script fails so be sure to change your script to use `exit` keyword when somethings fails. – Mariusz Pawelski Feb 04 '19 at 22:33
  • 3
    This solution doesn't seem to work for me - `/usr/bin/env: 'pwsh': No such file or directory`, and I have powershell core installed – Bort Jun 19 '19 at 20:59
  • @Bort if you got this exactly: `error: cannot spawn .git/hooks/pre-commit: No such file or directory` You probably did the same thing I did and did not include `exit 0` thereby making it look to git you didn't complete successfully – CubanX Jun 14 '21 at 15:01
  • 2
    Thanks so much for this! If you want to run your Powershell with No Profile for speed sake (I sure did), you can do this for the top line: `#!/usr/bin/env -S pwsh -NoProfile` – CubanX Jun 14 '21 at 15:37
  • 1
    Unfortunaly, since powershell 7.2, the script must have .ps1 extension on Windows (https://github.com/PowerShell/PowerShell/releases/tag/v7.2.0-preview.9). A wrapper script like some answsers below will be needed. – Doraemoe Nov 10 '21 at 22:19
  • 1
    I was happy for a moment but then saw that they changed it in v7.2.0 :/ – Yash Garg Nov 28 '21 at 13:42
  • 2
    Yeah, that was disappointing. :-( – Keith Hill Dec 02 '21 at 21:42
34

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'
abdusco
  • 9,700
  • 2
  • 27
  • 44
Kim Ki Won
  • 1,795
  • 1
  • 22
  • 22
  • 2
    I think the syntax in this command line is wrong after -Command it's expecting an inline powershell command but you are specifying a file as well. It will throw an error about -File not being recognised as the name of a cmdlet, function or script file. – leinad13 Sep 30 '16 at 11:33
  • See [my answer below](http://stackoverflow.com/a/39796096/1225497) if you get errors like "#! is not recognized..." or "The term -File is not recognized...". – Taran Sep 30 '16 at 16:32
  • to make it work across platforms, you could enclose above in an if statement if [[ -f path_to_powershell.exe ]]; then etc etc ; fi... And yes above works, for anyone in doubt. Thanks, this was useful Kim. – Jepper Oct 04 '22 at 12:29
9

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
Taran
  • 12,822
  • 3
  • 43
  • 47
  • I was able to invoke a powershell from my precommit hook like this. Thanks for the comment. I don't know if it's the correct area to ask this question but is there a way i can get the return value of the powershell here and check it – Arjun Menon Mar 23 '22 at 11:19
9

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.

No Refunds No Returns
  • 8,092
  • 4
  • 32
  • 43
8

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.

Simon Elms
  • 17,832
  • 21
  • 87
  • 103
  • Powershell Core is the way ahead. MSFT has said all new stuff will be there so I think you're safe move to it. The only downside I can see as of this writing is that Windows 10 didn't make it a choice in the `Windows+x` shortcut ... yet. – No Refunds No Returns Jul 11 '19 at 15:17
  • This is brilliant. It was the only solution that worked for me right away. – Ryan P Sep 30 '19 at 19:18
  • 1
    @NoRefundsNoReturns: The problem with using PowerShell Core is portability. If would be okay if I were just writing scripts to run on my own machine. However, I write scripts that other developers can use, or that can be used on servers. So I have to stick to PowerShell 5.1 until PowerShell Core is deployed by default. – Simon Elms Nov 15 '19 at 02:07
  • I also found this phenomenon. It feels really wonderful! – Andy Aug 19 '21 at 23:10
  • 1
    This what worked for me, too. However, before it would work I had to open git bash AS AN ADMINISTRATOR and then run powershell Set-RemoteExecutionPolicy RemoteSigned – Seth Spearman Feb 20 '23 at 03:30
8

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.

Joey
  • 344,408
  • 85
  • 689
  • 683
  • 1
    This seems to be the answer. Is it a pity - we're not all bash lovers, and bash on windows will always be second place. Thanks. – Erick T Apr 13 '11 at 04:42
  • If git supported scripting for arbitrary platforms, how different would the config files for those hooks really look from the bash script bootstraps? – brianary Apr 09 '13 at 16:20
  • 3
    As of October 2018 you can directly use Powershell Core in the pre-commit hook as detailed by Keith Hill. Life is good. Please consider changing the accepted answer. – No Refunds No Returns Oct 27 '18 at 06:14
  • 3
    If you're looking for a different, possible better answer that lets you use Powershell Core directly, see my response to this question. The world has changed since this question was asked / answered and now it's almost `too` easy to do this. – No Refunds No Returns May 20 '19 at 15:09
5

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 <wojtha@gmail.com>
#
###############################################################################

### 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!

JasonMArcher
  • 14,195
  • 22
  • 56
  • 52
TiernanO
  • 1,597
  • 14
  • 31
3

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
Tudor Morar
  • 3,720
  • 2
  • 27
  • 25
0

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.

  • If you don't want to mess with links, you can also set the `core.hooksPath` config (eg. `git config core.hooksPath`) see [docs](https://git-scm.com/docs/git-config#Documentation/git-config.txt-corehooksPath) – ehiller Jun 12 '21 at 14:24
0

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.

Andy
  • 1,077
  • 1
  • 8
  • 20
  • Thanks for the info Andy. This was already explained in existing answers (see the one by Keith Hill, for example), so it may be worth rather upvoting the existing answer(s) than adding another one. – jamiebarrow Sep 23 '21 at 20:13