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