##// END OF EJS Templates
hooks: adjust to handle protected branch cases, and force push.
marcink -
r509:1db1b667 default
parent child Browse files
Show More
@@ -24,8 +24,7 b' which contain an extra attribute `_vcs_k'
24 different error conditions.
24 different error conditions.
25 """
25 """
26
26
27 import functools
27 from pyramid.httpexceptions import HTTPLocked, HTTPForbidden
28 from pyramid.httpexceptions import HTTPLocked
29
28
30
29
31 def _make_exception(kind, org_exc, *args):
30 def _make_exception(kind, org_exc, *args):
@@ -71,6 +70,12 b' def RepositoryLockedException(org_exc=No'
71 return _make_exception_wrapper
70 return _make_exception_wrapper
72
71
73
72
73 def RepositoryBranchProtectedException(org_exc=None):
74 def _make_exception_wrapper(*args):
75 return _make_exception('repo_branch_protected', org_exc, *args)
76 return _make_exception_wrapper
77
78
74 def RequirementException(org_exc=None):
79 def RequirementException(org_exc=None):
75 def _make_exception_wrapper(*args):
80 def _make_exception_wrapper(*args):
76 return _make_exception('requirement', org_exc, *args)
81 return _make_exception('requirement', org_exc, *args)
@@ -104,3 +109,8 b' class HTTPRepoLocked(HTTPLocked):'
104 self.code = status_code or HTTPLocked.code
109 self.code = status_code or HTTPLocked.code
105 self.title = title
110 self.title = title
106 super(HTTPRepoLocked, self).__init__(**kwargs)
111 super(HTTPRepoLocked, self).__init__(**kwargs)
112
113
114 class HTTPRepoBranchProtected(HTTPForbidden):
115 def __init__(self, *args, **kwargs):
116 super(HTTPForbidden, self).__init__(*args, **kwargs)
@@ -121,6 +121,8 b' def _handle_exception(result):'
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':
125 raise exceptions.RepositoryBranchProtectedException()(*result['exception_args'])
124 elif exception_class == 'RepositoryError':
126 elif exception_class == 'RepositoryError':
125 raise exceptions.VcsException()(*result['exception_args'])
127 raise exceptions.VcsException()(*result['exception_args'])
126 elif exception_class:
128 elif exception_class:
@@ -161,17 +163,54 b' def _extras_from_ui(ui):'
161 return extras
163 return extras
162
164
163
165
164 def _rev_range_hash(repo, node):
166 def _rev_range_hash(repo, node, check_heads=False):
165
167
166 commits = []
168 commits = []
169 revs = []
167 start = repo[node].rev()
170 start = repo[node].rev()
168 for rev in xrange(start, len(repo)):
171 end = len(repo)
172 for rev in range(start, end):
173 revs.append(rev)
169 ctx = repo[rev]
174 ctx = repo[rev]
170 commit_id = mercurial.node.hex(ctx.node())
175 commit_id = mercurial.node.hex(ctx.node())
171 branch = ctx.branch()
176 branch = ctx.branch()
172 commits.append((commit_id, branch))
177 commits.append((commit_id, branch))
173
178
174 return commits
179 parent_heads = []
180 if check_heads:
181 parent_heads = _check_heads(repo, start, end, revs)
182 return commits, parent_heads
183
184
185 def _check_heads(repo, start, end, commits):
186 changelog = repo.changelog
187 parents = set()
188
189 for new_rev in commits:
190 for p in changelog.parentrevs(new_rev):
191 if p == mercurial.node.nullrev:
192 continue
193 if p < start:
194 parents.add(p)
195
196 for p in parents:
197 branch = repo[p].branch()
198 # The heads descending from that parent, on the same branch
199 parent_heads = set([p])
200 reachable = set([p])
201 for x in xrange(p + 1, end):
202 if repo[x].branch() != branch:
203 continue
204 for pp in changelog.parentrevs(x):
205 if pp in reachable:
206 reachable.add(x)
207 parent_heads.discard(pp)
208 parent_heads.add(x)
209 # More than one head? Suggest merging
210 if len(parent_heads) > 1:
211 return list(parent_heads)
212
213 return []
175
214
176
215
177 def repo_size(ui, repo, **kwargs):
216 def repo_size(ui, repo, **kwargs):
@@ -204,15 +243,20 b' def post_pull_ssh(ui, repo, **kwargs):'
204
243
205
244
206 def pre_push(ui, repo, node=None, **kwargs):
245 def pre_push(ui, repo, node=None, **kwargs):
246 """
247 Mercurial pre_push hook
248 """
207 extras = _extras_from_ui(ui)
249 extras = _extras_from_ui(ui)
250 detect_force_push = extras.get('detect_force_push')
208
251
209 rev_data = []
252 rev_data = []
210 if node and kwargs.get('hooktype') == 'pretxnchangegroup':
253 if node and kwargs.get('hooktype') == 'pretxnchangegroup':
211 branches = collections.defaultdict(list)
254 branches = collections.defaultdict(list)
212 for commit_id, branch in _rev_range_hash(repo, node):
255 commits, _heads = _rev_range_hash(repo, node, check_heads=detect_force_push)
256 for commit_id, branch in commits:
213 branches[branch].append(commit_id)
257 branches[branch].append(commit_id)
214
258
215 for branch, commits in branches.iteritems():
259 for branch, commits in branches.items():
216 old_rev = kwargs.get('node_last') or commits[0]
260 old_rev = kwargs.get('node_last') or commits[0]
217 rev_data.append({
261 rev_data.append({
218 'old_rev': old_rev,
262 'old_rev': old_rev,
@@ -222,6 +266,9 b' def pre_push(ui, repo, node=None, **kwar'
222 'name': branch,
266 'name': branch,
223 })
267 })
224
268
269 for push_ref in rev_data:
270 push_ref['multiple_heads'] = _heads
271
225 extras['commit_ids'] = rev_data
272 extras['commit_ids'] = rev_data
226 return _call_hook('pre_push', extras, HgMessageWriter(ui))
273 return _call_hook('pre_push', extras, HgMessageWriter(ui))
227
274
@@ -234,6 +281,9 b' def pre_push_ssh(ui, repo, node=None, **'
234
281
235
282
236 def pre_push_ssh_auth(ui, repo, node=None, **kwargs):
283 def pre_push_ssh_auth(ui, repo, node=None, **kwargs):
284 """
285 Mercurial pre_push hook for SSH
286 """
237 extras = _extras_from_ui(ui)
287 extras = _extras_from_ui(ui)
238 if extras.get('SSH'):
288 if extras.get('SSH'):
239 permission = extras['SSH_PERMISSIONS']
289 permission = extras['SSH_PERMISSIONS']
@@ -248,6 +298,9 b' def pre_push_ssh_auth(ui, repo, node=Non'
248
298
249
299
250 def post_push(ui, repo, node, **kwargs):
300 def post_push(ui, repo, node, **kwargs):
301 """
302 Mercurial post_push hook
303 """
251 extras = _extras_from_ui(ui)
304 extras = _extras_from_ui(ui)
252
305
253 commit_ids = []
306 commit_ids = []
@@ -255,7 +308,8 b' def post_push(ui, repo, node, **kwargs):'
255 bookmarks = []
308 bookmarks = []
256 tags = []
309 tags = []
257
310
258 for commit_id, branch in _rev_range_hash(repo, node):
311 commits, _heads = _rev_range_hash(repo, node)
312 for commit_id, branch in commits:
259 commit_ids.append(commit_id)
313 commit_ids.append(commit_id)
260 if branch not in branches:
314 if branch not in branches:
261 branches.append(branch)
315 branches.append(branch)
@@ -274,6 +328,9 b' def post_push(ui, repo, node, **kwargs):'
274
328
275
329
276 def post_push_ssh(ui, repo, node, **kwargs):
330 def post_push_ssh(ui, repo, node, **kwargs):
331 """
332 Mercurial post_push hook for SSH
333 """
277 if _extras_from_ui(ui).get('SSH'):
334 if _extras_from_ui(ui).get('SSH'):
278 return post_push(ui, repo, node, **kwargs)
335 return post_push(ui, repo, node, **kwargs)
279 return 0
336 return 0
@@ -390,6 +447,30 b' def git_pre_receive(unused_repo_path, re'
390 rev_data = _parse_git_ref_lines(revision_lines)
447 rev_data = _parse_git_ref_lines(revision_lines)
391 if 'push' not in extras['hooks']:
448 if 'push' not in extras['hooks']:
392 return 0
449 return 0
450 empty_commit_id = '0' * 40
451
452 detect_force_push = extras.get('detect_force_push')
453
454 for push_ref in rev_data:
455 push_ref['pruned_sha'] = ''
456 if not detect_force_push:
457 # don't check for forced-push when we don't need to
458 continue
459
460 type_ = push_ref['type']
461 new_branch = push_ref['old_rev'] == empty_commit_id
462 if type_ == 'heads' and not new_branch:
463 old_rev = push_ref['old_rev']
464 new_rev = push_ref['new_rev']
465 cmd = [settings.GIT_EXECUTABLE, 'rev-list',
466 old_rev, '^{}'.format(new_rev)]
467 stdout, stderr = subprocessio.run_command(
468 cmd, env=os.environ.copy())
469 # means we're having some non-reachable objects, this forced push
470 # was used
471 if stdout:
472 push_ref['pruned_sha'] = stdout.splitlines()
473
393 extras['commit_ids'] = rev_data
474 extras['commit_ids'] = rev_data
394 return _call_hook('pre_push', extras, GitMessageWriter())
475 return _call_hook('pre_push', extras, GitMessageWriter())
395
476
@@ -50,7 +50,7 b' from vcsserver import remote_wsgi, scm_a'
50 from vcsserver.git_lfs.app import GIT_LFS_CONTENT_TYPE, GIT_LFS_PROTO_PAT
50 from vcsserver.git_lfs.app import GIT_LFS_CONTENT_TYPE, GIT_LFS_PROTO_PAT
51 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
51 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
52 from vcsserver.echo_stub.echo_app import EchoApp
52 from vcsserver.echo_stub.echo_app import EchoApp
53 from vcsserver.exceptions import HTTPRepoLocked
53 from vcsserver.exceptions import HTTPRepoLocked, HTTPRepoBranchProtected
54 from vcsserver.lib.exc_tracking import store_exception
54 from vcsserver.lib.exc_tracking import store_exception
55 from vcsserver.server import VcsServer
55 from vcsserver.server import VcsServer
56
56
@@ -524,6 +524,10 b' class HTTPApplication(object):'
524 return HTTPRepoLocked(
524 return HTTPRepoLocked(
525 title=exception.message, status_code=status_code)
525 title=exception.message, status_code=status_code)
526
526
527 elif _vcs_kind == 'repo_branch_protected':
528 # Get custom repo-branch-protected status code if present.
529 return HTTPRepoBranchProtected(title=exception.message)
530
527 exc_info = request.exc_info
531 exc_info = request.exc_info
528 store_exception(id(exc_info), exc_info)
532 store_exception(id(exc_info), exc_info)
529
533
General Comments 0
You need to be logged in to leave comments. Login now