@@ -27,24 +27,49 @@ Compare view from changelog
---------------------------
Checkboxes in compare view allow users to view combined compare view. You can
only show the range between the first and last checkbox (no cherry pick).
Clicking more than one checkbox will activate a link in top saying
`Show selected changes <from-rev> -> <to-rev>` clicking this will bring
compare view
Compare view is also available from the journal on pushes having more than
one changeset
Non changeable repository urls
------------------------------
Due to complicated nature of repository grouping, often urls of repositories
can change.
example::
#before
http://server.com/repo_name
# after insertion to test_group group the url will be
http://server.com/test_group/repo_name
This can be an issue for build systems and any other hardcoded scripts, moving
repository to a group leads to a need for changing external systems. To
overcome this RhodeCode introduces a non changable replacement url. It's
simply an repository ID prefixed with `_` above urls are also accessible as::
http://server.com/_<ID>
Since ID are always the same moving the repository will not affect such url.
the _<ID> syntax can be used anywhere in the system so urls with repo_name
for changelogs, files and other can be exchanged with _<ID> syntax.
Mailing
-------
When administrator will fill up the mailing settings in .ini files
RhodeCode will send mails on user registration, or when RhodeCode errors occur
on errors the mails will have a detailed traceback of error.
Trending source files
---------------------
"""
Routes configuration
The more specific and detailed routes should be defined first so they
may take precedent over the more generic routes. For more information
refer to the routes manual at http://routes.groovie.org/docs/
from __future__ import with_statement
from routes import Mapper
# prefix for non repository related links needs to be prefixed with `/`
ADMIN_PREFIX = '/_admin'
def make_map(config):
"""Create, configure and return the routes Mapper"""
rmap = Mapper(directory=config['pylons.paths']['controllers'],
always_scan=config['debug'])
rmap.minimization = False
rmap.explicit = False
from rhodecode.lib.utils import is_valid_repo
from rhodecode.lib.utils import is_valid_repos_group
def check_repo(environ, match_dict):
check for valid repository for proper 404 handling
:param environ:
:param match_dict:
from rhodecode.model.db import Repository
repo_name = match_dict.get('repo_name')
try:
by_id = repo_name.split('_')
if len(by_id) == 2 and by_id[1].isdigit():
repo_name = Repository.get(by_id[1]).repo_name
match_dict['repo_name'] = repo_name
except:
pass
return is_valid_repo(repo_name, config['base_path'])
def check_group(environ, match_dict):
check for valid repositories group for proper 404 handling
repos_group_name = match_dict.get('group_name')
return is_valid_repos_group(repos_group_name, config['base_path'])
@@ -83,31 +83,38 @@ class SummaryController(BaseRepoControll
password = ''
else:
username = str(self.rhodecode_user.username)
password = '@'
parsed_url = urlparse(url.current(qualified=True))
default_clone_uri = '{scheme}://{user}{pass}{netloc}{path}'
uri_tmpl = config.get('clone_uri', default_clone_uri)
uri_tmpl = uri_tmpl.replace('{', '%(').replace('}', ')s')
uri = uri_tmpl % {'user': username,
'pass': password,
'scheme': parsed_url.scheme,
'netloc': parsed_url.netloc,
'path':parsed_url.path}
uri_dict = {
'user': username,
'path': parsed_url.path
}
uri = uri_tmpl % uri_dict
# generate another clone url by id
uri_dict.update({'path': '/_%s' % c.dbrepo.repo_id})
uri_id = uri_tmpl % uri_dict
c.clone_repo_url = uri
c.clone_repo_url_id = uri_id
c.repo_tags = OrderedDict()
for name, hash in c.rhodecode_repo.tags.items()[:10]:
c.repo_tags[name] = c.rhodecode_repo.get_changeset(hash)
except ChangesetError:
c.repo_tags[name] = EmptyChangeset(hash)
c.repo_branches = OrderedDict()
for name, hash in c.rhodecode_repo.branches.items()[:10]:
c.repo_branches[name] = c.rhodecode_repo.get_changeset(hash)
"""The base Controller API
Provides the BaseController class for subclassing.
import logging
import time
import traceback
from paste.auth.basic import AuthBasicAuthenticator
from pylons import config, tmpl_context as c, request, session, url
from pylons.controllers import WSGIController
from pylons.controllers.util import redirect
from pylons.templating import render_mako as render
from rhodecode import __version__, BACKENDS
from rhodecode.lib import str2bool
from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
HasPermissionAnyMiddleware
from rhodecode.lib.utils import get_repo_slug, invalidate_cache
from rhodecode.model import meta
from rhodecode.model.notification import NotificationModel
from rhodecode.model.scm import ScmModel
log = logging.getLogger(__name__)
class BaseVCSController(object):
def __init__(self, application, config):
self.application = application
self.config = config
# base path of repo locations
self.basepath = self.config['base_path']
#authenticate this mercurial request using authfunc
self.authenticate = AuthBasicAuthenticator('', authfunc)
self.ipaddr = '0.0.0.0'
def _get_by_id(self, repo_name):
Get's a special pattern _<ID> from clone url and tries to replace it
with a repository_name for support of _<ID> non changable urls
:param repo_name:
data = repo_name.split('/')
if len(data) >= 2:
by_id = data[1].split('_')
_repo_name = Repository.get(by_id[1]).repo_name
data[1] = _repo_name
log.debug('Failed to extract repo_name from id %s' % (
traceback.format_exc()
)
return '/'.join(data)
def _invalidate_cache(self, repo_name):
Set's cache for this repository for invalidation on next access
:param repo_name: full repo name, also a cache key
invalidate_cache('get_repo_cached_%s' % repo_name)
def _check_permission(self, action, user, repo_name):
Checks permissions using action (push/pull) user and repository
name
:param action: push or pull action
:param user: user instance
:param repo_name: repository name
if action == 'push':
if not HasPermissionAnyMiddleware('repository.write',
'repository.admin')(user,
repo_name):
return False
#any other action need at least read permission
if not HasPermissionAnyMiddleware('repository.read',
'repository.write',
return True
def __call__(self, environ, start_response):
start = time.time()
return self._handle_request(environ, start_response)
finally:
log = logging.getLogger('rhodecode.' + self.__class__.__name__)
log.debug('Request time: %.3fs' % (time.time() - start))
meta.Session.remove()
class BaseController(WSGIController):
@@ -208,24 +208,25 @@ class SimpleGit(BaseVCSController):
backend = dulserver.DictBackend(_d)
gitserve = HTTPGitApplication(backend)
return gitserve
def __get_repository(self, environ):
Get's repository name out of PATH_INFO header
:param environ: environ where PATH_INFO is stored
environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
if repo_name.endswith('/'):
repo_name = repo_name.rstrip('/')
log.error(traceback.format_exc())
raise
repo_name = repo_name.split('/')[0]
return repo_name
def __get_user(self, username):
return User.get_by_username(username)
@@ -171,32 +171,32 @@ class SimpleHg(BaseVCSController):
return HTTPNotFound()(environ, start_response)
except Exception:
return HTTPInternalServerError()(environ, start_response)
def __make_app(self, repo_name, baseui, extras):
Make an wsgi application using hgweb, and inject generated baseui
instance, additionally inject some extras into ui object
return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
@@ -1341,27 +1341,29 @@ tbody .yui-dt-editable { cursor: pointer
#content div.box div.form div.fields div.field div.input input {
background: #FFF;
border-top: 1px solid #b3b3b3;
border-left: 1px solid #b3b3b3;
border-right: 1px solid #eaeaea;
border-bottom: 1px solid #eaeaea;
color: #000;
font-size: 11px;
margin: 0;
padding: 7px 7px 6px;
#content div.box div.form div.fields div.field div.input input#clone_url{
#content div.box div.form div.fields div.field div.input input#clone_url,
#content div.box div.form div.fields div.field div.input input#clone_url_id
{
font-size: 16px;
padding: 2px 7px 2px;
padding: 2px;
#content div.box div.form div.fields div.field div.file input {
background: none repeat scroll 0 0 #FFFFFF;
border-color: #B3B3B3 #EAEAEA #EAEAEA #B3B3B3;
border-style: solid;
border-width: 1px;
color: #000000;
@@ -3025,25 +3027,36 @@ div.gravatar img {
-webkit-border-radius: 4px 4px 4px 4px !important;
-khtml-border-radius: 4px 4px 4px 4px !important;
-moz-border-radius: 4px 4px 4px 4px !important;
border-radius: 4px 4px 4px 4px !important;
cursor: pointer !important;
padding: 3px 3px 3px 3px;
background-position: 0 -15px;
.ui-btn.xsmall{
padding: 1px 2px 1px 1px;
.ui-btn.clone{
padding: 5px 2px 6px 1px;
margin: 0px -4px 3px 0px;
-webkit-border-radius: 4px 0px 0px 4px !important;
-khtml-border-radius: 4px 0px 0px 4px !important;
-moz-border-radius: 4px 0px 0px 4px !important;
border-radius: 4px 0px 0px 4px !important;
width: 100px;
text-align: center;
float: left;
position: absolute;
.ui-btn:focus {
outline: none;
.ui-btn:hover{
background-position: 0 0px;
text-decoration: none;
color: #515151;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25), 0 0 3px #FFFFFF !important;
.ui-btn.red{
color:#fff;
@@ -3091,25 +3104,26 @@ div.gravatar img {
border-color: #57a957 #57a957 #3d773d;
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
ins,div.options a:hover {
img,
#header #header-inner #quick li a:hover span.normal,
#header #header-inner #quick li ul li.last,
#content div.box div.form div.fields div.field div.textarea table td table td a,
#clone_url
#clone_url,
#clone_url_id
border: none;
img.icon,.right .merge img {
vertical-align: bottom;
#header ul#logged-user,#content div.box div.title ul.links,
#content div.box div.message div.dismiss,
#content div.box div.traffic div.legend ul
@@ -65,25 +65,25 @@
%if c.dbrepo.repo_type =='git':
<img style="margin-bottom:2px" class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
%endif
##PUBLIC/PRIVATE
%if c.dbrepo.private:
<img style="margin-bottom:2px" class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/>
%else:
<img style="margin-bottom:2px" class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
##REPO NAME
<span class="repo_name">${h.repo_link(c.dbrepo.groups_and_repo)}</span>
<span class="repo_name" title="${_('Non changable ID %s') % c.dbrepo.repo_id}">${h.repo_link(c.dbrepo.groups_and_repo)}</span>
##FORK
%if c.dbrepo.fork:
<div style="margin-top:5px;clear:both"">
<a href="${h.url('summary_home',repo_name=c.dbrepo.fork.repo_name)}"><img class="icon" alt="${_('public')}" title="${_('Fork of')} ${c.dbrepo.fork.repo_name}" src="${h.url('/images/icons/arrow_divide.png')}"/>
${_('Fork of')} ${c.dbrepo.fork.repo_name}
</a>
</div>
##REMOTE
%if c.dbrepo.clone_uri:
<div style="margin-top:5px;clear:both">
@@ -112,25 +112,28 @@
${_('Username')}: ${c.dbrepo.user.username}<br/>
${_('Name')}: ${c.dbrepo.user.name} ${c.dbrepo.user.lastname}<br/>
${_('Email')}: <a href="mailto:${c.dbrepo.user.email}">${c.dbrepo.user.email}</a>
<div class="field">
<div class="label-summary">
<label>${_('Clone url')}:</label>
<div class="input ${summary(c.show_stats)}">
<input type="text" id="clone_url" readonly="readonly" value="${c.clone_repo_url}" size="70"/>
<div style="display:none" id="clone_by_name" class="ui-btn clone">${_('Show by Name')}</div>
<div id="clone_by_id" class="ui-btn clone">${_('Show by ID')}</div>
<input style="width:80%;margin-left:105px" type="text" id="clone_url" readonly="readonly" value="${c.clone_repo_url}"/>
<input style="display:none;width:80%;margin-left:105px" type="text" id="clone_url_id" readonly="readonly" value="${c.clone_repo_url_id}"/>
<label>${_('Trending files')}:</label>
%if c.show_stats:
<div id="lang_stats"></div>
${_('Statistics are disabled for this repository')}
@@ -231,24 +234,46 @@
<script type="text/javascript">
var clone_url = 'clone_url';
YUE.on(clone_url,'click',function(e){
if(YUD.hasClass(clone_url,'selected')){
return
else{
YUD.addClass(clone_url,'selected');
YUD.get(clone_url).select();
})
YUE.on('clone_by_name','click',function(e){
// show url by name and hide name button
YUD.setStyle('clone_url','display','');
YUD.setStyle('clone_by_name','display','none');
// hide url by id and show name button
YUD.setStyle('clone_by_id','display','');
YUD.setStyle('clone_url_id','display','none');
YUE.on('clone_by_id','click',function(e){
// show url by id and hide id button
YUD.setStyle('clone_by_id','display','none');
YUD.setStyle('clone_url_id','display','');
// hide url by name and show id button
YUD.setStyle('clone_by_name','display','');
YUD.setStyle('clone_url','display','none');
var tmpl_links = {};
%for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
tmpl_links["${archive['type']}"] = '${h.link_to('__NAME__', h.url('files_archive_home',repo_name=c.dbrepo.repo_name, fname='__CS__'+archive['extension'],subrepos='__SUB__'),class_='archive_icon ui-btn')}';
%endfor
YUE.on(['download_options','archive_subrepos'],'change',function(e){
var sm = YUD.get('download_options');
var new_cs = sm.options[sm.selectedIndex];
for(k in tmpl_links){
var s = YUD.get(k+'_link');
if(s){
@@ -159,48 +159,52 @@ def test_clone_with_credentials(no_error
cwd = path = jn(TESTS_TMP_PATH, repo)
if seq == None:
seq = _RandomNameSequence().next()
shutil.rmtree(path, ignore_errors=True)
os.makedirs(path)
#print 'made dirs %s' % jn(path)
except OSError:
clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s' % \
{'user':USER,
'pass':PASS,
'host':HOST,
'cloned_repo':repo, }
dest = path + seq
if method == 'pull':
stdout, stderr = Command(cwd).execute('hg', method, '--cwd', dest, clone_url)
stdout, stderr = Command(cwd).execute('hg', method, clone_url, dest)
if no_errors is False:
assert """adding file changes""" in stdout, 'no messages about cloning'
assert """abort""" not in stderr , 'got error from clone'
if __name__ == '__main__':
create_test_user(force=False)
seq = None
METHOD = sys.argv[3]
if METHOD == 'pull':
test_clone_with_credentials(repo=sys.argv[1], method='clone',
seq=seq)
s = time.time()
for i in range(int(sys.argv[2])):
for i in range(1, int(sys.argv[2]) + 1):
print 'take', i
test_clone_with_credentials(repo=sys.argv[1], method=METHOD,
print 'time taken %.3f' % (time.time() - s)
except Exception, e:
sys.exit('stop on %s' % e)
from rhodecode.tests import *
from rhodecode.lib.utils import invalidate_cache
class TestSummaryController(TestController):
def test_index(self):
self.log_user()
ID = Repository.get_by_repo_name(HG_REPO).repo_id
response = self.app.get(url(controller='summary',
action='index', repo_name=HG_REPO))
action='index',
repo_name=HG_REPO))
#repo type
self.assertTrue("""<img style="margin-bottom:2px" class="icon" """
response.mustcontain("""<img style="margin-bottom:2px" class="icon" """
"""title="Mercurial repository" alt="Mercurial """
"""repository" src="/images/icons/hgicon.png"/>"""
in response.body)
"""repository" src="/images/icons/hgicon.png"/>""")
"""title="public repository" alt="public """
"""repository" src="/images/icons/lock_open.png"/>"""
"""repository" src="/images/icons/lock_open.png"/>""")
#codes stats
self._enable_stats()
invalidate_cache('get_repo_cached_%s' % HG_REPO)
response = self.app.get(url(controller='summary', action='index',
self.assertTrue("""var data = {"py": {"count": 42, "desc": """
"""["Python"]}, "rst": {"count": 11, "desc": """
"""["Rst"]}, "sh": {"count": 2, "desc": ["Bash"]}, """
""""makefile": {"count": 1, "desc": ["Makefile", """
""""Makefile"]}, "cfg": {"count": 1, "desc": ["Ini"]},"""
""" "css": {"count": 1, "desc": ["Css"]}, "bat": """
"""{"count": 1, "desc": ["Batch"]}};"""
# clone url...
self.assertTrue("""<input type="text" id="clone_url" readonly="readonly" value="http://test_admin@localhost:80/%s" size="70"/>""" % HG_REPO in response.body)
response.mustcontain("""<input style="width:80%;margin-left:105px" type="text" id="clone_url" readonly="readonly" value="http://test_admin@localhost:80/vcs_test_hg"/>""")
response.mustcontain("""<input style="display:none;width:80%;margin-left:105px" type="text" id="clone_url_id" readonly="readonly" value="http://test_admin@localhost:80/_1"/>""")
def test_index_by_id(self):
repo_name='_%s' % ID))
def _enable_stats(self):
r = Repository.get_by_repo_name(HG_REPO)
r.enable_statistics = True
self.Session.add(r)
self.Session.commit()
Status change: