Show More
@@ -94,11 +94,9 b' def _get_scm_size(alias, root_path):' | |||||
94 | def repo_size(extras): |
|
94 | def repo_size(extras): | |
95 | """Present size of repository after push.""" |
|
95 | """Present size of repository after push.""" | |
96 | repo = Repository.get_by_repo_name(extras.repository) |
|
96 | repo = Repository.get_by_repo_name(extras.repository) | |
97 |
vcs_part = |
|
97 | vcs_part = f'.{repo.repo_type}' | |
98 | size_vcs, size_root, size_total = _get_scm_size(vcs_part, |
|
98 | size_vcs, size_root, size_total = _get_scm_size(vcs_part, repo.repo_full_path) | |
99 | repo.repo_full_path) |
|
99 | msg = (f'RhodeCode: `{repo.repo_name}` size summary {vcs_part}:{size_vcs} repo:{size_root} total:{size_total}\n') | |
100 | msg = ('Repository `%s` size summary %s:%s repo:%s total:%s\n' |
|
|||
101 | % (repo.repo_name, vcs_part, size_vcs, size_root, size_total)) |
|
|||
102 | return HookResponse(0, msg) |
|
100 | return HookResponse(0, msg) | |
103 |
|
101 | |||
104 |
|
102 | |||
@@ -126,8 +124,8 b' def pre_push(extras):' | |||||
126 |
|
124 | |||
127 | hook_response = '' |
|
125 | hook_response = '' | |
128 | if not is_shadow_repo(extras): |
|
126 | if not is_shadow_repo(extras): | |
|
127 | ||||
129 | if extras.commit_ids and extras.check_branch_perms: |
|
128 | if extras.commit_ids and extras.check_branch_perms: | |
130 |
|
||||
131 | auth_user = user.AuthUser() |
|
129 | auth_user = user.AuthUser() | |
132 | repo = Repository.get_by_repo_name(extras.repository) |
|
130 | repo = Repository.get_by_repo_name(extras.repository) | |
133 | affected_branches = [] |
|
131 | affected_branches = [] | |
@@ -155,11 +153,10 b' def pre_push(extras):' | |||||
155 | elif branch_perm == 'branch.push' and is_forced is False: |
|
153 | elif branch_perm == 'branch.push' and is_forced is False: | |
156 | continue |
|
154 | continue | |
157 | elif branch_perm == 'branch.push' and is_forced is True: |
|
155 | elif branch_perm == 'branch.push' and is_forced is True: | |
158 | halt_message = 'Branch `{}` changes rejected by rule {}. ' \ |
|
156 | halt_message = f'Branch `{branch_name}` changes rejected by rule {rule}. ' \ | |
159 |
'FORCE PUSH FORBIDDEN.' |
|
157 | f'FORCE PUSH FORBIDDEN.' | |
160 | else: |
|
158 | else: | |
161 |
halt_message = 'Branch `{}` changes rejected by rule {}.' |
|
159 | halt_message = f'Branch `{branch_name}` changes rejected by rule {rule}.' | |
162 | branch_name, rule) |
|
|||
163 |
|
160 | |||
164 | if halt_message: |
|
161 | if halt_message: | |
165 | _http_ret = HTTPBranchProtected(halt_message) |
|
162 | _http_ret = HTTPBranchProtected(halt_message) | |
@@ -281,7 +278,7 b' def post_push(extras):' | |||||
281 | # make lock is a tri state False, True, None. We only release lock on False |
|
278 | # make lock is a tri state False, True, None. We only release lock on False | |
282 | if extras.make_lock is False and not is_shadow_repo(extras): |
|
279 | if extras.make_lock is False and not is_shadow_repo(extras): | |
283 | Repository.unlock(Repository.get_by_repo_name(extras.repository)) |
|
280 | Repository.unlock(Repository.get_by_repo_name(extras.repository)) | |
284 |
msg = 'Released lock on repo `{}`\n' |
|
281 | msg = f'Released lock on repo `{extras.repository}`\n' | |
285 | output += msg |
|
282 | output += msg | |
286 |
|
283 | |||
287 | if extras.locked_by[0]: |
|
284 | if extras.locked_by[0]: | |
@@ -299,12 +296,12 b' def post_push(extras):' | |||||
299 | safe_str(extras.server_url), safe_str(extras.repository)) |
|
296 | safe_str(extras.server_url), safe_str(extras.repository)) | |
300 |
|
297 | |||
301 | for branch_name in extras.new_refs['branches']: |
|
298 | for branch_name in extras.new_refs['branches']: | |
302 | output += 'RhodeCode: open pull request link: {}\n'.format( |
|
299 | pr_link = tmpl.format(ref_type='branch', ref_name=safe_str(branch_name)) | |
303 | tmpl.format(ref_type='branch', ref_name=safe_str(branch_name))) |
|
300 | output += f'RhodeCode: open pull request link: {pr_link}\n' | |
304 |
|
301 | |||
305 | for book_name in extras.new_refs['bookmarks']: |
|
302 | for book_name in extras.new_refs['bookmarks']: | |
306 | output += 'RhodeCode: open pull request link: {}\n'.format( |
|
303 | pr_link = tmpl.format(ref_type='bookmark', ref_name=safe_str(book_name)) | |
307 | tmpl.format(ref_type='bookmark', ref_name=safe_str(book_name))) |
|
304 | output += f'RhodeCode: open pull request link: {pr_link}\n' | |
308 |
|
305 | |||
309 | hook_response = '' |
|
306 | hook_response = '' | |
310 | if not is_shadow_repo(extras): |
|
307 | if not is_shadow_repo(extras): | |
@@ -319,9 +316,7 b' def post_push(extras):' | |||||
319 |
|
316 | |||
320 |
|
317 | |||
321 | def _locked_by_explanation(repo_name, user_name, reason): |
|
318 | def _locked_by_explanation(repo_name, user_name, reason): | |
322 | message = ( |
|
319 | message = f'Repository `{repo_name}` locked by user `{user_name}`. Reason:`{reason}`' | |
323 | 'Repository `%s` locked by user `%s`. Reason:`%s`' |
|
|||
324 | % (repo_name, user_name, reason)) |
|
|||
325 | return message |
|
320 | return message | |
326 |
|
321 | |||
327 |
|
322 |
@@ -25,6 +25,7 b' import traceback' | |||||
25 | import threading |
|
25 | import threading | |
26 | import socket |
|
26 | import socket | |
27 | import msgpack |
|
27 | import msgpack | |
|
28 | import gevent | |||
28 |
|
29 | |||
29 | from http.server import BaseHTTPRequestHandler |
|
30 | from http.server import BaseHTTPRequestHandler | |
30 | from socketserver import TCPServer |
|
31 | from socketserver import TCPServer | |
@@ -43,6 +44,23 b' log = logging.getLogger(__name__)' | |||||
43 |
|
44 | |||
44 | class HooksHttpHandler(BaseHTTPRequestHandler): |
|
45 | class HooksHttpHandler(BaseHTTPRequestHandler): | |
45 |
|
46 | |||
|
47 | JSON_HOOKS_PROTO = 'json.v1' | |||
|
48 | MSGPACK_HOOKS_PROTO = 'msgpack.v1' | |||
|
49 | # starting with RhodeCode 5.0.0 MsgPack is the default, prior it used json | |||
|
50 | DEFAULT_HOOKS_PROTO = MSGPACK_HOOKS_PROTO | |||
|
51 | ||||
|
52 | @classmethod | |||
|
53 | def serialize_data(cls, data, proto=DEFAULT_HOOKS_PROTO): | |||
|
54 | if proto == cls.MSGPACK_HOOKS_PROTO: | |||
|
55 | return msgpack.packb(data) | |||
|
56 | return json.dumps(data) | |||
|
57 | ||||
|
58 | @classmethod | |||
|
59 | def deserialize_data(cls, data, proto=DEFAULT_HOOKS_PROTO): | |||
|
60 | if proto == cls.MSGPACK_HOOKS_PROTO: | |||
|
61 | return msgpack.unpackb(data) | |||
|
62 | return json.loads(data) | |||
|
63 | ||||
46 | def do_POST(self): |
|
64 | def do_POST(self): | |
47 | hooks_proto, method, extras = self._read_request() |
|
65 | hooks_proto, method, extras = self._read_request() | |
48 | log.debug('Handling HooksHttpHandler %s with %s proto', method, hooks_proto) |
|
66 | log.debug('Handling HooksHttpHandler %s with %s proto', method, hooks_proto) | |
@@ -62,6 +80,7 b' class HooksHttpHandler(BaseHTTPRequestHa' | |||||
62 | try: |
|
80 | try: | |
63 | hooks = Hooks(request=request, log_prefix='HOOKS: {} '.format(self.server.server_address)) |
|
81 | hooks = Hooks(request=request, log_prefix='HOOKS: {} '.format(self.server.server_address)) | |
64 | result = self._call_hook_method(hooks, method, extras) |
|
82 | result = self._call_hook_method(hooks, method, extras) | |
|
83 | ||||
65 | except Exception as e: |
|
84 | except Exception as e: | |
66 | exc_tb = traceback.format_exc() |
|
85 | exc_tb = traceback.format_exc() | |
67 | result = { |
|
86 | result = { | |
@@ -73,26 +92,30 b' class HooksHttpHandler(BaseHTTPRequestHa' | |||||
73 |
|
92 | |||
74 | def _read_request(self): |
|
93 | def _read_request(self): | |
75 | length = int(self.headers['Content-Length']) |
|
94 | length = int(self.headers['Content-Length']) | |
76 | hooks_proto = self.headers.get('rc-hooks-protocol') or 'json.v1' |
|
95 | # respect sent headers, fallback to OLD proto for compatability | |
77 | if hooks_proto == 'msgpack.v1': |
|
96 | hooks_proto = self.headers.get('rc-hooks-protocol') or self.JSON_HOOKS_PROTO | |
|
97 | if hooks_proto == self.MSGPACK_HOOKS_PROTO: | |||
78 | # support for new vcsserver msgpack based protocol hooks |
|
98 | # support for new vcsserver msgpack based protocol hooks | |
79 |
|
|
99 | body = self.rfile.read(length) | |
|
100 | data = self.deserialize_data(body) | |||
80 | else: |
|
101 | else: | |
81 | body = self.rfile.read(length) |
|
102 | body = self.rfile.read(length) | |
82 |
data = |
|
103 | data = self.deserialize_data(body) | |
83 |
|
104 | |||
84 | return hooks_proto, data['method'], data['extras'] |
|
105 | return hooks_proto, data['method'], data['extras'] | |
85 |
|
106 | |||
86 | def _write_response(self, hooks_proto, result): |
|
107 | def _write_response(self, hooks_proto, result): | |
87 | self.send_response(200) |
|
108 | self.send_response(200) | |
88 |
if hooks_proto == |
|
109 | if hooks_proto == self.MSGPACK_HOOKS_PROTO: | |
89 | self.send_header("Content-type", "application/msgpack") |
|
110 | self.send_header("Content-type", "application/msgpack") | |
90 | self.end_headers() |
|
111 | self.end_headers() | |
91 |
self. |
|
112 | data = self.serialize_data(result) | |
|
113 | self.wfile.write(data) | |||
92 | else: |
|
114 | else: | |
93 | self.send_header("Content-type", "text/json") |
|
115 | self.send_header("Content-type", "text/json") | |
94 | self.end_headers() |
|
116 | self.end_headers() | |
95 |
self. |
|
117 | data = self.serialize_data(result) | |
|
118 | self.wfile.write(data) | |||
96 |
|
119 | |||
97 | def _call_hook_method(self, hooks, method, extras): |
|
120 | def _call_hook_method(self, hooks, method, extras): | |
98 | try: |
|
121 | try: | |
@@ -110,7 +133,7 b' class HooksHttpHandler(BaseHTTPRequestHa' | |||||
110 | message = format % args |
|
133 | message = format % args | |
111 |
|
134 | |||
112 | log.debug( |
|
135 | log.debug( | |
113 | "HOOKS: %s - - [%s] %s", self.client_address, |
|
136 | "HOOKS: client=%s - - [%s] %s", self.client_address, | |
114 | self.log_date_time_string(), message) |
|
137 | self.log_date_time_string(), message) | |
115 |
|
138 | |||
116 |
|
139 | |||
@@ -133,18 +156,25 b' class ThreadedHookCallbackDaemon(object)' | |||||
133 | _callback_thread = None |
|
156 | _callback_thread = None | |
134 | _daemon = None |
|
157 | _daemon = None | |
135 | _done = False |
|
158 | _done = False | |
|
159 | use_gevent = False | |||
136 |
|
160 | |||
137 | def __init__(self, txn_id=None, host=None, port=None): |
|
161 | def __init__(self, txn_id=None, host=None, port=None): | |
138 | self._prepare(txn_id=txn_id, host=host, port=port) |
|
162 | self._prepare(txn_id=txn_id, host=host, port=port) | |
|
163 | if self.use_gevent: | |||
|
164 | self._run_func = self._run_gevent | |||
|
165 | self._stop_func = self._stop_gevent | |||
|
166 | else: | |||
|
167 | self._run_func = self._run | |||
|
168 | self._stop_func = self._stop | |||
139 |
|
169 | |||
140 | def __enter__(self): |
|
170 | def __enter__(self): | |
141 | log.debug('Running `%s` callback daemon', self.__class__.__name__) |
|
171 | log.debug('Running `%s` callback daemon', self.__class__.__name__) | |
142 | self._run() |
|
172 | self._run_func() | |
143 | return self |
|
173 | return self | |
144 |
|
174 | |||
145 | def __exit__(self, exc_type, exc_val, exc_tb): |
|
175 | def __exit__(self, exc_type, exc_val, exc_tb): | |
146 | log.debug('Exiting `%s` callback daemon', self.__class__.__name__) |
|
176 | log.debug('Exiting `%s` callback daemon', self.__class__.__name__) | |
147 | self._stop() |
|
177 | self._stop_func() | |
148 |
|
178 | |||
149 | def _prepare(self, txn_id=None, host=None, port=None): |
|
179 | def _prepare(self, txn_id=None, host=None, port=None): | |
150 | raise NotImplementedError() |
|
180 | raise NotImplementedError() | |
@@ -155,6 +185,12 b' class ThreadedHookCallbackDaemon(object)' | |||||
155 | def _stop(self): |
|
185 | def _stop(self): | |
156 | raise NotImplementedError() |
|
186 | raise NotImplementedError() | |
157 |
|
187 | |||
|
188 | def _run_gevent(self): | |||
|
189 | raise NotImplementedError() | |||
|
190 | ||||
|
191 | def _stop_gevent(self): | |||
|
192 | raise NotImplementedError() | |||
|
193 | ||||
158 |
|
194 | |||
159 | class HttpHooksCallbackDaemon(ThreadedHookCallbackDaemon): |
|
195 | class HttpHooksCallbackDaemon(ThreadedHookCallbackDaemon): | |
160 | """ |
|
196 | """ | |
@@ -167,6 +203,8 b' class HttpHooksCallbackDaemon(ThreadedHo' | |||||
167 | # request and wastes cpu at all other times. |
|
203 | # request and wastes cpu at all other times. | |
168 | POLL_INTERVAL = 0.01 |
|
204 | POLL_INTERVAL = 0.01 | |
169 |
|
205 | |||
|
206 | use_gevent = False | |||
|
207 | ||||
170 | @property |
|
208 | @property | |
171 | def _hook_prefix(self): |
|
209 | def _hook_prefix(self): | |
172 | return 'HOOKS: {} '.format(self.hooks_uri) |
|
210 | return 'HOOKS: {} '.format(self.hooks_uri) | |
@@ -203,7 +241,7 b' class HttpHooksCallbackDaemon(ThreadedHo' | |||||
203 | self._daemon.request = get_current_request() |
|
241 | self._daemon.request = get_current_request() | |
204 |
|
242 | |||
205 | def _run(self): |
|
243 | def _run(self): | |
206 |
log.debug("Running |
|
244 | log.debug("Running thread-based loop of callback daemon in background") | |
207 | callback_thread = threading.Thread( |
|
245 | callback_thread = threading.Thread( | |
208 | target=self._daemon.serve_forever, |
|
246 | target=self._daemon.serve_forever, | |
209 | kwargs={'poll_interval': self.POLL_INTERVAL}) |
|
247 | kwargs={'poll_interval': self.POLL_INTERVAL}) | |
@@ -211,6 +249,19 b' class HttpHooksCallbackDaemon(ThreadedHo' | |||||
211 | callback_thread.start() |
|
249 | callback_thread.start() | |
212 | self._callback_thread = callback_thread |
|
250 | self._callback_thread = callback_thread | |
213 |
|
251 | |||
|
252 | def _run_gevent(self): | |||
|
253 | log.debug("Running gevent-based loop of callback daemon in background") | |||
|
254 | # create a new greenlet for the daemon's serve_forever method | |||
|
255 | callback_greenlet = gevent.spawn( | |||
|
256 | self._daemon.serve_forever, | |||
|
257 | poll_interval=self.POLL_INTERVAL) | |||
|
258 | ||||
|
259 | # store reference to greenlet | |||
|
260 | self._callback_greenlet = callback_greenlet | |||
|
261 | ||||
|
262 | # switch to this greenlet | |||
|
263 | gevent.sleep(0.01) | |||
|
264 | ||||
214 | def _stop(self): |
|
265 | def _stop(self): | |
215 | log.debug("Waiting for background thread to finish.") |
|
266 | log.debug("Waiting for background thread to finish.") | |
216 | self._daemon.shutdown() |
|
267 | self._daemon.shutdown() | |
@@ -225,6 +276,29 b' class HttpHooksCallbackDaemon(ThreadedHo' | |||||
225 |
|
276 | |||
226 | log.debug("Background thread done.") |
|
277 | log.debug("Background thread done.") | |
227 |
|
278 | |||
|
279 | def _stop_gevent(self): | |||
|
280 | log.debug("Waiting for background greenlet to finish.") | |||
|
281 | ||||
|
282 | # if greenlet exists and is running | |||
|
283 | if self._callback_greenlet and not self._callback_greenlet.dead: | |||
|
284 | # shutdown daemon if it exists | |||
|
285 | if self._daemon: | |||
|
286 | self._daemon.shutdown() | |||
|
287 | ||||
|
288 | # kill the greenlet | |||
|
289 | self._callback_greenlet.kill() | |||
|
290 | ||||
|
291 | self._daemon = None | |||
|
292 | self._callback_greenlet = None | |||
|
293 | ||||
|
294 | if self.txn_id: | |||
|
295 | txn_id_file = get_txn_id_data_path(self.txn_id) | |||
|
296 | log.debug('Cleaning up TXN ID %s', txn_id_file) | |||
|
297 | if os.path.isfile(txn_id_file): | |||
|
298 | os.remove(txn_id_file) | |||
|
299 | ||||
|
300 | log.debug("Background greenlet done.") | |||
|
301 | ||||
228 |
|
302 | |||
229 | def get_txn_id_data_path(txn_id): |
|
303 | def get_txn_id_data_path(txn_id): | |
230 | import rhodecode |
|
304 | import rhodecode |
General Comments 0
You need to be logged in to leave comments.
Login now