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