##// END OF EJS Templates
hooks: expose few extra variables from hook calls
marcink -
r557:47d2196a default
parent child Browse files
Show More
@@ -1,662 +1,700 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
146
147 _handle_exception(result)
147 _handle_exception(result)
148 writer.write(result['output'])
148 writer.write(result['output'])
149
149
150 return result['status']
150 return result['status']
151
151
152
152
153 def _extras_from_ui(ui):
153 def _extras_from_ui(ui):
154 hook_data = ui.config('rhodecode', 'RC_SCM_DATA')
154 hook_data = ui.config('rhodecode', 'RC_SCM_DATA')
155 if not hook_data:
155 if not hook_data:
156 # maybe it's inside environ ?
156 # maybe it's inside environ ?
157 env_hook_data = os.environ.get('RC_SCM_DATA')
157 env_hook_data = os.environ.get('RC_SCM_DATA')
158 if env_hook_data:
158 if env_hook_data:
159 hook_data = env_hook_data
159 hook_data = env_hook_data
160
160
161 extras = {}
161 extras = {}
162 if hook_data:
162 if hook_data:
163 extras = json.loads(hook_data)
163 extras = json.loads(hook_data)
164 return extras
164 return extras
165
165
166
166
167 def _rev_range_hash(repo, node, check_heads=False):
167 def _rev_range_hash(repo, node, check_heads=False):
168
168
169 commits = []
169 commits = []
170 revs = []
170 revs = []
171 start = repo[node].rev()
171 start = repo[node].rev()
172 end = len(repo)
172 end = len(repo)
173 for rev in range(start, end):
173 for rev in range(start, end):
174 revs.append(rev)
174 revs.append(rev)
175 ctx = repo[rev]
175 ctx = repo[rev]
176 commit_id = mercurial.node.hex(ctx.node())
176 commit_id = mercurial.node.hex(ctx.node())
177 branch = ctx.branch()
177 branch = ctx.branch()
178 commits.append((commit_id, branch))
178 commits.append((commit_id, branch))
179
179
180 parent_heads = []
180 parent_heads = []
181 if check_heads:
181 if check_heads:
182 parent_heads = _check_heads(repo, start, end, revs)
182 parent_heads = _check_heads(repo, start, end, revs)
183 return commits, parent_heads
183 return commits, parent_heads
184
184
185
185
186 def _check_heads(repo, start, end, commits):
186 def _check_heads(repo, start, end, commits):
187 changelog = repo.changelog
187 changelog = repo.changelog
188 parents = set()
188 parents = set()
189
189
190 for new_rev in commits:
190 for new_rev in commits:
191 for p in changelog.parentrevs(new_rev):
191 for p in changelog.parentrevs(new_rev):
192 if p == mercurial.node.nullrev:
192 if p == mercurial.node.nullrev:
193 continue
193 continue
194 if p < start:
194 if p < start:
195 parents.add(p)
195 parents.add(p)
196
196
197 for p in parents:
197 for p in parents:
198 branch = repo[p].branch()
198 branch = repo[p].branch()
199 # The heads descending from that parent, on the same branch
199 # The heads descending from that parent, on the same branch
200 parent_heads = set([p])
200 parent_heads = set([p])
201 reachable = set([p])
201 reachable = set([p])
202 for x in xrange(p + 1, end):
202 for x in xrange(p + 1, end):
203 if repo[x].branch() != branch:
203 if repo[x].branch() != branch:
204 continue
204 continue
205 for pp in changelog.parentrevs(x):
205 for pp in changelog.parentrevs(x):
206 if pp in reachable:
206 if pp in reachable:
207 reachable.add(x)
207 reachable.add(x)
208 parent_heads.discard(pp)
208 parent_heads.discard(pp)
209 parent_heads.add(x)
209 parent_heads.add(x)
210 # More than one head? Suggest merging
210 # More than one head? Suggest merging
211 if len(parent_heads) > 1:
211 if len(parent_heads) > 1:
212 return list(parent_heads)
212 return list(parent_heads)
213
213
214 return []
214 return []
215
215
216
216
217 def _get_git_env():
218 env = {}
219 for k, v in os.environ.items():
220 if k.startswith('GIT'):
221 env[k] = v
222
223 # serialized version
224 return [(k, v) for k, v in env.items()]
225
226
227 def _get_hg_env(old_rev, new_rev, txnid, repo_path):
228 env = {}
229 for k, v in os.environ.items():
230 if k.startswith('HG'):
231 env[k] = v
232
233 env['HG_NODE'] = old_rev
234 env['HG_NODE_LAST'] = new_rev
235 env['HG_TXNID'] = txnid
236 env['HG_PENDING'] = repo_path
237
238 return [(k, v) for k, v in env.items()]
239
240
217 def repo_size(ui, repo, **kwargs):
241 def repo_size(ui, repo, **kwargs):
218 extras = _extras_from_ui(ui)
242 extras = _extras_from_ui(ui)
219 return _call_hook('repo_size', extras, HgMessageWriter(ui))
243 return _call_hook('repo_size', extras, HgMessageWriter(ui))
220
244
221
245
222 def pre_pull(ui, repo, **kwargs):
246 def pre_pull(ui, repo, **kwargs):
223 extras = _extras_from_ui(ui)
247 extras = _extras_from_ui(ui)
224 return _call_hook('pre_pull', extras, HgMessageWriter(ui))
248 return _call_hook('pre_pull', extras, HgMessageWriter(ui))
225
249
226
250
227 def pre_pull_ssh(ui, repo, **kwargs):
251 def pre_pull_ssh(ui, repo, **kwargs):
228 extras = _extras_from_ui(ui)
252 extras = _extras_from_ui(ui)
229 if extras and extras.get('SSH'):
253 if extras and extras.get('SSH'):
230 return pre_pull(ui, repo, **kwargs)
254 return pre_pull(ui, repo, **kwargs)
231 return 0
255 return 0
232
256
233
257
234 def post_pull(ui, repo, **kwargs):
258 def post_pull(ui, repo, **kwargs):
235 extras = _extras_from_ui(ui)
259 extras = _extras_from_ui(ui)
236 return _call_hook('post_pull', extras, HgMessageWriter(ui))
260 return _call_hook('post_pull', extras, HgMessageWriter(ui))
237
261
238
262
239 def post_pull_ssh(ui, repo, **kwargs):
263 def post_pull_ssh(ui, repo, **kwargs):
240 extras = _extras_from_ui(ui)
264 extras = _extras_from_ui(ui)
241 if extras and extras.get('SSH'):
265 if extras and extras.get('SSH'):
242 return post_pull(ui, repo, **kwargs)
266 return post_pull(ui, repo, **kwargs)
243 return 0
267 return 0
244
268
245
269
246 def pre_push(ui, repo, node=None, **kwargs):
270 def pre_push(ui, repo, node=None, **kwargs):
247 """
271 """
248 Mercurial pre_push hook
272 Mercurial pre_push hook
249 """
273 """
250 extras = _extras_from_ui(ui)
274 extras = _extras_from_ui(ui)
251 detect_force_push = extras.get('detect_force_push')
275 detect_force_push = extras.get('detect_force_push')
252
276
253 rev_data = []
277 rev_data = []
254 if node and kwargs.get('hooktype') == 'pretxnchangegroup':
278 if node and kwargs.get('hooktype') == 'pretxnchangegroup':
255 branches = collections.defaultdict(list)
279 branches = collections.defaultdict(list)
256 commits, _heads = _rev_range_hash(repo, node, check_heads=detect_force_push)
280 commits, _heads = _rev_range_hash(repo, node, check_heads=detect_force_push)
257 for commit_id, branch in commits:
281 for commit_id, branch in commits:
258 branches[branch].append(commit_id)
282 branches[branch].append(commit_id)
259
283
260 for branch, commits in branches.items():
284 for branch, commits in branches.items():
261 old_rev = kwargs.get('node_last') or commits[0]
285 old_rev = kwargs.get('node_last') or commits[0]
262 rev_data.append({
286 rev_data.append({
287 'total_commits': len(commits),
263 'old_rev': old_rev,
288 'old_rev': old_rev,
264 'new_rev': commits[-1],
289 'new_rev': commits[-1],
265 'ref': '',
290 'ref': '',
266 'type': 'branch',
291 'type': 'branch',
267 'name': branch,
292 'name': branch,
268 })
293 })
269
294
270 for push_ref in rev_data:
295 for push_ref in rev_data:
271 push_ref['multiple_heads'] = _heads
296 push_ref['multiple_heads'] = _heads
272
297
298 repo_path = os.path.join(
299 extras.get('repo_store', ''), extras.get('repository', ''))
300 push_ref['hg_env'] = _get_hg_env(
301 old_rev=push_ref['old_rev'],
302 new_rev=push_ref['new_rev'], txnid=kwargs.get('txnid'),
303 repo_path=repo_path)
304
273 extras['hook_type'] = kwargs.get('hooktype', 'pre_push')
305 extras['hook_type'] = kwargs.get('hooktype', 'pre_push')
274 extras['commit_ids'] = rev_data
306 extras['commit_ids'] = rev_data
307
275 return _call_hook('pre_push', extras, HgMessageWriter(ui))
308 return _call_hook('pre_push', extras, HgMessageWriter(ui))
276
309
277
310
278 def pre_push_ssh(ui, repo, node=None, **kwargs):
311 def pre_push_ssh(ui, repo, node=None, **kwargs):
279 extras = _extras_from_ui(ui)
312 extras = _extras_from_ui(ui)
280 if extras.get('SSH'):
313 if extras.get('SSH'):
281 return pre_push(ui, repo, node, **kwargs)
314 return pre_push(ui, repo, node, **kwargs)
282
315
283 return 0
316 return 0
284
317
285
318
286 def pre_push_ssh_auth(ui, repo, node=None, **kwargs):
319 def pre_push_ssh_auth(ui, repo, node=None, **kwargs):
287 """
320 """
288 Mercurial pre_push hook for SSH
321 Mercurial pre_push hook for SSH
289 """
322 """
290 extras = _extras_from_ui(ui)
323 extras = _extras_from_ui(ui)
291 if extras.get('SSH'):
324 if extras.get('SSH'):
292 permission = extras['SSH_PERMISSIONS']
325 permission = extras['SSH_PERMISSIONS']
293
326
294 if 'repository.write' == permission or 'repository.admin' == permission:
327 if 'repository.write' == permission or 'repository.admin' == permission:
295 return 0
328 return 0
296
329
297 # non-zero ret code
330 # non-zero ret code
298 return 1
331 return 1
299
332
300 return 0
333 return 0
301
334
302
335
303 def post_push(ui, repo, node, **kwargs):
336 def post_push(ui, repo, node, **kwargs):
304 """
337 """
305 Mercurial post_push hook
338 Mercurial post_push hook
306 """
339 """
307 extras = _extras_from_ui(ui)
340 extras = _extras_from_ui(ui)
308
341
309 commit_ids = []
342 commit_ids = []
310 branches = []
343 branches = []
311 bookmarks = []
344 bookmarks = []
312 tags = []
345 tags = []
313
346
314 commits, _heads = _rev_range_hash(repo, node)
347 commits, _heads = _rev_range_hash(repo, node)
315 for commit_id, branch in commits:
348 for commit_id, branch in commits:
316 commit_ids.append(commit_id)
349 commit_ids.append(commit_id)
317 if branch not in branches:
350 if branch not in branches:
318 branches.append(branch)
351 branches.append(branch)
319
352
320 if hasattr(ui, '_rc_pushkey_branches'):
353 if hasattr(ui, '_rc_pushkey_branches'):
321 bookmarks = ui._rc_pushkey_branches
354 bookmarks = ui._rc_pushkey_branches
322
355
323 extras['hook_type'] = kwargs.get('hooktype', 'post_push')
356 extras['hook_type'] = kwargs.get('hooktype', 'post_push')
324 extras['commit_ids'] = commit_ids
357 extras['commit_ids'] = commit_ids
325 extras['new_refs'] = {
358 extras['new_refs'] = {
326 'branches': branches,
359 'branches': branches,
327 'bookmarks': bookmarks,
360 'bookmarks': bookmarks,
328 'tags': tags
361 'tags': tags
329 }
362 }
330
363
331 return _call_hook('post_push', extras, HgMessageWriter(ui))
364 return _call_hook('post_push', extras, HgMessageWriter(ui))
332
365
333
366
334 def post_push_ssh(ui, repo, node, **kwargs):
367 def post_push_ssh(ui, repo, node, **kwargs):
335 """
368 """
336 Mercurial post_push hook for SSH
369 Mercurial post_push hook for SSH
337 """
370 """
338 if _extras_from_ui(ui).get('SSH'):
371 if _extras_from_ui(ui).get('SSH'):
339 return post_push(ui, repo, node, **kwargs)
372 return post_push(ui, repo, node, **kwargs)
340 return 0
373 return 0
341
374
342
375
343 def key_push(ui, repo, **kwargs):
376 def key_push(ui, repo, **kwargs):
344 if kwargs['new'] != '0' and kwargs['namespace'] == 'bookmarks':
377 if kwargs['new'] != '0' and kwargs['namespace'] == 'bookmarks':
345 # store new bookmarks in our UI object propagated later to post_push
378 # store new bookmarks in our UI object propagated later to post_push
346 ui._rc_pushkey_branches = repo[kwargs['key']].bookmarks()
379 ui._rc_pushkey_branches = repo[kwargs['key']].bookmarks()
347 return
380 return
348
381
349
382
350 # backward compat
383 # backward compat
351 log_pull_action = post_pull
384 log_pull_action = post_pull
352
385
353 # backward compat
386 # backward compat
354 log_push_action = post_push
387 log_push_action = post_push
355
388
356
389
357 def handle_git_pre_receive(unused_repo_path, unused_revs, unused_env):
390 def handle_git_pre_receive(unused_repo_path, unused_revs, unused_env):
358 """
391 """
359 Old hook name: keep here for backward compatibility.
392 Old hook name: keep here for backward compatibility.
360
393
361 This is only required when the installed git hooks are not upgraded.
394 This is only required when the installed git hooks are not upgraded.
362 """
395 """
363 pass
396 pass
364
397
365
398
366 def handle_git_post_receive(unused_repo_path, unused_revs, unused_env):
399 def handle_git_post_receive(unused_repo_path, unused_revs, unused_env):
367 """
400 """
368 Old hook name: keep here for backward compatibility.
401 Old hook name: keep here for backward compatibility.
369
402
370 This is only required when the installed git hooks are not upgraded.
403 This is only required when the installed git hooks are not upgraded.
371 """
404 """
372 pass
405 pass
373
406
374
407
375 HookResponse = collections.namedtuple('HookResponse', ('status', 'output'))
408 HookResponse = collections.namedtuple('HookResponse', ('status', 'output'))
376
409
377
410
378 def git_pre_pull(extras):
411 def git_pre_pull(extras):
379 """
412 """
380 Pre pull hook.
413 Pre pull hook.
381
414
382 :param extras: dictionary containing the keys defined in simplevcs
415 :param extras: dictionary containing the keys defined in simplevcs
383 :type extras: dict
416 :type extras: dict
384
417
385 :return: status code of the hook. 0 for success.
418 :return: status code of the hook. 0 for success.
386 :rtype: int
419 :rtype: int
387 """
420 """
388 if 'pull' not in extras['hooks']:
421 if 'pull' not in extras['hooks']:
389 return HookResponse(0, '')
422 return HookResponse(0, '')
390
423
391 stdout = io.BytesIO()
424 stdout = io.BytesIO()
392 try:
425 try:
393 status = _call_hook('pre_pull', extras, GitMessageWriter(stdout))
426 status = _call_hook('pre_pull', extras, GitMessageWriter(stdout))
394 except Exception as error:
427 except Exception as error:
395 status = 128
428 status = 128
396 stdout.write('ERROR: %s\n' % str(error))
429 stdout.write('ERROR: %s\n' % str(error))
397
430
398 return HookResponse(status, stdout.getvalue())
431 return HookResponse(status, stdout.getvalue())
399
432
400
433
401 def git_post_pull(extras):
434 def git_post_pull(extras):
402 """
435 """
403 Post pull hook.
436 Post pull hook.
404
437
405 :param extras: dictionary containing the keys defined in simplevcs
438 :param extras: dictionary containing the keys defined in simplevcs
406 :type extras: dict
439 :type extras: dict
407
440
408 :return: status code of the hook. 0 for success.
441 :return: status code of the hook. 0 for success.
409 :rtype: int
442 :rtype: int
410 """
443 """
411 if 'pull' not in extras['hooks']:
444 if 'pull' not in extras['hooks']:
412 return HookResponse(0, '')
445 return HookResponse(0, '')
413
446
414 stdout = io.BytesIO()
447 stdout = io.BytesIO()
415 try:
448 try:
416 status = _call_hook('post_pull', extras, GitMessageWriter(stdout))
449 status = _call_hook('post_pull', extras, GitMessageWriter(stdout))
417 except Exception as error:
450 except Exception as error:
418 status = 128
451 status = 128
419 stdout.write('ERROR: %s\n' % error)
452 stdout.write('ERROR: %s\n' % error)
420
453
421 return HookResponse(status, stdout.getvalue())
454 return HookResponse(status, stdout.getvalue())
422
455
423
456
424 def _parse_git_ref_lines(revision_lines):
457 def _parse_git_ref_lines(revision_lines):
425 rev_data = []
458 rev_data = []
426 for revision_line in revision_lines or []:
459 for revision_line in revision_lines or []:
427 old_rev, new_rev, ref = revision_line.strip().split(' ')
460 old_rev, new_rev, ref = revision_line.strip().split(' ')
428 ref_data = ref.split('/', 2)
461 ref_data = ref.split('/', 2)
429 if ref_data[1] in ('tags', 'heads'):
462 if ref_data[1] in ('tags', 'heads'):
430 rev_data.append({
463 rev_data.append({
464 # NOTE(marcink):
465 # we're unable to tell total_commits for git at this point
466 # but we set the variable for consistency with GIT
467 'total_commits': -1,
431 'old_rev': old_rev,
468 'old_rev': old_rev,
432 'new_rev': new_rev,
469 'new_rev': new_rev,
433 'ref': ref,
470 'ref': ref,
434 'type': ref_data[1],
471 'type': ref_data[1],
435 'name': ref_data[2],
472 'name': ref_data[2],
436 })
473 })
437 return rev_data
474 return rev_data
438
475
439
476
440 def git_pre_receive(unused_repo_path, revision_lines, env):
477 def git_pre_receive(unused_repo_path, revision_lines, env):
441 """
478 """
442 Pre push hook.
479 Pre push hook.
443
480
444 :param extras: dictionary containing the keys defined in simplevcs
481 :param extras: dictionary containing the keys defined in simplevcs
445 :type extras: dict
482 :type extras: dict
446
483
447 :return: status code of the hook. 0 for success.
484 :return: status code of the hook. 0 for success.
448 :rtype: int
485 :rtype: int
449 """
486 """
450 extras = json.loads(env['RC_SCM_DATA'])
487 extras = json.loads(env['RC_SCM_DATA'])
451 rev_data = _parse_git_ref_lines(revision_lines)
488 rev_data = _parse_git_ref_lines(revision_lines)
452 if 'push' not in extras['hooks']:
489 if 'push' not in extras['hooks']:
453 return 0
490 return 0
454 empty_commit_id = '0' * 40
491 empty_commit_id = '0' * 40
455
492
456 detect_force_push = extras.get('detect_force_push')
493 detect_force_push = extras.get('detect_force_push')
457
494
458 for push_ref in rev_data:
495 for push_ref in rev_data:
459 # store our git-env which holds the temp store
496 # store our git-env which holds the temp store
460 push_ref['git_env'] = [
497 push_ref['git_env'] = _get_git_env()
461 (k, v) for k, v in os.environ.items() if k.startswith('GIT')]
462 push_ref['pruned_sha'] = ''
498 push_ref['pruned_sha'] = ''
463 if not detect_force_push:
499 if not detect_force_push:
464 # don't check for forced-push when we don't need to
500 # don't check for forced-push when we don't need to
465 continue
501 continue
466
502
467 type_ = push_ref['type']
503 type_ = push_ref['type']
468 new_branch = push_ref['old_rev'] == empty_commit_id
504 new_branch = push_ref['old_rev'] == empty_commit_id
469 if type_ == 'heads' and not new_branch:
505 if type_ == 'heads' and not new_branch:
470 old_rev = push_ref['old_rev']
506 old_rev = push_ref['old_rev']
471 new_rev = push_ref['new_rev']
507 new_rev = push_ref['new_rev']
472 cmd = [settings.GIT_EXECUTABLE, 'rev-list',
508 cmd = [settings.GIT_EXECUTABLE, 'rev-list',
473 old_rev, '^{}'.format(new_rev)]
509 old_rev, '^{}'.format(new_rev)]
474 stdout, stderr = subprocessio.run_command(
510 stdout, stderr = subprocessio.run_command(
475 cmd, env=os.environ.copy())
511 cmd, env=os.environ.copy())
476 # means we're having some non-reachable objects, this forced push
512 # means we're having some non-reachable objects, this forced push
477 # was used
513 # was used
478 if stdout:
514 if stdout:
479 push_ref['pruned_sha'] = stdout.splitlines()
515 push_ref['pruned_sha'] = stdout.splitlines()
480
516
481 extras['hook_type'] = 'pre_receive'
517 extras['hook_type'] = 'pre_receive'
482 extras['commit_ids'] = rev_data
518 extras['commit_ids'] = rev_data
483 return _call_hook('pre_push', extras, GitMessageWriter())
519 return _call_hook('pre_push', extras, GitMessageWriter())
484
520
485
521
486 def git_post_receive(unused_repo_path, revision_lines, env):
522 def git_post_receive(unused_repo_path, revision_lines, env):
487 """
523 """
488 Post push hook.
524 Post push hook.
489
525
490 :param extras: dictionary containing the keys defined in simplevcs
526 :param extras: dictionary containing the keys defined in simplevcs
491 :type extras: dict
527 :type extras: dict
492
528
493 :return: status code of the hook. 0 for success.
529 :return: status code of the hook. 0 for success.
494 :rtype: int
530 :rtype: int
495 """
531 """
496 extras = json.loads(env['RC_SCM_DATA'])
532 extras = json.loads(env['RC_SCM_DATA'])
497 if 'push' not in extras['hooks']:
533 if 'push' not in extras['hooks']:
498 return 0
534 return 0
499
535
500 rev_data = _parse_git_ref_lines(revision_lines)
536 rev_data = _parse_git_ref_lines(revision_lines)
501
537
502 git_revs = []
538 git_revs = []
503
539
504 # N.B.(skreft): it is ok to just call git, as git before calling a
540 # N.B.(skreft): it is ok to just call git, as git before calling a
505 # subcommand sets the PATH environment variable so that it point to the
541 # subcommand sets the PATH environment variable so that it point to the
506 # correct version of the git executable.
542 # correct version of the git executable.
507 empty_commit_id = '0' * 40
543 empty_commit_id = '0' * 40
508 branches = []
544 branches = []
509 tags = []
545 tags = []
510 for push_ref in rev_data:
546 for push_ref in rev_data:
511 type_ = push_ref['type']
547 type_ = push_ref['type']
512
548
513 if type_ == 'heads':
549 if type_ == 'heads':
514 if push_ref['old_rev'] == empty_commit_id:
550 if push_ref['old_rev'] == empty_commit_id:
515 # starting new branch case
551 # starting new branch case
516 if push_ref['name'] not in branches:
552 if push_ref['name'] not in branches:
517 branches.append(push_ref['name'])
553 branches.append(push_ref['name'])
518
554
519 # Fix up head revision if needed
555 # Fix up head revision if needed
520 cmd = [settings.GIT_EXECUTABLE, 'show', 'HEAD']
556 cmd = [settings.GIT_EXECUTABLE, 'show', 'HEAD']
521 try:
557 try:
522 subprocessio.run_command(cmd, env=os.environ.copy())
558 subprocessio.run_command(cmd, env=os.environ.copy())
523 except Exception:
559 except Exception:
524 cmd = [settings.GIT_EXECUTABLE, 'symbolic-ref', 'HEAD',
560 cmd = [settings.GIT_EXECUTABLE, 'symbolic-ref', 'HEAD',
525 'refs/heads/%s' % push_ref['name']]
561 'refs/heads/%s' % push_ref['name']]
526 print("Setting default branch to %s" % push_ref['name'])
562 print("Setting default branch to %s" % push_ref['name'])
527 subprocessio.run_command(cmd, env=os.environ.copy())
563 subprocessio.run_command(cmd, env=os.environ.copy())
528
564
529 cmd = [settings.GIT_EXECUTABLE, 'for-each-ref',
565 cmd = [settings.GIT_EXECUTABLE, 'for-each-ref',
530 '--format=%(refname)', 'refs/heads/*']
566 '--format=%(refname)', 'refs/heads/*']
531 stdout, stderr = subprocessio.run_command(
567 stdout, stderr = subprocessio.run_command(
532 cmd, env=os.environ.copy())
568 cmd, env=os.environ.copy())
533 heads = stdout
569 heads = stdout
534 heads = heads.replace(push_ref['ref'], '')
570 heads = heads.replace(push_ref['ref'], '')
535 heads = ' '.join(head for head
571 heads = ' '.join(head for head
536 in heads.splitlines() if head) or '.'
572 in heads.splitlines() if head) or '.'
537 cmd = [settings.GIT_EXECUTABLE, 'log', '--reverse',
573 cmd = [settings.GIT_EXECUTABLE, 'log', '--reverse',
538 '--pretty=format:%H', '--', push_ref['new_rev'],
574 '--pretty=format:%H', '--', push_ref['new_rev'],
539 '--not', heads]
575 '--not', heads]
540 stdout, stderr = subprocessio.run_command(
576 stdout, stderr = subprocessio.run_command(
541 cmd, env=os.environ.copy())
577 cmd, env=os.environ.copy())
542 git_revs.extend(stdout.splitlines())
578 git_revs.extend(stdout.splitlines())
543 elif push_ref['new_rev'] == empty_commit_id:
579 elif push_ref['new_rev'] == empty_commit_id:
544 # delete branch case
580 # delete branch case
545 git_revs.append('delete_branch=>%s' % push_ref['name'])
581 git_revs.append('delete_branch=>%s' % push_ref['name'])
546 else:
582 else:
547 if push_ref['name'] not in branches:
583 if push_ref['name'] not in branches:
548 branches.append(push_ref['name'])
584 branches.append(push_ref['name'])
549
585
550 cmd = [settings.GIT_EXECUTABLE, 'log',
586 cmd = [settings.GIT_EXECUTABLE, 'log',
551 '{old_rev}..{new_rev}'.format(**push_ref),
587 '{old_rev}..{new_rev}'.format(**push_ref),
552 '--reverse', '--pretty=format:%H']
588 '--reverse', '--pretty=format:%H']
553 stdout, stderr = subprocessio.run_command(
589 stdout, stderr = subprocessio.run_command(
554 cmd, env=os.environ.copy())
590 cmd, env=os.environ.copy())
555 git_revs.extend(stdout.splitlines())
591 git_revs.extend(stdout.splitlines())
556 elif type_ == 'tags':
592 elif type_ == 'tags':
557 if push_ref['name'] not in tags:
593 if push_ref['name'] not in tags:
558 tags.append(push_ref['name'])
594 tags.append(push_ref['name'])
559 git_revs.append('tag=>%s' % push_ref['name'])
595 git_revs.append('tag=>%s' % push_ref['name'])
560
596
561 extras['hook_type'] = 'post_receive'
597 extras['hook_type'] = 'post_receive'
562 extras['commit_ids'] = git_revs
598 extras['commit_ids'] = git_revs
563 extras['new_refs'] = {
599 extras['new_refs'] = {
564 'branches': branches,
600 'branches': branches,
565 'bookmarks': [],
601 'bookmarks': [],
566 'tags': tags,
602 'tags': tags,
567 }
603 }
568
604
569 if 'repo_size' in extras['hooks']:
605 if 'repo_size' in extras['hooks']:
570 try:
606 try:
571 _call_hook('repo_size', extras, GitMessageWriter())
607 _call_hook('repo_size', extras, GitMessageWriter())
572 except:
608 except:
573 pass
609 pass
574
610
575 return _call_hook('post_push', extras, GitMessageWriter())
611 return _call_hook('post_push', extras, GitMessageWriter())
576
612
577
613
578 def _get_extras_from_txn_id(path, txn_id):
614 def _get_extras_from_txn_id(path, txn_id):
579 extras = {}
615 extras = {}
580 try:
616 try:
581 cmd = ['svnlook', 'pget',
617 cmd = ['svnlook', 'pget',
582 '-t', txn_id,
618 '-t', txn_id,
583 '--revprop', path, 'rc-scm-extras']
619 '--revprop', path, 'rc-scm-extras']
584 stdout, stderr = subprocessio.run_command(
620 stdout, stderr = subprocessio.run_command(
585 cmd, env=os.environ.copy())
621 cmd, env=os.environ.copy())
586 extras = json.loads(base64.urlsafe_b64decode(stdout))
622 extras = json.loads(base64.urlsafe_b64decode(stdout))
587 except Exception:
623 except Exception:
588 log.exception('Failed to extract extras info from txn_id')
624 log.exception('Failed to extract extras info from txn_id')
589
625
590 return extras
626 return extras
591
627
592
628
629 def _get_extras_from_commit_id(commit_id, path):
630 extras = {}
631 try:
632 cmd = ['svnlook', 'pget',
633 '-r', commit_id,
634 '--revprop', path, 'rc-scm-extras']
635 stdout, stderr = subprocessio.run_command(
636 cmd, env=os.environ.copy())
637 extras = json.loads(base64.urlsafe_b64decode(stdout))
638 except Exception:
639 log.exception('Failed to extract extras info from commit_id')
640
641 return extras
642
643
593 def svn_pre_commit(repo_path, commit_data, env):
644 def svn_pre_commit(repo_path, commit_data, env):
594 path, txn_id = commit_data
645 path, txn_id = commit_data
595 branches = []
646 branches = []
596 tags = []
647 tags = []
597
648
598 if env.get('RC_SCM_DATA'):
649 if env.get('RC_SCM_DATA'):
599 extras = json.loads(env['RC_SCM_DATA'])
650 extras = json.loads(env['RC_SCM_DATA'])
600 else:
651 else:
601 # fallback method to read from TXN-ID stored data
652 # fallback method to read from TXN-ID stored data
602 extras = _get_extras_from_txn_id(path, txn_id)
653 extras = _get_extras_from_txn_id(path, txn_id)
603 if not extras:
654 if not extras:
604 return 0
655 return 0
605
656
606 extras['commit_ids'] = []
657 extras['commit_ids'] = []
607 extras['txn_id'] = txn_id
658 extras['txn_id'] = txn_id
608 extras['new_refs'] = {
659 extras['new_refs'] = {
660 'total_commits': 1,
609 'branches': branches,
661 'branches': branches,
610 'bookmarks': [],
662 'bookmarks': [],
611 'tags': tags,
663 'tags': tags,
612 }
664 }
613
665
614 return _call_hook('pre_push', extras, SvnMessageWriter())
666 return _call_hook('pre_push', extras, SvnMessageWriter())
615
667
616
668
617 def _get_extras_from_commit_id(commit_id, path):
618 extras = {}
619 try:
620 cmd = ['svnlook', 'pget',
621 '-r', commit_id,
622 '--revprop', path, 'rc-scm-extras']
623 stdout, stderr = subprocessio.run_command(
624 cmd, env=os.environ.copy())
625 extras = json.loads(base64.urlsafe_b64decode(stdout))
626 except Exception:
627 log.exception('Failed to extract extras info from commit_id')
628
629 return extras
630
631
632 def svn_post_commit(repo_path, commit_data, env):
669 def svn_post_commit(repo_path, commit_data, env):
633 """
670 """
634 commit_data is path, rev, txn_id
671 commit_data is path, rev, txn_id
635 """
672 """
636 path, commit_id, txn_id = commit_data
673 path, commit_id, txn_id = commit_data
637 branches = []
674 branches = []
638 tags = []
675 tags = []
639
676
640 if env.get('RC_SCM_DATA'):
677 if env.get('RC_SCM_DATA'):
641 extras = json.loads(env['RC_SCM_DATA'])
678 extras = json.loads(env['RC_SCM_DATA'])
642 else:
679 else:
643 # fallback method to read from TXN-ID stored data
680 # fallback method to read from TXN-ID stored data
644 extras = _get_extras_from_commit_id(commit_id, path)
681 extras = _get_extras_from_commit_id(commit_id, path)
645 if not extras:
682 if not extras:
646 return 0
683 return 0
647
684
648 extras['commit_ids'] = [commit_id]
685 extras['commit_ids'] = [commit_id]
649 extras['txn_id'] = txn_id
686 extras['txn_id'] = txn_id
650 extras['new_refs'] = {
687 extras['new_refs'] = {
651 'branches': branches,
688 'branches': branches,
652 'bookmarks': [],
689 'bookmarks': [],
653 'tags': tags,
690 'tags': tags,
691 'total_commits': 1,
654 }
692 }
655
693
656 if 'repo_size' in extras['hooks']:
694 if 'repo_size' in extras['hooks']:
657 try:
695 try:
658 _call_hook('repo_size', extras, SvnMessageWriter())
696 _call_hook('repo_size', extras, SvnMessageWriter())
659 except Exception:
697 except Exception:
660 pass
698 pass
661
699
662 return _call_hook('post_push', extras, SvnMessageWriter())
700 return _call_hook('post_push', extras, SvnMessageWriter())
General Comments 0
You need to be logged in to leave comments. Login now