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 | 'user': username, | |||
96 |
|
|
97 | 'pass': password, | |
97 |
|
|
98 | 'scheme': parsed_url.scheme, | |
98 |
|
|
99 | 'netloc': parsed_url.netloc, | |
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 | 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,6 +27,7 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): | |
@@ -37,6 +39,28 b' class BaseVCSController(object):' | |||||
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 |
@@ -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 |
|
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', |
|
12 | action='index', | |
|
13 | repo_name=HG_REPO)) | |||
11 |
|
14 | |||
12 | #repo type |
|
15 | #repo type | |
13 |
se |
|
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 |
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 | 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