##// 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 1 # -*- coding: utf-8 -*-
2 2 # Copyright (C) 2016-2018 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 """
21 21 us in hooks::
22 22
23 23 from .helpers import extract_pre_commits
24 24 # returns list of dicts with key-val fetched from extra fields
25 25 commit_list = extract_pre_commits.run(**kwargs)
26 26
27 27 """
28 28 import re
29 29 import collections
30 import json
30 31
31 32
32 33 def get_hg_commits(repo, refs):
33 34 commits = []
34 35 return commits
35 36
36 37
37 38 def get_git_commits(repo, refs):
38 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 65 return commits
40 66
41 67
42 68 def run(*args, **kwargs):
43 69 from rhodecode.model.db import Repository
44 70
45 71 vcs_type = kwargs['scm']
46 72 # use temp name then the main one propagated
47 73 repo_name = kwargs.pop('REPOSITORY', None) or kwargs['repository']
48 74
49 75 repo = Repository.get_by_repo_name(repo_name)
50 76 vcs_repo = repo.scm_instance(cache=False)
51 77
52 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 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 83 commits = get_git_commits(vcs_repo, kwargs['commit_ids'])
59 84
60 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 88 commits = get_hg_commits(vcs_repo, kwargs['commit_ids'])
62 89
63 90 return commits
@@ -1,147 +1,185 b''
1 1 # Copyright (C) 2016-2018 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 import os
20 20 import functools
21 21 import collections
22 22
23 23
24 24 class HookResponse(object):
25 25 def __init__(self, status, output):
26 26 self.status = status
27 27 self.output = output
28 28
29 29 def __add__(self, other):
30 30 other_status = getattr(other, 'status', 0)
31 31 new_status = max(self.status, other_status)
32 32 other_output = getattr(other, 'output', '')
33 33 new_output = self.output + other_output
34 34
35 35 return HookResponse(new_status, new_output)
36 36
37 37 def __bool__(self):
38 38 return self.status == 0
39 39
40 40
41 41 class DotDict(dict):
42 42
43 43 def __contains__(self, k):
44 44 try:
45 45 return dict.__contains__(self, k) or hasattr(self, k)
46 46 except:
47 47 return False
48 48
49 49 # only called if k not found in normal places
50 50 def __getattr__(self, k):
51 51 try:
52 52 return object.__getattribute__(self, k)
53 53 except AttributeError:
54 54 try:
55 55 return self[k]
56 56 except KeyError:
57 57 raise AttributeError(k)
58 58
59 59 def __setattr__(self, k, v):
60 60 try:
61 61 object.__getattribute__(self, k)
62 62 except AttributeError:
63 63 try:
64 64 self[k] = v
65 65 except:
66 66 raise AttributeError(k)
67 67 else:
68 68 object.__setattr__(self, k, v)
69 69
70 70 def __delattr__(self, k):
71 71 try:
72 72 object.__getattribute__(self, k)
73 73 except AttributeError:
74 74 try:
75 75 del self[k]
76 76 except KeyError:
77 77 raise AttributeError(k)
78 78 else:
79 79 object.__delattr__(self, k)
80 80
81 81 def toDict(self):
82 82 return unserialize(self)
83 83
84 84 def __repr__(self):
85 85 keys = list(self.keys())
86 86 keys.sort()
87 87 args = ', '.join(['%s=%r' % (key, self[key]) for key in keys])
88 88 return '%s(%s)' % (self.__class__.__name__, args)
89 89
90 90 @staticmethod
91 91 def fromDict(d):
92 92 return serialize(d)
93 93
94 94
95 95 def serialize(x):
96 96 if isinstance(x, dict):
97 97 return DotDict((k, serialize(v)) for k, v in x.items())
98 98 elif isinstance(x, (list, tuple)):
99 99 return type(x)(serialize(v) for v in x)
100 100 else:
101 101 return x
102 102
103 103
104 104 def unserialize(x):
105 105 if isinstance(x, dict):
106 106 return dict((k, unserialize(v)) for k, v in x.items())
107 107 elif isinstance(x, (list, tuple)):
108 108 return type(x)(unserialize(v) for v in x)
109 109 else:
110 110 return x
111 111
112 112
113 113 def _verify_kwargs(func_name, expected_parameters, kwargs):
114 114 """
115 115 Verify that exactly `expected_parameters` are passed in as `kwargs`.
116 116 """
117 117 expected_parameters = set(expected_parameters)
118 118 kwargs_keys = set(kwargs.keys())
119 119 if kwargs_keys != expected_parameters:
120 120 missing_kwargs = expected_parameters - kwargs_keys
121 121 unexpected_kwargs = kwargs_keys - expected_parameters
122 122 raise AssertionError(
123 123 "func:%s: missing parameters: %r, unexpected parameters: %s" %
124 124 (func_name, missing_kwargs, unexpected_kwargs))
125 125
126 126
127 127 def has_kwargs(required_args):
128 128 """
129 129 decorator to verify extension calls arguments.
130 130
131 131 :param required_args:
132 132 """
133 133 def wrap(func):
134 134 def wrapper(*args, **kwargs):
135 135 _verify_kwargs(func.func_name, required_args.keys(), kwargs)
136 136 # in case there's `calls` defined on module we store the data
137 137 maybe_log_call(func.func_name, args, kwargs)
138 138 return func(*args, **kwargs)
139 139 return wrapper
140 140 return wrap
141 141
142 142
143 143 def maybe_log_call(name, args, kwargs):
144 144 from rhodecode.config import rcextensions
145 145 if hasattr(rcextensions, 'calls'):
146 146 calls = rcextensions.calls
147 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
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now