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 |
|
|
97 | 98 |
|
|
98 | 99 |
|
|
99 |
|
|
|
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 |
|
|
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', |
|
|
12 | action='index', | |
|
13 | repo_name=HG_REPO)) | |
|
11 | 14 | |
|
12 | 15 | #repo type |
|
13 |
se |
|
|
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 |
se |
|
|
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