Setting Up the Pre-commit Git Hook on Windows with PowerShell

June 09, 2023#Software Development
Article
Author image.

Sarah Dutkiewicz, Senior Trainer

Have you checked in code that’s misaligned and not formatted cleanly? Have you checked in code with broken tests? What if there was a way that you could have a script run the formatter for you and have it run your tests before committing into Git? Oh there is - with Git Hooks!

What are Git Hooks?

Git Hooks are scripts that Git will execute when certain events happen. This allows you to hook into Git events and inject your own code to be executed during these events. These scripts live in the .git\hooks folder. These are the events that you can tap into.

  • applypatch-msg
  • commit-msg
  • fsmonitor-watchman
  • post-update
  • pre-applypatch
  • pre-commit
  • pre-merge-commit
  • pre-push
  • pre-rebase
  • pre-receive
  • prepare-commit-msg
  • push-to-checkout
  • update

Note: If you want a demo of these concepts using shell scripts without PowerShell, watch our YouTube video: Keep Your Repo Clean - Format & Test Using Git Hooks

Setup

For this demo, you will see that you can format code before committing and prevent committing broken tests. We have forked and cloned Ardalis.GuardClauses. We will intentionally fail a test and prevent the code from going in through the use of the pre-commit Git Hook.

As PowerShell is the primary scripting language on Windows, I would like to write a script in PowerShell that automatically formats my code and prevents me from checking in code with broken tests.

If you want to code-along with this post:

  1. Fork Ardalis.GuardClauses into your own repositories.
  2. Clone your fork locally.

Create a pre-commit PowerShell script

Git Hooks work as shell scripts. However, in a Windows environment, you may prefer to write your Git Hook scripts in PowerShell. In order to do that, you will have a shell script to call PowerShell to execute your custom script.

Sequence diagram showing that the pre-commit shell script triggers a PowerShell script. The PowerShell script passes the exit code back to the pre-commit shell script.

Notice that PowerShell has to include the exit code to the pre-commit hook. If it does not pass along the exit code, the git hook has no idea whether the PowerShell checks are successful. PowerShell can exit with the automatic variable $LASTEXITCODE to pass the exit code to the shell script.

Create the PowerShell script

The PowerShell script will:

  1. Run dotnet format to format the code.
  2. Run dotnet test to run the tests.
  3. Exit with the exit code.

To create this:

  1. Create a new file in the root of the solution called pre-commit.ps1.

  2. Copy the code below and paste it into this file. Save the file.

    #!powershell
    
    # Create pre-commit.ps1 - created in the root folder of the project
    #
    # Format your code before committing it
    #
    # This can be called manually with:
    # .\pre-commit -a
    
    Write-Host "Formatting code"
    dotnet format
    Write-Host "Done formatting code"
    Write-Host "Running tests"
    
    dotnet test
    
    Write-Host "Testing complete. Exit code: $LASTEXITCODE"
    
    # Pass the failure code back to the pre-commit hook
    exit $LASTEXITCODE

When you exit on an unsuccessful exit code - something greater than 0, the commit process will fail. The commit will not happen.

Create the pre-commit script

Now that you have a PowerShell script to execute, let’s set up the pre-commit Git Hook to run this script.

  1. Navigate to the .git\hooks folder.

  2. Rename pre-commit.sample to pre-commit.

    Note: This is an extensionless file.

  3. If there is anything in this file, empty the file.

  4. Copy the following code to create a shell script that will execute a PowerShell script:

    #!/bin/sh
    echo 
    exec powershell.exe -ExecutionPolicy RemoteSigned -File '.\pre-commit.ps1'
    exit

At this point, you can intentionally fail a test and confirm that the commit is stopped.

Intentionally fail a test

  1. In Solution Explorer, expand GuardClauses.UnitTest.

  2. Open GuardAgainstDefault.cs.

  3. In the first test, add the following line as the first line within the test:

    Assert.Fail("intentionally failing");
  4. Save the file.

  5. Try to commit the file with git commit -m "Failing tests". Notice that the commit fails and the error message includes the failing exit code.

Bypassing pre-commit

If you find yourself in a situation where you need to bypass the pre-commit hook, you can use the -n parameter with your git commit command.

git commit -m "Failing tests" -n

Note: -n is short for --no-verify.

The -n parameter bypasses both the pre-commit and commit-msg hooks.

Conclusion

This gives you a small glimpse of the capabilities of Git Hooks and using them to save your code base from failing tests and poorly formatted code.

For this particular exercise, there are a couple notes to think about:

  • We have seen developers feel test fatigue with test checks this early in the process. This can lead to sloppy tests and lower morale. It may be better to check for failing tests as part of your CI/CD process rather than during the Git Hook. If that’s the case, check out our live webinar “Real World CI/CD Tips & Tricks to Boost Your App’s Quality” on June 27, 2023 at 1:00pm US Eastern Time. We will show you how to incorporate tests and code coverage as part of this webinar. Sign up today!
  • By adding the formatter as part of the Git Hooks process, this prevents poorly formatted code from getting in. This could lead to devs writing sloppier code and depending on the formatter. However, this could also improve the quality of code getting checked in.

Look into Git Hooks if you want to enhance your development process and make great strides.


Copyright © 2023 NimblePros - All Rights Reserved