##// END OF EJS Templates
rcextensions: added example for validation of commit messages for git.
marcink -
r3211:ab158566 default
parent child Browse files
Show More
@@ -0,0 +1,91 b''
1 # Example to validate commit message or author using some sort of rules
2
3
4 @has_kwargs({
5 'server_url': 'url of instance that triggered this hook',
6 'config': 'path to .ini config used',
7 'scm': 'type of version control "git", "hg", "svn"',
8 'username': 'username of actor who triggered this event',
9 'ip': 'ip address of actor who triggered this hook',
10 'action': '',
11 'repository': 'repository name',
12 'repo_store_path': 'full path to where repositories are stored',
13 'commit_ids': 'pre transaction metadata for commit ids',
14 'hook_type': '',
15 'user_agent': 'Client user agent, e.g git or mercurial CLI version',
16 })
17 @has_kwargs({
18 'server_url': 'url of instance that triggered this hook',
19 'config': 'path to .ini config used',
20 'scm': 'type of version control "git", "hg", "svn"',
21 'username': 'username of actor who triggered this event',
22 'ip': 'ip address of actor who triggered this hook',
23 'action': '',
24 'repository': 'repository name',
25 'repo_store_path': 'full path to where repositories are stored',
26 'commit_ids': 'pre transaction metadata for commit ids',
27 'hook_type': '',
28 'user_agent': 'Client user agent, e.g git or mercurial CLI version',
29 })
30 def _pre_push_hook(*args, **kwargs):
31 """
32 Post push hook
33 To stop version control from storing the transaction and send a message to user
34 use non-zero HookResponse with a message, e.g return HookResponse(1, 'Not allowed')
35
36 This message will be shown back to client during PUSH operation
37
38 Commit ids might look like that::
39
40 [{u'hg_env|git_env': ...,
41 u'multiple_heads': [],
42 u'name': u'default',
43 u'new_rev': u'd0befe0692e722e01d5677f27a104631cf798b69',
44 u'old_rev': u'd0befe0692e722e01d5677f27a104631cf798b69',
45 u'ref': u'',
46 u'total_commits': 2,
47 u'type': u'branch'}]
48 """
49 import re
50 from .helpers import extra_fields, extract_pre_commits
51 from .utils import str2bool
52
53 # returns list of dicts with key-val fetched from extra fields
54 repo_extra_fields = extra_fields.run(**kwargs)
55
56 # optionally use 'extra fields' to control the logic per repo
57 should_validate = str2bool(repo_extra_fields.get('validate_author', True))
58
59 # optionally store validation regex into extra fields
60 validation_regex = repo_extra_fields.get('validation_regex', '')
61
62 def validate_commit_message(commit_message, message_regex=None):
63 """
64 This function validates commit_message against some sort of rules.
65 It should return a valid boolean, and a reason for failure
66 """
67
68 if "secret_string" in commit_message:
69 msg = "!!Push forbidden: secret string found in commit messages"
70 return False, msg
71
72 if validation_regex:
73 regexp = re.compile(validation_regex)
74 if not regexp.match(message):
75 msg = "!!Push forbidden: commit message does not match regexp"
76 return False, msg
77
78 return True, ''
79
80 if should_validate:
81 # returns list of dicts with key-val fetched from extra fields
82 commit_list = extract_pre_commits.run(**kwargs)
83
84 for commit_data in commit_list:
85 message = commit_data['message']
86
87 message_valid, reason = validate_commit_message(message, validation_regex)
88 if not message_valid:
89 return HookResponse(1, reason)
90
91 return HookResponse(0, '')
@@ -1,63 +1,90 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2016-2018 RhodeCode GmbH
2 # Copyright (C) 2016-2018 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 """
20 """
21 us in hooks::
21 us in hooks::
22
22
23 from .helpers import extract_pre_commits
23 from .helpers import extract_pre_commits
24 # returns list of dicts with key-val fetched from extra fields
24 # returns list of dicts with key-val fetched from extra fields
25 commit_list = extract_pre_commits.run(**kwargs)
25 commit_list = extract_pre_commits.run(**kwargs)
26
26
27 """
27 """
28 import re
28 import re
29 import collections
29 import collections
30 import json
30
31
31
32
32 def get_hg_commits(repo, refs):
33 def get_hg_commits(repo, refs):
33 commits = []
34 commits = []
34 return commits
35 return commits
35
36
36
37
37 def get_git_commits(repo, refs):
38 def get_git_commits(repo, refs):
38 commits = []
39 commits = []
40
41 for data in refs:
42 # we should now extract commit data
43 old_rev = data['old_rev']
44 new_rev = data['new_rev']
45
46 if '00000000' in old_rev:
47 # new branch, we don't need to extract nothing
48 return commits
49
50 git_env = dict(data['git_env'])
51 cmd = [
52 'log',
53 '--pretty=format:{"commit_id": "%H", "author": "%aN <%aE>", "date": "%ad", "message": "%f"}',
54 '{}...{}'.format(old_rev, new_rev)
55 ]
56
57 stdout, stderr = repo.run_git_command(cmd, env=git_env)
58 for line in stdout.splitlines():
59 try:
60 data = json.loads(line)
61 commits.append(data)
62 except Exception:
63 print('Failed to load data from GIT line')
64
39 return commits
65 return commits
40
66
41
67
42 def run(*args, **kwargs):
68 def run(*args, **kwargs):
43 from rhodecode.model.db import Repository
69 from rhodecode.model.db import Repository
44
70
45 vcs_type = kwargs['scm']
71 vcs_type = kwargs['scm']
46 # use temp name then the main one propagated
72 # use temp name then the main one propagated
47 repo_name = kwargs.pop('REPOSITORY', None) or kwargs['repository']
73 repo_name = kwargs.pop('REPOSITORY', None) or kwargs['repository']
48
74
49 repo = Repository.get_by_repo_name(repo_name)
75 repo = Repository.get_by_repo_name(repo_name)
50 vcs_repo = repo.scm_instance(cache=False)
76 vcs_repo = repo.scm_instance(cache=False)
51
77
52 commits = []
78 commits = []
53
79
54 for rev_data in kwargs['commit_ids']:
55 new_environ = dict((k, v) for k, v in rev_data['hg_env'])
56
57 if vcs_type == 'git':
80 if vcs_type == 'git':
81 for rev_data in kwargs['commit_ids']:
82 new_environ = dict((k, v) for k, v in rev_data['git_env'])
58 commits = get_git_commits(vcs_repo, kwargs['commit_ids'])
83 commits = get_git_commits(vcs_repo, kwargs['commit_ids'])
59
84
60 if vcs_type == 'hg':
85 if vcs_type == 'hg':
86 for rev_data in kwargs['commit_ids']:
87 new_environ = dict((k, v) for k, v in rev_data['hg_env'])
61 commits = get_hg_commits(vcs_repo, kwargs['commit_ids'])
88 commits = get_hg_commits(vcs_repo, kwargs['commit_ids'])
62
89
63 return commits
90 return commits
@@ -1,147 +1,185 b''
1 # Copyright (C) 2016-2018 RhodeCode GmbH
1 # Copyright (C) 2016-2018 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import os
19 import os
20 import functools
20 import functools
21 import collections
21 import collections
22
22
23
23
24 class HookResponse(object):
24 class HookResponse(object):
25 def __init__(self, status, output):
25 def __init__(self, status, output):
26 self.status = status
26 self.status = status
27 self.output = output
27 self.output = output
28
28
29 def __add__(self, other):
29 def __add__(self, other):
30 other_status = getattr(other, 'status', 0)
30 other_status = getattr(other, 'status', 0)
31 new_status = max(self.status, other_status)
31 new_status = max(self.status, other_status)
32 other_output = getattr(other, 'output', '')
32 other_output = getattr(other, 'output', '')
33 new_output = self.output + other_output
33 new_output = self.output + other_output
34
34
35 return HookResponse(new_status, new_output)
35 return HookResponse(new_status, new_output)
36
36
37 def __bool__(self):
37 def __bool__(self):
38 return self.status == 0
38 return self.status == 0
39
39
40
40
41 class DotDict(dict):
41 class DotDict(dict):
42
42
43 def __contains__(self, k):
43 def __contains__(self, k):
44 try:
44 try:
45 return dict.__contains__(self, k) or hasattr(self, k)
45 return dict.__contains__(self, k) or hasattr(self, k)
46 except:
46 except:
47 return False
47 return False
48
48
49 # only called if k not found in normal places
49 # only called if k not found in normal places
50 def __getattr__(self, k):
50 def __getattr__(self, k):
51 try:
51 try:
52 return object.__getattribute__(self, k)
52 return object.__getattribute__(self, k)
53 except AttributeError:
53 except AttributeError:
54 try:
54 try:
55 return self[k]
55 return self[k]
56 except KeyError:
56 except KeyError:
57 raise AttributeError(k)
57 raise AttributeError(k)
58
58
59 def __setattr__(self, k, v):
59 def __setattr__(self, k, v):
60 try:
60 try:
61 object.__getattribute__(self, k)
61 object.__getattribute__(self, k)
62 except AttributeError:
62 except AttributeError:
63 try:
63 try:
64 self[k] = v
64 self[k] = v
65 except:
65 except:
66 raise AttributeError(k)
66 raise AttributeError(k)
67 else:
67 else:
68 object.__setattr__(self, k, v)
68 object.__setattr__(self, k, v)
69
69
70 def __delattr__(self, k):
70 def __delattr__(self, k):
71 try:
71 try:
72 object.__getattribute__(self, k)
72 object.__getattribute__(self, k)
73 except AttributeError:
73 except AttributeError:
74 try:
74 try:
75 del self[k]
75 del self[k]
76 except KeyError:
76 except KeyError:
77 raise AttributeError(k)
77 raise AttributeError(k)
78 else:
78 else:
79 object.__delattr__(self, k)
79 object.__delattr__(self, k)
80
80
81 def toDict(self):
81 def toDict(self):
82 return unserialize(self)
82 return unserialize(self)
83
83
84 def __repr__(self):
84 def __repr__(self):
85 keys = list(self.keys())
85 keys = list(self.keys())
86 keys.sort()
86 keys.sort()
87 args = ', '.join(['%s=%r' % (key, self[key]) for key in keys])
87 args = ', '.join(['%s=%r' % (key, self[key]) for key in keys])
88 return '%s(%s)' % (self.__class__.__name__, args)
88 return '%s(%s)' % (self.__class__.__name__, args)
89
89
90 @staticmethod
90 @staticmethod
91 def fromDict(d):
91 def fromDict(d):
92 return serialize(d)
92 return serialize(d)
93
93
94
94
95 def serialize(x):
95 def serialize(x):
96 if isinstance(x, dict):
96 if isinstance(x, dict):
97 return DotDict((k, serialize(v)) for k, v in x.items())
97 return DotDict((k, serialize(v)) for k, v in x.items())
98 elif isinstance(x, (list, tuple)):
98 elif isinstance(x, (list, tuple)):
99 return type(x)(serialize(v) for v in x)
99 return type(x)(serialize(v) for v in x)
100 else:
100 else:
101 return x
101 return x
102
102
103
103
104 def unserialize(x):
104 def unserialize(x):
105 if isinstance(x, dict):
105 if isinstance(x, dict):
106 return dict((k, unserialize(v)) for k, v in x.items())
106 return dict((k, unserialize(v)) for k, v in x.items())
107 elif isinstance(x, (list, tuple)):
107 elif isinstance(x, (list, tuple)):
108 return type(x)(unserialize(v) for v in x)
108 return type(x)(unserialize(v) for v in x)
109 else:
109 else:
110 return x
110 return x
111
111
112
112
113 def _verify_kwargs(func_name, expected_parameters, kwargs):
113 def _verify_kwargs(func_name, expected_parameters, kwargs):
114 """
114 """
115 Verify that exactly `expected_parameters` are passed in as `kwargs`.
115 Verify that exactly `expected_parameters` are passed in as `kwargs`.
116 """
116 """
117 expected_parameters = set(expected_parameters)
117 expected_parameters = set(expected_parameters)
118 kwargs_keys = set(kwargs.keys())
118 kwargs_keys = set(kwargs.keys())
119 if kwargs_keys != expected_parameters:
119 if kwargs_keys != expected_parameters:
120 missing_kwargs = expected_parameters - kwargs_keys
120 missing_kwargs = expected_parameters - kwargs_keys
121 unexpected_kwargs = kwargs_keys - expected_parameters
121 unexpected_kwargs = kwargs_keys - expected_parameters
122 raise AssertionError(
122 raise AssertionError(
123 "func:%s: missing parameters: %r, unexpected parameters: %s" %
123 "func:%s: missing parameters: %r, unexpected parameters: %s" %
124 (func_name, missing_kwargs, unexpected_kwargs))
124 (func_name, missing_kwargs, unexpected_kwargs))
125
125
126
126
127 def has_kwargs(required_args):
127 def has_kwargs(required_args):
128 """
128 """
129 decorator to verify extension calls arguments.
129 decorator to verify extension calls arguments.
130
130
131 :param required_args:
131 :param required_args:
132 """
132 """
133 def wrap(func):
133 def wrap(func):
134 def wrapper(*args, **kwargs):
134 def wrapper(*args, **kwargs):
135 _verify_kwargs(func.func_name, required_args.keys(), kwargs)
135 _verify_kwargs(func.func_name, required_args.keys(), kwargs)
136 # in case there's `calls` defined on module we store the data
136 # in case there's `calls` defined on module we store the data
137 maybe_log_call(func.func_name, args, kwargs)
137 maybe_log_call(func.func_name, args, kwargs)
138 return func(*args, **kwargs)
138 return func(*args, **kwargs)
139 return wrapper
139 return wrapper
140 return wrap
140 return wrap
141
141
142
142
143 def maybe_log_call(name, args, kwargs):
143 def maybe_log_call(name, args, kwargs):
144 from rhodecode.config import rcextensions
144 from rhodecode.config import rcextensions
145 if hasattr(rcextensions, 'calls'):
145 if hasattr(rcextensions, 'calls'):
146 calls = rcextensions.calls
146 calls = rcextensions.calls
147 calls[name].append((args, kwargs))
147 calls[name].append((args, kwargs))
148
149
150 def str2bool(_str):
151 """
152 returns True/False value from given string, it tries to translate the
153 string into boolean
154
155 :param _str: string value to translate into boolean
156 :rtype: boolean
157 :returns: boolean from given string
158 """
159 if _str is None:
160 return False
161 if _str in (True, False):
162 return _str
163 _str = str(_str).strip().lower()
164 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
165
166
167 def aslist(obj, sep=None, strip=True):
168 """
169 Returns given string separated by sep as list
170
171 :param obj:
172 :param sep:
173 :param strip:
174 """
175 if isinstance(obj, (basestring,)):
176 lst = obj.split(sep)
177 if strip:
178 lst = [v.strip() for v in lst]
179 return lst
180 elif isinstance(obj, (list, tuple)):
181 return obj
182 elif obj is None:
183 return []
184 else:
185 return [obj] No newline at end of file
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now