@@ -18,48 +18,51 @@ news
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.
- 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
fixes
+++++
- 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
@@ -252,49 +252,48 @@ def engine_from_config(configuration, pr
return efc(configuration, prefix, **kwargs)
else:
import time
from sqlalchemy import event
from sqlalchemy.engine import Engine
log = logging.getLogger('sqlalchemy.engine')
BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
engine = efc(configuration, prefix, **kwargs)
def color_sql(sql):
COLOR_SEQ = "\033[1;%dm"
COLOR_SQL = YELLOW
normal = '\x1b[0m'
return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
if configuration['debug']:
#attach events only for debug configuration
def before_cursor_execute(conn, cursor, statement,
parameters, context, executemany):
context._query_start_time = time.time()
log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
def after_cursor_execute(conn, cursor, statement,
total = time.time() - context._query_start_time
log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
event.listen(engine, "before_cursor_execute",
before_cursor_execute)
event.listen(engine, "after_cursor_execute",
after_cursor_execute)
return engine
def age(prevdate):
"""
turns a datetime into an age string.
:param prevdate: datetime object
:rtype: unicode
:returns: unicode words describing age
order = ['year', 'month', 'day', 'hour', 'minute', 'second']
deltas = {}
Set of generic validators
import os
import re
import formencode
import logging
from collections import defaultdict
from pylons.i18n.translation import _
from webhelpers.pylonslib.secure_form import authentication_token
from formencode.validators import (
UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
NotEmpty
)
from rhodecode.lib.compat import OrderedSet
from rhodecode.lib.utils import repo_name_slug
from rhodecode.model.db import RepoGroup, Repository, UsersGroup, User,\
ChangesetStatus
from rhodecode.lib.exceptions import LdapImportError
from rhodecode.config.routing import ADMIN_PREFIX
# silence warnings and pylint
UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
log = logging.getLogger(__name__)
class UniqueList(formencode.FancyValidator):
Unique List !
messages = dict(
empty=_('Value cannot be an empty list'),
missing_value=_('Value cannot be an empty list'),
def _to_python(self, value, state):
if isinstance(value, list):
@@ -456,75 +458,89 @@ def ValidForkType(old_data={}):
}
def validate_python(self, value, state):
if old_data['repo_type'] != value:
msg = M(self, 'invalid_fork_type', state)
raise formencode.Invalid(msg, value, state,
error_dict=dict(repo_type=msg)
return _validator
def ValidPerms(type_='repo'):
if type_ == 'group':
EMPTY_PERM = 'group.none'
elif type_ == 'repo':
EMPTY_PERM = 'repository.none'
class _validator(formencode.validators.FancyValidator):
messages = {
'perm_new_member_name':
_(u'This username or users group name is not valid')
def to_python(self, value, state):
perms_update = []
perms_new = []
perms_update = OrderedSet()
perms_new = OrderedSet()
# build a list of permission to update and new permission to create
for k, v in value.items():
# means new added member to permissions
#CLEAN OUT ORG VALUE FROM NEW MEMBERS, and group them using
new_perms_group = defaultdict(dict)
for k, v in value.copy().iteritems():
if k.startswith('perm_new_member'):
new_perm = value.get('perm_new_member', False)
new_member = value.get('perm_new_member_name', False)
new_type = value.get('perm_new_member_type')
del value[k]
_type, part = k.split('perm_new_member_')
args = part.split('_')
if len(args) == 1:
new_perms_group[args[0]]['perm'] = v
elif len(args) == 2:
_key, pos = args
new_perms_group[pos][_key] = v
if new_member and new_perm:
if (new_member, new_perm, new_type) not in perms_new:
perms_new.append((new_member, new_perm, new_type))
elif k.startswith('u_perm_') or k.startswith('g_perm_'):
# fill new permissions in order of how they were added
for k in sorted(map(int, new_perms_group.keys())):
perm_dict = new_perms_group[str(k)]
new_member = perm_dict['name']
new_perm = perm_dict['perm']
new_type = perm_dict['type']
if new_member and new_perm and new_type:
perms_new.add((new_member, new_perm, new_type))
for k, v in value.iteritems():
if k.startswith('u_perm_') or k.startswith('g_perm_'):
member = k[7:]
t = {'u': 'user',
'g': 'users_group'
}[k[0]]
if member == 'default':
if value.get('private'):
# set none for default when updating to
# private repo
v = EMPTY_PERM
perms_update.append((member, v, t))
perms_update.add((member, v, t))
value['perms_updates'] = perms_update
value['perms_new'] = perms_new
value['perms_updates'] = list(perms_update)
value['perms_new'] = list(perms_new)
# update permissions
for k, v, t in perms_new:
try:
if t is 'user':
self.user_db = User.query()\
.filter(User.active == True)\
.filter(User.username == k).one()
if t is 'users_group':
self.user_db = UsersGroup.query()\
.filter(UsersGroup.users_group_active == True)\
.filter(UsersGroup.users_group_name == k).one()
except Exception:
log.exception('Updated permission failed')
msg = M(self, 'perm_new_member_type', state)
error_dict=dict(perm_new_member_name=msg)
return value
def ValidSettings():
@@ -887,49 +887,49 @@ var deleteNotification = function(url, n
var readNotification = function(url, notification_id,callbacks){
var callback = {
success:function(o){
var obj = YUD.get(String("notification_"+notification_id));
YUD.removeClass(obj, 'unread');
var r_button = YUD.getElementsByClassName('read-notification',null,obj.children[0])[0];
if(r_button.parentNode !== undefined){
r_button.parentNode.removeChild(r_button);
_run_callbacks(callbacks);
},
failure:function(o){
alert("error");
};
var postData = '_method=put';
var sUrl = url.replace('__NOTIFICATION_ID__',notification_id);
var request = YAHOO.util.Connect.asyncRequest('POST', sUrl,
callback, postData);
/** MEMBERS AUTOCOMPLETE WIDGET **/
var MembersAutoComplete = function (users_list, groups_list) {
var MembersAutoComplete = function (divid, cont, users_list, groups_list) {
var myUsers = users_list;
var myGroups = groups_list;
// Define a custom search function for the DataSource of users
var matchUsers = function (sQuery) {
// Case insensitive matching
var query = sQuery.toLowerCase();
var i = 0;
var l = myUsers.length;
var matches = [];
// Match against each name of each contact
for (; i < l; i++) {
contact = myUsers[i];
if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
matches[matches.length] = contact;
return matches;
// Define a custom search function for the DataSource of usersGroups
@@ -949,49 +949,49 @@ var MembersAutoComplete = function (user
//match all
var matchAll = function (sQuery) {
u = matchUsers(sQuery);
g = matchGroups(sQuery);
return u.concat(g);
// DataScheme for members
var memberDS = new YAHOO.util.FunctionDataSource(matchAll);
memberDS.responseSchema = {
fields: ["id", "fname", "lname", "nname", "grname", "grmembers", "gravatar_lnk"]
// DataScheme for owner
var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
ownerDS.responseSchema = {
fields: ["id", "fname", "lname", "nname", "gravatar_lnk"]
// Instantiate AutoComplete for perms
var membersAC = new YAHOO.widget.AutoComplete("perm_new_member_name", "perm_container", memberDS);
var membersAC = new YAHOO.widget.AutoComplete(divid, cont, memberDS);
membersAC.useShadow = false;
membersAC.resultTypeList = false;
membersAC.animVert = false;
membersAC.animHoriz = false;
membersAC.animSpeed = 0.1;
// Instantiate AutoComplete for owner
var ownerAC = new YAHOO.widget.AutoComplete("user", "owner_container", ownerDS);
ownerAC.useShadow = false;
ownerAC.resultTypeList = false;
ownerAC.animVert = false;
ownerAC.animHoriz = false;
ownerAC.animSpeed = 0.1;
// Helper highlight function for the formatter
var highlightMatch = function (full, snippet, matchindex) {
return full.substring(0, matchindex)
+ "<span class='match'>"
+ full.substr(matchindex, snippet.length)
+ "</span>" + full.substring(matchindex + snippet.length);
// Custom formatter to highlight the matching letters
var custom_formatter = function (oResultData, sQuery, sResultMatch) {
@@ -1034,61 +1034,61 @@ var MembersAutoComplete = function (user
displayfname = fname;
if (lnameMatchIndex > -1) {
displaylname = highlightMatch(lname, query, lnameMatchIndex);
} else {
displaylname = lname;
if (nnameMatchIndex > -1) {
displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
displaynname = nname ? "(" + nname + ")" : "";
return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk);
return '';
membersAC.formatResult = custom_formatter;
ownerAC.formatResult = custom_formatter;
var myHandler = function (sType, aArgs) {
var nextId = divid.split('perm_new_member_name_')[1];
var myAC = aArgs[0]; // reference back to the AC instance
var elLI = aArgs[1]; // reference to the selected LI element
var oData = aArgs[2]; // object literal of selected item's result data
//fill the autocomplete with value
if (oData.nname != undefined) {
//users
myAC.getInputEl().value = oData.nname;
YUD.get('perm_new_member_type').value = 'user';
YUD.get('perm_new_member_type_'+nextId).value = 'user';
//groups
myAC.getInputEl().value = oData.grname;
YUD.get('perm_new_member_type').value = 'users_group';
YUD.get('perm_new_member_type_'+nextId).value = 'users_group';
membersAC.itemSelectEvent.subscribe(myHandler);
if(ownerAC.itemSelectEvent){
ownerAC.itemSelectEvent.subscribe(myHandler);
return {
memberDS: memberDS,
ownerDS: ownerDS,
membersAC: membersAC,
ownerAC: ownerAC,
var MentionsAutoComplete = function (divid, cont, users_list, groups_list) {
var org_sQuery = sQuery;
@@ -1581,48 +1581,67 @@ var dateSort = function(a, b, desc, fiel
var b_ = fromHTML(b.getData(field));
// extract name from table
a_ = get_date(a_)
b_ = get_date(b_)
var comp = YAHOO.util.Sort.compare;
var compState = comp(a_, b_, desc);
return compState;
var linkSort = function(a, b, desc, field) {
var a_ = fromHTML(a.getData(field));
var b_ = fromHTML(a.getData(field));
// extract url text from string nodes
a_ = get_link(a_)
b_ = get_link(b_)
var addPermAction = function(_html, users_list, groups_list){
var elmts = YUD.getElementsByClassName('last_new_member');
var last_node = elmts[elmts.length-1];
if (last_node){
var next_id = (YUD.getElementsByClassName('new_members')).length;
_html = _html.format(next_id);
last_node.innerHTML = _html;
YUD.setStyle(last_node, 'display', '');
YUD.removeClass(last_node, 'last_new_member');
MembersAutoComplete("perm_new_member_name_"+next_id,
"perm_container_"+next_id, users_list, groups_list);
//create new last NODE
var el = document.createElement('tr');
el.id = 'add_perm_input';
YUD.addClass(el,'last_new_member');
YUD.addClass(el,'new_members');
YUD.insertAfter(el, last_node);
/* Multi selectors */
var MultiSelectWidget = function(selected_id, available_id, form_id){
//definition of containers ID's
var selected_container = selected_id;
var available_container = available_id;
//temp container for selected storage.
var cache = new Array();
var av_cache = new Array();
var c = YUD.get(selected_container);
var ac = YUD.get(available_container);
//get only selected options for further fullfilment
for(var i = 0;node =c.options[i];i++){
if(node.selected){
//push selected to my temp storage left overs :)
cache.push(node);
@@ -39,90 +39,90 @@
%endfor
## USERS GROUPS
%for g2p in c.repo_info.users_group_to_perm:
<tr id="id${id(g2p.users_group.users_group_name)}">
<td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.none')}</td>
<td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.read')}</td>
<td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.write')}</td>
<td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.admin')}</td>
<td style="white-space: nowrap;">
<img class="perm-gravatar" src="${h.url('/images/icons/group.png')}"/>
%if h.HasPermissionAny('hg.admin')():
<a href="${h.url('edit_users_group',id=g2p.users_group.users_group_id)}">${g2p.users_group.users_group_name}</a>
%else:
${g2p.users_group.users_group_name}
%endif
</td>
<td>
<span class="delete_icon action_button" onclick="ajaxActionUsersGroup(${g2p.users_group.users_group_id},'${'id%s'%id(g2p.users_group.users_group_name)}')">
${_('revoke')}
</span>
</tr>
<tr id="add_perm_input">
<td>${h.radio('perm_new_member','repository.none')}</td>
<td>${h.radio('perm_new_member','repository.read')}</td>
<td>${h.radio('perm_new_member','repository.write')}</td>
<td>${h.radio('perm_new_member','repository.admin')}</td>
<td class='ac'>
<div class="perm_ac" id="perm_ac">
${h.text('perm_new_member_name',class_='yui-ac-input')}
${h.hidden('perm_new_member_type')}
<div id="perm_container"></div>
</div>
<td></td>
<%
_tmpl = h.literal("""' \
<td><input type="radio" value="repository.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
<td><input type="radio" value="repository.read" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
<td><input type="radio" value="repository.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
<td><input type="radio" value="repository.admin" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
<td class="ac"> \
<div class="perm_ac" id="perm_ac_{0}"> \
<input class="yui-ac-input" id="perm_new_member_name_{0}" name="perm_new_member_name_{0}" value="" type="text"> \
<input id="perm_new_member_type_{0}" name="perm_new_member_type_{0}" value="" type="hidden"> \
<div id="perm_container_{0}"></div> \
</div> \
</td> \
<td></td>'""")
%>
## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
<tr class="new_members last_new_member" id="add_perm_input"></tr>
<tr>
<td colspan="6">
<span id="add_perm" class="add_icon" style="cursor: pointer;">
${_('Add another member')}
</table>
<script type="text/javascript">
function ajaxActionUser(user_id, field_id) {
var sUrl = "${h.url('delete_repo_user',repo_name=c.repo_name)}";
success: function (o) {
var tr = YUD.get(String(field_id));
tr.parentNode.removeChild(tr);
failure: function (o) {
alert("${_('Failed to remove user')}");
var postData = '_method=delete&user_id=' + user_id;
var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
function ajaxActionUsersGroup(users_group_id,field_id){
var sUrl = "${h.url('delete_repo_users_group',repo_name=c.repo_name)}";
alert("${_('Failed to remove users group')}");
var postData = '_method=delete&users_group_id='+users_group_id;
YUE.onDOMReady(function () {
if (!YUD.hasClass('perm_new_member_name', 'error')) {
YUD.setStyle('add_perm_input', 'display', 'none');
YAHOO.util.Event.addListener('add_perm', 'click', function () {
YUD.setStyle('add_perm_input', 'display', '');
YUD.setStyle('add_perm', 'opacity', '0.6');
YUD.setStyle('add_perm', 'cursor', 'default');
addPermAction(${_tmpl}, ${c.users_array|n}, ${c.users_groups_array|n});
});
MembersAutoComplete(${c.users_array|n}, ${c.users_groups_array|n});
</script>
@@ -23,90 +23,90 @@
%for g2p in c.repos_group.users_group_to_perm:
<td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.none')}</td>
<td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.read')}</td>
<td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.write')}</td>
<td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.admin')}</td>
<img class="perm-gravatar" src="${h.url('/images/icons/group.png')}"/>${g2p.users_group.users_group_name}
<td>${h.radio('perm_new_member','group.none')}</td>
<td>${h.radio('perm_new_member','group.read')}</td>
<td>${h.radio('perm_new_member','group.write')}</td>
<td>${h.radio('perm_new_member','group.admin')}</td>
<td><input type="radio" value="group.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
<td><input type="radio" value="group.read" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
<td><input type="radio" value="group.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
<td><input type="radio" value="group.admin" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
var sUrl = "${h.url('delete_repos_group_user_perm',group_name=c.repos_group.group_name)}";
var sUrl = "${h.url('delete_repos_group_users_group_perm',group_name=c.repos_group.group_name)}";
Status change: