@@ -13,66 +13,87 @@
# 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
"""
Created on Aug 6, 2010
@author: marcink
import sys
from mercurial.cmdutil import revrange
from mercurial.node import nullrev
from rhodecode.lib import helpers as h
from rhodecode.lib.utils import action_logger
import os
from rhodecode.model import meta
from rhodecode.model.db import UserLog, User
def repo_size(ui, repo, hooktype=None, **kwargs):
if hooktype != 'changegroup':
return False
size_hg, size_root = 0, 0
for path, dirs, files in os.walk(repo.root):
if path.find('.hg') != -1:
for f in files:
size_hg += os.path.getsize(os.path.join(path, f))
else:
size_root += os.path.getsize(os.path.join(path, f))
size_hg_f = h.format_byte_size(size_hg)
size_root_f = h.format_byte_size(size_root)
size_total_f = h.format_byte_size(size_root + size_hg)
sys.stdout.write('Repository size .hg:%s repo:%s total:%s\n' \
% (size_hg_f, size_root_f, size_total_f))
user_action_mapper(ui, repo, hooktype, **kwargs)
def log_pull_action(ui, repo, **kwargs):
Logs user last pull action
:param ui:
:param repo:
extra_params = dict(repo.ui.configitems('rhodecode_extras'))
username = extra_params['username']
repository = extra_params['repository']
action = 'pull'
action_logger(username, action, repository, extra_params['ip'])
return 0
def user_action_mapper(ui, repo, hooktype=None, **kwargs):
def log_push_action(ui, repo, **kwargs):
Maps user last push action to new changeset id, from mercurial
:param hooktype:
try:
sa = meta.Session()
username = kwargs['url'].split(':')[-1]
user_log = sa.query(UserLog)\
.filter(UserLog.user == sa.query(User)\
.filter(User.username == username).one())\
.order_by(UserLog.user_log_id.desc()).first()
action = 'push:%s'
node = kwargs['node']
def get_revs(repo, rev_opt):
if rev_opt:
revs = revrange(repo, rev_opt)
if len(revs) == 0:
return (nullrev, nullrev)
return (max(revs), min(revs))
return (len(repo) - 1, 0)
stop, start = get_revs(repo, [node + ':'])
revs = (str(repo[r]) for r in xrange(start, stop + 1))
action = action % ','.join(revs)
if user_log and not user_log.revision:
user_log.revision = str(repo['tip'])
sa.add(user_log)
sa.commit()
except Exception, e:
sa.rollback()
raise
finally:
meta.Session.remove()
@@ -40,106 +40,118 @@ import logging
import traceback
log = logging.getLogger(__name__)
class SimpleHg(object):
def __init__(self, application, config):
self.application = application
self.config = config
#authenticate this mercurial request using
self.authenticate = AuthBasicAuthenticator('', authfunc)
self.ipaddr = '0.0.0.0'
self.repository = None
self.username = None
self.action = None
def __call__(self, environ, start_response):
if not is_mercurial(environ):
return self.application(environ, start_response)
proxy_key = 'HTTP_X_REAL_IP'
def_key = 'REMOTE_ADDR'
self.ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
#===================================================================
# AUTHENTICATE THIS MERCURIAL REQUEST
username = REMOTE_USER(environ)
if not username:
self.authenticate.realm = self.config['rhodecode_realm']
result = self.authenticate(environ)
if isinstance(result, str):
AUTH_TYPE.update(environ, 'basic')
REMOTE_USER.update(environ, result)
return result.wsgi_application(environ, start_response)
#=======================================================================
# GET REPOSITORY
repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
if repo_name.endswith('/'):
repo_name = repo_name.rstrip('/')
self.repository = repo_name
except:
log.error(traceback.format_exc())
return HTTPInternalServerError()(environ, start_response)
# CHECK PERMISSIONS FOR THIS REQUEST
action = self.__get_action(environ)
if action:
self.action = self.__get_action(environ)
if self.action:
username = self.__get_environ_user(environ)
user = self.__get_user(username)
self.username = user.username
#check permissions for this repository
if action == 'push':
if self.action == 'push':
if not HasPermissionAnyMiddleware('repository.write',
'repository.admin')\
(user, repo_name):
return HTTPForbidden()(environ, start_response)
#any other action need at least read permission
if not HasPermissionAnyMiddleware('repository.read',
'repository.write',
#log action
if action in ('push', 'pull', 'clone'):
ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
self.__log_user_action(user, action, repo_name, ipaddr)
self.extras = {'ip':self.ipaddr,
'username':self.username,
'action':self.action,
'repository':self.repository}
print self.extras
# MERCURIAL REQUEST HANDLING
environ['PATH_INFO'] = '/'#since we wrap into hgweb, reset the path
self.baseui = make_ui('db')
self.basepath = self.config['base_path']
self.repo_path = os.path.join(self.basepath, repo_name)
#quick check if that dir exists...
if check_repo_fast(repo_name, self.basepath):
return HTTPNotFound()(environ, start_response)
app = wsgiapplication(self.__make_app)
except RepoError, e:
if str(e).find('not found') != -1:
except Exception:
#invalidate cache on push
self.__invalidate_cache(repo_name)
messages = []
messages.append('thank you for using rhodecode')
return self.msg_wrapper(app, environ, start_response, messages)
return app(environ, start_response)
def msg_wrapper(self, app, environ, start_response, messages=[]):
Wrapper for custom messages that come out of mercurial respond messages
@@ -148,77 +160,79 @@ class SimpleHg(object):
:param app:
:param environ:
:param start_response:
def custom_messages(msg_list):
for msg in msg_list:
yield msg + '\n'
org_response = app(environ, start_response)
return chain(org_response, custom_messages(messages))
def __make_app(self):
hgserve = hgweb(str(self.repo_path), baseui=self.baseui)
return self.__load_web_settings(hgserve)
return self.__load_web_settings(hgserve, self.extras)
def __get_environ_user(self, environ):
return environ.get('REMOTE_USER')
def __get_user(self, username):
return UserModel().get_by_username(username, cache=True)
def __get_action(self, environ):
Maps mercurial request commands into a clone,pull or push command.
This should always return a valid command string
mapping = {'changegroup': 'pull',
'changegroupsubset': 'pull',
'stream_out': 'pull',
#'listkeys': 'pull',
'listkeys': 'pull',
'unbundle': 'push',
'pushkey': 'push', }
for qry in environ['QUERY_STRING'].split('&'):
if qry.startswith('cmd'):
cmd = qry.split('=')[-1]
if mapping.has_key(cmd):
return mapping[cmd]
return cmd
def __log_user_action(self, user, action, repo, ipaddr):
action_logger(user, action, repo, ipaddr)
def __invalidate_cache(self, repo_name):
"""we know that some change was made to repositories and we should
invalidate the cache to see the changes right away but only for
push requests"""
invalidate_cache('cached_repo_list')
invalidate_cache('full_changelog', repo_name)
def __load_web_settings(self, hgserve):
def __load_web_settings(self, hgserve, extras={}):
#set the global ui for hgserve instance passed
hgserve.repo.ui = self.baseui
hgrc = os.path.join(self.repo_path, '.hg', 'hgrc')
#inject some additional parameters that will be available in ui
#for hooks
for k, v in extras.items():
hgserve.repo.ui.setconfig('rhodecode_extras', k, v)
repoui = make_ui('file', hgrc, False)
if repoui:
#overwrite our ui instance with the section from hgrc file
for section in ui_sections:
for k, v in repoui.configitems(section):
hgserve.repo.ui.setconfig(section, k, v)
return hgserve
@@ -26,24 +26,25 @@ from UserDict import DictMixin
from mercurial import ui, config, hg
from mercurial.error import RepoError
from rhodecode.model.caching_query import FromCache
from rhodecode.model.db import Repository, User, RhodeCodeUi, RhodeCodeSettings, \
UserLog
from rhodecode.model.repo import RepoModel
from rhodecode.model.user import UserModel
from vcs.backends.base import BaseChangeset
from vcs.backends.git import GitRepository
from vcs.backends.hg import MercurialRepository
from vcs.utils.lazy import LazyProperty
import datetime
import logging
def get_repo_slug(request):
return request.environ['pylons.routes_dict'].get('repo_name')
def is_mercurial(environ):
@@ -68,46 +69,46 @@ def is_git(environ):
def action_logger(user, action, repo, ipaddr, sa=None):
Action logger for various action made by users
if not sa:
if hasattr(user, 'user_id'):
user_id = user.user_id
user_obj = user
elif isinstance(user, basestring):
user_id = UserModel(sa).get_by_username(user, cache=False).user_id
user_obj = UserModel(sa).get_by_username(user, cache=False)
raise Exception('You have to provide user object or username')
repo_name = repo.lstrip('/')
user_log = UserLog()
user_log.user_id = user_id
user_log.user_id = user_obj.user_id
user_log.action = action
user_log.repository_name = repo_name
user_log.repository = RepoModel(sa).get(repo_name, cache=False)
user_log.action_date = datetime.datetime.now()
user_log.user_ip = ipaddr
log.info('Adding user %s, action %s on %s',
user.username, action, repo)
user_obj.username, action, repo)
log.error('could not log user action:%s', str(e))
def get_repos(path, recursive=False, initial=False):
Scans given path for repos and return (name,(type,path)) tuple
:param prefix:
:param path:
:param recursive:
:param initial:
from vcs.utils.helpers import get_scm
from vcs.exceptions import VCSError
Status change: