Git: many useful and different hooks

    The article is a free translation of this material . Fans of long and abstruse primary sources can immediately read the original.

    When we are faced with the task of changing the codebase, for example, in the Github repository to rebuild / restart some application on some of our environments, the first thing that comes to mind as a possible trigger for such a rebuild is the mechanism provided by the same github web hooks: upon the occurrence of an event with our remote repository (as the appearance of a new commit in some of its monitored branches), the github uses the corresponding web hook and “pulls” the service specified in its settings, which Start the process of rebuilding / restart of the application. This is a standard widely used and simple mechanism for such cases, everyone does it, and so on ...

    But what if our application lives on a host, for which for some reason the github is not allowed to access? For example, is the host located in a closed network or behind NAT and is not accessible from the Internet?

    In this case, you can use the local hook mechanism of Git itself, about which (the mechanism), as it turns out, few people even know who have been using Git for quite some time.

    When we create a local repository (initialize empty, clone remote, ...), then in the .git folder, in its root, or in the root itself, if it is a bare repository, there is a hooks folder. After the repository is initialized, hook templates for various events are saved in this folder, for example: post-merge, post-receive, post-update, etc.

    A full description of supported events for hooks can be found, for example, here .

    We will use this mechanism and implement a simple push-to-deploy scheme for our miserable application.

    We will need two local repositories. Let's create them, for example, using the following paths:

    1. / opt / repo-dev / example-repo /
    2. /opt/repo-remote.git/

    The first repository is a clone of our remote example-repo repository on the github.
    The second is the bare repository, a copy of the first that will serve us solely to handle the post-update event when updates appear in the remote repository. So how do we do this?

    The scheme is very simple (suppose we track the test branch, and our application is node.js, managed by the pm2 manager):

    1. Periodically update the first local repository to a state that fully corresponds to the state of the remote repository.
    2. From the first local repository, update the second.
    3. Once HEAD has moved with us - a new commit has appeared - the post-update hook will be used in the second repository, which is executed when any changes in the repository appear, and which will perform the necessary actions to rebuild and restart the application.

    To do this, we do the following:

    1. In the first local repository, add remote - the second local repository:

    cd /opt/repo-dev/example-repo/ && git remote add prod /opt/repo-remote.git

    Now we can do git push from the first local repository to the second.

    2. In the /opt/repo-remote.git/hooks/ folder, create a post-update file and make it executable:

    touch /opt/repo-remote.git/hooks/post-update && chmod +x /opt/repo-remote.git/hooks/post-update

    This is a regular shell script, but according to the internal convention of Git without the .sh extension !
    Let's add some commands to it:

    cd /opt/repo-remote.git
    /usr/bin/git --work-tree=/opt/repo/example-repo/ checkout -f origin/test
    cd /opt/repo/example-repo/
    /usr/bin/npm install
    /usr/local/bin/pm2 restart all

    What does the script do? First, it simply unloads the working tree of our bare repository into the folder with our working application, and then rebuilds the dependencies and restarts the pm2 services. As you can see, no magic.

    3. Configure cron, which every n minutes will update the first repository from the remote:

    git fetch origin && git reset --hard -f origin/test

    T.O. now our host will be a regular initiator of checking the remote repository - but have there been any updates there?

    Please note that we are updating the local repository not by git pull, but by git reset --hard. This is done in order to eliminate the need for merges with certain contents of the next commit - we make the local repository a complete copy of the remote one.

    4. Immediately after synchronizing the first local repository with the remote one, we push all the changes to our pseudo-remote second local repository:

    git push prod test

    That's all. As soon as our pseudo-remote local repository receives non-zero changes, Git pulls its post-update hook, which executes the corresponding script. And we get a simple push-to-deploy working scheme, which, if desired, can be further improved in accordance with our needs.

    “Why make such an indigestible monstrous scheme ?!” you ask. At first I asked the same question, but it turned out that with the existing list of Git hooks the only way we can call the necessary processing for any update of our branch in the remote repository. The post-update hook we need is designed to run on the remote repository (and some of the hooks are designed to run on the local repository). And in such a not-so-elegant way, we have emulated this pseudo-remote repository. Perhaps in the near future there will be some more convenient hook running locally, and the scheme can be simplified. But so far.

    In conclusion, I want to give some advice to those who decide to implement this for themselves and, faced with the problems of a non-working hook or some parts of it, will violently curse me, Git, the author of the original article and everyone else on the list:

    1. Remember the specifics of the work cron - the programs in it run by default in a completely different environment than you probably expect.
    2. Check the versions of your utilities (npm, node etc) when calling them from scripts and cron - they may not be the same as when starting manually due to the difference in the paths to executable files in environment variables, for example. And the launch of their other versions can lead to unpredictable results.
    3. Spend 20 minutes watching the next Simpsons series and get back to experimenting with renewed vigor and good humor.

    I will be glad to any comments and clarifications on the merits.

    Have a nice day!

    Also popular now: