How to use git-hooks to make better commit
Tags: bash, git, npm, node
Published: 2023-07-31 07:19:47

**The following example will use nodejs tools, but you can use any similiar tools in your language. Also this post assumes you already have linting tools configured in your project.**

Git hooks is one of git features which will trigger a custom scripts when a certain action occurs. There is multiple git-hooks actions, but today we will use 2 of client side hooks which will help you or your team writes better commit message and changes.



![git hooks flow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nz244hy5xgz2kzl7olhp.png)


## pre-commit
This hooks will be triggered before commiting the changes to git, we will use this hooks to lint/optimize our changes using below scripts. [SVGO](https://www.npmjs.com/package/svgo) is an amazing npm package that will optimize your svg files, the same implementation on https://jakearchibald.github.io/svgomg/

```json
{
  "scripts": {
    // This will allow the lint to automatically fix our staged files
    "format-eslint": "eslint --fix",
    "format-prettier-eslint": "prettier-eslint --log-level 'silent' --write --eslint-config-path .eslintrc.js",
    "format-stylelint": "stylelint --config .stylelintrc.json --fix",
    "optimize-svgo": "svgo"
  }
}
```

```bash
#!/bin/bash
PASS=true

# 'git diff --diff-filter=d --cached --name-only' will output the staged file names
# with 'grep -E' we can capture the output and filter out the files we wanted to lint using RegExp
STAGED_FILES=$(git diff --diff-filter=d --cached --name-only | grep -E '.*\.(js|vue|ts|svg)$')

# This will exit the script if there is no files matching the pattern
if [[ "$STAGED_FILES" = "" ]]; then
  exit 0
fi

# '\033[0;33m' is yellow bash color
# '\e[0m' means we reset the color back to normal
echo -e "\033[0;33m☐ Running pre-commmit-hooks:\e[0m"

for FILE in $STAGED_FILES
do
  # This will capture the initial has of the file, we use this to detect if there is changes applied after linting
  PRE_LINT_HASH=$(git hash-object "$FILE")

  # Only run eslint/prettier/stylelint on vue/js/ts files
  if [[ $FILE == *vue  || $FILE == *js || $FILE == *ts ]];
  then
    # Only run stylelint on vue files since other file types doesn't contain stylesheet
    if [[ $FILE == *vue ]];
    then
      npm run --silent format-stylelint "$FILE" || PASS=false
      npm run --silent format-prettier-eslint "$FILE" || PASS=false
      npm run --silent format-eslint "$FILE" || PASS=false
    else
      npm run --silent format-prettier-eslint "$FILE" || PASS=false
      npm run --silent format-eslint "$FILE" || PASS=false
    fi
  # SVGO optimize svg files
  elif [[ $FILE == *svg ]]
  then
    npm run optimize-svgo "$FILE"
  fi

  POST_LINT_HASH=$(git hash-object "$FILE")
  # If there is changes detected, we will set the PASS flag to false to stop the commit from continuing
  if [[ "$PRE_LINT_HASH" != "$POST_LINT_HASH" ]]; then
    PASS=false
  fi
done

# Stop the commit if there is file changes during above process
if ! $PASS; then
  # '\033[0;31m' is red bash color
  echo -e "\033[0;31m☒ pre-commmit-hooks failed, please check all the errors or stage all changes\e[0m"
  # 'exit 1' will stop the process by throwing error
  exit 1
fi

exit $?
```

The output will looks something like this
```shell
-> % git commit -m "foo"
☐ Running pre-commmit-hooks:

~/project/foo.js
  82:11  error  Identifier 'foo_fighter' is not in camel case  camelcase

✖ 1 problem (1 error, 0 warnings)

☒ pre-commmit-hooks failed, please check all the errors or stage all changes
```

## prepare-commit-msg
This hooks will be triggered before entering the commit message CLI. Sometimes we make silly or undescriptive commit message, using this hooks we will append our branch name into the beginning of the commit message so we can see the branch name during git blame. If you're using JIRA or any other issue tracker board, usually we create a branch name with their codes, example: `git checkout -b PROD-34`.

```bash
#!/bin/bash

# This way you can customize which branches should be skipped when
# prepending commit message. 
if [ -z "$BRANCHES_TO_SKIP" ]; then
	BRANCHES_TO_SKIP=(master)
fi

# Get current commit branch name
BRANCH_NAME=$(git symbolic-ref --short HEAD)
BRANCH_NAME="${BRANCH_NAME##*/}"

BRANCH_EXCLUDED=$(printf "%s\n" "${BRANCHES_TO_SKIP[@]}" | grep -c "^$BRANCH_NAME$")
BRANCH_IN_COMMIT=$(grep -c "\[$BRANCH_NAME\]" $1)

# '-n' is a test pattern, we check if the branch name matches with the one we wanted to skip
if [ -n "$BRANCH_NAME" ] && ! [[ $BRANCH_EXCLUDED -eq 1 ]] && ! [[ $BRANCH_IN_COMMIT -ge 1 ]]; then 
  # This will append the commit message with '[branch-name]:'
	sed -i.bak -e "1s/^/[$BRANCH_NAME]: /" $1
fi
```

With above script if we do
```bash
-> % git commit -m "foo"
[PROD-34 abc123456] [PROD-34]: foo
 1 files changed, 2 insertions(+), 3 deletions(-)
 create mode 100644 foo.js
```

---
**But we can't install git-hooks on remote project, only locally**

Yes, that's why we're going to use the `postinstall` script. This will get triggered after running `npm install`, we will create a simple `nodejs` script to copy the script files we've prepared into `.git/hooks` folder after user has finished installing the project `node_modules`. Here I'm saving my hooks into `git-hooks` folder.

```json
{
  "scripts": {
    "postinstall": "node build/post-install.js",
  }
}
```

```javascript
// post-install.js
const fs = require('fs')

// destination will be created or overwritten by default.
fs.copyFile('./git-hooks/prepare-commit-msg', './.git/hooks/prepare-commit-msg', err => {
  if (err) throw err
  console.log('File was copied to destination')
})

// destination will be created or overwritten by default.
fs.copyFile('./git-hooks/pre-commit', './.git/hooks/pre-commit', err => {
  if (err) throw err
  console.log('File was copied to destination')
})
```

And that's how you can use git hooks to create some automation for your team code quality. You can check my [dotfiles](https://github.com/devTeaa/dotfiles) if you would like to see other helpful script that I used during development.

## [LinkedIn](https://www.linkedin.com/in/herman-h-8b9b49186/)
## [Github](https://github.com/devTeaa)