##// END OF EJS Templates
git: fixed code after version upgrade
marcink -
r434:74d2792f default
parent child Browse files
Show More
@@ -1,542 +1,543 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # RhodeCode VCSServer provides access to different vcs backends via network.
4 4 # Copyright (C) 2014-2018 RhodeCode GmbH
5 5 #
6 6 # This program is free software; you can redistribute it and/or modify
7 7 # it under the terms of the GNU General Public License as published by
8 8 # the Free Software Foundation; either version 3 of the License, or
9 9 # (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software Foundation,
18 18 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 19
20 20 import io
21 21 import os
22 22 import sys
23 23 import logging
24 24 import collections
25 25 import importlib
26 26 import base64
27 27
28 28 from httplib import HTTPConnection
29 29
30 30
31 31 import mercurial.scmutil
32 32 import mercurial.node
33 33 import simplejson as json
34 34
35 35 from vcsserver import exceptions, subprocessio, settings
36 36
37 37 log = logging.getLogger(__name__)
38 38
39 39
40 40 class HooksHttpClient(object):
41 41 connection = None
42 42
43 43 def __init__(self, hooks_uri):
44 44 self.hooks_uri = hooks_uri
45 45
46 46 def __call__(self, method, extras):
47 47 connection = HTTPConnection(self.hooks_uri)
48 48 body = self._serialize(method, extras)
49 49 try:
50 50 connection.request('POST', '/', body)
51 51 except Exception:
52 52 log.error('Connection failed on %s', connection)
53 53 raise
54 54 response = connection.getresponse()
55 55 return json.loads(response.read())
56 56
57 57 def _serialize(self, hook_name, extras):
58 58 data = {
59 59 'method': hook_name,
60 60 'extras': extras
61 61 }
62 62 return json.dumps(data)
63 63
64 64
65 65 class HooksDummyClient(object):
66 66 def __init__(self, hooks_module):
67 67 self._hooks_module = importlib.import_module(hooks_module)
68 68
69 69 def __call__(self, hook_name, extras):
70 70 with self._hooks_module.Hooks() as hooks:
71 71 return getattr(hooks, hook_name)(extras)
72 72
73 73
74 74 class RemoteMessageWriter(object):
75 75 """Writer base class."""
76 76 def write(self, message):
77 77 raise NotImplementedError()
78 78
79 79
80 80 class HgMessageWriter(RemoteMessageWriter):
81 81 """Writer that knows how to send messages to mercurial clients."""
82 82
83 83 def __init__(self, ui):
84 84 self.ui = ui
85 85
86 86 def write(self, message):
87 87 # TODO: Check why the quiet flag is set by default.
88 88 old = self.ui.quiet
89 89 self.ui.quiet = False
90 90 self.ui.status(message.encode('utf-8'))
91 91 self.ui.quiet = old
92 92
93 93
94 94 class GitMessageWriter(RemoteMessageWriter):
95 95 """Writer that knows how to send messages to git clients."""
96 96
97 97 def __init__(self, stdout=None):
98 98 self.stdout = stdout or sys.stdout
99 99
100 100 def write(self, message):
101 101 self.stdout.write(message.encode('utf-8'))
102 102
103 103
104 104 class SvnMessageWriter(RemoteMessageWriter):
105 105 """Writer that knows how to send messages to svn clients."""
106 106
107 107 def __init__(self, stderr=None):
108 108 # SVN needs data sent to stderr for back-to-client messaging
109 109 self.stderr = stderr or sys.stderr
110 110
111 111 def write(self, message):
112 112 self.stderr.write(message.encode('utf-8'))
113 113
114 114
115 115 def _handle_exception(result):
116 116 exception_class = result.get('exception')
117 117 exception_traceback = result.get('exception_traceback')
118 118
119 119 if exception_traceback:
120 120 log.error('Got traceback from remote call:%s', exception_traceback)
121 121
122 122 if exception_class == 'HTTPLockedRC':
123 123 raise exceptions.RepositoryLockedException(*result['exception_args'])
124 124 elif exception_class == 'RepositoryError':
125 125 raise exceptions.VcsException(*result['exception_args'])
126 126 elif exception_class:
127 127 raise Exception('Got remote exception "%s" with args "%s"' %
128 128 (exception_class, result['exception_args']))
129 129
130 130
131 131 def _get_hooks_client(extras):
132 132 if 'hooks_uri' in extras:
133 133 protocol = extras.get('hooks_protocol')
134 134 return HooksHttpClient(extras['hooks_uri'])
135 135 else:
136 136 return HooksDummyClient(extras['hooks_module'])
137 137
138 138
139 139 def _call_hook(hook_name, extras, writer):
140 140 hooks_client = _get_hooks_client(extras)
141 141 log.debug('Hooks, using client:%s', hooks_client)
142 142 result = hooks_client(hook_name, extras)
143 143 log.debug('Hooks got result: %s', result)
144 144 writer.write(result['output'])
145 145 _handle_exception(result)
146 146
147 147 return result['status']
148 148
149 149
150 150 def _extras_from_ui(ui):
151 151 hook_data = ui.config('rhodecode', 'RC_SCM_DATA')
152 152 if not hook_data:
153 153 # maybe it's inside environ ?
154 154 env_hook_data = os.environ.get('RC_SCM_DATA')
155 155 if env_hook_data:
156 156 hook_data = env_hook_data
157 157
158 158 extras = {}
159 159 if hook_data:
160 160 extras = json.loads(hook_data)
161 161 return extras
162 162
163 163
164 164 def _rev_range_hash(repo, node):
165 165
166 166 commits = []
167 167 start = repo[node].rev()
168 168 for rev in xrange(start, len(repo)):
169 169 ctx = repo[rev]
170 170 commit_id = mercurial.node.hex(ctx.node())
171 171 branch = ctx.branch()
172 172 commits.append((commit_id, branch))
173 173
174 174 return commits
175 175
176 176
177 177 def repo_size(ui, repo, **kwargs):
178 178 extras = _extras_from_ui(ui)
179 179 return _call_hook('repo_size', extras, HgMessageWriter(ui))
180 180
181 181
182 182 def pre_pull(ui, repo, **kwargs):
183 183 extras = _extras_from_ui(ui)
184 184 return _call_hook('pre_pull', extras, HgMessageWriter(ui))
185 185
186 186
187 187 def pre_pull_ssh(ui, repo, **kwargs):
188 188 extras = _extras_from_ui(ui)
189 189 if extras and extras.get('SSH'):
190 190 return pre_pull(ui, repo, **kwargs)
191 191 return 0
192 192
193 193
194 194 def post_pull(ui, repo, **kwargs):
195 195 extras = _extras_from_ui(ui)
196 196 return _call_hook('post_pull', extras, HgMessageWriter(ui))
197 197
198 198
199 199 def post_pull_ssh(ui, repo, **kwargs):
200 200 extras = _extras_from_ui(ui)
201 201 if extras and extras.get('SSH'):
202 202 return post_pull(ui, repo, **kwargs)
203 203 return 0
204 204
205 205
206 206 def pre_push(ui, repo, node=None, **kwargs):
207 207 extras = _extras_from_ui(ui)
208 208
209 209 rev_data = []
210 210 if node and kwargs.get('hooktype') == 'pretxnchangegroup':
211 211 branches = collections.defaultdict(list)
212 212 for commit_id, branch in _rev_range_hash(repo, node):
213 213 branches[branch].append(commit_id)
214 214
215 215 for branch, commits in branches.iteritems():
216 216 old_rev = kwargs.get('node_last') or commits[0]
217 217 rev_data.append({
218 218 'old_rev': old_rev,
219 219 'new_rev': commits[-1],
220 220 'ref': '',
221 221 'type': 'branch',
222 222 'name': branch,
223 223 })
224 224
225 225 extras['commit_ids'] = rev_data
226 226 return _call_hook('pre_push', extras, HgMessageWriter(ui))
227 227
228 228
229 229 def pre_push_ssh(ui, repo, node=None, **kwargs):
230 230 if _extras_from_ui(ui).get('SSH'):
231 231 return pre_push(ui, repo, node, **kwargs)
232 232
233 233 return 0
234 234
235 235
236 236 def pre_push_ssh_auth(ui, repo, node=None, **kwargs):
237 237 extras = _extras_from_ui(ui)
238 238 if extras.get('SSH'):
239 239 permission = extras['SSH_PERMISSIONS']
240 240
241 241 if 'repository.write' == permission or 'repository.admin' == permission:
242 242 return 0
243 243
244 244 # non-zero ret code
245 245 return 1
246 246
247 247 return 0
248 248
249 249
250 250 def post_push(ui, repo, node, **kwargs):
251 251 extras = _extras_from_ui(ui)
252 252
253 253 commit_ids = []
254 254 branches = []
255 255 bookmarks = []
256 256 tags = []
257 257
258 258 for commit_id, branch in _rev_range_hash(repo, node):
259 259 commit_ids.append(commit_id)
260 260 if branch not in branches:
261 261 branches.append(branch)
262 262
263 263 if hasattr(ui, '_rc_pushkey_branches'):
264 264 bookmarks = ui._rc_pushkey_branches
265 265
266 266 extras['commit_ids'] = commit_ids
267 267 extras['new_refs'] = {
268 268 'branches': branches,
269 269 'bookmarks': bookmarks,
270 270 'tags': tags
271 271 }
272 272
273 273 return _call_hook('post_push', extras, HgMessageWriter(ui))
274 274
275 275
276 276 def post_push_ssh(ui, repo, node, **kwargs):
277 277 if _extras_from_ui(ui).get('SSH'):
278 278 return post_push(ui, repo, node, **kwargs)
279 279 return 0
280 280
281 281
282 282 def key_push(ui, repo, **kwargs):
283 283 if kwargs['new'] != '0' and kwargs['namespace'] == 'bookmarks':
284 284 # store new bookmarks in our UI object propagated later to post_push
285 285 ui._rc_pushkey_branches = repo[kwargs['key']].bookmarks()
286 286 return
287 287
288 288
289 289 # backward compat
290 290 log_pull_action = post_pull
291 291
292 292 # backward compat
293 293 log_push_action = post_push
294 294
295 295
296 296 def handle_git_pre_receive(unused_repo_path, unused_revs, unused_env):
297 297 """
298 298 Old hook name: keep here for backward compatibility.
299 299
300 300 This is only required when the installed git hooks are not upgraded.
301 301 """
302 302 pass
303 303
304 304
305 305 def handle_git_post_receive(unused_repo_path, unused_revs, unused_env):
306 306 """
307 307 Old hook name: keep here for backward compatibility.
308 308
309 309 This is only required when the installed git hooks are not upgraded.
310 310 """
311 311 pass
312 312
313 313
314 314 HookResponse = collections.namedtuple('HookResponse', ('status', 'output'))
315 315
316 316
317 317 def git_pre_pull(extras):
318 318 """
319 319 Pre pull hook.
320 320
321 321 :param extras: dictionary containing the keys defined in simplevcs
322 322 :type extras: dict
323 323
324 324 :return: status code of the hook. 0 for success.
325 325 :rtype: int
326 326 """
327 327 if 'pull' not in extras['hooks']:
328 328 return HookResponse(0, '')
329 329
330 330 stdout = io.BytesIO()
331 331 try:
332 332 status = _call_hook('pre_pull', extras, GitMessageWriter(stdout))
333 333 except Exception as error:
334 334 status = 128
335 335 stdout.write('ERROR: %s\n' % str(error))
336 336
337 337 return HookResponse(status, stdout.getvalue())
338 338
339 339
340 340 def git_post_pull(extras):
341 341 """
342 342 Post pull hook.
343 343
344 344 :param extras: dictionary containing the keys defined in simplevcs
345 345 :type extras: dict
346 346
347 347 :return: status code of the hook. 0 for success.
348 348 :rtype: int
349 349 """
350 350 if 'pull' not in extras['hooks']:
351 351 return HookResponse(0, '')
352 352
353 353 stdout = io.BytesIO()
354 354 try:
355 355 status = _call_hook('post_pull', extras, GitMessageWriter(stdout))
356 356 except Exception as error:
357 357 status = 128
358 358 stdout.write('ERROR: %s\n' % error)
359 359
360 360 return HookResponse(status, stdout.getvalue())
361 361
362 362
363 363 def _parse_git_ref_lines(revision_lines):
364 364 rev_data = []
365 365 for revision_line in revision_lines or []:
366 366 old_rev, new_rev, ref = revision_line.strip().split(' ')
367 367 ref_data = ref.split('/', 2)
368 368 if ref_data[1] in ('tags', 'heads'):
369 369 rev_data.append({
370 370 'old_rev': old_rev,
371 371 'new_rev': new_rev,
372 372 'ref': ref,
373 373 'type': ref_data[1],
374 374 'name': ref_data[2],
375 375 })
376 376 return rev_data
377 377
378 378
379 379 def git_pre_receive(unused_repo_path, revision_lines, env):
380 380 """
381 381 Pre push hook.
382 382
383 383 :param extras: dictionary containing the keys defined in simplevcs
384 384 :type extras: dict
385 385
386 386 :return: status code of the hook. 0 for success.
387 387 :rtype: int
388 388 """
389 389 extras = json.loads(env['RC_SCM_DATA'])
390 390 rev_data = _parse_git_ref_lines(revision_lines)
391 391 if 'push' not in extras['hooks']:
392 392 return 0
393 393 extras['commit_ids'] = rev_data
394 394 return _call_hook('pre_push', extras, GitMessageWriter())
395 395
396 396
397 397 def git_post_receive(unused_repo_path, revision_lines, env):
398 398 """
399 399 Post push hook.
400 400
401 401 :param extras: dictionary containing the keys defined in simplevcs
402 402 :type extras: dict
403 403
404 404 :return: status code of the hook. 0 for success.
405 405 :rtype: int
406 406 """
407 407 extras = json.loads(env['RC_SCM_DATA'])
408 408 if 'push' not in extras['hooks']:
409 409 return 0
410 410
411 411 rev_data = _parse_git_ref_lines(revision_lines)
412 412
413 413 git_revs = []
414 414
415 415 # N.B.(skreft): it is ok to just call git, as git before calling a
416 416 # subcommand sets the PATH environment variable so that it point to the
417 417 # correct version of the git executable.
418 418 empty_commit_id = '0' * 40
419 419 branches = []
420 420 tags = []
421 421 for push_ref in rev_data:
422 422 type_ = push_ref['type']
423 423
424 424 if type_ == 'heads':
425 425 if push_ref['old_rev'] == empty_commit_id:
426 426 # starting new branch case
427 427 if push_ref['name'] not in branches:
428 428 branches.append(push_ref['name'])
429 429
430 430 # Fix up head revision if needed
431 431 cmd = [settings.GIT_EXECUTABLE, 'show', 'HEAD']
432 432 try:
433 433 subprocessio.run_command(cmd, env=os.environ.copy())
434 434 except Exception:
435 435 cmd = [settings.GIT_EXECUTABLE, 'symbolic-ref', 'HEAD',
436 436 'refs/heads/%s' % push_ref['name']]
437 437 print("Setting default branch to %s" % push_ref['name'])
438 438 subprocessio.run_command(cmd, env=os.environ.copy())
439 439
440 440 cmd = [settings.GIT_EXECUTABLE, 'for-each-ref',
441 441 '--format=%(refname)', 'refs/heads/*']
442 442 stdout, stderr = subprocessio.run_command(
443 443 cmd, env=os.environ.copy())
444 444 heads = stdout
445 445 heads = heads.replace(push_ref['ref'], '')
446 heads = ' '.join(head for head in heads.splitlines() if head)
446 heads = ' '.join(head for head
447 in heads.splitlines() if head) or '.'
447 448 cmd = [settings.GIT_EXECUTABLE, 'log', '--reverse',
448 449 '--pretty=format:%H', '--', push_ref['new_rev'],
449 450 '--not', heads]
450 451 stdout, stderr = subprocessio.run_command(
451 452 cmd, env=os.environ.copy())
452 453 git_revs.extend(stdout.splitlines())
453 454 elif push_ref['new_rev'] == empty_commit_id:
454 455 # delete branch case
455 456 git_revs.append('delete_branch=>%s' % push_ref['name'])
456 457 else:
457 458 if push_ref['name'] not in branches:
458 459 branches.append(push_ref['name'])
459 460
460 461 cmd = [settings.GIT_EXECUTABLE, 'log',
461 462 '{old_rev}..{new_rev}'.format(**push_ref),
462 463 '--reverse', '--pretty=format:%H']
463 464 stdout, stderr = subprocessio.run_command(
464 465 cmd, env=os.environ.copy())
465 466 git_revs.extend(stdout.splitlines())
466 467 elif type_ == 'tags':
467 468 if push_ref['name'] not in tags:
468 469 tags.append(push_ref['name'])
469 470 git_revs.append('tag=>%s' % push_ref['name'])
470 471
471 472 extras['commit_ids'] = git_revs
472 473 extras['new_refs'] = {
473 474 'branches': branches,
474 475 'bookmarks': [],
475 476 'tags': tags,
476 477 }
477 478
478 479 if 'repo_size' in extras['hooks']:
479 480 try:
480 481 _call_hook('repo_size', extras, GitMessageWriter())
481 482 except:
482 483 pass
483 484
484 485 return _call_hook('post_push', extras, GitMessageWriter())
485 486
486 487
487 488 def svn_pre_commit(repo_path, commit_data, env):
488 489 path, txn_id = commit_data
489 490 branches = []
490 491 tags = []
491 492
492 493 cmd = ['svnlook', 'pget',
493 494 '-t', txn_id,
494 495 '--revprop', path, 'rc-scm-extras']
495 496 stdout, stderr = subprocessio.run_command(
496 497 cmd, env=os.environ.copy())
497 498 extras = json.loads(base64.urlsafe_b64decode(stdout))
498 499
499 500 extras['commit_ids'] = []
500 501 extras['txn_id'] = txn_id
501 502 extras['new_refs'] = {
502 503 'branches': branches,
503 504 'bookmarks': [],
504 505 'tags': tags,
505 506 }
506 507 sys.stderr.write(str(extras))
507 508 return _call_hook('pre_push', extras, SvnMessageWriter())
508 509
509 510
510 511 def svn_post_commit(repo_path, commit_data, env):
511 512 """
512 513 commit_data is path, rev, txn_id
513 514 """
514 515 path, commit_id, txn_id = commit_data
515 516 branches = []
516 517 tags = []
517 518
518 519 cmd = ['svnlook', 'pget',
519 520 '-r', commit_id,
520 521 '--revprop', path, 'rc-scm-extras']
521 522 stdout, stderr = subprocessio.run_command(
522 523 cmd, env=os.environ.copy())
523 524
524 525 extras = json.loads(base64.urlsafe_b64decode(stdout))
525 526
526 527 extras['commit_ids'] = [commit_id]
527 528 extras['txn_id'] = txn_id
528 529 extras['new_refs'] = {
529 530 'branches': branches,
530 531 'bookmarks': [],
531 532 'tags': tags,
532 533 }
533 534
534 535 if 'repo_size' in extras['hooks']:
535 536 try:
536 537 _call_hook('repo_size', extras, SvnMessageWriter())
537 538 except:
538 539 pass
539 540
540 541 return _call_hook('post_push', extras, SvnMessageWriter())
541 542
542 543
General Comments 0
You need to be logged in to leave comments. Login now