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