.. _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
- fix for issue #602, enforce str when setting mercurial UI object.
When this is used together with mercurial internal translation system
it can lead to UnicodeDecodeErrors
- fixes #645 Fix git handler when doing delete remote branch
- implements #649 added two seperate method for author and commiter to VCS
changeset class switch author for git backed to be the real author not commiter
- fix issue #504 RhodeCode is showing different versions of README on
different summary page loads
- implemented #658 Changing username in LDAP-Mode should not be allowed.
- fixes #652 switch to generator approach when doing file annotation to prevent
huge memory consumption
- fixes #666 move lockkey path location to cache_dir to ensure this path is
always writable for rhodecode server
- many more small fixes and improvements
- fixed issues with recursive scans on removed repositories that could take
long time on instance start
1.4.4 (**2012-10-08**)
- obfuscate db password in logs for engine connection string
- #574 Show pull request status also in shortlog (if any)
- remember selected tab in my account page
- Bumped mercurial version to 2.3.2
- #595 rcextension hook for repository delete
- Add git version detection to warn users that Git used in system is to
old. Ref #588 - also show git version in system details in settings page
- fixed files quick filter links
- #590 Add GET flag that controls the way the diff are generated, for pull
requests we want to use non-bundle based diffs, That are far better for
doing code reviews. The /compare url still uses bundle compare for full
comparison including the incoming changesets
- Fixed #585, checks for status of revision where to strict, and made
opening pull request with those revision impossible due to previously set
status. Checks now are made also for the repository.
- fixes #591 git backend was causing encoding errors when handling binary
files - added a test case for VCS lib tests
- fixed #597 commits in future get negative age.
- fixed #598 API docs methods had wrong members parameter as returned data
1.4.3 (**2012-09-28**)
- #558 Added config file to hooks extra data
- bumped mercurial version to 2.3.1
- #518 added possibility of specifying multiple patterns for issues
- update codemirror to latest version
- fixed #570 explicit users group permissions can overwrite owner permissions
- fixed #578 set proper PATH with current Python for Git
hooks to execute within same Python as RhodeCode
- fixed issue with Git bare repos that ends with .git in name
1.4.2 (**2012-09-12**)
- added option to menu to quick lock/unlock repository for users that have
write access to
- Implemented permissions for writing to repo
groups. Now only write access to group allows to create a repostiory
within that group
- #565 Add support for {netloc} and {scheme} to alternative_gravatar_url
- updated translation for zh_CN
- fixed visual permissions check on repos groups inside groups
- fixed issues with non-ascii search terms in search, and indexers
- fixed parsing of page number in GET parameters
- fixed issues with generating pull-request overview for repos with
bookmarks and tags, also preview doesn't loose chosen revision from
select dropdown
1.4.1 (**2012-09-07**)
- always put a comment about code-review status change even if user send
empty data
- modified_on column saves repository update and it's going to be used
later for light version of main page ref #500
- pull request notifications send much nicer emails with details about pull
request
- #551 show breadcrumbs in summary view for repositories inside a group
- fixed migrations of permissions that can lead to inconsistency.
Some users sent feedback that after upgrading from older versions issues
with updating default permissions occurred. RhodeCode detects that now and
resets default user permission to initial state if there is a need for that.
Also forces users to set the default value for new forking permission.
- #535 improved apache wsgi example configuration in docs
- fixes #550 mercurial repositories comparision failed when origin repo had
additional not-common changesets
- fixed status of code-review in preview windows of pull request
- git forks were not initialized at bare repos
- fixes #555 fixes issues with comparing non-related repositories
- fixes #557 follower counter always counts up
- fixed issue #560 require push ssl checkbox wasn't shown when option was
enabled
- fixed #559
- fixed issue #559 fixed bug in routing that mapped repo names with <name>_<num> in name as
if it was a request to url by repository ID
1.4.0 (**2012-09-03**)
- new codereview system
- email map, allowing users to have multiple email addresses mapped into
their accounts
- improved git-hook system. Now all actions for git are logged into journal
including pushed revisions, user and IP address
- changed setup-app into setup-rhodecode and added default options to it.
- new git repos are created as bare now by default
- #464 added links to groups in permission box
- #465 mentions autocomplete inside comments boxes
- #469 added --update-only option to whoosh to re-index only given list
of repos in index
- rhodecode-api CLI client
- new git http protocol replaced buggy dulwich implementation.
Now based on pygrack & gitweb
- Improved RSS/ATOM feeds. Discoverable by browsers using proper headers, and
reformated based on user suggestions. Additional rss/atom feeds for user
journal
- various i18n improvements
- #478 permissions overview for admin in user edit view
- File view now displays small gravatars off all authors of given file
- Implemented landing revisions. Each repository will get landing_rev attribute
that defines 'default' revision/branch for generating readme files
- Implemented #509, RhodeCode enforces SSL for push/pulling if requested at
earliest possible call.
- Import remote svn repositories to mercurial using hgsubversion.
- Fixed #508 RhodeCode now has a option to explicitly set forking permissions
- RhodeCode can use alternative server for generating avatar icons
- implemented repositories locking. Pull locks, push unlocks. Also can be done
via API calls
- #538 form for permissions can handle multiple users at once
- improved translations
- fixes issue #455 Creating an archive generates an exception on Windows
- fixes #448 Download ZIP archive keeps file in /tmp open and results
in out of disk space
- fixes issue #454 Search results under Windows include proceeding
backslash
- fixed issue #450. Rhodecode no longer will crash when bad revision is
present in journal data.
- fix for issue #417, git execution was broken on windows for certain
commands.
- fixed #413. Don't disable .git directory for bare repos on deleting
- fixed issue #459. Changed the way of obtaining logger in reindex task.
- fixed #453 added ID field in whoosh SCHEMA that solves the issue of
reindexing modified files
- fixed #481 rhodecode emails are sent without Date header
- fixed #458 wrong count when no repos are present
- fixed issue #492 missing `\ No newline at end of file` test at the end of
new chunk in html diff
- full text search now works also for commit messages
1.3.6 (**2012-05-17**)
- chinese traditional translation
- changed setup-app into setup-rhodecode and added arguments for auto-setup
mode that doesn't need user interaction
- fixed no scm found warning
- fixed __future__ import error on rcextensions
- made simplejson required lib for speedup on JSON encoding
- fixes #449 bad regex could get more than revisions from parsing history
- don't clear DB session when CELERY_EAGER is turned ON
1.3.5 (**2012-05-10**)
- use ext_json for json module
- unified annotation view with file source view
- notification improvements, better inbox + css
- #419 don't strip passwords for login forms, make rhodecode
more compatible with LDAP servers
- Added HTTP_X_FORWARDED_FOR as another method of extracting
IP for pull/push logs. - moved all to base controller
- #415: Adding comment to changeset causes reload.
Comments are now added via ajax and doesn't reload the page
- #374 LDAP config is discarded when LDAP can't be activated
- limited push/pull operations are now logged for git in the journal
- bumped mercurial to 2.2.X series
- added support for displaying submodules in file-browser
- #421 added bookmarks in changelog view
- fixed dev-version marker for stable when served from source codes
- fixed missing permission checks on show forks page
- #418 cast to unicode fixes in notification objects
- #426 fixed mention extracting regex
- fixed remote-pulling for git remotes remopositories
- fixed #434: Error when accessing files or changesets of a git repository
with submodules
- fixed issue with empty APIKEYS for users after registration ref. #438
- fixed issue with getting README files from git repositories
1.3.4 (**2012-03-28**)
- Whoosh logging is now controlled by the .ini files logging setup
- added clone-url into edit form on /settings page
- added help text into repo add/edit forms
- created rcextensions module with additional mappings (ref #322) and
post push/pull/create repo hooks callbacks
- implemented #377 Users view for his own permissions on account page
- #399 added inheritance of permissions for users group on repos groups
- #401 repository group is automatically pre-selected when adding repos
inside a repository group
- added alternative HTTP 403 response when client failed to authenticate. Helps
solving issues with Mercurial and LDAP
- #402 removed group prefix from repository name when listing repositories
inside a group
- added gravatars into permission view and permissions autocomplete
- #347 when running multiple RhodeCode instances, properly invalidates cache
for all registered servers
- fixed #390 cache invalidation problems on repos inside group
- fixed #385 clone by ID url was loosing proxy prefix in URL
- fixed some unicode problems with waitress
- fixed issue with escaping < and > in changeset commits
- fixed error occurring during recursive group creation in API
create_repo function
- fixed #393 py2.5 fixes for routes url generator
- fixed #397 Private repository groups shows up before login
- fixed #396 fixed problems with revoking users in nested groups
- fixed mysql unicode issues + specified InnoDB as default engine with
utf8 charset
- #406 trim long branch/tag names in changelog to not break UI
1.3.3 (**2012-03-02**)
- fixed some python2.5 compatibility issues
- fixed issues with removed repos was accidentally added as groups, after
full rescan of paths
- fixes #376 Cannot edit user (using container auth)
- fixes #378 Invalid image urls on changeset screen with proxy-prefix
configuration
- fixed initial sorting of repos inside repo group
- fixes issue when user tried to resubmit same permission into user/user_groups
- bumped beaker version that fixes #375 leap error bug
- fixed raw_changeset for git. It was generated with hg patch headers
- fixed vcs issue with last_changeset for filenodes
- fixed missing commit after hook delete
- fixed #372 issues with git operation detection that caused a security issue
for git repos
1.3.2 (**2012-02-28**)
- fixed git protocol issues with repos-groups
- fixed git remote repos validator that prevented from cloning remote git repos
- fixes #370 ending slashes fixes for repo and groups
- fixes #368 improved git-protocol detection to handle other clients
- fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
Moved To Root
- fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
- fixed #373 missing cascade drop on user_group_to_perm table
1.3.1 (**2012-02-27**)
- redirection loop occurs when remember-me wasn't checked during login
- fixes issues with git blob history generation
- don't fetch branch for git in file history dropdown. Causes unneeded slowness
1.3.0 (**2012-02-26**)
# -*- coding: utf-8 -*-
"""
rhodecode.controllers.admin.repos
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Repositories controller for RhodeCode
:created_on: Apr 7, 2010
:author: marcink
:copyright: (C) 2010-2012 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 webob.exc import HTTPInternalServerError
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
import rhodecode
from rhodecode.lib import helpers as h
from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
HasPermissionAnyDecorator, HasRepoPermissionAllDecorator
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,\
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)
_tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
quick_menu = lambda repo_name: (template.get_def("quick_menu")
.render(repo_name, _=_, h=h, c=c))
repo_lnk = lambda name, rtype, private, fork_of: (
template.get_def("repo_name")
.render(name, rtype, private, fork_of, short_name=False,
admin=True, _=_, h=h, c=c))
repo_actions = lambda repo_name: (template.get_def("repo_actions")
for repo in c.repos_list:
repos_data.append({
"menu": quick_menu(repo.repo_name),
"raw_name": repo.repo_name.lower(),
"name": repo_lnk(repo.repo_name, repo.repo_type,
repo.private, repo.fork),
"desc": repo.description,
"owner": repo.user.username,
"action": repo_actions(repo.repo_name),
})
c.data = json.dumps({
"totalRecords": total_records,
"startIndex": 0,
"sort": "name",
"dir": "asc",
"records": repos_data
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,
landing_revs=c.landing_revs_choices)()\
.to_python(dict(request.POST))
new_repo = RepoModel().create(form_result,
self.rhodecode_user.user_id)
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
action_logger(self.rhodecode_user, 'user_created_repo',
form_result['repo_name_full'], self.ip_addr,
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')
#redirect to our new repo !
return redirect(url('summary_home', repo_name=new_repo.repo_name))
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)
## apply the defaults from defaults page
defaults = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
render('admin/repos/repo_add.html'),
defaults=defaults,
errors={},
encoding="UTF-8"
)
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
#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)()
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.ip_addr, self.sa)
defaults = self.__load_data(repo_name)
defaults.update(errors.value)
render('admin/repos/repo_edit.html'),
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.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
RepoModel().revoke_users_group_permission(
repo=repo_name, group_name=request.POST['users_group_id']
h.flash(_('An error occurred during deletion of repository'
' users groups'),
def repo_stats(self, repo_name):
DELETE an existing repository statistics
RepoModel().delete_stats(repo_name)
h.flash(_('An error occurred during deletion of repository stats'),
return redirect(url('edit_repo', repo_name=repo_name))
def repo_cache(self, repo_name):
INVALIDATE existing repository cache
ScmModel().mark_for_invalidation(repo_name)
h.flash(_('An error occurred during cache invalidation'),
def repo_locking(self, repo_name):
Unlock repository when it is locked !
repo = Repository.get_by_repo_name(repo_name)
if request.POST.get('set_lock'):
Repository.lock(repo, c.rhodecode_user.user_id)
elif request.POST.get('set_unlock'):
Repository.unlock(repo)
h.flash(_('An error occurred during unlocking'),
def repo_public_journal(self, repo_name):
Set's this repository to be visible in public journal,
in other words assing default user to follow this repo
cur_token = request.POST.get('auth_token')
token = get_token()
if cur_token == token:
repo_id = Repository.get_by_repo_name(repo_name).repo_id
user_id = User.get_by_username('default').user_id
self.scm_model.toggle_following_repo(repo_id, user_id)
h.flash(_('Updated repository visibility in public journal'),
except:
h.flash(_('An error occurred during setting this'
' repository in public journal'),
h.flash(_('Token mismatch'), category='error')
def repo_pull(self, repo_name):
Runs task to update given repository with remote changes,
ie. make pull on remote location
ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
h.flash(_('Pulled from remote location'), category='success')
h.flash(_('An error occurred during pull from remote location'),
def repo_as_fork(self, repo_name):
Mark given repository as a fork of another
fork_id = request.POST.get('id_fork_of')
repo = ScmModel().mark_as_fork(repo_name, fork_id,
self.rhodecode_user.username)
fork = repo.fork.repo_name if repo.fork else _('Nothing')
h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork),
h.flash(_('An error occurred during this operation'),
def show(self, repo_name, format='html'):
"""GET /repos/repo_name: Show a specific item"""
def edit(self, repo_name, format='html'):
"""GET /repos/repo_name/edit: Form to edit an existing item"""
# url('edit_repo', repo_name=ID)
encoding="UTF-8",
force_defaults=False
rhodecode.controllers.forks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
forks controller for rhodecode
:created_on: Apr 23, 2011
:copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
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, \
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'),
def fork_create(self, repo_name):
_form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type},
# 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']),
h.flash(_('An error occurred during repository forking %s') %
repo_name, category='error')
rhodecode.controllers.settings
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Settings controller for rhodecode
:created_on: Jun 30, 2010
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'),
_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
if repo.enable_locking:
if repo.locked[0]:
action = _('unlocked')
action = _('locked')
h.flash(_('Repository has been %s') % action,
return redirect(url('summary_home', repo_name=repo_name))
@@ -776,384 +776,391 @@ def gravatar_url(email_address, size=30)
if isinstance(email_address, unicode):
#hashlib crashes on unicode items
email_address = safe_str(email_address)
# construct the url
gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
return gravatar_url
#==============================================================================
# REPO PAGER, PAGER FOR REPOSITORY
class RepoPage(Page):
def __init__(self, collection, page=1, items_per_page=20,
item_count=None, url=None, **kwargs):
"""Create a "RepoPage" instance. special pager for paging
repository
self._url_generator = url
# Safe the kwargs class-wide so they can be used in the pager() method
self.kwargs = kwargs
# Save a reference to the collection
self.original_collection = collection
self.collection = collection
# The self.page is the number of the current page.
# The first page has the number 1!
self.page = int(page) # make it int() if we get it as a string
except (ValueError, TypeError):
self.page = 1
self.items_per_page = items_per_page
# Unless the user tells us how many items the collections has
# we calculate that ourselves.
if item_count is not None:
self.item_count = item_count
self.item_count = len(self.collection)
# Compute the number of the first and last available page
if self.item_count > 0:
self.first_page = 1
self.page_count = int(math.ceil(float(self.item_count) /
self.items_per_page))
self.last_page = self.first_page + self.page_count - 1
# Make sure that the requested page number is the range of
# valid pages
if self.page > self.last_page:
self.page = self.last_page
elif self.page < self.first_page:
self.page = self.first_page
# Note: the number of items on this page can be less than
# items_per_page if the last page is not full
self.first_item = max(0, (self.item_count) - (self.page *
items_per_page))
self.last_item = ((self.item_count - 1) - items_per_page *
(self.page - 1))
self.items = list(self.collection[self.first_item:self.last_item + 1])
# Links to previous and next page
if self.page > self.first_page:
self.previous_page = self.page - 1
self.previous_page = None
if self.page < self.last_page:
self.next_page = self.page + 1
self.next_page = None
# No items available
self.first_page = None
self.page_count = 0
self.last_page = None
self.first_item = None
self.last_item = None
self.items = []
# This is a subclass of the 'list' type. Initialise the list now.
list.__init__(self, reversed(self.items))
def changed_tooltip(nodes):
Generates a html string for changed nodes in changeset page.
It limits the output to 30 entries
:param nodes: LazyNodesGenerator
if nodes:
pref = ': <br/> '
suf = ''
if len(nodes) > 30:
suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
return literal(pref + '<br/> '.join([safe_unicode(x.path)
for x in nodes[:30]]) + suf)
return ': ' + _('No Files')
def repo_link(groups_and_repos, last_url=None):
Makes a breadcrumbs link to repo within a group
joins » on each group to create a fancy link
ex::
group >> subgroup >> repo
:param groups_and_repos:
:param last_url:
groups, repo_name = groups_and_repos
last_link = link_to(repo_name, last_url) if last_url else repo_name
if not groups:
if last_url:
return last_link
return repo_name
def make_link(group):
return link_to(group.name,
url('repos_group_home', group_name=group.group_name))
return literal(' » '.join(map(make_link, groups) + [last_link]))
def fancy_file_stats(stats):
Displays a fancy two colored bar for number of added/deleted
lines of code on file
:param stats: two element list of added/deleted lines of code
def cgen(l_type, a_v, d_v):
mapping = {'tr': 'top-right-rounded-corner-mid',
'tl': 'top-left-rounded-corner-mid',
'br': 'bottom-right-rounded-corner-mid',
'bl': 'bottom-left-rounded-corner-mid'}
map_getter = lambda x: mapping[x]
if l_type == 'a' and d_v:
#case when added and deleted are present
return ' '.join(map(map_getter, ['tl', 'bl']))
if l_type == 'a' and not d_v:
return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
if l_type == 'd' and a_v:
return ' '.join(map(map_getter, ['tr', 'br']))
if l_type == 'd' and not a_v:
a, d = stats[0], stats[1]
width = 100
if a == 'b':
#binary mode
b_d = '<div class="bin%s %s" style="width:100%%">%s</div>' % (d, cgen('a', a_v='', d_v=0), 'bin')
b_a = '<div class="bin1" style="width:0%%">%s</div>' % ('bin')
return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
t = stats[0] + stats[1]
unit = float(width) / (t or 1)
# needs > 9% of width to be visible or 0 to be hidden
a_p = max(9, unit * a) if a > 0 else 0
d_p = max(9, unit * d) if d > 0 else 0
p_sum = a_p + d_p
if p_sum > width:
#adjust the percentage to be == 100% since we adjusted to 9
if a_p > d_p:
a_p = a_p - (p_sum - width)
d_p = d_p - (p_sum - width)
a_v = a if a > 0 else ''
d_v = d if d > 0 else ''
d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
cgen('a', a_v, d_v), a_p, a_v
d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
cgen('d', a_v, d_v), d_p, d_v
return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
def urlify_text(text_):
url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
'''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
def url_func(match_obj):
url_full = match_obj.groups()[0]
return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
return literal(url_pat.sub(url_func, text_))
def urlify_changesets(text_, repository):
Extract revision ids from changeset and make link from them
:param text_:
:param repository:
URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
rev = match_obj.groups()[0]
pref = ''
if match_obj.group().startswith(' '):
pref = ' '
tmpl = (
'%(pref)s<a class="%(cls)s" href="%(url)s">'
'%(rev)s'
'</a>'
return tmpl % {
'pref': pref,
'cls': 'revision-link',
'url': url('changeset_home', repo_name=repository, revision=rev),
'rev': rev,
}
newtext = URL_PAT.sub(url_func, text_)
return newtext
def urlify_commit(text_, repository=None, link_=None):
Parses given text message and makes proper links.
issues are linked to given issue-server, and rest is a changeset link
if link_ is given, in other case it's a plain text
:param link_: changeset link
def escaper(string):
return string.replace('<', '<').replace('>', '>')
def linkify_others(t, l):
urls = re.compile(r'(\<a.*?\<\/a\>)',)
links = []
for e in urls.split(t):
if not urls.match(e):
links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
links.append(e)
return ''.join(links)
# urlify changesets - extrac revisions and make link out of them
newtext = urlify_changesets(escaper(text_), repository)
conf = config['app_conf']
# allow multiple issue servers to be used
valid_indices = [
x.group(1)
for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
if x and 'issue_server_link%s' % x.group(1) in conf
and 'issue_prefix%s' % x.group(1) in conf
]
log.debug('found issue server suffixes `%s` during valuation of: %s'
% (','.join(valid_indices), newtext))
for pattern_index in valid_indices:
ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
% (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
ISSUE_PREFIX))
URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
issue_id = ''.join(match_obj.groups())
'%(issue-prefix)s%(id-repr)s'
url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
if repository:
url = url.replace('{repo}', repository)
repo_name = repository.split(URL_SEP)[-1]
url = url.replace('{repo_name}', repo_name)
'cls': 'issue-tracker-link',
'url': url,
'id-repr': issue_id,
'issue-prefix': ISSUE_PREFIX,
'serv': ISSUE_SERVER_LNK,
newtext = URL_PAT.sub(url_func, newtext)
log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
# if we actually did something above
if link_:
# wrap not links into final link => link_
newtext = linkify_others(newtext, link_)
pass
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: