Git hooks with PowerShell

It’s time to provide some client-side hooks for our Git repository! Git hooks are stored in the .git directory under following path:

.git/hooks

As you can see there’s plenty of them, ending with .sample. When we remove this extension, the hook will kick in, handling events according to its name. There’s an issue with Git hooks. They’re stored under .git folder which is not versioned on its own, so you need to apply them locally, right? Not exactly. You can use following method, to have hooks version in the same repository as the code. Just copy the hooks to the repository level (above .git folder, to the root of the repo) and place a link to the folder above in the .git folder. Once it’s done, git will use versioned hooks, from your repository. This enables you to treat hooks as a part of your repo and work with them as with any feature of your product.

Having established the hooks foundation, we can take a look at the pre-push hook. Just to cut this bash let me call PowerShell script with exec. This results in passing the whole execution directly to PowerShell, additionally handling exiting from ps script properly (it’s passed to bash).

The important thing to mention is that this script will be called for every branch you push. That’s why it’s important to properly pass parameters.

If the PowerShell script returns non-zero code, it’s treated as an error and push fails. This means, that we can validate the push against any source and fail it on purpose when some conditions are not met.

Summing up, you’ve seen so far a bit of Git plumbing and a way how to turn pre-push hook into script calling your Prepare-Push function from a script. As you probably remember, that’s the place when we’re going to validate the push quality against some service. In our case it’s going to be TeamCity API.

Git plumbing with PowerShell

In the recent post I’ve shown the need of securing the development branch in GitFlow. The same should be applied to all release branches and hotfixes as well. To provide a proper protection we need tooling and ability to gather some information from the git repository. Fortunately, Git structure is extremely easy and as shown in the very best Git book chapter, consists of a few atoms, elements, which because of their simplicity let us to create powerfull approaches like GitFlow. For sake of reference, this post won’t cover whole plumbing but only the needed parts.

If you have any Git repo on disk, that’s the time to open the hidden folder named .git. Don’t be shy, just list its content. You’ll see a structure which beside other elements contains following:

  1. /refs
    1. /heads
      1. your branches are here!
    2. /remotes
      1. /origin
        1. origin branches go here!
    3. /tags
  2. HEAD

Let’s start with the HEAD (although Joker says no). Open your PowerShell and use simple cat or Get-Content its content. You’ll see something similar to these:

ref: refs/heads/feature/blob-management

So that’s how Git holds the reference to the current branch you’re working on! With the following script you can easily obtain its full name.

function Git-GetHeadFullReference(){
               return ((gc .git\HEAD).Replace(“ref: “, “”).Replace(‘/’, ‘\’))
}

That was easy, wasn’t it?

Now let’s try to get all the names of features branches, having in mind the fact, that they are prefixed with the “feature/” string. We already know that all the feature branches are stored as files in .git/refs/heads/feature/ For sake of security that someone added a subdirectory for any feature, we need to go recursively down, but skipping directories (if there’s a directory, the real branch is below).

If you take a look and open any file representing branch, you’ll find 40 characters. You probably know what is it. It’s the identifier of the commit the branch is pointing at! Isn’t it simple and beautiful? A branch is just a file with the identifier of the commit. Now you know why branching in Git is so cheap! Every branch is a single small file! Going back to our script let’s enhance it with ability to return not only names of the branches, but identifiers of the commits they’re pointing to as well.

function Git-GetAllFeatureBranches(){
                return gci .\.git\refs\heads\feature -Recurse |
                             where { ! $_.PSIsContainer } |
                             Select-Object -Property @{n=’name’; e={$_.Name}}, @{n=’sha1′; e={(gc $_.FullName)}}
}

With a few lines of PowerShell we were able to obtain local branches with the commit identifiers. Admit that simplicity of this is so powerful!

Having this tooling, we’ll be able to build up a nice fence around development, to keep it always green. Stay tuned!

Protect development at all cost

Some time ago I wrote a few entries about my attitude to the GitFlow and a small adjustment that can greatly reduce the possibility of breaking development/release branches. The main reasoning was following: review, build your merge commits in your feature branches, then using fastforward-only merge apply it on develop. Having this applied should keep your development, healthy, right?

Not at all. The final step is pushing changes. And it’s a manual step, as ending the feature is. If that’s a manual step, there’s a bunch of things that can wrong. Yes, you can push quick-fixes, fixups, minor-improvements to development and say to your whole team “it’ll be there in a sec”, but isn’t it easier to just don’t break things? Having that said, is possible to be guarded against push that isn’t proper? Yes, and my automation for this is simple:

  • use git prehooks
  • add check against TeamCity API if the build is ok
  • add check against a code review tool
  • allow push only if the checks are successful

In the following posts I’d present how to

  • create git hooks, preferably using PowerShell (“You won’t be able to run on Linux oooooh nooooo!”)
  • successfully get statuses and consume TeamCity API
  • combine this to revoke commits that might hurt development branch

Stay tuned!