##// END OF EJS Templates
chore(deps): bumped celery to 5.3.6 and kombu to latest version
super-admin -
r1205:b35336c8 default
parent child Browse files
Show More
@@ -1,77 +1,77 b''
1 # deps, generated via pipdeptree --exclude setuptools,wheel,pipdeptree,pip -f | tr '[:upper:]' '[:lower:]'
1 # deps, generated via pipdeptree --exclude setuptools,wheel,pipdeptree,pip -f | tr '[:upper:]' '[:lower:]'
2
2
3 async-timeout==4.0.3
3 async-timeout==4.0.3
4 atomicwrites==1.4.1
4 atomicwrites==1.4.1
5 celery==5.3.4
5 celery==5.3.6
6 billiard==4.1.0
6 billiard==4.2.0
7 click==8.1.3
7 click==8.1.3
8 click-didyoumean==0.3.0
8 click-didyoumean==0.3.0
9 click==8.1.3
9 click==8.1.3
10 click-plugins==1.1.1
10 click-plugins==1.1.1
11 click==8.1.3
11 click==8.1.3
12 click-repl==0.2.0
12 click-repl==0.2.0
13 click==8.1.3
13 click==8.1.3
14 prompt-toolkit==3.0.38
14 prompt-toolkit==3.0.38
15 wcwidth==0.2.6
15 wcwidth==0.2.6
16 six==1.16.0
16 six==1.16.0
17 kombu==5.3.2
17 kombu==5.3.5
18 amqp==5.1.1
18 amqp==5.2.0
19 vine==5.1.0
19 vine==5.1.0
20 vine==5.1.0
20 vine==5.1.0
21 python-dateutil==2.8.2
21 python-dateutil==2.8.2
22 six==1.16.0
22 six==1.16.0
23 tzdata==2023.4
23 tzdata==2023.4
24 vine==5.1.0
24 vine==5.1.0
25 contextlib2==21.6.0
25 contextlib2==21.6.0
26 cov-core==1.15.0
26 cov-core==1.15.0
27 coverage==7.2.3
27 coverage==7.2.3
28 diskcache==5.6.3
28 diskcache==5.6.3
29 dogpile.cache==1.3.0
29 dogpile.cache==1.3.0
30 decorator==5.1.1
30 decorator==5.1.1
31 stevedore==5.1.0
31 stevedore==5.1.0
32 pbr==5.11.1
32 pbr==5.11.1
33 dulwich==0.21.6
33 dulwich==0.21.6
34 urllib3==1.26.14
34 urllib3==1.26.14
35 gunicorn==21.2.0
35 gunicorn==21.2.0
36 packaging==23.1
36 packaging==23.1
37 hg-evolve==11.0.2
37 hg-evolve==11.0.2
38 importlib-metadata==6.0.0
38 importlib-metadata==6.0.0
39 zipp==3.15.0
39 zipp==3.15.0
40 mercurial==6.3.3
40 mercurial==6.3.3
41 mock==5.0.2
41 mock==5.0.2
42 more-itertools==9.1.0
42 more-itertools==9.1.0
43 msgpack==1.0.7
43 msgpack==1.0.7
44 orjson==3.9.13
44 orjson==3.9.13
45 psutil==5.9.8
45 psutil==5.9.8
46 py==1.11.0
46 py==1.11.0
47 pygit2==1.13.3
47 pygit2==1.13.3
48 cffi==1.16.0
48 cffi==1.16.0
49 pycparser==2.21
49 pycparser==2.21
50 pygments==2.15.1
50 pygments==2.15.1
51 pyparsing==3.1.1
51 pyparsing==3.1.1
52 pyramid==2.0.2
52 pyramid==2.0.2
53 hupper==1.12
53 hupper==1.12
54 plaster==1.1.2
54 plaster==1.1.2
55 plaster-pastedeploy==1.0.1
55 plaster-pastedeploy==1.0.1
56 pastedeploy==3.1.0
56 pastedeploy==3.1.0
57 plaster==1.1.2
57 plaster==1.1.2
58 translationstring==1.4
58 translationstring==1.4
59 venusian==3.0.0
59 venusian==3.0.0
60 webob==1.8.7
60 webob==1.8.7
61 zope.deprecation==5.0.0
61 zope.deprecation==5.0.0
62 zope.interface==6.1.0
62 zope.interface==6.1.0
63 redis==5.0.1
63 redis==5.0.1
64 async-timeout==4.0.3
64 async-timeout==4.0.3
65 repoze.lru==0.7
65 repoze.lru==0.7
66 scandir==1.10.0
66 scandir==1.10.0
67 setproctitle==1.3.3
67 setproctitle==1.3.3
68 subvertpy==0.11.0
68 subvertpy==0.11.0
69 waitress==3.0.0
69 waitress==3.0.0
70 wcwidth==0.2.6
70 wcwidth==0.2.6
71
71
72
72
73 ## test related requirements
73 ## test related requirements
74 #-r requirements_test.txt
74 #-r requirements_test.txt
75
75
76 ## uncomment to add the debug libraries
76 ## uncomment to add the debug libraries
77 #-r requirements_debug.txt
77 #-r requirements_debug.txt
@@ -1,796 +1,795 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2023 RhodeCode GmbH
2 # Copyright (C) 2014-2023 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 General Public License as published by
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
7 # (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
18 import io
18 import io
19 import os
19 import os
20 import sys
20 import sys
21 import logging
21 import logging
22 import collections
22 import collections
23 import importlib
24 import base64
23 import base64
25 import msgpack
24 import msgpack
26 import dataclasses
25 import dataclasses
27 import pygit2
26 import pygit2
28
27
29 import http.client
28 import http.client
30 from celery import Celery
29 from celery import Celery
31
30
32
33 import mercurial.scmutil
31 import mercurial.scmutil
34 import mercurial.node
32 import mercurial.node
35
33
36 from vcsserver.lib.rc_json import json
34 from vcsserver.lib.rc_json import json
37 from vcsserver import exceptions, subprocessio, settings
35 from vcsserver import exceptions, subprocessio, settings
38 from vcsserver.str_utils import ascii_str, safe_str
36 from vcsserver.str_utils import ascii_str, safe_str
39 from vcsserver.remote.git_remote import Repository
37 from vcsserver.remote.git_remote import Repository
40
38
41 celery_app = Celery()
39 celery_app = Celery('__vcsserver__')
42 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
43
41
44
42
45 class HooksHttpClient:
43 class HooksHttpClient:
46 proto = 'msgpack.v1'
44 proto = 'msgpack.v1'
47 connection = None
45 connection = None
48
46
49 def __init__(self, hooks_uri):
47 def __init__(self, hooks_uri):
50 self.hooks_uri = hooks_uri
48 self.hooks_uri = hooks_uri
51
49
52 def __repr__(self):
50 def __repr__(self):
53 return f'{self.__class__}(hook_uri={self.hooks_uri}, proto={self.proto})'
51 return f'{self.__class__}(hook_uri={self.hooks_uri}, proto={self.proto})'
54
52
55 def __call__(self, method, extras):
53 def __call__(self, method, extras):
56 connection = http.client.HTTPConnection(self.hooks_uri)
54 connection = http.client.HTTPConnection(self.hooks_uri)
57 # binary msgpack body
55 # binary msgpack body
58 headers, body = self._serialize(method, extras)
56 headers, body = self._serialize(method, extras)
59 log.debug('Doing a new hooks call using HTTPConnection to %s', self.hooks_uri)
57 log.debug('Doing a new hooks call using HTTPConnection to %s', self.hooks_uri)
60
58
61 try:
59 try:
62 try:
60 try:
63 connection.request('POST', '/', body, headers)
61 connection.request('POST', '/', body, headers)
64 except Exception as error:
62 except Exception as error:
65 log.error('Hooks calling Connection failed on %s, org error: %s', connection.__dict__, error)
63 log.error('Hooks calling Connection failed on %s, org error: %s', connection.__dict__, error)
66 raise
64 raise
67
65
68 response = connection.getresponse()
66 response = connection.getresponse()
69 try:
67 try:
70 return msgpack.load(response)
68 return msgpack.load(response)
71 except Exception:
69 except Exception:
72 response_data = response.read()
70 response_data = response.read()
73 log.exception('Failed to decode hook response json data. '
71 log.exception('Failed to decode hook response json data. '
74 'response_code:%s, raw_data:%s',
72 'response_code:%s, raw_data:%s',
75 response.status, response_data)
73 response.status, response_data)
76 raise
74 raise
77 finally:
75 finally:
78 connection.close()
76 connection.close()
79
77
80 @classmethod
78 @classmethod
81 def _serialize(cls, hook_name, extras):
79 def _serialize(cls, hook_name, extras):
82 data = {
80 data = {
83 'method': hook_name,
81 'method': hook_name,
84 'extras': extras
82 'extras': extras
85 }
83 }
86 headers = {
84 headers = {
87 "rc-hooks-protocol": cls.proto,
85 "rc-hooks-protocol": cls.proto,
88 "Connection": "keep-alive"
86 "Connection": "keep-alive"
89 }
87 }
90 return headers, msgpack.packb(data)
88 return headers, msgpack.packb(data)
91
89
92
90
93 class HooksCeleryClient:
91 class HooksCeleryClient:
94 TASK_TIMEOUT = 60 # time in seconds
92 TASK_TIMEOUT = 60 # time in seconds
95
93
96 def __init__(self, queue, backend):
94 def __init__(self, queue, backend):
97 celery_app.config_from_object({'broker_url': queue, 'result_backend': backend,
95 celery_app.config_from_object({
98 'broker_connection_retry_on_startup': True,
96 'broker_url': queue, 'result_backend': backend,
99 'task_serializer': 'msgpack',
97 'broker_connection_retry_on_startup': True,
100 'accept_content': ['json', 'msgpack'],
98 'task_serializer': 'msgpack',
101 'result_serializer': 'msgpack',
99 'accept_content': ['json', 'msgpack'],
102 'result_accept_content': ['json', 'msgpack']
100 'result_serializer': 'msgpack',
103 })
101 'result_accept_content': ['json', 'msgpack']
102 })
104 self.celery_app = celery_app
103 self.celery_app = celery_app
105
104
106 def __call__(self, method, extras):
105 def __call__(self, method, extras):
107 inquired_task = self.celery_app.signature(
106 inquired_task = self.celery_app.signature(
108 f'rhodecode.lib.celerylib.tasks.{method}'
107 f'rhodecode.lib.celerylib.tasks.{method}'
109 )
108 )
110 return inquired_task.delay(extras).get(timeout=self.TASK_TIMEOUT)
109 return inquired_task.delay(extras).get(timeout=self.TASK_TIMEOUT)
111
110
112
111
113 class HooksShadowRepoClient:
112 class HooksShadowRepoClient:
114
113
115 def __call__(self, hook_name, extras):
114 def __call__(self, hook_name, extras):
116 return {'output': '', 'status': 0}
115 return {'output': '', 'status': 0}
117
116
118
117
119 class RemoteMessageWriter:
118 class RemoteMessageWriter:
120 """Writer base class."""
119 """Writer base class."""
121 def write(self, message):
120 def write(self, message):
122 raise NotImplementedError()
121 raise NotImplementedError()
123
122
124
123
125 class HgMessageWriter(RemoteMessageWriter):
124 class HgMessageWriter(RemoteMessageWriter):
126 """Writer that knows how to send messages to mercurial clients."""
125 """Writer that knows how to send messages to mercurial clients."""
127
126
128 def __init__(self, ui):
127 def __init__(self, ui):
129 self.ui = ui
128 self.ui = ui
130
129
131 def write(self, message: str):
130 def write(self, message: str):
132 # TODO: Check why the quiet flag is set by default.
131 # TODO: Check why the quiet flag is set by default.
133 old = self.ui.quiet
132 old = self.ui.quiet
134 self.ui.quiet = False
133 self.ui.quiet = False
135 self.ui.status(message.encode('utf-8'))
134 self.ui.status(message.encode('utf-8'))
136 self.ui.quiet = old
135 self.ui.quiet = old
137
136
138
137
139 class GitMessageWriter(RemoteMessageWriter):
138 class GitMessageWriter(RemoteMessageWriter):
140 """Writer that knows how to send messages to git clients."""
139 """Writer that knows how to send messages to git clients."""
141
140
142 def __init__(self, stdout=None):
141 def __init__(self, stdout=None):
143 self.stdout = stdout or sys.stdout
142 self.stdout = stdout or sys.stdout
144
143
145 def write(self, message: str):
144 def write(self, message: str):
146 self.stdout.write(message)
145 self.stdout.write(message)
147
146
148
147
149 class SvnMessageWriter(RemoteMessageWriter):
148 class SvnMessageWriter(RemoteMessageWriter):
150 """Writer that knows how to send messages to svn clients."""
149 """Writer that knows how to send messages to svn clients."""
151
150
152 def __init__(self, stderr=None):
151 def __init__(self, stderr=None):
153 # SVN needs data sent to stderr for back-to-client messaging
152 # SVN needs data sent to stderr for back-to-client messaging
154 self.stderr = stderr or sys.stderr
153 self.stderr = stderr or sys.stderr
155
154
156 def write(self, message):
155 def write(self, message):
157 self.stderr.write(message.encode('utf-8'))
156 self.stderr.write(message.encode('utf-8'))
158
157
159
158
160 def _handle_exception(result):
159 def _handle_exception(result):
161 exception_class = result.get('exception')
160 exception_class = result.get('exception')
162 exception_traceback = result.get('exception_traceback')
161 exception_traceback = result.get('exception_traceback')
163 log.debug('Handling hook-call exception: %s', exception_class)
162 log.debug('Handling hook-call exception: %s', exception_class)
164
163
165 if exception_traceback:
164 if exception_traceback:
166 log.error('Got traceback from remote call:%s', exception_traceback)
165 log.error('Got traceback from remote call:%s', exception_traceback)
167
166
168 if exception_class == 'HTTPLockedRC':
167 if exception_class == 'HTTPLockedRC':
169 raise exceptions.RepositoryLockedException()(*result['exception_args'])
168 raise exceptions.RepositoryLockedException()(*result['exception_args'])
170 elif exception_class == 'HTTPBranchProtected':
169 elif exception_class == 'HTTPBranchProtected':
171 raise exceptions.RepositoryBranchProtectedException()(*result['exception_args'])
170 raise exceptions.RepositoryBranchProtectedException()(*result['exception_args'])
172 elif exception_class == 'RepositoryError':
171 elif exception_class == 'RepositoryError':
173 raise exceptions.VcsException()(*result['exception_args'])
172 raise exceptions.VcsException()(*result['exception_args'])
174 elif exception_class:
173 elif exception_class:
175 raise Exception(
174 raise Exception(
176 f"""Got remote exception "{exception_class}" with args "{result['exception_args']}" """
175 f"""Got remote exception "{exception_class}" with args "{result['exception_args']}" """
177 )
176 )
178
177
179
178
180 def _get_hooks_client(extras):
179 def _get_hooks_client(extras):
181 hooks_uri = extras.get('hooks_uri')
180 hooks_uri = extras.get('hooks_uri')
182 task_queue = extras.get('task_queue')
181 task_queue = extras.get('task_queue')
183 task_backend = extras.get('task_backend')
182 task_backend = extras.get('task_backend')
184 is_shadow_repo = extras.get('is_shadow_repo')
183 is_shadow_repo = extras.get('is_shadow_repo')
185
184
186 if hooks_uri:
185 if hooks_uri:
187 return HooksHttpClient(hooks_uri)
186 return HooksHttpClient(hooks_uri)
188 elif task_queue and task_backend:
187 elif task_queue and task_backend:
189 return HooksCeleryClient(task_queue, task_backend)
188 return HooksCeleryClient(task_queue, task_backend)
190 elif is_shadow_repo:
189 elif is_shadow_repo:
191 return HooksShadowRepoClient()
190 return HooksShadowRepoClient()
192 else:
191 else:
193 raise Exception("Hooks client not found!")
192 raise Exception("Hooks client not found!")
194
193
195
194
196 def _call_hook(hook_name, extras, writer):
195 def _call_hook(hook_name, extras, writer):
197 hooks_client = _get_hooks_client(extras)
196 hooks_client = _get_hooks_client(extras)
198 log.debug('Hooks, using client:%s', hooks_client)
197 log.debug('Hooks, using client:%s', hooks_client)
199 result = hooks_client(hook_name, extras)
198 result = hooks_client(hook_name, extras)
200 log.debug('Hooks got result: %s', result)
199 log.debug('Hooks got result: %s', result)
201 _handle_exception(result)
200 _handle_exception(result)
202 writer.write(result['output'])
201 writer.write(result['output'])
203
202
204 return result['status']
203 return result['status']
205
204
206
205
207 def _extras_from_ui(ui):
206 def _extras_from_ui(ui):
208 hook_data = ui.config(b'rhodecode', b'RC_SCM_DATA')
207 hook_data = ui.config(b'rhodecode', b'RC_SCM_DATA')
209 if not hook_data:
208 if not hook_data:
210 # maybe it's inside environ ?
209 # maybe it's inside environ ?
211 env_hook_data = os.environ.get('RC_SCM_DATA')
210 env_hook_data = os.environ.get('RC_SCM_DATA')
212 if env_hook_data:
211 if env_hook_data:
213 hook_data = env_hook_data
212 hook_data = env_hook_data
214
213
215 extras = {}
214 extras = {}
216 if hook_data:
215 if hook_data:
217 extras = json.loads(hook_data)
216 extras = json.loads(hook_data)
218 return extras
217 return extras
219
218
220
219
221 def _rev_range_hash(repo, node, check_heads=False):
220 def _rev_range_hash(repo, node, check_heads=False):
222 from vcsserver.hgcompat import get_ctx
221 from vcsserver.hgcompat import get_ctx
223
222
224 commits = []
223 commits = []
225 revs = []
224 revs = []
226 start = get_ctx(repo, node).rev()
225 start = get_ctx(repo, node).rev()
227 end = len(repo)
226 end = len(repo)
228 for rev in range(start, end):
227 for rev in range(start, end):
229 revs.append(rev)
228 revs.append(rev)
230 ctx = get_ctx(repo, rev)
229 ctx = get_ctx(repo, rev)
231 commit_id = ascii_str(mercurial.node.hex(ctx.node()))
230 commit_id = ascii_str(mercurial.node.hex(ctx.node()))
232 branch = safe_str(ctx.branch())
231 branch = safe_str(ctx.branch())
233 commits.append((commit_id, branch))
232 commits.append((commit_id, branch))
234
233
235 parent_heads = []
234 parent_heads = []
236 if check_heads:
235 if check_heads:
237 parent_heads = _check_heads(repo, start, end, revs)
236 parent_heads = _check_heads(repo, start, end, revs)
238 return commits, parent_heads
237 return commits, parent_heads
239
238
240
239
241 def _check_heads(repo, start, end, commits):
240 def _check_heads(repo, start, end, commits):
242 from vcsserver.hgcompat import get_ctx
241 from vcsserver.hgcompat import get_ctx
243 changelog = repo.changelog
242 changelog = repo.changelog
244 parents = set()
243 parents = set()
245
244
246 for new_rev in commits:
245 for new_rev in commits:
247 for p in changelog.parentrevs(new_rev):
246 for p in changelog.parentrevs(new_rev):
248 if p == mercurial.node.nullrev:
247 if p == mercurial.node.nullrev:
249 continue
248 continue
250 if p < start:
249 if p < start:
251 parents.add(p)
250 parents.add(p)
252
251
253 for p in parents:
252 for p in parents:
254 branch = get_ctx(repo, p).branch()
253 branch = get_ctx(repo, p).branch()
255 # The heads descending from that parent, on the same branch
254 # The heads descending from that parent, on the same branch
256 parent_heads = {p}
255 parent_heads = {p}
257 reachable = {p}
256 reachable = {p}
258 for x in range(p + 1, end):
257 for x in range(p + 1, end):
259 if get_ctx(repo, x).branch() != branch:
258 if get_ctx(repo, x).branch() != branch:
260 continue
259 continue
261 for pp in changelog.parentrevs(x):
260 for pp in changelog.parentrevs(x):
262 if pp in reachable:
261 if pp in reachable:
263 reachable.add(x)
262 reachable.add(x)
264 parent_heads.discard(pp)
263 parent_heads.discard(pp)
265 parent_heads.add(x)
264 parent_heads.add(x)
266 # More than one head? Suggest merging
265 # More than one head? Suggest merging
267 if len(parent_heads) > 1:
266 if len(parent_heads) > 1:
268 return list(parent_heads)
267 return list(parent_heads)
269
268
270 return []
269 return []
271
270
272
271
273 def _get_git_env():
272 def _get_git_env():
274 env = {}
273 env = {}
275 for k, v in os.environ.items():
274 for k, v in os.environ.items():
276 if k.startswith('GIT'):
275 if k.startswith('GIT'):
277 env[k] = v
276 env[k] = v
278
277
279 # serialized version
278 # serialized version
280 return [(k, v) for k, v in env.items()]
279 return [(k, v) for k, v in env.items()]
281
280
282
281
283 def _get_hg_env(old_rev, new_rev, txnid, repo_path):
282 def _get_hg_env(old_rev, new_rev, txnid, repo_path):
284 env = {}
283 env = {}
285 for k, v in os.environ.items():
284 for k, v in os.environ.items():
286 if k.startswith('HG'):
285 if k.startswith('HG'):
287 env[k] = v
286 env[k] = v
288
287
289 env['HG_NODE'] = old_rev
288 env['HG_NODE'] = old_rev
290 env['HG_NODE_LAST'] = new_rev
289 env['HG_NODE_LAST'] = new_rev
291 env['HG_TXNID'] = txnid
290 env['HG_TXNID'] = txnid
292 env['HG_PENDING'] = repo_path
291 env['HG_PENDING'] = repo_path
293
292
294 return [(k, v) for k, v in env.items()]
293 return [(k, v) for k, v in env.items()]
295
294
296
295
297 def repo_size(ui, repo, **kwargs):
296 def repo_size(ui, repo, **kwargs):
298 extras = _extras_from_ui(ui)
297 extras = _extras_from_ui(ui)
299 return _call_hook('repo_size', extras, HgMessageWriter(ui))
298 return _call_hook('repo_size', extras, HgMessageWriter(ui))
300
299
301
300
302 def pre_pull(ui, repo, **kwargs):
301 def pre_pull(ui, repo, **kwargs):
303 extras = _extras_from_ui(ui)
302 extras = _extras_from_ui(ui)
304 return _call_hook('pre_pull', extras, HgMessageWriter(ui))
303 return _call_hook('pre_pull', extras, HgMessageWriter(ui))
305
304
306
305
307 def pre_pull_ssh(ui, repo, **kwargs):
306 def pre_pull_ssh(ui, repo, **kwargs):
308 extras = _extras_from_ui(ui)
307 extras = _extras_from_ui(ui)
309 if extras and extras.get('SSH'):
308 if extras and extras.get('SSH'):
310 return pre_pull(ui, repo, **kwargs)
309 return pre_pull(ui, repo, **kwargs)
311 return 0
310 return 0
312
311
313
312
314 def post_pull(ui, repo, **kwargs):
313 def post_pull(ui, repo, **kwargs):
315 extras = _extras_from_ui(ui)
314 extras = _extras_from_ui(ui)
316 return _call_hook('post_pull', extras, HgMessageWriter(ui))
315 return _call_hook('post_pull', extras, HgMessageWriter(ui))
317
316
318
317
319 def post_pull_ssh(ui, repo, **kwargs):
318 def post_pull_ssh(ui, repo, **kwargs):
320 extras = _extras_from_ui(ui)
319 extras = _extras_from_ui(ui)
321 if extras and extras.get('SSH'):
320 if extras and extras.get('SSH'):
322 return post_pull(ui, repo, **kwargs)
321 return post_pull(ui, repo, **kwargs)
323 return 0
322 return 0
324
323
325
324
326 def pre_push(ui, repo, node=None, **kwargs):
325 def pre_push(ui, repo, node=None, **kwargs):
327 """
326 """
328 Mercurial pre_push hook
327 Mercurial pre_push hook
329 """
328 """
330 extras = _extras_from_ui(ui)
329 extras = _extras_from_ui(ui)
331 detect_force_push = extras.get('detect_force_push')
330 detect_force_push = extras.get('detect_force_push')
332
331
333 rev_data = []
332 rev_data = []
334 hook_type: str = safe_str(kwargs.get('hooktype'))
333 hook_type: str = safe_str(kwargs.get('hooktype'))
335
334
336 if node and hook_type == 'pretxnchangegroup':
335 if node and hook_type == 'pretxnchangegroup':
337 branches = collections.defaultdict(list)
336 branches = collections.defaultdict(list)
338 commits, _heads = _rev_range_hash(repo, node, check_heads=detect_force_push)
337 commits, _heads = _rev_range_hash(repo, node, check_heads=detect_force_push)
339 for commit_id, branch in commits:
338 for commit_id, branch in commits:
340 branches[branch].append(commit_id)
339 branches[branch].append(commit_id)
341
340
342 for branch, commits in branches.items():
341 for branch, commits in branches.items():
343 old_rev = ascii_str(kwargs.get('node_last')) or commits[0]
342 old_rev = ascii_str(kwargs.get('node_last')) or commits[0]
344 rev_data.append({
343 rev_data.append({
345 'total_commits': len(commits),
344 'total_commits': len(commits),
346 'old_rev': old_rev,
345 'old_rev': old_rev,
347 'new_rev': commits[-1],
346 'new_rev': commits[-1],
348 'ref': '',
347 'ref': '',
349 'type': 'branch',
348 'type': 'branch',
350 'name': branch,
349 'name': branch,
351 })
350 })
352
351
353 for push_ref in rev_data:
352 for push_ref in rev_data:
354 push_ref['multiple_heads'] = _heads
353 push_ref['multiple_heads'] = _heads
355
354
356 repo_path = os.path.join(
355 repo_path = os.path.join(
357 extras.get('repo_store', ''), extras.get('repository', ''))
356 extras.get('repo_store', ''), extras.get('repository', ''))
358 push_ref['hg_env'] = _get_hg_env(
357 push_ref['hg_env'] = _get_hg_env(
359 old_rev=push_ref['old_rev'],
358 old_rev=push_ref['old_rev'],
360 new_rev=push_ref['new_rev'], txnid=ascii_str(kwargs.get('txnid')),
359 new_rev=push_ref['new_rev'], txnid=ascii_str(kwargs.get('txnid')),
361 repo_path=repo_path)
360 repo_path=repo_path)
362
361
363 extras['hook_type'] = hook_type or 'pre_push'
362 extras['hook_type'] = hook_type or 'pre_push'
364 extras['commit_ids'] = rev_data
363 extras['commit_ids'] = rev_data
365
364
366 return _call_hook('pre_push', extras, HgMessageWriter(ui))
365 return _call_hook('pre_push', extras, HgMessageWriter(ui))
367
366
368
367
369 def pre_push_ssh(ui, repo, node=None, **kwargs):
368 def pre_push_ssh(ui, repo, node=None, **kwargs):
370 extras = _extras_from_ui(ui)
369 extras = _extras_from_ui(ui)
371 if extras.get('SSH'):
370 if extras.get('SSH'):
372 return pre_push(ui, repo, node, **kwargs)
371 return pre_push(ui, repo, node, **kwargs)
373
372
374 return 0
373 return 0
375
374
376
375
377 def pre_push_ssh_auth(ui, repo, node=None, **kwargs):
376 def pre_push_ssh_auth(ui, repo, node=None, **kwargs):
378 """
377 """
379 Mercurial pre_push hook for SSH
378 Mercurial pre_push hook for SSH
380 """
379 """
381 extras = _extras_from_ui(ui)
380 extras = _extras_from_ui(ui)
382 if extras.get('SSH'):
381 if extras.get('SSH'):
383 permission = extras['SSH_PERMISSIONS']
382 permission = extras['SSH_PERMISSIONS']
384
383
385 if 'repository.write' == permission or 'repository.admin' == permission:
384 if 'repository.write' == permission or 'repository.admin' == permission:
386 return 0
385 return 0
387
386
388 # non-zero ret code
387 # non-zero ret code
389 return 1
388 return 1
390
389
391 return 0
390 return 0
392
391
393
392
394 def post_push(ui, repo, node, **kwargs):
393 def post_push(ui, repo, node, **kwargs):
395 """
394 """
396 Mercurial post_push hook
395 Mercurial post_push hook
397 """
396 """
398 extras = _extras_from_ui(ui)
397 extras = _extras_from_ui(ui)
399
398
400 commit_ids = []
399 commit_ids = []
401 branches = []
400 branches = []
402 bookmarks = []
401 bookmarks = []
403 tags = []
402 tags = []
404 hook_type: str = safe_str(kwargs.get('hooktype'))
403 hook_type: str = safe_str(kwargs.get('hooktype'))
405
404
406 commits, _heads = _rev_range_hash(repo, node)
405 commits, _heads = _rev_range_hash(repo, node)
407 for commit_id, branch in commits:
406 for commit_id, branch in commits:
408 commit_ids.append(commit_id)
407 commit_ids.append(commit_id)
409 if branch not in branches:
408 if branch not in branches:
410 branches.append(branch)
409 branches.append(branch)
411
410
412 if hasattr(ui, '_rc_pushkey_bookmarks'):
411 if hasattr(ui, '_rc_pushkey_bookmarks'):
413 bookmarks = ui._rc_pushkey_bookmarks
412 bookmarks = ui._rc_pushkey_bookmarks
414
413
415 extras['hook_type'] = hook_type or 'post_push'
414 extras['hook_type'] = hook_type or 'post_push'
416 extras['commit_ids'] = commit_ids
415 extras['commit_ids'] = commit_ids
417
416
418 extras['new_refs'] = {
417 extras['new_refs'] = {
419 'branches': branches,
418 'branches': branches,
420 'bookmarks': bookmarks,
419 'bookmarks': bookmarks,
421 'tags': tags
420 'tags': tags
422 }
421 }
423
422
424 return _call_hook('post_push', extras, HgMessageWriter(ui))
423 return _call_hook('post_push', extras, HgMessageWriter(ui))
425
424
426
425
427 def post_push_ssh(ui, repo, node, **kwargs):
426 def post_push_ssh(ui, repo, node, **kwargs):
428 """
427 """
429 Mercurial post_push hook for SSH
428 Mercurial post_push hook for SSH
430 """
429 """
431 if _extras_from_ui(ui).get('SSH'):
430 if _extras_from_ui(ui).get('SSH'):
432 return post_push(ui, repo, node, **kwargs)
431 return post_push(ui, repo, node, **kwargs)
433 return 0
432 return 0
434
433
435
434
436 def key_push(ui, repo, **kwargs):
435 def key_push(ui, repo, **kwargs):
437 from vcsserver.hgcompat import get_ctx
436 from vcsserver.hgcompat import get_ctx
438
437
439 if kwargs['new'] != b'0' and kwargs['namespace'] == b'bookmarks':
438 if kwargs['new'] != b'0' and kwargs['namespace'] == b'bookmarks':
440 # store new bookmarks in our UI object propagated later to post_push
439 # store new bookmarks in our UI object propagated later to post_push
441 ui._rc_pushkey_bookmarks = get_ctx(repo, kwargs['key']).bookmarks()
440 ui._rc_pushkey_bookmarks = get_ctx(repo, kwargs['key']).bookmarks()
442 return
441 return
443
442
444
443
445 # backward compat
444 # backward compat
446 log_pull_action = post_pull
445 log_pull_action = post_pull
447
446
448 # backward compat
447 # backward compat
449 log_push_action = post_push
448 log_push_action = post_push
450
449
451
450
452 def handle_git_pre_receive(unused_repo_path, unused_revs, unused_env):
451 def handle_git_pre_receive(unused_repo_path, unused_revs, unused_env):
453 """
452 """
454 Old hook name: keep here for backward compatibility.
453 Old hook name: keep here for backward compatibility.
455
454
456 This is only required when the installed git hooks are not upgraded.
455 This is only required when the installed git hooks are not upgraded.
457 """
456 """
458 pass
457 pass
459
458
460
459
461 def handle_git_post_receive(unused_repo_path, unused_revs, unused_env):
460 def handle_git_post_receive(unused_repo_path, unused_revs, unused_env):
462 """
461 """
463 Old hook name: keep here for backward compatibility.
462 Old hook name: keep here for backward compatibility.
464
463
465 This is only required when the installed git hooks are not upgraded.
464 This is only required when the installed git hooks are not upgraded.
466 """
465 """
467 pass
466 pass
468
467
469
468
470 @dataclasses.dataclass
469 @dataclasses.dataclass
471 class HookResponse:
470 class HookResponse:
472 status: int
471 status: int
473 output: str
472 output: str
474
473
475
474
476 def git_pre_pull(extras) -> HookResponse:
475 def git_pre_pull(extras) -> HookResponse:
477 """
476 """
478 Pre pull hook.
477 Pre pull hook.
479
478
480 :param extras: dictionary containing the keys defined in simplevcs
479 :param extras: dictionary containing the keys defined in simplevcs
481 :type extras: dict
480 :type extras: dict
482
481
483 :return: status code of the hook. 0 for success.
482 :return: status code of the hook. 0 for success.
484 :rtype: int
483 :rtype: int
485 """
484 """
486
485
487 if 'pull' not in extras['hooks']:
486 if 'pull' not in extras['hooks']:
488 return HookResponse(0, '')
487 return HookResponse(0, '')
489
488
490 stdout = io.StringIO()
489 stdout = io.StringIO()
491 try:
490 try:
492 status_code = _call_hook('pre_pull', extras, GitMessageWriter(stdout))
491 status_code = _call_hook('pre_pull', extras, GitMessageWriter(stdout))
493
492
494 except Exception as error:
493 except Exception as error:
495 log.exception('Failed to call pre_pull hook')
494 log.exception('Failed to call pre_pull hook')
496 status_code = 128
495 status_code = 128
497 stdout.write(f'ERROR: {error}\n')
496 stdout.write(f'ERROR: {error}\n')
498
497
499 return HookResponse(status_code, stdout.getvalue())
498 return HookResponse(status_code, stdout.getvalue())
500
499
501
500
502 def git_post_pull(extras) -> HookResponse:
501 def git_post_pull(extras) -> HookResponse:
503 """
502 """
504 Post pull hook.
503 Post pull hook.
505
504
506 :param extras: dictionary containing the keys defined in simplevcs
505 :param extras: dictionary containing the keys defined in simplevcs
507 :type extras: dict
506 :type extras: dict
508
507
509 :return: status code of the hook. 0 for success.
508 :return: status code of the hook. 0 for success.
510 :rtype: int
509 :rtype: int
511 """
510 """
512 if 'pull' not in extras['hooks']:
511 if 'pull' not in extras['hooks']:
513 return HookResponse(0, '')
512 return HookResponse(0, '')
514
513
515 stdout = io.StringIO()
514 stdout = io.StringIO()
516 try:
515 try:
517 status = _call_hook('post_pull', extras, GitMessageWriter(stdout))
516 status = _call_hook('post_pull', extras, GitMessageWriter(stdout))
518 except Exception as error:
517 except Exception as error:
519 status = 128
518 status = 128
520 stdout.write(f'ERROR: {error}\n')
519 stdout.write(f'ERROR: {error}\n')
521
520
522 return HookResponse(status, stdout.getvalue())
521 return HookResponse(status, stdout.getvalue())
523
522
524
523
525 def _parse_git_ref_lines(revision_lines):
524 def _parse_git_ref_lines(revision_lines):
526 rev_data = []
525 rev_data = []
527 for revision_line in revision_lines or []:
526 for revision_line in revision_lines or []:
528 old_rev, new_rev, ref = revision_line.strip().split(' ')
527 old_rev, new_rev, ref = revision_line.strip().split(' ')
529 ref_data = ref.split('/', 2)
528 ref_data = ref.split('/', 2)
530 if ref_data[1] in ('tags', 'heads'):
529 if ref_data[1] in ('tags', 'heads'):
531 rev_data.append({
530 rev_data.append({
532 # NOTE(marcink):
531 # NOTE(marcink):
533 # we're unable to tell total_commits for git at this point
532 # we're unable to tell total_commits for git at this point
534 # but we set the variable for consistency with GIT
533 # but we set the variable for consistency with GIT
535 'total_commits': -1,
534 'total_commits': -1,
536 'old_rev': old_rev,
535 'old_rev': old_rev,
537 'new_rev': new_rev,
536 'new_rev': new_rev,
538 'ref': ref,
537 'ref': ref,
539 'type': ref_data[1],
538 'type': ref_data[1],
540 'name': ref_data[2],
539 'name': ref_data[2],
541 })
540 })
542 return rev_data
541 return rev_data
543
542
544
543
545 def git_pre_receive(unused_repo_path, revision_lines, env) -> int:
544 def git_pre_receive(unused_repo_path, revision_lines, env) -> int:
546 """
545 """
547 Pre push hook.
546 Pre push hook.
548
547
549 :return: status code of the hook. 0 for success.
548 :return: status code of the hook. 0 for success.
550 """
549 """
551 extras = json.loads(env['RC_SCM_DATA'])
550 extras = json.loads(env['RC_SCM_DATA'])
552 rev_data = _parse_git_ref_lines(revision_lines)
551 rev_data = _parse_git_ref_lines(revision_lines)
553 if 'push' not in extras['hooks']:
552 if 'push' not in extras['hooks']:
554 return 0
553 return 0
555 empty_commit_id = '0' * 40
554 empty_commit_id = '0' * 40
556
555
557 detect_force_push = extras.get('detect_force_push')
556 detect_force_push = extras.get('detect_force_push')
558
557
559 for push_ref in rev_data:
558 for push_ref in rev_data:
560 # store our git-env which holds the temp store
559 # store our git-env which holds the temp store
561 push_ref['git_env'] = _get_git_env()
560 push_ref['git_env'] = _get_git_env()
562 push_ref['pruned_sha'] = ''
561 push_ref['pruned_sha'] = ''
563 if not detect_force_push:
562 if not detect_force_push:
564 # don't check for forced-push when we don't need to
563 # don't check for forced-push when we don't need to
565 continue
564 continue
566
565
567 type_ = push_ref['type']
566 type_ = push_ref['type']
568 new_branch = push_ref['old_rev'] == empty_commit_id
567 new_branch = push_ref['old_rev'] == empty_commit_id
569 delete_branch = push_ref['new_rev'] == empty_commit_id
568 delete_branch = push_ref['new_rev'] == empty_commit_id
570 if type_ == 'heads' and not (new_branch or delete_branch):
569 if type_ == 'heads' and not (new_branch or delete_branch):
571 old_rev = push_ref['old_rev']
570 old_rev = push_ref['old_rev']
572 new_rev = push_ref['new_rev']
571 new_rev = push_ref['new_rev']
573 cmd = [settings.GIT_EXECUTABLE, 'rev-list', old_rev, f'^{new_rev}']
572 cmd = [settings.GIT_EXECUTABLE, 'rev-list', old_rev, f'^{new_rev}']
574 stdout, stderr = subprocessio.run_command(
573 stdout, stderr = subprocessio.run_command(
575 cmd, env=os.environ.copy())
574 cmd, env=os.environ.copy())
576 # means we're having some non-reachable objects, this forced push was used
575 # means we're having some non-reachable objects, this forced push was used
577 if stdout:
576 if stdout:
578 push_ref['pruned_sha'] = stdout.splitlines()
577 push_ref['pruned_sha'] = stdout.splitlines()
579
578
580 extras['hook_type'] = 'pre_receive'
579 extras['hook_type'] = 'pre_receive'
581 extras['commit_ids'] = rev_data
580 extras['commit_ids'] = rev_data
582
581
583 stdout = sys.stdout
582 stdout = sys.stdout
584 status_code = _call_hook('pre_push', extras, GitMessageWriter(stdout))
583 status_code = _call_hook('pre_push', extras, GitMessageWriter(stdout))
585
584
586 return status_code
585 return status_code
587
586
588
587
589 def git_post_receive(unused_repo_path, revision_lines, env) -> int:
588 def git_post_receive(unused_repo_path, revision_lines, env) -> int:
590 """
589 """
591 Post push hook.
590 Post push hook.
592
591
593 :return: status code of the hook. 0 for success.
592 :return: status code of the hook. 0 for success.
594 """
593 """
595 extras = json.loads(env['RC_SCM_DATA'])
594 extras = json.loads(env['RC_SCM_DATA'])
596 if 'push' not in extras['hooks']:
595 if 'push' not in extras['hooks']:
597 return 0
596 return 0
598
597
599 rev_data = _parse_git_ref_lines(revision_lines)
598 rev_data = _parse_git_ref_lines(revision_lines)
600
599
601 git_revs = []
600 git_revs = []
602
601
603 # N.B.(skreft): it is ok to just call git, as git before calling a
602 # N.B.(skreft): it is ok to just call git, as git before calling a
604 # subcommand sets the PATH environment variable so that it point to the
603 # subcommand sets the PATH environment variable so that it point to the
605 # correct version of the git executable.
604 # correct version of the git executable.
606 empty_commit_id = '0' * 40
605 empty_commit_id = '0' * 40
607 branches = []
606 branches = []
608 tags = []
607 tags = []
609 for push_ref in rev_data:
608 for push_ref in rev_data:
610 type_ = push_ref['type']
609 type_ = push_ref['type']
611
610
612 if type_ == 'heads':
611 if type_ == 'heads':
613 # starting new branch case
612 # starting new branch case
614 if push_ref['old_rev'] == empty_commit_id:
613 if push_ref['old_rev'] == empty_commit_id:
615 push_ref_name = push_ref['name']
614 push_ref_name = push_ref['name']
616
615
617 if push_ref_name not in branches:
616 if push_ref_name not in branches:
618 branches.append(push_ref_name)
617 branches.append(push_ref_name)
619
618
620 need_head_set = ''
619 need_head_set = ''
621 with Repository(os.getcwd()) as repo:
620 with Repository(os.getcwd()) as repo:
622 try:
621 try:
623 repo.head
622 repo.head
624 except pygit2.GitError:
623 except pygit2.GitError:
625 need_head_set = f'refs/heads/{push_ref_name}'
624 need_head_set = f'refs/heads/{push_ref_name}'
626
625
627 if need_head_set:
626 if need_head_set:
628 repo.set_head(need_head_set)
627 repo.set_head(need_head_set)
629 print(f"Setting default branch to {push_ref_name}")
628 print(f"Setting default branch to {push_ref_name}")
630
629
631 cmd = [settings.GIT_EXECUTABLE, 'for-each-ref', '--format=%(refname)', 'refs/heads/*']
630 cmd = [settings.GIT_EXECUTABLE, 'for-each-ref', '--format=%(refname)', 'refs/heads/*']
632 stdout, stderr = subprocessio.run_command(
631 stdout, stderr = subprocessio.run_command(
633 cmd, env=os.environ.copy())
632 cmd, env=os.environ.copy())
634 heads = safe_str(stdout)
633 heads = safe_str(stdout)
635 heads = heads.replace(push_ref['ref'], '')
634 heads = heads.replace(push_ref['ref'], '')
636 heads = ' '.join(head for head
635 heads = ' '.join(head for head
637 in heads.splitlines() if head) or '.'
636 in heads.splitlines() if head) or '.'
638 cmd = [settings.GIT_EXECUTABLE, 'log', '--reverse',
637 cmd = [settings.GIT_EXECUTABLE, 'log', '--reverse',
639 '--pretty=format:%H', '--', push_ref['new_rev'],
638 '--pretty=format:%H', '--', push_ref['new_rev'],
640 '--not', heads]
639 '--not', heads]
641 stdout, stderr = subprocessio.run_command(
640 stdout, stderr = subprocessio.run_command(
642 cmd, env=os.environ.copy())
641 cmd, env=os.environ.copy())
643 git_revs.extend(list(map(ascii_str, stdout.splitlines())))
642 git_revs.extend(list(map(ascii_str, stdout.splitlines())))
644
643
645 # delete branch case
644 # delete branch case
646 elif push_ref['new_rev'] == empty_commit_id:
645 elif push_ref['new_rev'] == empty_commit_id:
647 git_revs.append(f'delete_branch=>{push_ref["name"]}')
646 git_revs.append(f'delete_branch=>{push_ref["name"]}')
648 else:
647 else:
649 if push_ref['name'] not in branches:
648 if push_ref['name'] not in branches:
650 branches.append(push_ref['name'])
649 branches.append(push_ref['name'])
651
650
652 cmd = [settings.GIT_EXECUTABLE, 'log',
651 cmd = [settings.GIT_EXECUTABLE, 'log',
653 f'{push_ref["old_rev"]}..{push_ref["new_rev"]}',
652 f'{push_ref["old_rev"]}..{push_ref["new_rev"]}',
654 '--reverse', '--pretty=format:%H']
653 '--reverse', '--pretty=format:%H']
655 stdout, stderr = subprocessio.run_command(
654 stdout, stderr = subprocessio.run_command(
656 cmd, env=os.environ.copy())
655 cmd, env=os.environ.copy())
657 # we get bytes from stdout, we need str to be consistent
656 # we get bytes from stdout, we need str to be consistent
658 log_revs = list(map(ascii_str, stdout.splitlines()))
657 log_revs = list(map(ascii_str, stdout.splitlines()))
659 git_revs.extend(log_revs)
658 git_revs.extend(log_revs)
660
659
661 # Pure pygit2 impl. but still 2-3x slower :/
660 # Pure pygit2 impl. but still 2-3x slower :/
662 # results = []
661 # results = []
663 #
662 #
664 # with Repository(os.getcwd()) as repo:
663 # with Repository(os.getcwd()) as repo:
665 # repo_new_rev = repo[push_ref['new_rev']]
664 # repo_new_rev = repo[push_ref['new_rev']]
666 # repo_old_rev = repo[push_ref['old_rev']]
665 # repo_old_rev = repo[push_ref['old_rev']]
667 # walker = repo.walk(repo_new_rev.id, pygit2.GIT_SORT_TOPOLOGICAL)
666 # walker = repo.walk(repo_new_rev.id, pygit2.GIT_SORT_TOPOLOGICAL)
668 #
667 #
669 # for commit in walker:
668 # for commit in walker:
670 # if commit.id == repo_old_rev.id:
669 # if commit.id == repo_old_rev.id:
671 # break
670 # break
672 # results.append(commit.id.hex)
671 # results.append(commit.id.hex)
673 # # reverse the order, can't use GIT_SORT_REVERSE
672 # # reverse the order, can't use GIT_SORT_REVERSE
674 # log_revs = results[::-1]
673 # log_revs = results[::-1]
675
674
676 elif type_ == 'tags':
675 elif type_ == 'tags':
677 if push_ref['name'] not in tags:
676 if push_ref['name'] not in tags:
678 tags.append(push_ref['name'])
677 tags.append(push_ref['name'])
679 git_revs.append(f'tag=>{push_ref["name"]}')
678 git_revs.append(f'tag=>{push_ref["name"]}')
680
679
681 extras['hook_type'] = 'post_receive'
680 extras['hook_type'] = 'post_receive'
682 extras['commit_ids'] = git_revs
681 extras['commit_ids'] = git_revs
683 extras['new_refs'] = {
682 extras['new_refs'] = {
684 'branches': branches,
683 'branches': branches,
685 'bookmarks': [],
684 'bookmarks': [],
686 'tags': tags,
685 'tags': tags,
687 }
686 }
688
687
689 stdout = sys.stdout
688 stdout = sys.stdout
690
689
691 if 'repo_size' in extras['hooks']:
690 if 'repo_size' in extras['hooks']:
692 try:
691 try:
693 _call_hook('repo_size', extras, GitMessageWriter(stdout))
692 _call_hook('repo_size', extras, GitMessageWriter(stdout))
694 except Exception:
693 except Exception:
695 pass
694 pass
696
695
697 status_code = _call_hook('post_push', extras, GitMessageWriter(stdout))
696 status_code = _call_hook('post_push', extras, GitMessageWriter(stdout))
698 return status_code
697 return status_code
699
698
700
699
701 def _get_extras_from_txn_id(path, txn_id):
700 def _get_extras_from_txn_id(path, txn_id):
702 extras = {}
701 extras = {}
703 try:
702 try:
704 cmd = [settings.SVNLOOK_EXECUTABLE, 'pget',
703 cmd = [settings.SVNLOOK_EXECUTABLE, 'pget',
705 '-t', txn_id,
704 '-t', txn_id,
706 '--revprop', path, 'rc-scm-extras']
705 '--revprop', path, 'rc-scm-extras']
707 stdout, stderr = subprocessio.run_command(
706 stdout, stderr = subprocessio.run_command(
708 cmd, env=os.environ.copy())
707 cmd, env=os.environ.copy())
709 extras = json.loads(base64.urlsafe_b64decode(stdout))
708 extras = json.loads(base64.urlsafe_b64decode(stdout))
710 except Exception:
709 except Exception:
711 log.exception('Failed to extract extras info from txn_id')
710 log.exception('Failed to extract extras info from txn_id')
712
711
713 return extras
712 return extras
714
713
715
714
716 def _get_extras_from_commit_id(commit_id, path):
715 def _get_extras_from_commit_id(commit_id, path):
717 extras = {}
716 extras = {}
718 try:
717 try:
719 cmd = [settings.SVNLOOK_EXECUTABLE, 'pget',
718 cmd = [settings.SVNLOOK_EXECUTABLE, 'pget',
720 '-r', commit_id,
719 '-r', commit_id,
721 '--revprop', path, 'rc-scm-extras']
720 '--revprop', path, 'rc-scm-extras']
722 stdout, stderr = subprocessio.run_command(
721 stdout, stderr = subprocessio.run_command(
723 cmd, env=os.environ.copy())
722 cmd, env=os.environ.copy())
724 extras = json.loads(base64.urlsafe_b64decode(stdout))
723 extras = json.loads(base64.urlsafe_b64decode(stdout))
725 except Exception:
724 except Exception:
726 log.exception('Failed to extract extras info from commit_id')
725 log.exception('Failed to extract extras info from commit_id')
727
726
728 return extras
727 return extras
729
728
730
729
731 def svn_pre_commit(repo_path, commit_data, env):
730 def svn_pre_commit(repo_path, commit_data, env):
732 path, txn_id = commit_data
731 path, txn_id = commit_data
733 branches = []
732 branches = []
734 tags = []
733 tags = []
735
734
736 if env.get('RC_SCM_DATA'):
735 if env.get('RC_SCM_DATA'):
737 extras = json.loads(env['RC_SCM_DATA'])
736 extras = json.loads(env['RC_SCM_DATA'])
738 else:
737 else:
739 # fallback method to read from TXN-ID stored data
738 # fallback method to read from TXN-ID stored data
740 extras = _get_extras_from_txn_id(path, txn_id)
739 extras = _get_extras_from_txn_id(path, txn_id)
741 if not extras:
740 if not extras:
742 return 0
741 return 0
743
742
744 extras['hook_type'] = 'pre_commit'
743 extras['hook_type'] = 'pre_commit'
745 extras['commit_ids'] = [txn_id]
744 extras['commit_ids'] = [txn_id]
746 extras['txn_id'] = txn_id
745 extras['txn_id'] = txn_id
747 extras['new_refs'] = {
746 extras['new_refs'] = {
748 'total_commits': 1,
747 'total_commits': 1,
749 'branches': branches,
748 'branches': branches,
750 'bookmarks': [],
749 'bookmarks': [],
751 'tags': tags,
750 'tags': tags,
752 }
751 }
753
752
754 return _call_hook('pre_push', extras, SvnMessageWriter())
753 return _call_hook('pre_push', extras, SvnMessageWriter())
755
754
756
755
757 def svn_post_commit(repo_path, commit_data, env):
756 def svn_post_commit(repo_path, commit_data, env):
758 """
757 """
759 commit_data is path, rev, txn_id
758 commit_data is path, rev, txn_id
760 """
759 """
761 if len(commit_data) == 3:
760 if len(commit_data) == 3:
762 path, commit_id, txn_id = commit_data
761 path, commit_id, txn_id = commit_data
763 elif len(commit_data) == 2:
762 elif len(commit_data) == 2:
764 log.error('Failed to extract txn_id from commit_data using legacy method. '
763 log.error('Failed to extract txn_id from commit_data using legacy method. '
765 'Some functionality might be limited')
764 'Some functionality might be limited')
766 path, commit_id = commit_data
765 path, commit_id = commit_data
767 txn_id = None
766 txn_id = None
768
767
769 branches = []
768 branches = []
770 tags = []
769 tags = []
771
770
772 if env.get('RC_SCM_DATA'):
771 if env.get('RC_SCM_DATA'):
773 extras = json.loads(env['RC_SCM_DATA'])
772 extras = json.loads(env['RC_SCM_DATA'])
774 else:
773 else:
775 # fallback method to read from TXN-ID stored data
774 # fallback method to read from TXN-ID stored data
776 extras = _get_extras_from_commit_id(commit_id, path)
775 extras = _get_extras_from_commit_id(commit_id, path)
777 if not extras:
776 if not extras:
778 return 0
777 return 0
779
778
780 extras['hook_type'] = 'post_commit'
779 extras['hook_type'] = 'post_commit'
781 extras['commit_ids'] = [commit_id]
780 extras['commit_ids'] = [commit_id]
782 extras['txn_id'] = txn_id
781 extras['txn_id'] = txn_id
783 extras['new_refs'] = {
782 extras['new_refs'] = {
784 'branches': branches,
783 'branches': branches,
785 'bookmarks': [],
784 'bookmarks': [],
786 'tags': tags,
785 'tags': tags,
787 'total_commits': 1,
786 'total_commits': 1,
788 }
787 }
789
788
790 if 'repo_size' in extras['hooks']:
789 if 'repo_size' in extras['hooks']:
791 try:
790 try:
792 _call_hook('repo_size', extras, SvnMessageWriter())
791 _call_hook('repo_size', extras, SvnMessageWriter())
793 except Exception:
792 except Exception:
794 pass
793 pass
795
794
796 return _call_hook('post_push', extras, SvnMessageWriter())
795 return _call_hook('post_push', extras, SvnMessageWriter())
General Comments 0
You need to be logged in to leave comments. Login now