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