pre-commit / pre-commit-hooks

Some out-of-the-box hooks for pre-commit
MIT License
5.37k stars 709 forks source link

How do we check whether a commit message has a prefix? #460

Closed gaurav517 closed 4 years ago

gaurav517 commented 4 years ago

How do we check whether a commit message has a prefix e.g. JIRA-XXXXX ?

Thanks

asottile commented 4 years ago

commit-msg / prepare-commit-msg hooks are passed a single filename as a positional argument -- that file contains the commit message

you'd presumably write some code to validate that

or you could use a pygrep hook probably, here's an example which requires [A-Z]+-[0-9]+:

repos:
-   repo: local
    hooks:
    -   id: jira-ticket
        name: check for jira ticket
        language: pygrep
        entry: '\A(?![A-Z]+-[0-9]+)'
        args: [--multiline]
        stages: [commit-msg]

there's a bit of regex wizardry going on there, I've set the following:

$ git commit --allow-empty -m 'no ticket here'
check for jira ticket....................................................Failed
- hook id: jira-ticket
- exit code: 1

.git/COMMIT_EDITMSG:1:no ticket here

$ git commit --allow-empty -m 'JIRA-123: this works'
check for jira ticket....................................................Passed
[master (root-commit) eb841fc] JIRA-123: this works
 1 file changed, 9 insertions(+)
 create mode 100644 .pre-commit-config.yaml
$ git commit --allow-empty -m $'JIRA-123: this works\n\nand with newlines'
check for jira ticket....................................................Passed
[master e7d3331] JIRA-123: this works
gaurav517 commented 4 years ago

not working for me.. anything I am doing wrong? Thanks.

 ~/Documents/my-project: cat .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: jira-ticket-check
        name: check for jira ticket
        language: pygrep
        entry: '\A(?!JIRA-[0-9]+)'
        args: [--multiline]
        stages: [commit-msg]
 ~/Documents/my-project: pre-commit install
pre-commit installed at .git/hooks/pre-commit
 ~/Documents/my-project: git commit --allow-empty -m 'no ticket'
[add_commit_hook 74b6ad57392] no ticket
 ~/Documents/my-project: pre-commit --version
pre-commit 2.0.1
asottile commented 4 years ago

you need to make sure you install the commit-msg hook -- you've only installed the pre-commit hook

gaurav517 commented 4 years ago

Thanks its working now. :) Any way we can change the message shown to user? Currently it shows:

check for jira ticket....................................................Failed
- hook id: jira-ticket-check
- exit code: 1

.git/COMMIT_EDITMSG:1:JIRA- ticket

would be nice if it shows why it failed (one line description). Thanks.

asottile commented 4 years ago

yes, the name configures the message

gaurav517 commented 4 years ago

thanks.

gaurav517 commented 4 years ago

Hello, I am struggling to create regex for checking commit messages that can contain multiple jira tickets. e.g. valid commits:

fix: PROJECTA-12345 is a fix
feat: PROJECTB-12345 is a feature
feat: PROJECTC-12345 is another feature

invalid commits:

fix: this does not contain jira ticket

not well familiar with pygrep.. can't really find doc as well. any suggestion for that?

Thanks.

asottile commented 4 years ago

the docs are here

gaurav517 commented 4 years ago

Thanks. I have written a test for this.. but haven't been able to figure out yet the right regex: tried various patterns, e.g. (?!PROA-[0-9]+) | (?!PROB-[0-9]+), '(?!.*PROA-[0-9]+.*) | (?!.*PROB-[0-9]+.*)', .*((?!PROA-[0-9]+) | (?!PROB-[0-9]+)).* and more. still trying.

import re
from string import Template

valid_commit_message_template = Template('$commit is a valid commit')
invalid_commit_nsg_template = Template('$commit is an invalid commit')
pattern = '(?!((PROA-[0-9]+) | (PROB-[0-9]+)))'

def test_valid_commits(commits_to_match):
    for commit in commits_to_match:
        x = re.search(pattern, commit)
        assert x is None, valid_commit_message_template.substitute(dict(commit=commit))

def test_invalid_commits(commits_to_match):
    for commit in commits_to_match:
        x = re.search(pattern, commit)
        assert x is not None, invalid_commit_nsg_template.substitute(dict(commit=commit))

test_valid_commits(['PROA-12345', 'PROB-12345', 'fix: PROA-12345 test', 'fix: PROB-12345 test', 'fix: PROA-12345'])
test_invalid_commits(['PROA-test', 'fix', 'fix: PROA-', 'PROB-test', 'PROC-12345'])

anything I am doing obvious wrong? thanks.

asottile commented 4 years ago

you'll need to anchor your pattern, with re.search it is going to trivially match the negative lookahead at every position in your string (minus the exact negative match)

also I'd suggest using .format() instead of string.Template but that's kinda unrelated

gaurav517 commented 4 years ago

Thanks with your tips, at least its working now for one project type with ^(?!.*PROA-[0-9]+).. now trying a pattern for multiple project types.

asottile commented 4 years ago

for multiple negative assertions in regex you'll use ^(?!...)(?!...)(?!...)

gaurav517 commented 4 years ago

thanks. ^(?!.*PROA-[0-9]+)(?!.*PROB-[0-9]+) works in the test. But its not working when used with pre-commit:

repos:
  - repo: local
    hooks:
    - id: jira-ticket-check
      name: commit message should have jira ticket
      language: pygrep
      entry: '^(?!.*PROA-[0-9]+)(?!.*PROB-[0-9]+)'
      args: [--multiline]
      stages: [commit-msg]

when I do: git commit -a -m 'fix: PROA-12345 test', it says:

commit message should have jira ticket...................................Failed
- hook id: jira-ticket-check
- exit code: 1

.git/COMMIT_EDITMSG:2:

any suggestions? thanks.

asottile commented 4 years ago

--multiline implies DOTALL and MULTILINE and the commit message filename will end in a newline

>>> reg_s = yaml.load("""entry: '^(?!.*PROA-[0-9]+)(?!.*PROB-[0-9]+)'""")['entry']
>>> reg = re.compile(reg_s, re.MULTILINE | re.DOTALL)
>>> reg.search('fix: PROA-12345 test')
>>> reg.search('fix: PROA-12345 test\n')
<re.Match object; span=(21, 21), match=''>

I believe you want to use \A instead of ^ to anchor at the very beginning instead of the beginning of each line

>>> reg_s = yaml.load(r"""entry: '\A(?!.*PROA-[0-9]+)(?!.*PROB-[0-9]+)'""")['entry']
>>> reg = re.compile(reg_s, re.MULTILINE | re.DOTALL)
>>> reg.search('fix: PROA-12345 test\n')
>>> reg.search('fix: PROC-12345 test\n')
<re.Match object; span=(0, 0), match=''>
gaurav517 commented 4 years ago

Thanks a lot.. \A instead of ^ worked.

gaurav517 commented 4 years ago

I am planning to make a plugin for this purpose. So following https://pre-commit.com/#new-hooks, I

gaurav517 commented 4 years ago

I also tried .pre-commit-hooks.yaml:

- id: jira-ticket-check
  name: commit message should have jira ticket
  description: checks for jira ticket in commit message
  entry: '^(?!.*PROA-[0-9]+)(?!.*PROB-[0-9]+)(?!.*PROC-[0-9]+)(?!.*PROD-[0-9]+)'
  args: [--multiline]
  language: pygrep

and .pre-commit-config.yaml:

repos:
  - repo: mygithub:myorg/my-repo
    rev: master
    hooks:
      - id: jira-ticket-check
        stages: [commit-msg]

still check is failing for all messages.

asottile commented 4 years ago

you should not use rev: master

gaurav517 commented 4 years ago

thanks it worked with sha and I also had to change regex from ^(?!..)(?!..)(?!..) to \A(?!..)(?!..)(?!..).

ag-monk commented 3 years ago

thanks it worked with sha and I also had to change regex from ^(?!..)(?!..)(?!..) to \A(?!..)(?!..)(?!..).

@gaurav517 can you please share snips how you set this up?

gaurav517 commented 3 years ago

@ag-monk Snippet that worked for me:

- id: jira-ticket-check
  name: commit message should have jira ticket
  description: checks for jira ticket in commit message
  entry: '\A(?!.*PROJA-[0-9]+)(?!.*PROJB-[0-9]+)(?!.*PROJC-[0-9]+)(?!.*PROJD-[0-9]+)(?!.*PROJE-[0-9]+)'
  args: [--multiline]
  language: pygrep
  stages: [commit-msg]
ag-monk commented 3 years ago

Thanks this worked for me.. did u also do anything for allowing a commit to a branch only when state of jira ticket is open or similar scenario? Can you give suggestions for this?

asottile commented 3 years ago

you'd have to write code using the jira api

oliverchiu1993 commented 2 years ago

Hi guys, I'm also struggling with the regex expression. I want to match the commit message such as "ERP-18 #feat test", but no mater what I entered, I always get true

repos:
  - repo: local
    hooks:
      - id: jira-ticket
        name: check for jira ticket
        entry: '\A(ERP-[0-9]+ #(feat|fix|docs|style|refactor|perf|test|chore) .+)\Z'
        args: [--multiline]
        language: pygrep
        stages: [commit-msg]
asottile commented 2 years ago

your regex doesn't follow the structure I outlined above

RobinMaub commented 2 years ago

Hi guys,

I would like to check if my commit message contains "Changelog: " (for gitlab changelog auto-generation). Here is my hook `

But this nevers works. I installed the commit-msg hook.

Does anyone have an idea =

asottile commented 2 years ago

"doesn't work" is not a bug report -- please open a new issue and go through the template and show what you're seeing and what you expect