##// 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 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 65 Mailing
41 66 -------
@@ -8,7 +8,6 b' refer to the routes manual at http://rou'
8 8 from __future__ import with_statement
9 9 from routes import Mapper
10 10
11
12 11 # prefix for non repository related links needs to be prefixed with `/`
13 12 ADMIN_PREFIX = '/_admin'
14 13
@@ -30,8 +29,17 b' def make_map(config):'
30 29 :param environ:
31 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 43 return is_valid_repo(repo_name, config['base_path'])
36 44
37 45 def check_group(environ, match_dict):
@@ -92,13 +92,20 b' class SummaryController(BaseRepoControll'
92 92 uri_tmpl = config.get('clone_uri', default_clone_uri)
93 93 uri_tmpl = uri_tmpl.replace('{', '%(').replace('}', ')s')
94 94
95 uri = uri_tmpl % {'user': username,
95 uri_dict = {
96 'user': username,
96 97 'pass': password,
97 98 'scheme': parsed_url.scheme,
98 99 'netloc': parsed_url.netloc,
99 'path':parsed_url.path}
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 107 c.clone_repo_url = uri
108 c.clone_repo_url_id = uri_id
102 109 c.repo_tags = OrderedDict()
103 110 for name, hash in c.rhodecode_repo.tags.items()[:10]:
104 111 try:
@@ -4,6 +4,7 b' Provides the BaseController class for su'
4 4 """
5 5 import logging
6 6 import time
7 import traceback
7 8
8 9 from paste.auth.basic import AuthBasicAuthenticator
9 10
@@ -26,6 +27,7 b' from rhodecode.model.scm import ScmModel'
26 27
27 28 log = logging.getLogger(__name__)
28 29
30
29 31 class BaseVCSController(object):
30 32
31 33 def __init__(self, application, config):
@@ -37,6 +39,28 b' class BaseVCSController(object):'
37 39 self.authenticate = AuthBasicAuthenticator('', authfunc)
38 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 64 def _invalidate_cache(self, repo_name):
41 65 """
42 66 Set's cache for this repository for invalidation on next access
@@ -217,6 +217,7 b' class SimpleGit(BaseVCSController):'
217 217 :param environ: environ where PATH_INFO is stored
218 218 """
219 219 try:
220 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
220 221 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
221 222 if repo_name.endswith('/'):
222 223 repo_name = repo_name.rstrip('/')
@@ -180,7 +180,6 b' class SimpleHg(BaseVCSController):'
180 180 """
181 181 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
182 182
183
184 183 def __get_repository(self, environ):
185 184 """
186 185 Get's repository name out of PATH_INFO header
@@ -188,6 +187,7 b' class SimpleHg(BaseVCSController):'
188 187 :param environ: environ where PATH_INFO is stored
189 188 """
190 189 try:
190 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
191 191 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
192 192 if repo_name.endswith('/'):
193 193 repo_name = repo_name.rstrip('/')
@@ -1350,9 +1350,11 b' tbody .yui-dt-editable { cursor: pointer'
1350 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 1356 font-size: 16px;
1355 padding: 2px 7px 2px;
1357 padding: 2px;
1356 1358 }
1357 1359
1358 1360 #content div.box div.form div.fields div.field div.file input {
@@ -3034,7 +3036,18 b' div.gravatar img {'
3034 3036 .ui-btn.xsmall{
3035 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 3051 .ui-btn:focus {
3039 3052 outline: none;
3040 3053 }
@@ -3100,7 +3113,8 b' img,'
3100 3113 #header #header-inner #quick li a:hover span.normal,
3101 3114 #header #header-inner #quick li ul li.last,
3102 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 3119 border: none;
3106 3120 }
@@ -74,7 +74,7 b''
74 74 %endif
75 75
76 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 79 ##FORK
80 80 %if c.dbrepo.fork:
@@ -121,7 +121,10 b''
121 121 <label>${_('Clone url')}:</label>
122 122 </div>
123 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 128 </div>
126 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 268 var tmpl_links = {};
244 269 %for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
245 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 168 except OSError:
169 169 raise
170 170
171
172 171 clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s' % \
173 172 {'user':USER,
174 173 'pass':PASS,
@@ -191,12 +190,17 b" if __name__ == '__main__':"
191 190 seq = None
192 191 import time
193 192
193 try:
194 METHOD = sys.argv[3]
195 except:
196 pass
197
194 198 if METHOD == 'pull':
195 199 seq = _RandomNameSequence().next()
196 200 test_clone_with_credentials(repo=sys.argv[1], method='clone',
197 201 seq=seq)
198 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 204 print 'take', i
201 205 test_clone_with_credentials(repo=sys.argv[1], method=METHOD,
202 206 seq=seq)
@@ -2,27 +2,27 b' from rhodecode.tests import *'
2 2 from rhodecode.model.db import Repository
3 3 from rhodecode.lib.utils import invalidate_cache
4 4
5
5 6 class TestSummaryController(TestController):
6 7
7 8 def test_index(self):
8 9 self.log_user()
10 ID = Repository.get_by_repo_name(HG_REPO).repo_id
9 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 15 #repo type
13 self.assertTrue("""<img style="margin-bottom:2px" class="icon" """
16 response.mustcontain("""<img style="margin-bottom:2px" class="icon" """
14 17 """title="Mercurial repository" alt="Mercurial """
15 """repository" src="/images/icons/hgicon.png"/>"""
16 in response.body)
17 self.assertTrue("""<img style="margin-bottom:2px" class="icon" """
18 """repository" src="/images/icons/hgicon.png"/>""")
19 response.mustcontain("""<img style="margin-bottom:2px" class="icon" """
18 20 """title="public repository" alt="public """
19 """repository" src="/images/icons/lock_open.png"/>"""
20 in response.body)
21 """repository" src="/images/icons/lock_open.png"/>""")
21 22
22 23 #codes stats
23 24 self._enable_stats()
24 25
25
26 26 invalidate_cache('get_repo_cached_%s' % HG_REPO)
27 27 response = self.app.get(url(controller='summary', action='index',
28 28 repo_name=HG_REPO))
@@ -37,8 +37,23 b' class TestSummaryController(TestControll'
37 37 in response.body)
38 38
39 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 58 def _enable_stats(self):
44 59 r = Repository.get_by_repo_name(HG_REPO)
General Comments 0
You need to be logged in to leave comments. Login now