@@ -420,64 +420,65 @@ def make_map(config):
rmap.connect('files_rawfile_home',
'/{repo_name:.*}/rawfile/{revision}/{f_path:.*}',
controller='files', action='rawfile', revision='tip',
f_path='', conditions=dict(function=check_repo))
rmap.connect('files_raw_home',
'/{repo_name:.*}/raw/{revision}/{f_path:.*}',
controller='files', action='raw', revision='tip', f_path='',
conditions=dict(function=check_repo))
rmap.connect('files_annotate_home',
'/{repo_name:.*}/annotate/{revision}/{f_path:.*}',
controller='files', action='annotate', revision='tip',
rmap.connect('files_edit_home',
'/{repo_name:.*}/edit/{revision}/{f_path:.*}',
controller='files', action='edit', revision='tip',
rmap.connect('files_add_home',
'/{repo_name:.*}/add/{revision}/{f_path:.*}',
controller='files', action='add', revision='tip',
rmap.connect('files_archive_home', '/{repo_name:.*}/archive/{fname}',
controller='files', action='archivefile',
rmap.connect('files_nodelist_home',
'/{repo_name:.*}/nodelist/{revision}/{f_path:.*}',
controller='files', action='nodelist',
rmap.connect('repo_settings_delete', '/{repo_name:.*}/settings',
controller='settings', action="delete",
conditions=dict(method=["DELETE"], function=check_repo))
rmap.connect('repo_settings_update', '/{repo_name:.*}/settings',
controller='settings', action="update",
conditions=dict(method=["PUT"], function=check_repo))
rmap.connect('repo_settings_home', '/{repo_name:.*}/settings',
controller='settings', action='index',
rmap.connect('repo_fork_create_home', '/{repo_name:.*}/fork',
controller='settings', action='fork_create',
controller='forks', action='fork_create',
conditions=dict(function=check_repo, method=["POST"]))
rmap.connect('repo_fork_home', '/{repo_name:.*}/fork',
controller='settings', action='fork',
controller='forks', action='fork',
rmap.connect('repo_forks_home', '/{repo_name:.*}/forks',
controller='forks', action='forks',
rmap.connect('repo_followers_home', '/{repo_name:.*}/followers',
controller='followers', action='followers',
return rmap
# -*- coding: utf-8 -*-
"""
rhodecode.controllers.admin.repos
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Admin controller for RhodeCode
:created_on: Apr 7, 2010
:author: marcink
:copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
:license: GPLv3, see COPYING for more details.
# 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/>.
import logging
import traceback
import formencode
from formencode import htmlfill
from paste.httpexceptions import HTTPInternalServerError
from pylons import request, response, session, tmpl_context as c, url
from pylons.controllers.util import abort, redirect
from pylons import request, session, tmpl_context as c, url
from pylons.controllers.util import redirect
from pylons.i18n.translation import _
from sqlalchemy.exc import IntegrityError
from rhodecode.lib import helpers as h
from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
HasPermissionAnyDecorator
from rhodecode.lib.base import BaseController, render
from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
from rhodecode.lib.helpers import get_token
from rhodecode.model.meta import Session
from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup
from rhodecode.model.forms import RepoForm
from rhodecode.model.scm import ScmModel
from rhodecode.model.repo import RepoModel
log = logging.getLogger(__name__)
class ReposController(BaseController):
REST Controller styled on the Atom Publishing Protocol"""
# To properly map this controller, ensure your config/routing.py
# file has a resource setup:
# map.resource('repo', 'repos')
@LoginRequired()
@HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
def __before__(self):
c.admin_user = session.get('admin_user')
c.admin_username = session.get('admin_username')
super(ReposController, self).__before__()
def __load_defaults(self):
c.repo_groups = RepoGroup.groups_choices()
c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
repo_model = RepoModel()
c.users_array = repo_model.get_users_js()
c.users_groups_array = repo_model.get_users_groups_js()
def __load_data(self, repo_name=None):
Load defaults settings for edit, and update
:param repo_name:
self.__load_defaults()
c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
repo = db_repo.scm_instance
if c.repo_info is None:
h.flash(_('%s repository is not mapped to db perhaps'
' it was created or renamed from the filesystem'
' please run the application again'
' in order to rescan repositories') % repo_name,
category='error')
return redirect(url('repos'))
c.default_user_id = User.get_by_username('default').user_id
c.in_public_journal = UserFollowing.query()\
.filter(UserFollowing.user_id == c.default_user_id)\
.filter(UserFollowing.follows_repository == c.repo_info).scalar()
if c.repo_info.stats:
last_rev = c.repo_info.stats.stat_on_revision
else:
last_rev = 0
c.stats_revision = last_rev
c.repo_last_rev = repo.count() - 1 if repo.revisions else 0
if last_rev == 0 or c.repo_last_rev == 0:
c.stats_percentage = 0
c.stats_percentage = '%.2f' % ((float((last_rev)) /
c.repo_last_rev) * 100)
defaults = RepoModel()._get_defaults(repo_name)
return defaults
@HasPermissionAllDecorator('hg.admin')
def index(self, format='html'):
"""GET /repos: All items in the collection"""
# url('repos')
c.repos_list = ScmModel().get_repos(Repository.query()
.order_by(Repository.repo_name)
.all(), sort_key='name_sort')
return render('admin/repos/repos.html')
def create(self):
POST /repos: Create a new item"""
form_result = {}
try:
form_result = RepoForm(repo_groups=c.repo_groups_choices)()\
.to_python(dict(request.POST))
repo_model.create(form_result, self.rhodecode_user)
RepoModel().create(form_result, self.rhodecode_user)
if form_result['clone_uri']:
h.flash(_('created repository %s from %s') \
% (form_result['repo_name'], form_result['clone_uri']),
category='success')
h.flash(_('created repository %s') % form_result['repo_name'],
if request.POST.get('user_created'):
#created by regular non admin user
# created by regular non admin user
action_logger(self.rhodecode_user, 'user_created_repo',
form_result['repo_name_full'], '', self.sa)
action_logger(self.rhodecode_user, 'admin_created_repo',
Session().commit()
except formencode.Invalid, errors:
c.new_repo = errors.value['repo_name']
r = render('admin/repos/repo_add_create_repository.html')
r = render('admin/repos/repo_add.html')
return htmlfill.render(
r,
defaults=errors.value,
errors=errors.error_dict or {},
prefix_error=False,
encoding="UTF-8")
except Exception:
log.error(traceback.format_exc())
msg = _('error occurred during creation of repository %s') \
% form_result.get('repo_name')
h.flash(msg, category='error')
return redirect(url('home'))
def new(self, format='html'):
"""GET /repos/new: Form to create a new item"""
new_repo = request.GET.get('repo', '')
c.new_repo = repo_name_slug(new_repo)
return render('admin/repos/repo_add.html')
def update(self, repo_name):
PUT /repos/repo_name: Update an existing item"""
# Forms posted to this method should contain a hidden field:
# <input type="hidden" name="_method" value="PUT" />
# Or using helpers:
# h.form(url('repo', repo_name=ID),
# method='put')
# url('repo', repo_name=ID)
changed_name = repo_name
_form = RepoForm(edit=True, old_data={'repo_name': repo_name},
repo_groups=c.repo_groups_choices)()
form_result = _form.to_python(dict(request.POST))
repo = repo_model.update(repo_name, form_result)
invalidate_cache('get_repo_cached_%s' % repo_name)
h.flash(_('Repository %s updated successfully' % repo_name),
changed_name = repo.repo_name
action_logger(self.rhodecode_user, 'admin_updated_repo',
changed_name, '', self.sa)
defaults = self.__load_data(repo_name)
defaults.update(errors.value)
render('admin/repos/repo_edit.html'),
defaults=defaults,
h.flash(_('error occurred during update of repository %s') \
% repo_name, category='error')
return redirect(url('edit_repo', repo_name=changed_name))
def delete(self, repo_name):
DELETE /repos/repo_name: Delete an existing item"""
# <input type="hidden" name="_method" value="DELETE" />
# method='delete')
repo = repo_model.get_by_repo_name(repo_name)
if not repo:
' it was moved or renamed from the filesystem'
action_logger(self.rhodecode_user, 'admin_deleted_repo',
repo_name, '', self.sa)
repo_model.delete(repo)
h.flash(_('deleted repository %s') % repo_name, category='success')
except IntegrityError, e:
if e.message.find('repositories_fork_id_fkey'):
h.flash(_('Cannot delete %s it still contains attached '
'forks') % repo_name,
category='warning')
h.flash(_('An error occurred during '
'deletion of %s') % repo_name,
except Exception, e:
h.flash(_('An error occurred during deletion of %s') % repo_name,
def delete_perm_user(self, repo_name):
DELETE an existing repository permission user
repo_model.delete_perm_user(request.POST, repo_name)
h.flash(_('An error occurred during deletion of repository user'),
raise HTTPInternalServerError()
def delete_perm_users_group(self, repo_name):
DELETE an existing repository permission users group
repo_model.delete_perm_users_group(request.POST, repo_name)
h.flash(_('An error occurred during deletion of repository'
' users groups'),
rhodecode.controllers.forks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
forks controller for rhodecode
:created_on: Apr 23, 2011
from pylons import tmpl_context as c, request
from pylons import tmpl_context as c, request, url
import rhodecode.lib.helpers as h
from rhodecode.lib.helpers import Page
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \
NotAnonymous
from rhodecode.lib.base import BaseRepoController, render
from rhodecode.model.db import Repository, User, UserFollowing
from rhodecode.model.db import Repository, RepoGroup, UserFollowing, User
from rhodecode.model.forms import RepoForkForm
class ForksController(BaseRepoController):
@HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
'repository.admin')
super(ForksController, self).__before__()
# add prefix to fork
defaults['repo_name'] = 'fork-' + defaults['repo_name']
def forks(self, repo_name):
p = int(request.params.get('page', 1))
repo_id = c.rhodecode_db_repo.repo_id
d = Repository.get_repo_forks(repo_id)
c.forks_pager = Page(d, page=p, items_per_page=20)
c.forks_data = render('/forks/forks_data.html')
if request.environ.get('HTTP_X_PARTIAL_XHR'):
return c.forks_data
return render('/forks/forks.html')
@NotAnonymous()
def fork(self, repo_name):
c.repo_info = Repository.get_by_repo_name(repo_name)
if not c.repo_info:
' it was created or renamed from the file system'
render('forks/fork.html'),
encoding="UTF-8",
force_defaults=False
)
def fork_create(self, repo_name):
_form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type},
repo_groups=c.repo_groups_choices,)()
# add org_path of repo so we can do a clone from it later
form_result['org_path'] = c.repo_info.repo_name
# create fork is done sometimes async on celery, db transaction
# management is handled there.
RepoModel().create_fork(form_result, self.rhodecode_user)
h.flash(_('forked %s repository as %s') \
% (repo_name, form_result['repo_name']),
h.flash(_('An error occurred during repository forking %s') %
repo_name, category='error')
rhodecode.controllers.journal
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Journal controller for pylons
:created_on: Nov 21, 2010
from itertools import groupby
from sqlalchemy import or_
from sqlalchemy.orm import joinedload, make_transient
from sqlalchemy.orm import joinedload
from webhelpers.paginate import Page
from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
from paste.httpexceptions import HTTPBadRequest
from pylons import request, tmpl_context as c, response, url
from rhodecode.lib.auth import LoginRequired, NotAnonymous
from rhodecode.model.db import UserLog, UserFollowing
class JournalController(BaseController):
super(JournalController, self).__before__()
self.rhodecode_user = self.rhodecode_user
self.title = _('%s public journal %s feed') % (c.rhodecode_name, '%s')
self.language = 'en-us'
self.ttl = "5"
self.feed_nr = 20
def index(self):
# Return a rendered template
c.following = self.sa.query(UserFollowing)\
.filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
.options(joinedload(UserFollowing.follows_repository))\
.all()
journal = self._get_journal_data(c.following)
c.journal_pager = Page(journal, page=p, items_per_page=20)
c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
c.journal_data = render('journal/journal_data.html')
return c.journal_data
return render('journal/journal.html')
def _get_daily_aggregate(self, journal):
groups = []
for k, g in groupby(journal, lambda x: x.action_as_day):
user_group = []
for k2, g2 in groupby(list(g), lambda x: x.user.email):
l = list(g2)
user_group.append((l[0].user, l))
groups.append((k, user_group,))
return groups
def _get_journal_data(self, following_repos):
repo_ids = [x.follows_repository.repo_id for x in following_repos
if x.follows_repository is not None]
user_ids = [x.follows_user.user_id for x in following_repos
if x.follows_user is not None]
filtering_criterion = None
if repo_ids and user_ids:
filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
UserLog.user_id.in_(user_ids))
if repo_ids and not user_ids:
filtering_criterion = UserLog.repository_id.in_(repo_ids)
if not repo_ids and user_ids:
filtering_criterion = UserLog.user_id.in_(user_ids)
if filtering_criterion is not None:
journal = self.sa.query(UserLog)\
.options(joinedload(UserLog.user))\
.options(joinedload(UserLog.repository))\
.filter(filtering_criterion)\
.order_by(UserLog.action_date.desc())
journal = []
return journal
def toggle_following(self):
cur_token = request.POST.get('auth_token')
token = h.get_token()
if cur_token == token:
user_id = request.POST.get('follows_user_id')
if user_id:
self.scm_model.toggle_following_user(user_id,
self.rhodecode_user.user_id)
return 'ok'
except:
raise HTTPBadRequest()
repo_id = request.POST.get('follows_repo_id')
if repo_id:
self.scm_model.toggle_following_repo(repo_id,
log.debug('token mismatch %s vs %s', cur_token, token)
def public_journal(self):
return render('journal/public_journal.html')
@LoginRequired(api_access=True)
def public_journal_atom(self):
Produce an atom-1.0 feed via feedgenerator module
feed = Atom1Feed(title=self.title % 'atom',
link=url('public_journal_atom', qualified=True),
description=_('Public journal'),
language=self.language,
ttl=self.ttl)
for entry in journal[:self.feed_nr]:
#tmpl = h.action_parser(entry)[0]
rhodecode.controllers.settings
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Settings controller for rhodecode
:created_on: Jun 30, 2010
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator, \
HasRepoPermissionAnyDecorator, NotAnonymous
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator
from rhodecode.lib.utils import invalidate_cache, action_logger
from rhodecode.model.forms import RepoSettingsForm, RepoForkForm
from rhodecode.model.forms import RepoSettingsForm
from rhodecode.model.db import RepoGroup
class SettingsController(BaseRepoController):
super(SettingsController, self).__before__()
@HasRepoPermissionAllDecorator('repository.admin')
def index(self, repo_name):
c.repo_info = repo = repo_model.get_by_repo_name(repo_name)
render('settings/repo_settings.html'),
_form = RepoSettingsForm(edit=True,
old_data={'repo_name': repo_name},
repo_model.update(repo_name, form_result)
changed_name = form_result['repo_name_full']
action_logger(self.rhodecode_user, 'user_updated_repo',
c.repo_info = repo_model.get_by_repo_name(repo_name)
errors.value.update({'user': c.repo_info.user.username})
return redirect(url('repo_settings_home', repo_name=changed_name))
"""DELETE /repos/repo_name: Delete an existing item"""
# h.form(url('repo_settings_delete', repo_name=ID),
# url('repo_settings_delete', repo_name=ID)
action_logger(self.rhodecode_user, 'user_deleted_repo',
return render('settings/repo_fork.html')
_form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type})()
form_result.update({'repo_name': repo_name})
repo_model.create_fork(form_result, self.rhodecode_user)
% (repo_name, form_result['fork_name']),
action_logger(self.rhodecode_user,
'user_forked_repo:%s' % form_result['fork_name'],
c.new_repo = errors.value['fork_name']
r = render('settings/repo_fork.html')
rhodecode.lib.celerylib.tasks
RhodeCode task modules, containing all task that suppose to be run
by celery daemon
:created_on: Oct 6, 2010
from celery.decorators import task
import os
from os.path import join as jn
from time import mktime
from operator import itemgetter
from string import lower
from pylons import config, url
from vcs import get_backend
from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, safe_str
from rhodecode.lib.celerylib import run_task, locked_task, str2bool, \
__get_lockkey, LockHeld, DaemonLock
from rhodecode.lib.helpers import person
from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
from rhodecode.lib.utils import add_cache
from rhodecode.lib.utils import add_cache, action_logger
from rhodecode.lib.compat import json, OrderedDict
from rhodecode.model import init_model
from rhodecode.model import meta
from rhodecode.model.db import RhodeCodeUi, Statistics, Repository, User
from vcs.backends import get_repo
from rhodecode.model.db import Statistics, Repository, User
from sqlalchemy import engine_from_config
add_cache(config)
__all__ = ['whoosh_index', 'get_commits_stats',
'reset_user_password', 'send_email']
CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
def get_session():
if CELERY_ON:
engine = engine_from_config(config, 'sqlalchemy.db1.')
init_model(engine)
sa = meta.Session()
return sa
def get_logger(cls):
log = cls.get_logger()
return log
def get_repos_path():
sa = get_session()
q = sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
return q.ui_value
@task(ignore_result=True)
@locked_task
def whoosh_index(repo_location, full_index):
from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
#log = whoosh_index.get_logger()
index_location = config['index_dir']
WhooshIndexingDaemon(index_location=index_location,
repo_location=repo_location, sa=get_session())\
.run(full_index=full_index)
def get_commits_stats(repo_name, ts_min_y, ts_max_y):
log = get_logger(get_commits_stats)
lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
ts_max_y)
lockkey_path = config['here']
log.info('running task with lockkey %s', lockkey)
lock = l = DaemonLock(file_=jn(lockkey_path, lockkey))
#for js data compatibilty cleans the key for person from '
# for js data compatibilty cleans the key for person from '
akc = lambda k: person(k).replace('"', "")
co_day_auth_aggr = {}
commits_by_day_aggregate = {}
repos_path = get_repos_path()
repo = get_repo(safe_str(os.path.join(repos_path, repo_name)))
repo = Repository.get_by_repo_name(repo_name).scm_instance
repo_size = len(repo.revisions)
#return if repo have no revisions
if repo_size < 1:
lock.release()
return True
skip_date_limit = True
parse_limit = int(config['app_conf'].get('commit_parse_limit'))
last_cs = None
timegetter = itemgetter('time')
dbrepo = sa.query(Repository)\
.filter(Repository.repo_name == repo_name).scalar()
cur_stats = sa.query(Statistics)\
.filter(Statistics.repository == dbrepo).scalar()
if cur_stats is not None:
last_rev = cur_stats.stat_on_revision
if last_rev == repo.get_changeset().revision and repo_size > 1:
#pass silently without any work if we're not on first revision or
#current state of parsing revision(from db marker) is the
#last revision
# pass silently without any work if we're not on first revision or
# current state of parsing revision(from db marker) is the
# last revision
if cur_stats:
commits_by_day_aggregate = OrderedDict(json.loads(
cur_stats.commit_activity_combined))
co_day_auth_aggr = json.loads(cur_stats.commit_activity)
log.debug('starting parsing %s', parse_limit)
lmktime = mktime
last_rev = last_rev + 1 if last_rev > 0 else last_rev
for cs in repo[last_rev:last_rev + parse_limit]:
last_cs = cs # remember last parsed changeset
k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
if akc(cs.author) in co_day_auth_aggr:
l = [timegetter(x) for x in
co_day_auth_aggr[akc(cs.author)]['data']]
time_pos = l.index(k)
except ValueError:
time_pos = False
if time_pos >= 0 and time_pos is not False:
datadict = \
co_day_auth_aggr[akc(cs.author)]['data'][time_pos]
datadict["commits"] += 1
datadict["added"] += len(cs.added)
datadict["changed"] += len(cs.changed)
datadict["removed"] += len(cs.removed)
if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
datadict = {"time": k,
"commits": 1,
"added": len(cs.added),
"changed": len(cs.changed),
"removed": len(cs.removed),
}
co_day_auth_aggr[akc(cs.author)]['data']\
.append(datadict)
@@ -210,190 +204,203 @@ def get_commits_stats(repo_name, ts_min_
commits_by_day_aggregate[k] = 1
overview_data = sorted(commits_by_day_aggregate.items(),
key=itemgetter(0))
if not co_day_auth_aggr:
co_day_auth_aggr[akc(repo.contact)] = {
"label": akc(repo.contact),
"data": [0, 1],
"schema": ["commits"],
stats = cur_stats if cur_stats else Statistics()
stats.commit_activity = json.dumps(co_day_auth_aggr)
stats.commit_activity_combined = json.dumps(overview_data)
log.debug('last revison %s', last_rev)
leftovers = len(repo.revisions[last_rev:])
log.debug('revisions to parse %s', leftovers)
if last_rev == 0 or leftovers < parse_limit:
log.debug('getting code trending stats')
stats.languages = json.dumps(__get_codes_stats(repo_name))
stats.repository = dbrepo
stats.stat_on_revision = last_cs.revision if last_cs else 0
sa.add(stats)
sa.commit()
sa.rollback()
return False
#final release
#execute another task if celery is enabled
if len(repo.revisions) > 1 and CELERY_ON:
run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
except LockHeld:
log.info('LockHeld')
return 'Task with key %s already running' % lockkey
def send_password_link(user_email):
from rhodecode.model.notification import EmailNotificationModel
log = get_logger(send_password_link)
user = User.get_by_email(user_email)
if user:
log.debug('password reset user found %s' % user)
link = url('reset_password_confirmation', key=user.api_key,
qualified=True)
reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
body = EmailNotificationModel().get_email_tmpl(reg_type,
**{'user':user.short_contact,
'reset_url':link})
log.debug('sending email')
run_task(send_email, user_email,
_("password reset link"), body)
log.info('send new password mail to %s', user_email)
log.debug("password reset email %s not found" % user_email)
def reset_user_password(user_email):
log = get_logger(reset_user_password)
from rhodecode.lib import auth
new_passwd = auth.PasswordGenerator().gen_password(8,
auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
user.password = auth.get_crypt_password(new_passwd)
user.api_key = auth.generate_api_key(user.username)
sa.add(user)
log.info('change password for %s', user_email)
if new_passwd is None:
raise Exception('unable to generate new password')
'Your new password',
'Your new RhodeCode password:%s' % (new_passwd))
log.error('Failed to update user password')
def send_email(recipients, subject, body, html_body=''):
Sends an email with defined parameters from the .ini files.
:param recipients: list of recipients, it this is empty the defined email
address from field 'email_to' is used instead
:param subject: subject of the mail
:param body: body of the mail
:param html_body: html version of body
log = get_logger(send_email)
email_config = config
subject = "%s %s" % (email_config.get('email_prefix'), subject)
if not recipients:
# if recipients are not defined we send to email_config + all admins
admins = [u.email for u in User.query()
.filter(User.admin == True).all()]
recipients = [email_config.get('email_to')] + admins
mail_from = email_config.get('app_email_from', 'RhodeCode')
user = email_config.get('smtp_username')
passwd = email_config.get('smtp_password')
mail_server = email_config.get('smtp_server')
mail_port = email_config.get('smtp_port')
tls = str2bool(email_config.get('smtp_use_tls'))
ssl = str2bool(email_config.get('smtp_use_ssl'))
debug = str2bool(config.get('debug'))
smtp_auth = email_config.get('smtp_auth')
m = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth,
mail_port, ssl, tls, debug=debug)
m.send(recipients, subject, body, html_body)
log.error('Mail sending failed')
def create_repo_fork(form_data, cur_user):
Creates a fork of repository using interval VCS methods
:param form_data:
:param cur_user:
log = get_logger(create_repo_fork)
Session = get_session()
base_path = Repository.base_path()
RepoModel(Session).create(form_data, cur_user, just_db=True, fork=True)
alias = form_data['repo_type']
org_repo_name = form_data['org_path']
source_repo_path = os.path.join(base_path, org_repo_name)
destination_fork_path = os.path.join(base_path, form_data['repo_name_full'])
repo_model = RepoModel(get_session())
repo_model.create(form_data, cur_user, just_db=True, fork=True)
repo_name = form_data['repo_name']
repo_path = os.path.join(repos_path, repo_name)
repo_fork_path = os.path.join(repos_path, form_data['fork_name'])
log.info('creating repo fork %s as %s', repo_name, repo_path)
log.info('creating fork of %s as %s', source_repo_path,
destination_fork_path)
backend = get_backend(alias)
backend(str(repo_fork_path), create=True, src_url=str(repo_path))
backend(safe_str(destination_fork_path), create=True,
src_url=safe_str(source_repo_path))
action_logger(cur_user, 'user_forked_repo:%s' % org_repo_name,
org_repo_name, '', Session)
# finally commit at latest possible stage
Session.commit()
def __get_codes_stats(repo_name):
tip = repo.get_changeset()
code_stats = {}
def aggregate(cs):
for f in cs[2]:
ext = lower(f.extension)
if ext in LANGUAGES_EXTENSIONS_MAP.keys() and not f.is_binary:
if ext in code_stats:
code_stats[ext] += 1
code_stats[ext] = 1
map(aggregate, tip.walk('/'))
return code_stats or {}
rhodecode.lib.hooks
~~~~~~~~~~~~~~~~~~~
Hooks runned by rhodecode
:created_on: Aug 6, 2010
import sys
from mercurial.scmutil import revrange
from mercurial.node import nullrev
from rhodecode.lib.utils import action_logger
def repo_size(ui, repo, hooktype=None, **kwargs):
"""Presents size of repository after push
Presents size of repository after push
:param ui:
:param repo:
:param hooktype:
if hooktype != 'changegroup':
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))
except OSError:
pass
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))
def log_pull_action(ui, repo, **kwargs):
"""Logs user last pull action
Logs user last pull action
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'])
action_logger(username, action, repository, extra_params['ip'],
commit=True)
return 0
def log_push_action(ui, repo, **kwargs):
"""Maps user last push action to new changeset id, from mercurial
Maps user last push action to new changeset id, from mercurial
action = extra_params['action'] + ':%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)
@@ -48,147 +48,148 @@ from vcs.exceptions import VCSError
from rhodecode.lib.caching_query import FromCache
from rhodecode.model.db import Repository, User, RhodeCodeUi, \
UserLog, RepoGroup, RhodeCodeSetting
def recursive_replace(str_, replace=' '):
"""Recursive replace of given sign to just one instance
:param str_: given string
:param replace: char to find and replace multiple instances
Examples::
>>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
'Mighty-Mighty-Bo-sstones'
if str_.find(replace * 2) == -1:
return str_
str_ = str_.replace(replace * 2, replace)
return recursive_replace(str_, replace)
def repo_name_slug(value):
"""Return slug of name of repository
This function is called on each creation/modification
of repository to prevent bad names in repo
slug = remove_formatting(value)
slug = strip_tags(slug)
for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
slug = slug.replace(c, '-')
slug = recursive_replace(slug, '-')
slug = collapse(slug, '-')
return slug
def get_repo_slug(request):
return request.environ['pylons.routes_dict'].get('repo_name')
def action_logger(user, action, repo, ipaddr='', sa=None):
def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
Action logger for various actions made by users
:param user: user that made this action, can be a unique username string or
object containing user_id attribute
:param action: action to log, should be on of predefined unique actions for
easy translations
:param repo: string name of repository or object containing repo_id,
that action was made on
:param ipaddr: optional ip address from what the action was made
:param sa: optional sqlalchemy session
if not sa:
if hasattr(user, 'user_id'):
user_obj = user
elif isinstance(user, basestring):
user_obj = User.get_by_username(user)
raise Exception('You have to provide user object or username')
if hasattr(repo, 'repo_id'):
repo_obj = Repository.get(repo.repo_id)
repo_name = repo_obj.repo_name
elif isinstance(repo, basestring):
repo_name = repo.lstrip('/')
repo_obj = Repository.get_by_repo_name(repo_name)
raise Exception('You have to provide repository to action logger')
user_log = UserLog()
user_log.user_id = user_obj.user_id
user_log.action = action
user_log.repository_id = repo_obj.repo_id
user_log.repository_name = repo_name
user_log.action_date = datetime.datetime.now()
user_log.user_ip = ipaddr
sa.add(user_log)
log.info('Adding user %s, action %s on %s', user_obj, action, repo)
if commit:
raise
def get_repos(path, recursive=False):
Scans given path for repos and return (name,(type,path)) tuple
:param path: path to scann for repositories
:param recursive: recursive search and return names with subdirs in front
if path.endswith(os.sep):
#remove ending slash for better results
path = path[:-1]
def _get_repos(p):
if not os.access(p, os.W_OK):
return
for dirpath in os.listdir(p):
if os.path.isfile(os.path.join(p, dirpath)):
continue
cur_path = os.path.join(p, dirpath)
scm_info = get_scm(cur_path)
yield scm_info[1].split(path)[-1].lstrip(os.sep), scm_info
except VCSError:
if not recursive:
#check if this dir containts other repos for recursive scan
rec_path = os.path.join(p, dirpath)
if os.path.isdir(rec_path):
for inner_scm in _get_repos(rec_path):
yield inner_scm
return _get_repos(path)
def is_valid_repo(repo_name, base_path):
Returns True if given path is a valid repository False otherwise
:param base_path:
:return True: if given path is a valid repository
full_path = os.path.join(base_path, repo_name)
get_scm(full_path)
@@ -140,218 +140,202 @@ def ValidReposGroup(edit, old_data):
old_data.get('group_id')).group_name
if old_gname != group_name or not edit:
# check filesystem
gr = RepoGroup.query().filter(RepoGroup.group_name == slug)\
.filter(RepoGroup.group_parent_id == group_parent_id).scalar()
if gr:
e_dict = {'group_name':_('This group already exists')}
raise formencode.Invalid('', value, state,
error_dict=e_dict)
return _ValidReposGroup
class ValidPassword(formencode.validators.FancyValidator):
def to_python(self, value, state):
if value:
if value.get('password'):
value['password'] = get_crypt_password(value['password'])
except UnicodeEncodeError:
e_dict = {'password':_('Invalid characters in password')}
raise formencode.Invalid('', value, state, error_dict=e_dict)
if value.get('password_confirmation'):
value['password_confirmation'] = \
get_crypt_password(value['password_confirmation'])
e_dict = {'password_confirmation':_('Invalid characters in password')}
if value.get('new_password'):
value['new_password'] = \
get_crypt_password(value['new_password'])
e_dict = {'new_password':_('Invalid characters in password')}
return value
class ValidPasswordsMatch(formencode.validators.FancyValidator):
def validate_python(self, value, state):
pass_val = value.get('password') or value.get('new_password')
if pass_val != value['password_confirmation']:
e_dict = {'password_confirmation':
_('Passwords do not match')}
class ValidAuth(formencode.validators.FancyValidator):
messages = {
'invalid_password':_('invalid password'),
'invalid_login':_('invalid user name'),
'disabled_account':_('Your account is disabled')
# error mapping
e_dict = {'username':messages['invalid_login'],
'password':messages['invalid_password']}
e_dict_disable = {'username':messages['disabled_account']}
password = value['password']
username = value['username']
user = User.get_by_username(username)
if authenticate(username, password):
if user and user.active is False:
log.warning('user %s is disabled', username)
raise formencode.Invalid(self.message('disabled_account',
state=State_obj),
value, state,
error_dict=self.e_dict_disable)
log.warning('user %s not authenticated', username)
raise formencode.Invalid(self.message('invalid_password',
state=State_obj), value, state,
error_dict=self.e_dict)
class ValidRepoUser(formencode.validators.FancyValidator):
User.query().filter(User.active == True)\
.filter(User.username == value).one()
raise formencode.Invalid(_('This username is not valid'),
value, state)
def ValidRepoName(edit, old_data):
class _ValidRepoName(formencode.validators.FancyValidator):
repo_name = value.get('repo_name')
slug = repo_name_slug(repo_name)
if slug in [ADMIN_PREFIX, '']:
e_dict = {'repo_name': _('This repository name is disallowed')}
if value.get('repo_group'):
gr = RepoGroup.get(value.get('repo_group'))
group_path = gr.full_path
# value needs to be aware of group name in order to check
# db key This is an actual just the name to store in the
# database
repo_name_full = group_path + RepoGroup.url_sep() + repo_name
group_path = ''
repo_name_full = repo_name
value['repo_name_full'] = repo_name_full
rename = old_data.get('repo_name') != repo_name_full
create = not edit
if rename or create:
if group_path != '':
if RepoModel().get_by_repo_name(repo_name_full,):
e_dict = {'repo_name':_('This repository already '
'exists in a group "%s"') %
gr.group_name}
elif RepoGroup.get_by_group_name(repo_name_full):
e_dict = {'repo_name':_('There is a group with this'
' name already "%s"') %
repo_name_full}
elif RepoModel().get_by_repo_name(repo_name_full):
e_dict = {'repo_name':_('This repository '
'already exists')}
return _ValidRepoName
def ValidForkName():
class _ValidForkName(formencode.validators.FancyValidator):
repo_name = value.get('fork_name')
if RepoModel().get_by_repo_name(repo_name):
e_dict = {'fork_name':_('This repository '
return _ValidForkName
def ValidForkName(*args, **kwargs):
return ValidRepoName(*args, **kwargs)
def SlugifyName():
class _SlugifyName(formencode.validators.FancyValidator):
return repo_name_slug(value)
return _SlugifyName
def ValidCloneUri():
from mercurial.httprepo import httprepository, httpsrepository
from rhodecode.lib.utils import make_ui
class _ValidCloneUri(formencode.validators.FancyValidator):
if not value:
elif value.startswith('https'):
httpsrepository(make_ui('db'), value).capabilities
raise formencode.Invalid(_('invalid clone url'), value,
state)
elif value.startswith('http'):
httprepository(make_ui('db'), value).capabilities
raise formencode.Invalid(_('Invalid clone url, provide a '
'valid clone http\s url'), value,
return _ValidCloneUri
def ValidForkType(old_data):
class _ValidForkType(formencode.validators.FancyValidator):
if old_data['repo_type'] != value:
raise formencode.Invalid(_('Fork have to be the same '
'type as original'), value, state)
@@ -468,214 +452,217 @@ class AttrLoginValidator(formencode.vali
if not value or not isinstance(value, (str, unicode)):
raise formencode.Invalid(_("The LDAP Login attribute of the CN "
"must be specified - this is the name "
"of the attribute that is equivalent "
"to 'username'"),
#===============================================================================
# FORMS
class LoginForm(formencode.Schema):
allow_extra_fields = True
filter_extra_fields = True
username = UnicodeString(
strip=True,
min=1,
not_empty=True,
messages={
'empty':_('Please enter a login'),
'tooShort':_('Enter a value %(min)i characters long or more')}
password = UnicodeString(
min=3,
'empty':_('Please enter a password'),
'tooShort':_('Enter %(min)i characters or more')}
chained_validators = [ValidAuth]
def UserForm(edit=False, old_data={}):
class _UserForm(formencode.Schema):
username = All(UnicodeString(strip=True, min=1, not_empty=True),
ValidUsername(edit, old_data))
if edit:
new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=False))
admin = StringBoolean(if_missing=False)
password = All(UnicodeString(strip=True, min=6, not_empty=True))
active = StringBoolean(if_missing=False)
name = UnicodeString(strip=True, min=1, not_empty=True)
lastname = UnicodeString(strip=True, min=1, not_empty=True)
email = All(Email(not_empty=True), UniqSystemEmail(old_data))
chained_validators = [ValidPasswordsMatch, ValidPassword]
return _UserForm
def UsersGroupForm(edit=False, old_data={}, available_members=[]):
class _UsersGroupForm(formencode.Schema):
users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
ValidUsersGroup(edit, old_data))
users_group_active = StringBoolean(if_missing=False)
users_group_members = OneOf(available_members, hideList=False,
testValueList=True,
if_missing=None, not_empty=False)
return _UsersGroupForm
def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
class _ReposGroupForm(formencode.Schema):
group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
SlugifyName())
group_description = UnicodeString(strip=True, min=1,
not_empty=True)
group_parent_id = OneOf(available_groups, hideList=False,
chained_validators = [ValidReposGroup(edit, old_data)]
return _ReposGroupForm
def RegisterForm(edit=False, old_data={}):
class _RegisterForm(formencode.Schema):
username = All(ValidUsername(edit, old_data),
UnicodeString(strip=True, min=1, not_empty=True))
password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
return _RegisterForm
def PasswordResetForm():
class _PasswordResetForm(formencode.Schema):
email = All(ValidSystemEmail(), Email(not_empty=True))
return _PasswordResetForm
def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
repo_groups=[]):
class _RepoForm(formencode.Schema):
filter_extra_fields = False
repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False),
ValidCloneUri()())
repo_group = OneOf(repo_groups, hideList=True)
repo_type = OneOf(supported_backends)
description = UnicodeString(strip=True, min=1, not_empty=True)
private = StringBoolean(if_missing=False)
enable_statistics = StringBoolean(if_missing=False)
enable_downloads = StringBoolean(if_missing=False)
#this is repo owner
user = All(UnicodeString(not_empty=True), ValidRepoUser)
chained_validators = [ValidRepoName(edit, old_data), ValidPerms]
return _RepoForm
def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
class _RepoForkForm(formencode.Schema):
fork_name = All(UnicodeString(strip=True, min=1, not_empty=True),
repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
chained_validators = [ValidForkName()]
copy_permissions = StringBoolean(if_missing=False)
fork_parent_id = UnicodeString()
chained_validators = [ValidForkName(edit, old_data)]
return _RepoForkForm
def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
chained_validators = [ValidRepoName(edit, old_data), ValidPerms,
ValidSettings]
def ApplicationSettingsForm():
class _ApplicationSettingsForm(formencode.Schema):
rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
return _ApplicationSettingsForm
def ApplicationUiSettingsForm():
class _ApplicationUiSettingsForm(formencode.Schema):
web_push_ssl = OneOf(['true', 'false'], if_missing='false')
paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
return _ApplicationUiSettingsForm
def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
class _DefaultPermissionsForm(formencode.Schema):
overwrite_default = StringBoolean(if_missing=False)
anonymous = OneOf(['True', 'False'], if_missing=False)
default_perm = OneOf(perms_choices)
default_register = OneOf(register_choices)
default_create = OneOf(create_choices)
return _DefaultPermissionsForm
def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices):
class _LdapSettingsForm(formencode.Schema):
pre_validators = [LdapLibValidator]
ldap_active = StringBoolean(if_missing=False)
ldap_host = UnicodeString(strip=True,)
ldap_port = Number(strip=True,)
rhodecode.model.notification
~~~~~~~~~~~~~~
Model for notifications
:created_on: Nov 20, 2011
import datetime
from pylons import config
from rhodecode.model import BaseModel
from rhodecode.model.db import Notification, User, UserNotification
from rhodecode.lib.celerylib import run_task
from rhodecode.lib.celerylib.tasks import send_email
class NotificationModel(BaseModel):
def __get_user(self, user):
if isinstance(user, basestring):
return User.get_by_username(username=user)
return self._get_instance(User, user)
def __get_notification(self, notification):
if isinstance(notification, Notification):
return notification
elif isinstance(notification, int):
return Notification.get(notification)
if notification:
raise Exception('notification must be int or Instance'
' of Notification got %s' % type(notification))
def create(self, created_by, subject, body, recipients,
type_=Notification.TYPE_MESSAGE):
Creates notification of given type
:param created_by: int, str or User instance. User who created this
notification
:param subject:
:param body:
:param recipients: list of int, str or User objects
:param type_: type of notification
from rhodecode.lib.celerylib import tasks, run_task
if not getattr(recipients, '__iter__', False):
raise Exception('recipients must be a list of iterable')
created_by_obj = self.__get_user(created_by)
recipients_objs = []
for u in recipients:
obj = self.__get_user(u)
if obj:
recipients_objs.append(obj)
recipients_objs = set(recipients_objs)
notif = Notification.create(created_by=created_by_obj, subject=subject,
body=body, recipients=recipients_objs,
type_=type_)
# send email with notification
for rec in recipients_objs:
email_subject = NotificationModel().make_description(notif, False)
type_ = EmailNotificationModel.TYPE_CHANGESET_COMMENT
email_body = body
email_body_html = EmailNotificationModel()\
.get_email_tmpl(type_, **{'subject':subject,
'body':h.rst(body)})
run_task(send_email, rec.email, email_subject, email_body,
run_task(tasks.send_email, rec.email, email_subject, email_body,
email_body_html)
return notif
def delete(self, user, notification):
# we don't want to remove actual notification just the assignment
notification = self.__get_notification(notification)
user = self.__get_user(user)
if notification and user:
obj = UserNotification.query()\
.filter(UserNotification.user == user)\
.filter(UserNotification.notification
== notification)\
.one()
self.sa.delete(obj)
def get_for_user(self, user):
return user.notifications
def get_unread_cnt_for_user(self, user):
return UserNotification.query()\
.filter(UserNotification.read == False)\
.filter(UserNotification.user == user).count()
def get_unread_for_user(self, user):
return [x.notification for x in UserNotification.query()\
.filter(UserNotification.user == user).all()]
def get_user_notification(self, user, notification):
.filter(UserNotification.notification == notification)\
.filter(UserNotification.user == user).scalar()
def make_description(self, notification, show_age=True):
Creates a human readable description based on properties
@@ -167,209 +167,213 @@ class RepoModel(BaseModel):
r2p = UserRepoToPerm()
r2p.repository = cur_repo
r2p.user = User.get_by_username(member)
r2p.permission = self.sa.query(Permission)\
.filter(Permission.
permission_name == perm)\
.scalar()
self.sa.add(r2p)
g2p = UsersGroupRepoToPerm()
g2p.repository = cur_repo
g2p.users_group = UsersGroup.get_by_group_name(member)
g2p.permission = self.sa.query(Permission)\
self.sa.add(g2p)
# update current repo
for k, v in form_data.items():
if k == 'user':
cur_repo.user = User.get_by_username(v)
elif k == 'repo_name':
elif k == 'repo_group':
cur_repo.group = RepoGroup.get(v)
setattr(cur_repo, k, v)
new_name = cur_repo.get_new_name(form_data['repo_name'])
cur_repo.repo_name = new_name
self.sa.add(cur_repo)
if repo_name != new_name:
# rename repository
self.__rename_repo(old=repo_name, new=new_name)
self.sa.commit()
return cur_repo
self.sa.rollback()
def create(self, form_data, cur_user, just_db=False, fork=False):
if fork:
repo_name = form_data['fork_name']
org_name = form_data['repo_name']
org_full_name = org_name
fork_parent_id = form_data['fork_parent_id']
org_name = repo_name = form_data['repo_name']
repo_name_full = form_data['repo_name_full']
# repo name is just a name of repository
# while repo_name_full is a full qualified name that is combined
# with name and path of group
new_repo = Repository()
new_repo.enable_statistics = False
if k == 'repo_name':
v = repo_name
v = repo_name_full
if k == 'repo_group':
k = 'group_id'
if k == 'description':
v = v or repo_name
setattr(new_repo, k, v)
parent_repo = self.sa.query(Repository)\
.filter(Repository.repo_name == org_full_name).one()
parent_repo = Repository.get(fork_parent_id)
new_repo.fork = parent_repo
new_repo.user_id = cur_user.user_id
self.sa.add(new_repo)
#create default permission
repo_to_perm = UserRepoToPerm()
default = 'repository.read'
for p in User.get_by_username('default').user_perms:
if p.permission.permission_name.startswith('repository.'):
default = p.permission.permission_name
break
default_perm = 'repository.none' if form_data['private'] else default
repo_to_perm.permission_id = self.sa.query(Permission)\
.filter(Permission.permission_name == default_perm)\
.one().permission_id
repo_to_perm.repository = new_repo
repo_to_perm.user_id = User.get_by_username('default').user_id
self.sa.add(repo_to_perm)
if not just_db:
self.__create_repo(repo_name, form_data['repo_type'],
form_data['repo_group'],
form_data['clone_uri'])
#now automatically start following this repository as owner
# now automatically start following this repository as owner
ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
cur_user.user_id)
return new_repo
def create_fork(self, form_data, cur_user):
Simple wrapper into executing celery task for fork creation
run_task(tasks.create_repo_fork, form_data, cur_user)
def delete(self, repo):
self.sa.delete(repo)
self.__delete_repo(repo)
def delete_perm_user(self, form_data, repo_name):
obj = self.sa.query(UserRepoToPerm)\
.filter(UserRepoToPerm.repository \
== self.get_by_repo_name(repo_name))\
.filter(UserRepoToPerm.user_id == form_data['user_id']).one()
def delete_perm_users_group(self, form_data, repo_name):
obj = self.sa.query(UsersGroupRepoToPerm)\
.filter(UsersGroupRepoToPerm.repository \
.filter(UsersGroupRepoToPerm.users_group_id
== form_data['users_group_id']).one()
def delete_stats(self, repo_name):
removes stats for given repo
obj = self.sa.query(Statistics)\
.filter(Statistics.repository == \
self.get_by_repo_name(repo_name)).one()
def __create_repo(self, repo_name, alias, new_parent_id, clone_uri=False):
makes repository on filesystem. It's group aware means it'll create
a repository within a group, and alter the paths accordingly of
group location
:param alias:
:param parent_id:
:param clone_uri:
from rhodecode.lib.utils import is_valid_repo, is_valid_repos_group
if new_parent_id:
paths = RepoGroup.get(new_parent_id)\
.full_path.split(RepoGroup.url_sep())
new_parent_path = os.sep.join(paths)
new_parent_path = ''
repo_path = os.path.join(*map(lambda x:safe_str(x),
[self.repos_path, new_parent_path, repo_name]))
# check if this path is not a repository
if is_valid_repo(repo_path, self.repos_path):
raise Exception('This path %s is a valid repository' % repo_path)
# check if this path is a group
if is_valid_repos_group(repo_path, self.repos_path):
raise Exception('This path %s is a valid group' % repo_path)
log.info('creating repo %s in %s @ %s', repo_name, repo_path,
clone_uri)
backend(repo_path, create=True, src_url=clone_uri)
@@ -163,207 +163,199 @@ class ScmModel(BaseModel):
if path[0] == 'hg' and path[0] in BACKENDS.keys():
# for mercurial we need to have an str path
repos_list[name] = klass(safe_str(path[1]),
baseui=baseui)
if path[0] == 'git' and path[0] in BACKENDS.keys():
repos_list[name] = klass(path[1])
return repos_list
def get_repos(self, all_repos=None, sort_key=None):
Get all repos from db and for each repo create it's
backend instance and fill that backed with information from database
:param all_repos: list of repository names as strings
give specific repositories list, good for filtering
if all_repos is None:
all_repos = self.sa.query(Repository)\
.filter(Repository.group_id == None)\
.order_by(Repository.repo_name).all()
repo_iter = CachedRepoList(all_repos, repos_path=self.repos_path,
order_by=sort_key)
return repo_iter
def mark_for_invalidation(self, repo_name):
"""Puts cache invalidation task into db for
further global cache invalidation
:param repo_name: this repo that should invalidation take place
CacheInvalidation.set_invalidate(repo_name)
CacheInvalidation.set_invalidate(repo_name + "_README")
def toggle_following_repo(self, follow_repo_id, user_id):
f = self.sa.query(UserFollowing)\
.filter(UserFollowing.follows_repo_id == follow_repo_id)\
.filter(UserFollowing.user_id == user_id).scalar()
if f is not None:
self.sa.delete(f)
action_logger(UserTemp(user_id),
'stopped_following_repo',
RepoTemp(follow_repo_id))
f = UserFollowing()
f.user_id = user_id
f.follows_repo_id = follow_repo_id
self.sa.add(f)
'started_following_repo',
def toggle_following_user(self, follow_user_id, user_id):
.filter(UserFollowing.follows_user_id == follow_user_id)\
f.follows_user_id = follow_user_id
def is_following_repo(self, repo_name, user_id, cache=False):
r = self.sa.query(Repository)\
.filter(UserFollowing.follows_repository == r)\
return f is not None
def is_following_user(self, username, user_id, cache=False):
u = User.get_by_username(username)
.filter(UserFollowing.follows_user == u)\
def get_followers(self, repo_id):
if not isinstance(repo_id, int):
repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
return self.sa.query(UserFollowing)\
.filter(UserFollowing.follows_repo_id == repo_id).count()
def get_forks(self, repo_id):
return self.sa.query(Repository)\
.filter(Repository.fork_id == repo_id).count()
def pull_changes(self, repo_name, username):
dbrepo = Repository.get_by_repo_name(repo_name)
clone_uri = dbrepo.clone_uri
if not clone_uri:
raise Exception("This repository doesn't have a clone uri")
repo = dbrepo.scm_instance
extras = {'ip': '',
'username': username,
'action': 'push_remote',
'repository': repo_name}
#inject ui extra param to log this action via push logger
for k, v in extras.items():
repo._repo.ui.setconfig('rhodecode_extras', k, v)
repo.pull(clone_uri)
self.mark_for_invalidation(repo_name)
def commit_change(self, repo, repo_name, cs, user, author, message, content,
f_path):
def commit_change(self, repo, repo_name, cs, user, author, message,
content, f_path):
if repo.alias == 'hg':
from vcs.backends.hg import MercurialInMemoryChangeset as IMC
elif repo.alias == 'git':
from vcs.backends.git import GitInMemoryChangeset as IMC
# decoding here will force that we have proper encoded values
# in any other case this will throw exceptions and deny commit
content = safe_str(content)
message = safe_str(message)
path = safe_str(f_path)
author = safe_str(author)
m = IMC(repo)
m.change(FileNode(path, content))
tip = m.commit(message=message,
author=author,
parents=[cs], branch=cs.branch)
new_cs = tip.short_id
action = 'push_local:%s' % new_cs
action_logger(user, action, repo_name)
def create_node(self, repo, repo_name, cs, user, author, message, content,
if isinstance(content, (basestring,)):
elif isinstance(content, file):
content = content.read()
if isinstance(cs, EmptyChangeset):
# Emptychangeset means we we're editing empty repository
parents = None
@@ -1745,104 +1745,108 @@ div.form div.fields div.field div.button
#journal .journal_action {
padding-top: 4px;
min-height: 2px;
float: left
#journal .journal_action_params {
clear: left;
padding-left: 22px;
#journal .journal_repo {
float: left;
margin-left: 6px;
padding-top: 3px;
#journal .date {
clear: both;
color: #777777;
font-size: 11px;
#journal .journal_repo .journal_repo_name {
font-weight: bold;
font-size: 1.1em;
#journal .compare_view {
padding: 5px 0px 5px 0px;
width: 95px;
.journal_highlight {
padding: 0 2px;
vertical-align: bottom;
.trending_language_tbl,.trending_language_tbl td {
border: 0 !important;
margin: 0 !important;
padding: 0 !important;
.trending_language_tbl,.trending_language_tbl tr {
border-spacing: 1px;
.trending_language {
background-color: #003367;
color: #FFF;
display: block;
min-width: 20px;
text-decoration: none;
height: 12px;
margin-bottom: 4px;
margin-bottom: 0px;
margin-left: 5px;
white-space: pre;
padding: 3px;
h3.files_location {
font-size: 1.8em;
font-weight: 700;
border-bottom: none !important;
margin: 10px 0 !important;
#files_data dl dt {
width: 115px;
padding: 5px;
#files_data dl dd {
padding: 5px !important;
#changeset_content {
border: 1px solid #CCC;
#changeset_compare_view_content {
#changeset_content .container {
min-height: 120px;
font-size: 1.2em;
overflow: hidden;
#changeset_compare_view_content .compare_view_commits {
width: auto !important;
#changeset_compare_view_content .compare_view_commits td {
padding: 0px 0px 0px 12px !important;
file renamed from rhodecode/templates/settings/repo_fork.html to rhodecode/templates/forks/fork.html
## -*- coding: utf-8 -*-
<%inherit file="/base/base.html"/>
<%def name="title()">
${c.repo_name} ${_('Fork')} - ${c.rhodecode_name}
</%def>
<%def name="breadcrumbs_links()">
${h.link_to(u'Home',h.url('/'))}
»
${h.link_to(c.repo_info.repo_name,h.url('summary_home',repo_name=c.repo_info.repo_name))}
${_('fork')}
<%def name="page_nav()">
${self.menu('')}
<%def name="main()">
<div class="box">
<!-- box / title -->
<div class="title">
${self.breadcrumbs()}
</div>
${h.form(url('repo_fork_create_home',repo_name=c.repo_info.repo_name))}
<div class="form">
<!-- fields -->
<div class="fields">
<div class="field">
<div class="label">
<label for="repo_name">${_('Fork name')}:</label>
<div class="input">
${h.text('fork_name',class_="small")}
${h.hidden('repo_type',c.repo_info.repo_type)}
${h.text('repo_name',class_="small")}
${h.hidden('fork_parent_id',c.repo_info.repo_id)}
<label for="repo_group">${_('Repository group')}:</label>
${h.select('repo_group','',c.repo_groups,class_="medium")}
<div class="label label-textarea">
<label for="description">${_('Description')}:</label>
<div class="textarea text-area editor">
${h.textarea('description',cols=23,rows=5)}
<div class="label label-checkbox">
<label for="private">${_('Private')}:</label>
<div class="checkboxes">
${h.checkbox('private',value="True")}
<label for="private">${_('Copy permissions')}:</label>
${h.checkbox('copy_permissions',value="True")}
<div class="buttons">
${h.submit('',_('fork this repository'),class_="ui-button")}
${h.end_form()}
Status change: