Pre-Commit

A framework for managing and maintaining multi-language pre-commit hooks.


Introduction

Git hook scripts are useful for identifying simple issues before submission to code review. It will run the hooks on every commit to automatically point out issues in code such as missing semicolons, trailing whitespace, and debug statements. By pointing these issues out before code review, this allows a code reviewer to focus on the architecture of a change while not wasting time with trivial style nitpicks. For more info Click here

Installation

Python

Requirements

You will need the following sofwares to use this tool.

  1. Python > 3.5
  2. GIT

Setup Virtual Environment

Before starting a project, we should create a virtual environment for that project. To create a virtual environment, we need to install the below package.

$ pip install virtualenv
OR
$ pip3 install virtualenv

Test your installation:

$ virtualenv --version

Go to your project root directory in the terminal. Now, You can create a virtualenv using the following command:

$ virtualenv env

After running this command, a directory named env will be created. This is the directory which contains all the necessary executables to use the packages that a Python project would need. This is where Python packages will be installed.

If you want to specify Python interpreter of your choice, for example Python 3, it can be done using the following command:

$ virtualenv -p /usr/bin/python3 virtualenv_name

Now after creating virtual environment, you need to activate it. Remember to activate the relevant virtual environment every time you work on the project. This can be done using the following command:

For Linux
$ source virtualenv_name/bin/activate
For Windows
$ env\Scripts\activate

Install Pre-Commit

Before you can run hooks, you need to have the pre-commit package manager installed.

Using pip:
pip install pre-commit

OR

pip3 install pre-commit

In a python project, add the following to your requirements.txt (or requirements-dev.txt):

pre-commit

Setting up Pre-commit

create a file named .pre-commit-config.yaml in the project root

Copy the below configuration and paste it into your yaml file. This configuration has been tested with python 3.6, 3.7, and 3.8. We can upgrade or downgrade any hooks by changing the rev: attribute in the YAML file. But that hook version should compatible with your python version.

exclude: 'docs|node_modules|migrations|.git|.tox'

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
  rev: v4.1.0
  hooks:
    - id: check-added-large-files
    - id: trailing-whitespace
    - id: double-quote-string-fixer
    - id: end-of-file-fixer
    - id: check-yaml

- repo: https://github.com/psf/black
  rev: '22.1.0'
  hooks:
    - id: black
      args: [--line-length=120, --skip-string-normalization]

- repo: https://github.com/asottile/reorder_python_imports
  rev: v2.0.0
  hooks:
    - id: reorder-python-imports

- repo: https://gitlab.com/pycqa/flake8
  rev: 4.0.1
  hooks:
    - id: flake8
      additional_dependencies: [
      'flake8-comprehensions',
      'flake8-deprecated',
      'flake8-mutable',
      'flake8-string-format',
      'flake8-tidy-imports']
      args: ['--config=setup.cfg', '--max-line-length=120', --extend-ignore=E203]

- repo: https://github.com/asottile/pyupgrade
  rev: v2.31.0
  hooks:
    - id: pyupgrade

- repo: https://github.com/PyCQA/pylint
  rev: v2.12.2
  hooks:
    - id: pylint
      args: [--max-line-length=120, --disable=all, --enable=W0102, --enable= R1714, --enable=C0116,
      --enable= C0115, --enable=W0107, --enable=E1101, --enable=C0114]
                                    

Install the git hook scripts

Run the below command to set up the git hook scripts

$ pre-commit install

Now pre-commit will run automatically on git commit!

Example

Let us take the below python code for example.

                            


                            


import pandas
import os
import sys
import threading


class Animal:
    def __init__(      self, source=''):
        self.init = "This is the class welcome message for Animal class. This init function will be invoked when the object is created."
        if source:
            print('Call from ' + source)
        else:
            print(self.init)

    def who_am_i(self):
        listNumbers = [1, 2, 3, 4, 5]
        print('I am a animal')

    def eat(self):
        print('I am eating')


class students_details:
    def find_grade     (     self, mark):
        if mark > 90:
            letter = 'A'
        elif mark > 80:
            letter = 'B'
        elif mark > 60:
            letter = 'C'
        elif mark > 50:
            letter = 'D'
        elif mark > 40:
            letter = 'E'
        return letter

    def get_medal(self, grade):
        if grade == 'A' or grade is 'B':
            return 'GOLD MEDAL'
        if grade == 'C' or grade == 'D':
            return 'SILVER MEDAL'
        if grade == 'E':
            return 'Fail'


if __name__ == '__main__':
    animal = Animal()
    animal.who_am_i()
    animal.eat()

    grade = students_details()
    grade_letter = grade.find_grade(85)
    grades = ['A', 'B', 'C', 'D', 'E']
    all_grade = {grade.get_medal(x) for x in grades}
    print(all_grade)





    








                        

Add and commit the file in the GIT. For the first commit, it will take some time to set up the environment. Please be patient until completing.

$ git add sample.py 
$ git commit sample.py -m "test pre commit tool"
                        

The pre-commit tool will check the code and suggest errors/changes whenever the git commit command is executed. Some changes will be made automatically by the pre-commit tool. Here are errors/changes suggested by pre-commit for the above code

pre-commit error

After we have fixed all the issues suggested by the pre-commit tool, see the final code. It looks clean and beautiful !!!

                            '''
                            This is the docstring of the sample class file to demonstrate pre-commit functionality
                            '''
                            
                            
                            class Animal:
                                '''
                                This is the docstring of the Animal class
                                '''
                            
                                def __init__(self, source=''):
                                    self.init = """This is the class welcome message for Animal class.
                                    This init function will be invoked when the object is created."""
                                    if source:
                                        print('Call from ' + source)
                                    else:
                                        print(self.init)
                            
                                def who_am_i(self):
                                    '''
                                    This is the docstring of method
                                    '''
                                    print('I am a animal')
                            
                                def eat(self):
                                    '''
                                    This is the docstring of method
                                    '''
                                    print('I am eating')
                            
                            
                            class students_details:
                                '''
                                This is the docstring of the student_details class
                                '''
                            
                                def find_grade(self, mark):
                                    '''
                                    This is the docstring of method
                                    '''
                                    if mark > 90:
                                        letter = 'A'
                                    elif mark > 80:
                                        letter = 'B'
                                    elif mark > 60:
                                        letter = 'C'
                                    elif mark > 50:
                                        letter = 'D'
                                    elif mark > 40:
                                        letter = 'E'
                                    return letter
                            
                                def get_medal(self, grade):
                                    '''
                                    This is the docstring of method
                                    '''
                                    if grade in ('A', 'B'):
                                        return 'GOLD MEDAL'
                                    if grade in ('C', 'D'):
                                        return 'SILVER MEDAL'
                                    if grade == 'E':
                                        return 'Fail'
                            
                            
                            if __name__ == '__main__':
                                animal = Animal()
                                animal.who_am_i()
                                animal.eat()
                            
                                grade = students_details()
                                grade_letter = grade.find_grade(85)
                                grades = ['A', 'B', 'C', 'D', 'E']
                                all_grade = {grade.get_medal(x) for x in grades}
                                print(all_grade)
                            
                        

If you want to fix some issues and check with the pre-commit tool, we can run the below command.

$ pre-commit run --files sample.py

PHP

Requirements

You will need the following sofwares to use this tool.

  1. Python > 3.5
  2. PHP >= 7.3
  3. Composer
  4. GIT

Install Pre-Commit

Before installing the pre-commit tool you should have installed python > 3.5 in your system. Run the below command in your terminal.

Using pip:
pip install pre-commit

OR

pip3 install pre-commit

Install PHP git hook package

Before installing the PHP git hook package you should have installed the composer in your system. Run the below commands in your terminal. These packages will be added to your composer.json file. And those packages are available in your vendor folder.

Using Composer:
$ composer require --dev "squizlabs/php_codesniffer=*"
$ composer require --dev friendsofphp/php-cs-fixer
$ composer require --dev phpstan/phpstan

Setting up Pre-commit

create a file named .pre-commit-config.yaml in the project root

Copy the below configuration and paste it into your yaml file. We can upgrade or downgrade any hooks by changing the rev: attribute in the YAML file.

exclude: 'docs|node_modules|migrations|.git|.tox|vendor|venv'

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
  rev: v4.1.0
  hooks:
    - id: check-added-large-files
    - id: end-of-file-fixer
    - id: check-yaml
- repo: https://github.com/digitalpulp/pre-commit-php.git
  rev: 1.4.0
  hooks:
  - id: php-lint-all
    name: PHP Syntax Check (Comprehensive)
    description: Check PHP Syntax on ALL PHP staged files with user friendly messages and colors
    entry: pre_commit_hooks/php-lint.sh
    language: script
    files: \.php$
    args: [-s all]
  - id: php-cbf
    name: PHP Codesniffer (Code Beutifier and Formatter)
    description: Run php codesniffer against all staged PHP files.
    entry: pre_commit_hooks/php-cbf.sh
    language: script
    files: \.php$
  - id: php-cs
    name: PHP Codesniffer
    description: Run php codesniffer against all staged PHP files.
    #you can change the standard check from these anyone(PEAR, PSR1, PSR12, PSR2, Squiz, and Zend)
    entry: pre_commit_hooks/php-cs.sh --standard=PEAR 
    language: script
    files: \.php$
  - id: php-cs-fixer
    name: PHP Coding Standards Fixer
    description: Run php coding standards fixer against all staged PHP files.
    entry: pre_commit_hooks/php-cs-fixer.sh
    language: script
    files: \.php$
  - id: php-stan
    name: PHPStan
    description: Run PHPStan against all staged PHP files.
    entry: pre_commit_hooks/php-stan.sh
    language: script
    files: \.php$

                                
                            

Install the git hook scripts

Run the below command to set up the git hook scripts

$ pre-commit install

Now pre-commit will run automatically on git commit!

Example

Let us take the below PHP code for example.





namespace App\Http\Controllers;

use Illuminate\Http\Request;

class LoginRegisterController extends Controller
{
    public function pre_commit_demo()
    {
        return view('pre_commit_demo_view');
    }

    public function validate_form(Request $request)
    {
        $this->validate($request,
            [
            'firstname' => 'required|min:5|max:35',
            'lastname' => 'required|min:5|max:35',
            'email' => 'required|email|unique:users',
            'mobileno' => 'required|numeric',
            'password' => 'required|min:3|max:20',
            'confirm_password' => 'required|min:3|max:20|same:password',
            'details' => 'required'
            ],
            [
            'firstname.required' => ' The first name field is required.',
            'firstname.min' => ' The first name must be at least 5 characters.',
            'firstname.max' => ' The first name may not be greater than 35 characters.',
            'lastname.required' => ' The last name field is required.',
            'lastname.min' => ' The last name must be at least 5 characters.',
            'lastname.max' => ' The last name may not be greater than 35 characters.',
            ]
        );
        dd('You are successfully added all fields.');
    }
}





                            
                    

Add and commit the file in the GIT. For the first commit, it will take some time to set up the environment. Please be patient until completing.

$ git add LoginRegisterController.php
$ git commit LoginRegisterController.php -m "test pre commit tool"
                    

The pre-commit tool will check the code and suggest errors/changes whenever the git commit command is executed. Some changes will be made automatically by the pre-commit tool. Here are errors/changes suggested by pre-commit for the above code

pre-commit error

After we have fixed all the issues suggested by the pre-commit tool, see the final code. It looks clean and beautiful !!!

/**
 * PHP version 7.4
 * Controller for Login and Register modules
 *
 * @category Controller_File
 * @package  LoginRegisterController
 * @author   Sheik Azarudeen <sheik.azarudeen@galaxyweblinks.in>
 * @license  https://www.gnu.org/copyleft/gpl.html GNU General Public License
 * @link     https://www.galaxyweblinks.com/ YOUR PROJECT URL
 */

namespace App\Http\Controllers;

use Illuminate\Http\Request;

/**
 * Render and validate the Login and Register form
 *
 * @category Controller_Class
 * @package  LoginRegisterController
 * @author   Sheik Azarudeen <sheik.azarudeen@galaxyweblinks.in>
 * @license  https://www.gnu.org/copyleft/gpl.html GNU General Public License
 * @link     https://www.galaxyweblinks.com/ YOUR PROJECT URL
 */
class LoginRegisterController extends Controller
{
    /**
     * Render form template
     *
     * @return view
     */
    public function preCommitDemo()
    {
        return view('pre_commit_demo_view');
    }

    /**
     * Define validation rules
     *
     * @param Request $request   Pass the http request header
     * @param string  $page_from To identify the request made from which page
     *
     * @return bool
     */
    public function validateForm(Request $request, $page_from)
    {
        $this->validate(
            $request,
            [
            'firstname' => 'required|min:5|max:35',
            'lastname' => 'required|min:5|max:35',
            'email' => 'required|email|unique:users',
            'mobileno' => 'required|numeric',
            'password' => 'required|min:3|max:20',
            'confirm_password' => 'required|min:3|max:20|same:password',
            'details' => 'required'
            ],
            [
            'firstname.required' => ' The first name field is required.',
            'firstname.min' => ' The first name must be at least 5 characters.',
            'firstname.max' => ' The first name may not be greater than 
            35 characters.',
            'lastname.required' => ' The last name field is required.',
            'lastname.min' => ' The last name must be at least 5 characters.',
            'lastname.max' => ' The last name may not be greater than 
            35 characters.',
            ]
        );
        dd('You are successfully added all fields.');
    }
}
                       
                    

Note

Before committing, the file should be staged. Because the pre-commit tool will look only at staged version files. If you want to skip the git add command, use the below command. But this command will stage & commit all modified and deleted files.


$ git commit -a -m "Pre-commit test"