Skip to content
Snippets Groups Projects
Commit 3dec761b authored by Georges Racinet's avatar Georges Racinet
Browse files

Merged heptapod-0-7 into default

That makes the hook available in default, too
parents eaa7b1f9 6d2155dc
Branches
Tags
No related merge requests found
Pipeline #
......@@ -4,3 +4,12 @@
## WSGI serving of repositories
Provided by `heptapod.wsgi` (not fully independent yet)
## Mercurial Hooks
`heptapod.hooks.check_publish.check_publish`:
permission rules about public changesets in pushes.
`heptapod.hooks.git_sync.mirror`:
synchronisation to inner auxiliary Git repository for exposition to GitLab
`heptapod.hooks.dev_util`: useful hooks for debug and development
# Python package
from mercurial import (
error,
scmutil,
configitems,
phases,
)
if 'allow-publish' not in configitems.coreitems['web']:
configitems._register(configitems.coreitems,
section='web',
name='allow-publish',
alias=[('web', 'allow_publish')],
default=lambda: list(['*']),
)
def check_publish(repo, *args, **kwargs):
hooktype = kwargs.get('hooktype')
if hooktype != "pretxnclose":
msg = 'check_publish must be used as "pretxnclose" hook (not "%s")'
raise error.ProgrammingError(msg % hooktype)
remoteuser = repo.ui.environ.get("REMOTE_USER", None)
allowed = repo.ui.configlist('web', 'allow-publish')
if remoteuser is not None:
repo.ui.debug(
'check_publish detected push from user: %r\n' % remoteuser)
# remoteuser `None` is for command-line operations. In the context
# of Heptapod, that's from other GitLab components that perform
# their own permission checks (e.g., merge request from the UI)
if remoteuser is None or '*' in allowed or remoteuser in allowed:
repo.ui.debug('check_publish user %r is allowed, allowed=%r' % (
remoteuser, allowed))
return 0
tr = repo.currenttransaction()
assert tr is not None and tr.running()
phaseschanges = tr.changes.get("phases", {})
publishing = set(rev for rev, (old, new) in phaseschanges.iteritems()
if new == phases.public and old != phases.public)
if publishing:
node = repo.changelog.node
nodes = [node(r) for r in sorted(publishing)]
nodes = scmutil.nodesummaries(repo, nodes)
msg = 'user "%s" is not authorised to publish changesets: %s\n'
repo.ui.warn(msg % (remoteuser, nodes))
return 1
return 0
def print_heptapod_env(repo, *args, **kwargs):
repo.ui.status(repr(sorted(
(k, v) for (k, v) in repo.ui.environ.items()
if k.startswith('HEPTAPOD_'))))
return 0
import os
from mercurial import cmdutil, commands, error
from mercurial.i18n import _
UP_DIR = os.path.pardir + os.path.pathsep
def mirror_path(ui, repo):
repos_root = ui.config('heptapod', 'repositories-root')
if not repos_root:
raise error.Abort(_("heptapod.repositories-root not set"))
dest = ui.config('heptapod', 'mirror-path')
if not dest:
# Mercurial gives us `realpath` of repos. Therefore, if the whole
# repositories root turns out to be a symlink, we need to
# use its `realpath`, too.
repos_root = os.path.realpath(repos_root)
repo_path = repo.root
if repo_path is None:
raise error.Abort(_(
'heptapod.mirror-path repository not configured, and '
'cannot find current repository filesystem path'))
rpath = os.path.relpath(repo_path, repos_root)
if rpath.startswith(UP_DIR) or not rpath.endswith('.hg'):
raise error.Abort(_(
'heptapod.mirror-path repository not configured, and '
'inferred relative path %r is invalid' % rpath))
dest = 'git+ssh://git@localhost:' + rpath.rsplit('.', 1)[0] + '.git'
return dest
def mirror(ui, repo, *args, **kwargs):
dest = mirror_path(ui, repo)
repo.ui.note("Heptapod mirror starting, target is %r" % dest)
push = cmdutil.findcmd('push', commands.table)[1][0]
push(ui, repo, dest=dest, force=True)
# command.push converts pushoperation.cgresult into a boolean, with True
# meaning a problem occurred, and False being the normal condition.
# It does it like so:
# result = not pushop.cgresult
#
# It can also return 2 upon some undocumented conditions with bookmarks
# but that's not an immediate concert within Heptapod.
#
# Actually its docstring says:
#
# "Returns 0 if push was successful, 1 if nothing to push."
#
# But that's inaccurate, the __init__ of pushoperation has this comment:
# Integer version of the changegroup push result
# - None means nothing to push
# - 0 means HTTP error
# - 1 means we pushed and remote head count is unchanged *or*
# we have outgoing changesets but refused to push
# - other values as described by addchangegroup()
# self.cgresult = None
#
# According to this, push_result=True would conflate having nothing to push
# with transport errors.
#
# But hggit never returns 0 upon transport errors (see `GitHandler.push()`)
# Instead, it raises `error.Abort`
#
# Therefore, push_result is `True` if and only if there was nothing
# to push to Git (and no exception got raised, obviously).
# In Heptapod, we need empty pushes to Git (such as pruning
# a changeset without evolving its children, or changing changeset phases
# only) to be considered normal.
# Here are the consequences:
repo.ui.note("Heptapod mirror done\n")
return 0 # always succeed at this point
# name space package to host third party extensions
from __future__ import absolute_import
import pkgutil
__path__ = pkgutil.extend_path(__path__, __name__)
# Copyright 2019 Georges Racinet <georges.racinet@octobus.net>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
"""
Server side Heptapod extension.
This extension should enclose all Mercurial modifications and commands
needed in the Heptapod context.
"""
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment