.. _changelog:
=========
Changelog
1.5.2 (**2012-XX-XX**)
----------------------
:status: in-progress
:branch: beta
news
++++
fixes
+++++
1.5.1 (**2012-12-13**)
- implements #677: Don't allow to close pull requests when they are
under-review status
- implemented #670 Implementation of Roles in Pull Request
- default permissions can get duplicated after migration
- fixed changeset status labels, they now select radio buttons
- #682 translation difficult for multi-line text
- #683 fixed difference between messages about not mapped repositories
1.5.0 (**2012-12-12**)
- new rewritten from scratch diff engine. 10x faster in edge cases. Handling
of file renames, copies, change flags and binary files
- added lightweight dashboard option. ref #500. New version of dashboard
page that doesn't use any VCS data and is super fast to render. Recommended
for large amount of repositories.
- implements #648 write Script for updating last modification time for
lightweight dashboard
- implemented compare engine for git repositories.
- LDAP failover, option to specify multiple servers
- added Errormator and Sentry support for monitoring RhodeCode
- implemented #628: Pass server URL to rc-extensions hooks
- new tooltip implementation - added lazy loading of changesets from journal
pages. This can significantly improve speed of rendering the page
- implements #632,added branch/tag/bookmarks info into feeds
added changeset link to body of message
- implemented #638 permissions overview to groups
- implements #636, lazy loading of history and authors to speed up source
pages rendering
- implemented #647, option to pass list of default encoding used to
encode to/decode from unicode
- added caching layer into RSS/ATOM feeds.
- basic implementation of cherry picking changesets for pull request, ref #575
- implemented #661 Add option to include diff in RSS feed
- implemented file history page for showing detailed changelog for a given file
- implemented #663 Admin/permission: specify default repogroup perms
- implemented #379 defaults settings page for creation of repositories, locking
statistics, downloads, repository type
- implemented #210 filtering of admin journal based on Whoosh Query language
- added parents/children links in changeset viewref #650
- fixed git version checker
- #586 patched basic auth handler to fix issues with git behind proxy
- #589 search urlgenerator didn't properly escape special characters
- fixed issue #614 Include repo name in delete confirmation dialog
- fixed #623: Lang meta-tag doesn't work with C#/C++
- fixes #612 Double quotes to Single quotes result in bad html in diff
- fixes #630 git statistics do too much work making them slow.
- fixes #625 Git-Tags are not displayed in Shortlog
@@ -44,102 +44,97 @@ from rhodecode.lib.helpers import get_to
from rhodecode.model.meta import Session
from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup,\
RhodeCodeSetting
from rhodecode.model.forms import RepoForm
from rhodecode.model.scm import ScmModel
from rhodecode.model.repo import RepoModel
from rhodecode.lib.compat import json
from sqlalchemy.sql.expression import func
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(check_perms=True)
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()
choices, c.landing_revs = ScmModel().get_repo_landing_revs()
c.landing_revs_choices = choices
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')
h.not_mapped_error(repo_name)
return redirect(url('repos'))
##override defaults for exact repo info here git/hg etc
choices, c.landing_revs = ScmModel().get_repo_landing_revs(c.repo_info)
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:
# this is on what revision we ended up so we add +1 for count
last_rev = c.repo_info.stats.stat_on_revision + 1
else:
last_rev = 0
c.stats_revision = last_rev
c.repo_last_rev = repo.count() 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)
c.repos_list = [('', _('--REMOVE FORK--'))]
c.repos_list += [(x.repo_id, x.repo_name) for x in
Repository.query().order_by(Repository.repo_name).all()
if x.repo_id != c.repo_info.repo_id]
defaults['id_fork_of'] = db_repo.fork.repo_id if db_repo.fork else ''
return defaults
@HasPermissionAllDecorator('hg.admin')
def index(self, format='html'):
"""GET /repos: All items in the collection"""
# url('repos')
c.repos_list = Repository.query()\
.order_by(func.lower(Repository.repo_name))\
.all()
repos_data = []
total_records = len(c.repos_list)
@@ -265,102 +260,97 @@ class ReposController(BaseController):
changed_name = repo_name
#override the choices with extracted revisions !
choices, c.landing_revs = ScmModel().get_repo_landing_revs(repo_name)
_form = RepoForm(edit=True, old_data={'repo_name': repo_name},
repo_groups=c.repo_groups_choices,
landing_revs=c.landing_revs_choices)()
try:
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,
category='success')
changed_name = repo.repo_name
action_logger(self.rhodecode_user, 'admin_updated_repo',
changed_name, self.ip_addr, self.sa)
Session().commit()
except formencode.Invalid, errors:
defaults = self.__load_data(repo_name)
defaults.update(errors.value)
return htmlfill.render(
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())
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"""
# Forms posted to this method should contain a hidden field:
# <input type="hidden" name="_method" value="DELETE" />
# Or using helpers:
# h.form(url('repo', repo_name=ID),
# method='delete')
# url('repo', repo_name=ID)
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.ip_addr, 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') != -1:
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,
@HasRepoPermissionAllDecorator('repository.admin')
def delete_perm_user(self, repo_name):
DELETE an existing repository permission user
RepoModel().revoke_user_permission(repo=repo_name,
user=request.POST['user_id'])
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
@@ -26,160 +26,150 @@ import logging
import formencode
import traceback
from formencode import htmlfill
from pylons import tmpl_context as c, request, url
from pylons.controllers.util import redirect
from pylons.i18n.translation import _
import rhodecode.lib.helpers as h
from rhodecode.lib.helpers import Page
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \
NotAnonymous, HasRepoPermissionAny, HasPermissionAllDecorator,\
HasPermissionAnyDecorator
from rhodecode.lib.base import BaseRepoController, render
from rhodecode.model.db import Repository, RepoGroup, UserFollowing, User
from rhodecode.model.forms import RepoForkForm
from rhodecode.lib.utils2 import safe_int
class ForksController(BaseRepoController):
super(ForksController, self).__before__()
last_rev = c.repo_info.stats.stat_on_revision+1
# add suffix to fork
defaults['repo_name'] = '%s-fork' % defaults['repo_name']
@HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
'repository.admin')
def forks(self, repo_name):
p = safe_int(request.params.get('page', 1), 1)
repo_id = c.rhodecode_db_repo.repo_id
d = []
for r in Repository.get_repo_forks(repo_id):
if not HasRepoPermissionAny(
'repository.read', 'repository.write', 'repository.admin'
)(r.repo_name, 'get forks check'):
continue
d.append(r)
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()
@HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
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},
form_result = {}
# create fork is done sometimes async on celery, db transaction
# management is handled there.
RepoModel().create_fork(form_result, self.rhodecode_user.user_id)
h.flash(_('forked %s repository as %s') \
% (repo_name, form_result['repo_name']),
c.new_repo = errors.value['repo_name']
defaults=errors.value,
h.flash(_('An error occurred during repository forking %s') %
repo_name, category='error')
@@ -32,175 +32,165 @@ from formencode import htmlfill
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator,\
HasRepoPermissionAnyDecorator
from rhodecode.lib.utils import invalidate_cache, action_logger
from rhodecode.model.forms import RepoSettingsForm
from rhodecode.model.db import RepoGroup, Repository
class SettingsController(BaseRepoController):
super(SettingsController, self).__before__()
def index(self, repo_name):
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',
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 redirect(url('admin_settings_my_account', anchor='my'))
@HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
def toggle_locking(self, repo_name):
Toggle locking of repository by simple GET call to url
repo = Repository.get_by_repo_name(repo_name)
if repo.enable_locking:
if repo.locked[0]:
Repository.unlock(repo)
action = _('unlocked')
Repository.lock(repo, c.rhodecode_user.user_id)
action = _('locked')
h.flash(_('Repository has been %s') % action,
h.flash(_('An error occurred during unlocking'),
return redirect(url('summary_home', repo_name=repo_name))
@@ -1112,48 +1112,55 @@ def urlify_commit(text_, repository=None
return literal(newtext)
def rst(source):
return literal('<div class="rst-block">%s</div>' %
MarkupRenderer.rst(source))
def rst_w_mentions(source):
Wrapped rst renderer with @mention highlighting
:param source:
MarkupRenderer.rst_with_mentions(source))
def changeset_status(repo, revision):
return ChangesetStatusModel().get_status(repo, revision)
def changeset_status_lbl(changeset_status):
return dict(ChangesetStatus.STATUSES).get(changeset_status)
def get_permission_name(key):
return dict(Permission.PERMS).get(key)
def journal_filter_help():
return _(textwrap.dedent('''
Example filter terms:
repository:vcs
username:marcin
action:*push*
ip:127.0.0.1
date:20120101
date:[20120101100000 TO 20120102]
Generate wildcards using '*' character:
"repositroy:vcs*" - search everything starting with 'vcs'
"repository:*vcs*" - search for repository containing 'vcs'
Optional AND / OR operators in queries
"repository:vcs OR repository:test"
"username:test AND repository:test*"
'''))
def not_mapped_error(repo_name):
flash(_('%s repository is not mapped to db perhaps'
' in order to rescan repositories') % repo_name, category='error')
Status change: