##// END OF EJS Templates
hooks: handle errors before trying to fetch the output.
marcink -
r529:548d9d51 stable
parent child Browse files
Show More
@@ -1,657 +1,658 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # RhodeCode VCSServer provides access to different vcs backends via network.
3 # RhodeCode VCSServer provides access to different vcs backends via network.
4 # Copyright (C) 2014-2018 RhodeCode GmbH
4 # Copyright (C) 2014-2018 RhodeCode GmbH
5 #
5 #
6 # This program is free software; you can redistribute it and/or modify
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
9 # (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software Foundation,
17 # along with this program; if not, write to the Free Software Foundation,
18 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
19
20 import io
20 import io
21 import os
21 import os
22 import sys
22 import sys
23 import logging
23 import logging
24 import collections
24 import collections
25 import importlib
25 import importlib
26 import base64
26 import base64
27
27
28 from httplib import HTTPConnection
28 from httplib import HTTPConnection
29
29
30
30
31 import mercurial.scmutil
31 import mercurial.scmutil
32 import mercurial.node
32 import mercurial.node
33 import simplejson as json
33 import simplejson as json
34
34
35 from vcsserver import exceptions, subprocessio, settings
35 from vcsserver import exceptions, subprocessio, settings
36
36
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39
39
40 class HooksHttpClient(object):
40 class HooksHttpClient(object):
41 connection = None
41 connection = None
42
42
43 def __init__(self, hooks_uri):
43 def __init__(self, hooks_uri):
44 self.hooks_uri = hooks_uri
44 self.hooks_uri = hooks_uri
45
45
46 def __call__(self, method, extras):
46 def __call__(self, method, extras):
47 connection = HTTPConnection(self.hooks_uri)
47 connection = HTTPConnection(self.hooks_uri)
48 body = self._serialize(method, extras)
48 body = self._serialize(method, extras)
49 try:
49 try:
50 connection.request('POST', '/', body)
50 connection.request('POST', '/', body)
51 except Exception:
51 except Exception:
52 log.error('Connection failed on %s', connection)
52 log.error('Connection failed on %s', connection)
53 raise
53 raise
54 response = connection.getresponse()
54 response = connection.getresponse()
55 return json.loads(response.read())
55 return json.loads(response.read())
56
56
57 def _serialize(self, hook_name, extras):
57 def _serialize(self, hook_name, extras):
58 data = {
58 data = {
59 'method': hook_name,
59 'method': hook_name,
60 'extras': extras
60 'extras': extras
61 }
61 }
62 return json.dumps(data)
62 return json.dumps(data)
63
63
64
64
65 class HooksDummyClient(object):
65 class HooksDummyClient(object):
66 def __init__(self, hooks_module):
66 def __init__(self, hooks_module):
67 self._hooks_module = importlib.import_module(hooks_module)
67 self._hooks_module = importlib.import_module(hooks_module)
68
68
69 def __call__(self, hook_name, extras):
69 def __call__(self, hook_name, extras):
70 with self._hooks_module.Hooks() as hooks:
70 with self._hooks_module.Hooks() as hooks:
71 return getattr(hooks, hook_name)(extras)
71 return getattr(hooks, hook_name)(extras)
72
72
73
73
74 class RemoteMessageWriter(object):
74 class RemoteMessageWriter(object):
75 """Writer base class."""
75 """Writer base class."""
76 def write(self, message):
76 def write(self, message):
77 raise NotImplementedError()
77 raise NotImplementedError()
78
78
79
79
80 class HgMessageWriter(RemoteMessageWriter):
80 class HgMessageWriter(RemoteMessageWriter):
81 """Writer that knows how to send messages to mercurial clients."""
81 """Writer that knows how to send messages to mercurial clients."""
82
82
83 def __init__(self, ui):
83 def __init__(self, ui):
84 self.ui = ui
84 self.ui = ui
85
85
86 def write(self, message):
86 def write(self, message):
87 # TODO: Check why the quiet flag is set by default.
87 # TODO: Check why the quiet flag is set by default.
88 old = self.ui.quiet
88 old = self.ui.quiet
89 self.ui.quiet = False
89 self.ui.quiet = False
90 self.ui.status(message.encode('utf-8'))
90 self.ui.status(message.encode('utf-8'))
91 self.ui.quiet = old
91 self.ui.quiet = old
92
92
93
93
94 class GitMessageWriter(RemoteMessageWriter):
94 class GitMessageWriter(RemoteMessageWriter):
95 """Writer that knows how to send messages to git clients."""
95 """Writer that knows how to send messages to git clients."""
96
96
97 def __init__(self, stdout=None):
97 def __init__(self, stdout=None):
98 self.stdout = stdout or sys.stdout
98 self.stdout = stdout or sys.stdout
99
99
100 def write(self, message):
100 def write(self, message):
101 self.stdout.write(message.encode('utf-8'))
101 self.stdout.write(message.encode('utf-8'))
102
102
103
103
104 class SvnMessageWriter(RemoteMessageWriter):
104 class SvnMessageWriter(RemoteMessageWriter):
105 """Writer that knows how to send messages to svn clients."""
105 """Writer that knows how to send messages to svn clients."""
106
106
107 def __init__(self, stderr=None):
107 def __init__(self, stderr=None):
108 # SVN needs data sent to stderr for back-to-client messaging
108 # SVN needs data sent to stderr for back-to-client messaging
109 self.stderr = stderr or sys.stderr
109 self.stderr = stderr or sys.stderr
110
110
111 def write(self, message):
111 def write(self, message):
112 self.stderr.write(message.encode('utf-8'))
112 self.stderr.write(message.encode('utf-8'))
113
113
114
114
115 def _handle_exception(result):
115 def _handle_exception(result):
116 exception_class = result.get('exception')
116 exception_class = result.get('exception')
117 exception_traceback = result.get('exception_traceback')
117 exception_traceback = result.get('exception_traceback')
118
118
119 if exception_traceback:
119 if exception_traceback:
120 log.error('Got traceback from remote call:%s', exception_traceback)
120 log.error('Got traceback from remote call:%s', exception_traceback)
121
121
122 if exception_class == 'HTTPLockedRC':
122 if exception_class == 'HTTPLockedRC':
123 raise exceptions.RepositoryLockedException()(*result['exception_args'])
123 raise exceptions.RepositoryLockedException()(*result['exception_args'])
124 elif exception_class == 'HTTPBranchProtected':
124 elif exception_class == 'HTTPBranchProtected':
125 raise exceptions.RepositoryBranchProtectedException()(*result['exception_args'])
125 raise exceptions.RepositoryBranchProtectedException()(*result['exception_args'])
126 elif exception_class == 'RepositoryError':
126 elif exception_class == 'RepositoryError':
127 raise exceptions.VcsException()(*result['exception_args'])
127 raise exceptions.VcsException()(*result['exception_args'])
128 elif exception_class:
128 elif exception_class:
129 raise Exception('Got remote exception "%s" with args "%s"' %
129 raise Exception('Got remote exception "%s" with args "%s"' %
130 (exception_class, result['exception_args']))
130 (exception_class, result['exception_args']))
131
131
132
132
133 def _get_hooks_client(extras):
133 def _get_hooks_client(extras):
134 if 'hooks_uri' in extras:
134 if 'hooks_uri' in extras:
135 protocol = extras.get('hooks_protocol')
135 protocol = extras.get('hooks_protocol')
136 return HooksHttpClient(extras['hooks_uri'])
136 return HooksHttpClient(extras['hooks_uri'])
137 else:
137 else:
138 return HooksDummyClient(extras['hooks_module'])
138 return HooksDummyClient(extras['hooks_module'])
139
139
140
140
141 def _call_hook(hook_name, extras, writer):
141 def _call_hook(hook_name, extras, writer):
142 hooks_client = _get_hooks_client(extras)
142 hooks_client = _get_hooks_client(extras)
143 log.debug('Hooks, using client:%s', hooks_client)
143 log.debug('Hooks, using client:%s', hooks_client)
144 result = hooks_client(hook_name, extras)
144 result = hooks_client(hook_name, extras)
145 log.debug('Hooks got result: %s', result)
145 log.debug('Hooks got result: %s', result)
146
147 _handle_exception(result)
146 writer.write(result['output'])
148 writer.write(result['output'])
147 _handle_exception(result)
148
149
149 return result['status']
150 return result['status']
150
151
151
152
152 def _extras_from_ui(ui):
153 def _extras_from_ui(ui):
153 hook_data = ui.config('rhodecode', 'RC_SCM_DATA')
154 hook_data = ui.config('rhodecode', 'RC_SCM_DATA')
154 if not hook_data:
155 if not hook_data:
155 # maybe it's inside environ ?
156 # maybe it's inside environ ?
156 env_hook_data = os.environ.get('RC_SCM_DATA')
157 env_hook_data = os.environ.get('RC_SCM_DATA')
157 if env_hook_data:
158 if env_hook_data:
158 hook_data = env_hook_data
159 hook_data = env_hook_data
159
160
160 extras = {}
161 extras = {}
161 if hook_data:
162 if hook_data:
162 extras = json.loads(hook_data)
163 extras = json.loads(hook_data)
163 return extras
164 return extras
164
165
165
166
166 def _rev_range_hash(repo, node, check_heads=False):
167 def _rev_range_hash(repo, node, check_heads=False):
167
168
168 commits = []
169 commits = []
169 revs = []
170 revs = []
170 start = repo[node].rev()
171 start = repo[node].rev()
171 end = len(repo)
172 end = len(repo)
172 for rev in range(start, end):
173 for rev in range(start, end):
173 revs.append(rev)
174 revs.append(rev)
174 ctx = repo[rev]
175 ctx = repo[rev]
175 commit_id = mercurial.node.hex(ctx.node())
176 commit_id = mercurial.node.hex(ctx.node())
176 branch = ctx.branch()
177 branch = ctx.branch()
177 commits.append((commit_id, branch))
178 commits.append((commit_id, branch))
178
179
179 parent_heads = []
180 parent_heads = []
180 if check_heads:
181 if check_heads:
181 parent_heads = _check_heads(repo, start, end, revs)
182 parent_heads = _check_heads(repo, start, end, revs)
182 return commits, parent_heads
183 return commits, parent_heads
183
184
184
185
185 def _check_heads(repo, start, end, commits):
186 def _check_heads(repo, start, end, commits):
186 changelog = repo.changelog
187 changelog = repo.changelog
187 parents = set()
188 parents = set()
188
189
189 for new_rev in commits:
190 for new_rev in commits:
190 for p in changelog.parentrevs(new_rev):
191 for p in changelog.parentrevs(new_rev):
191 if p == mercurial.node.nullrev:
192 if p == mercurial.node.nullrev:
192 continue
193 continue
193 if p < start:
194 if p < start:
194 parents.add(p)
195 parents.add(p)
195
196
196 for p in parents:
197 for p in parents:
197 branch = repo[p].branch()
198 branch = repo[p].branch()
198 # The heads descending from that parent, on the same branch
199 # The heads descending from that parent, on the same branch
199 parent_heads = set([p])
200 parent_heads = set([p])
200 reachable = set([p])
201 reachable = set([p])
201 for x in xrange(p + 1, end):
202 for x in xrange(p + 1, end):
202 if repo[x].branch() != branch:
203 if repo[x].branch() != branch:
203 continue
204 continue
204 for pp in changelog.parentrevs(x):
205 for pp in changelog.parentrevs(x):
205 if pp in reachable:
206 if pp in reachable:
206 reachable.add(x)
207 reachable.add(x)
207 parent_heads.discard(pp)
208 parent_heads.discard(pp)
208 parent_heads.add(x)
209 parent_heads.add(x)
209 # More than one head? Suggest merging
210 # More than one head? Suggest merging
210 if len(parent_heads) > 1:
211 if len(parent_heads) > 1:
211 return list(parent_heads)
212 return list(parent_heads)
212
213
213 return []
214 return []
214
215
215
216
216 def repo_size(ui, repo, **kwargs):
217 def repo_size(ui, repo, **kwargs):
217 extras = _extras_from_ui(ui)
218 extras = _extras_from_ui(ui)
218 return _call_hook('repo_size', extras, HgMessageWriter(ui))
219 return _call_hook('repo_size', extras, HgMessageWriter(ui))
219
220
220
221
221 def pre_pull(ui, repo, **kwargs):
222 def pre_pull(ui, repo, **kwargs):
222 extras = _extras_from_ui(ui)
223 extras = _extras_from_ui(ui)
223 return _call_hook('pre_pull', extras, HgMessageWriter(ui))
224 return _call_hook('pre_pull', extras, HgMessageWriter(ui))
224
225
225
226
226 def pre_pull_ssh(ui, repo, **kwargs):
227 def pre_pull_ssh(ui, repo, **kwargs):
227 extras = _extras_from_ui(ui)
228 extras = _extras_from_ui(ui)
228 if extras and extras.get('SSH'):
229 if extras and extras.get('SSH'):
229 return pre_pull(ui, repo, **kwargs)
230 return pre_pull(ui, repo, **kwargs)
230 return 0
231 return 0
231
232
232
233
233 def post_pull(ui, repo, **kwargs):
234 def post_pull(ui, repo, **kwargs):
234 extras = _extras_from_ui(ui)
235 extras = _extras_from_ui(ui)
235 return _call_hook('post_pull', extras, HgMessageWriter(ui))
236 return _call_hook('post_pull', extras, HgMessageWriter(ui))
236
237
237
238
238 def post_pull_ssh(ui, repo, **kwargs):
239 def post_pull_ssh(ui, repo, **kwargs):
239 extras = _extras_from_ui(ui)
240 extras = _extras_from_ui(ui)
240 if extras and extras.get('SSH'):
241 if extras and extras.get('SSH'):
241 return post_pull(ui, repo, **kwargs)
242 return post_pull(ui, repo, **kwargs)
242 return 0
243 return 0
243
244
244
245
245 def pre_push(ui, repo, node=None, **kwargs):
246 def pre_push(ui, repo, node=None, **kwargs):
246 """
247 """
247 Mercurial pre_push hook
248 Mercurial pre_push hook
248 """
249 """
249 extras = _extras_from_ui(ui)
250 extras = _extras_from_ui(ui)
250 detect_force_push = extras.get('detect_force_push')
251 detect_force_push = extras.get('detect_force_push')
251
252
252 rev_data = []
253 rev_data = []
253 if node and kwargs.get('hooktype') == 'pretxnchangegroup':
254 if node and kwargs.get('hooktype') == 'pretxnchangegroup':
254 branches = collections.defaultdict(list)
255 branches = collections.defaultdict(list)
255 commits, _heads = _rev_range_hash(repo, node, check_heads=detect_force_push)
256 commits, _heads = _rev_range_hash(repo, node, check_heads=detect_force_push)
256 for commit_id, branch in commits:
257 for commit_id, branch in commits:
257 branches[branch].append(commit_id)
258 branches[branch].append(commit_id)
258
259
259 for branch, commits in branches.items():
260 for branch, commits in branches.items():
260 old_rev = kwargs.get('node_last') or commits[0]
261 old_rev = kwargs.get('node_last') or commits[0]
261 rev_data.append({
262 rev_data.append({
262 'old_rev': old_rev,
263 'old_rev': old_rev,
263 'new_rev': commits[-1],
264 'new_rev': commits[-1],
264 'ref': '',
265 'ref': '',
265 'type': 'branch',
266 'type': 'branch',
266 'name': branch,
267 'name': branch,
267 })
268 })
268
269
269 for push_ref in rev_data:
270 for push_ref in rev_data:
270 push_ref['multiple_heads'] = _heads
271 push_ref['multiple_heads'] = _heads
271
272
272 extras['commit_ids'] = rev_data
273 extras['commit_ids'] = rev_data
273 return _call_hook('pre_push', extras, HgMessageWriter(ui))
274 return _call_hook('pre_push', extras, HgMessageWriter(ui))
274
275
275
276
276 def pre_push_ssh(ui, repo, node=None, **kwargs):
277 def pre_push_ssh(ui, repo, node=None, **kwargs):
277 extras = _extras_from_ui(ui)
278 extras = _extras_from_ui(ui)
278 if extras.get('SSH'):
279 if extras.get('SSH'):
279 return pre_push(ui, repo, node, **kwargs)
280 return pre_push(ui, repo, node, **kwargs)
280
281
281 return 0
282 return 0
282
283
283
284
284 def pre_push_ssh_auth(ui, repo, node=None, **kwargs):
285 def pre_push_ssh_auth(ui, repo, node=None, **kwargs):
285 """
286 """
286 Mercurial pre_push hook for SSH
287 Mercurial pre_push hook for SSH
287 """
288 """
288 extras = _extras_from_ui(ui)
289 extras = _extras_from_ui(ui)
289 if extras.get('SSH'):
290 if extras.get('SSH'):
290 permission = extras['SSH_PERMISSIONS']
291 permission = extras['SSH_PERMISSIONS']
291
292
292 if 'repository.write' == permission or 'repository.admin' == permission:
293 if 'repository.write' == permission or 'repository.admin' == permission:
293 return 0
294 return 0
294
295
295 # non-zero ret code
296 # non-zero ret code
296 return 1
297 return 1
297
298
298 return 0
299 return 0
299
300
300
301
301 def post_push(ui, repo, node, **kwargs):
302 def post_push(ui, repo, node, **kwargs):
302 """
303 """
303 Mercurial post_push hook
304 Mercurial post_push hook
304 """
305 """
305 extras = _extras_from_ui(ui)
306 extras = _extras_from_ui(ui)
306
307
307 commit_ids = []
308 commit_ids = []
308 branches = []
309 branches = []
309 bookmarks = []
310 bookmarks = []
310 tags = []
311 tags = []
311
312
312 commits, _heads = _rev_range_hash(repo, node)
313 commits, _heads = _rev_range_hash(repo, node)
313 for commit_id, branch in commits:
314 for commit_id, branch in commits:
314 commit_ids.append(commit_id)
315 commit_ids.append(commit_id)
315 if branch not in branches:
316 if branch not in branches:
316 branches.append(branch)
317 branches.append(branch)
317
318
318 if hasattr(ui, '_rc_pushkey_branches'):
319 if hasattr(ui, '_rc_pushkey_branches'):
319 bookmarks = ui._rc_pushkey_branches
320 bookmarks = ui._rc_pushkey_branches
320
321
321 extras['commit_ids'] = commit_ids
322 extras['commit_ids'] = commit_ids
322 extras['new_refs'] = {
323 extras['new_refs'] = {
323 'branches': branches,
324 'branches': branches,
324 'bookmarks': bookmarks,
325 'bookmarks': bookmarks,
325 'tags': tags
326 'tags': tags
326 }
327 }
327
328
328 return _call_hook('post_push', extras, HgMessageWriter(ui))
329 return _call_hook('post_push', extras, HgMessageWriter(ui))
329
330
330
331
331 def post_push_ssh(ui, repo, node, **kwargs):
332 def post_push_ssh(ui, repo, node, **kwargs):
332 """
333 """
333 Mercurial post_push hook for SSH
334 Mercurial post_push hook for SSH
334 """
335 """
335 if _extras_from_ui(ui).get('SSH'):
336 if _extras_from_ui(ui).get('SSH'):
336 return post_push(ui, repo, node, **kwargs)
337 return post_push(ui, repo, node, **kwargs)
337 return 0
338 return 0
338
339
339
340
340 def key_push(ui, repo, **kwargs):
341 def key_push(ui, repo, **kwargs):
341 if kwargs['new'] != '0' and kwargs['namespace'] == 'bookmarks':
342 if kwargs['new'] != '0' and kwargs['namespace'] == 'bookmarks':
342 # store new bookmarks in our UI object propagated later to post_push
343 # store new bookmarks in our UI object propagated later to post_push
343 ui._rc_pushkey_branches = repo[kwargs['key']].bookmarks()
344 ui._rc_pushkey_branches = repo[kwargs['key']].bookmarks()
344 return
345 return
345
346
346
347
347 # backward compat
348 # backward compat
348 log_pull_action = post_pull
349 log_pull_action = post_pull
349
350
350 # backward compat
351 # backward compat
351 log_push_action = post_push
352 log_push_action = post_push
352
353
353
354
354 def handle_git_pre_receive(unused_repo_path, unused_revs, unused_env):
355 def handle_git_pre_receive(unused_repo_path, unused_revs, unused_env):
355 """
356 """
356 Old hook name: keep here for backward compatibility.
357 Old hook name: keep here for backward compatibility.
357
358
358 This is only required when the installed git hooks are not upgraded.
359 This is only required when the installed git hooks are not upgraded.
359 """
360 """
360 pass
361 pass
361
362
362
363
363 def handle_git_post_receive(unused_repo_path, unused_revs, unused_env):
364 def handle_git_post_receive(unused_repo_path, unused_revs, unused_env):
364 """
365 """
365 Old hook name: keep here for backward compatibility.
366 Old hook name: keep here for backward compatibility.
366
367
367 This is only required when the installed git hooks are not upgraded.
368 This is only required when the installed git hooks are not upgraded.
368 """
369 """
369 pass
370 pass
370
371
371
372
372 HookResponse = collections.namedtuple('HookResponse', ('status', 'output'))
373 HookResponse = collections.namedtuple('HookResponse', ('status', 'output'))
373
374
374
375
375 def git_pre_pull(extras):
376 def git_pre_pull(extras):
376 """
377 """
377 Pre pull hook.
378 Pre pull hook.
378
379
379 :param extras: dictionary containing the keys defined in simplevcs
380 :param extras: dictionary containing the keys defined in simplevcs
380 :type extras: dict
381 :type extras: dict
381
382
382 :return: status code of the hook. 0 for success.
383 :return: status code of the hook. 0 for success.
383 :rtype: int
384 :rtype: int
384 """
385 """
385 if 'pull' not in extras['hooks']:
386 if 'pull' not in extras['hooks']:
386 return HookResponse(0, '')
387 return HookResponse(0, '')
387
388
388 stdout = io.BytesIO()
389 stdout = io.BytesIO()
389 try:
390 try:
390 status = _call_hook('pre_pull', extras, GitMessageWriter(stdout))
391 status = _call_hook('pre_pull', extras, GitMessageWriter(stdout))
391 except Exception as error:
392 except Exception as error:
392 status = 128
393 status = 128
393 stdout.write('ERROR: %s\n' % str(error))
394 stdout.write('ERROR: %s\n' % str(error))
394
395
395 return HookResponse(status, stdout.getvalue())
396 return HookResponse(status, stdout.getvalue())
396
397
397
398
398 def git_post_pull(extras):
399 def git_post_pull(extras):
399 """
400 """
400 Post pull hook.
401 Post pull hook.
401
402
402 :param extras: dictionary containing the keys defined in simplevcs
403 :param extras: dictionary containing the keys defined in simplevcs
403 :type extras: dict
404 :type extras: dict
404
405
405 :return: status code of the hook. 0 for success.
406 :return: status code of the hook. 0 for success.
406 :rtype: int
407 :rtype: int
407 """
408 """
408 if 'pull' not in extras['hooks']:
409 if 'pull' not in extras['hooks']:
409 return HookResponse(0, '')
410 return HookResponse(0, '')
410
411
411 stdout = io.BytesIO()
412 stdout = io.BytesIO()
412 try:
413 try:
413 status = _call_hook('post_pull', extras, GitMessageWriter(stdout))
414 status = _call_hook('post_pull', extras, GitMessageWriter(stdout))
414 except Exception as error:
415 except Exception as error:
415 status = 128
416 status = 128
416 stdout.write('ERROR: %s\n' % error)
417 stdout.write('ERROR: %s\n' % error)
417
418
418 return HookResponse(status, stdout.getvalue())
419 return HookResponse(status, stdout.getvalue())
419
420
420
421
421 def _parse_git_ref_lines(revision_lines):
422 def _parse_git_ref_lines(revision_lines):
422 rev_data = []
423 rev_data = []
423 for revision_line in revision_lines or []:
424 for revision_line in revision_lines or []:
424 old_rev, new_rev, ref = revision_line.strip().split(' ')
425 old_rev, new_rev, ref = revision_line.strip().split(' ')
425 ref_data = ref.split('/', 2)
426 ref_data = ref.split('/', 2)
426 if ref_data[1] in ('tags', 'heads'):
427 if ref_data[1] in ('tags', 'heads'):
427 rev_data.append({
428 rev_data.append({
428 'old_rev': old_rev,
429 'old_rev': old_rev,
429 'new_rev': new_rev,
430 'new_rev': new_rev,
430 'ref': ref,
431 'ref': ref,
431 'type': ref_data[1],
432 'type': ref_data[1],
432 'name': ref_data[2],
433 'name': ref_data[2],
433 })
434 })
434 return rev_data
435 return rev_data
435
436
436
437
437 def git_pre_receive(unused_repo_path, revision_lines, env):
438 def git_pre_receive(unused_repo_path, revision_lines, env):
438 """
439 """
439 Pre push hook.
440 Pre push hook.
440
441
441 :param extras: dictionary containing the keys defined in simplevcs
442 :param extras: dictionary containing the keys defined in simplevcs
442 :type extras: dict
443 :type extras: dict
443
444
444 :return: status code of the hook. 0 for success.
445 :return: status code of the hook. 0 for success.
445 :rtype: int
446 :rtype: int
446 """
447 """
447 extras = json.loads(env['RC_SCM_DATA'])
448 extras = json.loads(env['RC_SCM_DATA'])
448 rev_data = _parse_git_ref_lines(revision_lines)
449 rev_data = _parse_git_ref_lines(revision_lines)
449 if 'push' not in extras['hooks']:
450 if 'push' not in extras['hooks']:
450 return 0
451 return 0
451 empty_commit_id = '0' * 40
452 empty_commit_id = '0' * 40
452
453
453 detect_force_push = extras.get('detect_force_push')
454 detect_force_push = extras.get('detect_force_push')
454
455
455 for push_ref in rev_data:
456 for push_ref in rev_data:
456 # store our git-env which holds the temp store
457 # store our git-env which holds the temp store
457 push_ref['git_env'] = [
458 push_ref['git_env'] = [
458 (k, v) for k, v in os.environ.items() if k.startswith('GIT')]
459 (k, v) for k, v in os.environ.items() if k.startswith('GIT')]
459 push_ref['pruned_sha'] = ''
460 push_ref['pruned_sha'] = ''
460 if not detect_force_push:
461 if not detect_force_push:
461 # don't check for forced-push when we don't need to
462 # don't check for forced-push when we don't need to
462 continue
463 continue
463
464
464 type_ = push_ref['type']
465 type_ = push_ref['type']
465 new_branch = push_ref['old_rev'] == empty_commit_id
466 new_branch = push_ref['old_rev'] == empty_commit_id
466 if type_ == 'heads' and not new_branch:
467 if type_ == 'heads' and not new_branch:
467 old_rev = push_ref['old_rev']
468 old_rev = push_ref['old_rev']
468 new_rev = push_ref['new_rev']
469 new_rev = push_ref['new_rev']
469 cmd = [settings.GIT_EXECUTABLE, 'rev-list',
470 cmd = [settings.GIT_EXECUTABLE, 'rev-list',
470 old_rev, '^{}'.format(new_rev)]
471 old_rev, '^{}'.format(new_rev)]
471 stdout, stderr = subprocessio.run_command(
472 stdout, stderr = subprocessio.run_command(
472 cmd, env=os.environ.copy())
473 cmd, env=os.environ.copy())
473 # means we're having some non-reachable objects, this forced push
474 # means we're having some non-reachable objects, this forced push
474 # was used
475 # was used
475 if stdout:
476 if stdout:
476 push_ref['pruned_sha'] = stdout.splitlines()
477 push_ref['pruned_sha'] = stdout.splitlines()
477
478
478 extras['commit_ids'] = rev_data
479 extras['commit_ids'] = rev_data
479 return _call_hook('pre_push', extras, GitMessageWriter())
480 return _call_hook('pre_push', extras, GitMessageWriter())
480
481
481
482
482 def git_post_receive(unused_repo_path, revision_lines, env):
483 def git_post_receive(unused_repo_path, revision_lines, env):
483 """
484 """
484 Post push hook.
485 Post push hook.
485
486
486 :param extras: dictionary containing the keys defined in simplevcs
487 :param extras: dictionary containing the keys defined in simplevcs
487 :type extras: dict
488 :type extras: dict
488
489
489 :return: status code of the hook. 0 for success.
490 :return: status code of the hook. 0 for success.
490 :rtype: int
491 :rtype: int
491 """
492 """
492 extras = json.loads(env['RC_SCM_DATA'])
493 extras = json.loads(env['RC_SCM_DATA'])
493 if 'push' not in extras['hooks']:
494 if 'push' not in extras['hooks']:
494 return 0
495 return 0
495
496
496 rev_data = _parse_git_ref_lines(revision_lines)
497 rev_data = _parse_git_ref_lines(revision_lines)
497
498
498 git_revs = []
499 git_revs = []
499
500
500 # N.B.(skreft): it is ok to just call git, as git before calling a
501 # N.B.(skreft): it is ok to just call git, as git before calling a
501 # subcommand sets the PATH environment variable so that it point to the
502 # subcommand sets the PATH environment variable so that it point to the
502 # correct version of the git executable.
503 # correct version of the git executable.
503 empty_commit_id = '0' * 40
504 empty_commit_id = '0' * 40
504 branches = []
505 branches = []
505 tags = []
506 tags = []
506 for push_ref in rev_data:
507 for push_ref in rev_data:
507 type_ = push_ref['type']
508 type_ = push_ref['type']
508
509
509 if type_ == 'heads':
510 if type_ == 'heads':
510 if push_ref['old_rev'] == empty_commit_id:
511 if push_ref['old_rev'] == empty_commit_id:
511 # starting new branch case
512 # starting new branch case
512 if push_ref['name'] not in branches:
513 if push_ref['name'] not in branches:
513 branches.append(push_ref['name'])
514 branches.append(push_ref['name'])
514
515
515 # Fix up head revision if needed
516 # Fix up head revision if needed
516 cmd = [settings.GIT_EXECUTABLE, 'show', 'HEAD']
517 cmd = [settings.GIT_EXECUTABLE, 'show', 'HEAD']
517 try:
518 try:
518 subprocessio.run_command(cmd, env=os.environ.copy())
519 subprocessio.run_command(cmd, env=os.environ.copy())
519 except Exception:
520 except Exception:
520 cmd = [settings.GIT_EXECUTABLE, 'symbolic-ref', 'HEAD',
521 cmd = [settings.GIT_EXECUTABLE, 'symbolic-ref', 'HEAD',
521 'refs/heads/%s' % push_ref['name']]
522 'refs/heads/%s' % push_ref['name']]
522 print("Setting default branch to %s" % push_ref['name'])
523 print("Setting default branch to %s" % push_ref['name'])
523 subprocessio.run_command(cmd, env=os.environ.copy())
524 subprocessio.run_command(cmd, env=os.environ.copy())
524
525
525 cmd = [settings.GIT_EXECUTABLE, 'for-each-ref',
526 cmd = [settings.GIT_EXECUTABLE, 'for-each-ref',
526 '--format=%(refname)', 'refs/heads/*']
527 '--format=%(refname)', 'refs/heads/*']
527 stdout, stderr = subprocessio.run_command(
528 stdout, stderr = subprocessio.run_command(
528 cmd, env=os.environ.copy())
529 cmd, env=os.environ.copy())
529 heads = stdout
530 heads = stdout
530 heads = heads.replace(push_ref['ref'], '')
531 heads = heads.replace(push_ref['ref'], '')
531 heads = ' '.join(head for head
532 heads = ' '.join(head for head
532 in heads.splitlines() if head) or '.'
533 in heads.splitlines() if head) or '.'
533 cmd = [settings.GIT_EXECUTABLE, 'log', '--reverse',
534 cmd = [settings.GIT_EXECUTABLE, 'log', '--reverse',
534 '--pretty=format:%H', '--', push_ref['new_rev'],
535 '--pretty=format:%H', '--', push_ref['new_rev'],
535 '--not', heads]
536 '--not', heads]
536 stdout, stderr = subprocessio.run_command(
537 stdout, stderr = subprocessio.run_command(
537 cmd, env=os.environ.copy())
538 cmd, env=os.environ.copy())
538 git_revs.extend(stdout.splitlines())
539 git_revs.extend(stdout.splitlines())
539 elif push_ref['new_rev'] == empty_commit_id:
540 elif push_ref['new_rev'] == empty_commit_id:
540 # delete branch case
541 # delete branch case
541 git_revs.append('delete_branch=>%s' % push_ref['name'])
542 git_revs.append('delete_branch=>%s' % push_ref['name'])
542 else:
543 else:
543 if push_ref['name'] not in branches:
544 if push_ref['name'] not in branches:
544 branches.append(push_ref['name'])
545 branches.append(push_ref['name'])
545
546
546 cmd = [settings.GIT_EXECUTABLE, 'log',
547 cmd = [settings.GIT_EXECUTABLE, 'log',
547 '{old_rev}..{new_rev}'.format(**push_ref),
548 '{old_rev}..{new_rev}'.format(**push_ref),
548 '--reverse', '--pretty=format:%H']
549 '--reverse', '--pretty=format:%H']
549 stdout, stderr = subprocessio.run_command(
550 stdout, stderr = subprocessio.run_command(
550 cmd, env=os.environ.copy())
551 cmd, env=os.environ.copy())
551 git_revs.extend(stdout.splitlines())
552 git_revs.extend(stdout.splitlines())
552 elif type_ == 'tags':
553 elif type_ == 'tags':
553 if push_ref['name'] not in tags:
554 if push_ref['name'] not in tags:
554 tags.append(push_ref['name'])
555 tags.append(push_ref['name'])
555 git_revs.append('tag=>%s' % push_ref['name'])
556 git_revs.append('tag=>%s' % push_ref['name'])
556
557
557 extras['commit_ids'] = git_revs
558 extras['commit_ids'] = git_revs
558 extras['new_refs'] = {
559 extras['new_refs'] = {
559 'branches': branches,
560 'branches': branches,
560 'bookmarks': [],
561 'bookmarks': [],
561 'tags': tags,
562 'tags': tags,
562 }
563 }
563
564
564 if 'repo_size' in extras['hooks']:
565 if 'repo_size' in extras['hooks']:
565 try:
566 try:
566 _call_hook('repo_size', extras, GitMessageWriter())
567 _call_hook('repo_size', extras, GitMessageWriter())
567 except:
568 except:
568 pass
569 pass
569
570
570 return _call_hook('post_push', extras, GitMessageWriter())
571 return _call_hook('post_push', extras, GitMessageWriter())
571
572
572
573
573 def _get_extras_from_txn_id(path, txn_id):
574 def _get_extras_from_txn_id(path, txn_id):
574 extras = {}
575 extras = {}
575 try:
576 try:
576 cmd = ['svnlook', 'pget',
577 cmd = ['svnlook', 'pget',
577 '-t', txn_id,
578 '-t', txn_id,
578 '--revprop', path, 'rc-scm-extras']
579 '--revprop', path, 'rc-scm-extras']
579 stdout, stderr = subprocessio.run_command(
580 stdout, stderr = subprocessio.run_command(
580 cmd, env=os.environ.copy())
581 cmd, env=os.environ.copy())
581 extras = json.loads(base64.urlsafe_b64decode(stdout))
582 extras = json.loads(base64.urlsafe_b64decode(stdout))
582 except Exception:
583 except Exception:
583 log.exception('Failed to extract extras info from txn_id')
584 log.exception('Failed to extract extras info from txn_id')
584
585
585 return extras
586 return extras
586
587
587
588
588 def svn_pre_commit(repo_path, commit_data, env):
589 def svn_pre_commit(repo_path, commit_data, env):
589 path, txn_id = commit_data
590 path, txn_id = commit_data
590 branches = []
591 branches = []
591 tags = []
592 tags = []
592
593
593 if env.get('RC_SCM_DATA'):
594 if env.get('RC_SCM_DATA'):
594 extras = json.loads(env['RC_SCM_DATA'])
595 extras = json.loads(env['RC_SCM_DATA'])
595 else:
596 else:
596 # fallback method to read from TXN-ID stored data
597 # fallback method to read from TXN-ID stored data
597 extras = _get_extras_from_txn_id(path, txn_id)
598 extras = _get_extras_from_txn_id(path, txn_id)
598 if not extras:
599 if not extras:
599 return 0
600 return 0
600
601
601 extras['commit_ids'] = []
602 extras['commit_ids'] = []
602 extras['txn_id'] = txn_id
603 extras['txn_id'] = txn_id
603 extras['new_refs'] = {
604 extras['new_refs'] = {
604 'branches': branches,
605 'branches': branches,
605 'bookmarks': [],
606 'bookmarks': [],
606 'tags': tags,
607 'tags': tags,
607 }
608 }
608
609
609 return _call_hook('pre_push', extras, SvnMessageWriter())
610 return _call_hook('pre_push', extras, SvnMessageWriter())
610
611
611
612
612 def _get_extras_from_commit_id(commit_id, path):
613 def _get_extras_from_commit_id(commit_id, path):
613 extras = {}
614 extras = {}
614 try:
615 try:
615 cmd = ['svnlook', 'pget',
616 cmd = ['svnlook', 'pget',
616 '-r', commit_id,
617 '-r', commit_id,
617 '--revprop', path, 'rc-scm-extras']
618 '--revprop', path, 'rc-scm-extras']
618 stdout, stderr = subprocessio.run_command(
619 stdout, stderr = subprocessio.run_command(
619 cmd, env=os.environ.copy())
620 cmd, env=os.environ.copy())
620 extras = json.loads(base64.urlsafe_b64decode(stdout))
621 extras = json.loads(base64.urlsafe_b64decode(stdout))
621 except Exception:
622 except Exception:
622 log.exception('Failed to extract extras info from commit_id')
623 log.exception('Failed to extract extras info from commit_id')
623
624
624 return extras
625 return extras
625
626
626
627
627 def svn_post_commit(repo_path, commit_data, env):
628 def svn_post_commit(repo_path, commit_data, env):
628 """
629 """
629 commit_data is path, rev, txn_id
630 commit_data is path, rev, txn_id
630 """
631 """
631 path, commit_id, txn_id = commit_data
632 path, commit_id, txn_id = commit_data
632 branches = []
633 branches = []
633 tags = []
634 tags = []
634
635
635 if env.get('RC_SCM_DATA'):
636 if env.get('RC_SCM_DATA'):
636 extras = json.loads(env['RC_SCM_DATA'])
637 extras = json.loads(env['RC_SCM_DATA'])
637 else:
638 else:
638 # fallback method to read from TXN-ID stored data
639 # fallback method to read from TXN-ID stored data
639 extras = _get_extras_from_commit_id(commit_id, path)
640 extras = _get_extras_from_commit_id(commit_id, path)
640 if not extras:
641 if not extras:
641 return 0
642 return 0
642
643
643 extras['commit_ids'] = [commit_id]
644 extras['commit_ids'] = [commit_id]
644 extras['txn_id'] = txn_id
645 extras['txn_id'] = txn_id
645 extras['new_refs'] = {
646 extras['new_refs'] = {
646 'branches': branches,
647 'branches': branches,
647 'bookmarks': [],
648 'bookmarks': [],
648 'tags': tags,
649 'tags': tags,
649 }
650 }
650
651
651 if 'repo_size' in extras['hooks']:
652 if 'repo_size' in extras['hooks']:
652 try:
653 try:
653 _call_hook('repo_size', extras, SvnMessageWriter())
654 _call_hook('repo_size', extras, SvnMessageWriter())
654 except Exception:
655 except Exception:
655 pass
656 pass
656
657
657 return _call_hook('post_push', extras, SvnMessageWriter())
658 return _call_hook('post_push', extras, SvnMessageWriter())
General Comments 0
You need to be logged in to leave comments. Login now