hooks.py
400 lines
| 11.8 KiB
| text/x-python
|
PythonLexer
Bradley M. Kuhn
|
r4187 | # -*- coding: utf-8 -*- | ||
# This program is free software: you can redistribute it and/or modify | ||||
# it under the terms of the GNU General Public License as published by | ||||
# the Free Software Foundation, either version 3 of the License, or | ||||
# (at your option) any later version. | ||||
# | ||||
# This program is distributed in the hope that it will be useful, | ||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
# GNU General Public License for more details. | ||||
# | ||||
# You should have received a copy of the GNU General Public License | ||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
""" | ||||
kallithea.lib.hooks | ||||
~~~~~~~~~~~~~~~~~~~ | ||||
Thomas De Schampheleire
|
r4918 | Hooks run by Kallithea | ||
Bradley M. Kuhn
|
r4187 | |||
Bradley M. Kuhn
|
r4211 | This file was forked by the Kallithea project in July 2014. | ||
Original author and date, and relevant copyright and licensing information is below: | ||||
Bradley M. Kuhn
|
r4187 | :created_on: Aug 6, 2010 | ||
:author: marcink | ||||
Bradley M. Kuhn
|
r4211 | :copyright: (c) 2013 RhodeCode GmbH, and others. | ||
Bradley M. Kuhn
|
r4208 | :license: GPLv3, see LICENSE.md for more details. | ||
Bradley M. Kuhn
|
r4187 | """ | ||
import os | ||||
Mads Kiilerich
|
r7972 | import sys | ||
Bradley M. Kuhn
|
r4187 | import time | ||
Mads Kiilerich
|
r7977 | import mercurial.scmutil | ||
Bradley M. Kuhn
|
r4187 | from kallithea.lib import helpers as h | ||
Mads Kiilerich
|
r7718 | from kallithea.lib.exceptions import UserCreationError | ||
Mads Kiilerich
|
r7993 | from kallithea.lib.utils import action_logger, make_ui | ||
Mads Kiilerich
|
r8079 | from kallithea.lib.utils2 import HookEnvironmentError, ascii_str, get_hook_environment, safe_bytes, safe_str | ||
Bradley M. Kuhn
|
r4187 | from kallithea.lib.vcs.backends.base import EmptyChangeset | ||
Mads Kiilerich
|
r7719 | from kallithea.model.db import Repository, User | ||
Bradley M. Kuhn
|
r4187 | |||
def _get_scm_size(alias, root_path): | ||||
if not alias.startswith('.'): | ||||
alias += '.' | ||||
size_scm, size_root = 0, 0 | ||||
Mads Kiilerich
|
r8076 | for path, dirs, files in os.walk(root_path): | ||
Bradley M. Kuhn
|
r4187 | if path.find(alias) != -1: | ||
for f in files: | ||||
try: | ||||
size_scm += os.path.getsize(os.path.join(path, f)) | ||||
except OSError: | ||||
pass | ||||
else: | ||||
for f in files: | ||||
try: | ||||
size_root += os.path.getsize(os.path.join(path, f)) | ||||
except OSError: | ||||
pass | ||||
size_scm_f = h.format_byte_size(size_scm) | ||||
size_root_f = h.format_byte_size(size_root) | ||||
size_total_f = h.format_byte_size(size_root + size_scm) | ||||
return size_scm_f, size_root_f, size_total_f | ||||
def repo_size(ui, repo, hooktype=None, **kwargs): | ||||
Mads Kiilerich
|
r7978 | """Show size of Mercurial repository, to be called after push.""" | ||
Mads Kiilerich
|
r8079 | size_hg_f, size_root_f, size_total_f = _get_scm_size('.hg', safe_str(repo.root)) | ||
Bradley M. Kuhn
|
r4187 | |||
last_cs = repo[len(repo) - 1] | ||||
Mads Kiilerich
|
r6270 | msg = ('Repository size .hg: %s Checkout: %s Total: %s\n' | ||
Bradley M. Kuhn
|
r4187 | 'Last revision is now r%s:%s\n') % ( | ||
Mads Kiilerich
|
r7960 | size_hg_f, size_root_f, size_total_f, last_cs.rev(), ascii_str(last_cs.hex())[:12] | ||
Bradley M. Kuhn
|
r4187 | ) | ||
Mads Kiilerich
|
r7992 | ui.status(safe_bytes(msg)) | ||
Bradley M. Kuhn
|
r4187 | |||
def log_pull_action(ui, repo, **kwargs): | ||||
Mads Kiilerich
|
r7578 | """Logs user last pull action | ||
Bradley M. Kuhn
|
r4187 | |||
Mads Kiilerich
|
r7273 | Called as Mercurial hook outgoing.pull_logger or from Kallithea before invoking Git. | ||
Mads Kiilerich
|
r7581 | |||
Does *not* use the action from the hook environment but is always 'pull'. | ||||
Bradley M. Kuhn
|
r4187 | """ | ||
Mads Kiilerich
|
r7634 | ex = get_hook_environment() | ||
Bradley M. Kuhn
|
r4187 | |||
user = User.get_by_username(ex.username) | ||||
action = 'pull' | ||||
action_logger(user, action, ex.repository, ex.ip, commit=True) | ||||
# extension hook call | ||||
from kallithea import EXTENSIONS | ||||
callback = getattr(EXTENSIONS, 'PULL_HOOK', None) | ||||
if callable(callback): | ||||
kw = {} | ||||
kw.update(ex) | ||||
callback(**kw) | ||||
return 0 | ||||
Mads Kiilerich
|
r7581 | def log_push_action(ui, repo, node, node_last, **kwargs): | ||
""" | ||||
Entry point for Mercurial hook changegroup.push_logger. | ||||
The pushed changesets is given by the revset 'node:node_last'. | ||||
Note: This hook is not only logging, but also the side effect invalidating | ||||
cahes! The function should perhaps be renamed. | ||||
Bradley M. Kuhn
|
r4187 | """ | ||
Mads Kiilerich
|
r7977 | revs = [ascii_str(repo[r].hex()) for r in mercurial.scmutil.revrange(repo, [b'%s:%s' % (node, node_last)])] | ||
Mads Kiilerich
|
r7581 | process_pushed_raw_ids(revs) | ||
return 0 | ||||
Mads Kiilerich
|
r7273 | |||
Mads Kiilerich
|
r7581 | def process_pushed_raw_ids(revs): | ||
""" | ||||
Register that changes have been added to the repo - log the action *and* invalidate caches. | ||||
Mads Kiilerich
|
r7281 | |||
Mads Kiilerich
|
r7581 | Called from Mercurial changegroup.push_logger calling hook log_push_action, | ||
or from the Git post-receive hook calling handle_git_post_receive ... | ||||
or from scm _handle_push. | ||||
Bradley M. Kuhn
|
r4187 | """ | ||
Mads Kiilerich
|
r7634 | ex = get_hook_environment() | ||
Bradley M. Kuhn
|
r4187 | |||
Mads Kiilerich
|
r7581 | action = '%s:%s' % (ex.action, ','.join(revs)) | ||
Bradley M. Kuhn
|
r4187 | action_logger(ex.username, action, ex.repository, ex.ip, commit=True) | ||
Mads Kiilerich
|
r7281 | from kallithea.model.scm import ScmModel | ||
ScmModel().mark_for_invalidation(ex.repository) | ||||
Bradley M. Kuhn
|
r4187 | # extension hook call | ||
from kallithea import EXTENSIONS | ||||
callback = getattr(EXTENSIONS, 'PUSH_HOOK', None) | ||||
if callable(callback): | ||||
kw = {'pushed_revs': revs} | ||||
kw.update(ex) | ||||
callback(**kw) | ||||
def log_create_repository(repository_dict, created_by, **kwargs): | ||||
""" | ||||
Post create repository Hook. | ||||
:param repository: dict dump of repository object | ||||
:param created_by: username who created repository | ||||
available keys of repository_dict: | ||||
'repo_type', | ||||
'description', | ||||
'private', | ||||
'created_on', | ||||
'enable_downloads', | ||||
'repo_id', | ||||
Søren Løvborg
|
r6193 | 'owner_id', | ||
Bradley M. Kuhn
|
r4187 | 'enable_statistics', | ||
'clone_uri', | ||||
'fork_id', | ||||
'group_id', | ||||
'repo_name' | ||||
""" | ||||
from kallithea import EXTENSIONS | ||||
callback = getattr(EXTENSIONS, 'CREATE_REPO_HOOK', None) | ||||
if callable(callback): | ||||
kw = {} | ||||
kw.update(repository_dict) | ||||
kw.update({'created_by': created_by}) | ||||
kw.update(kwargs) | ||||
return callback(**kw) | ||||
return 0 | ||||
def check_allowed_create_user(user_dict, created_by, **kwargs): | ||||
# pre create hooks | ||||
from kallithea import EXTENSIONS | ||||
callback = getattr(EXTENSIONS, 'PRE_CREATE_USER_HOOK', None) | ||||
if callable(callback): | ||||
allowed, reason = callback(created_by=created_by, **user_dict) | ||||
if not allowed: | ||||
raise UserCreationError(reason) | ||||
def log_create_user(user_dict, created_by, **kwargs): | ||||
""" | ||||
Post create user Hook. | ||||
:param user_dict: dict dump of user object | ||||
available keys for user_dict: | ||||
'username', | ||||
'full_name_or_username', | ||||
'full_contact', | ||||
'user_id', | ||||
'name', | ||||
'firstname', | ||||
'short_contact', | ||||
'admin', | ||||
'lastname', | ||||
'ip_addresses', | ||||
'ldap_dn', | ||||
'email', | ||||
'api_key', | ||||
'last_login', | ||||
'full_name', | ||||
'active', | ||||
'password', | ||||
'emails', | ||||
""" | ||||
from kallithea import EXTENSIONS | ||||
callback = getattr(EXTENSIONS, 'CREATE_USER_HOOK', None) | ||||
if callable(callback): | ||||
return callback(created_by=created_by, **user_dict) | ||||
return 0 | ||||
def log_delete_repository(repository_dict, deleted_by, **kwargs): | ||||
""" | ||||
Post delete repository Hook. | ||||
:param repository: dict dump of repository object | ||||
:param deleted_by: username who deleted the repository | ||||
available keys of repository_dict: | ||||
'repo_type', | ||||
'description', | ||||
'private', | ||||
'created_on', | ||||
'enable_downloads', | ||||
'repo_id', | ||||
Søren Løvborg
|
r6193 | 'owner_id', | ||
Bradley M. Kuhn
|
r4187 | 'enable_statistics', | ||
'clone_uri', | ||||
'fork_id', | ||||
'group_id', | ||||
'repo_name' | ||||
""" | ||||
from kallithea import EXTENSIONS | ||||
callback = getattr(EXTENSIONS, 'DELETE_REPO_HOOK', None) | ||||
if callable(callback): | ||||
kw = {} | ||||
kw.update(repository_dict) | ||||
kw.update({'deleted_by': deleted_by, | ||||
'deleted_on': time.time()}) | ||||
kw.update(kwargs) | ||||
return callback(**kw) | ||||
return 0 | ||||
def log_delete_user(user_dict, deleted_by, **kwargs): | ||||
""" | ||||
Post delete user Hook. | ||||
:param user_dict: dict dump of user object | ||||
available keys for user_dict: | ||||
'username', | ||||
'full_name_or_username', | ||||
'full_contact', | ||||
'user_id', | ||||
'name', | ||||
'firstname', | ||||
'short_contact', | ||||
'admin', | ||||
'lastname', | ||||
'ip_addresses', | ||||
'ldap_dn', | ||||
'email', | ||||
'api_key', | ||||
'last_login', | ||||
'full_name', | ||||
'active', | ||||
'password', | ||||
'emails', | ||||
""" | ||||
from kallithea import EXTENSIONS | ||||
callback = getattr(EXTENSIONS, 'DELETE_USER_HOOK', None) | ||||
if callable(callback): | ||||
return callback(deleted_by=deleted_by, **user_dict) | ||||
return 0 | ||||
Mads Kiilerich
|
r7278 | def _hook_environment(repo_path): | ||
Bradley M. Kuhn
|
r4187 | """ | ||
Mads Kiilerich
|
r7274 | Create a light-weight environment for stand-alone scripts and return an UI and the | ||
db repository. | ||||
Bradley M. Kuhn
|
r4187 | |||
Mads Kiilerich
|
r7274 | Git hooks are executed as subprocess of Git while Kallithea is waiting, and | ||
they thus need enough info to be able to create an app environment and | ||||
connect to the database. | ||||
Bradley M. Kuhn
|
r4187 | """ | ||
Mads Kiilerich
|
r7993 | import paste.deploy | ||
import kallithea.config.middleware | ||||
Mads Kiilerich
|
r7273 | |||
Mads Kiilerich
|
r7634 | extras = get_hook_environment() | ||
Mads Kiilerich
|
r7993 | |||
path_to_ini_file = extras['config'] | ||||
kallithea.CONFIG = paste.deploy.appconfig('config:' + path_to_ini_file) | ||||
Mads Kiilerich
|
r7283 | #logging.config.fileConfig(ini_file_path) # Note: we are in a different process - don't use configured logging | ||
Mads Kiilerich
|
r8023 | kallithea.config.middleware.make_app(kallithea.CONFIG.global_conf, **kallithea.CONFIG.local_conf) | ||
Bradley M. Kuhn
|
r4187 | |||
# fix if it's not a bare repo | ||||
if repo_path.endswith(os.sep + '.git'): | ||||
repo_path = repo_path[:-5] | ||||
repo = Repository.get_by_full_path(repo_path) | ||||
if not repo: | ||||
Mads Kiilerich
|
r8076 | raise OSError('Repository %s not found in database' % repo_path) | ||
Bradley M. Kuhn
|
r4187 | |||
Mads Kiilerich
|
r7580 | baseui = make_ui() | ||
Mads Kiilerich
|
r7274 | return baseui, repo | ||
Mads Kiilerich
|
r7278 | def handle_git_pre_receive(repo_path, git_stdin_lines): | ||
Mads Kiilerich
|
r7275 | """Called from Git pre-receive hook""" | ||
Mads Kiilerich
|
r7581 | # Currently unused. TODO: remove? | ||
Mads Kiilerich
|
r7279 | return 0 | ||
Mads Kiilerich
|
r7275 | |||
Mads Kiilerich
|
r7278 | def handle_git_post_receive(repo_path, git_stdin_lines): | ||
Mads Kiilerich
|
r7275 | """Called from Git post-receive hook""" | ||
Mads Kiilerich
|
r7972 | try: | ||
baseui, repo = _hook_environment(repo_path) | ||||
except HookEnvironmentError as e: | ||||
sys.stderr.write("Skipping Kallithea Git post-recieve hook %r.\nGit was apparently not invoked by Kallithea: %s\n" % (sys.argv[0], e)) | ||||
return 0 | ||||
Mads Kiilerich
|
r7273 | |||
Mads Kiilerich
|
r7275 | # the post push hook should never use the cached instance | ||
scm_repo = repo.scm_instance_no_cache() | ||||
Mads Kiilerich
|
r7273 | |||
Mads Kiilerich
|
r7579 | rev_data = [] | ||
for l in git_stdin_lines: | ||||
old_rev, new_rev, ref = l.strip().split(' ') | ||||
_ref_data = ref.split('/') | ||||
if _ref_data[1] in ['tags', 'heads']: | ||||
rev_data.append({'old_rev': old_rev, | ||||
'new_rev': new_rev, | ||||
'ref': ref, | ||||
'type': _ref_data[1], | ||||
'name': '/'.join(_ref_data[2:])}) | ||||
Bradley M. Kuhn
|
r4187 | |||
Mads Kiilerich
|
r7579 | git_revs = [] | ||
for push_ref in rev_data: | ||||
_type = push_ref['type'] | ||||
if _type == 'heads': | ||||
if push_ref['old_rev'] == EmptyChangeset().raw_id: | ||||
# update the symbolic ref if we push new repo | ||||
if scm_repo.is_empty(): | ||||
Mads Kiilerich
|
r7958 | scm_repo._repo.refs.set_symbolic_ref( | ||
b'HEAD', | ||||
Mads Kiilerich
|
r7992 | b'refs/heads/%s' % safe_bytes(push_ref['name'])) | ||
Bradley M. Kuhn
|
r4187 | |||
Mads Kiilerich
|
r7579 | # build exclude list without the ref | ||
cmd = ['for-each-ref', '--format=%(refname)', 'refs/heads/*'] | ||||
Mads Kiilerich
|
r7938 | stdout = scm_repo.run_git_command(cmd) | ||
Mads Kiilerich
|
r7579 | ref = push_ref['ref'] | ||
heads = [head for head in stdout.splitlines() if head != ref] | ||||
# now list the git revs while excluding from the list | ||||
cmd = ['log', push_ref['new_rev'], '--reverse', '--pretty=format:%H'] | ||||
cmd.append('--not') | ||||
cmd.extend(heads) # empty list is ok | ||||
Mads Kiilerich
|
r7938 | stdout = scm_repo.run_git_command(cmd) | ||
Mads Kiilerich
|
r7579 | git_revs += stdout.splitlines() | ||
Bradley M. Kuhn
|
r4187 | |||
Mads Kiilerich
|
r7579 | elif push_ref['new_rev'] == EmptyChangeset().raw_id: | ||
# delete branch case | ||||
git_revs += ['delete_branch=>%s' % push_ref['name']] | ||||
else: | ||||
cmd = ['log', '%(old_rev)s..%(new_rev)s' % push_ref, | ||||
'--reverse', '--pretty=format:%H'] | ||||
Mads Kiilerich
|
r7938 | stdout = scm_repo.run_git_command(cmd) | ||
Mads Kiilerich
|
r7579 | git_revs += stdout.splitlines() | ||
Bradley M. Kuhn
|
r4187 | |||
Mads Kiilerich
|
r7579 | elif _type == 'tags': | ||
git_revs += ['tag=>%s' % push_ref['name']] | ||||
Bradley M. Kuhn
|
r4187 | |||
Mads Kiilerich
|
r7581 | process_pushed_raw_ids(git_revs) | ||
Mads Kiilerich
|
r7579 | |||
Mads Kiilerich
|
r7279 | return 0 | ||
Christian Oyarzun
|
r7682 | |||
# Almost exactly like Mercurial contrib/hg-ssh: | ||||
def rejectpush(ui, **kwargs): | ||||
"""Mercurial hook to be installed as pretxnopen and prepushkey for read-only repos""" | ||||
ex = get_hook_environment() | ||||
Mads Kiilerich
|
r8076 | ui.warn(safe_bytes("Push access to %r denied\n" % ex.repository)) | ||
Christian Oyarzun
|
r7682 | return 1 | ||