{"id":16934033,"url":"https://github.com/lucas-c/pre-commit-hooks","last_synced_at":"2025-06-25T00:09:42.273Z","repository":{"id":31341292,"uuid":"34903976","full_name":"Lucas-C/pre-commit-hooks","owner":"Lucas-C","description":"git pre-commit hooks","archived":false,"fork":false,"pushed_at":"2024-02-13T21:21:34.000Z","size":218,"stargazers_count":111,"open_issues_count":8,"forks_count":48,"subscribers_count":6,"default_branch":"master","last_synced_at":"2024-05-22T17:30:52.865Z","etag":null,"topics":["crlf","git-hooks","license","pre-commit","tabs"],"latest_commit_sha":null,"homepage":"http://pre-commit.com/hooks.html","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Lucas-C.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-05-01T13:26:10.000Z","updated_at":"2024-08-02T13:18:05.714Z","dependencies_parsed_at":"2024-01-17T01:16:31.668Z","dependency_job_id":"4a9b4e01-c203-4288-bb03-4c3540896ff2","html_url":"https://github.com/Lucas-C/pre-commit-hooks","commit_stats":null,"previous_names":[],"tags_count":30,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Lucas-C%2Fpre-commit-hooks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Lucas-C%2Fpre-commit-hooks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Lucas-C%2Fpre-commit-hooks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Lucas-C%2Fpre-commit-hooks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Lucas-C","download_url":"https://codeload.github.com/Lucas-C/pre-commit-hooks/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243852425,"owners_count":20358270,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["crlf","git-hooks","license","pre-commit","tabs"],"created_at":"2024-10-13T20:51:08.316Z","updated_at":"2025-06-25T00:09:41.570Z","avatar_url":"https://github.com/Lucas-C.png","language":"Python","readme":"[![build status](https://github.com/Lucas-C/pre-commit-hooks/workflows/CI/badge.svg)](https://github.com/Lucas-C/pre-commit-hooks/actions?query=branch%3Amaster)\n\nA few useful git hooks to integrate with\n[pre-commit](http://pre-commit.com).\n\n⚠️ ⚠️ **This hook, since v1.5.2, requires `pre-commit` 3.2.0 or superior.**\nIf you get an error like `Expected one of ... but got: 'pre-commit'`, check\nthis issue: [#83](https://github.com/Lucas-C/pre-commit-hooks/issues/83)\n\n⚠️ **The last version of this hook to support Python 2.7 \u0026 3.6 is v1.1.15**\n\n\u003c!-- mdformat-toc start --slug=github --no-anchors --maxlevel=4 --minlevel=1 --\u003e\n\n- [Usage](#usage)\n  - [insert-license](#insert-license)\n    - [Comment styles](#comment-styles)\n    - [How to specify in how many lines to search for the license header in each file](#how-to-specify-in-how-many-lines-to-search-for-the-license-header-in-each-file)\n    - [Removing old license and replacing it with a new one](#removing-old-license-and-replacing-it-with-a-new-one)\n    - [Handling years flexibly](#handling-years-flexibly)\n    - [No extra EOL](#no-extra-eol)\n    - [Fuzzy license matching](#fuzzy-license-matching)\n    - [Multiple license files](#multiple-license-files)\n- [Handy shell functions](#handy-shell-functions)\n- [Useful local hooks](#useful-local-hooks)\n  - [Forbid / remove some unicode characters](#forbid--remove-some-unicode-characters)\n  - [Bash syntax validation](#bash-syntax-validation)\n  - [For Groovy-like Jenkins pipelines](#for-groovy-like-jenkins-pipelines)\n  - [Forbid some Javascript keywords for browser retrocompatibility issues](#forbid-some-javascript-keywords-for-browser-retrocompatibility-issues)\n  - [CSS](#css)\n  - [Some Angular 1.5 checks](#some-angular-15-checks)\n- [Development](#development)\n  - [Releasing a new version](#releasing-a-new-version)\n\n\u003c!-- mdformat-toc end --\u003e\n\nHooks specific to a language, or with more dependencies have been extracted\ninto separate repos:\n\n- https://github.com/Lucas-C/pre-commit-hooks-bandit\n- https://github.com/Lucas-C/pre-commit-hooks-go\n- https://github.com/Lucas-C/pre-commit-hooks-java\n- https://github.com/Lucas-C/pre-commit-hooks-lxml\n- https://github.com/Lucas-C/pre-commit-hooks-markup\n- https://github.com/Lucas-C/pre-commit-hooks-nodejs\n- https://github.com/Lucas-C/pre-commit-hooks-safety\n\n## Usage\n\nSee [hook definitions](./.pre-commit-hooks.yaml) for additional hook\ndocumentation.\n\nAll hooks work on text files, except for the `chmod` hook that applies to\nall files.\n\n```yaml\n- repo: https://github.com/Lucas-C/pre-commit-hooks\n  rev: v1.5.5\n  hooks:\n    - id: forbid-crlf  # Forbid files containing CRLF end-lines to be committed\n    - id: remove-crlf  # Replace CRLF end-lines by LF ones before committing\n    - id: forbid-tabs  # Forbid files containing tabs to be committed\n    - id: remove-tabs  # Replace tabs by whitespaces before committing\n      args: [--whitespaces-count, '2']  # defaults to: 4\n    - id: chmod  # Set file permissions\n      args: ['644']\n      files: \\.md$\n    - id: insert-license  # Insert a short license disclaimer as a header comment in source files\n      files: \\.groovy$\n      args:\n        - --license-filepath\n        - src/license_header.txt        # defaults to: LICENSE.txt\n        - --comment-style\n        - //                            # defaults to:  #\n        - --use-current-year\n        - --no-extra-eol                # see below\n```\n\n### insert-license\n\n#### Comment styles\n\nThe following styles can be used for example:\n\n- For Java / Javascript / CSS/ C / C++ (multi-line comments) set\n  `/*| *| */` ;\n- For Java / Javascript / C / C++ (single line comments) set `//` ;\n- For HTML files: `\u003c!--|  ~|  --\u003e` ;\n- For Python: `#` ;\n- For Jinja templates: `'{#||#}'` .\n\n#### How to specify in how many lines to search for the license header in each file\n\nYou can add `--detect-license-in-X-top-lines=\u003cX\u003e` to search for the license\nin top X lines (default 5).\n\n#### Removing old license and replacing it with a new one\n\nIn case you want to remove the comment headers introduced by\n`insert-license` hook, e.g. because you want to change the wording of your\n`LICENSE.txt` and update the comments in your source files:\n\n1. Temporarily add the `--remove-header` arg in your\n   `.pre-commit-config.yaml` ;\n2. Run the hook on all your files:\n   `pre-commit run insert-license --all-files` ;\n3. Remove the `--remove-header` arg and update your `LICENSE.txt` ;\n4. Re-run the hook on all your files.\n\n#### Handling years flexibly\n\nYou can add `--use-current-year` to change how the hook treats years in the\nheaders:\n\n- When inserting a header, the current year will always be inserted\n  regardless of the year listed in the license file.\n- When modifying a file that already has a header, the hook will ensure the\n  current year is listed in the header by using a range. For instance,\n  `2015` or `2015-2018` would get updated to `2015-2023` in the year 2023.\n- When removing headers, the licenses will be removed regardless of the\n  years they contain -- as if they used the year currently present in the\n  license file.\n\nYou can also use `--allow-past-years` to allow stale years to be unchanged.\nUsing both `--allow-past-years` and `--use-current-year` issues a year\nrange as described above.\n\n#### No extra EOL\n\nThe `--no-extra-eol` argument prevents the insertion of an additional\nEnd-of-Line (EOL) character at the end of the license header; see\n[issue #70](https://github.com/Lucas-C/pre-commit-hooks/issues/70).\n\n#### Fuzzy license matching\n\nIn some cases your license files can contain several slightly different\nvariants of the license - either containing slight modifications or\ndifferently broken lines of the license text.\\\nBy default the plugin does\nexact matching when searching for the license and in such case it will add\nsecond license on top - leaving the non-perfectly matched one in the source\ncode.\\\nYou can prevent that and add `--fuzzy-match-generates-todo` flag in\nwhich case fuzzy matching is performed based on Levenshtein distance of set\nof tokens in expected and actual license text (partial match in two sets is\nused).\\\nThe license is detected if the ratio is \u003e than\n`--fuzzy-ratio-cut-off` parameter (default 85) - ration corresponds roughly\nto how well the expected and actual license match (scale 0 - 100).\nAdditionally `--fuzzy-match-extra-lines-to-check` lines in this case are\nchecked for the license in case it has lines broken differently and takes\nmore lines (default 3).\n\nIf a fuzzy match is found (and no exact match), a TODO comment is inserted\nat the beginning of the match found. The comment inserted can be overridden\nby `--fuzzy-match-todo-comment=\u003cCOMMENT\u003e` flag.\\\nBy default the inserted\ncomment is\n`TODO: This license is not consistent with license used in the project`\nAdditionally instructions on what to do are inserted in this case.\\\nBy\ndefault instructions\nare:\\\n`Delete the inconsistent license and above line and rerun pre-commit to insert a good license.`.\\\nYou\ncan change it via `--fuzzy-match-todo-instructions` argument of the hook.\n\nWhen the TODO comment is committed, pre-commit will fail with appropriate\nmessage. The check will fails systematically if the\n`--fuzzy-match-generates-todo` flag is set or not.\\\nYou will need to remove\nthe TODO comment and license so that it gets re-added in order to get rid\nof the error.\n\nLicense insertion can be skipped altogether if the file contains the\n`SKIP LICENSE INSERTION` in the first X top lines. This can also be\noverridden by `--skip-license-insertion-comment=\u003cCOMMENT\u003e` flag.\n\n#### Multiple license files\n\nIf more than one `--license-filepath` argument is specified, the checks are\nperformed as follows:\n\n1. First, an exact match is pursued, checking the 1st license file, then\n   the 2nd, and so on. If a match is found, the normal behavior is\n   followed, as if the matched license file was the only license file\n   specified.\n\n2. If no exact match is found, then the software resorts to fuzzy matching.\n   Again, as soon as a match is found, the normal behavior is followed, as\n   if the fuzzy-matched license file was the only license file specified.\n\n3. Finally, if neither exact nor fuzzy matches are found, the content of\n   the first license file is inserted.\n\n## Handy shell functions\n\n```shell\npre_commit_all_cache_repos () {  # Requires sqlite3\n    sqlite3 -header -column ~/.cache/pre-commit/db.db \u003c \u003c(echo -e \".width 50\\nSELECT repo, ref, path FROM repos ORDER BY repo;\")\n}\n\npre_commit_local_cache_repos () {  # Requires PyYaml \u0026 sqlite3\n    \u003c $(git rev-parse --show-toplevel)/.pre-commit-config.yaml \\\n        python -c \"from __future__ import print_function; import sys, yaml; print('\\n'.join(h['repo']+' '+h['sha'] for h in yaml.load(sys.stdin) if h['repo'] != 'local'))\" \\\n        | while read repo sha; do\n            echo $repo\n            sqlite3 ~/.cache/pre-commit/db.db \"SELECT ref, path FROM repos WHERE repo = '$repo' AND ref = '$sha';\"\n            echo\n        done\n}\n\npre_commit_db_rm_repo () {  # Requires sqlite3\n    local repo=${1?'Missing parameter'}\n    local repo_path=$(sqlite3 ~/.cache/pre-commit/db.db \"SELECT path FROM repos WHERE repo LIKE '%${repo}%';\")\n    if [ -z \"$repo_path\" ]; then\n        echo \"No repository known for repo $repo\"\n        return 1\n    fi\n    rm -rf \"$repo_path\"\n    sqlite3 ~/.cache/pre-commit/db.db \"DELETE FROM repos WHERE repo LIKE '%${repo}%';\";\n}\n```\n\n## Useful local hooks\n\n### Forbid / remove some unicode characters\n\n```yaml\n- repo: local\n  hooks:\n    - id: forbid-unicode-non-breaking-spaces\n      name: Detect unicode non-breaking space character U+00A0 aka M-BM-\n      language: system\n      entry: perl -ne 'print if $m = /\\xc2\\xa0/; $t ||= $m; END{{exit $t}}'\n      files: ''\n    - id: remove-unicode-non-breaking-spaces\n      name: Remove unicode non-breaking space character U+00A0 aka M-BM-\n      language: system\n      entry: perl -pi* -e 's/\\xc2\\xa0/ /g \u0026\u0026 ($t = 1) \u0026\u0026 print STDERR $_; END{{exit\n        $t}}'\n      files: ''\n    - id: forbid-en-dashes\n      name: Detect the EXTREMELY confusing unicode character U+2013\n      language: system\n      entry: perl -ne 'print if $m = /\\xe2\\x80\\x93/; $t ||= $m; END{{exit $t}}'\n      files: ''\n    - id: remove-en-dashes\n      name: Remove the EXTREMELY confusing unicode character U+2013\n      language: system\n      entry: perl -pi* -e 's/\\xe2\\x80\\x93/-/g \u0026\u0026 ($t = 1) \u0026\u0026 print STDERR $_; END{{exit\n        $t}}'\n      files: ''\n```\n\n### Bash syntax validation\n\n```yaml\n- repo: local\n  hooks:\n  - id: check-bash-syntax\n    name: Check Shell scripts syntax correctness\n    language: system\n    entry: bash -n\n    files: \\.sh$\n```\n\n### For Groovy-like Jenkins pipelines\n\n```yaml\n- repo: local\n  hooks:\n  - id: forbid-abstract-classes-and-traits\n    name: Ensure neither abstract classes nor traits are used\n    language: pygrep\n    entry: \"^(abstract|trait) \"\n    files: ^src/.*\\.groovy$\n```\n\n**Rationale:** `abstract` classes \u0026 `traits` do not work in Jenkins\npipelines : cf. https://issues.jenkins-ci.org/browse/JENKINS-39329 \u0026\nhttps://issues.jenkins-ci.org/browse/JENKINS-46145 .\n\n```yaml\n- repo: local\n  hooks:\n  - id: force-JsonSlurperClassic\n    name: Ensure JsonSlurperClassic is used instead of non-serializable JsonSlurper\n    language: pygrep\n    entry: JsonSlurper[^C]\n    files: \\.groovy$\n```\n\n**Rationale:** cf. http://stackoverflow.com/a/38439681/636849\n\n```yaml\n- repo: local\n  hooks:\n    - id: Jenkinsfile-linter\n      name: Check Jenkinsfile following the scripted-pipeline syntax using Jenkins\n        API\n      files: Jenkinsfile\n      language: system\n      entry: sh -c '! curl --silent $JENKINS_URL/job/MyPipelineName/job/master/1/replay/checkScriptCompile\n        --user $JENKINS_USER:$JENKINS_TOKEN --data-urlencode value@Jenkinsfile |\n        grep -F \"\\\"status\\\":\\\"fail\\\"\"'\n```\n\nNote: the `$JENKINS_TOKEN` can be retrieved from\n`$JENKINS_URL/user/$USER_NAME/configure`\n\nBeware, in 1 case on 6 I faced this unsolved bug with explictly-loaded\nlibraries: https://issues.jenkins-ci.org/browse/JENKINS-42730 .\n\nAlso, there is also a linter for the declarative syntax:\nhttps://jenkins.io/doc/book/pipeline/development/#linter .\n\n### Forbid some Javascript keywords for browser retrocompatibility issues\n\n```yaml\n- repo: local\n  hooks:\n    - id: js-forbid-const\n      name: The const keyword is not supported by IE10\n      language: pygrep\n      entry: 'const '\n      files: \\.js$\n    - id: js-forbid-let\n      name: The let keyword is not supported by IE10\n      language: pygrep\n      entry: 'let '\n      files: \\.js$\n```\n\n### CSS\n\n```yaml\n- repo: local\n  hooks:\n    - id: css-forbid-px\n      name: In CSS files, use rem or % over px\n      language: pygrep\n      entry: px\n      files: \\.css$\n    - id: ot-sanitize-fonts\n      name: Calling ot-sanitise on otf/ttf/woff/woff2 font files\n      language: system\n      entry: sh -c 'type ot-sanitise \u003e/dev/null\n        \u0026\u0026 for font in \"$@\";\n        do echo \"$font\";\n        ot-sanitise \"$font\"; done\n        || echo \"WARNING Command ot-sanitise not found - skipping check\"'\n      files: \\.(otf|ttf|woff|woff2)$\n```\n\n### Some Angular 1.5 checks\n\n```yaml\n- repo: local\n  hooks:\n    - id: angular-forbid-apply\n      name: In AngularJS, use $digest over $apply\n      language: pygrep\n      entry: \\$apply\n      files: \\.js$\n    - id: angular-forbid-ngrepeat-without-trackby\n      name: In AngularJS, ALWAYS use 'track by' with ng-repeat\n      language: pygrep\n      entry: ng-repeat(?!.*track by)\n      files: \\.html$\n    - id: angular-forbid-ngmodel-with-no-dot\n      name: In AngularJS, whenever you have ng-model there's gotta be a dot in\n        there somewhere\n      language: pygrep\n      entry: ng-model=\"?[^.]+[\" ]\n      files: \\.html$\n```\n\n## Development\n\nThe [GitHub releases](https://github.com/Lucas-C/pre-commit-hooks/releases)\nform the historical ChangeLog.\n\n### Releasing a new version\n\n1. Bump version in `README.md`, `setup.py` \u0026 `.pre-commit-config.yaml`\n2. `git commit -nam \"New release $version\" \u0026\u0026 git tag $version \u0026\u0026 git push \u0026\u0026 git push --tags`\n3. Publish a GitHub release.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flucas-c%2Fpre-commit-hooks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flucas-c%2Fpre-commit-hooks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flucas-c%2Fpre-commit-hooks/lists"}