##// END OF EJS Templates
svn: make hooks safe and fully backward compatible....
marcink -
r436:74eb96f2 stable
parent child Browse files
Show More
@@ -1,541 +1,570 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 == 'RepositoryError':
124 elif exception_class == 'RepositoryError':
125 raise exceptions.VcsException(*result['exception_args'])
125 raise exceptions.VcsException(*result['exception_args'])
126 elif exception_class:
126 elif exception_class:
127 raise Exception('Got remote exception "%s" with args "%s"' %
127 raise Exception('Got remote exception "%s" with args "%s"' %
128 (exception_class, result['exception_args']))
128 (exception_class, result['exception_args']))
129
129
130
130
131 def _get_hooks_client(extras):
131 def _get_hooks_client(extras):
132 if 'hooks_uri' in extras:
132 if 'hooks_uri' in extras:
133 protocol = extras.get('hooks_protocol')
133 protocol = extras.get('hooks_protocol')
134 return HooksHttpClient(extras['hooks_uri'])
134 return HooksHttpClient(extras['hooks_uri'])
135 else:
135 else:
136 return HooksDummyClient(extras['hooks_module'])
136 return HooksDummyClient(extras['hooks_module'])
137
137
138
138
139 def _call_hook(hook_name, extras, writer):
139 def _call_hook(hook_name, extras, writer):
140 hooks_client = _get_hooks_client(extras)
140 hooks_client = _get_hooks_client(extras)
141 log.debug('Hooks, using client:%s', hooks_client)
141 log.debug('Hooks, using client:%s', hooks_client)
142 result = hooks_client(hook_name, extras)
142 result = hooks_client(hook_name, extras)
143 log.debug('Hooks got result: %s', result)
143 log.debug('Hooks got result: %s', result)
144 writer.write(result['output'])
144 writer.write(result['output'])
145 _handle_exception(result)
145 _handle_exception(result)
146
146
147 return result['status']
147 return result['status']
148
148
149
149
150 def _extras_from_ui(ui):
150 def _extras_from_ui(ui):
151 hook_data = ui.config('rhodecode', 'RC_SCM_DATA')
151 hook_data = ui.config('rhodecode', 'RC_SCM_DATA')
152 if not hook_data:
152 if not hook_data:
153 # maybe it's inside environ ?
153 # maybe it's inside environ ?
154 env_hook_data = os.environ.get('RC_SCM_DATA')
154 env_hook_data = os.environ.get('RC_SCM_DATA')
155 if env_hook_data:
155 if env_hook_data:
156 hook_data = env_hook_data
156 hook_data = env_hook_data
157
157
158 extras = {}
158 extras = {}
159 if hook_data:
159 if hook_data:
160 extras = json.loads(hook_data)
160 extras = json.loads(hook_data)
161 return extras
161 return extras
162
162
163
163
164 def _rev_range_hash(repo, node):
164 def _rev_range_hash(repo, node):
165
165
166 commits = []
166 commits = []
167 for rev in xrange(repo[node], len(repo)):
167 for rev in xrange(repo[node], len(repo)):
168 ctx = repo[rev]
168 ctx = repo[rev]
169 commit_id = mercurial.node.hex(ctx.node())
169 commit_id = mercurial.node.hex(ctx.node())
170 branch = ctx.branch()
170 branch = ctx.branch()
171 commits.append((commit_id, branch))
171 commits.append((commit_id, branch))
172
172
173 return commits
173 return commits
174
174
175
175
176 def repo_size(ui, repo, **kwargs):
176 def repo_size(ui, repo, **kwargs):
177 extras = _extras_from_ui(ui)
177 extras = _extras_from_ui(ui)
178 return _call_hook('repo_size', extras, HgMessageWriter(ui))
178 return _call_hook('repo_size', extras, HgMessageWriter(ui))
179
179
180
180
181 def pre_pull(ui, repo, **kwargs):
181 def pre_pull(ui, repo, **kwargs):
182 extras = _extras_from_ui(ui)
182 extras = _extras_from_ui(ui)
183 return _call_hook('pre_pull', extras, HgMessageWriter(ui))
183 return _call_hook('pre_pull', extras, HgMessageWriter(ui))
184
184
185
185
186 def pre_pull_ssh(ui, repo, **kwargs):
186 def pre_pull_ssh(ui, repo, **kwargs):
187 extras = _extras_from_ui(ui)
187 extras = _extras_from_ui(ui)
188 if extras and extras.get('SSH'):
188 if extras and extras.get('SSH'):
189 return pre_pull(ui, repo, **kwargs)
189 return pre_pull(ui, repo, **kwargs)
190 return 0
190 return 0
191
191
192
192
193 def post_pull(ui, repo, **kwargs):
193 def post_pull(ui, repo, **kwargs):
194 extras = _extras_from_ui(ui)
194 extras = _extras_from_ui(ui)
195 return _call_hook('post_pull', extras, HgMessageWriter(ui))
195 return _call_hook('post_pull', extras, HgMessageWriter(ui))
196
196
197
197
198 def post_pull_ssh(ui, repo, **kwargs):
198 def post_pull_ssh(ui, repo, **kwargs):
199 extras = _extras_from_ui(ui)
199 extras = _extras_from_ui(ui)
200 if extras and extras.get('SSH'):
200 if extras and extras.get('SSH'):
201 return post_pull(ui, repo, **kwargs)
201 return post_pull(ui, repo, **kwargs)
202 return 0
202 return 0
203
203
204
204
205 def pre_push(ui, repo, node=None, **kwargs):
205 def pre_push(ui, repo, node=None, **kwargs):
206 extras = _extras_from_ui(ui)
206 extras = _extras_from_ui(ui)
207
207
208 rev_data = []
208 rev_data = []
209 if node and kwargs.get('hooktype') == 'pretxnchangegroup':
209 if node and kwargs.get('hooktype') == 'pretxnchangegroup':
210 branches = collections.defaultdict(list)
210 branches = collections.defaultdict(list)
211 for commit_id, branch in _rev_range_hash(repo, node):
211 for commit_id, branch in _rev_range_hash(repo, node):
212 branches[branch].append(commit_id)
212 branches[branch].append(commit_id)
213
213
214 for branch, commits in branches.iteritems():
214 for branch, commits in branches.iteritems():
215 old_rev = kwargs.get('node_last') or commits[0]
215 old_rev = kwargs.get('node_last') or commits[0]
216 rev_data.append({
216 rev_data.append({
217 'old_rev': old_rev,
217 'old_rev': old_rev,
218 'new_rev': commits[-1],
218 'new_rev': commits[-1],
219 'ref': '',
219 'ref': '',
220 'type': 'branch',
220 'type': 'branch',
221 'name': branch,
221 'name': branch,
222 })
222 })
223
223
224 extras['commit_ids'] = rev_data
224 extras['commit_ids'] = rev_data
225 return _call_hook('pre_push', extras, HgMessageWriter(ui))
225 return _call_hook('pre_push', extras, HgMessageWriter(ui))
226
226
227
227
228 def pre_push_ssh(ui, repo, node=None, **kwargs):
228 def pre_push_ssh(ui, repo, node=None, **kwargs):
229 if _extras_from_ui(ui).get('SSH'):
229 if _extras_from_ui(ui).get('SSH'):
230 return pre_push(ui, repo, node, **kwargs)
230 return pre_push(ui, repo, node, **kwargs)
231
231
232 return 0
232 return 0
233
233
234
234
235 def pre_push_ssh_auth(ui, repo, node=None, **kwargs):
235 def pre_push_ssh_auth(ui, repo, node=None, **kwargs):
236 extras = _extras_from_ui(ui)
236 extras = _extras_from_ui(ui)
237 if extras.get('SSH'):
237 if extras.get('SSH'):
238 permission = extras['SSH_PERMISSIONS']
238 permission = extras['SSH_PERMISSIONS']
239
239
240 if 'repository.write' == permission or 'repository.admin' == permission:
240 if 'repository.write' == permission or 'repository.admin' == permission:
241 return 0
241 return 0
242
242
243 # non-zero ret code
243 # non-zero ret code
244 return 1
244 return 1
245
245
246 return 0
246 return 0
247
247
248
248
249 def post_push(ui, repo, node, **kwargs):
249 def post_push(ui, repo, node, **kwargs):
250 extras = _extras_from_ui(ui)
250 extras = _extras_from_ui(ui)
251
251
252 commit_ids = []
252 commit_ids = []
253 branches = []
253 branches = []
254 bookmarks = []
254 bookmarks = []
255 tags = []
255 tags = []
256
256
257 for commit_id, branch in _rev_range_hash(repo, node):
257 for commit_id, branch in _rev_range_hash(repo, node):
258 commit_ids.append(commit_id)
258 commit_ids.append(commit_id)
259 if branch not in branches:
259 if branch not in branches:
260 branches.append(branch)
260 branches.append(branch)
261
261
262 if hasattr(ui, '_rc_pushkey_branches'):
262 if hasattr(ui, '_rc_pushkey_branches'):
263 bookmarks = ui._rc_pushkey_branches
263 bookmarks = ui._rc_pushkey_branches
264
264
265 extras['commit_ids'] = commit_ids
265 extras['commit_ids'] = commit_ids
266 extras['new_refs'] = {
266 extras['new_refs'] = {
267 'branches': branches,
267 'branches': branches,
268 'bookmarks': bookmarks,
268 'bookmarks': bookmarks,
269 'tags': tags
269 'tags': tags
270 }
270 }
271
271
272 return _call_hook('post_push', extras, HgMessageWriter(ui))
272 return _call_hook('post_push', extras, HgMessageWriter(ui))
273
273
274
274
275 def post_push_ssh(ui, repo, node, **kwargs):
275 def post_push_ssh(ui, repo, node, **kwargs):
276 if _extras_from_ui(ui).get('SSH'):
276 if _extras_from_ui(ui).get('SSH'):
277 return post_push(ui, repo, node, **kwargs)
277 return post_push(ui, repo, node, **kwargs)
278 return 0
278 return 0
279
279
280
280
281 def key_push(ui, repo, **kwargs):
281 def key_push(ui, repo, **kwargs):
282 if kwargs['new'] != '0' and kwargs['namespace'] == 'bookmarks':
282 if kwargs['new'] != '0' and kwargs['namespace'] == 'bookmarks':
283 # store new bookmarks in our UI object propagated later to post_push
283 # store new bookmarks in our UI object propagated later to post_push
284 ui._rc_pushkey_branches = repo[kwargs['key']].bookmarks()
284 ui._rc_pushkey_branches = repo[kwargs['key']].bookmarks()
285 return
285 return
286
286
287
287
288 # backward compat
288 # backward compat
289 log_pull_action = post_pull
289 log_pull_action = post_pull
290
290
291 # backward compat
291 # backward compat
292 log_push_action = post_push
292 log_push_action = post_push
293
293
294
294
295 def handle_git_pre_receive(unused_repo_path, unused_revs, unused_env):
295 def handle_git_pre_receive(unused_repo_path, unused_revs, unused_env):
296 """
296 """
297 Old hook name: keep here for backward compatibility.
297 Old hook name: keep here for backward compatibility.
298
298
299 This is only required when the installed git hooks are not upgraded.
299 This is only required when the installed git hooks are not upgraded.
300 """
300 """
301 pass
301 pass
302
302
303
303
304 def handle_git_post_receive(unused_repo_path, unused_revs, unused_env):
304 def handle_git_post_receive(unused_repo_path, unused_revs, unused_env):
305 """
305 """
306 Old hook name: keep here for backward compatibility.
306 Old hook name: keep here for backward compatibility.
307
307
308 This is only required when the installed git hooks are not upgraded.
308 This is only required when the installed git hooks are not upgraded.
309 """
309 """
310 pass
310 pass
311
311
312
312
313 HookResponse = collections.namedtuple('HookResponse', ('status', 'output'))
313 HookResponse = collections.namedtuple('HookResponse', ('status', 'output'))
314
314
315
315
316 def git_pre_pull(extras):
316 def git_pre_pull(extras):
317 """
317 """
318 Pre pull hook.
318 Pre pull hook.
319
319
320 :param extras: dictionary containing the keys defined in simplevcs
320 :param extras: dictionary containing the keys defined in simplevcs
321 :type extras: dict
321 :type extras: dict
322
322
323 :return: status code of the hook. 0 for success.
323 :return: status code of the hook. 0 for success.
324 :rtype: int
324 :rtype: int
325 """
325 """
326 if 'pull' not in extras['hooks']:
326 if 'pull' not in extras['hooks']:
327 return HookResponse(0, '')
327 return HookResponse(0, '')
328
328
329 stdout = io.BytesIO()
329 stdout = io.BytesIO()
330 try:
330 try:
331 status = _call_hook('pre_pull', extras, GitMessageWriter(stdout))
331 status = _call_hook('pre_pull', extras, GitMessageWriter(stdout))
332 except Exception as error:
332 except Exception as error:
333 status = 128
333 status = 128
334 stdout.write('ERROR: %s\n' % str(error))
334 stdout.write('ERROR: %s\n' % str(error))
335
335
336 return HookResponse(status, stdout.getvalue())
336 return HookResponse(status, stdout.getvalue())
337
337
338
338
339 def git_post_pull(extras):
339 def git_post_pull(extras):
340 """
340 """
341 Post pull hook.
341 Post pull hook.
342
342
343 :param extras: dictionary containing the keys defined in simplevcs
343 :param extras: dictionary containing the keys defined in simplevcs
344 :type extras: dict
344 :type extras: dict
345
345
346 :return: status code of the hook. 0 for success.
346 :return: status code of the hook. 0 for success.
347 :rtype: int
347 :rtype: int
348 """
348 """
349 if 'pull' not in extras['hooks']:
349 if 'pull' not in extras['hooks']:
350 return HookResponse(0, '')
350 return HookResponse(0, '')
351
351
352 stdout = io.BytesIO()
352 stdout = io.BytesIO()
353 try:
353 try:
354 status = _call_hook('post_pull', extras, GitMessageWriter(stdout))
354 status = _call_hook('post_pull', extras, GitMessageWriter(stdout))
355 except Exception as error:
355 except Exception as error:
356 status = 128
356 status = 128
357 stdout.write('ERROR: %s\n' % error)
357 stdout.write('ERROR: %s\n' % error)
358
358
359 return HookResponse(status, stdout.getvalue())
359 return HookResponse(status, stdout.getvalue())
360
360
361
361
362 def _parse_git_ref_lines(revision_lines):
362 def _parse_git_ref_lines(revision_lines):
363 rev_data = []
363 rev_data = []
364 for revision_line in revision_lines or []:
364 for revision_line in revision_lines or []:
365 old_rev, new_rev, ref = revision_line.strip().split(' ')
365 old_rev, new_rev, ref = revision_line.strip().split(' ')
366 ref_data = ref.split('/', 2)
366 ref_data = ref.split('/', 2)
367 if ref_data[1] in ('tags', 'heads'):
367 if ref_data[1] in ('tags', 'heads'):
368 rev_data.append({
368 rev_data.append({
369 'old_rev': old_rev,
369 'old_rev': old_rev,
370 'new_rev': new_rev,
370 'new_rev': new_rev,
371 'ref': ref,
371 'ref': ref,
372 'type': ref_data[1],
372 'type': ref_data[1],
373 'name': ref_data[2],
373 'name': ref_data[2],
374 })
374 })
375 return rev_data
375 return rev_data
376
376
377
377
378 def git_pre_receive(unused_repo_path, revision_lines, env):
378 def git_pre_receive(unused_repo_path, revision_lines, env):
379 """
379 """
380 Pre push hook.
380 Pre push hook.
381
381
382 :param extras: dictionary containing the keys defined in simplevcs
382 :param extras: dictionary containing the keys defined in simplevcs
383 :type extras: dict
383 :type extras: dict
384
384
385 :return: status code of the hook. 0 for success.
385 :return: status code of the hook. 0 for success.
386 :rtype: int
386 :rtype: int
387 """
387 """
388 extras = json.loads(env['RC_SCM_DATA'])
388 extras = json.loads(env['RC_SCM_DATA'])
389 rev_data = _parse_git_ref_lines(revision_lines)
389 rev_data = _parse_git_ref_lines(revision_lines)
390 if 'push' not in extras['hooks']:
390 if 'push' not in extras['hooks']:
391 return 0
391 return 0
392 extras['commit_ids'] = rev_data
392 extras['commit_ids'] = rev_data
393 return _call_hook('pre_push', extras, GitMessageWriter())
393 return _call_hook('pre_push', extras, GitMessageWriter())
394
394
395
395
396 def git_post_receive(unused_repo_path, revision_lines, env):
396 def git_post_receive(unused_repo_path, revision_lines, env):
397 """
397 """
398 Post push hook.
398 Post push hook.
399
399
400 :param extras: dictionary containing the keys defined in simplevcs
400 :param extras: dictionary containing the keys defined in simplevcs
401 :type extras: dict
401 :type extras: dict
402
402
403 :return: status code of the hook. 0 for success.
403 :return: status code of the hook. 0 for success.
404 :rtype: int
404 :rtype: int
405 """
405 """
406 extras = json.loads(env['RC_SCM_DATA'])
406 extras = json.loads(env['RC_SCM_DATA'])
407 if 'push' not in extras['hooks']:
407 if 'push' not in extras['hooks']:
408 return 0
408 return 0
409
409
410 rev_data = _parse_git_ref_lines(revision_lines)
410 rev_data = _parse_git_ref_lines(revision_lines)
411
411
412 git_revs = []
412 git_revs = []
413
413
414 # N.B.(skreft): it is ok to just call git, as git before calling a
414 # N.B.(skreft): it is ok to just call git, as git before calling a
415 # subcommand sets the PATH environment variable so that it point to the
415 # subcommand sets the PATH environment variable so that it point to the
416 # correct version of the git executable.
416 # correct version of the git executable.
417 empty_commit_id = '0' * 40
417 empty_commit_id = '0' * 40
418 branches = []
418 branches = []
419 tags = []
419 tags = []
420 for push_ref in rev_data:
420 for push_ref in rev_data:
421 type_ = push_ref['type']
421 type_ = push_ref['type']
422
422
423 if type_ == 'heads':
423 if type_ == 'heads':
424 if push_ref['old_rev'] == empty_commit_id:
424 if push_ref['old_rev'] == empty_commit_id:
425 # starting new branch case
425 # starting new branch case
426 if push_ref['name'] not in branches:
426 if push_ref['name'] not in branches:
427 branches.append(push_ref['name'])
427 branches.append(push_ref['name'])
428
428
429 # Fix up head revision if needed
429 # Fix up head revision if needed
430 cmd = [settings.GIT_EXECUTABLE, 'show', 'HEAD']
430 cmd = [settings.GIT_EXECUTABLE, 'show', 'HEAD']
431 try:
431 try:
432 subprocessio.run_command(cmd, env=os.environ.copy())
432 subprocessio.run_command(cmd, env=os.environ.copy())
433 except Exception:
433 except Exception:
434 cmd = [settings.GIT_EXECUTABLE, 'symbolic-ref', 'HEAD',
434 cmd = [settings.GIT_EXECUTABLE, 'symbolic-ref', 'HEAD',
435 'refs/heads/%s' % push_ref['name']]
435 'refs/heads/%s' % push_ref['name']]
436 print("Setting default branch to %s" % push_ref['name'])
436 print("Setting default branch to %s" % push_ref['name'])
437 subprocessio.run_command(cmd, env=os.environ.copy())
437 subprocessio.run_command(cmd, env=os.environ.copy())
438
438
439 cmd = [settings.GIT_EXECUTABLE, 'for-each-ref',
439 cmd = [settings.GIT_EXECUTABLE, 'for-each-ref',
440 '--format=%(refname)', 'refs/heads/*']
440 '--format=%(refname)', 'refs/heads/*']
441 stdout, stderr = subprocessio.run_command(
441 stdout, stderr = subprocessio.run_command(
442 cmd, env=os.environ.copy())
442 cmd, env=os.environ.copy())
443 heads = stdout
443 heads = stdout
444 heads = heads.replace(push_ref['ref'], '')
444 heads = heads.replace(push_ref['ref'], '')
445 heads = ' '.join(head for head in heads.splitlines() if head)
445 heads = ' '.join(head for head in heads.splitlines() if head)
446 cmd = [settings.GIT_EXECUTABLE, 'log', '--reverse',
446 cmd = [settings.GIT_EXECUTABLE, 'log', '--reverse',
447 '--pretty=format:%H', '--', push_ref['new_rev'],
447 '--pretty=format:%H', '--', push_ref['new_rev'],
448 '--not', heads]
448 '--not', heads]
449 stdout, stderr = subprocessio.run_command(
449 stdout, stderr = subprocessio.run_command(
450 cmd, env=os.environ.copy())
450 cmd, env=os.environ.copy())
451 git_revs.extend(stdout.splitlines())
451 git_revs.extend(stdout.splitlines())
452 elif push_ref['new_rev'] == empty_commit_id:
452 elif push_ref['new_rev'] == empty_commit_id:
453 # delete branch case
453 # delete branch case
454 git_revs.append('delete_branch=>%s' % push_ref['name'])
454 git_revs.append('delete_branch=>%s' % push_ref['name'])
455 else:
455 else:
456 if push_ref['name'] not in branches:
456 if push_ref['name'] not in branches:
457 branches.append(push_ref['name'])
457 branches.append(push_ref['name'])
458
458
459 cmd = [settings.GIT_EXECUTABLE, 'log',
459 cmd = [settings.GIT_EXECUTABLE, 'log',
460 '{old_rev}..{new_rev}'.format(**push_ref),
460 '{old_rev}..{new_rev}'.format(**push_ref),
461 '--reverse', '--pretty=format:%H']
461 '--reverse', '--pretty=format:%H']
462 stdout, stderr = subprocessio.run_command(
462 stdout, stderr = subprocessio.run_command(
463 cmd, env=os.environ.copy())
463 cmd, env=os.environ.copy())
464 git_revs.extend(stdout.splitlines())
464 git_revs.extend(stdout.splitlines())
465 elif type_ == 'tags':
465 elif type_ == 'tags':
466 if push_ref['name'] not in tags:
466 if push_ref['name'] not in tags:
467 tags.append(push_ref['name'])
467 tags.append(push_ref['name'])
468 git_revs.append('tag=>%s' % push_ref['name'])
468 git_revs.append('tag=>%s' % push_ref['name'])
469
469
470 extras['commit_ids'] = git_revs
470 extras['commit_ids'] = git_revs
471 extras['new_refs'] = {
471 extras['new_refs'] = {
472 'branches': branches,
472 'branches': branches,
473 'bookmarks': [],
473 'bookmarks': [],
474 'tags': tags,
474 'tags': tags,
475 }
475 }
476
476
477 if 'repo_size' in extras['hooks']:
477 if 'repo_size' in extras['hooks']:
478 try:
478 try:
479 _call_hook('repo_size', extras, GitMessageWriter())
479 _call_hook('repo_size', extras, GitMessageWriter())
480 except:
480 except:
481 pass
481 pass
482
482
483 return _call_hook('post_push', extras, GitMessageWriter())
483 return _call_hook('post_push', extras, GitMessageWriter())
484
484
485
485
486 def _get_extras_from_txn_id(path, txn_id):
487 extras = {}
488 try:
489 cmd = ['svnlook', 'pget',
490 '-t', txn_id,
491 '--revprop', path, 'rc-scm-extras']
492 stdout, stderr = subprocessio.run_command(
493 cmd, env=os.environ.copy())
494 extras = json.loads(base64.urlsafe_b64decode(stdout))
495 except Exception:
496 log.exception('Failed to extract extras info from txn_id')
497
498 return extras
499
500
486 def svn_pre_commit(repo_path, commit_data, env):
501 def svn_pre_commit(repo_path, commit_data, env):
487 path, txn_id = commit_data
502 path, txn_id = commit_data
488 branches = []
503 branches = []
489 tags = []
504 tags = []
490
505
491 cmd = ['svnlook', 'pget',
506 if env.get('RC_SCM_DATA'):
492 '-t', txn_id,
507 extras = json.loads(env['RC_SCM_DATA'])
493 '--revprop', path, 'rc-scm-extras']
508 else:
494 stdout, stderr = subprocessio.run_command(
509 # fallback method to read from TXN-ID stored data
495 cmd, env=os.environ.copy())
510 extras = _get_extras_from_txn_id(path, txn_id)
496 extras = json.loads(base64.urlsafe_b64decode(stdout))
511 if not extras:
512 return 0
497
513
498 extras['commit_ids'] = []
514 extras['commit_ids'] = []
499 extras['txn_id'] = txn_id
515 extras['txn_id'] = txn_id
500 extras['new_refs'] = {
516 extras['new_refs'] = {
501 'branches': branches,
517 'branches': branches,
502 'bookmarks': [],
518 'bookmarks': [],
503 'tags': tags,
519 'tags': tags,
504 }
520 }
505 sys.stderr.write(str(extras))
521
506 return _call_hook('pre_push', extras, SvnMessageWriter())
522 return _call_hook('pre_push', extras, SvnMessageWriter())
507
523
508
524
525 def _get_extras_from_commit_id(commit_id, path):
526 extras = {}
527 try:
528 cmd = ['svnlook', 'pget',
529 '-r', commit_id,
530 '--revprop', path, 'rc-scm-extras']
531 stdout, stderr = subprocessio.run_command(
532 cmd, env=os.environ.copy())
533 extras = json.loads(base64.urlsafe_b64decode(stdout))
534 except Exception:
535 log.exception('Failed to extract extras info from commit_id')
536
537 return extras
538
539
509 def svn_post_commit(repo_path, commit_data, env):
540 def svn_post_commit(repo_path, commit_data, env):
510 """
541 """
511 commit_data is path, rev, txn_id
542 commit_data is path, rev, txn_id
512 """
543 """
513 path, commit_id, txn_id = commit_data
544 path, commit_id, txn_id = commit_data
514 branches = []
545 branches = []
515 tags = []
546 tags = []
516
547
517 cmd = ['svnlook', 'pget',
548 if env.get('RC_SCM_DATA'):
518 '-r', commit_id,
549 extras = json.loads(env['RC_SCM_DATA'])
519 '--revprop', path, 'rc-scm-extras']
550 else:
520 stdout, stderr = subprocessio.run_command(
551 # fallback method to read from TXN-ID stored data
521 cmd, env=os.environ.copy())
552 extras = _get_extras_from_commit_id(commit_id, path)
522
553 if not extras:
523 extras = json.loads(base64.urlsafe_b64decode(stdout))
554 return 0
524
555
525 extras['commit_ids'] = [commit_id]
556 extras['commit_ids'] = [commit_id]
526 extras['txn_id'] = txn_id
557 extras['txn_id'] = txn_id
527 extras['new_refs'] = {
558 extras['new_refs'] = {
528 'branches': branches,
559 'branches': branches,
529 'bookmarks': [],
560 'bookmarks': [],
530 'tags': tags,
561 'tags': tags,
531 }
562 }
532
563
533 if 'repo_size' in extras['hooks']:
564 if 'repo_size' in extras['hooks']:
534 try:
565 try:
535 _call_hook('repo_size', extras, SvnMessageWriter())
566 _call_hook('repo_size', extras, SvnMessageWriter())
536 except:
567 except Exception:
537 pass
568 pass
538
569
539 return _call_hook('post_push', extras, SvnMessageWriter())
570 return _call_hook('post_push', extras, SvnMessageWriter())
540
541
General Comments 0
You need to be logged in to leave comments. Login now