Wilson Mar bio photo

Wilson Mar

Hello. Join me!

Email me Calendar Skype call 310 320-7878

LinkedIn Twitter Gitter Google+ Youtube

Github Stackoverflow Pinterest

On git commit it’s hooked


Overview

Here is a tutorial on how to make Git on a local machine automatically run a “hook” script in response to git commands.

Dimensions to Hooks

Hooks scripts run on two locations:

Git and GitHub have been written to look for a hook program before and after each of these commands:

Just so we can use nerdy language, hooks before each event begin with “pre-“ and hooks after each even begin with “post-“. This illustration (by Sarah Goff-Dupont of Atlassian) identifies common uses across four situations: githooksgrid

Ensure adherance to standards

PROTIP: Some organizations impose a set of hook files on repositories in order to impose some standards, such as:

  • Reject very short commit messages, such as 10 characters or less.
  • Move media files (.mp4, .mp3, .jpg, .png) from repositories that are only “supposed” to contain text to a repository hold media files that humans can’t read. The job of git-lfs (Large File System) is to move and replace binary files with a (texual) link to binary repositories.
  • etc.

Hooks On the client

The free Community Edition (CE) of IntelliJ IDEA from Jetbrains provides check-boxes to activate built-in Git hook functionality:

git intellij before commit 302x418-87kb

PROTIP: We don’t see how to get to Version Control in IntelliJ on videos because it’s often accessed via a hotkey - Apple command 9 on macs and Alt+9 on Windows.

See this video about VCS improvements in IntelliJ 2016.3.

Let’s use a sample project that already has .idea folder containing files that define configurations for IntelliJ.

IntelliJ has a project wizard to start from scratch on various languages.

Default Git Hook files

PROTIP: Every Git repository is created with a hooks folder containing sample hook files that are named so they don’t execute.

  1. Navigate into any Git repository you want to automate.

    cd .git/hooks

    Git and GitHub looks into this specific folder name for scripts to run when specific events occur.

    PROTIP: Git and GitHub have been written such that it recognizes specific file names for each internal event.

    PROTIP: Each Git repository is created with a set of sample automation files in the folder. But the file names end with “.sample” so that they won’t run.

    Listed alphabetically:

    applypatch-msg.sample
    commit-msg.sample
    post-update.sample
    pre-applypatch.sample
    pre-commit.sample
    pre-push.sample
    pre-rebase.sample
    pre-receive.sample
    prepare-commit-msg.sample
    update.sample
    

    There are others that Git automatically runs.

    pre-checkout
    pre-applypatch
    post-applypatch
    post-merge
    post-receive
    pre-auto-gc
    post-rewrite runs after a commit is modified by a git commit --amend or git rebase.
    
  2. PROTIP: Click each link above for the lastest version online at:

    https://github.com/git/git/tree/master/templates

    CAUTION: Nobody uses the default files as they are by just removing the “.sample” part of the file name because they probably don’t do what you want.

  3. Delete the sample files in your repo because it’s best to get the latest version from GitHub.

    No file extension

    Due to their Linux origins, Git and GitHub were written to recognize command files that have no file extension, since in Linux the type of command is defined in the first line within each file (with a “SheBang magic number”) rather than in the file name’s extension as in Windows.

    Multiple languages

    Git will run any script that can run on the command line (and properly installed):

    All commands in a particular hook file must be of the same language.

    Bash hook script

    During installation of git-lfs (GitHub’s Large File System), commands like these are placed in hook files:

    #!/bin/sh
    command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting .git/hooks/post-commit.\n"; exit 2; }
    git lfs post-commit "$@"
    

</pre>

The above commands first checks if git-lfs is installed, then performs the git lfs command, with “$@” forwarding the attributes passed into the hook file.

The hook files used by git-lfs are:

  • pre-push
  • post-checkout
  • post-commit
  • post-merge

#### Python hook script

NOTE: The minimal Python hook script (commit-msg):

#!/usr/bin/env python
# post-checkout hook
import argparse
def parse_args():
  pass
def main(args=None):
  pass
if __name__ == "__main__":
  args = parse_args()
main(args)
   

TODO: Print out values of arguments supplied when calling the hook.

#### PowerShell script

NOTE: To have Git run a PowerShell script, a shell script invokes an additional PowerShell (.ps1) file added in the hooks folder:

#!/bin/sh
# pre-commit (no file extension)
c:/Windows/System32/WindowsPowerShell/v1.0/powershell.exe \
-ExecutionPolicy RemoteSigned -Command .\.git\hooks\pre-commit.ps1
   

Demo simple hooks

I have prepared a repository to demo use of local Git hooks to run. The repo contains a “hooks” folder containing script files. Files in the folder are copied into the .git/hooks folder where Git looks.

  1. In an internet browser, go to this repository (which we will later call the “upstream”):

    https://github.com/wilsonmar/git-utilities

  2. Sign in using your account. The rest of this document assumes “hotwilson” is your account name. So replace “hotwilson” with your own account whereever you see it.

  3. Fork the repo under your own account.

  4. Open a Terminal window.
  5. Make and cd into a folder where you will the repository will be downloaded.

    Again, in this example, you would change “hotwilson” to your own account name.

    
    cd ~ && mkdir gits && cd gits && mkdir hotwilson && cd hotwilson
    
  6. Confirm you are indeed where you want to create a new repository:

    
    pwd
    
  7. Clone to download:

    
    git clone https://github.com/hotwilson/git-utilities.git
    cd git-utilities
    
  8. Optionally, to enable updates from the original repo:

    
    git remote add upstream https://github.com/wilsonmar/git-utilities.git
    git remote -v
    

    Global templates

    NOTE Git looks for hooks in a global template folder applicable to all repositories when either a GIT_TEMPLATE_DIR variable is set in .bash_profile or .gitconfig contains a setting defined by command:

    
    git config --global init.templatedir /path/to/your/templates/
    

    Alternatively, symbolic links or symlinks can be used to link custom hooks to the ones in the .git/hooks folder.

  9. Copy these lines and paste in Terminal (at the root folder) to copy demo hook files from the hooks folder into the .gits/hooks folder:

    
    cp hooks/* .git/hooks
    chmod +x .git/hooks/*
    

    PROTIP: In Mac and Linux machines, scripts must be authorized to be run using the chmod command.

    PROTIP: It is too difficult and dangerous to edit and add/commit files inside the .git folder, which Git itself uses to track changes. So copying files in is the better approach than error messages such as:

    fatal: This operation must be run in a work tree
  10. Copy these lines and paste in the Terminal to make a change, then add and commit it for the Git Hook to take action:

    
    echo "random text" >>README.md
    git add .
    git commit -m"try git commit hooks"
    

    Skip hook invocation

    BTW one can suppress hooks from firing with the --no-verify command attribute:

    git commit --no-verify

    Variables

    Each of the demo hook files can contain variables. For example:

    #!/bin/sh
    COMMIT_MSG_FILE=$1  # ".git/COMMIT_EDITMSG"
    echo "prepare-commit-msg: $(cat $COMMIT_MSG_FILE)"
    exit 0
    

CheatSheet Matrix

Click to expand this cheatsheet (from Daniel Convissor) to see the 15 hooks:

git-hooks-cheat-650x292-64103.jpg

The table lists for each hook what command triggers it, when it runs, what parameters are passed into it, and what happens when the hook exits abnormally (with a 1).

git commit hooks

First of all, there is already some editing to git commit. Git does not allow empty commit messages. If a commit message does not include a message, your favorite editor is opened for you to enter one. If you still haven’t typed anything in your editor, Git aborts the commit.

The flow of processing on the client is illustrated by this (from Johan Abildskov & Jan Krag of Praqma)
git hooks diagram 650x251(click for full screen image)

Upon git commit, client hooks are executed in this order:

  1. The pre-commit hook runs after a git commit is executed, but before the commit message editor is displayed. Since an exit from this with anything other than zero aborts the commit, it is the place to check assets involved in the commit itself (rather than the commit message), to run linters and unit tests on the local laptop. This program has access to the commit date/time, author name and email.

    NOTE: pre-commit hooks do not support hooks with side effects (such as modifying files and adding them to the index with git add).

    A non-zero exit aborts the commit, which can happen if a file being committed contains “#DONTPUSH” (or other keyword recognized by the hook program) developers put in their code as a reminder to hold off pushing to the team repository.

    The following pre-commit code keeps debugging code (such as “console.log” debugging statements) from reaching the shared code base:

    FILES_PATTERN='\.(js|coffee)(\..+)?$'
    FORBIDDEN='console.log'
    git diff --cached --name-only | \
     grep -E $FILES_PATTERN | \
     GREP_COLOR='4;5;37;41' xargs grep --color --with-filename -n $FORBIDDEN && echo 'COMMIT REJECTED Found "$FORBIDDEN" references. Please remove them before commiting' && exit 1
    
  2. The prepare-commit-msg hook is invoked after receiving a git commit, just prior to firing up the commit message editor. This hook can edit the commit message in a way that cannot be suppressed. For example, ensuring a capital letter.

    QUESTION: This has access to the commit SHA-1 (when operating on an existing commit)?

  3. The commit-msg hook adjusts the commit message after it has been edited in order to ensure conformity to a standard or to reject based on any criteria. It can abort the commit if it exits with a non-zero value.

  4. The post-commit hook is called after the actual commit is made so that it cannot disrupt the commit. It is mainly used for sending notifications (emails, SMS, Slack, Twitter, etc.). This NOTE describes deploying to a Local Web Server with a Post-Commit Hook.

git push hooks

The order of execution is:

  1. pre-receive run just before pushed files are updated. So it can abort the receive process by exiting with a non-zero status. Thus, it is used to enforce commit policies and reject the entire commit if it is deemed unsatisfactory. (code coverage)

  2. update filters each commit ref made to the remote repository independently. It can be used to reject or accept each ref being pushed.

  3. post-receive is triggered after an update has been done on the remote repository. So it cannot abort the update process. But it can trigger notifications on a successful remote repository update. (send email, run load tests, so that a log of notifications is stored on a remote server.

Sample JavaScript Mocha linter

From https://scotch.io/tutorials/using-git-hooks-in-your-development-workflow#client-side-hooks

#!/bin/bash
 
# Exits with non zero status if tests fail or linting errors exist
num_of_failures=`mocha -R json | grep failures -m 1 | awk '{print $2}' | sed 's/[,]/''/'`
 
errors=`jscs -r inline ./test/test.js`
num_of_linting_errors=`jscs -r junit ./test/test.js | grep failures -m 1 | awk '{print $4}' | sed 's/failures=/''/' | sed s/">"/''/ | sed s/\"/''/ | sed s/\"/''/`
 
if [ $num_of_failures != '0' ]; then
  echo "$num_of_failures tests have failed. You cannot commit until all tests pass.
        Commit exiting with a non-zero status."
  exit 1
fi
 
if [ $num_of_linting_errors !=  '0' ]; then
  echo "Linting errors present. $errors"
  exit 1
fi
   

Useful Hook examples

  • A list of websites offering Git hooks is at Matthew Hudson’s githooks.com, along with projects others have done with hooks. The canonical documentation is at https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks

  • https://longair.net/blog/2011/04/09/missing-git-hooks-documentation/

  • https://github.com/brigade/overcommit#built-in-hooks

  • https://git-scm.com/book/en/v2/Customizing-Git-An-Example-Git-Enforced-Policy

pre-rebase hooks

  1. Use a text editor to create in the hooks folder a “pre-rebase” containing this text described in YouTube video Life’s better with Git hooks - Grumpy Gits [17:58] Nov 16, 2015 by Deepank Vora.

    #!/bin/sh
    echo "Custom rebasing message."
    exit 1
    

    (Unlike Windows, Linux defines file handling in the first line inside the file).

    PROTIP: Non-zero finish aborts the commit.

  2. Enable the new script:

    
    chmod +x pre-rebase
    

    This would avoid messages such as:

    -bash: ./pre-rebase: No such file or directory
    
  3. Run the new script to make sure it works:

    
    ./pre-rebase
    
  4. Open another Terminal shell window to the working directory of the repo to run the git rebase master command.

    You should see the custom message.

    QUESTION: If instead you see:

    Current branch master is up to date.
    

    View pre-commit.sample

  5. Use a text editor Set to always open .sample files with a text editor.

    #!/bin/sh at the top of the file means that it’s a Bash shell script.

    (Author Junio C Hamano is the primary developer of Git, now works at Google.)

applypatch-msg

GitLab

You can connect to the GitLab API using your API key to perform actions inside GitLab when performing, for example, a commit. This you can do from the client side, but of course, you can also use the server-side hooks, such as post-receive to run programs and scripts after they have reached the remote. An example is at:

https://github.com/gitlabhq/gitlab-public-wiki/wiki/Hooks

closes or fixes

  1. The following script is saved as .git/hooks/commit-msg (with no file extension):

    #!/bin/sh
    PRIVATE_TOKEN="API_KEY"
    GITLAB_URL="https://your_gitlab_domain.com"
    URL=`git config --get remote.origin.url`
    PROJECT=`basename ${URL} .git | cut -d':' -f2`
    for issue_id in `grep -o -e "\(closes\|fixes\) #[0-9]\+" $1 | cut
    -d'#' -f2`; do
        curl -X PUT -d "closed=1" \
            ${GITLAB_URL}api/v3/projects/${PROJECT}/issues/${issue_
    id}/?private_token=${PRIVATE_TOKEN}
    done
    
  2. Edit the program with your gitlab URL.

    If this script sees a commit message text containing “closes #” or “fixes #”, the hook closes the issue identified by the number after the # symbol.

  3. Make the file executable to enable this functionality.

    chmod +x *
    

Resources

https://github.com/git/git/blob/master/Documentation/githooks.txt

https://git-scm.com/book/it/v2/Customizing-Git-Git-Hooks is the official guide to Git Hooks.

https://git-scm.com/book/it/v2/Customizing-Git-Git-Hooks is the Git man page on Git Hooks.

https://www.atlassian.com/git/tutorials/git-hooks/local-hooks

http://toroid.org/ams/git-website-howto

023 Introduction to Git Hooks by Dan Gitschooldude

gated-commit with git pre-commit [12:41] by fullstack

Git hooks man page

Andrew Burgess for Tuts [7:28] June 28, 2012 describes run of mocha test processed by a Bash script.

https://github.com/typicode/husky Git hooks made easy

https://github.com/git/git/tree/master/contrib/hooks contains contributor code in the git team’s repo.

More on DevOps

This is one of a series on DevOps:

  1. DevOps_2.0
  2. ci-cd (Continuous Integration and Continuous Delivery)
  3. User Stories for DevOps

  4. Git and GitHub vs File Archival
  5. Git Commands and Statuses
  6. Git Commit, Tag, Push
  7. Git Utilities
  8. Data Security GitHub
  9. GitHub API
  10. TFS vs. GitHub

  11. Choices for DevOps Technologies
  12. Java DevOps Workflow
  13. AWS DevOps (CodeCommit, CodePipeline, CodeDeploy)
  14. AWS server deployment options

  15. Digital Ocean
  16. Cloud regions
  17. AWS Virtual Private Cloud
  18. Azure Cloud Onramp
  19. Azure Cloud
  20. Azure Cloud Powershell

  21. Packer automation to build Vagrant images
  22. Terraform multi-cloud provisioning automation

  23. Powershell Ecosystem
  24. Powershell on MacOS
  25. Powershell Desired System Configuration

  26. Jenkins Server Setup
  27. Jenkins Plug-ins
  28. Jenkins Freestyle jobs
  29. Jenkins2 Pipeline jobs using Groovy code in Jenkinsfile

  30. Dockerize apps
  31. Docker Setup
  32. Docker Build

  33. Maven on MacOSX

  34. Ansible

  35. MySQL Setup

  36. SonarQube static code scan

  37. API Management Microsoft
  38. API Management Amazon

  39. Scenarios for load