@@ -456,28 +456,29 @@ def make_map(config):
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',
conditions=dict(function=check_repo))
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
@@ -20,61 +20,62 @@
# 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()
@@ -118,47 +119,47 @@ class ReposController(BaseController):
# 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')
else:
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,
@@ -198,25 +199,25 @@ class ReposController(BaseController):
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,
errors=errors.error_dict or {},
prefix_error=False,
encoding="UTF-8")
except Exception:
log.error(traceback.format_exc())
@@ -242,25 +243,25 @@ class ReposController(BaseController):
' it was moved or renamed from the filesystem'
' please run the application again'
' in order to rescan repositories') % repo_name,
category='error')
return redirect(url('repos'))
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,
@@ -14,43 +14,161 @@
# 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
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__()
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'
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
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)
# add prefix to fork
defaults['repo_name'] = 'fork-' + defaults['repo_name']
return defaults
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'
return redirect(url('home'))
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')
@@ -14,39 +14,40 @@
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"
@@ -115,33 +116,35 @@ class JournalController(BaseController):
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 a rendered template
@@ -26,50 +26,50 @@
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)
if not repo:
@@ -80,40 +80,41 @@ class SettingsController(BaseRepoControl
render('settings/repo_settings.html'),
def update(self, repo_name):
_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})
@@ -139,70 +140,19 @@ class SettingsController(BaseRepoControl
action_logger(self.rhodecode_user, 'user_deleted_repo',
h.flash(_('An error occurred during deletion of %s') % repo_name,
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')
@@ -28,129 +28,123 @@ 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
@@ -246,55 +240,56 @@ def get_commits_stats(repo_name, ts_min_
#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)
return False
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)
sa.commit()
@@ -352,45 +347,57 @@ def send_email(recipients, subject, body
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
@@ -24,25 +24,26 @@
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:
@@ -56,42 +57,45 @@ def repo_size(ui, repo, hooktype=None, *
size_root += os.path.getsize(os.path.join(path, f))
except OSError:
pass
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):
@@ -101,15 +105,16 @@ def log_push_action(ui, repo, **kwargs):
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)
@@ -84,25 +84,25 @@ def repo_name_slug(value):
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
@@ -129,30 +129,31 @@ def action_logger(user, action, repo, ip
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:
sa.rollback()
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
@@ -176,48 +176,48 @@ class ValidPassword(formencode.validator
value['new_password'] = \
get_crypt_password(value['new_password'])
except UnicodeEncodeError:
e_dict = {'new_password':_('Invalid characters in password')}
raise formencode.Invalid('', value, state, error_dict=e_dict)
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',
@@ -245,25 +245,25 @@ def ValidRepoName(edit, old_data):
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,):
@@ -280,42 +280,26 @@ def ValidRepoName(edit, old_data):
error_dict=e_dict)
elif RepoModel().get_by_repo_name(repo_name_full):
e_dict = {'repo_name':_('This repository '
'already exists')}
raise formencode.Invalid('', value, state,
return _ValidRepoName
def ValidForkName():
class _ValidForkName(formencode.validators.FancyValidator):
def to_python(self, value, state):
repo_name = value.get('fork_name')
slug = repo_name_slug(repo_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
@@ -504,25 +488,25 @@ def UserForm(edit=False, old_data={}):
class _UserForm(formencode.Schema):
allow_extra_fields = True
filter_extra_fields = True
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):
@@ -596,50 +580,53 @@ def RepoForm(edit=False, old_data={}, su
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(),
repo_groups=[]):
class _RepoForkForm(formencode.Schema):
filter_extra_fields = False
fork_name = All(UnicodeString(strip=True, min=1, not_empty=True),
repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
SlugifyName())
repo_group = OneOf(repo_groups, hideList=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(),
class _RepoForm(formencode.Schema):
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)
@@ -26,26 +26,24 @@
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)
@@ -65,24 +63,25 @@ class NotificationModel(BaseModel):
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)
@@ -91,25 +90,25 @@ class NotificationModel(BaseModel):
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)\
@@ -203,54 +203,51 @@ class RepoModel(BaseModel):
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
for k, v in form_data.items():
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
@@ -262,37 +259,39 @@ class RepoModel(BaseModel):
.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)
@@ -316,24 +315,29 @@ class RepoModel(BaseModel):
.filter(UsersGroupRepoToPerm.repository \
== self.get_by_repo_name(repo_name))\
.filter(UsersGroupRepoToPerm.users_group_id
== form_data['users_group_id']).one()
self.sa.delete(obj)
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):
@@ -199,75 +199,67 @@ class ScmModel(BaseModel):
: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))
return
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
@@ -308,26 +300,26 @@ class ScmModel(BaseModel):
'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)
@@ -1781,32 +1781,36 @@ div.form div.fields div.field div.button
.journal_highlight {
font-weight: bold;
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;
file renamed from rhodecode/templates/settings/repo_fork.html to rhodecode/templates/forks/fork.html
@@ -18,44 +18,61 @@
</%def>
<%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: