• Servers and Tools

Using Git Hooks

From the class:  Git Deploy

One of the really cool features of Git that we're going to take advantage of for deployment are Git Hooks. It allows us to put some script or code into a file that Git will automatically execute in response to different events that happen. For instance, after we've updated a remote repository, it might execute this update script.

There's a bunch of samples that you can find in the .githooks folder of any repository, and take a look at these samples to get a sense of what's possible. The other way to learn about the hooks is to type man githooks, all one word. And if you scroll down a little bit, you'll see a list of all the different hooks, the parameters that they take, and what they're intended for.

The hook that we're going to use is called post-receive. So we need to create a script called post-receive, and that script can be any language we want it to be in, but we're going to write it in Bash. And then we need to put it up into the server into the githooks folder.

Notice from the description here that this hook gets called once a git receive packed process is completed, so when we're done with the git push. And it happens once all the references have been updated on the remote machine. So this is a good place to put a deployment hook.

One thing to note in the descriptions here is, when does the hook execute? In this case, it says that it's going to execute once for the receive operation. So even if you push up multiple references, this file is only going to be executed once. And it says it doesn't take any arguments on the command line, but Git is going to pass information to it using standard input. And so we're going to have to read from standard input to get the references that have been pushed up.

So let's go create a very simple post-receive hook and just get it working. I went ahead and created a file. I put it in the config directory under git hooks just because I needed a place to put it. And I called it post-receive and with the Bash extension so that I get syntax highlighting. And this star here means that I've made it an executable file. And you'll need it to be an executable file so that Git can execute it as a script.

So far that file is blank. Let's start off by telling Bash what kind of script it's going to be, so which interpreter it should use. In this case, it's going to be a regular Bash script. The next line is set e. This means to bail out if there's any errors at all. So we don't need to check every command independently for whether it succeeded or not. If any of them return a non-zero status code, it will just bail out of this whole script.

Let's just tell the user that we've started the deployment. So we'll say deployment started, and at the end we'll echo out that we're done. So deployment completed, and we'll exit with a status code of 0.

And just to start off simply, what we need to do is to read in the variables from standard input that we're going to get. And those variables are called old revision, new revision, and the ref name. Let me explain what this line is doing in case you're not that familiar with Bash scripting. Read is going to read from standard input. It's going to read one line at a time.

So if we wanted to read multiple lines, we could put this into a loop. But we just want to read the first line, and what we're going to get off of standard input is the first word of that line is going to be the old revisions. So that's going to be SHA-1 hash, something like ABC of what we pointed to last time, then the new revision that's actually being pushed up.

So maybe that's another hash that looks something like this. Just making up characters here. So this is the one that we're actually pushing up. This is the new one. And the ref name is going to be the name of the branch or tag or the reference that we're pushing up. So that would be something like refs master.

And to start off, why don't we just echo each one of these out to the console. So I'll say echo those so we can see what they are and make sure that we're getting the right thing. So nothing too fancy yet. Just want to make sure that it works and that we can get something showing up on the console before we get really crazy.

And I'll quit out of this, and we can actually test this out right here on the console on our local machine before sending that file up. So let's see, I'll just execute it directly. Git hooks post-receive bash, and what we need to do is to send it something on standard input. So to do that, I can echo something and pipe it into this command. And maybe this will be 1234-- that will be just a made up revision-- 5678 and then refs heads master.

Let's see whether or not we get any errors back. It looks like it's working OK. So this is what we should expect to see when we run this, when Git runs this automatically for us after a deploy. So it says the deployment started, it takes this first revision right off of the standard input. And this is the new thing being pushed up. And this is the branch that we're going to be pushing up.

So when we say git push deploy master, that's going to be the reference name. So this looks great. Let's get this file up onto the server. And to do that, we can just use Secure Copy. We're going to take that post-receive Bash file and put it into the current directory of the application under the .git folder in hooks.

And notice that it's just going to be called post-receive. There's no extension here. This is what it needs to be called in order for Git to be able to find it and execute it properly. I just have a Bash extension here so that I get syntax highlighting in my editor.

When I press Enter, that should copy up the file. And lets go and make a change to the application. We'll open up the application HTML file and just change this to a version 2 and commit the result. And then we can type git push deploy master, and our script should execute automatically if things worked correctly. And it looks like it did.

This time we have real SHA-1 hashes for our revisions. Notice that the branch we're pushing up is master, and that becomes our ref name. So it's the full ref name. Refs heads master, and this is the old, and this is the new commit that we're pushing up. So this should be the latest commit on our master branch here that's just been pushed up. So everything looks good. This is a very simple script, but now let's enhance it to do a deployment. So that will be to update the working tree in the remote machine and also to restart our app server.

I'm going to write a little bit of Bash here, and if it doesn't make sense, because I haven't covered it in other places, try to follow along. I'll try to explain what I'm doing, but if something doesn't make sense, go ahead and ask in the comments area so that other people can benefit from the answer. Or if you want to just talk to me one on one, ask in the private text box. Either one is OK.

And so what we need to do now is write some Bash that is going to check out the latest version that was just pushed up and then restart our web server. And also I'd like to print out a friendly message to the user that says how many files changed so that we know that we pushed up something that seems like it's the right thing.

I need to start off with a little bit of a trick here. There's an environment variable that Git uses called git_dir, get directory, and when this environment variable is set, that is the directory that git is going to operate in. And currently, because this is being executed as a hook, this is going to be the .git folder itself. And so we need to unset this.

I'm going to say if this is set, then we're going to change into the project directory itself. So dot dot is going to send us back a directory, and that directory that we're going to be in is going to be the current folder. And then I'm going to unset this so that git doesn't get confused. So we just need to put that at the top of.

And this weird syntax here is how you do IF statements in Bash. A little bit strange. Don't forget the semi-colon, and then you've got to have the THEN token, and then at the end you say FI, F-I, instead of done or something like that.

Now, we can read the old revision, new revision, and the ref name. And I'll put a little comment to make this clear. And next we're going to check out the latest changes by using the git checkout command. I'm going to check out the ref name that was pushed up so this evaluates the variable name ref name. That's what the dollar sign brackets do. And I'm going to use the force flag so that we just wipe out anything that's in the working tree and forcefully check out the branch that was pushed up.

Next, let's use some Bash scripting magic to print out the number of files that changed as part of this deployment. It'll give us a nice feeling about the fact that we've pushed something that seems legitimate. I'm going to paste in some code, because it's a bit of a long line here, but I'll explain what it does.

First, we're going to echo something to the user. We're going to say the number of files that have changed, and then we're going to execute this script here. That's what the dollar sign and the open parentheses here does. It's going to execute the script.

Then I'm going to use the regular git diff command like we're used to using. And I'm going to pass it two parameters, the old SHA-1 hash and the new one, so that'll give us a diff. And I'm going to pass it an option, the diff filter, and the diff filter says that we only want to look at the files that have been added, copied, deleted, modified, or renamed. And we only need to get the names.

We're going to take that output and pipe it into the word count program and only look at the line count. So this trick here is going to end up with a number, where it's going to say that it's 2 or 4 or something like that. And those will be the number of files that have changed. And so using this Bash foo here, we were able to get a count, and we're going to print that out to the user. Now we can get rid of this down here, because we're not going to echo this out anymore.

And now the last step is we need to restart our server. So we'll restart the app service, and to do that, we can just type sudo system control restart boom, because the name of the service is boom, and I'm leaving off the service part because it's not required. And let's tell the user that we're restarting the app service.

And one last thing that I need to fix here that I just spotted on line 7, I need to make sure that the syntax is correct here, otherwise it we'll get a syntax error. And then we have a fully functioning Bash script. It was a little complex. We definitely used some tricks here to do some fancy work, but this is a full deployment script that's going to check out the latest copy into the working directory and restart our app service.

If you wanted to again, you could write this script in another interpreter. It doesn't need to be Bash. It could be Note or it could be Ruby or something like that. It could be any script you want, but oftentimes you'll see them written in Bash.

Let's go ahead and update the app HTML. We'll bump that version to version 3 so we know that this worked, and I'll save and quit out of this. And then I'll Secure Copy the changes from post-receive up to the hooks directory and make this commit.

And the moment of truth, I'll git push deploy master. Everything looks great. We get a little message here that says the deployment has started. It tells us that the head position has changed. It was here, and now it's been moved to the latest. Here's the number of files that have changed. We just changed one in this case.

Restarting the app service and finally deployment has completed. And just to make sure we weren't duped, let's curl the output to the console and make sure. And it looks like we're good to go. Version 3 was deployed successfully.