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