##// END OF EJS Templates
implements #285: Implemented non changeable urls for clone url, and web views
marcink -
r1813:a8c66e87 beta
parent child Browse files
Show More
@@ -36,6 +36,31 b' Compare view is also available from the '
36 one changeset
36 one changeset
37
37
38
38
39 Non changeable repository urls
40 ------------------------------
41
42 Due to complicated nature of repository grouping, often urls of repositories
43 can change.
44
45 example::
46
47 #before
48 http://server.com/repo_name
49 # after insertion to test_group group the url will be
50 http://server.com/test_group/repo_name
51
52 This can be an issue for build systems and any other hardcoded scripts, moving
53 repository to a group leads to a need for changing external systems. To
54 overcome this RhodeCode introduces a non changable replacement url. It's
55 simply an repository ID prefixed with `_` above urls are also accessible as::
56
57 http://server.com/_<ID>
58
59 Since ID are always the same moving the repository will not affect such url.
60 the _<ID> syntax can be used anywhere in the system so urls with repo_name
61 for changelogs, files and other can be exchanged with _<ID> syntax.
62
63
39
64
40 Mailing
65 Mailing
41 -------
66 -------
@@ -8,7 +8,6 b' refer to the routes manual at http://rou'
8 from __future__ import with_statement
8 from __future__ import with_statement
9 from routes import Mapper
9 from routes import Mapper
10
10
11
12 # prefix for non repository related links needs to be prefixed with `/`
11 # prefix for non repository related links needs to be prefixed with `/`
13 ADMIN_PREFIX = '/_admin'
12 ADMIN_PREFIX = '/_admin'
14
13
@@ -30,8 +29,17 b' def make_map(config):'
30 :param environ:
29 :param environ:
31 :param match_dict:
30 :param match_dict:
32 """
31 """
32 from rhodecode.model.db import Repository
33 repo_name = match_dict.get('repo_name')
33
34
34 repo_name = match_dict.get('repo_name')
35 try:
36 by_id = repo_name.split('_')
37 if len(by_id) == 2 and by_id[1].isdigit():
38 repo_name = Repository.get(by_id[1]).repo_name
39 match_dict['repo_name'] = repo_name
40 except:
41 pass
42
35 return is_valid_repo(repo_name, config['base_path'])
43 return is_valid_repo(repo_name, config['base_path'])
36
44
37 def check_group(environ, match_dict):
45 def check_group(environ, match_dict):
@@ -92,13 +92,20 b' class SummaryController(BaseRepoControll'
92 uri_tmpl = config.get('clone_uri', default_clone_uri)
92 uri_tmpl = config.get('clone_uri', default_clone_uri)
93 uri_tmpl = uri_tmpl.replace('{', '%(').replace('}', ')s')
93 uri_tmpl = uri_tmpl.replace('{', '%(').replace('}', ')s')
94
94
95 uri = uri_tmpl % {'user': username,
95 uri_dict = {
96 'pass': password,
96 'user': username,
97 'scheme': parsed_url.scheme,
97 'pass': password,
98 'netloc': parsed_url.netloc,
98 'scheme': parsed_url.scheme,
99 'path':parsed_url.path}
99 'netloc': parsed_url.netloc,
100 'path': parsed_url.path
101 }
102 uri = uri_tmpl % uri_dict
103 # generate another clone url by id
104 uri_dict.update({'path': '/_%s' % c.dbrepo.repo_id})
105 uri_id = uri_tmpl % uri_dict
100
106
101 c.clone_repo_url = uri
107 c.clone_repo_url = uri
108 c.clone_repo_url_id = uri_id
102 c.repo_tags = OrderedDict()
109 c.repo_tags = OrderedDict()
103 for name, hash in c.rhodecode_repo.tags.items()[:10]:
110 for name, hash in c.rhodecode_repo.tags.items()[:10]:
104 try:
111 try:
@@ -4,6 +4,7 b' Provides the BaseController class for su'
4 """
4 """
5 import logging
5 import logging
6 import time
6 import time
7 import traceback
7
8
8 from paste.auth.basic import AuthBasicAuthenticator
9 from paste.auth.basic import AuthBasicAuthenticator
9
10
@@ -26,8 +27,9 b' from rhodecode.model.scm import ScmModel'
26
27
27 log = logging.getLogger(__name__)
28 log = logging.getLogger(__name__)
28
29
30
29 class BaseVCSController(object):
31 class BaseVCSController(object):
30
32
31 def __init__(self, application, config):
33 def __init__(self, application, config):
32 self.application = application
34 self.application = application
33 self.config = config
35 self.config = config
@@ -36,15 +38,37 b' class BaseVCSController(object):'
36 #authenticate this mercurial request using authfunc
38 #authenticate this mercurial request using authfunc
37 self.authenticate = AuthBasicAuthenticator('', authfunc)
39 self.authenticate = AuthBasicAuthenticator('', authfunc)
38 self.ipaddr = '0.0.0.0'
40 self.ipaddr = '0.0.0.0'
39
41
42 def _get_by_id(self, repo_name):
43 """
44 Get's a special pattern _<ID> from clone url and tries to replace it
45 with a repository_name for support of _<ID> non changable urls
46
47 :param repo_name:
48 """
49 try:
50 data = repo_name.split('/')
51 if len(data) >= 2:
52 by_id = data[1].split('_')
53 if len(by_id) == 2 and by_id[1].isdigit():
54 _repo_name = Repository.get(by_id[1]).repo_name
55 data[1] = _repo_name
56 except:
57 log.debug('Failed to extract repo_name from id %s' % (
58 traceback.format_exc()
59 )
60 )
61
62 return '/'.join(data)
63
40 def _invalidate_cache(self, repo_name):
64 def _invalidate_cache(self, repo_name):
41 """
65 """
42 Set's cache for this repository for invalidation on next access
66 Set's cache for this repository for invalidation on next access
43
67
44 :param repo_name: full repo name, also a cache key
68 :param repo_name: full repo name, also a cache key
45 """
69 """
46 invalidate_cache('get_repo_cached_%s' % repo_name)
70 invalidate_cache('get_repo_cached_%s' % repo_name)
47
71
48 def _check_permission(self, action, user, repo_name):
72 def _check_permission(self, action, user, repo_name):
49 """
73 """
50 Checks permissions using action (push/pull) user and repository
74 Checks permissions using action (push/pull) user and repository
@@ -68,8 +92,8 b' class BaseVCSController(object):'
68 repo_name):
92 repo_name):
69 return False
93 return False
70
94
71 return True
95 return True
72
96
73 def __call__(self, environ, start_response):
97 def __call__(self, environ, start_response):
74 start = time.time()
98 start = time.time()
75 try:
99 try:
@@ -217,6 +217,7 b' class SimpleGit(BaseVCSController):'
217 :param environ: environ where PATH_INFO is stored
217 :param environ: environ where PATH_INFO is stored
218 """
218 """
219 try:
219 try:
220 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
220 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
221 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
221 if repo_name.endswith('/'):
222 if repo_name.endswith('/'):
222 repo_name = repo_name.rstrip('/')
223 repo_name = repo_name.rstrip('/')
@@ -180,7 +180,6 b' class SimpleHg(BaseVCSController):'
180 """
180 """
181 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
181 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
182
182
183
184 def __get_repository(self, environ):
183 def __get_repository(self, environ):
185 """
184 """
186 Get's repository name out of PATH_INFO header
185 Get's repository name out of PATH_INFO header
@@ -188,6 +187,7 b' class SimpleHg(BaseVCSController):'
188 :param environ: environ where PATH_INFO is stored
187 :param environ: environ where PATH_INFO is stored
189 """
188 """
190 try:
189 try:
190 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
191 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
191 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
192 if repo_name.endswith('/'):
192 if repo_name.endswith('/'):
193 repo_name = repo_name.rstrip('/')
193 repo_name = repo_name.rstrip('/')
@@ -1350,9 +1350,11 b' tbody .yui-dt-editable { cursor: pointer'
1350 padding: 7px 7px 6px;
1350 padding: 7px 7px 6px;
1351 }
1351 }
1352
1352
1353 #content div.box div.form div.fields div.field div.input input#clone_url{
1353 #content div.box div.form div.fields div.field div.input input#clone_url,
1354 #content div.box div.form div.fields div.field div.input input#clone_url_id
1355 {
1354 font-size: 16px;
1356 font-size: 16px;
1355 padding: 2px 7px 2px;
1357 padding: 2px;
1356 }
1358 }
1357
1359
1358 #content div.box div.form div.fields div.field div.file input {
1360 #content div.box div.form div.fields div.field div.file input {
@@ -3034,7 +3036,18 b' div.gravatar img {'
3034 .ui-btn.xsmall{
3036 .ui-btn.xsmall{
3035 padding: 1px 2px 1px 1px;
3037 padding: 1px 2px 1px 1px;
3036 }
3038 }
3037
3039 .ui-btn.clone{
3040 padding: 5px 2px 6px 1px;
3041 margin: 0px -4px 3px 0px;
3042 -webkit-border-radius: 4px 0px 0px 4px !important;
3043 -khtml-border-radius: 4px 0px 0px 4px !important;
3044 -moz-border-radius: 4px 0px 0px 4px !important;
3045 border-radius: 4px 0px 0px 4px !important;
3046 width: 100px;
3047 text-align: center;
3048 float: left;
3049 position: absolute;
3050 }
3038 .ui-btn:focus {
3051 .ui-btn:focus {
3039 outline: none;
3052 outline: none;
3040 }
3053 }
@@ -3100,7 +3113,8 b' img,'
3100 #header #header-inner #quick li a:hover span.normal,
3113 #header #header-inner #quick li a:hover span.normal,
3101 #header #header-inner #quick li ul li.last,
3114 #header #header-inner #quick li ul li.last,
3102 #content div.box div.form div.fields div.field div.textarea table td table td a,
3115 #content div.box div.form div.fields div.field div.textarea table td table td a,
3103 #clone_url
3116 #clone_url,
3117 #clone_url_id
3104 {
3118 {
3105 border: none;
3119 border: none;
3106 }
3120 }
@@ -74,7 +74,7 b''
74 %endif
74 %endif
75
75
76 ##REPO NAME
76 ##REPO NAME
77 <span class="repo_name">${h.repo_link(c.dbrepo.groups_and_repo)}</span>
77 <span class="repo_name" title="${_('Non changable ID %s') % c.dbrepo.repo_id}">${h.repo_link(c.dbrepo.groups_and_repo)}</span>
78
78
79 ##FORK
79 ##FORK
80 %if c.dbrepo.fork:
80 %if c.dbrepo.fork:
@@ -121,7 +121,10 b''
121 <label>${_('Clone url')}:</label>
121 <label>${_('Clone url')}:</label>
122 </div>
122 </div>
123 <div class="input ${summary(c.show_stats)}">
123 <div class="input ${summary(c.show_stats)}">
124 <input type="text" id="clone_url" readonly="readonly" value="${c.clone_repo_url}" size="70"/>
124 <div style="display:none" id="clone_by_name" class="ui-btn clone">${_('Show by Name')}</div>
125 <div id="clone_by_id" class="ui-btn clone">${_('Show by ID')}</div>
126 <input style="width:80%;margin-left:105px" type="text" id="clone_url" readonly="readonly" value="${c.clone_repo_url}"/>
127 <input style="display:none;width:80%;margin-left:105px" type="text" id="clone_url_id" readonly="readonly" value="${c.clone_repo_url_id}"/>
125 </div>
128 </div>
126 </div>
129 </div>
127
130
@@ -240,6 +243,28 b" YUE.on(clone_url,'click',function(e){"
240 }
243 }
241 })
244 })
242
245
246 YUE.on('clone_by_name','click',function(e){
247 // show url by name and hide name button
248 YUD.setStyle('clone_url','display','');
249 YUD.setStyle('clone_by_name','display','none');
250
251 // hide url by id and show name button
252 YUD.setStyle('clone_by_id','display','');
253 YUD.setStyle('clone_url_id','display','none');
254
255 })
256 YUE.on('clone_by_id','click',function(e){
257
258 // show url by id and hide id button
259 YUD.setStyle('clone_by_id','display','none');
260 YUD.setStyle('clone_url_id','display','');
261
262 // hide url by name and show id button
263 YUD.setStyle('clone_by_name','display','');
264 YUD.setStyle('clone_url','display','none');
265 })
266
267
243 var tmpl_links = {};
268 var tmpl_links = {};
244 %for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
269 %for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
245 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')}';
270 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')}';
@@ -168,7 +168,6 b' def test_clone_with_credentials(no_error'
168 except OSError:
168 except OSError:
169 raise
169 raise
170
170
171
172 clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s' % \
171 clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s' % \
173 {'user':USER,
172 {'user':USER,
174 'pass':PASS,
173 'pass':PASS,
@@ -191,12 +190,17 b" if __name__ == '__main__':"
191 seq = None
190 seq = None
192 import time
191 import time
193
192
193 try:
194 METHOD = sys.argv[3]
195 except:
196 pass
197
194 if METHOD == 'pull':
198 if METHOD == 'pull':
195 seq = _RandomNameSequence().next()
199 seq = _RandomNameSequence().next()
196 test_clone_with_credentials(repo=sys.argv[1], method='clone',
200 test_clone_with_credentials(repo=sys.argv[1], method='clone',
197 seq=seq)
201 seq=seq)
198 s = time.time()
202 s = time.time()
199 for i in range(int(sys.argv[2])):
203 for i in range(1, int(sys.argv[2]) + 1):
200 print 'take', i
204 print 'take', i
201 test_clone_with_credentials(repo=sys.argv[1], method=METHOD,
205 test_clone_with_credentials(repo=sys.argv[1], method=METHOD,
202 seq=seq)
206 seq=seq)
@@ -2,27 +2,27 b' from rhodecode.tests import *'
2 from rhodecode.model.db import Repository
2 from rhodecode.model.db import Repository
3 from rhodecode.lib.utils import invalidate_cache
3 from rhodecode.lib.utils import invalidate_cache
4
4
5
5 class TestSummaryController(TestController):
6 class TestSummaryController(TestController):
6
7
7 def test_index(self):
8 def test_index(self):
8 self.log_user()
9 self.log_user()
10 ID = Repository.get_by_repo_name(HG_REPO).repo_id
9 response = self.app.get(url(controller='summary',
11 response = self.app.get(url(controller='summary',
10 action='index', repo_name=HG_REPO))
12 action='index',
13 repo_name=HG_REPO))
11
14
12 #repo type
15 #repo type
13 self.assertTrue("""<img style="margin-bottom:2px" class="icon" """
16 response.mustcontain("""<img style="margin-bottom:2px" class="icon" """
14 """title="Mercurial repository" alt="Mercurial """
17 """title="Mercurial repository" alt="Mercurial """
15 """repository" src="/images/icons/hgicon.png"/>"""
18 """repository" src="/images/icons/hgicon.png"/>""")
16 in response.body)
19 response.mustcontain("""<img style="margin-bottom:2px" class="icon" """
17 self.assertTrue("""<img style="margin-bottom:2px" class="icon" """
18 """title="public repository" alt="public """
20 """title="public repository" alt="public """
19 """repository" src="/images/icons/lock_open.png"/>"""
21 """repository" src="/images/icons/lock_open.png"/>""")
20 in response.body)
21
22
22 #codes stats
23 #codes stats
23 self._enable_stats()
24 self._enable_stats()
24
25
25
26 invalidate_cache('get_repo_cached_%s' % HG_REPO)
26 invalidate_cache('get_repo_cached_%s' % HG_REPO)
27 response = self.app.get(url(controller='summary', action='index',
27 response = self.app.get(url(controller='summary', action='index',
28 repo_name=HG_REPO))
28 repo_name=HG_REPO))
@@ -37,8 +37,23 b' class TestSummaryController(TestControll'
37 in response.body)
37 in response.body)
38
38
39 # clone url...
39 # clone url...
40 self.assertTrue("""<input type="text" id="clone_url" readonly="readonly" value="http://test_admin@localhost:80/%s" size="70"/>""" % HG_REPO in response.body)
40 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"/>""")
41 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"/>""")
41
42
43 def test_index_by_id(self):
44 self.log_user()
45 ID = Repository.get_by_repo_name(HG_REPO).repo_id
46 response = self.app.get(url(controller='summary',
47 action='index',
48 repo_name='_%s' % ID))
49
50 #repo type
51 response.mustcontain("""<img style="margin-bottom:2px" class="icon" """
52 """title="Mercurial repository" alt="Mercurial """
53 """repository" src="/images/icons/hgicon.png"/>""")
54 response.mustcontain("""<img style="margin-bottom:2px" class="icon" """
55 """title="public repository" alt="public """
56 """repository" src="/images/icons/lock_open.png"/>""")
42
57
43 def _enable_stats(self):
58 def _enable_stats(self):
44 r = Repository.get_by_repo_name(HG_REPO)
59 r = Repository.get_by_repo_name(HG_REPO)
General Comments 0
You need to be logged in to leave comments. Login now