##// END OF EJS Templates
reviewers: add repo review rule models and expose default...
dan -
r821:618c046d default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
@@ -0,0 +1,35 b''
1 import logging
2 import datetime
3
4 from sqlalchemy import *
5 from sqlalchemy.exc import DatabaseError
6 from sqlalchemy.orm import relation, backref, class_mapper, joinedload
7 from sqlalchemy.orm.session import Session
8 from sqlalchemy.ext.declarative import declarative_base
9
10 from rhodecode.lib.dbmigrate.migrate import *
11 from rhodecode.lib.dbmigrate.migrate.changeset import *
12 from rhodecode.lib.utils2 import str2bool
13
14 from rhodecode.model.meta import Base
15 from rhodecode.model import meta
16 from rhodecode.lib.dbmigrate.versions import _reset_base, notify
17
18 log = logging.getLogger(__name__)
19
20
21 def upgrade(migrate_engine):
22 """
23 Upgrade operations go here.
24 Don't create your own engine; bind migrate_engine to your metadata
25 """
26 _reset_base(migrate_engine)
27 from rhodecode.lib.dbmigrate.schema import db_4_4_0_2
28
29 db_4_4_0_2.RepoReviewRule.__table__.create()
30 db_4_4_0_2.RepoReviewRuleUser.__table__.create()
31 db_4_4_0_2.RepoReviewRuleUserGroup.__table__.create()
32
33 def downgrade(migrate_engine):
34 meta = MetaData()
35 meta.bind = migrate_engine
@@ -1,63 +1,63 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22
22
23 RhodeCode, a web based repository management software
23 RhodeCode, a web based repository management software
24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
25 """
25 """
26
26
27 import os
27 import os
28 import sys
28 import sys
29 import platform
29 import platform
30
30
31 VERSION = tuple(open(os.path.join(
31 VERSION = tuple(open(os.path.join(
32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
33
33
34 BACKENDS = {
34 BACKENDS = {
35 'hg': 'Mercurial repository',
35 'hg': 'Mercurial repository',
36 'git': 'Git repository',
36 'git': 'Git repository',
37 'svn': 'Subversion repository',
37 'svn': 'Subversion repository',
38 }
38 }
39
39
40 CELERY_ENABLED = False
40 CELERY_ENABLED = False
41 CELERY_EAGER = False
41 CELERY_EAGER = False
42
42
43 # link to config for pylons
43 # link to config for pylons
44 CONFIG = {}
44 CONFIG = {}
45
45
46 # Populated with the settings dictionary from application init in
46 # Populated with the settings dictionary from application init in
47 # rhodecode.conf.environment.load_pyramid_environment
47 # rhodecode.conf.environment.load_pyramid_environment
48 PYRAMID_SETTINGS = {}
48 PYRAMID_SETTINGS = {}
49
49
50 # Linked module for extensions
50 # Linked module for extensions
51 EXTENSIONS = {}
51 EXTENSIONS = {}
52
52
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
54 __dbversion__ = 58 # defines current db version for migrations
54 __dbversion__ = 59 # defines current db version for migrations
55 __platform__ = platform.system()
55 __platform__ = platform.system()
56 __license__ = 'AGPLv3, and Commercial License'
56 __license__ = 'AGPLv3, and Commercial License'
57 __author__ = 'RhodeCode GmbH'
57 __author__ = 'RhodeCode GmbH'
58 __url__ = 'http://rhodecode.com'
58 __url__ = 'http://rhodecode.com'
59
59
60 is_windows = __platform__ in ['Windows']
60 is_windows = __platform__ in ['Windows']
61 is_unix = not is_windows
61 is_unix = not is_windows
62 is_test = False
62 is_test = False
63 disable_error_handler = False
63 disable_error_handler = False
@@ -1,1160 +1,1167 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Routes configuration
22 Routes configuration
23
23
24 The more specific and detailed routes should be defined first so they
24 The more specific and detailed routes should be defined first so they
25 may take precedent over the more generic routes. For more information
25 may take precedent over the more generic routes. For more information
26 refer to the routes manual at http://routes.groovie.org/docs/
26 refer to the routes manual at http://routes.groovie.org/docs/
27
27
28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
29 and _route_name variable which uses some of stored naming here to do redirects.
29 and _route_name variable which uses some of stored naming here to do redirects.
30 """
30 """
31 import os
31 import os
32 import re
32 import re
33 from routes import Mapper
33 from routes import Mapper
34
34
35 from rhodecode.config import routing_links
35 from rhodecode.config import routing_links
36
36
37 # prefix for non repository related links needs to be prefixed with `/`
37 # prefix for non repository related links needs to be prefixed with `/`
38 ADMIN_PREFIX = '/_admin'
38 ADMIN_PREFIX = '/_admin'
39 STATIC_FILE_PREFIX = '/_static'
39 STATIC_FILE_PREFIX = '/_static'
40
40
41 # Default requirements for URL parts
41 # Default requirements for URL parts
42 URL_NAME_REQUIREMENTS = {
42 URL_NAME_REQUIREMENTS = {
43 # group name can have a slash in them, but they must not end with a slash
43 # group name can have a slash in them, but they must not end with a slash
44 'group_name': r'.*?[^/]',
44 'group_name': r'.*?[^/]',
45 'repo_group_name': r'.*?[^/]',
45 'repo_group_name': r'.*?[^/]',
46 # repo names can have a slash in them, but they must not end with a slash
46 # repo names can have a slash in them, but they must not end with a slash
47 'repo_name': r'.*?[^/]',
47 'repo_name': r'.*?[^/]',
48 # file path eats up everything at the end
48 # file path eats up everything at the end
49 'f_path': r'.*',
49 'f_path': r'.*',
50 # reference types
50 # reference types
51 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
51 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
52 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
52 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
53 }
53 }
54
54
55
55
56 def add_route_requirements(route_path, requirements):
56 def add_route_requirements(route_path, requirements):
57 """
57 """
58 Adds regex requirements to pyramid routes using a mapping dict
58 Adds regex requirements to pyramid routes using a mapping dict
59
59
60 >>> add_route_requirements('/{action}/{id}', {'id': r'\d+'})
60 >>> add_route_requirements('/{action}/{id}', {'id': r'\d+'})
61 '/{action}/{id:\d+}'
61 '/{action}/{id:\d+}'
62
62
63 """
63 """
64 for key, regex in requirements.items():
64 for key, regex in requirements.items():
65 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
65 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
66 return route_path
66 return route_path
67
67
68
68
69 class JSRoutesMapper(Mapper):
69 class JSRoutesMapper(Mapper):
70 """
70 """
71 Wrapper for routes.Mapper to make pyroutes compatible url definitions
71 Wrapper for routes.Mapper to make pyroutes compatible url definitions
72 """
72 """
73 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
73 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
74 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
74 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
75 def __init__(self, *args, **kw):
75 def __init__(self, *args, **kw):
76 super(JSRoutesMapper, self).__init__(*args, **kw)
76 super(JSRoutesMapper, self).__init__(*args, **kw)
77 self._jsroutes = []
77 self._jsroutes = []
78
78
79 def connect(self, *args, **kw):
79 def connect(self, *args, **kw):
80 """
80 """
81 Wrapper for connect to take an extra argument jsroute=True
81 Wrapper for connect to take an extra argument jsroute=True
82
82
83 :param jsroute: boolean, if True will add the route to the pyroutes list
83 :param jsroute: boolean, if True will add the route to the pyroutes list
84 """
84 """
85 if kw.pop('jsroute', False):
85 if kw.pop('jsroute', False):
86 if not self._named_route_regex.match(args[0]):
86 if not self._named_route_regex.match(args[0]):
87 raise Exception('only named routes can be added to pyroutes')
87 raise Exception('only named routes can be added to pyroutes')
88 self._jsroutes.append(args[0])
88 self._jsroutes.append(args[0])
89
89
90 super(JSRoutesMapper, self).connect(*args, **kw)
90 super(JSRoutesMapper, self).connect(*args, **kw)
91
91
92 def _extract_route_information(self, route):
92 def _extract_route_information(self, route):
93 """
93 """
94 Convert a route into tuple(name, path, args), eg:
94 Convert a route into tuple(name, path, args), eg:
95 ('user_profile', '/profile/%(username)s', ['username'])
95 ('user_profile', '/profile/%(username)s', ['username'])
96 """
96 """
97 routepath = route.routepath
97 routepath = route.routepath
98 def replace(matchobj):
98 def replace(matchobj):
99 if matchobj.group(1):
99 if matchobj.group(1):
100 return "%%(%s)s" % matchobj.group(1).split(':')[0]
100 return "%%(%s)s" % matchobj.group(1).split(':')[0]
101 else:
101 else:
102 return "%%(%s)s" % matchobj.group(2)
102 return "%%(%s)s" % matchobj.group(2)
103
103
104 routepath = self._argument_prog.sub(replace, routepath)
104 routepath = self._argument_prog.sub(replace, routepath)
105 return (
105 return (
106 route.name,
106 route.name,
107 routepath,
107 routepath,
108 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
108 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
109 for arg in self._argument_prog.findall(route.routepath)]
109 for arg in self._argument_prog.findall(route.routepath)]
110 )
110 )
111
111
112 def jsroutes(self):
112 def jsroutes(self):
113 """
113 """
114 Return a list of pyroutes.js compatible routes
114 Return a list of pyroutes.js compatible routes
115 """
115 """
116 for route_name in self._jsroutes:
116 for route_name in self._jsroutes:
117 yield self._extract_route_information(self._routenames[route_name])
117 yield self._extract_route_information(self._routenames[route_name])
118
118
119
119
120 def make_map(config):
120 def make_map(config):
121 """Create, configure and return the routes Mapper"""
121 """Create, configure and return the routes Mapper"""
122 rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'],
122 rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'],
123 always_scan=config['debug'])
123 always_scan=config['debug'])
124 rmap.minimization = False
124 rmap.minimization = False
125 rmap.explicit = False
125 rmap.explicit = False
126
126
127 from rhodecode.lib.utils2 import str2bool
127 from rhodecode.lib.utils2 import str2bool
128 from rhodecode.model import repo, repo_group
128 from rhodecode.model import repo, repo_group
129
129
130 def check_repo(environ, match_dict):
130 def check_repo(environ, match_dict):
131 """
131 """
132 check for valid repository for proper 404 handling
132 check for valid repository for proper 404 handling
133
133
134 :param environ:
134 :param environ:
135 :param match_dict:
135 :param match_dict:
136 """
136 """
137 repo_name = match_dict.get('repo_name')
137 repo_name = match_dict.get('repo_name')
138
138
139 if match_dict.get('f_path'):
139 if match_dict.get('f_path'):
140 # fix for multiple initial slashes that causes errors
140 # fix for multiple initial slashes that causes errors
141 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
141 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
142 repo_model = repo.RepoModel()
142 repo_model = repo.RepoModel()
143 by_name_match = repo_model.get_by_repo_name(repo_name)
143 by_name_match = repo_model.get_by_repo_name(repo_name)
144 # if we match quickly from database, short circuit the operation,
144 # if we match quickly from database, short circuit the operation,
145 # and validate repo based on the type.
145 # and validate repo based on the type.
146 if by_name_match:
146 if by_name_match:
147 return True
147 return True
148
148
149 by_id_match = repo_model.get_repo_by_id(repo_name)
149 by_id_match = repo_model.get_repo_by_id(repo_name)
150 if by_id_match:
150 if by_id_match:
151 repo_name = by_id_match.repo_name
151 repo_name = by_id_match.repo_name
152 match_dict['repo_name'] = repo_name
152 match_dict['repo_name'] = repo_name
153 return True
153 return True
154
154
155 return False
155 return False
156
156
157 def check_group(environ, match_dict):
157 def check_group(environ, match_dict):
158 """
158 """
159 check for valid repository group path for proper 404 handling
159 check for valid repository group path for proper 404 handling
160
160
161 :param environ:
161 :param environ:
162 :param match_dict:
162 :param match_dict:
163 """
163 """
164 repo_group_name = match_dict.get('group_name')
164 repo_group_name = match_dict.get('group_name')
165 repo_group_model = repo_group.RepoGroupModel()
165 repo_group_model = repo_group.RepoGroupModel()
166 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
166 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
167 if by_name_match:
167 if by_name_match:
168 return True
168 return True
169
169
170 return False
170 return False
171
171
172 def check_user_group(environ, match_dict):
172 def check_user_group(environ, match_dict):
173 """
173 """
174 check for valid user group for proper 404 handling
174 check for valid user group for proper 404 handling
175
175
176 :param environ:
176 :param environ:
177 :param match_dict:
177 :param match_dict:
178 """
178 """
179 return True
179 return True
180
180
181 def check_int(environ, match_dict):
181 def check_int(environ, match_dict):
182 return match_dict.get('id').isdigit()
182 return match_dict.get('id').isdigit()
183
183
184
184
185 #==========================================================================
185 #==========================================================================
186 # CUSTOM ROUTES HERE
186 # CUSTOM ROUTES HERE
187 #==========================================================================
187 #==========================================================================
188
188
189 # MAIN PAGE
189 # MAIN PAGE
190 rmap.connect('home', '/', controller='home', action='index', jsroute=True)
190 rmap.connect('home', '/', controller='home', action='index', jsroute=True)
191 rmap.connect('goto_switcher_data', '/_goto_data', controller='home',
191 rmap.connect('goto_switcher_data', '/_goto_data', controller='home',
192 action='goto_switcher_data')
192 action='goto_switcher_data')
193 rmap.connect('repo_list_data', '/_repos', controller='home',
193 rmap.connect('repo_list_data', '/_repos', controller='home',
194 action='repo_list_data')
194 action='repo_list_data')
195
195
196 rmap.connect('user_autocomplete_data', '/_users', controller='home',
196 rmap.connect('user_autocomplete_data', '/_users', controller='home',
197 action='user_autocomplete_data', jsroute=True)
197 action='user_autocomplete_data', jsroute=True)
198 rmap.connect('user_group_autocomplete_data', '/_user_groups', controller='home',
198 rmap.connect('user_group_autocomplete_data', '/_user_groups', controller='home',
199 action='user_group_autocomplete_data')
199 action='user_group_autocomplete_data', jsroute=True)
200
200
201 rmap.connect(
201 rmap.connect(
202 'user_profile', '/_profiles/{username}', controller='users',
202 'user_profile', '/_profiles/{username}', controller='users',
203 action='user_profile')
203 action='user_profile')
204
204
205 # TODO: johbo: Static links, to be replaced by our redirection mechanism
205 # TODO: johbo: Static links, to be replaced by our redirection mechanism
206 rmap.connect('rst_help',
206 rmap.connect('rst_help',
207 'http://docutils.sourceforge.net/docs/user/rst/quickref.html',
207 'http://docutils.sourceforge.net/docs/user/rst/quickref.html',
208 _static=True)
208 _static=True)
209 rmap.connect('markdown_help',
209 rmap.connect('markdown_help',
210 'http://daringfireball.net/projects/markdown/syntax',
210 'http://daringfireball.net/projects/markdown/syntax',
211 _static=True)
211 _static=True)
212 rmap.connect('rhodecode_official', 'https://rhodecode.com', _static=True)
212 rmap.connect('rhodecode_official', 'https://rhodecode.com', _static=True)
213 rmap.connect('rhodecode_support', 'https://rhodecode.com/help/', _static=True)
213 rmap.connect('rhodecode_support', 'https://rhodecode.com/help/', _static=True)
214 rmap.connect('rhodecode_translations', 'https://rhodecode.com/translate/enterprise', _static=True)
214 rmap.connect('rhodecode_translations', 'https://rhodecode.com/translate/enterprise', _static=True)
215 # TODO: anderson - making this a static link since redirect won't play
215 # TODO: anderson - making this a static link since redirect won't play
216 # nice with POST requests
216 # nice with POST requests
217 rmap.connect('enterprise_license_convert_from_old',
217 rmap.connect('enterprise_license_convert_from_old',
218 'https://rhodecode.com/u/license-upgrade',
218 'https://rhodecode.com/u/license-upgrade',
219 _static=True)
219 _static=True)
220
220
221 routing_links.connect_redirection_links(rmap)
221 routing_links.connect_redirection_links(rmap)
222
222
223 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
223 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
224 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
224 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
225
225
226 # ADMIN REPOSITORY ROUTES
226 # ADMIN REPOSITORY ROUTES
227 with rmap.submapper(path_prefix=ADMIN_PREFIX,
227 with rmap.submapper(path_prefix=ADMIN_PREFIX,
228 controller='admin/repos') as m:
228 controller='admin/repos') as m:
229 m.connect('repos', '/repos',
229 m.connect('repos', '/repos',
230 action='create', conditions={'method': ['POST']})
230 action='create', conditions={'method': ['POST']})
231 m.connect('repos', '/repos',
231 m.connect('repos', '/repos',
232 action='index', conditions={'method': ['GET']})
232 action='index', conditions={'method': ['GET']})
233 m.connect('new_repo', '/create_repository', jsroute=True,
233 m.connect('new_repo', '/create_repository', jsroute=True,
234 action='create_repository', conditions={'method': ['GET']})
234 action='create_repository', conditions={'method': ['GET']})
235 m.connect('/repos/{repo_name}',
235 m.connect('/repos/{repo_name}',
236 action='update', conditions={'method': ['PUT'],
236 action='update', conditions={'method': ['PUT'],
237 'function': check_repo},
237 'function': check_repo},
238 requirements=URL_NAME_REQUIREMENTS)
238 requirements=URL_NAME_REQUIREMENTS)
239 m.connect('delete_repo', '/repos/{repo_name}',
239 m.connect('delete_repo', '/repos/{repo_name}',
240 action='delete', conditions={'method': ['DELETE']},
240 action='delete', conditions={'method': ['DELETE']},
241 requirements=URL_NAME_REQUIREMENTS)
241 requirements=URL_NAME_REQUIREMENTS)
242 m.connect('repo', '/repos/{repo_name}',
242 m.connect('repo', '/repos/{repo_name}',
243 action='show', conditions={'method': ['GET'],
243 action='show', conditions={'method': ['GET'],
244 'function': check_repo},
244 'function': check_repo},
245 requirements=URL_NAME_REQUIREMENTS)
245 requirements=URL_NAME_REQUIREMENTS)
246
246
247 # ADMIN REPOSITORY GROUPS ROUTES
247 # ADMIN REPOSITORY GROUPS ROUTES
248 with rmap.submapper(path_prefix=ADMIN_PREFIX,
248 with rmap.submapper(path_prefix=ADMIN_PREFIX,
249 controller='admin/repo_groups') as m:
249 controller='admin/repo_groups') as m:
250 m.connect('repo_groups', '/repo_groups',
250 m.connect('repo_groups', '/repo_groups',
251 action='create', conditions={'method': ['POST']})
251 action='create', conditions={'method': ['POST']})
252 m.connect('repo_groups', '/repo_groups',
252 m.connect('repo_groups', '/repo_groups',
253 action='index', conditions={'method': ['GET']})
253 action='index', conditions={'method': ['GET']})
254 m.connect('new_repo_group', '/repo_groups/new',
254 m.connect('new_repo_group', '/repo_groups/new',
255 action='new', conditions={'method': ['GET']})
255 action='new', conditions={'method': ['GET']})
256 m.connect('update_repo_group', '/repo_groups/{group_name}',
256 m.connect('update_repo_group', '/repo_groups/{group_name}',
257 action='update', conditions={'method': ['PUT'],
257 action='update', conditions={'method': ['PUT'],
258 'function': check_group},
258 'function': check_group},
259 requirements=URL_NAME_REQUIREMENTS)
259 requirements=URL_NAME_REQUIREMENTS)
260
260
261 # EXTRAS REPO GROUP ROUTES
261 # EXTRAS REPO GROUP ROUTES
262 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
262 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
263 action='edit',
263 action='edit',
264 conditions={'method': ['GET'], 'function': check_group},
264 conditions={'method': ['GET'], 'function': check_group},
265 requirements=URL_NAME_REQUIREMENTS)
265 requirements=URL_NAME_REQUIREMENTS)
266 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
266 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
267 action='edit',
267 action='edit',
268 conditions={'method': ['PUT'], 'function': check_group},
268 conditions={'method': ['PUT'], 'function': check_group},
269 requirements=URL_NAME_REQUIREMENTS)
269 requirements=URL_NAME_REQUIREMENTS)
270
270
271 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
271 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
272 action='edit_repo_group_advanced',
272 action='edit_repo_group_advanced',
273 conditions={'method': ['GET'], 'function': check_group},
273 conditions={'method': ['GET'], 'function': check_group},
274 requirements=URL_NAME_REQUIREMENTS)
274 requirements=URL_NAME_REQUIREMENTS)
275 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
275 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
276 action='edit_repo_group_advanced',
276 action='edit_repo_group_advanced',
277 conditions={'method': ['PUT'], 'function': check_group},
277 conditions={'method': ['PUT'], 'function': check_group},
278 requirements=URL_NAME_REQUIREMENTS)
278 requirements=URL_NAME_REQUIREMENTS)
279
279
280 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
280 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
281 action='edit_repo_group_perms',
281 action='edit_repo_group_perms',
282 conditions={'method': ['GET'], 'function': check_group},
282 conditions={'method': ['GET'], 'function': check_group},
283 requirements=URL_NAME_REQUIREMENTS)
283 requirements=URL_NAME_REQUIREMENTS)
284 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
284 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
285 action='update_perms',
285 action='update_perms',
286 conditions={'method': ['PUT'], 'function': check_group},
286 conditions={'method': ['PUT'], 'function': check_group},
287 requirements=URL_NAME_REQUIREMENTS)
287 requirements=URL_NAME_REQUIREMENTS)
288
288
289 m.connect('delete_repo_group', '/repo_groups/{group_name}',
289 m.connect('delete_repo_group', '/repo_groups/{group_name}',
290 action='delete', conditions={'method': ['DELETE'],
290 action='delete', conditions={'method': ['DELETE'],
291 'function': check_group},
291 'function': check_group},
292 requirements=URL_NAME_REQUIREMENTS)
292 requirements=URL_NAME_REQUIREMENTS)
293
293
294 # ADMIN USER ROUTES
294 # ADMIN USER ROUTES
295 with rmap.submapper(path_prefix=ADMIN_PREFIX,
295 with rmap.submapper(path_prefix=ADMIN_PREFIX,
296 controller='admin/users') as m:
296 controller='admin/users') as m:
297 m.connect('users', '/users',
297 m.connect('users', '/users',
298 action='create', conditions={'method': ['POST']})
298 action='create', conditions={'method': ['POST']})
299 m.connect('users', '/users',
299 m.connect('users', '/users',
300 action='index', conditions={'method': ['GET']})
300 action='index', conditions={'method': ['GET']})
301 m.connect('new_user', '/users/new',
301 m.connect('new_user', '/users/new',
302 action='new', conditions={'method': ['GET']})
302 action='new', conditions={'method': ['GET']})
303 m.connect('update_user', '/users/{user_id}',
303 m.connect('update_user', '/users/{user_id}',
304 action='update', conditions={'method': ['PUT']})
304 action='update', conditions={'method': ['PUT']})
305 m.connect('delete_user', '/users/{user_id}',
305 m.connect('delete_user', '/users/{user_id}',
306 action='delete', conditions={'method': ['DELETE']})
306 action='delete', conditions={'method': ['DELETE']})
307 m.connect('edit_user', '/users/{user_id}/edit',
307 m.connect('edit_user', '/users/{user_id}/edit',
308 action='edit', conditions={'method': ['GET']})
308 action='edit', conditions={'method': ['GET']})
309 m.connect('user', '/users/{user_id}',
309 m.connect('user', '/users/{user_id}',
310 action='show', conditions={'method': ['GET']})
310 action='show', conditions={'method': ['GET']})
311 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
311 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
312 action='reset_password', conditions={'method': ['POST']})
312 action='reset_password', conditions={'method': ['POST']})
313 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
313 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
314 action='create_personal_repo_group', conditions={'method': ['POST']})
314 action='create_personal_repo_group', conditions={'method': ['POST']})
315
315
316 # EXTRAS USER ROUTES
316 # EXTRAS USER ROUTES
317 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
317 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
318 action='edit_advanced', conditions={'method': ['GET']})
318 action='edit_advanced', conditions={'method': ['GET']})
319 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
319 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
320 action='update_advanced', conditions={'method': ['PUT']})
320 action='update_advanced', conditions={'method': ['PUT']})
321
321
322 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
322 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
323 action='edit_auth_tokens', conditions={'method': ['GET']})
323 action='edit_auth_tokens', conditions={'method': ['GET']})
324 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
324 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
325 action='add_auth_token', conditions={'method': ['PUT']})
325 action='add_auth_token', conditions={'method': ['PUT']})
326 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
326 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
327 action='delete_auth_token', conditions={'method': ['DELETE']})
327 action='delete_auth_token', conditions={'method': ['DELETE']})
328
328
329 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
329 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
330 action='edit_global_perms', conditions={'method': ['GET']})
330 action='edit_global_perms', conditions={'method': ['GET']})
331 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
331 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
332 action='update_global_perms', conditions={'method': ['PUT']})
332 action='update_global_perms', conditions={'method': ['PUT']})
333
333
334 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
334 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
335 action='edit_perms_summary', conditions={'method': ['GET']})
335 action='edit_perms_summary', conditions={'method': ['GET']})
336
336
337 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
337 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
338 action='edit_emails', conditions={'method': ['GET']})
338 action='edit_emails', conditions={'method': ['GET']})
339 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
339 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
340 action='add_email', conditions={'method': ['PUT']})
340 action='add_email', conditions={'method': ['PUT']})
341 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
341 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
342 action='delete_email', conditions={'method': ['DELETE']})
342 action='delete_email', conditions={'method': ['DELETE']})
343
343
344 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
344 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
345 action='edit_ips', conditions={'method': ['GET']})
345 action='edit_ips', conditions={'method': ['GET']})
346 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
346 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
347 action='add_ip', conditions={'method': ['PUT']})
347 action='add_ip', conditions={'method': ['PUT']})
348 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
348 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
349 action='delete_ip', conditions={'method': ['DELETE']})
349 action='delete_ip', conditions={'method': ['DELETE']})
350
350
351 # ADMIN USER GROUPS REST ROUTES
351 # ADMIN USER GROUPS REST ROUTES
352 with rmap.submapper(path_prefix=ADMIN_PREFIX,
352 with rmap.submapper(path_prefix=ADMIN_PREFIX,
353 controller='admin/user_groups') as m:
353 controller='admin/user_groups') as m:
354 m.connect('users_groups', '/user_groups',
354 m.connect('users_groups', '/user_groups',
355 action='create', conditions={'method': ['POST']})
355 action='create', conditions={'method': ['POST']})
356 m.connect('users_groups', '/user_groups',
356 m.connect('users_groups', '/user_groups',
357 action='index', conditions={'method': ['GET']})
357 action='index', conditions={'method': ['GET']})
358 m.connect('new_users_group', '/user_groups/new',
358 m.connect('new_users_group', '/user_groups/new',
359 action='new', conditions={'method': ['GET']})
359 action='new', conditions={'method': ['GET']})
360 m.connect('update_users_group', '/user_groups/{user_group_id}',
360 m.connect('update_users_group', '/user_groups/{user_group_id}',
361 action='update', conditions={'method': ['PUT']})
361 action='update', conditions={'method': ['PUT']})
362 m.connect('delete_users_group', '/user_groups/{user_group_id}',
362 m.connect('delete_users_group', '/user_groups/{user_group_id}',
363 action='delete', conditions={'method': ['DELETE']})
363 action='delete', conditions={'method': ['DELETE']})
364 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
364 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
365 action='edit', conditions={'method': ['GET']},
365 action='edit', conditions={'method': ['GET']},
366 function=check_user_group)
366 function=check_user_group)
367
367
368 # EXTRAS USER GROUP ROUTES
368 # EXTRAS USER GROUP ROUTES
369 m.connect('edit_user_group_global_perms',
369 m.connect('edit_user_group_global_perms',
370 '/user_groups/{user_group_id}/edit/global_permissions',
370 '/user_groups/{user_group_id}/edit/global_permissions',
371 action='edit_global_perms', conditions={'method': ['GET']})
371 action='edit_global_perms', conditions={'method': ['GET']})
372 m.connect('edit_user_group_global_perms',
372 m.connect('edit_user_group_global_perms',
373 '/user_groups/{user_group_id}/edit/global_permissions',
373 '/user_groups/{user_group_id}/edit/global_permissions',
374 action='update_global_perms', conditions={'method': ['PUT']})
374 action='update_global_perms', conditions={'method': ['PUT']})
375 m.connect('edit_user_group_perms_summary',
375 m.connect('edit_user_group_perms_summary',
376 '/user_groups/{user_group_id}/edit/permissions_summary',
376 '/user_groups/{user_group_id}/edit/permissions_summary',
377 action='edit_perms_summary', conditions={'method': ['GET']})
377 action='edit_perms_summary', conditions={'method': ['GET']})
378
378
379 m.connect('edit_user_group_perms',
379 m.connect('edit_user_group_perms',
380 '/user_groups/{user_group_id}/edit/permissions',
380 '/user_groups/{user_group_id}/edit/permissions',
381 action='edit_perms', conditions={'method': ['GET']})
381 action='edit_perms', conditions={'method': ['GET']})
382 m.connect('edit_user_group_perms',
382 m.connect('edit_user_group_perms',
383 '/user_groups/{user_group_id}/edit/permissions',
383 '/user_groups/{user_group_id}/edit/permissions',
384 action='update_perms', conditions={'method': ['PUT']})
384 action='update_perms', conditions={'method': ['PUT']})
385
385
386 m.connect('edit_user_group_advanced',
386 m.connect('edit_user_group_advanced',
387 '/user_groups/{user_group_id}/edit/advanced',
387 '/user_groups/{user_group_id}/edit/advanced',
388 action='edit_advanced', conditions={'method': ['GET']})
388 action='edit_advanced', conditions={'method': ['GET']})
389
389
390 m.connect('edit_user_group_members',
390 m.connect('edit_user_group_members',
391 '/user_groups/{user_group_id}/edit/members', jsroute=True,
391 '/user_groups/{user_group_id}/edit/members', jsroute=True,
392 action='edit_members', conditions={'method': ['GET']})
392 action='edit_members', conditions={'method': ['GET']})
393
393
394 # ADMIN PERMISSIONS ROUTES
394 # ADMIN PERMISSIONS ROUTES
395 with rmap.submapper(path_prefix=ADMIN_PREFIX,
395 with rmap.submapper(path_prefix=ADMIN_PREFIX,
396 controller='admin/permissions') as m:
396 controller='admin/permissions') as m:
397 m.connect('admin_permissions_application', '/permissions/application',
397 m.connect('admin_permissions_application', '/permissions/application',
398 action='permission_application_update', conditions={'method': ['POST']})
398 action='permission_application_update', conditions={'method': ['POST']})
399 m.connect('admin_permissions_application', '/permissions/application',
399 m.connect('admin_permissions_application', '/permissions/application',
400 action='permission_application', conditions={'method': ['GET']})
400 action='permission_application', conditions={'method': ['GET']})
401
401
402 m.connect('admin_permissions_global', '/permissions/global',
402 m.connect('admin_permissions_global', '/permissions/global',
403 action='permission_global_update', conditions={'method': ['POST']})
403 action='permission_global_update', conditions={'method': ['POST']})
404 m.connect('admin_permissions_global', '/permissions/global',
404 m.connect('admin_permissions_global', '/permissions/global',
405 action='permission_global', conditions={'method': ['GET']})
405 action='permission_global', conditions={'method': ['GET']})
406
406
407 m.connect('admin_permissions_object', '/permissions/object',
407 m.connect('admin_permissions_object', '/permissions/object',
408 action='permission_objects_update', conditions={'method': ['POST']})
408 action='permission_objects_update', conditions={'method': ['POST']})
409 m.connect('admin_permissions_object', '/permissions/object',
409 m.connect('admin_permissions_object', '/permissions/object',
410 action='permission_objects', conditions={'method': ['GET']})
410 action='permission_objects', conditions={'method': ['GET']})
411
411
412 m.connect('admin_permissions_ips', '/permissions/ips',
412 m.connect('admin_permissions_ips', '/permissions/ips',
413 action='permission_ips', conditions={'method': ['POST']})
413 action='permission_ips', conditions={'method': ['POST']})
414 m.connect('admin_permissions_ips', '/permissions/ips',
414 m.connect('admin_permissions_ips', '/permissions/ips',
415 action='permission_ips', conditions={'method': ['GET']})
415 action='permission_ips', conditions={'method': ['GET']})
416
416
417 m.connect('admin_permissions_overview', '/permissions/overview',
417 m.connect('admin_permissions_overview', '/permissions/overview',
418 action='permission_perms', conditions={'method': ['GET']})
418 action='permission_perms', conditions={'method': ['GET']})
419
419
420 # ADMIN DEFAULTS REST ROUTES
420 # ADMIN DEFAULTS REST ROUTES
421 with rmap.submapper(path_prefix=ADMIN_PREFIX,
421 with rmap.submapper(path_prefix=ADMIN_PREFIX,
422 controller='admin/defaults') as m:
422 controller='admin/defaults') as m:
423 m.connect('admin_defaults_repositories', '/defaults/repositories',
423 m.connect('admin_defaults_repositories', '/defaults/repositories',
424 action='update_repository_defaults', conditions={'method': ['POST']})
424 action='update_repository_defaults', conditions={'method': ['POST']})
425 m.connect('admin_defaults_repositories', '/defaults/repositories',
425 m.connect('admin_defaults_repositories', '/defaults/repositories',
426 action='index', conditions={'method': ['GET']})
426 action='index', conditions={'method': ['GET']})
427
427
428 # ADMIN DEBUG STYLE ROUTES
428 # ADMIN DEBUG STYLE ROUTES
429 if str2bool(config.get('debug_style')):
429 if str2bool(config.get('debug_style')):
430 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
430 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
431 controller='debug_style') as m:
431 controller='debug_style') as m:
432 m.connect('debug_style_home', '',
432 m.connect('debug_style_home', '',
433 action='index', conditions={'method': ['GET']})
433 action='index', conditions={'method': ['GET']})
434 m.connect('debug_style_template', '/t/{t_path}',
434 m.connect('debug_style_template', '/t/{t_path}',
435 action='template', conditions={'method': ['GET']})
435 action='template', conditions={'method': ['GET']})
436
436
437 # ADMIN SETTINGS ROUTES
437 # ADMIN SETTINGS ROUTES
438 with rmap.submapper(path_prefix=ADMIN_PREFIX,
438 with rmap.submapper(path_prefix=ADMIN_PREFIX,
439 controller='admin/settings') as m:
439 controller='admin/settings') as m:
440
440
441 # default
441 # default
442 m.connect('admin_settings', '/settings',
442 m.connect('admin_settings', '/settings',
443 action='settings_global_update',
443 action='settings_global_update',
444 conditions={'method': ['POST']})
444 conditions={'method': ['POST']})
445 m.connect('admin_settings', '/settings',
445 m.connect('admin_settings', '/settings',
446 action='settings_global', conditions={'method': ['GET']})
446 action='settings_global', conditions={'method': ['GET']})
447
447
448 m.connect('admin_settings_vcs', '/settings/vcs',
448 m.connect('admin_settings_vcs', '/settings/vcs',
449 action='settings_vcs_update',
449 action='settings_vcs_update',
450 conditions={'method': ['POST']})
450 conditions={'method': ['POST']})
451 m.connect('admin_settings_vcs', '/settings/vcs',
451 m.connect('admin_settings_vcs', '/settings/vcs',
452 action='settings_vcs',
452 action='settings_vcs',
453 conditions={'method': ['GET']})
453 conditions={'method': ['GET']})
454 m.connect('admin_settings_vcs', '/settings/vcs',
454 m.connect('admin_settings_vcs', '/settings/vcs',
455 action='delete_svn_pattern',
455 action='delete_svn_pattern',
456 conditions={'method': ['DELETE']})
456 conditions={'method': ['DELETE']})
457
457
458 m.connect('admin_settings_mapping', '/settings/mapping',
458 m.connect('admin_settings_mapping', '/settings/mapping',
459 action='settings_mapping_update',
459 action='settings_mapping_update',
460 conditions={'method': ['POST']})
460 conditions={'method': ['POST']})
461 m.connect('admin_settings_mapping', '/settings/mapping',
461 m.connect('admin_settings_mapping', '/settings/mapping',
462 action='settings_mapping', conditions={'method': ['GET']})
462 action='settings_mapping', conditions={'method': ['GET']})
463
463
464 m.connect('admin_settings_global', '/settings/global',
464 m.connect('admin_settings_global', '/settings/global',
465 action='settings_global_update',
465 action='settings_global_update',
466 conditions={'method': ['POST']})
466 conditions={'method': ['POST']})
467 m.connect('admin_settings_global', '/settings/global',
467 m.connect('admin_settings_global', '/settings/global',
468 action='settings_global', conditions={'method': ['GET']})
468 action='settings_global', conditions={'method': ['GET']})
469
469
470 m.connect('admin_settings_visual', '/settings/visual',
470 m.connect('admin_settings_visual', '/settings/visual',
471 action='settings_visual_update',
471 action='settings_visual_update',
472 conditions={'method': ['POST']})
472 conditions={'method': ['POST']})
473 m.connect('admin_settings_visual', '/settings/visual',
473 m.connect('admin_settings_visual', '/settings/visual',
474 action='settings_visual', conditions={'method': ['GET']})
474 action='settings_visual', conditions={'method': ['GET']})
475
475
476 m.connect('admin_settings_issuetracker',
476 m.connect('admin_settings_issuetracker',
477 '/settings/issue-tracker', action='settings_issuetracker',
477 '/settings/issue-tracker', action='settings_issuetracker',
478 conditions={'method': ['GET']})
478 conditions={'method': ['GET']})
479 m.connect('admin_settings_issuetracker_save',
479 m.connect('admin_settings_issuetracker_save',
480 '/settings/issue-tracker/save',
480 '/settings/issue-tracker/save',
481 action='settings_issuetracker_save',
481 action='settings_issuetracker_save',
482 conditions={'method': ['POST']})
482 conditions={'method': ['POST']})
483 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
483 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
484 action='settings_issuetracker_test',
484 action='settings_issuetracker_test',
485 conditions={'method': ['POST']})
485 conditions={'method': ['POST']})
486 m.connect('admin_issuetracker_delete',
486 m.connect('admin_issuetracker_delete',
487 '/settings/issue-tracker/delete',
487 '/settings/issue-tracker/delete',
488 action='settings_issuetracker_delete',
488 action='settings_issuetracker_delete',
489 conditions={'method': ['DELETE']})
489 conditions={'method': ['DELETE']})
490
490
491 m.connect('admin_settings_email', '/settings/email',
491 m.connect('admin_settings_email', '/settings/email',
492 action='settings_email_update',
492 action='settings_email_update',
493 conditions={'method': ['POST']})
493 conditions={'method': ['POST']})
494 m.connect('admin_settings_email', '/settings/email',
494 m.connect('admin_settings_email', '/settings/email',
495 action='settings_email', conditions={'method': ['GET']})
495 action='settings_email', conditions={'method': ['GET']})
496
496
497 m.connect('admin_settings_hooks', '/settings/hooks',
497 m.connect('admin_settings_hooks', '/settings/hooks',
498 action='settings_hooks_update',
498 action='settings_hooks_update',
499 conditions={'method': ['POST', 'DELETE']})
499 conditions={'method': ['POST', 'DELETE']})
500 m.connect('admin_settings_hooks', '/settings/hooks',
500 m.connect('admin_settings_hooks', '/settings/hooks',
501 action='settings_hooks', conditions={'method': ['GET']})
501 action='settings_hooks', conditions={'method': ['GET']})
502
502
503 m.connect('admin_settings_search', '/settings/search',
503 m.connect('admin_settings_search', '/settings/search',
504 action='settings_search', conditions={'method': ['GET']})
504 action='settings_search', conditions={'method': ['GET']})
505
505
506 m.connect('admin_settings_system', '/settings/system',
506 m.connect('admin_settings_system', '/settings/system',
507 action='settings_system', conditions={'method': ['GET']})
507 action='settings_system', conditions={'method': ['GET']})
508
508
509 m.connect('admin_settings_system_update', '/settings/system/updates',
509 m.connect('admin_settings_system_update', '/settings/system/updates',
510 action='settings_system_update', conditions={'method': ['GET']})
510 action='settings_system_update', conditions={'method': ['GET']})
511
511
512 m.connect('admin_settings_supervisor', '/settings/supervisor',
512 m.connect('admin_settings_supervisor', '/settings/supervisor',
513 action='settings_supervisor', conditions={'method': ['GET']})
513 action='settings_supervisor', conditions={'method': ['GET']})
514 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
514 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
515 action='settings_supervisor_log', conditions={'method': ['GET']})
515 action='settings_supervisor_log', conditions={'method': ['GET']})
516
516
517 m.connect('admin_settings_labs', '/settings/labs',
517 m.connect('admin_settings_labs', '/settings/labs',
518 action='settings_labs_update',
518 action='settings_labs_update',
519 conditions={'method': ['POST']})
519 conditions={'method': ['POST']})
520 m.connect('admin_settings_labs', '/settings/labs',
520 m.connect('admin_settings_labs', '/settings/labs',
521 action='settings_labs', conditions={'method': ['GET']})
521 action='settings_labs', conditions={'method': ['GET']})
522
522
523 # ADMIN MY ACCOUNT
523 # ADMIN MY ACCOUNT
524 with rmap.submapper(path_prefix=ADMIN_PREFIX,
524 with rmap.submapper(path_prefix=ADMIN_PREFIX,
525 controller='admin/my_account') as m:
525 controller='admin/my_account') as m:
526
526
527 m.connect('my_account', '/my_account',
527 m.connect('my_account', '/my_account',
528 action='my_account', conditions={'method': ['GET']})
528 action='my_account', conditions={'method': ['GET']})
529 m.connect('my_account_edit', '/my_account/edit',
529 m.connect('my_account_edit', '/my_account/edit',
530 action='my_account_edit', conditions={'method': ['GET']})
530 action='my_account_edit', conditions={'method': ['GET']})
531 m.connect('my_account', '/my_account',
531 m.connect('my_account', '/my_account',
532 action='my_account_update', conditions={'method': ['POST']})
532 action='my_account_update', conditions={'method': ['POST']})
533
533
534 m.connect('my_account_password', '/my_account/password',
534 m.connect('my_account_password', '/my_account/password',
535 action='my_account_password', conditions={'method': ['GET', 'POST']})
535 action='my_account_password', conditions={'method': ['GET', 'POST']})
536
536
537 m.connect('my_account_repos', '/my_account/repos',
537 m.connect('my_account_repos', '/my_account/repos',
538 action='my_account_repos', conditions={'method': ['GET']})
538 action='my_account_repos', conditions={'method': ['GET']})
539
539
540 m.connect('my_account_watched', '/my_account/watched',
540 m.connect('my_account_watched', '/my_account/watched',
541 action='my_account_watched', conditions={'method': ['GET']})
541 action='my_account_watched', conditions={'method': ['GET']})
542
542
543 m.connect('my_account_pullrequests', '/my_account/pull_requests',
543 m.connect('my_account_pullrequests', '/my_account/pull_requests',
544 action='my_account_pullrequests', conditions={'method': ['GET']})
544 action='my_account_pullrequests', conditions={'method': ['GET']})
545
545
546 m.connect('my_account_perms', '/my_account/perms',
546 m.connect('my_account_perms', '/my_account/perms',
547 action='my_account_perms', conditions={'method': ['GET']})
547 action='my_account_perms', conditions={'method': ['GET']})
548
548
549 m.connect('my_account_emails', '/my_account/emails',
549 m.connect('my_account_emails', '/my_account/emails',
550 action='my_account_emails', conditions={'method': ['GET']})
550 action='my_account_emails', conditions={'method': ['GET']})
551 m.connect('my_account_emails', '/my_account/emails',
551 m.connect('my_account_emails', '/my_account/emails',
552 action='my_account_emails_add', conditions={'method': ['POST']})
552 action='my_account_emails_add', conditions={'method': ['POST']})
553 m.connect('my_account_emails', '/my_account/emails',
553 m.connect('my_account_emails', '/my_account/emails',
554 action='my_account_emails_delete', conditions={'method': ['DELETE']})
554 action='my_account_emails_delete', conditions={'method': ['DELETE']})
555
555
556 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
556 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
557 action='my_account_auth_tokens', conditions={'method': ['GET']})
557 action='my_account_auth_tokens', conditions={'method': ['GET']})
558 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
558 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
559 action='my_account_auth_tokens_add', conditions={'method': ['POST']})
559 action='my_account_auth_tokens_add', conditions={'method': ['POST']})
560 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
560 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
561 action='my_account_auth_tokens_delete', conditions={'method': ['DELETE']})
561 action='my_account_auth_tokens_delete', conditions={'method': ['DELETE']})
562 m.connect('my_account_notifications', '/my_account/notifications',
562 m.connect('my_account_notifications', '/my_account/notifications',
563 action='my_notifications',
563 action='my_notifications',
564 conditions={'method': ['GET']})
564 conditions={'method': ['GET']})
565 m.connect('my_account_notifications_toggle_visibility',
565 m.connect('my_account_notifications_toggle_visibility',
566 '/my_account/toggle_visibility',
566 '/my_account/toggle_visibility',
567 action='my_notifications_toggle_visibility',
567 action='my_notifications_toggle_visibility',
568 conditions={'method': ['POST']})
568 conditions={'method': ['POST']})
569
569
570 # NOTIFICATION REST ROUTES
570 # NOTIFICATION REST ROUTES
571 with rmap.submapper(path_prefix=ADMIN_PREFIX,
571 with rmap.submapper(path_prefix=ADMIN_PREFIX,
572 controller='admin/notifications') as m:
572 controller='admin/notifications') as m:
573 m.connect('notifications', '/notifications',
573 m.connect('notifications', '/notifications',
574 action='index', conditions={'method': ['GET']})
574 action='index', conditions={'method': ['GET']})
575 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
575 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
576 action='mark_all_read', conditions={'method': ['POST']})
576 action='mark_all_read', conditions={'method': ['POST']})
577 m.connect('/notifications/{notification_id}',
577 m.connect('/notifications/{notification_id}',
578 action='update', conditions={'method': ['PUT']})
578 action='update', conditions={'method': ['PUT']})
579 m.connect('/notifications/{notification_id}',
579 m.connect('/notifications/{notification_id}',
580 action='delete', conditions={'method': ['DELETE']})
580 action='delete', conditions={'method': ['DELETE']})
581 m.connect('notification', '/notifications/{notification_id}',
581 m.connect('notification', '/notifications/{notification_id}',
582 action='show', conditions={'method': ['GET']})
582 action='show', conditions={'method': ['GET']})
583
583
584 # ADMIN GIST
584 # ADMIN GIST
585 with rmap.submapper(path_prefix=ADMIN_PREFIX,
585 with rmap.submapper(path_prefix=ADMIN_PREFIX,
586 controller='admin/gists') as m:
586 controller='admin/gists') as m:
587 m.connect('gists', '/gists',
587 m.connect('gists', '/gists',
588 action='create', conditions={'method': ['POST']})
588 action='create', conditions={'method': ['POST']})
589 m.connect('gists', '/gists', jsroute=True,
589 m.connect('gists', '/gists', jsroute=True,
590 action='index', conditions={'method': ['GET']})
590 action='index', conditions={'method': ['GET']})
591 m.connect('new_gist', '/gists/new', jsroute=True,
591 m.connect('new_gist', '/gists/new', jsroute=True,
592 action='new', conditions={'method': ['GET']})
592 action='new', conditions={'method': ['GET']})
593
593
594 m.connect('/gists/{gist_id}',
594 m.connect('/gists/{gist_id}',
595 action='delete', conditions={'method': ['DELETE']})
595 action='delete', conditions={'method': ['DELETE']})
596 m.connect('edit_gist', '/gists/{gist_id}/edit',
596 m.connect('edit_gist', '/gists/{gist_id}/edit',
597 action='edit_form', conditions={'method': ['GET']})
597 action='edit_form', conditions={'method': ['GET']})
598 m.connect('edit_gist', '/gists/{gist_id}/edit',
598 m.connect('edit_gist', '/gists/{gist_id}/edit',
599 action='edit', conditions={'method': ['POST']})
599 action='edit', conditions={'method': ['POST']})
600 m.connect(
600 m.connect(
601 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
601 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
602 action='check_revision', conditions={'method': ['GET']})
602 action='check_revision', conditions={'method': ['GET']})
603
603
604 m.connect('gist', '/gists/{gist_id}',
604 m.connect('gist', '/gists/{gist_id}',
605 action='show', conditions={'method': ['GET']})
605 action='show', conditions={'method': ['GET']})
606 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
606 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
607 revision='tip',
607 revision='tip',
608 action='show', conditions={'method': ['GET']})
608 action='show', conditions={'method': ['GET']})
609 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
609 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
610 revision='tip',
610 revision='tip',
611 action='show', conditions={'method': ['GET']})
611 action='show', conditions={'method': ['GET']})
612 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
612 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
613 revision='tip',
613 revision='tip',
614 action='show', conditions={'method': ['GET']},
614 action='show', conditions={'method': ['GET']},
615 requirements=URL_NAME_REQUIREMENTS)
615 requirements=URL_NAME_REQUIREMENTS)
616
616
617 # ADMIN MAIN PAGES
617 # ADMIN MAIN PAGES
618 with rmap.submapper(path_prefix=ADMIN_PREFIX,
618 with rmap.submapper(path_prefix=ADMIN_PREFIX,
619 controller='admin/admin') as m:
619 controller='admin/admin') as m:
620 m.connect('admin_home', '', action='index')
620 m.connect('admin_home', '', action='index')
621 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
621 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
622 action='add_repo')
622 action='add_repo')
623 m.connect(
623 m.connect(
624 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
624 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
625 action='pull_requests')
625 action='pull_requests')
626 m.connect(
626 m.connect(
627 'pull_requests_global', '/pull-requests/{pull_request_id:[0-9]+}',
627 'pull_requests_global', '/pull-requests/{pull_request_id:[0-9]+}',
628 action='pull_requests')
628 action='pull_requests')
629
629
630
630
631 # USER JOURNAL
631 # USER JOURNAL
632 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
632 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
633 controller='journal', action='index')
633 controller='journal', action='index')
634 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
634 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
635 controller='journal', action='journal_rss')
635 controller='journal', action='journal_rss')
636 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
636 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
637 controller='journal', action='journal_atom')
637 controller='journal', action='journal_atom')
638
638
639 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
639 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
640 controller='journal', action='public_journal')
640 controller='journal', action='public_journal')
641
641
642 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
642 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
643 controller='journal', action='public_journal_rss')
643 controller='journal', action='public_journal_rss')
644
644
645 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
645 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
646 controller='journal', action='public_journal_rss')
646 controller='journal', action='public_journal_rss')
647
647
648 rmap.connect('public_journal_atom',
648 rmap.connect('public_journal_atom',
649 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
649 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
650 action='public_journal_atom')
650 action='public_journal_atom')
651
651
652 rmap.connect('public_journal_atom_old',
652 rmap.connect('public_journal_atom_old',
653 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
653 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
654 action='public_journal_atom')
654 action='public_journal_atom')
655
655
656 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
656 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
657 controller='journal', action='toggle_following', jsroute=True,
657 controller='journal', action='toggle_following', jsroute=True,
658 conditions={'method': ['POST']})
658 conditions={'method': ['POST']})
659
659
660 # FULL TEXT SEARCH
660 # FULL TEXT SEARCH
661 rmap.connect('search', '%s/search' % (ADMIN_PREFIX,),
661 rmap.connect('search', '%s/search' % (ADMIN_PREFIX,),
662 controller='search')
662 controller='search')
663 rmap.connect('search_repo_home', '/{repo_name}/search',
663 rmap.connect('search_repo_home', '/{repo_name}/search',
664 controller='search',
664 controller='search',
665 action='index',
665 action='index',
666 conditions={'function': check_repo},
666 conditions={'function': check_repo},
667 requirements=URL_NAME_REQUIREMENTS)
667 requirements=URL_NAME_REQUIREMENTS)
668
668
669 # FEEDS
669 # FEEDS
670 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
670 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
671 controller='feed', action='rss',
671 controller='feed', action='rss',
672 conditions={'function': check_repo},
672 conditions={'function': check_repo},
673 requirements=URL_NAME_REQUIREMENTS)
673 requirements=URL_NAME_REQUIREMENTS)
674
674
675 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
675 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
676 controller='feed', action='atom',
676 controller='feed', action='atom',
677 conditions={'function': check_repo},
677 conditions={'function': check_repo},
678 requirements=URL_NAME_REQUIREMENTS)
678 requirements=URL_NAME_REQUIREMENTS)
679
679
680 #==========================================================================
680 #==========================================================================
681 # REPOSITORY ROUTES
681 # REPOSITORY ROUTES
682 #==========================================================================
682 #==========================================================================
683
683
684 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
684 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
685 controller='admin/repos', action='repo_creating',
685 controller='admin/repos', action='repo_creating',
686 requirements=URL_NAME_REQUIREMENTS)
686 requirements=URL_NAME_REQUIREMENTS)
687 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
687 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
688 controller='admin/repos', action='repo_check',
688 controller='admin/repos', action='repo_check',
689 requirements=URL_NAME_REQUIREMENTS)
689 requirements=URL_NAME_REQUIREMENTS)
690
690
691 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
691 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
692 controller='summary', action='repo_stats',
692 controller='summary', action='repo_stats',
693 conditions={'function': check_repo},
693 conditions={'function': check_repo},
694 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
694 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
695
695
696 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
696 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
697 controller='summary', action='repo_refs_data', jsroute=True,
697 controller='summary', action='repo_refs_data', jsroute=True,
698 requirements=URL_NAME_REQUIREMENTS)
698 requirements=URL_NAME_REQUIREMENTS)
699 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
699 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
700 controller='summary', action='repo_refs_changelog_data',
700 controller='summary', action='repo_refs_changelog_data',
701 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
701 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
702 rmap.connect('repo_default_reviewers_data', '/{repo_name}/default-reviewers',
703 controller='summary', action='repo_default_reviewers_data',
704 jsroute=True, requirements=URL_NAME_REQUIREMENTS)
702
705
703 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
706 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
704 controller='changeset', revision='tip', jsroute=True,
707 controller='changeset', revision='tip', jsroute=True,
705 conditions={'function': check_repo},
708 conditions={'function': check_repo},
706 requirements=URL_NAME_REQUIREMENTS)
709 requirements=URL_NAME_REQUIREMENTS)
707 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
710 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
708 controller='changeset', revision='tip', action='changeset_children',
711 controller='changeset', revision='tip', action='changeset_children',
709 conditions={'function': check_repo},
712 conditions={'function': check_repo},
710 requirements=URL_NAME_REQUIREMENTS)
713 requirements=URL_NAME_REQUIREMENTS)
711 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
714 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
712 controller='changeset', revision='tip', action='changeset_parents',
715 controller='changeset', revision='tip', action='changeset_parents',
713 conditions={'function': check_repo},
716 conditions={'function': check_repo},
714 requirements=URL_NAME_REQUIREMENTS)
717 requirements=URL_NAME_REQUIREMENTS)
715
718
716 # repo edit options
719 # repo edit options
717 rmap.connect('edit_repo', '/{repo_name}/settings', jsroute=True,
720 rmap.connect('edit_repo', '/{repo_name}/settings', jsroute=True,
718 controller='admin/repos', action='edit',
721 controller='admin/repos', action='edit',
719 conditions={'method': ['GET'], 'function': check_repo},
722 conditions={'method': ['GET'], 'function': check_repo},
720 requirements=URL_NAME_REQUIREMENTS)
723 requirements=URL_NAME_REQUIREMENTS)
721
724
722 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
725 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
723 jsroute=True,
726 jsroute=True,
724 controller='admin/repos', action='edit_permissions',
727 controller='admin/repos', action='edit_permissions',
725 conditions={'method': ['GET'], 'function': check_repo},
728 conditions={'method': ['GET'], 'function': check_repo},
726 requirements=URL_NAME_REQUIREMENTS)
729 requirements=URL_NAME_REQUIREMENTS)
727 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
730 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
728 controller='admin/repos', action='edit_permissions_update',
731 controller='admin/repos', action='edit_permissions_update',
729 conditions={'method': ['PUT'], 'function': check_repo},
732 conditions={'method': ['PUT'], 'function': check_repo},
730 requirements=URL_NAME_REQUIREMENTS)
733 requirements=URL_NAME_REQUIREMENTS)
731
734
732 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
735 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
733 controller='admin/repos', action='edit_fields',
736 controller='admin/repos', action='edit_fields',
734 conditions={'method': ['GET'], 'function': check_repo},
737 conditions={'method': ['GET'], 'function': check_repo},
735 requirements=URL_NAME_REQUIREMENTS)
738 requirements=URL_NAME_REQUIREMENTS)
736 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
739 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
737 controller='admin/repos', action='create_repo_field',
740 controller='admin/repos', action='create_repo_field',
738 conditions={'method': ['PUT'], 'function': check_repo},
741 conditions={'method': ['PUT'], 'function': check_repo},
739 requirements=URL_NAME_REQUIREMENTS)
742 requirements=URL_NAME_REQUIREMENTS)
740 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
743 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
741 controller='admin/repos', action='delete_repo_field',
744 controller='admin/repos', action='delete_repo_field',
742 conditions={'method': ['DELETE'], 'function': check_repo},
745 conditions={'method': ['DELETE'], 'function': check_repo},
743 requirements=URL_NAME_REQUIREMENTS)
746 requirements=URL_NAME_REQUIREMENTS)
744
747
745 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
748 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
746 controller='admin/repos', action='edit_advanced',
749 controller='admin/repos', action='edit_advanced',
747 conditions={'method': ['GET'], 'function': check_repo},
750 conditions={'method': ['GET'], 'function': check_repo},
748 requirements=URL_NAME_REQUIREMENTS)
751 requirements=URL_NAME_REQUIREMENTS)
749
752
750 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
753 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
751 controller='admin/repos', action='edit_advanced_locking',
754 controller='admin/repos', action='edit_advanced_locking',
752 conditions={'method': ['PUT'], 'function': check_repo},
755 conditions={'method': ['PUT'], 'function': check_repo},
753 requirements=URL_NAME_REQUIREMENTS)
756 requirements=URL_NAME_REQUIREMENTS)
754 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
757 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
755 controller='admin/repos', action='toggle_locking',
758 controller='admin/repos', action='toggle_locking',
756 conditions={'method': ['GET'], 'function': check_repo},
759 conditions={'method': ['GET'], 'function': check_repo},
757 requirements=URL_NAME_REQUIREMENTS)
760 requirements=URL_NAME_REQUIREMENTS)
758
761
759 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
762 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
760 controller='admin/repos', action='edit_advanced_journal',
763 controller='admin/repos', action='edit_advanced_journal',
761 conditions={'method': ['PUT'], 'function': check_repo},
764 conditions={'method': ['PUT'], 'function': check_repo},
762 requirements=URL_NAME_REQUIREMENTS)
765 requirements=URL_NAME_REQUIREMENTS)
763
766
764 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
767 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
765 controller='admin/repos', action='edit_advanced_fork',
768 controller='admin/repos', action='edit_advanced_fork',
766 conditions={'method': ['PUT'], 'function': check_repo},
769 conditions={'method': ['PUT'], 'function': check_repo},
767 requirements=URL_NAME_REQUIREMENTS)
770 requirements=URL_NAME_REQUIREMENTS)
768
771
769 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
772 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
770 controller='admin/repos', action='edit_caches_form',
773 controller='admin/repos', action='edit_caches_form',
771 conditions={'method': ['GET'], 'function': check_repo},
774 conditions={'method': ['GET'], 'function': check_repo},
772 requirements=URL_NAME_REQUIREMENTS)
775 requirements=URL_NAME_REQUIREMENTS)
773 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
776 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
774 controller='admin/repos', action='edit_caches',
777 controller='admin/repos', action='edit_caches',
775 conditions={'method': ['PUT'], 'function': check_repo},
778 conditions={'method': ['PUT'], 'function': check_repo},
776 requirements=URL_NAME_REQUIREMENTS)
779 requirements=URL_NAME_REQUIREMENTS)
777
780
778 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
781 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
779 controller='admin/repos', action='edit_remote_form',
782 controller='admin/repos', action='edit_remote_form',
780 conditions={'method': ['GET'], 'function': check_repo},
783 conditions={'method': ['GET'], 'function': check_repo},
781 requirements=URL_NAME_REQUIREMENTS)
784 requirements=URL_NAME_REQUIREMENTS)
782 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
785 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
783 controller='admin/repos', action='edit_remote',
786 controller='admin/repos', action='edit_remote',
784 conditions={'method': ['PUT'], 'function': check_repo},
787 conditions={'method': ['PUT'], 'function': check_repo},
785 requirements=URL_NAME_REQUIREMENTS)
788 requirements=URL_NAME_REQUIREMENTS)
786
789
787 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
790 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
788 controller='admin/repos', action='edit_statistics_form',
791 controller='admin/repos', action='edit_statistics_form',
789 conditions={'method': ['GET'], 'function': check_repo},
792 conditions={'method': ['GET'], 'function': check_repo},
790 requirements=URL_NAME_REQUIREMENTS)
793 requirements=URL_NAME_REQUIREMENTS)
791 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
794 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
792 controller='admin/repos', action='edit_statistics',
795 controller='admin/repos', action='edit_statistics',
793 conditions={'method': ['PUT'], 'function': check_repo},
796 conditions={'method': ['PUT'], 'function': check_repo},
794 requirements=URL_NAME_REQUIREMENTS)
797 requirements=URL_NAME_REQUIREMENTS)
795 rmap.connect('repo_settings_issuetracker',
798 rmap.connect('repo_settings_issuetracker',
796 '/{repo_name}/settings/issue-tracker',
799 '/{repo_name}/settings/issue-tracker',
797 controller='admin/repos', action='repo_issuetracker',
800 controller='admin/repos', action='repo_issuetracker',
798 conditions={'method': ['GET'], 'function': check_repo},
801 conditions={'method': ['GET'], 'function': check_repo},
799 requirements=URL_NAME_REQUIREMENTS)
802 requirements=URL_NAME_REQUIREMENTS)
800 rmap.connect('repo_issuetracker_test',
803 rmap.connect('repo_issuetracker_test',
801 '/{repo_name}/settings/issue-tracker/test',
804 '/{repo_name}/settings/issue-tracker/test',
802 controller='admin/repos', action='repo_issuetracker_test',
805 controller='admin/repos', action='repo_issuetracker_test',
803 conditions={'method': ['POST'], 'function': check_repo},
806 conditions={'method': ['POST'], 'function': check_repo},
804 requirements=URL_NAME_REQUIREMENTS)
807 requirements=URL_NAME_REQUIREMENTS)
805 rmap.connect('repo_issuetracker_delete',
808 rmap.connect('repo_issuetracker_delete',
806 '/{repo_name}/settings/issue-tracker/delete',
809 '/{repo_name}/settings/issue-tracker/delete',
807 controller='admin/repos', action='repo_issuetracker_delete',
810 controller='admin/repos', action='repo_issuetracker_delete',
808 conditions={'method': ['DELETE'], 'function': check_repo},
811 conditions={'method': ['DELETE'], 'function': check_repo},
809 requirements=URL_NAME_REQUIREMENTS)
812 requirements=URL_NAME_REQUIREMENTS)
810 rmap.connect('repo_issuetracker_save',
813 rmap.connect('repo_issuetracker_save',
811 '/{repo_name}/settings/issue-tracker/save',
814 '/{repo_name}/settings/issue-tracker/save',
812 controller='admin/repos', action='repo_issuetracker_save',
815 controller='admin/repos', action='repo_issuetracker_save',
813 conditions={'method': ['POST'], 'function': check_repo},
816 conditions={'method': ['POST'], 'function': check_repo},
814 requirements=URL_NAME_REQUIREMENTS)
817 requirements=URL_NAME_REQUIREMENTS)
815 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
818 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
816 controller='admin/repos', action='repo_settings_vcs_update',
819 controller='admin/repos', action='repo_settings_vcs_update',
817 conditions={'method': ['POST'], 'function': check_repo},
820 conditions={'method': ['POST'], 'function': check_repo},
818 requirements=URL_NAME_REQUIREMENTS)
821 requirements=URL_NAME_REQUIREMENTS)
819 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
822 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
820 controller='admin/repos', action='repo_settings_vcs',
823 controller='admin/repos', action='repo_settings_vcs',
821 conditions={'method': ['GET'], 'function': check_repo},
824 conditions={'method': ['GET'], 'function': check_repo},
822 requirements=URL_NAME_REQUIREMENTS)
825 requirements=URL_NAME_REQUIREMENTS)
823 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
826 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
824 controller='admin/repos', action='repo_delete_svn_pattern',
827 controller='admin/repos', action='repo_delete_svn_pattern',
825 conditions={'method': ['DELETE'], 'function': check_repo},
828 conditions={'method': ['DELETE'], 'function': check_repo},
826 requirements=URL_NAME_REQUIREMENTS)
829 requirements=URL_NAME_REQUIREMENTS)
830 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
831 controller='admin/repos', action='repo_settings_pullrequest',
832 conditions={'method': ['GET', 'POST'], 'function': check_repo},
833 requirements=URL_NAME_REQUIREMENTS)
827
834
828 # still working url for backward compat.
835 # still working url for backward compat.
829 rmap.connect('raw_changeset_home_depraced',
836 rmap.connect('raw_changeset_home_depraced',
830 '/{repo_name}/raw-changeset/{revision}',
837 '/{repo_name}/raw-changeset/{revision}',
831 controller='changeset', action='changeset_raw',
838 controller='changeset', action='changeset_raw',
832 revision='tip', conditions={'function': check_repo},
839 revision='tip', conditions={'function': check_repo},
833 requirements=URL_NAME_REQUIREMENTS)
840 requirements=URL_NAME_REQUIREMENTS)
834
841
835 # new URLs
842 # new URLs
836 rmap.connect('changeset_raw_home',
843 rmap.connect('changeset_raw_home',
837 '/{repo_name}/changeset-diff/{revision}',
844 '/{repo_name}/changeset-diff/{revision}',
838 controller='changeset', action='changeset_raw',
845 controller='changeset', action='changeset_raw',
839 revision='tip', conditions={'function': check_repo},
846 revision='tip', conditions={'function': check_repo},
840 requirements=URL_NAME_REQUIREMENTS)
847 requirements=URL_NAME_REQUIREMENTS)
841
848
842 rmap.connect('changeset_patch_home',
849 rmap.connect('changeset_patch_home',
843 '/{repo_name}/changeset-patch/{revision}',
850 '/{repo_name}/changeset-patch/{revision}',
844 controller='changeset', action='changeset_patch',
851 controller='changeset', action='changeset_patch',
845 revision='tip', conditions={'function': check_repo},
852 revision='tip', conditions={'function': check_repo},
846 requirements=URL_NAME_REQUIREMENTS)
853 requirements=URL_NAME_REQUIREMENTS)
847
854
848 rmap.connect('changeset_download_home',
855 rmap.connect('changeset_download_home',
849 '/{repo_name}/changeset-download/{revision}',
856 '/{repo_name}/changeset-download/{revision}',
850 controller='changeset', action='changeset_download',
857 controller='changeset', action='changeset_download',
851 revision='tip', conditions={'function': check_repo},
858 revision='tip', conditions={'function': check_repo},
852 requirements=URL_NAME_REQUIREMENTS)
859 requirements=URL_NAME_REQUIREMENTS)
853
860
854 rmap.connect('changeset_comment',
861 rmap.connect('changeset_comment',
855 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
862 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
856 controller='changeset', revision='tip', action='comment',
863 controller='changeset', revision='tip', action='comment',
857 conditions={'function': check_repo},
864 conditions={'function': check_repo},
858 requirements=URL_NAME_REQUIREMENTS)
865 requirements=URL_NAME_REQUIREMENTS)
859
866
860 rmap.connect('changeset_comment_preview',
867 rmap.connect('changeset_comment_preview',
861 '/{repo_name}/changeset/comment/preview', jsroute=True,
868 '/{repo_name}/changeset/comment/preview', jsroute=True,
862 controller='changeset', action='preview_comment',
869 controller='changeset', action='preview_comment',
863 conditions={'function': check_repo, 'method': ['POST']},
870 conditions={'function': check_repo, 'method': ['POST']},
864 requirements=URL_NAME_REQUIREMENTS)
871 requirements=URL_NAME_REQUIREMENTS)
865
872
866 rmap.connect('changeset_comment_delete',
873 rmap.connect('changeset_comment_delete',
867 '/{repo_name}/changeset/comment/{comment_id}/delete',
874 '/{repo_name}/changeset/comment/{comment_id}/delete',
868 controller='changeset', action='delete_comment',
875 controller='changeset', action='delete_comment',
869 conditions={'function': check_repo, 'method': ['DELETE']},
876 conditions={'function': check_repo, 'method': ['DELETE']},
870 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
877 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
871
878
872 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
879 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
873 controller='changeset', action='changeset_info',
880 controller='changeset', action='changeset_info',
874 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
881 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
875
882
876 rmap.connect('compare_home',
883 rmap.connect('compare_home',
877 '/{repo_name}/compare',
884 '/{repo_name}/compare',
878 controller='compare', action='index',
885 controller='compare', action='index',
879 conditions={'function': check_repo},
886 conditions={'function': check_repo},
880 requirements=URL_NAME_REQUIREMENTS)
887 requirements=URL_NAME_REQUIREMENTS)
881
888
882 rmap.connect('compare_url',
889 rmap.connect('compare_url',
883 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
890 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
884 controller='compare', action='compare',
891 controller='compare', action='compare',
885 conditions={'function': check_repo},
892 conditions={'function': check_repo},
886 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
893 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
887
894
888 rmap.connect('pullrequest_home',
895 rmap.connect('pullrequest_home',
889 '/{repo_name}/pull-request/new', controller='pullrequests',
896 '/{repo_name}/pull-request/new', controller='pullrequests',
890 action='index', conditions={'function': check_repo,
897 action='index', conditions={'function': check_repo,
891 'method': ['GET']},
898 'method': ['GET']},
892 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
899 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
893
900
894 rmap.connect('pullrequest',
901 rmap.connect('pullrequest',
895 '/{repo_name}/pull-request/new', controller='pullrequests',
902 '/{repo_name}/pull-request/new', controller='pullrequests',
896 action='create', conditions={'function': check_repo,
903 action='create', conditions={'function': check_repo,
897 'method': ['POST']},
904 'method': ['POST']},
898 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
905 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
899
906
900 rmap.connect('pullrequest_repo_refs',
907 rmap.connect('pullrequest_repo_refs',
901 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
908 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
902 controller='pullrequests',
909 controller='pullrequests',
903 action='get_repo_refs',
910 action='get_repo_refs',
904 conditions={'function': check_repo, 'method': ['GET']},
911 conditions={'function': check_repo, 'method': ['GET']},
905 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
912 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
906
913
907 rmap.connect('pullrequest_repo_destinations',
914 rmap.connect('pullrequest_repo_destinations',
908 '/{repo_name}/pull-request/repo-destinations',
915 '/{repo_name}/pull-request/repo-destinations',
909 controller='pullrequests',
916 controller='pullrequests',
910 action='get_repo_destinations',
917 action='get_repo_destinations',
911 conditions={'function': check_repo, 'method': ['GET']},
918 conditions={'function': check_repo, 'method': ['GET']},
912 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
919 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
913
920
914 rmap.connect('pullrequest_show',
921 rmap.connect('pullrequest_show',
915 '/{repo_name}/pull-request/{pull_request_id}',
922 '/{repo_name}/pull-request/{pull_request_id}',
916 controller='pullrequests',
923 controller='pullrequests',
917 action='show', conditions={'function': check_repo,
924 action='show', conditions={'function': check_repo,
918 'method': ['GET']},
925 'method': ['GET']},
919 requirements=URL_NAME_REQUIREMENTS)
926 requirements=URL_NAME_REQUIREMENTS)
920
927
921 rmap.connect('pullrequest_update',
928 rmap.connect('pullrequest_update',
922 '/{repo_name}/pull-request/{pull_request_id}',
929 '/{repo_name}/pull-request/{pull_request_id}',
923 controller='pullrequests',
930 controller='pullrequests',
924 action='update', conditions={'function': check_repo,
931 action='update', conditions={'function': check_repo,
925 'method': ['PUT']},
932 'method': ['PUT']},
926 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
933 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
927
934
928 rmap.connect('pullrequest_merge',
935 rmap.connect('pullrequest_merge',
929 '/{repo_name}/pull-request/{pull_request_id}',
936 '/{repo_name}/pull-request/{pull_request_id}',
930 controller='pullrequests',
937 controller='pullrequests',
931 action='merge', conditions={'function': check_repo,
938 action='merge', conditions={'function': check_repo,
932 'method': ['POST']},
939 'method': ['POST']},
933 requirements=URL_NAME_REQUIREMENTS)
940 requirements=URL_NAME_REQUIREMENTS)
934
941
935 rmap.connect('pullrequest_delete',
942 rmap.connect('pullrequest_delete',
936 '/{repo_name}/pull-request/{pull_request_id}',
943 '/{repo_name}/pull-request/{pull_request_id}',
937 controller='pullrequests',
944 controller='pullrequests',
938 action='delete', conditions={'function': check_repo,
945 action='delete', conditions={'function': check_repo,
939 'method': ['DELETE']},
946 'method': ['DELETE']},
940 requirements=URL_NAME_REQUIREMENTS)
947 requirements=URL_NAME_REQUIREMENTS)
941
948
942 rmap.connect('pullrequest_show_all',
949 rmap.connect('pullrequest_show_all',
943 '/{repo_name}/pull-request',
950 '/{repo_name}/pull-request',
944 controller='pullrequests',
951 controller='pullrequests',
945 action='show_all', conditions={'function': check_repo,
952 action='show_all', conditions={'function': check_repo,
946 'method': ['GET']},
953 'method': ['GET']},
947 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
954 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
948
955
949 rmap.connect('pullrequest_comment',
956 rmap.connect('pullrequest_comment',
950 '/{repo_name}/pull-request-comment/{pull_request_id}',
957 '/{repo_name}/pull-request-comment/{pull_request_id}',
951 controller='pullrequests',
958 controller='pullrequests',
952 action='comment', conditions={'function': check_repo,
959 action='comment', conditions={'function': check_repo,
953 'method': ['POST']},
960 'method': ['POST']},
954 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
961 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
955
962
956 rmap.connect('pullrequest_comment_delete',
963 rmap.connect('pullrequest_comment_delete',
957 '/{repo_name}/pull-request-comment/{comment_id}/delete',
964 '/{repo_name}/pull-request-comment/{comment_id}/delete',
958 controller='pullrequests', action='delete_comment',
965 controller='pullrequests', action='delete_comment',
959 conditions={'function': check_repo, 'method': ['DELETE']},
966 conditions={'function': check_repo, 'method': ['DELETE']},
960 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
967 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
961
968
962 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
969 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
963 controller='summary', conditions={'function': check_repo},
970 controller='summary', conditions={'function': check_repo},
964 requirements=URL_NAME_REQUIREMENTS)
971 requirements=URL_NAME_REQUIREMENTS)
965
972
966 rmap.connect('branches_home', '/{repo_name}/branches',
973 rmap.connect('branches_home', '/{repo_name}/branches',
967 controller='branches', conditions={'function': check_repo},
974 controller='branches', conditions={'function': check_repo},
968 requirements=URL_NAME_REQUIREMENTS)
975 requirements=URL_NAME_REQUIREMENTS)
969
976
970 rmap.connect('tags_home', '/{repo_name}/tags',
977 rmap.connect('tags_home', '/{repo_name}/tags',
971 controller='tags', conditions={'function': check_repo},
978 controller='tags', conditions={'function': check_repo},
972 requirements=URL_NAME_REQUIREMENTS)
979 requirements=URL_NAME_REQUIREMENTS)
973
980
974 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
981 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
975 controller='bookmarks', conditions={'function': check_repo},
982 controller='bookmarks', conditions={'function': check_repo},
976 requirements=URL_NAME_REQUIREMENTS)
983 requirements=URL_NAME_REQUIREMENTS)
977
984
978 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
985 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
979 controller='changelog', conditions={'function': check_repo},
986 controller='changelog', conditions={'function': check_repo},
980 requirements=URL_NAME_REQUIREMENTS)
987 requirements=URL_NAME_REQUIREMENTS)
981
988
982 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
989 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
983 controller='changelog', action='changelog_summary',
990 controller='changelog', action='changelog_summary',
984 conditions={'function': check_repo},
991 conditions={'function': check_repo},
985 requirements=URL_NAME_REQUIREMENTS)
992 requirements=URL_NAME_REQUIREMENTS)
986
993
987 rmap.connect('changelog_file_home',
994 rmap.connect('changelog_file_home',
988 '/{repo_name}/changelog/{revision}/{f_path}',
995 '/{repo_name}/changelog/{revision}/{f_path}',
989 controller='changelog', f_path=None,
996 controller='changelog', f_path=None,
990 conditions={'function': check_repo},
997 conditions={'function': check_repo},
991 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
998 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
992
999
993 rmap.connect('changelog_details', '/{repo_name}/changelog_details/{cs}',
1000 rmap.connect('changelog_details', '/{repo_name}/changelog_details/{cs}',
994 controller='changelog', action='changelog_details',
1001 controller='changelog', action='changelog_details',
995 conditions={'function': check_repo},
1002 conditions={'function': check_repo},
996 requirements=URL_NAME_REQUIREMENTS)
1003 requirements=URL_NAME_REQUIREMENTS)
997
1004
998 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
1005 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
999 controller='files', revision='tip', f_path='',
1006 controller='files', revision='tip', f_path='',
1000 conditions={'function': check_repo},
1007 conditions={'function': check_repo},
1001 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1008 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1002
1009
1003 rmap.connect('files_home_simple_catchrev',
1010 rmap.connect('files_home_simple_catchrev',
1004 '/{repo_name}/files/{revision}',
1011 '/{repo_name}/files/{revision}',
1005 controller='files', revision='tip', f_path='',
1012 controller='files', revision='tip', f_path='',
1006 conditions={'function': check_repo},
1013 conditions={'function': check_repo},
1007 requirements=URL_NAME_REQUIREMENTS)
1014 requirements=URL_NAME_REQUIREMENTS)
1008
1015
1009 rmap.connect('files_home_simple_catchall',
1016 rmap.connect('files_home_simple_catchall',
1010 '/{repo_name}/files',
1017 '/{repo_name}/files',
1011 controller='files', revision='tip', f_path='',
1018 controller='files', revision='tip', f_path='',
1012 conditions={'function': check_repo},
1019 conditions={'function': check_repo},
1013 requirements=URL_NAME_REQUIREMENTS)
1020 requirements=URL_NAME_REQUIREMENTS)
1014
1021
1015 rmap.connect('files_history_home',
1022 rmap.connect('files_history_home',
1016 '/{repo_name}/history/{revision}/{f_path}',
1023 '/{repo_name}/history/{revision}/{f_path}',
1017 controller='files', action='history', revision='tip', f_path='',
1024 controller='files', action='history', revision='tip', f_path='',
1018 conditions={'function': check_repo},
1025 conditions={'function': check_repo},
1019 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1026 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1020
1027
1021 rmap.connect('files_authors_home',
1028 rmap.connect('files_authors_home',
1022 '/{repo_name}/authors/{revision}/{f_path}',
1029 '/{repo_name}/authors/{revision}/{f_path}',
1023 controller='files', action='authors', revision='tip', f_path='',
1030 controller='files', action='authors', revision='tip', f_path='',
1024 conditions={'function': check_repo},
1031 conditions={'function': check_repo},
1025 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1032 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1026
1033
1027 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
1034 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
1028 controller='files', action='diff', f_path='',
1035 controller='files', action='diff', f_path='',
1029 conditions={'function': check_repo},
1036 conditions={'function': check_repo},
1030 requirements=URL_NAME_REQUIREMENTS)
1037 requirements=URL_NAME_REQUIREMENTS)
1031
1038
1032 rmap.connect('files_diff_2way_home',
1039 rmap.connect('files_diff_2way_home',
1033 '/{repo_name}/diff-2way/{f_path}',
1040 '/{repo_name}/diff-2way/{f_path}',
1034 controller='files', action='diff_2way', f_path='',
1041 controller='files', action='diff_2way', f_path='',
1035 conditions={'function': check_repo},
1042 conditions={'function': check_repo},
1036 requirements=URL_NAME_REQUIREMENTS)
1043 requirements=URL_NAME_REQUIREMENTS)
1037
1044
1038 rmap.connect('files_rawfile_home',
1045 rmap.connect('files_rawfile_home',
1039 '/{repo_name}/rawfile/{revision}/{f_path}',
1046 '/{repo_name}/rawfile/{revision}/{f_path}',
1040 controller='files', action='rawfile', revision='tip',
1047 controller='files', action='rawfile', revision='tip',
1041 f_path='', conditions={'function': check_repo},
1048 f_path='', conditions={'function': check_repo},
1042 requirements=URL_NAME_REQUIREMENTS)
1049 requirements=URL_NAME_REQUIREMENTS)
1043
1050
1044 rmap.connect('files_raw_home',
1051 rmap.connect('files_raw_home',
1045 '/{repo_name}/raw/{revision}/{f_path}',
1052 '/{repo_name}/raw/{revision}/{f_path}',
1046 controller='files', action='raw', revision='tip', f_path='',
1053 controller='files', action='raw', revision='tip', f_path='',
1047 conditions={'function': check_repo},
1054 conditions={'function': check_repo},
1048 requirements=URL_NAME_REQUIREMENTS)
1055 requirements=URL_NAME_REQUIREMENTS)
1049
1056
1050 rmap.connect('files_render_home',
1057 rmap.connect('files_render_home',
1051 '/{repo_name}/render/{revision}/{f_path}',
1058 '/{repo_name}/render/{revision}/{f_path}',
1052 controller='files', action='index', revision='tip', f_path='',
1059 controller='files', action='index', revision='tip', f_path='',
1053 rendered=True, conditions={'function': check_repo},
1060 rendered=True, conditions={'function': check_repo},
1054 requirements=URL_NAME_REQUIREMENTS)
1061 requirements=URL_NAME_REQUIREMENTS)
1055
1062
1056 rmap.connect('files_annotate_home',
1063 rmap.connect('files_annotate_home',
1057 '/{repo_name}/annotate/{revision}/{f_path}',
1064 '/{repo_name}/annotate/{revision}/{f_path}',
1058 controller='files', action='index', revision='tip',
1065 controller='files', action='index', revision='tip',
1059 f_path='', annotate=True, conditions={'function': check_repo},
1066 f_path='', annotate=True, conditions={'function': check_repo},
1060 requirements=URL_NAME_REQUIREMENTS)
1067 requirements=URL_NAME_REQUIREMENTS)
1061
1068
1062 rmap.connect('files_edit',
1069 rmap.connect('files_edit',
1063 '/{repo_name}/edit/{revision}/{f_path}',
1070 '/{repo_name}/edit/{revision}/{f_path}',
1064 controller='files', action='edit', revision='tip',
1071 controller='files', action='edit', revision='tip',
1065 f_path='',
1072 f_path='',
1066 conditions={'function': check_repo, 'method': ['POST']},
1073 conditions={'function': check_repo, 'method': ['POST']},
1067 requirements=URL_NAME_REQUIREMENTS)
1074 requirements=URL_NAME_REQUIREMENTS)
1068
1075
1069 rmap.connect('files_edit_home',
1076 rmap.connect('files_edit_home',
1070 '/{repo_name}/edit/{revision}/{f_path}',
1077 '/{repo_name}/edit/{revision}/{f_path}',
1071 controller='files', action='edit_home', revision='tip',
1078 controller='files', action='edit_home', revision='tip',
1072 f_path='', conditions={'function': check_repo},
1079 f_path='', conditions={'function': check_repo},
1073 requirements=URL_NAME_REQUIREMENTS)
1080 requirements=URL_NAME_REQUIREMENTS)
1074
1081
1075 rmap.connect('files_add',
1082 rmap.connect('files_add',
1076 '/{repo_name}/add/{revision}/{f_path}',
1083 '/{repo_name}/add/{revision}/{f_path}',
1077 controller='files', action='add', revision='tip',
1084 controller='files', action='add', revision='tip',
1078 f_path='',
1085 f_path='',
1079 conditions={'function': check_repo, 'method': ['POST']},
1086 conditions={'function': check_repo, 'method': ['POST']},
1080 requirements=URL_NAME_REQUIREMENTS)
1087 requirements=URL_NAME_REQUIREMENTS)
1081
1088
1082 rmap.connect('files_add_home',
1089 rmap.connect('files_add_home',
1083 '/{repo_name}/add/{revision}/{f_path}',
1090 '/{repo_name}/add/{revision}/{f_path}',
1084 controller='files', action='add_home', revision='tip',
1091 controller='files', action='add_home', revision='tip',
1085 f_path='', conditions={'function': check_repo},
1092 f_path='', conditions={'function': check_repo},
1086 requirements=URL_NAME_REQUIREMENTS)
1093 requirements=URL_NAME_REQUIREMENTS)
1087
1094
1088 rmap.connect('files_delete',
1095 rmap.connect('files_delete',
1089 '/{repo_name}/delete/{revision}/{f_path}',
1096 '/{repo_name}/delete/{revision}/{f_path}',
1090 controller='files', action='delete', revision='tip',
1097 controller='files', action='delete', revision='tip',
1091 f_path='',
1098 f_path='',
1092 conditions={'function': check_repo, 'method': ['POST']},
1099 conditions={'function': check_repo, 'method': ['POST']},
1093 requirements=URL_NAME_REQUIREMENTS)
1100 requirements=URL_NAME_REQUIREMENTS)
1094
1101
1095 rmap.connect('files_delete_home',
1102 rmap.connect('files_delete_home',
1096 '/{repo_name}/delete/{revision}/{f_path}',
1103 '/{repo_name}/delete/{revision}/{f_path}',
1097 controller='files', action='delete_home', revision='tip',
1104 controller='files', action='delete_home', revision='tip',
1098 f_path='', conditions={'function': check_repo},
1105 f_path='', conditions={'function': check_repo},
1099 requirements=URL_NAME_REQUIREMENTS)
1106 requirements=URL_NAME_REQUIREMENTS)
1100
1107
1101 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1108 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1102 controller='files', action='archivefile',
1109 controller='files', action='archivefile',
1103 conditions={'function': check_repo},
1110 conditions={'function': check_repo},
1104 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1111 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1105
1112
1106 rmap.connect('files_nodelist_home',
1113 rmap.connect('files_nodelist_home',
1107 '/{repo_name}/nodelist/{revision}/{f_path}',
1114 '/{repo_name}/nodelist/{revision}/{f_path}',
1108 controller='files', action='nodelist',
1115 controller='files', action='nodelist',
1109 conditions={'function': check_repo},
1116 conditions={'function': check_repo},
1110 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1117 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1111
1118
1112 rmap.connect('files_nodetree_full',
1119 rmap.connect('files_nodetree_full',
1113 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1120 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1114 controller='files', action='nodetree_full',
1121 controller='files', action='nodetree_full',
1115 conditions={'function': check_repo},
1122 conditions={'function': check_repo},
1116 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1123 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1117
1124
1118 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1125 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1119 controller='forks', action='fork_create',
1126 controller='forks', action='fork_create',
1120 conditions={'function': check_repo, 'method': ['POST']},
1127 conditions={'function': check_repo, 'method': ['POST']},
1121 requirements=URL_NAME_REQUIREMENTS)
1128 requirements=URL_NAME_REQUIREMENTS)
1122
1129
1123 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1130 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1124 controller='forks', action='fork',
1131 controller='forks', action='fork',
1125 conditions={'function': check_repo},
1132 conditions={'function': check_repo},
1126 requirements=URL_NAME_REQUIREMENTS)
1133 requirements=URL_NAME_REQUIREMENTS)
1127
1134
1128 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1135 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1129 controller='forks', action='forks',
1136 controller='forks', action='forks',
1130 conditions={'function': check_repo},
1137 conditions={'function': check_repo},
1131 requirements=URL_NAME_REQUIREMENTS)
1138 requirements=URL_NAME_REQUIREMENTS)
1132
1139
1133 rmap.connect('repo_followers_home', '/{repo_name}/followers',
1140 rmap.connect('repo_followers_home', '/{repo_name}/followers',
1134 controller='followers', action='followers',
1141 controller='followers', action='followers',
1135 conditions={'function': check_repo},
1142 conditions={'function': check_repo},
1136 requirements=URL_NAME_REQUIREMENTS)
1143 requirements=URL_NAME_REQUIREMENTS)
1137
1144
1138 # must be here for proper group/repo catching pattern
1145 # must be here for proper group/repo catching pattern
1139 _connect_with_slash(
1146 _connect_with_slash(
1140 rmap, 'repo_group_home', '/{group_name}',
1147 rmap, 'repo_group_home', '/{group_name}',
1141 controller='home', action='index_repo_group',
1148 controller='home', action='index_repo_group',
1142 conditions={'function': check_group},
1149 conditions={'function': check_group},
1143 requirements=URL_NAME_REQUIREMENTS)
1150 requirements=URL_NAME_REQUIREMENTS)
1144
1151
1145 # catch all, at the end
1152 # catch all, at the end
1146 _connect_with_slash(
1153 _connect_with_slash(
1147 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1154 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1148 controller='summary', action='index',
1155 controller='summary', action='index',
1149 conditions={'function': check_repo},
1156 conditions={'function': check_repo},
1150 requirements=URL_NAME_REQUIREMENTS)
1157 requirements=URL_NAME_REQUIREMENTS)
1151
1158
1152 return rmap
1159 return rmap
1153
1160
1154
1161
1155 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1162 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1156 """
1163 """
1157 Connect a route with an optional trailing slash in `path`.
1164 Connect a route with an optional trailing slash in `path`.
1158 """
1165 """
1159 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1166 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1160 mapper.connect(name, path, *args, **kwargs)
1167 mapper.connect(name, path, *args, **kwargs)
@@ -1,289 +1,288 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Home controller for RhodeCode Enterprise
22 Home controller for RhodeCode Enterprise
23 """
23 """
24
24
25 import logging
25 import logging
26 import time
26 import time
27 import re
27 import re
28
28
29 from pylons import tmpl_context as c, request, url, config
29 from pylons import tmpl_context as c, request, url, config
30 from pylons.i18n.translation import _
30 from pylons.i18n.translation import _
31 from sqlalchemy.sql import func
31 from sqlalchemy.sql import func
32
32
33 from rhodecode.lib.auth import (
33 from rhodecode.lib.auth import (
34 LoginRequired, HasPermissionAllDecorator, AuthUser,
34 LoginRequired, HasPermissionAllDecorator, AuthUser,
35 HasRepoGroupPermissionAnyDecorator, XHRRequired)
35 HasRepoGroupPermissionAnyDecorator, XHRRequired)
36 from rhodecode.lib.base import BaseController, render
36 from rhodecode.lib.base import BaseController, render
37 from rhodecode.lib.index import searcher_from_config
37 from rhodecode.lib.index import searcher_from_config
38 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.ext_json import json
39 from rhodecode.lib.utils import jsonify
39 from rhodecode.lib.utils import jsonify
40 from rhodecode.lib.utils2 import safe_unicode, str2bool
40 from rhodecode.lib.utils2 import safe_unicode, str2bool
41 from rhodecode.model.db import Repository, RepoGroup
41 from rhodecode.model.db import Repository, RepoGroup
42 from rhodecode.model.repo import RepoModel
42 from rhodecode.model.repo import RepoModel
43 from rhodecode.model.repo_group import RepoGroupModel
43 from rhodecode.model.repo_group import RepoGroupModel
44 from rhodecode.model.scm import RepoList, RepoGroupList
44 from rhodecode.model.scm import RepoList, RepoGroupList
45
45
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 class HomeController(BaseController):
50 class HomeController(BaseController):
51 def __before__(self):
51 def __before__(self):
52 super(HomeController, self).__before__()
52 super(HomeController, self).__before__()
53
53
54 def ping(self):
54 def ping(self):
55 """
55 """
56 Ping, doesn't require login, good for checking out the platform
56 Ping, doesn't require login, good for checking out the platform
57 """
57 """
58 instance_id = getattr(c, 'rhodecode_instanceid', '')
58 instance_id = getattr(c, 'rhodecode_instanceid', '')
59 return 'pong[%s] => %s' % (instance_id, self.ip_addr,)
59 return 'pong[%s] => %s' % (instance_id, self.ip_addr,)
60
60
61 @LoginRequired()
61 @LoginRequired()
62 @HasPermissionAllDecorator('hg.admin')
62 @HasPermissionAllDecorator('hg.admin')
63 def error_test(self):
63 def error_test(self):
64 """
64 """
65 Test exception handling and emails on errors
65 Test exception handling and emails on errors
66 """
66 """
67 class TestException(Exception):
67 class TestException(Exception):
68 pass
68 pass
69
69
70 msg = ('RhodeCode Enterprise %s test exception. Generation time: %s'
70 msg = ('RhodeCode Enterprise %s test exception. Generation time: %s'
71 % (c.rhodecode_name, time.time()))
71 % (c.rhodecode_name, time.time()))
72 raise TestException(msg)
72 raise TestException(msg)
73
73
74 def _get_groups_and_repos(self, repo_group_id=None):
74 def _get_groups_and_repos(self, repo_group_id=None):
75 # repo groups groups
75 # repo groups groups
76 repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id)
76 repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id)
77 _perms = ['group.read', 'group.write', 'group.admin']
77 _perms = ['group.read', 'group.write', 'group.admin']
78 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
78 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
79 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
79 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
80 repo_group_list=repo_group_list_acl, admin=False)
80 repo_group_list=repo_group_list_acl, admin=False)
81
81
82 # repositories
82 # repositories
83 repo_list = Repository.get_all_repos(group_id=repo_group_id)
83 repo_list = Repository.get_all_repos(group_id=repo_group_id)
84 _perms = ['repository.read', 'repository.write', 'repository.admin']
84 _perms = ['repository.read', 'repository.write', 'repository.admin']
85 repo_list_acl = RepoList(repo_list, perm_set=_perms)
85 repo_list_acl = RepoList(repo_list, perm_set=_perms)
86 repo_data = RepoModel().get_repos_as_dict(
86 repo_data = RepoModel().get_repos_as_dict(
87 repo_list=repo_list_acl, admin=False)
87 repo_list=repo_list_acl, admin=False)
88
88
89 return repo_data, repo_group_data
89 return repo_data, repo_group_data
90
90
91 @LoginRequired()
91 @LoginRequired()
92 def index(self):
92 def index(self):
93 c.repo_group = None
93 c.repo_group = None
94
94
95 repo_data, repo_group_data = self._get_groups_and_repos()
95 repo_data, repo_group_data = self._get_groups_and_repos()
96 # json used to render the grids
96 # json used to render the grids
97 c.repos_data = json.dumps(repo_data)
97 c.repos_data = json.dumps(repo_data)
98 c.repo_groups_data = json.dumps(repo_group_data)
98 c.repo_groups_data = json.dumps(repo_group_data)
99
99
100 return render('/index.html')
100 return render('/index.html')
101
101
102 @LoginRequired()
102 @LoginRequired()
103 @HasRepoGroupPermissionAnyDecorator('group.read', 'group.write',
103 @HasRepoGroupPermissionAnyDecorator('group.read', 'group.write',
104 'group.admin')
104 'group.admin')
105 def index_repo_group(self, group_name):
105 def index_repo_group(self, group_name):
106 """GET /repo_group_name: Show a specific item"""
106 """GET /repo_group_name: Show a specific item"""
107 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
107 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
108 repo_data, repo_group_data = self._get_groups_and_repos(
108 repo_data, repo_group_data = self._get_groups_and_repos(
109 c.repo_group.group_id)
109 c.repo_group.group_id)
110
110
111 # json used to render the grids
111 # json used to render the grids
112 c.repos_data = json.dumps(repo_data)
112 c.repos_data = json.dumps(repo_data)
113 c.repo_groups_data = json.dumps(repo_group_data)
113 c.repo_groups_data = json.dumps(repo_group_data)
114
114
115 return render('index_repo_group.html')
115 return render('index_repo_group.html')
116
116
117 def _get_repo_list(self, name_contains=None, repo_type=None, limit=20):
117 def _get_repo_list(self, name_contains=None, repo_type=None, limit=20):
118 query = Repository.query()\
118 query = Repository.query()\
119 .order_by(func.length(Repository.repo_name))\
119 .order_by(func.length(Repository.repo_name))\
120 .order_by(Repository.repo_name)
120 .order_by(Repository.repo_name)
121
121
122 if repo_type:
122 if repo_type:
123 query = query.filter(Repository.repo_type == repo_type)
123 query = query.filter(Repository.repo_type == repo_type)
124
124
125 if name_contains:
125 if name_contains:
126 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
126 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
127 query = query.filter(
127 query = query.filter(
128 Repository.repo_name.ilike(ilike_expression))
128 Repository.repo_name.ilike(ilike_expression))
129 query = query.limit(limit)
129 query = query.limit(limit)
130
130
131 all_repos = query.all()
131 all_repos = query.all()
132 repo_iter = self.scm_model.get_repos(all_repos)
132 repo_iter = self.scm_model.get_repos(all_repos)
133 return [
133 return [
134 {
134 {
135 'id': obj['name'],
135 'id': obj['name'],
136 'text': obj['name'],
136 'text': obj['name'],
137 'type': 'repo',
137 'type': 'repo',
138 'obj': obj['dbrepo'],
138 'obj': obj['dbrepo'],
139 'url': url('summary_home', repo_name=obj['name'])
139 'url': url('summary_home', repo_name=obj['name'])
140 }
140 }
141 for obj in repo_iter]
141 for obj in repo_iter]
142
142
143 def _get_repo_group_list(self, name_contains=None, limit=20):
143 def _get_repo_group_list(self, name_contains=None, limit=20):
144 query = RepoGroup.query()\
144 query = RepoGroup.query()\
145 .order_by(func.length(RepoGroup.group_name))\
145 .order_by(func.length(RepoGroup.group_name))\
146 .order_by(RepoGroup.group_name)
146 .order_by(RepoGroup.group_name)
147
147
148 if name_contains:
148 if name_contains:
149 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
149 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
150 query = query.filter(
150 query = query.filter(
151 RepoGroup.group_name.ilike(ilike_expression))
151 RepoGroup.group_name.ilike(ilike_expression))
152 query = query.limit(limit)
152 query = query.limit(limit)
153
153
154 all_groups = query.all()
154 all_groups = query.all()
155 repo_groups_iter = self.scm_model.get_repo_groups(all_groups)
155 repo_groups_iter = self.scm_model.get_repo_groups(all_groups)
156 return [
156 return [
157 {
157 {
158 'id': obj.group_name,
158 'id': obj.group_name,
159 'text': obj.group_name,
159 'text': obj.group_name,
160 'type': 'group',
160 'type': 'group',
161 'obj': {},
161 'obj': {},
162 'url': url('repo_group_home', group_name=obj.group_name)
162 'url': url('repo_group_home', group_name=obj.group_name)
163 }
163 }
164 for obj in repo_groups_iter]
164 for obj in repo_groups_iter]
165
165
166 def _get_hash_commit_list(self, hash_starts_with=None, limit=20):
166 def _get_hash_commit_list(self, hash_starts_with=None, limit=20):
167 if not hash_starts_with or len(hash_starts_with) < 3:
167 if not hash_starts_with or len(hash_starts_with) < 3:
168 return []
168 return []
169
169
170 commit_hashes = re.compile('([0-9a-f]{2,40})').findall(hash_starts_with)
170 commit_hashes = re.compile('([0-9a-f]{2,40})').findall(hash_starts_with)
171
171
172 if len(commit_hashes) != 1:
172 if len(commit_hashes) != 1:
173 return []
173 return []
174
174
175 commit_hash_prefix = commit_hashes[0]
175 commit_hash_prefix = commit_hashes[0]
176
176
177 auth_user = AuthUser(
177 auth_user = AuthUser(
178 user_id=c.rhodecode_user.user_id, ip_addr=self.ip_addr)
178 user_id=c.rhodecode_user.user_id, ip_addr=self.ip_addr)
179 searcher = searcher_from_config(config)
179 searcher = searcher_from_config(config)
180 result = searcher.search(
180 result = searcher.search(
181 'commit_id:%s*' % commit_hash_prefix, 'commit', auth_user)
181 'commit_id:%s*' % commit_hash_prefix, 'commit', auth_user)
182
182
183 return [
183 return [
184 {
184 {
185 'id': entry['commit_id'],
185 'id': entry['commit_id'],
186 'text': entry['commit_id'],
186 'text': entry['commit_id'],
187 'type': 'commit',
187 'type': 'commit',
188 'obj': {'repo': entry['repository']},
188 'obj': {'repo': entry['repository']},
189 'url': url('changeset_home',
189 'url': url('changeset_home',
190 repo_name=entry['repository'], revision=entry['commit_id'])
190 repo_name=entry['repository'], revision=entry['commit_id'])
191 }
191 }
192 for entry in result['results']]
192 for entry in result['results']]
193
193
194 @LoginRequired()
194 @LoginRequired()
195 @XHRRequired()
195 @XHRRequired()
196 @jsonify
196 @jsonify
197 def goto_switcher_data(self):
197 def goto_switcher_data(self):
198 query = request.GET.get('query')
198 query = request.GET.get('query')
199 log.debug('generating goto switcher list, query %s', query)
199 log.debug('generating goto switcher list, query %s', query)
200
200
201 res = []
201 res = []
202 repo_groups = self._get_repo_group_list(query)
202 repo_groups = self._get_repo_group_list(query)
203 if repo_groups:
203 if repo_groups:
204 res.append({
204 res.append({
205 'text': _('Groups'),
205 'text': _('Groups'),
206 'children': repo_groups
206 'children': repo_groups
207 })
207 })
208
208
209 repos = self._get_repo_list(query)
209 repos = self._get_repo_list(query)
210 if repos:
210 if repos:
211 res.append({
211 res.append({
212 'text': _('Repositories'),
212 'text': _('Repositories'),
213 'children': repos
213 'children': repos
214 })
214 })
215
215
216 commits = self._get_hash_commit_list(query)
216 commits = self._get_hash_commit_list(query)
217 if commits:
217 if commits:
218 unique_repos = {}
218 unique_repos = {}
219 for commit in commits:
219 for commit in commits:
220 unique_repos.setdefault(commit['obj']['repo'], []
220 unique_repos.setdefault(commit['obj']['repo'], []
221 ).append(commit)
221 ).append(commit)
222
222
223 for repo in unique_repos:
223 for repo in unique_repos:
224 res.append({
224 res.append({
225 'text': _('Commits in %(repo)s') % {'repo': repo},
225 'text': _('Commits in %(repo)s') % {'repo': repo},
226 'children': unique_repos[repo]
226 'children': unique_repos[repo]
227 })
227 })
228
228
229 data = {
229 data = {
230 'more': False,
230 'more': False,
231 'results': res
231 'results': res
232 }
232 }
233 return data
233 return data
234
234
235 @LoginRequired()
235 @LoginRequired()
236 @XHRRequired()
236 @XHRRequired()
237 @jsonify
237 @jsonify
238 def repo_list_data(self):
238 def repo_list_data(self):
239 query = request.GET.get('query')
239 query = request.GET.get('query')
240 repo_type = request.GET.get('repo_type')
240 repo_type = request.GET.get('repo_type')
241 log.debug('generating repo list, query:%s', query)
241 log.debug('generating repo list, query:%s', query)
242
242
243 res = []
243 res = []
244 repos = self._get_repo_list(query, repo_type=repo_type)
244 repos = self._get_repo_list(query, repo_type=repo_type)
245 if repos:
245 if repos:
246 res.append({
246 res.append({
247 'text': _('Repositories'),
247 'text': _('Repositories'),
248 'children': repos
248 'children': repos
249 })
249 })
250
250
251 data = {
251 data = {
252 'more': False,
252 'more': False,
253 'results': res
253 'results': res
254 }
254 }
255 return data
255 return data
256
256
257 @LoginRequired()
257 @LoginRequired()
258 @XHRRequired()
258 @XHRRequired()
259 @jsonify
259 @jsonify
260 def user_autocomplete_data(self):
260 def user_autocomplete_data(self):
261 query = request.GET.get('query')
261 query = request.GET.get('query')
262 active = str2bool(request.GET.get('active') or True)
262 active = str2bool(request.GET.get('active') or True)
263
263
264 repo_model = RepoModel()
264 repo_model = RepoModel()
265 _users = repo_model.get_users(
265 _users = repo_model.get_users(
266 name_contains=query, only_active=active)
266 name_contains=query, only_active=active)
267
267
268 if request.GET.get('user_groups'):
268 if request.GET.get('user_groups'):
269 # extend with user groups
269 # extend with user groups
270 _user_groups = repo_model.get_user_groups(
270 _user_groups = repo_model.get_user_groups(
271 name_contains=query, only_active=active)
271 name_contains=query, only_active=active)
272 _users = _users + _user_groups
272 _users = _users + _user_groups
273
273
274 return {'suggestions': _users}
274 return {'suggestions': _users}
275
275
276 @LoginRequired()
276 @LoginRequired()
277 @XHRRequired()
277 @XHRRequired()
278 @jsonify
278 @jsonify
279 def user_group_autocomplete_data(self):
279 def user_group_autocomplete_data(self):
280 query = request.GET.get('query')
280 query = request.GET.get('query')
281 active = str2bool(request.GET.get('active') or True)
281 active = str2bool(request.GET.get('active') or True)
282
282
283 repo_model = RepoModel()
283 repo_model = RepoModel()
284 _user_groups = repo_model.get_user_groups(
284 _user_groups = repo_model.get_user_groups(
285 name_contains=query, only_active=active)
285 name_contains=query, only_active=active)
286 _user_groups = _user_groups
286 _user_groups = _user_groups
287
287
288 return {'suggestions': _user_groups}
288 return {'suggestions': _user_groups}
289
@@ -1,308 +1,318 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Summary controller for RhodeCode Enterprise
22 Summary controller for RhodeCode Enterprise
23 """
23 """
24
24
25 import logging
25 import logging
26 from string import lower
26 from string import lower
27
27
28 from pylons import tmpl_context as c, request
28 from pylons import tmpl_context as c, request
29 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
30 from beaker.cache import cache_region, region_invalidate
30 from beaker.cache import cache_region, region_invalidate
31
31
32 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
32 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
33 from rhodecode.controllers import utils
33 from rhodecode.controllers import utils
34 from rhodecode.controllers.changelog import _load_changelog_summary
34 from rhodecode.controllers.changelog import _load_changelog_summary
35 from rhodecode.lib import caches, helpers as h
35 from rhodecode.lib import caches, helpers as h
36 from rhodecode.lib.utils import jsonify
36 from rhodecode.lib.utils import jsonify
37 from rhodecode.lib.utils2 import safe_str
37 from rhodecode.lib.utils2 import safe_str
38 from rhodecode.lib.auth import (
38 from rhodecode.lib.auth import (
39 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, XHRRequired)
39 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, XHRRequired)
40 from rhodecode.lib.base import BaseRepoController, render
40 from rhodecode.lib.base import BaseRepoController, render
41 from rhodecode.lib.markup_renderer import MarkupRenderer
41 from rhodecode.lib.markup_renderer import MarkupRenderer
42 from rhodecode.lib.ext_json import json
42 from rhodecode.lib.ext_json import json
43 from rhodecode.lib.vcs.backends.base import EmptyCommit
43 from rhodecode.lib.vcs.backends.base import EmptyCommit
44 from rhodecode.lib.vcs.exceptions import (
44 from rhodecode.lib.vcs.exceptions import (
45 CommitError, EmptyRepositoryError, NodeDoesNotExistError)
45 CommitError, EmptyRepositoryError, NodeDoesNotExistError)
46 from rhodecode.model.db import Statistics, CacheKey, User
46 from rhodecode.model.db import Statistics, CacheKey, User
47 from rhodecode.model.repo import ReadmeFinder
47 from rhodecode.model.repo import ReadmeFinder
48
48
49
49
50 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
51
51
52
52
53 class SummaryController(BaseRepoController):
53 class SummaryController(BaseRepoController):
54
54
55 def __before__(self):
55 def __before__(self):
56 super(SummaryController, self).__before__()
56 super(SummaryController, self).__before__()
57
57
58 def __get_readme_data(self, db_repo):
58 def __get_readme_data(self, db_repo):
59 repo_name = db_repo.repo_name
59 repo_name = db_repo.repo_name
60 log.debug('Looking for README file')
60 log.debug('Looking for README file')
61 default_renderer = c.visual.default_renderer
61 default_renderer = c.visual.default_renderer
62
62
63 @cache_region('long_term')
63 @cache_region('long_term')
64 def _generate_readme(cache_key):
64 def _generate_readme(cache_key):
65 readme_data = None
65 readme_data = None
66 readme_node = None
66 readme_node = None
67 readme_filename = None
67 readme_filename = None
68 commit = self._get_landing_commit_or_none(db_repo)
68 commit = self._get_landing_commit_or_none(db_repo)
69 if commit:
69 if commit:
70 log.debug("Searching for a README file.")
70 log.debug("Searching for a README file.")
71 readme_node = ReadmeFinder(default_renderer).search(commit)
71 readme_node = ReadmeFinder(default_renderer).search(commit)
72 if readme_node:
72 if readme_node:
73 readme_data = self._render_readme_or_none(commit, readme_node)
73 readme_data = self._render_readme_or_none(commit, readme_node)
74 readme_filename = readme_node.path
74 readme_filename = readme_node.path
75 return readme_data, readme_filename
75 return readme_data, readme_filename
76
76
77 invalidator_context = CacheKey.repo_context_cache(
77 invalidator_context = CacheKey.repo_context_cache(
78 _generate_readme, repo_name, CacheKey.CACHE_TYPE_README)
78 _generate_readme, repo_name, CacheKey.CACHE_TYPE_README)
79
79
80 with invalidator_context as context:
80 with invalidator_context as context:
81 context.invalidate()
81 context.invalidate()
82 computed = context.compute()
82 computed = context.compute()
83
83
84 return computed
84 return computed
85
85
86 def _get_landing_commit_or_none(self, db_repo):
86 def _get_landing_commit_or_none(self, db_repo):
87 log.debug("Getting the landing commit.")
87 log.debug("Getting the landing commit.")
88 try:
88 try:
89 commit = db_repo.get_landing_commit()
89 commit = db_repo.get_landing_commit()
90 if not isinstance(commit, EmptyCommit):
90 if not isinstance(commit, EmptyCommit):
91 return commit
91 return commit
92 else:
92 else:
93 log.debug("Repository is empty, no README to render.")
93 log.debug("Repository is empty, no README to render.")
94 except CommitError:
94 except CommitError:
95 log.exception(
95 log.exception(
96 "Problem getting commit when trying to render the README.")
96 "Problem getting commit when trying to render the README.")
97
97
98 def _render_readme_or_none(self, commit, readme_node):
98 def _render_readme_or_none(self, commit, readme_node):
99 log.debug(
99 log.debug(
100 'Found README file `%s` rendering...', readme_node.path)
100 'Found README file `%s` rendering...', readme_node.path)
101 renderer = MarkupRenderer()
101 renderer = MarkupRenderer()
102 try:
102 try:
103 return renderer.render(
103 return renderer.render(
104 readme_node.content, filename=readme_node.path)
104 readme_node.content, filename=readme_node.path)
105 except Exception:
105 except Exception:
106 log.exception(
106 log.exception(
107 "Exception while trying to render the README")
107 "Exception while trying to render the README")
108
108
109 @LoginRequired()
109 @LoginRequired()
110 @HasRepoPermissionAnyDecorator(
110 @HasRepoPermissionAnyDecorator(
111 'repository.read', 'repository.write', 'repository.admin')
111 'repository.read', 'repository.write', 'repository.admin')
112 def index(self, repo_name):
112 def index(self, repo_name):
113
113
114 # Prepare the clone URL
114 # Prepare the clone URL
115
115
116 username = ''
116 username = ''
117 if c.rhodecode_user.username != User.DEFAULT_USER:
117 if c.rhodecode_user.username != User.DEFAULT_USER:
118 username = safe_str(c.rhodecode_user.username)
118 username = safe_str(c.rhodecode_user.username)
119
119
120 _def_clone_uri = _def_clone_uri_by_id = c.clone_uri_tmpl
120 _def_clone_uri = _def_clone_uri_by_id = c.clone_uri_tmpl
121 if '{repo}' in _def_clone_uri:
121 if '{repo}' in _def_clone_uri:
122 _def_clone_uri_by_id = _def_clone_uri.replace(
122 _def_clone_uri_by_id = _def_clone_uri.replace(
123 '{repo}', '_{repoid}')
123 '{repo}', '_{repoid}')
124 elif '{repoid}' in _def_clone_uri:
124 elif '{repoid}' in _def_clone_uri:
125 _def_clone_uri_by_id = _def_clone_uri.replace(
125 _def_clone_uri_by_id = _def_clone_uri.replace(
126 '_{repoid}', '{repo}')
126 '_{repoid}', '{repo}')
127
127
128 c.clone_repo_url = c.rhodecode_db_repo.clone_url(
128 c.clone_repo_url = c.rhodecode_db_repo.clone_url(
129 user=username, uri_tmpl=_def_clone_uri)
129 user=username, uri_tmpl=_def_clone_uri)
130 c.clone_repo_url_id = c.rhodecode_db_repo.clone_url(
130 c.clone_repo_url_id = c.rhodecode_db_repo.clone_url(
131 user=username, uri_tmpl=_def_clone_uri_by_id)
131 user=username, uri_tmpl=_def_clone_uri_by_id)
132
132
133 # If enabled, get statistics data
133 # If enabled, get statistics data
134
134
135 c.show_stats = bool(c.rhodecode_db_repo.enable_statistics)
135 c.show_stats = bool(c.rhodecode_db_repo.enable_statistics)
136
136
137 stats = self.sa.query(Statistics)\
137 stats = self.sa.query(Statistics)\
138 .filter(Statistics.repository == c.rhodecode_db_repo)\
138 .filter(Statistics.repository == c.rhodecode_db_repo)\
139 .scalar()
139 .scalar()
140
140
141 c.stats_percentage = 0
141 c.stats_percentage = 0
142
142
143 if stats and stats.languages:
143 if stats and stats.languages:
144 c.no_data = False is c.rhodecode_db_repo.enable_statistics
144 c.no_data = False is c.rhodecode_db_repo.enable_statistics
145 lang_stats_d = json.loads(stats.languages)
145 lang_stats_d = json.loads(stats.languages)
146
146
147 # Sort first by decreasing count and second by the file extension,
147 # Sort first by decreasing count and second by the file extension,
148 # so we have a consistent output.
148 # so we have a consistent output.
149 lang_stats_items = sorted(lang_stats_d.iteritems(),
149 lang_stats_items = sorted(lang_stats_d.iteritems(),
150 key=lambda k: (-k[1], k[0]))[:10]
150 key=lambda k: (-k[1], k[0]))[:10]
151 lang_stats = [(x, {"count": y,
151 lang_stats = [(x, {"count": y,
152 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
152 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
153 for x, y in lang_stats_items]
153 for x, y in lang_stats_items]
154
154
155 c.trending_languages = json.dumps(lang_stats)
155 c.trending_languages = json.dumps(lang_stats)
156 else:
156 else:
157 c.no_data = True
157 c.no_data = True
158 c.trending_languages = json.dumps({})
158 c.trending_languages = json.dumps({})
159
159
160 c.enable_downloads = c.rhodecode_db_repo.enable_downloads
160 c.enable_downloads = c.rhodecode_db_repo.enable_downloads
161 c.repository_followers = self.scm_model.get_followers(
161 c.repository_followers = self.scm_model.get_followers(
162 c.rhodecode_db_repo)
162 c.rhodecode_db_repo)
163 c.repository_forks = self.scm_model.get_forks(c.rhodecode_db_repo)
163 c.repository_forks = self.scm_model.get_forks(c.rhodecode_db_repo)
164 c.repository_is_user_following = self.scm_model.is_following_repo(
164 c.repository_is_user_following = self.scm_model.is_following_repo(
165 c.repo_name, c.rhodecode_user.user_id)
165 c.repo_name, c.rhodecode_user.user_id)
166
166
167 if c.repository_requirements_missing:
167 if c.repository_requirements_missing:
168 return render('summary/missing_requirements.html')
168 return render('summary/missing_requirements.html')
169
169
170 c.readme_data, c.readme_file = \
170 c.readme_data, c.readme_file = \
171 self.__get_readme_data(c.rhodecode_db_repo)
171 self.__get_readme_data(c.rhodecode_db_repo)
172
172
173 _load_changelog_summary()
173 _load_changelog_summary()
174
174
175 if request.is_xhr:
175 if request.is_xhr:
176 return render('changelog/changelog_summary_data.html')
176 return render('changelog/changelog_summary_data.html')
177
177
178 return render('summary/summary.html')
178 return render('summary/summary.html')
179
179
180 @LoginRequired()
180 @LoginRequired()
181 @XHRRequired()
181 @XHRRequired()
182 @HasRepoPermissionAnyDecorator(
182 @HasRepoPermissionAnyDecorator(
183 'repository.read', 'repository.write', 'repository.admin')
183 'repository.read', 'repository.write', 'repository.admin')
184 @jsonify
184 @jsonify
185 def repo_stats(self, repo_name, commit_id):
185 def repo_stats(self, repo_name, commit_id):
186 _namespace = caches.get_repo_namespace_key(
186 _namespace = caches.get_repo_namespace_key(
187 caches.SUMMARY_STATS, repo_name)
187 caches.SUMMARY_STATS, repo_name)
188 show_stats = bool(c.rhodecode_db_repo.enable_statistics)
188 show_stats = bool(c.rhodecode_db_repo.enable_statistics)
189 cache_manager = caches.get_cache_manager('repo_cache_long', _namespace)
189 cache_manager = caches.get_cache_manager('repo_cache_long', _namespace)
190 _cache_key = caches.compute_key_from_params(
190 _cache_key = caches.compute_key_from_params(
191 repo_name, commit_id, show_stats)
191 repo_name, commit_id, show_stats)
192
192
193 def compute_stats():
193 def compute_stats():
194 code_stats = {}
194 code_stats = {}
195 size = 0
195 size = 0
196 try:
196 try:
197 scm_instance = c.rhodecode_db_repo.scm_instance()
197 scm_instance = c.rhodecode_db_repo.scm_instance()
198 commit = scm_instance.get_commit(commit_id)
198 commit = scm_instance.get_commit(commit_id)
199
199
200 for node in commit.get_filenodes_generator():
200 for node in commit.get_filenodes_generator():
201 size += node.size
201 size += node.size
202 if not show_stats:
202 if not show_stats:
203 continue
203 continue
204 ext = lower(node.extension)
204 ext = lower(node.extension)
205 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
205 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
206 if ext_info:
206 if ext_info:
207 if ext in code_stats:
207 if ext in code_stats:
208 code_stats[ext]['count'] += 1
208 code_stats[ext]['count'] += 1
209 else:
209 else:
210 code_stats[ext] = {"count": 1, "desc": ext_info}
210 code_stats[ext] = {"count": 1, "desc": ext_info}
211 except EmptyRepositoryError:
211 except EmptyRepositoryError:
212 pass
212 pass
213 return {'size': h.format_byte_size_binary(size),
213 return {'size': h.format_byte_size_binary(size),
214 'code_stats': code_stats}
214 'code_stats': code_stats}
215
215
216 stats = cache_manager.get(_cache_key, createfunc=compute_stats)
216 stats = cache_manager.get(_cache_key, createfunc=compute_stats)
217 return stats
217 return stats
218
218
219 def _switcher_reference_data(self, repo_name, references, is_svn):
219 def _switcher_reference_data(self, repo_name, references, is_svn):
220 """Prepare reference data for given `references`"""
220 """Prepare reference data for given `references`"""
221 items = []
221 items = []
222 for name, commit_id in references.items():
222 for name, commit_id in references.items():
223 use_commit_id = '/' in name or is_svn
223 use_commit_id = '/' in name or is_svn
224 items.append({
224 items.append({
225 'name': name,
225 'name': name,
226 'commit_id': commit_id,
226 'commit_id': commit_id,
227 'files_url': h.url(
227 'files_url': h.url(
228 'files_home',
228 'files_home',
229 repo_name=repo_name,
229 repo_name=repo_name,
230 f_path=name if is_svn else '',
230 f_path=name if is_svn else '',
231 revision=commit_id if use_commit_id else name,
231 revision=commit_id if use_commit_id else name,
232 at=name)
232 at=name)
233 })
233 })
234 return items
234 return items
235
235
236 @LoginRequired()
236 @LoginRequired()
237 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
237 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
238 'repository.admin')
238 'repository.admin')
239 @jsonify
239 @jsonify
240 def repo_refs_data(self, repo_name):
240 def repo_refs_data(self, repo_name):
241 repo = c.rhodecode_repo
241 repo = c.rhodecode_repo
242 refs_to_create = [
242 refs_to_create = [
243 (_("Branch"), repo.branches, 'branch'),
243 (_("Branch"), repo.branches, 'branch'),
244 (_("Tag"), repo.tags, 'tag'),
244 (_("Tag"), repo.tags, 'tag'),
245 (_("Bookmark"), repo.bookmarks, 'book'),
245 (_("Bookmark"), repo.bookmarks, 'book'),
246 ]
246 ]
247 res = self._create_reference_data(repo, repo_name, refs_to_create)
247 res = self._create_reference_data(repo, repo_name, refs_to_create)
248 data = {
248 data = {
249 'more': False,
249 'more': False,
250 'results': res
250 'results': res
251 }
251 }
252 return data
252 return data
253
253
254 @LoginRequired()
255 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
256 'repository.admin')
257 @jsonify
258 def repo_default_reviewers_data(self, repo_name):
259 return {
260 'reviewers': [utils.reviewer_as_json(
261 user=c.rhodecode_db_repo.user, reasons=None)]
262 }
263
254 @jsonify
264 @jsonify
255 def repo_refs_changelog_data(self, repo_name):
265 def repo_refs_changelog_data(self, repo_name):
256 repo = c.rhodecode_repo
266 repo = c.rhodecode_repo
257
267
258 refs_to_create = [
268 refs_to_create = [
259 (_("Branches"), repo.branches, 'branch'),
269 (_("Branches"), repo.branches, 'branch'),
260 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
270 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
261 # TODO: enable when vcs can handle bookmarks filters
271 # TODO: enable when vcs can handle bookmarks filters
262 # (_("Bookmarks"), repo.bookmarks, "book"),
272 # (_("Bookmarks"), repo.bookmarks, "book"),
263 ]
273 ]
264 res = self._create_reference_data(repo, repo_name, refs_to_create)
274 res = self._create_reference_data(repo, repo_name, refs_to_create)
265 data = {
275 data = {
266 'more': False,
276 'more': False,
267 'results': res
277 'results': res
268 }
278 }
269 return data
279 return data
270
280
271 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
281 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
272 format_ref_id = utils.get_format_ref_id(repo)
282 format_ref_id = utils.get_format_ref_id(repo)
273
283
274 result = []
284 result = []
275 for title, refs, ref_type in refs_to_create:
285 for title, refs, ref_type in refs_to_create:
276 if refs:
286 if refs:
277 result.append({
287 result.append({
278 'text': title,
288 'text': title,
279 'children': self._create_reference_items(
289 'children': self._create_reference_items(
280 repo, full_repo_name, refs, ref_type, format_ref_id),
290 repo, full_repo_name, refs, ref_type, format_ref_id),
281 })
291 })
282 return result
292 return result
283
293
284 def _create_reference_items(self, repo, full_repo_name, refs, ref_type,
294 def _create_reference_items(self, repo, full_repo_name, refs, ref_type,
285 format_ref_id):
295 format_ref_id):
286 result = []
296 result = []
287 is_svn = h.is_svn(repo)
297 is_svn = h.is_svn(repo)
288 for ref_name, raw_id in refs.iteritems():
298 for ref_name, raw_id in refs.iteritems():
289 files_url = self._create_files_url(
299 files_url = self._create_files_url(
290 repo, full_repo_name, ref_name, raw_id, is_svn)
300 repo, full_repo_name, ref_name, raw_id, is_svn)
291 result.append({
301 result.append({
292 'text': ref_name,
302 'text': ref_name,
293 'id': format_ref_id(ref_name, raw_id),
303 'id': format_ref_id(ref_name, raw_id),
294 'raw_id': raw_id,
304 'raw_id': raw_id,
295 'type': ref_type,
305 'type': ref_type,
296 'files_url': files_url,
306 'files_url': files_url,
297 })
307 })
298 return result
308 return result
299
309
300 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id,
310 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id,
301 is_svn):
311 is_svn):
302 use_commit_id = '/' in ref_name or is_svn
312 use_commit_id = '/' in ref_name or is_svn
303 return h.url(
313 return h.url(
304 'files_home',
314 'files_home',
305 repo_name=full_repo_name,
315 repo_name=full_repo_name,
306 f_path=ref_name if is_svn else '',
316 f_path=ref_name if is_svn else '',
307 revision=raw_id if use_commit_id else ref_name,
317 revision=raw_id if use_commit_id else ref_name,
308 at=ref_name)
318 at=ref_name)
@@ -1,88 +1,106 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Utilities to be shared by multiple controllers.
22 Utilities to be shared by multiple controllers.
23
23
24 Should only contain utilities to be shared in the controller layer.
24 Should only contain utilities to be shared in the controller layer.
25 """
25 """
26
26
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib.vcs.exceptions import RepositoryError
28 from rhodecode.lib.vcs.exceptions import RepositoryError
29
29
30 def parse_path_ref(ref, default_path=None):
30 def parse_path_ref(ref, default_path=None):
31 """
31 """
32 Parse out a path and reference combination and return both parts of it.
32 Parse out a path and reference combination and return both parts of it.
33
33
34 This is used to allow support of path based comparisons for Subversion
34 This is used to allow support of path based comparisons for Subversion
35 as an iterim solution in parameter handling.
35 as an iterim solution in parameter handling.
36 """
36 """
37 if '@' in ref:
37 if '@' in ref:
38 return ref.rsplit('@', 1)
38 return ref.rsplit('@', 1)
39 else:
39 else:
40 return default_path, ref
40 return default_path, ref
41
41
42
42
43 def get_format_ref_id(repo):
43 def get_format_ref_id(repo):
44 """Returns a `repo` specific reference formatter function"""
44 """Returns a `repo` specific reference formatter function"""
45 if h.is_svn(repo):
45 if h.is_svn(repo):
46 return _format_ref_id_svn
46 return _format_ref_id_svn
47 else:
47 else:
48 return _format_ref_id
48 return _format_ref_id
49
49
50
50
51 def _format_ref_id(name, raw_id):
51 def _format_ref_id(name, raw_id):
52 """Default formatting of a given reference `name`"""
52 """Default formatting of a given reference `name`"""
53 return name
53 return name
54
54
55
55
56 def _format_ref_id_svn(name, raw_id):
56 def _format_ref_id_svn(name, raw_id):
57 """Special way of formatting a reference for Subversion including path"""
57 """Special way of formatting a reference for Subversion including path"""
58 return '%s@%s' % (name, raw_id)
58 return '%s@%s' % (name, raw_id)
59
59
60
60
61 def get_commit_from_ref_name(repo, ref_name, ref_type=None):
61 def get_commit_from_ref_name(repo, ref_name, ref_type=None):
62 """
62 """
63 Gets the commit for a `ref_name` taking into account `ref_type`.
63 Gets the commit for a `ref_name` taking into account `ref_type`.
64 Needed in case a bookmark / tag share the same name.
64 Needed in case a bookmark / tag share the same name.
65
65
66 :param repo: the repo instance
66 :param repo: the repo instance
67 :param ref_name: the name of the ref to get
67 :param ref_name: the name of the ref to get
68 :param ref_type: optional, used to disambiguate colliding refs
68 :param ref_type: optional, used to disambiguate colliding refs
69 """
69 """
70 repo_scm = repo.scm_instance()
70 repo_scm = repo.scm_instance()
71 ref_type_mapping = {
71 ref_type_mapping = {
72 'book': repo_scm.bookmarks,
72 'book': repo_scm.bookmarks,
73 'bookmark': repo_scm.bookmarks,
73 'bookmark': repo_scm.bookmarks,
74 'tag': repo_scm.tags,
74 'tag': repo_scm.tags,
75 'branch': repo_scm.branches,
75 'branch': repo_scm.branches,
76 }
76 }
77
77
78 commit_id = ref_name
78 commit_id = ref_name
79 if repo_scm.alias != 'svn': # pass svn refs straight to backend until
79 if repo_scm.alias != 'svn': # pass svn refs straight to backend until
80 # the branch issue with svn is fixed
80 # the branch issue with svn is fixed
81 if ref_type and ref_type in ref_type_mapping:
81 if ref_type and ref_type in ref_type_mapping:
82 try:
82 try:
83 commit_id = ref_type_mapping[ref_type][ref_name]
83 commit_id = ref_type_mapping[ref_type][ref_name]
84 except KeyError:
84 except KeyError:
85 raise RepositoryError(
85 raise RepositoryError(
86 '%s "%s" does not exist' % (ref_type, ref_name))
86 '%s "%s" does not exist' % (ref_type, ref_name))
87
87
88 return repo_scm.get_commit(commit_id)
88 return repo_scm.get_commit(commit_id)
89
90
91 def reviewer_as_json(user, reasons):
92 """
93 Returns json struct of a reviewer for frontend
94
95 :param user: the reviewer
96 :param reasons: list of strings of why they are reviewers
97 """
98
99 return {
100 'user_id': user.user_id,
101 'reasons': reasons,
102 'username': user.username,
103 'firstname': user.firstname,
104 'lastname': user.lastname,
105 'gravatar_link': h.gravatar_url(user.email, 14),
106 }
@@ -1,895 +1,936 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2016 RhodeCode GmbH
3 # Copyright (C) 2011-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 Some simple helper functions
23 Some simple helper functions
24 """
24 """
25
25
26
26
27 import collections
27 import collections
28 import datetime
28 import datetime
29 import dateutil.relativedelta
29 import dateutil.relativedelta
30 import hashlib
30 import hashlib
31 import logging
31 import logging
32 import re
32 import re
33 import sys
33 import sys
34 import time
34 import time
35 import threading
35 import threading
36 import urllib
36 import urllib
37 import urlobject
37 import urlobject
38 import uuid
38 import uuid
39
39
40 import pygments.lexers
40 import pygments.lexers
41 import sqlalchemy
41 import sqlalchemy
42 import sqlalchemy.engine.url
42 import sqlalchemy.engine.url
43 import webob
43 import webob
44 import routes.util
44 import routes.util
45
45
46 import rhodecode
46 import rhodecode
47
47
48
48
49 def md5(s):
49 def md5(s):
50 return hashlib.md5(s).hexdigest()
50 return hashlib.md5(s).hexdigest()
51
51
52
52
53 def md5_safe(s):
53 def md5_safe(s):
54 return md5(safe_str(s))
54 return md5(safe_str(s))
55
55
56
56
57 def __get_lem(extra_mapping=None):
57 def __get_lem(extra_mapping=None):
58 """
58 """
59 Get language extension map based on what's inside pygments lexers
59 Get language extension map based on what's inside pygments lexers
60 """
60 """
61 d = collections.defaultdict(lambda: [])
61 d = collections.defaultdict(lambda: [])
62
62
63 def __clean(s):
63 def __clean(s):
64 s = s.lstrip('*')
64 s = s.lstrip('*')
65 s = s.lstrip('.')
65 s = s.lstrip('.')
66
66
67 if s.find('[') != -1:
67 if s.find('[') != -1:
68 exts = []
68 exts = []
69 start, stop = s.find('['), s.find(']')
69 start, stop = s.find('['), s.find(']')
70
70
71 for suffix in s[start + 1:stop]:
71 for suffix in s[start + 1:stop]:
72 exts.append(s[:s.find('[')] + suffix)
72 exts.append(s[:s.find('[')] + suffix)
73 return [e.lower() for e in exts]
73 return [e.lower() for e in exts]
74 else:
74 else:
75 return [s.lower()]
75 return [s.lower()]
76
76
77 for lx, t in sorted(pygments.lexers.LEXERS.items()):
77 for lx, t in sorted(pygments.lexers.LEXERS.items()):
78 m = map(__clean, t[-2])
78 m = map(__clean, t[-2])
79 if m:
79 if m:
80 m = reduce(lambda x, y: x + y, m)
80 m = reduce(lambda x, y: x + y, m)
81 for ext in m:
81 for ext in m:
82 desc = lx.replace('Lexer', '')
82 desc = lx.replace('Lexer', '')
83 d[ext].append(desc)
83 d[ext].append(desc)
84
84
85 data = dict(d)
85 data = dict(d)
86
86
87 extra_mapping = extra_mapping or {}
87 extra_mapping = extra_mapping or {}
88 if extra_mapping:
88 if extra_mapping:
89 for k, v in extra_mapping.items():
89 for k, v in extra_mapping.items():
90 if k not in data:
90 if k not in data:
91 # register new mapping2lexer
91 # register new mapping2lexer
92 data[k] = [v]
92 data[k] = [v]
93
93
94 return data
94 return data
95
95
96
96
97 def str2bool(_str):
97 def str2bool(_str):
98 """
98 """
99 returs True/False value from given string, it tries to translate the
99 returs True/False value from given string, it tries to translate the
100 string into boolean
100 string into boolean
101
101
102 :param _str: string value to translate into boolean
102 :param _str: string value to translate into boolean
103 :rtype: boolean
103 :rtype: boolean
104 :returns: boolean from given string
104 :returns: boolean from given string
105 """
105 """
106 if _str is None:
106 if _str is None:
107 return False
107 return False
108 if _str in (True, False):
108 if _str in (True, False):
109 return _str
109 return _str
110 _str = str(_str).strip().lower()
110 _str = str(_str).strip().lower()
111 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
111 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
112
112
113
113
114 def aslist(obj, sep=None, strip=True):
114 def aslist(obj, sep=None, strip=True):
115 """
115 """
116 Returns given string separated by sep as list
116 Returns given string separated by sep as list
117
117
118 :param obj:
118 :param obj:
119 :param sep:
119 :param sep:
120 :param strip:
120 :param strip:
121 """
121 """
122 if isinstance(obj, (basestring,)):
122 if isinstance(obj, (basestring,)):
123 lst = obj.split(sep)
123 lst = obj.split(sep)
124 if strip:
124 if strip:
125 lst = [v.strip() for v in lst]
125 lst = [v.strip() for v in lst]
126 return lst
126 return lst
127 elif isinstance(obj, (list, tuple)):
127 elif isinstance(obj, (list, tuple)):
128 return obj
128 return obj
129 elif obj is None:
129 elif obj is None:
130 return []
130 return []
131 else:
131 else:
132 return [obj]
132 return [obj]
133
133
134
134
135 def convert_line_endings(line, mode):
135 def convert_line_endings(line, mode):
136 """
136 """
137 Converts a given line "line end" accordingly to given mode
137 Converts a given line "line end" accordingly to given mode
138
138
139 Available modes are::
139 Available modes are::
140 0 - Unix
140 0 - Unix
141 1 - Mac
141 1 - Mac
142 2 - DOS
142 2 - DOS
143
143
144 :param line: given line to convert
144 :param line: given line to convert
145 :param mode: mode to convert to
145 :param mode: mode to convert to
146 :rtype: str
146 :rtype: str
147 :return: converted line according to mode
147 :return: converted line according to mode
148 """
148 """
149 if mode == 0:
149 if mode == 0:
150 line = line.replace('\r\n', '\n')
150 line = line.replace('\r\n', '\n')
151 line = line.replace('\r', '\n')
151 line = line.replace('\r', '\n')
152 elif mode == 1:
152 elif mode == 1:
153 line = line.replace('\r\n', '\r')
153 line = line.replace('\r\n', '\r')
154 line = line.replace('\n', '\r')
154 line = line.replace('\n', '\r')
155 elif mode == 2:
155 elif mode == 2:
156 line = re.sub('\r(?!\n)|(?<!\r)\n', '\r\n', line)
156 line = re.sub('\r(?!\n)|(?<!\r)\n', '\r\n', line)
157 return line
157 return line
158
158
159
159
160 def detect_mode(line, default):
160 def detect_mode(line, default):
161 """
161 """
162 Detects line break for given line, if line break couldn't be found
162 Detects line break for given line, if line break couldn't be found
163 given default value is returned
163 given default value is returned
164
164
165 :param line: str line
165 :param line: str line
166 :param default: default
166 :param default: default
167 :rtype: int
167 :rtype: int
168 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
168 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
169 """
169 """
170 if line.endswith('\r\n'):
170 if line.endswith('\r\n'):
171 return 2
171 return 2
172 elif line.endswith('\n'):
172 elif line.endswith('\n'):
173 return 0
173 return 0
174 elif line.endswith('\r'):
174 elif line.endswith('\r'):
175 return 1
175 return 1
176 else:
176 else:
177 return default
177 return default
178
178
179
179
180 def safe_int(val, default=None):
180 def safe_int(val, default=None):
181 """
181 """
182 Returns int() of val if val is not convertable to int use default
182 Returns int() of val if val is not convertable to int use default
183 instead
183 instead
184
184
185 :param val:
185 :param val:
186 :param default:
186 :param default:
187 """
187 """
188
188
189 try:
189 try:
190 val = int(val)
190 val = int(val)
191 except (ValueError, TypeError):
191 except (ValueError, TypeError):
192 val = default
192 val = default
193
193
194 return val
194 return val
195
195
196
196
197 def safe_unicode(str_, from_encoding=None):
197 def safe_unicode(str_, from_encoding=None):
198 """
198 """
199 safe unicode function. Does few trick to turn str_ into unicode
199 safe unicode function. Does few trick to turn str_ into unicode
200
200
201 In case of UnicodeDecode error, we try to return it with encoding detected
201 In case of UnicodeDecode error, we try to return it with encoding detected
202 by chardet library if it fails fallback to unicode with errors replaced
202 by chardet library if it fails fallback to unicode with errors replaced
203
203
204 :param str_: string to decode
204 :param str_: string to decode
205 :rtype: unicode
205 :rtype: unicode
206 :returns: unicode object
206 :returns: unicode object
207 """
207 """
208 if isinstance(str_, unicode):
208 if isinstance(str_, unicode):
209 return str_
209 return str_
210
210
211 if not from_encoding:
211 if not from_encoding:
212 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
212 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
213 'utf8'), sep=',')
213 'utf8'), sep=',')
214 from_encoding = DEFAULT_ENCODINGS
214 from_encoding = DEFAULT_ENCODINGS
215
215
216 if not isinstance(from_encoding, (list, tuple)):
216 if not isinstance(from_encoding, (list, tuple)):
217 from_encoding = [from_encoding]
217 from_encoding = [from_encoding]
218
218
219 try:
219 try:
220 return unicode(str_)
220 return unicode(str_)
221 except UnicodeDecodeError:
221 except UnicodeDecodeError:
222 pass
222 pass
223
223
224 for enc in from_encoding:
224 for enc in from_encoding:
225 try:
225 try:
226 return unicode(str_, enc)
226 return unicode(str_, enc)
227 except UnicodeDecodeError:
227 except UnicodeDecodeError:
228 pass
228 pass
229
229
230 try:
230 try:
231 import chardet
231 import chardet
232 encoding = chardet.detect(str_)['encoding']
232 encoding = chardet.detect(str_)['encoding']
233 if encoding is None:
233 if encoding is None:
234 raise Exception()
234 raise Exception()
235 return str_.decode(encoding)
235 return str_.decode(encoding)
236 except (ImportError, UnicodeDecodeError, Exception):
236 except (ImportError, UnicodeDecodeError, Exception):
237 return unicode(str_, from_encoding[0], 'replace')
237 return unicode(str_, from_encoding[0], 'replace')
238
238
239
239
240 def safe_str(unicode_, to_encoding=None):
240 def safe_str(unicode_, to_encoding=None):
241 """
241 """
242 safe str function. Does few trick to turn unicode_ into string
242 safe str function. Does few trick to turn unicode_ into string
243
243
244 In case of UnicodeEncodeError, we try to return it with encoding detected
244 In case of UnicodeEncodeError, we try to return it with encoding detected
245 by chardet library if it fails fallback to string with errors replaced
245 by chardet library if it fails fallback to string with errors replaced
246
246
247 :param unicode_: unicode to encode
247 :param unicode_: unicode to encode
248 :rtype: str
248 :rtype: str
249 :returns: str object
249 :returns: str object
250 """
250 """
251
251
252 # if it's not basestr cast to str
252 # if it's not basestr cast to str
253 if not isinstance(unicode_, basestring):
253 if not isinstance(unicode_, basestring):
254 return str(unicode_)
254 return str(unicode_)
255
255
256 if isinstance(unicode_, str):
256 if isinstance(unicode_, str):
257 return unicode_
257 return unicode_
258
258
259 if not to_encoding:
259 if not to_encoding:
260 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
260 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
261 'utf8'), sep=',')
261 'utf8'), sep=',')
262 to_encoding = DEFAULT_ENCODINGS
262 to_encoding = DEFAULT_ENCODINGS
263
263
264 if not isinstance(to_encoding, (list, tuple)):
264 if not isinstance(to_encoding, (list, tuple)):
265 to_encoding = [to_encoding]
265 to_encoding = [to_encoding]
266
266
267 for enc in to_encoding:
267 for enc in to_encoding:
268 try:
268 try:
269 return unicode_.encode(enc)
269 return unicode_.encode(enc)
270 except UnicodeEncodeError:
270 except UnicodeEncodeError:
271 pass
271 pass
272
272
273 try:
273 try:
274 import chardet
274 import chardet
275 encoding = chardet.detect(unicode_)['encoding']
275 encoding = chardet.detect(unicode_)['encoding']
276 if encoding is None:
276 if encoding is None:
277 raise UnicodeEncodeError()
277 raise UnicodeEncodeError()
278
278
279 return unicode_.encode(encoding)
279 return unicode_.encode(encoding)
280 except (ImportError, UnicodeEncodeError):
280 except (ImportError, UnicodeEncodeError):
281 return unicode_.encode(to_encoding[0], 'replace')
281 return unicode_.encode(to_encoding[0], 'replace')
282
282
283
283
284 def remove_suffix(s, suffix):
284 def remove_suffix(s, suffix):
285 if s.endswith(suffix):
285 if s.endswith(suffix):
286 s = s[:-1 * len(suffix)]
286 s = s[:-1 * len(suffix)]
287 return s
287 return s
288
288
289
289
290 def remove_prefix(s, prefix):
290 def remove_prefix(s, prefix):
291 if s.startswith(prefix):
291 if s.startswith(prefix):
292 s = s[len(prefix):]
292 s = s[len(prefix):]
293 return s
293 return s
294
294
295
295
296 def find_calling_context(ignore_modules=None):
296 def find_calling_context(ignore_modules=None):
297 """
297 """
298 Look through the calling stack and return the frame which called
298 Look through the calling stack and return the frame which called
299 this function and is part of core module ( ie. rhodecode.* )
299 this function and is part of core module ( ie. rhodecode.* )
300
300
301 :param ignore_modules: list of modules to ignore eg. ['rhodecode.lib']
301 :param ignore_modules: list of modules to ignore eg. ['rhodecode.lib']
302 """
302 """
303
303
304 ignore_modules = ignore_modules or []
304 ignore_modules = ignore_modules or []
305
305
306 f = sys._getframe(2)
306 f = sys._getframe(2)
307 while f.f_back is not None:
307 while f.f_back is not None:
308 name = f.f_globals.get('__name__')
308 name = f.f_globals.get('__name__')
309 if name and name.startswith(__name__.split('.')[0]):
309 if name and name.startswith(__name__.split('.')[0]):
310 if name not in ignore_modules:
310 if name not in ignore_modules:
311 return f
311 return f
312 f = f.f_back
312 f = f.f_back
313 return None
313 return None
314
314
315
315
316 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
316 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
317 """Custom engine_from_config functions."""
317 """Custom engine_from_config functions."""
318 log = logging.getLogger('sqlalchemy.engine')
318 log = logging.getLogger('sqlalchemy.engine')
319 engine = sqlalchemy.engine_from_config(configuration, prefix, **kwargs)
319 engine = sqlalchemy.engine_from_config(configuration, prefix, **kwargs)
320
320
321 def color_sql(sql):
321 def color_sql(sql):
322 color_seq = '\033[1;33m' # This is yellow: code 33
322 color_seq = '\033[1;33m' # This is yellow: code 33
323 normal = '\x1b[0m'
323 normal = '\x1b[0m'
324 return ''.join([color_seq, sql, normal])
324 return ''.join([color_seq, sql, normal])
325
325
326 if configuration['debug']:
326 if configuration['debug']:
327 # attach events only for debug configuration
327 # attach events only for debug configuration
328
328
329 def before_cursor_execute(conn, cursor, statement,
329 def before_cursor_execute(conn, cursor, statement,
330 parameters, context, executemany):
330 parameters, context, executemany):
331 setattr(conn, 'query_start_time', time.time())
331 setattr(conn, 'query_start_time', time.time())
332 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
332 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
333 calling_context = find_calling_context(ignore_modules=[
333 calling_context = find_calling_context(ignore_modules=[
334 'rhodecode.lib.caching_query',
334 'rhodecode.lib.caching_query',
335 'rhodecode.model.settings',
335 'rhodecode.model.settings',
336 ])
336 ])
337 if calling_context:
337 if calling_context:
338 log.info(color_sql('call context %s:%s' % (
338 log.info(color_sql('call context %s:%s' % (
339 calling_context.f_code.co_filename,
339 calling_context.f_code.co_filename,
340 calling_context.f_lineno,
340 calling_context.f_lineno,
341 )))
341 )))
342
342
343 def after_cursor_execute(conn, cursor, statement,
343 def after_cursor_execute(conn, cursor, statement,
344 parameters, context, executemany):
344 parameters, context, executemany):
345 delattr(conn, 'query_start_time')
345 delattr(conn, 'query_start_time')
346
346
347 sqlalchemy.event.listen(engine, "before_cursor_execute",
347 sqlalchemy.event.listen(engine, "before_cursor_execute",
348 before_cursor_execute)
348 before_cursor_execute)
349 sqlalchemy.event.listen(engine, "after_cursor_execute",
349 sqlalchemy.event.listen(engine, "after_cursor_execute",
350 after_cursor_execute)
350 after_cursor_execute)
351
351
352 return engine
352 return engine
353
353
354
354
355 def get_encryption_key(config):
355 def get_encryption_key(config):
356 secret = config.get('rhodecode.encrypted_values.secret')
356 secret = config.get('rhodecode.encrypted_values.secret')
357 default = config['beaker.session.secret']
357 default = config['beaker.session.secret']
358 return secret or default
358 return secret or default
359
359
360
360
361 def age(prevdate, now=None, show_short_version=False, show_suffix=True,
361 def age(prevdate, now=None, show_short_version=False, show_suffix=True,
362 short_format=False):
362 short_format=False):
363 """
363 """
364 Turns a datetime into an age string.
364 Turns a datetime into an age string.
365 If show_short_version is True, this generates a shorter string with
365 If show_short_version is True, this generates a shorter string with
366 an approximate age; ex. '1 day ago', rather than '1 day and 23 hours ago'.
366 an approximate age; ex. '1 day ago', rather than '1 day and 23 hours ago'.
367
367
368 * IMPORTANT*
368 * IMPORTANT*
369 Code of this function is written in special way so it's easier to
369 Code of this function is written in special way so it's easier to
370 backport it to javascript. If you mean to update it, please also update
370 backport it to javascript. If you mean to update it, please also update
371 `jquery.timeago-extension.js` file
371 `jquery.timeago-extension.js` file
372
372
373 :param prevdate: datetime object
373 :param prevdate: datetime object
374 :param now: get current time, if not define we use
374 :param now: get current time, if not define we use
375 `datetime.datetime.now()`
375 `datetime.datetime.now()`
376 :param show_short_version: if it should approximate the date and
376 :param show_short_version: if it should approximate the date and
377 return a shorter string
377 return a shorter string
378 :param show_suffix:
378 :param show_suffix:
379 :param short_format: show short format, eg 2D instead of 2 days
379 :param short_format: show short format, eg 2D instead of 2 days
380 :rtype: unicode
380 :rtype: unicode
381 :returns: unicode words describing age
381 :returns: unicode words describing age
382 """
382 """
383 from pylons.i18n.translation import _, ungettext
383 from pylons.i18n.translation import _, ungettext
384
384
385 def _get_relative_delta(now, prevdate):
385 def _get_relative_delta(now, prevdate):
386 base = dateutil.relativedelta.relativedelta(now, prevdate)
386 base = dateutil.relativedelta.relativedelta(now, prevdate)
387 return {
387 return {
388 'year': base.years,
388 'year': base.years,
389 'month': base.months,
389 'month': base.months,
390 'day': base.days,
390 'day': base.days,
391 'hour': base.hours,
391 'hour': base.hours,
392 'minute': base.minutes,
392 'minute': base.minutes,
393 'second': base.seconds,
393 'second': base.seconds,
394 }
394 }
395
395
396 def _is_leap_year(year):
396 def _is_leap_year(year):
397 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
397 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
398
398
399 def get_month(prevdate):
399 def get_month(prevdate):
400 return prevdate.month
400 return prevdate.month
401
401
402 def get_year(prevdate):
402 def get_year(prevdate):
403 return prevdate.year
403 return prevdate.year
404
404
405 now = now or datetime.datetime.now()
405 now = now or datetime.datetime.now()
406 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
406 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
407 deltas = {}
407 deltas = {}
408 future = False
408 future = False
409
409
410 if prevdate > now:
410 if prevdate > now:
411 now_old = now
411 now_old = now
412 now = prevdate
412 now = prevdate
413 prevdate = now_old
413 prevdate = now_old
414 future = True
414 future = True
415 if future:
415 if future:
416 prevdate = prevdate.replace(microsecond=0)
416 prevdate = prevdate.replace(microsecond=0)
417 # Get date parts deltas
417 # Get date parts deltas
418 for part in order:
418 for part in order:
419 rel_delta = _get_relative_delta(now, prevdate)
419 rel_delta = _get_relative_delta(now, prevdate)
420 deltas[part] = rel_delta[part]
420 deltas[part] = rel_delta[part]
421
421
422 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
422 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
423 # not 1 hour, -59 minutes and -59 seconds)
423 # not 1 hour, -59 minutes and -59 seconds)
424 offsets = [[5, 60], [4, 60], [3, 24]]
424 offsets = [[5, 60], [4, 60], [3, 24]]
425 for element in offsets: # seconds, minutes, hours
425 for element in offsets: # seconds, minutes, hours
426 num = element[0]
426 num = element[0]
427 length = element[1]
427 length = element[1]
428
428
429 part = order[num]
429 part = order[num]
430 carry_part = order[num - 1]
430 carry_part = order[num - 1]
431
431
432 if deltas[part] < 0:
432 if deltas[part] < 0:
433 deltas[part] += length
433 deltas[part] += length
434 deltas[carry_part] -= 1
434 deltas[carry_part] -= 1
435
435
436 # Same thing for days except that the increment depends on the (variable)
436 # Same thing for days except that the increment depends on the (variable)
437 # number of days in the month
437 # number of days in the month
438 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
438 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
439 if deltas['day'] < 0:
439 if deltas['day'] < 0:
440 if get_month(prevdate) == 2 and _is_leap_year(get_year(prevdate)):
440 if get_month(prevdate) == 2 and _is_leap_year(get_year(prevdate)):
441 deltas['day'] += 29
441 deltas['day'] += 29
442 else:
442 else:
443 deltas['day'] += month_lengths[get_month(prevdate) - 1]
443 deltas['day'] += month_lengths[get_month(prevdate) - 1]
444
444
445 deltas['month'] -= 1
445 deltas['month'] -= 1
446
446
447 if deltas['month'] < 0:
447 if deltas['month'] < 0:
448 deltas['month'] += 12
448 deltas['month'] += 12
449 deltas['year'] -= 1
449 deltas['year'] -= 1
450
450
451 # Format the result
451 # Format the result
452 if short_format:
452 if short_format:
453 fmt_funcs = {
453 fmt_funcs = {
454 'year': lambda d: u'%dy' % d,
454 'year': lambda d: u'%dy' % d,
455 'month': lambda d: u'%dm' % d,
455 'month': lambda d: u'%dm' % d,
456 'day': lambda d: u'%dd' % d,
456 'day': lambda d: u'%dd' % d,
457 'hour': lambda d: u'%dh' % d,
457 'hour': lambda d: u'%dh' % d,
458 'minute': lambda d: u'%dmin' % d,
458 'minute': lambda d: u'%dmin' % d,
459 'second': lambda d: u'%dsec' % d,
459 'second': lambda d: u'%dsec' % d,
460 }
460 }
461 else:
461 else:
462 fmt_funcs = {
462 fmt_funcs = {
463 'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
463 'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
464 'month': lambda d: ungettext(u'%d month', '%d months', d) % d,
464 'month': lambda d: ungettext(u'%d month', '%d months', d) % d,
465 'day': lambda d: ungettext(u'%d day', '%d days', d) % d,
465 'day': lambda d: ungettext(u'%d day', '%d days', d) % d,
466 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d,
466 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d,
467 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d,
467 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d,
468 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d,
468 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d,
469 }
469 }
470
470
471 i = 0
471 i = 0
472 for part in order:
472 for part in order:
473 value = deltas[part]
473 value = deltas[part]
474 if value != 0:
474 if value != 0:
475
475
476 if i < 5:
476 if i < 5:
477 sub_part = order[i + 1]
477 sub_part = order[i + 1]
478 sub_value = deltas[sub_part]
478 sub_value = deltas[sub_part]
479 else:
479 else:
480 sub_value = 0
480 sub_value = 0
481
481
482 if sub_value == 0 or show_short_version:
482 if sub_value == 0 or show_short_version:
483 _val = fmt_funcs[part](value)
483 _val = fmt_funcs[part](value)
484 if future:
484 if future:
485 if show_suffix:
485 if show_suffix:
486 return _(u'in %s') % _val
486 return _(u'in %s') % _val
487 else:
487 else:
488 return _val
488 return _val
489
489
490 else:
490 else:
491 if show_suffix:
491 if show_suffix:
492 return _(u'%s ago') % _val
492 return _(u'%s ago') % _val
493 else:
493 else:
494 return _val
494 return _val
495
495
496 val = fmt_funcs[part](value)
496 val = fmt_funcs[part](value)
497 val_detail = fmt_funcs[sub_part](sub_value)
497 val_detail = fmt_funcs[sub_part](sub_value)
498
498
499 if short_format:
499 if short_format:
500 datetime_tmpl = u'%s, %s'
500 datetime_tmpl = u'%s, %s'
501 if show_suffix:
501 if show_suffix:
502 datetime_tmpl = _(u'%s, %s ago')
502 datetime_tmpl = _(u'%s, %s ago')
503 if future:
503 if future:
504 datetime_tmpl = _(u'in %s, %s')
504 datetime_tmpl = _(u'in %s, %s')
505 else:
505 else:
506 datetime_tmpl = _(u'%s and %s')
506 datetime_tmpl = _(u'%s and %s')
507 if show_suffix:
507 if show_suffix:
508 datetime_tmpl = _(u'%s and %s ago')
508 datetime_tmpl = _(u'%s and %s ago')
509 if future:
509 if future:
510 datetime_tmpl = _(u'in %s and %s')
510 datetime_tmpl = _(u'in %s and %s')
511
511
512 return datetime_tmpl % (val, val_detail)
512 return datetime_tmpl % (val, val_detail)
513 i += 1
513 i += 1
514 return _(u'just now')
514 return _(u'just now')
515
515
516
516
517 def uri_filter(uri):
517 def uri_filter(uri):
518 """
518 """
519 Removes user:password from given url string
519 Removes user:password from given url string
520
520
521 :param uri:
521 :param uri:
522 :rtype: unicode
522 :rtype: unicode
523 :returns: filtered list of strings
523 :returns: filtered list of strings
524 """
524 """
525 if not uri:
525 if not uri:
526 return ''
526 return ''
527
527
528 proto = ''
528 proto = ''
529
529
530 for pat in ('https://', 'http://'):
530 for pat in ('https://', 'http://'):
531 if uri.startswith(pat):
531 if uri.startswith(pat):
532 uri = uri[len(pat):]
532 uri = uri[len(pat):]
533 proto = pat
533 proto = pat
534 break
534 break
535
535
536 # remove passwords and username
536 # remove passwords and username
537 uri = uri[uri.find('@') + 1:]
537 uri = uri[uri.find('@') + 1:]
538
538
539 # get the port
539 # get the port
540 cred_pos = uri.find(':')
540 cred_pos = uri.find(':')
541 if cred_pos == -1:
541 if cred_pos == -1:
542 host, port = uri, None
542 host, port = uri, None
543 else:
543 else:
544 host, port = uri[:cred_pos], uri[cred_pos + 1:]
544 host, port = uri[:cred_pos], uri[cred_pos + 1:]
545
545
546 return filter(None, [proto, host, port])
546 return filter(None, [proto, host, port])
547
547
548
548
549 def credentials_filter(uri):
549 def credentials_filter(uri):
550 """
550 """
551 Returns a url with removed credentials
551 Returns a url with removed credentials
552
552
553 :param uri:
553 :param uri:
554 """
554 """
555
555
556 uri = uri_filter(uri)
556 uri = uri_filter(uri)
557 # check if we have port
557 # check if we have port
558 if len(uri) > 2 and uri[2]:
558 if len(uri) > 2 and uri[2]:
559 uri[2] = ':' + uri[2]
559 uri[2] = ':' + uri[2]
560
560
561 return ''.join(uri)
561 return ''.join(uri)
562
562
563
563
564 def get_clone_url(uri_tmpl, qualifed_home_url, repo_name, repo_id, **override):
564 def get_clone_url(uri_tmpl, qualifed_home_url, repo_name, repo_id, **override):
565 parsed_url = urlobject.URLObject(qualifed_home_url)
565 parsed_url = urlobject.URLObject(qualifed_home_url)
566 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
566 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
567 args = {
567 args = {
568 'scheme': parsed_url.scheme,
568 'scheme': parsed_url.scheme,
569 'user': '',
569 'user': '',
570 # path if we use proxy-prefix
570 # path if we use proxy-prefix
571 'netloc': parsed_url.netloc+decoded_path,
571 'netloc': parsed_url.netloc+decoded_path,
572 'prefix': decoded_path,
572 'prefix': decoded_path,
573 'repo': repo_name,
573 'repo': repo_name,
574 'repoid': str(repo_id)
574 'repoid': str(repo_id)
575 }
575 }
576 args.update(override)
576 args.update(override)
577 args['user'] = urllib.quote(safe_str(args['user']))
577 args['user'] = urllib.quote(safe_str(args['user']))
578
578
579 for k, v in args.items():
579 for k, v in args.items():
580 uri_tmpl = uri_tmpl.replace('{%s}' % k, v)
580 uri_tmpl = uri_tmpl.replace('{%s}' % k, v)
581
581
582 # remove leading @ sign if it's present. Case of empty user
582 # remove leading @ sign if it's present. Case of empty user
583 url_obj = urlobject.URLObject(uri_tmpl)
583 url_obj = urlobject.URLObject(uri_tmpl)
584 url = url_obj.with_netloc(url_obj.netloc.lstrip('@'))
584 url = url_obj.with_netloc(url_obj.netloc.lstrip('@'))
585
585
586 return safe_unicode(url)
586 return safe_unicode(url)
587
587
588
588
589 def get_commit_safe(repo, commit_id=None, commit_idx=None, pre_load=None):
589 def get_commit_safe(repo, commit_id=None, commit_idx=None, pre_load=None):
590 """
590 """
591 Safe version of get_commit if this commit doesn't exists for a
591 Safe version of get_commit if this commit doesn't exists for a
592 repository it returns a Dummy one instead
592 repository it returns a Dummy one instead
593
593
594 :param repo: repository instance
594 :param repo: repository instance
595 :param commit_id: commit id as str
595 :param commit_id: commit id as str
596 :param pre_load: optional list of commit attributes to load
596 :param pre_load: optional list of commit attributes to load
597 """
597 """
598 # TODO(skreft): remove these circular imports
598 # TODO(skreft): remove these circular imports
599 from rhodecode.lib.vcs.backends.base import BaseRepository, EmptyCommit
599 from rhodecode.lib.vcs.backends.base import BaseRepository, EmptyCommit
600 from rhodecode.lib.vcs.exceptions import RepositoryError
600 from rhodecode.lib.vcs.exceptions import RepositoryError
601 if not isinstance(repo, BaseRepository):
601 if not isinstance(repo, BaseRepository):
602 raise Exception('You must pass an Repository '
602 raise Exception('You must pass an Repository '
603 'object as first argument got %s', type(repo))
603 'object as first argument got %s', type(repo))
604
604
605 try:
605 try:
606 commit = repo.get_commit(
606 commit = repo.get_commit(
607 commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load)
607 commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load)
608 except (RepositoryError, LookupError):
608 except (RepositoryError, LookupError):
609 commit = EmptyCommit()
609 commit = EmptyCommit()
610 return commit
610 return commit
611
611
612
612
613 def datetime_to_time(dt):
613 def datetime_to_time(dt):
614 if dt:
614 if dt:
615 return time.mktime(dt.timetuple())
615 return time.mktime(dt.timetuple())
616
616
617
617
618 def time_to_datetime(tm):
618 def time_to_datetime(tm):
619 if tm:
619 if tm:
620 if isinstance(tm, basestring):
620 if isinstance(tm, basestring):
621 try:
621 try:
622 tm = float(tm)
622 tm = float(tm)
623 except ValueError:
623 except ValueError:
624 return
624 return
625 return datetime.datetime.fromtimestamp(tm)
625 return datetime.datetime.fromtimestamp(tm)
626
626
627
627
628 def time_to_utcdatetime(tm):
628 def time_to_utcdatetime(tm):
629 if tm:
629 if tm:
630 if isinstance(tm, basestring):
630 if isinstance(tm, basestring):
631 try:
631 try:
632 tm = float(tm)
632 tm = float(tm)
633 except ValueError:
633 except ValueError:
634 return
634 return
635 return datetime.datetime.utcfromtimestamp(tm)
635 return datetime.datetime.utcfromtimestamp(tm)
636
636
637
637
638 MENTIONS_REGEX = re.compile(
638 MENTIONS_REGEX = re.compile(
639 # ^@ or @ without any special chars in front
639 # ^@ or @ without any special chars in front
640 r'(?:^@|[^a-zA-Z0-9\-\_\.]@)'
640 r'(?:^@|[^a-zA-Z0-9\-\_\.]@)'
641 # main body starts with letter, then can be . - _
641 # main body starts with letter, then can be . - _
642 r'([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)',
642 r'([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)',
643 re.VERBOSE | re.MULTILINE)
643 re.VERBOSE | re.MULTILINE)
644
644
645
645
646 def extract_mentioned_users(s):
646 def extract_mentioned_users(s):
647 """
647 """
648 Returns unique usernames from given string s that have @mention
648 Returns unique usernames from given string s that have @mention
649
649
650 :param s: string to get mentions
650 :param s: string to get mentions
651 """
651 """
652 usrs = set()
652 usrs = set()
653 for username in MENTIONS_REGEX.findall(s):
653 for username in MENTIONS_REGEX.findall(s):
654 usrs.add(username)
654 usrs.add(username)
655
655
656 return sorted(list(usrs), key=lambda k: k.lower())
656 return sorted(list(usrs), key=lambda k: k.lower())
657
657
658
658
659 class AttributeDict(dict):
659 class AttributeDict(dict):
660 def __getattr__(self, attr):
660 def __getattr__(self, attr):
661 return self.get(attr, None)
661 return self.get(attr, None)
662 __setattr__ = dict.__setitem__
662 __setattr__ = dict.__setitem__
663 __delattr__ = dict.__delitem__
663 __delattr__ = dict.__delitem__
664
664
665
665
666 def fix_PATH(os_=None):
666 def fix_PATH(os_=None):
667 """
667 """
668 Get current active python path, and append it to PATH variable to fix
668 Get current active python path, and append it to PATH variable to fix
669 issues of subprocess calls and different python versions
669 issues of subprocess calls and different python versions
670 """
670 """
671 if os_ is None:
671 if os_ is None:
672 import os
672 import os
673 else:
673 else:
674 os = os_
674 os = os_
675
675
676 cur_path = os.path.split(sys.executable)[0]
676 cur_path = os.path.split(sys.executable)[0]
677 if not os.environ['PATH'].startswith(cur_path):
677 if not os.environ['PATH'].startswith(cur_path):
678 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
678 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
679
679
680
680
681 def obfuscate_url_pw(engine):
681 def obfuscate_url_pw(engine):
682 _url = engine or ''
682 _url = engine or ''
683 try:
683 try:
684 _url = sqlalchemy.engine.url.make_url(engine)
684 _url = sqlalchemy.engine.url.make_url(engine)
685 if _url.password:
685 if _url.password:
686 _url.password = 'XXXXX'
686 _url.password = 'XXXXX'
687 except Exception:
687 except Exception:
688 pass
688 pass
689 return unicode(_url)
689 return unicode(_url)
690
690
691
691
692 def get_server_url(environ):
692 def get_server_url(environ):
693 req = webob.Request(environ)
693 req = webob.Request(environ)
694 return req.host_url + req.script_name
694 return req.host_url + req.script_name
695
695
696
696
697 def unique_id(hexlen=32):
697 def unique_id(hexlen=32):
698 alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnpqrstuvwxyz"
698 alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnpqrstuvwxyz"
699 return suuid(truncate_to=hexlen, alphabet=alphabet)
699 return suuid(truncate_to=hexlen, alphabet=alphabet)
700
700
701
701
702 def suuid(url=None, truncate_to=22, alphabet=None):
702 def suuid(url=None, truncate_to=22, alphabet=None):
703 """
703 """
704 Generate and return a short URL safe UUID.
704 Generate and return a short URL safe UUID.
705
705
706 If the url parameter is provided, set the namespace to the provided
706 If the url parameter is provided, set the namespace to the provided
707 URL and generate a UUID.
707 URL and generate a UUID.
708
708
709 :param url to get the uuid for
709 :param url to get the uuid for
710 :truncate_to: truncate the basic 22 UUID to shorter version
710 :truncate_to: truncate the basic 22 UUID to shorter version
711
711
712 The IDs won't be universally unique any longer, but the probability of
712 The IDs won't be universally unique any longer, but the probability of
713 a collision will still be very low.
713 a collision will still be very low.
714 """
714 """
715 # Define our alphabet.
715 # Define our alphabet.
716 _ALPHABET = alphabet or "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
716 _ALPHABET = alphabet or "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
717
717
718 # If no URL is given, generate a random UUID.
718 # If no URL is given, generate a random UUID.
719 if url is None:
719 if url is None:
720 unique_id = uuid.uuid4().int
720 unique_id = uuid.uuid4().int
721 else:
721 else:
722 unique_id = uuid.uuid3(uuid.NAMESPACE_URL, url).int
722 unique_id = uuid.uuid3(uuid.NAMESPACE_URL, url).int
723
723
724 alphabet_length = len(_ALPHABET)
724 alphabet_length = len(_ALPHABET)
725 output = []
725 output = []
726 while unique_id > 0:
726 while unique_id > 0:
727 digit = unique_id % alphabet_length
727 digit = unique_id % alphabet_length
728 output.append(_ALPHABET[digit])
728 output.append(_ALPHABET[digit])
729 unique_id = int(unique_id / alphabet_length)
729 unique_id = int(unique_id / alphabet_length)
730 return "".join(output)[:truncate_to]
730 return "".join(output)[:truncate_to]
731
731
732
732
733 def get_current_rhodecode_user():
733 def get_current_rhodecode_user():
734 """
734 """
735 Gets rhodecode user from threadlocal tmpl_context variable if it's
735 Gets rhodecode user from threadlocal tmpl_context variable if it's
736 defined, else returns None.
736 defined, else returns None.
737 """
737 """
738 from pylons import tmpl_context as c
738 from pylons import tmpl_context as c
739 if hasattr(c, 'rhodecode_user'):
739 if hasattr(c, 'rhodecode_user'):
740 return c.rhodecode_user
740 return c.rhodecode_user
741
741
742 return None
742 return None
743
743
744
744
745 def action_logger_generic(action, namespace=''):
745 def action_logger_generic(action, namespace=''):
746 """
746 """
747 A generic logger for actions useful to the system overview, tries to find
747 A generic logger for actions useful to the system overview, tries to find
748 an acting user for the context of the call otherwise reports unknown user
748 an acting user for the context of the call otherwise reports unknown user
749
749
750 :param action: logging message eg 'comment 5 deleted'
750 :param action: logging message eg 'comment 5 deleted'
751 :param type: string
751 :param type: string
752
752
753 :param namespace: namespace of the logging message eg. 'repo.comments'
753 :param namespace: namespace of the logging message eg. 'repo.comments'
754 :param type: string
754 :param type: string
755
755
756 """
756 """
757
757
758 logger_name = 'rhodecode.actions'
758 logger_name = 'rhodecode.actions'
759
759
760 if namespace:
760 if namespace:
761 logger_name += '.' + namespace
761 logger_name += '.' + namespace
762
762
763 log = logging.getLogger(logger_name)
763 log = logging.getLogger(logger_name)
764
764
765 # get a user if we can
765 # get a user if we can
766 user = get_current_rhodecode_user()
766 user = get_current_rhodecode_user()
767
767
768 logfunc = log.info
768 logfunc = log.info
769
769
770 if not user:
770 if not user:
771 user = '<unknown user>'
771 user = '<unknown user>'
772 logfunc = log.warning
772 logfunc = log.warning
773
773
774 logfunc('Logging action by {}: {}'.format(user, action))
774 logfunc('Logging action by {}: {}'.format(user, action))
775
775
776
776
777 def escape_split(text, sep=',', maxsplit=-1):
777 def escape_split(text, sep=',', maxsplit=-1):
778 r"""
778 r"""
779 Allows for escaping of the separator: e.g. arg='foo\, bar'
779 Allows for escaping of the separator: e.g. arg='foo\, bar'
780
780
781 It should be noted that the way bash et. al. do command line parsing, those
781 It should be noted that the way bash et. al. do command line parsing, those
782 single quotes are required.
782 single quotes are required.
783 """
783 """
784 escaped_sep = r'\%s' % sep
784 escaped_sep = r'\%s' % sep
785
785
786 if escaped_sep not in text:
786 if escaped_sep not in text:
787 return text.split(sep, maxsplit)
787 return text.split(sep, maxsplit)
788
788
789 before, _mid, after = text.partition(escaped_sep)
789 before, _mid, after = text.partition(escaped_sep)
790 startlist = before.split(sep, maxsplit) # a regular split is fine here
790 startlist = before.split(sep, maxsplit) # a regular split is fine here
791 unfinished = startlist[-1]
791 unfinished = startlist[-1]
792 startlist = startlist[:-1]
792 startlist = startlist[:-1]
793
793
794 # recurse because there may be more escaped separators
794 # recurse because there may be more escaped separators
795 endlist = escape_split(after, sep, maxsplit)
795 endlist = escape_split(after, sep, maxsplit)
796
796
797 # finish building the escaped value. we use endlist[0] becaue the first
797 # finish building the escaped value. we use endlist[0] becaue the first
798 # part of the string sent in recursion is the rest of the escaped value.
798 # part of the string sent in recursion is the rest of the escaped value.
799 unfinished += sep + endlist[0]
799 unfinished += sep + endlist[0]
800
800
801 return startlist + [unfinished] + endlist[1:] # put together all the parts
801 return startlist + [unfinished] + endlist[1:] # put together all the parts
802
802
803
803
804 class OptionalAttr(object):
804 class OptionalAttr(object):
805 """
805 """
806 Special Optional Option that defines other attribute. Example::
806 Special Optional Option that defines other attribute. Example::
807
807
808 def test(apiuser, userid=Optional(OAttr('apiuser')):
808 def test(apiuser, userid=Optional(OAttr('apiuser')):
809 user = Optional.extract(userid)
809 user = Optional.extract(userid)
810 # calls
810 # calls
811
811
812 """
812 """
813
813
814 def __init__(self, attr_name):
814 def __init__(self, attr_name):
815 self.attr_name = attr_name
815 self.attr_name = attr_name
816
816
817 def __repr__(self):
817 def __repr__(self):
818 return '<OptionalAttr:%s>' % self.attr_name
818 return '<OptionalAttr:%s>' % self.attr_name
819
819
820 def __call__(self):
820 def __call__(self):
821 return self
821 return self
822
822
823
823
824 # alias
824 # alias
825 OAttr = OptionalAttr
825 OAttr = OptionalAttr
826
826
827
827
828 class Optional(object):
828 class Optional(object):
829 """
829 """
830 Defines an optional parameter::
830 Defines an optional parameter::
831
831
832 param = param.getval() if isinstance(param, Optional) else param
832 param = param.getval() if isinstance(param, Optional) else param
833 param = param() if isinstance(param, Optional) else param
833 param = param() if isinstance(param, Optional) else param
834
834
835 is equivalent of::
835 is equivalent of::
836
836
837 param = Optional.extract(param)
837 param = Optional.extract(param)
838
838
839 """
839 """
840
840
841 def __init__(self, type_):
841 def __init__(self, type_):
842 self.type_ = type_
842 self.type_ = type_
843
843
844 def __repr__(self):
844 def __repr__(self):
845 return '<Optional:%s>' % self.type_.__repr__()
845 return '<Optional:%s>' % self.type_.__repr__()
846
846
847 def __call__(self):
847 def __call__(self):
848 return self.getval()
848 return self.getval()
849
849
850 def getval(self):
850 def getval(self):
851 """
851 """
852 returns value from this Optional instance
852 returns value from this Optional instance
853 """
853 """
854 if isinstance(self.type_, OAttr):
854 if isinstance(self.type_, OAttr):
855 # use params name
855 # use params name
856 return self.type_.attr_name
856 return self.type_.attr_name
857 return self.type_
857 return self.type_
858
858
859 @classmethod
859 @classmethod
860 def extract(cls, val):
860 def extract(cls, val):
861 """
861 """
862 Extracts value from Optional() instance
862 Extracts value from Optional() instance
863
863
864 :param val:
864 :param val:
865 :return: original value if it's not Optional instance else
865 :return: original value if it's not Optional instance else
866 value of instance
866 value of instance
867 """
867 """
868 if isinstance(val, cls):
868 if isinstance(val, cls):
869 return val.getval()
869 return val.getval()
870 return val
870 return val
871
871
872
872
873 def get_routes_generator_for_server_url(server_url):
873 def get_routes_generator_for_server_url(server_url):
874 parsed_url = urlobject.URLObject(server_url)
874 parsed_url = urlobject.URLObject(server_url)
875 netloc = safe_str(parsed_url.netloc)
875 netloc = safe_str(parsed_url.netloc)
876 script_name = safe_str(parsed_url.path)
876 script_name = safe_str(parsed_url.path)
877
877
878 if ':' in netloc:
878 if ':' in netloc:
879 server_name, server_port = netloc.split(':')
879 server_name, server_port = netloc.split(':')
880 else:
880 else:
881 server_name = netloc
881 server_name = netloc
882 server_port = (parsed_url.scheme == 'https' and '443' or '80')
882 server_port = (parsed_url.scheme == 'https' and '443' or '80')
883
883
884 environ = {
884 environ = {
885 'REQUEST_METHOD': 'GET',
885 'REQUEST_METHOD': 'GET',
886 'PATH_INFO': '/',
886 'PATH_INFO': '/',
887 'SERVER_NAME': server_name,
887 'SERVER_NAME': server_name,
888 'SERVER_PORT': server_port,
888 'SERVER_PORT': server_port,
889 'SCRIPT_NAME': script_name,
889 'SCRIPT_NAME': script_name,
890 }
890 }
891 if parsed_url.scheme == 'https':
891 if parsed_url.scheme == 'https':
892 environ['HTTPS'] = 'on'
892 environ['HTTPS'] = 'on'
893 environ['wsgi.url_scheme'] = 'https'
893 environ['wsgi.url_scheme'] = 'https'
894
894
895 return routes.util.URLGenerator(rhodecode.CONFIG['routes.map'], environ)
895 return routes.util.URLGenerator(rhodecode.CONFIG['routes.map'], environ)
896
897
898 def glob2re(pat):
899 """
900 Translate a shell PATTERN to a regular expression.
901
902 There is no way to quote meta-characters.
903 """
904
905 i, n = 0, len(pat)
906 res = ''
907 while i < n:
908 c = pat[i]
909 i = i+1
910 if c == '*':
911 #res = res + '.*'
912 res = res + '[^/]*'
913 elif c == '?':
914 #res = res + '.'
915 res = res + '[^/]'
916 elif c == '[':
917 j = i
918 if j < n and pat[j] == '!':
919 j = j+1
920 if j < n and pat[j] == ']':
921 j = j+1
922 while j < n and pat[j] != ']':
923 j = j+1
924 if j >= n:
925 res = res + '\\['
926 else:
927 stuff = pat[i:j].replace('\\','\\\\')
928 i = j+1
929 if stuff[0] == '!':
930 stuff = '^' + stuff[1:]
931 elif stuff[0] == '^':
932 stuff = '\\' + stuff
933 res = '%s[%s]' % (res, stuff)
934 else:
935 res = res + re.escape(c)
936 return res + '\Z(?ms)'
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,15 +1,26 b''
1 import os
1 import os
2 import re
2
3
3 import ipaddress
4 import ipaddress
4 import colander
5 import colander
5
6
6 from rhodecode.translation import _
7 from rhodecode.translation import _
8 from rhodecode.lib.utils2 import glob2re
7
9
8
10
9 def ip_addr_validator(node, value):
11 def ip_addr_validator(node, value):
10 try:
12 try:
11 # this raises an ValueError if address is not IpV4 or IpV6
13 # this raises an ValueError if address is not IpV4 or IpV6
12 ipaddress.ip_network(value, strict=False)
14 ipaddress.ip_network(value, strict=False)
13 except ValueError:
15 except ValueError:
14 msg = _(u'Please enter a valid IPv4 or IpV6 address')
16 msg = _(u'Please enter a valid IPv4 or IpV6 address')
15 raise colander.Invalid(node, msg)
17 raise colander.Invalid(node, msg)
18
19
20 def glob_validator(node, value):
21 try:
22 re.compile('^' + glob2re(value) + '$')
23 except Exception:
24 raise
25 msg = _(u'Invalid glob pattern')
26 raise colander.Invalid(node, msg)
@@ -1,117 +1,139 b''
1 .deform {
1 .deform {
2
2
3 * {
3 * {
4 box-sizing: border-box;
4 box-sizing: border-box;
5 }
5 }
6
6
7 .required:after {
7 .required:after {
8 color: #e32;
8 color: #e32;
9 content: '*';
9 content: '*';
10 display:inline;
10 display:inline;
11 }
11 }
12
12
13 .control-label {
13 .control-label {
14 width: 200px;
14 width: 200px;
15 padding: 10px;
15 padding: 10px;
16 float: left;
16 float: left;
17 }
17 }
18 .control-inputs {
18 .control-inputs {
19 width: 400px;
19 width: 400px;
20 float: left;
20 float: left;
21 }
21 }
22 .form-group .radio, .form-group .checkbox {
22 .form-group .radio, .form-group .checkbox {
23 position: relative;
23 position: relative;
24 display: block;
24 display: block;
25 /* margin-bottom: 10px; */
25 /* margin-bottom: 10px; */
26 }
26 }
27
27
28 .form-group {
28 .form-group {
29 clear: left;
29 clear: left;
30 margin-bottom: 20px;
30 margin-bottom: 20px;
31
31
32 &:after { /* clear fix */
32 &:after { /* clear fix */
33 content: " ";
33 content: " ";
34 display: block;
34 display: block;
35 clear: left;
35 clear: left;
36 }
36 }
37 }
37 }
38
38
39 .form-control {
39 .form-control {
40 width: 100%;
40 width: 100%;
41 padding: 0.9em;
41 padding: 0.9em;
42 border: 1px solid #979797;
42 border: 1px solid #979797;
43 border-radius: 2px;
43 border-radius: 2px;
44 }
44 }
45 .form-control.select2-container {
45 .form-control.select2-container {
46 padding: 0; /* padding already applied in .drop-menu a */
46 padding: 0; /* padding already applied in .drop-menu a */
47 }
47 }
48
48
49 .form-control.readonly {
49 .form-control.readonly {
50 background: #eeeeee;
50 background: #eeeeee;
51 cursor: not-allowed;
51 cursor: not-allowed;
52 }
52 }
53
53
54 .error-block {
54 .error-block {
55 color: red;
55 color: red;
56 margin: 0;
56 margin: 0;
57 }
57 }
58
58
59 .help-block {
59 .help-block {
60 margin: 0;
60 margin: 0;
61 }
61 }
62
62
63 .deform-seq-container .control-inputs {
63 .deform-seq-container .control-inputs {
64 width: 100%;
64 width: 100%;
65 }
65 }
66
66
67 .deform-seq-container .deform-seq-item-handle {
67 .deform-seq-container .deform-seq-item-handle {
68 width: 8.3%;
68 width: 8.3%;
69 float: left;
69 float: left;
70 }
70 }
71
71
72 .deform-seq-container .deform-seq-item-group {
72 .deform-seq-container .deform-seq-item-group {
73 width: 91.6%;
73 width: 91.6%;
74 float: left;
74 float: left;
75 }
75 }
76
76
77 .form-control {
77 .form-control {
78 input {
78 input {
79 height: 40px;
79 height: 40px;
80 }
80 }
81 input[type=checkbox], input[type=radio] {
81 input[type=checkbox], input[type=radio] {
82 height: auto;
82 height: auto;
83 }
83 }
84 select {
84 select {
85 height: 40px;
85 height: 40px;
86 }
86 }
87 }
87 }
88
88
89 .form-control.select2-container {
89 .form-control.select2-container {
90 height: 40px;
90 height: 40px;
91 }
91 }
92
92
93 .deform-two-field-sequence .deform-seq-container .deform-seq-item label {
93 .deform-full-field-sequence.control-inputs {
94 display: none;
94 width: 100%;
95 }
96 .deform-two-field-sequence .deform-seq-container .deform-seq-item:first-child label {
97 display: block;
98 }
99 .deform-two-field-sequence .deform-seq-container .deform-seq-item .panel-heading {
100 display: none;
101 }
102 .deform-two-field-sequence .deform-seq-container .deform-seq-item.form-group {
103 margin: 0;
104 }
105 .deform-two-field-sequence .deform-seq-container .deform-seq-item .deform-seq-item-group .form-group {
106 width: 45%; padding: 0 2px; float: left; clear: none;
107 }
108 .deform-two-field-sequence .deform-seq-container .deform-seq-item .deform-seq-item-group > .panel {
109 padding: 0;
110 margin: 5px 0;
111 border: none;
112 }
113 .deform-two-field-sequence .deform-seq-container .deform-seq-item .deform-seq-item-group > .panel > .panel-body {
114 padding: 0;
115 }
95 }
116
96
97 .deform-table-sequence {
98 .deform-seq-container {
99 .deform-seq-item {
100 margin: 0;
101 label {
102 display: none;
103 }
104 .panel-heading {
105 display: none;
106 }
107 .deform-seq-item-group > .panel {
108 padding: 0;
109 margin: 5px 0;
110 border: none;
111 &> .panel-body {
112 padding: 0;
113 }
114 }
115 &:first-child label {
116 display: block;
117 }
118 }
119 }
120 }
121 .deform-table-2-sequence {
122 .deform-seq-container {
123 .deform-seq-item {
124 .form-group {
125 width: 45% !important; padding: 0 2px; float: left; clear: none;
126 }
127 }
128 }
129 }
130 .deform-table-3-sequence {
131 .deform-seq-container {
132 .deform-seq-item {
133 .form-group {
134 width: 30% !important; padding: 0 2px; float: left; clear: none;
135 }
136 }
137 }
138 }
117 }
139 }
@@ -1,51 +1,53 b''
1
1
2 /******************************************************************************
2 /******************************************************************************
3 * *
3 * *
4 * DO NOT CHANGE THIS FILE MANUALLY *
4 * DO NOT CHANGE THIS FILE MANUALLY *
5 * *
5 * *
6 * *
6 * *
7 * This file is automatically generated when the app starts up with *
7 * This file is automatically generated when the app starts up with *
8 * generate_js_files = true *
8 * generate_js_files = true *
9 * *
9 * *
10 * To add a route here pass jsroute=True to the route definition in the app *
10 * To add a route here pass jsroute=True to the route definition in the app *
11 * *
11 * *
12 ******************************************************************************/
12 ******************************************************************************/
13 function registerRCRoutes() {
13 function registerRCRoutes() {
14 // routes registration
14 // routes registration
15 pyroutes.register('home', '/', []);
15 pyroutes.register('home', '/', []);
16 pyroutes.register('user_autocomplete_data', '/_users', []);
16 pyroutes.register('user_autocomplete_data', '/_users', []);
17 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
17 pyroutes.register('new_repo', '/_admin/create_repository', []);
18 pyroutes.register('new_repo', '/_admin/create_repository', []);
18 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
19 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
19 pyroutes.register('gists', '/_admin/gists', []);
20 pyroutes.register('gists', '/_admin/gists', []);
20 pyroutes.register('new_gist', '/_admin/gists/new', []);
21 pyroutes.register('new_gist', '/_admin/gists/new', []);
21 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
22 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
22 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
23 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
23 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
24 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
24 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
25 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
26 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/default-reviewers', ['repo_name']);
25 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
27 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
26 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
28 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
27 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
29 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
28 pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']);
30 pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']);
29 pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']);
31 pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']);
30 pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
32 pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
31 pyroutes.register('changeset_info', '/%(repo_name)s/changeset_info/%(revision)s', ['repo_name', 'revision']);
33 pyroutes.register('changeset_info', '/%(repo_name)s/changeset_info/%(revision)s', ['repo_name', 'revision']);
32 pyroutes.register('compare_url', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
34 pyroutes.register('compare_url', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
33 pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']);
35 pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']);
34 pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']);
36 pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']);
35 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
37 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
36 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
38 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
37 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
39 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
38 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
40 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
39 pyroutes.register('pullrequest_comment', '/%(repo_name)s/pull-request-comment/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
41 pyroutes.register('pullrequest_comment', '/%(repo_name)s/pull-request-comment/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
40 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request-comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
42 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request-comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
41 pyroutes.register('changelog_home', '/%(repo_name)s/changelog', ['repo_name']);
43 pyroutes.register('changelog_home', '/%(repo_name)s/changelog', ['repo_name']);
42 pyroutes.register('changelog_file_home', '/%(repo_name)s/changelog/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
44 pyroutes.register('changelog_file_home', '/%(repo_name)s/changelog/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
43 pyroutes.register('files_home', '/%(repo_name)s/files/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
45 pyroutes.register('files_home', '/%(repo_name)s/files/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
44 pyroutes.register('files_history_home', '/%(repo_name)s/history/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
46 pyroutes.register('files_history_home', '/%(repo_name)s/history/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
45 pyroutes.register('files_authors_home', '/%(repo_name)s/authors/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
47 pyroutes.register('files_authors_home', '/%(repo_name)s/authors/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
46 pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
48 pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
47 pyroutes.register('files_nodelist_home', '/%(repo_name)s/nodelist/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
49 pyroutes.register('files_nodelist_home', '/%(repo_name)s/nodelist/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
48 pyroutes.register('files_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
50 pyroutes.register('files_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
49 pyroutes.register('summary_home_slash', '/%(repo_name)s/', ['repo_name']);
51 pyroutes.register('summary_home_slash', '/%(repo_name)s/', ['repo_name']);
50 pyroutes.register('summary_home', '/%(repo_name)s', ['repo_name']);
52 pyroutes.register('summary_home', '/%(repo_name)s', ['repo_name']);
51 }
53 }
@@ -1,205 +1,214 b''
1 // # Copyright (C) 2010-2016 RhodeCode GmbH
1 // # Copyright (C) 2010-2016 RhodeCode GmbH
2 // #
2 // #
3 // # This program is free software: you can redistribute it and/or modify
3 // # This program is free software: you can redistribute it and/or modify
4 // # it under the terms of the GNU Affero General Public License, version 3
4 // # it under the terms of the GNU Affero General Public License, version 3
5 // # (only), as published by the Free Software Foundation.
5 // # (only), as published by the Free Software Foundation.
6 // #
6 // #
7 // # This program is distributed in the hope that it will be useful,
7 // # This program is distributed in the hope that it will be useful,
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // # GNU General Public License for more details.
10 // # GNU General Public License for more details.
11 // #
11 // #
12 // # You should have received a copy of the GNU Affero General Public License
12 // # You should have received a copy of the GNU Affero General Public License
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 // #
14 // #
15 // # This program is dual-licensed. If you wish to learn more about the
15 // # This program is dual-licensed. If you wish to learn more about the
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 /**
19 /**
20 * Pull request reviewers
20 * Pull request reviewers
21 */
21 */
22 var removeReviewMember = function(reviewer_id, mark_delete){
22 var removeReviewMember = function(reviewer_id, mark_delete){
23 var reviewer = $('#reviewer_{0}'.format(reviewer_id));
23 var reviewer = $('#reviewer_{0}'.format(reviewer_id));
24
24
25 if(typeof(mark_delete) === undefined){
25 if(typeof(mark_delete) === undefined){
26 mark_delete = false;
26 mark_delete = false;
27 }
27 }
28
28
29 if(mark_delete === true){
29 if(mark_delete === true){
30 if (reviewer){
30 if (reviewer){
31 // mark as to-remove
31 // mark as to-remove
32 var obj = $('#reviewer_{0}_name'.format(reviewer_id));
32 var obj = $('#reviewer_{0}_name'.format(reviewer_id));
33 obj.addClass('to-delete');
33 obj.addClass('to-delete');
34 // now delete the input
34 // now delete the input
35 $('#reviewer_{0}_input'.format(reviewer_id)).remove();
35 $('#reviewer_{0}_input'.format(reviewer_id)).remove();
36 }
36 }
37 }
37 }
38 else{
38 else{
39 $('#reviewer_{0}'.format(reviewer_id)).remove();
39 $('#reviewer_{0}'.format(reviewer_id)).remove();
40 }
40 }
41 };
41 };
42
42
43 var addReviewMember = function(id,fname,lname,nname,gravatar_link){
43 var addReviewMember = function(id, fname, lname, nname, gravatar_link, reasons) {
44 var members = $('#review_members').get(0);
44 var members = $('#review_members').get(0);
45 var reasons_html = '';
46 if (reasons) {
47 for (var i = 0; i < reasons.length; i++) {
48 reasons_html += '<div class="reviewer_reason">- {0}</div>'.format(
49 reasons[i]
50 );
51 }
52 }
45 var tmpl = '<li id="reviewer_{2}">'+
53 var tmpl = '<li id="reviewer_{2}">'+
46 '<div class="reviewer_status">'+
54 '<div class="reviewer_status">'+
47 '<div class="flag_status not_reviewed pull-left reviewer_member_status"></div>'+
55 '<div class="flag_status not_reviewed pull-left reviewer_member_status"></div>'+
48 '</div>'+
56 '</div>'+
49 '<img alt="gravatar" class="gravatar" src="{0}"/>'+
57 '<img alt="gravatar" class="gravatar" src="{0}"/>'+
50 '<span class="reviewer_name user">{1}</span>'+
58 '<span class="reviewer_name user">{1}</span>'+
59 reasons_html +
51 '<input type="hidden" value="{2}" name="review_members" />'+
60 '<input type="hidden" value="{2}" name="review_members" />'+
52 '<div class="reviewer_member_remove action_button" onclick="removeReviewMember({2})">' +
61 '<div class="reviewer_member_remove action_button" onclick="removeReviewMember({2})">' +
53 '<i class="icon-remove-sign"></i>'+
62 '<i class="icon-remove-sign"></i>'+
54 '</div>'+
63 '</div>'+
55 '</div>'+
64 '</div>'+
56 '</li>' ;
65 '</li>' ;
57 var displayname = "{0} ({1} {2})".format(
66 var displayname = "{0} ({1} {2})".format(
58 nname, escapeHtml(fname), escapeHtml(lname));
67 nname, escapeHtml(fname), escapeHtml(lname));
59 var element = tmpl.format(gravatar_link,displayname,id);
68 var element = tmpl.format(gravatar_link,displayname,id);
60 // check if we don't have this ID already in
69 // check if we don't have this ID already in
61 var ids = [];
70 var ids = [];
62 var _els = $('#review_members li').toArray();
71 var _els = $('#review_members li').toArray();
63 for (el in _els){
72 for (el in _els){
64 ids.push(_els[el].id)
73 ids.push(_els[el].id)
65 }
74 }
66 if(ids.indexOf('reviewer_'+id) == -1){
75 if(ids.indexOf('reviewer_'+id) == -1){
67 // only add if it's not there
76 // only add if it's not there
68 members.innerHTML += element;
77 members.innerHTML += element;
69 }
78 }
70
79
71 };
80 };
72
81
73 var _updatePullRequest = function(repo_name, pull_request_id, postData) {
82 var _updatePullRequest = function(repo_name, pull_request_id, postData) {
74 var url = pyroutes.url(
83 var url = pyroutes.url(
75 'pullrequest_update',
84 'pullrequest_update',
76 {"repo_name": repo_name, "pull_request_id": pull_request_id});
85 {"repo_name": repo_name, "pull_request_id": pull_request_id});
77 postData.csrf_token = CSRF_TOKEN;
86 postData.csrf_token = CSRF_TOKEN;
78 var success = function(o) {
87 var success = function(o) {
79 window.location.reload();
88 window.location.reload();
80 };
89 };
81 ajaxPOST(url, postData, success);
90 ajaxPOST(url, postData, success);
82 };
91 };
83
92
84 var updateReviewers = function(reviewers_ids, repo_name, pull_request_id){
93 var updateReviewers = function(reviewers_ids, repo_name, pull_request_id){
85 if (reviewers_ids === undefined){
94 if (reviewers_ids === undefined){
86 var reviewers_ids = [];
95 var reviewers_ids = [];
87 var ids = $('#review_members input').toArray();
96 var ids = $('#review_members input').toArray();
88 for(var i=0; i<ids.length;i++){
97 for(var i=0; i<ids.length;i++){
89 var id = ids[i].value
98 var id = ids[i].value
90 reviewers_ids.push(id);
99 reviewers_ids.push(id);
91 }
100 }
92 }
101 }
93 var postData = {
102 var postData = {
94 '_method':'put',
103 '_method':'put',
95 'reviewers_ids': reviewers_ids};
104 'reviewers_ids': reviewers_ids};
96 _updatePullRequest(repo_name, pull_request_id, postData);
105 _updatePullRequest(repo_name, pull_request_id, postData);
97 };
106 };
98
107
99 /**
108 /**
100 * PULL REQUEST reject & close
109 * PULL REQUEST reject & close
101 */
110 */
102 var closePullRequest = function(repo_name, pull_request_id) {
111 var closePullRequest = function(repo_name, pull_request_id) {
103 var postData = {
112 var postData = {
104 '_method': 'put',
113 '_method': 'put',
105 'close_pull_request': true};
114 'close_pull_request': true};
106 _updatePullRequest(repo_name, pull_request_id, postData);
115 _updatePullRequest(repo_name, pull_request_id, postData);
107 };
116 };
108
117
109 /**
118 /**
110 * PULL REQUEST update commits
119 * PULL REQUEST update commits
111 */
120 */
112 var updateCommits = function(repo_name, pull_request_id) {
121 var updateCommits = function(repo_name, pull_request_id) {
113 var postData = {
122 var postData = {
114 '_method': 'put',
123 '_method': 'put',
115 'update_commits': true};
124 'update_commits': true};
116 _updatePullRequest(repo_name, pull_request_id, postData);
125 _updatePullRequest(repo_name, pull_request_id, postData);
117 };
126 };
118
127
119
128
120 /**
129 /**
121 * PULL REQUEST edit info
130 * PULL REQUEST edit info
122 */
131 */
123 var editPullRequest = function(repo_name, pull_request_id, title, description) {
132 var editPullRequest = function(repo_name, pull_request_id, title, description) {
124 var url = pyroutes.url(
133 var url = pyroutes.url(
125 'pullrequest_update',
134 'pullrequest_update',
126 {"repo_name": repo_name, "pull_request_id": pull_request_id});
135 {"repo_name": repo_name, "pull_request_id": pull_request_id});
127
136
128 var postData = {
137 var postData = {
129 '_method': 'put',
138 '_method': 'put',
130 'title': title,
139 'title': title,
131 'description': description,
140 'description': description,
132 'edit_pull_request': true,
141 'edit_pull_request': true,
133 'csrf_token': CSRF_TOKEN
142 'csrf_token': CSRF_TOKEN
134 };
143 };
135 var success = function(o) {
144 var success = function(o) {
136 window.location.reload();
145 window.location.reload();
137 };
146 };
138 ajaxPOST(url, postData, success);
147 ajaxPOST(url, postData, success);
139 };
148 };
140
149
141 var initPullRequestsCodeMirror = function (textAreaId) {
150 var initPullRequestsCodeMirror = function (textAreaId) {
142 var ta = $(textAreaId).get(0);
151 var ta = $(textAreaId).get(0);
143 var initialHeight = '100px';
152 var initialHeight = '100px';
144
153
145 // default options
154 // default options
146 var codeMirrorOptions = {
155 var codeMirrorOptions = {
147 mode: "text",
156 mode: "text",
148 lineNumbers: false,
157 lineNumbers: false,
149 indentUnit: 4,
158 indentUnit: 4,
150 theme: 'rc-input'
159 theme: 'rc-input'
151 };
160 };
152
161
153 var codeMirrorInstance = CodeMirror.fromTextArea(ta, codeMirrorOptions);
162 var codeMirrorInstance = CodeMirror.fromTextArea(ta, codeMirrorOptions);
154 // marker for manually set description
163 // marker for manually set description
155 codeMirrorInstance._userDefinedDesc = false;
164 codeMirrorInstance._userDefinedDesc = false;
156 codeMirrorInstance.setSize(null, initialHeight);
165 codeMirrorInstance.setSize(null, initialHeight);
157 codeMirrorInstance.on("change", function(instance, changeObj) {
166 codeMirrorInstance.on("change", function(instance, changeObj) {
158 var height = initialHeight;
167 var height = initialHeight;
159 var lines = instance.lineCount();
168 var lines = instance.lineCount();
160 if (lines > 6 && lines < 20) {
169 if (lines > 6 && lines < 20) {
161 height = "auto"
170 height = "auto"
162 }
171 }
163 else if (lines >= 20) {
172 else if (lines >= 20) {
164 height = 20 * 15;
173 height = 20 * 15;
165 }
174 }
166 instance.setSize(null, height);
175 instance.setSize(null, height);
167
176
168 // detect if the change was trigger by auto desc, or user input
177 // detect if the change was trigger by auto desc, or user input
169 changeOrigin = changeObj.origin;
178 changeOrigin = changeObj.origin;
170
179
171 if (changeOrigin === "setValue") {
180 if (changeOrigin === "setValue") {
172 cmLog.debug('Change triggered by setValue');
181 cmLog.debug('Change triggered by setValue');
173 }
182 }
174 else {
183 else {
175 cmLog.debug('user triggered change !');
184 cmLog.debug('user triggered change !');
176 // set special marker to indicate user has created an input.
185 // set special marker to indicate user has created an input.
177 instance._userDefinedDesc = true;
186 instance._userDefinedDesc = true;
178 }
187 }
179
188
180 });
189 });
181
190
182 return codeMirrorInstance
191 return codeMirrorInstance
183 };
192 };
184
193
185 /**
194 /**
186 * Reviewer autocomplete
195 * Reviewer autocomplete
187 */
196 */
188 var ReviewerAutoComplete = function(input_id) {
197 var ReviewerAutoComplete = function(input_id) {
189 $('#'+input_id).autocomplete({
198 $('#'+input_id).autocomplete({
190 serviceUrl: pyroutes.url('user_autocomplete_data'),
199 serviceUrl: pyroutes.url('user_autocomplete_data'),
191 minChars:2,
200 minChars:2,
192 maxHeight:400,
201 maxHeight:400,
193 deferRequestBy: 300, //miliseconds
202 deferRequestBy: 300, //miliseconds
194 showNoSuggestionNotice: true,
203 showNoSuggestionNotice: true,
195 tabDisabled: true,
204 tabDisabled: true,
196 autoSelectFirst: true,
205 autoSelectFirst: true,
197 formatResult: autocompleteFormatResult,
206 formatResult: autocompleteFormatResult,
198 lookupFilter: autocompleteFilterResult,
207 lookupFilter: autocompleteFilterResult,
199 onSelect: function(suggestion, data){
208 onSelect: function(suggestion, data){
200 addReviewMember(data.id, data.first_name, data.last_name,
209 addReviewMember(data.id, data.first_name, data.last_name,
201 data.username, data.icon_link);
210 data.username, data.icon_link);
202 $('#'+input_id).val('');
211 $('#'+input_id).val('');
203 }
212 }
204 });
213 });
205 };
214 };
@@ -1,84 +1,100 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 ##
2 ##
3 ## See also repo_settings.html
3 ## See also repo_settings.html
4 ##
4 ##
5 <%inherit file="/base/base.html"/>
5 <%inherit file="/base/base.html"/>
6
6
7 <%def name="title()">
7 <%def name="title()">
8 ${_('%s repository settings') % c.repo_info.repo_name}
8 ${_('%s repository settings') % c.repo_info.repo_name}
9 %if c.rhodecode_name:
9 %if c.rhodecode_name:
10 &middot; ${h.branding(c.rhodecode_name)}
10 &middot; ${h.branding(c.rhodecode_name)}
11 %endif
11 %endif
12 </%def>
12 </%def>
13
13
14 <%def name="breadcrumbs_links()">
14 <%def name="breadcrumbs_links()">
15 ${_('Settings')}
15 ${_('Settings')}
16 </%def>
16 </%def>
17
17
18 <%def name="menu_bar_nav()">
18 <%def name="menu_bar_nav()">
19 ${self.menu_items(active='repositories')}
19 ${self.menu_items(active='repositories')}
20 </%def>
20 </%def>
21
21
22 <%def name="menu_bar_subnav()">
22 <%def name="menu_bar_subnav()">
23 ${self.repo_menu(active='options')}
23 ${self.repo_menu(active='options')}
24 </%def>
24 </%def>
25
25
26 <%def name="main_content()">
26 <%def name="main_content()">
27 <%include file="/admin/repos/repo_edit_${c.active}.html"/>
27 <%include file="/admin/repos/repo_edit_${c.active}.html"/>
28 </%def>
28 </%def>
29
29
30
30
31 <%def name="main()">
31 <%def name="main()">
32 <div class="box">
32 <div class="box">
33 <div class="title">
33 <div class="title">
34 ${self.repo_page_title(c.rhodecode_db_repo)}
34 ${self.repo_page_title(c.rhodecode_db_repo)}
35 ${self.breadcrumbs()}
35 ${self.breadcrumbs()}
36 </div>
36 </div>
37
37
38 <div class="sidebar-col-wrapper scw-small">
38 <div class="sidebar-col-wrapper scw-small">
39 ##main
39 ##main
40 <div class="sidebar">
40 <div class="sidebar">
41 <ul class="nav nav-pills nav-stacked">
41 <ul class="nav nav-pills nav-stacked">
42 <li class="${'active' if c.active=='settings' else ''}">
42 <li class="${'active' if c.active=='settings' else ''}">
43 <a href="${h.url('edit_repo', repo_name=c.repo_name)}">${_('Settings')}</a>
43 <a href="${h.url('edit_repo', repo_name=c.repo_name)}">${_('Settings')}</a>
44 </li>
44 </li>
45 <li class="${'active' if c.active=='permissions' else ''}">
45 <li class="${'active' if c.active=='permissions' else ''}">
46 <a href="${h.url('edit_repo_perms', repo_name=c.repo_name)}">${_('Permissions')}</a>
46 <a href="${h.url('edit_repo_perms', repo_name=c.repo_name)}">${_('Permissions')}</a>
47 </li>
47 </li>
48 <li class="${'active' if c.active=='advanced' else ''}">
48 <li class="${'active' if c.active=='advanced' else ''}">
49 <a href="${h.url('edit_repo_advanced', repo_name=c.repo_name)}">${_('Advanced')}</a>
49 <a href="${h.url('edit_repo_advanced', repo_name=c.repo_name)}">${_('Advanced')}</a>
50 </li>
50 </li>
51 <li class="${'active' if c.active=='vcs' else ''}">
51 <li class="${'active' if c.active=='vcs' else ''}">
52 <a href="${h.url('repo_vcs_settings', repo_name=c.repo_name)}">${_('VCS')}</a>
52 <a href="${h.url('repo_vcs_settings', repo_name=c.repo_name)}">${_('VCS')}</a>
53 </li>
53 </li>
54 <li class="${'active' if c.active=='fields' else ''}">
54 <li class="${'active' if c.active=='fields' else ''}">
55 <a href="${h.url('edit_repo_fields', repo_name=c.repo_name)}">${_('Extra Fields')}</a>
55 <a href="${h.url('edit_repo_fields', repo_name=c.repo_name)}">${_('Extra Fields')}</a>
56 </li>
56 </li>
57 <li class="${'active' if c.active=='issuetracker' else ''}">
57 <li class="${'active' if c.active=='issuetracker' else ''}">
58 <a href="${h.url('repo_settings_issuetracker', repo_name=c.repo_name)}">${_('Issue Tracker')}</a>
58 <a href="${h.url('repo_settings_issuetracker', repo_name=c.repo_name)}">${_('Issue Tracker')}</a>
59 </li>
59 </li>
60 <li class="${'active' if c.active=='caches' else ''}">
60 <li class="${'active' if c.active=='caches' else ''}">
61 <a href="${h.url('edit_repo_caches', repo_name=c.repo_name)}">${_('Caches')}</a>
61 <a href="${h.url('edit_repo_caches', repo_name=c.repo_name)}">${_('Caches')}</a>
62 </li>
62 </li>
63 %if c.repo_info.repo_type != 'svn':
63 %if c.repo_info.repo_type != 'svn':
64 <li class="${'active' if c.active=='remote' else ''}">
64 <li class="${'active' if c.active=='remote' else ''}">
65 <a href="${h.url('edit_repo_remote', repo_name=c.repo_name)}">${_('Remote')}</a>
65 <a href="${h.url('edit_repo_remote', repo_name=c.repo_name)}">${_('Remote')}</a>
66 </li>
66 </li>
67 %endif
67 %endif
68 <li class="${'active' if c.active=='statistics' else ''}">
68 <li class="${'active' if c.active=='statistics' else ''}">
69 <a href="${h.url('edit_repo_statistics', repo_name=c.repo_name)}">${_('Statistics')}</a>
69 <a href="${h.url('edit_repo_statistics', repo_name=c.repo_name)}">${_('Statistics')}</a>
70 </li>
70 </li>
71 <li class="${'active' if c.active=='integrations' else ''}">
71 <li class="${'active' if c.active=='integrations' else ''}">
72 <a href="${h.route_path('repo_integrations_home', repo_name=c.repo_name)}">${_('Integrations')}</a>
72 <a href="${h.route_path('repo_integrations_home', repo_name=c.repo_name)}">${_('Integrations')}</a>
73 </li>
73 </li>
74 ## TODO: dan: replace repo navigation with navlist registry like with
75 ## admin menu. First must find way to allow runtime configuration
76 ## it to account for the c.repo_info.repo_type != 'svn' call above
77 <%
78 reviewer_settings = False
79 try:
80 import rc_reviewers
81 reviewer_settings = True
82 except ImportError:
83 pass
84 %>
85 %if reviewer_settings:
86 <li class="${'active' if c.active=='reviewers' else ''}">
87 <a href="${h.route_path('repo_reviewers_home', repo_name=c.repo_name)}">${_('Reviewers')}</a>
88 </li>
89 %endif
74 </ul>
90 </ul>
75 </div>
91 </div>
76
92
77 <div class="main-content-full-width">
93 <div class="main-content-full-width">
78 ${self.main_content()}
94 ${self.main_content()}
79 </div>
95 </div>
80
96
81 </div>
97 </div>
82 </div>
98 </div>
83
99
84 </%def> No newline at end of file
100 </%def>
@@ -1,541 +1,567 b''
1 <%inherit file="/base/base.html"/>
1 <%inherit file="/base/base.html"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${c.repo_name} ${_('New pull request')}
4 ${c.repo_name} ${_('New pull request')}
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${_('New pull request')}
8 ${_('New pull request')}
9 </%def>
9 </%def>
10
10
11 <%def name="menu_bar_nav()">
11 <%def name="menu_bar_nav()">
12 ${self.menu_items(active='repositories')}
12 ${self.menu_items(active='repositories')}
13 </%def>
13 </%def>
14
14
15 <%def name="menu_bar_subnav()">
15 <%def name="menu_bar_subnav()">
16 ${self.repo_menu(active='showpullrequest')}
16 ${self.repo_menu(active='showpullrequest')}
17 </%def>
17 </%def>
18
18
19 <%def name="main()">
19 <%def name="main()">
20 <div class="box">
20 <div class="box">
21 <div class="title">
21 <div class="title">
22 ${self.repo_page_title(c.rhodecode_db_repo)}
22 ${self.repo_page_title(c.rhodecode_db_repo)}
23 ${self.breadcrumbs()}
23 ${self.breadcrumbs()}
24 </div>
24 </div>
25
25
26 ${h.secure_form(url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')}
26 ${h.secure_form(url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')}
27 <div class="box pr-summary">
27 <div class="box pr-summary">
28
28
29 <div class="summary-details block-left">
29 <div class="summary-details block-left">
30
30
31 <div class="form">
31 <div class="form">
32 <!-- fields -->
32 <!-- fields -->
33
33
34 <div class="fields" >
34 <div class="fields" >
35
35
36 <div class="field">
36 <div class="field">
37 <div class="label">
37 <div class="label">
38 <label for="pullrequest_title">${_('Title')}:</label>
38 <label for="pullrequest_title">${_('Title')}:</label>
39 </div>
39 </div>
40 <div class="input">
40 <div class="input">
41 ${h.text('pullrequest_title', c.default_title, class_="medium autogenerated-title")}
41 ${h.text('pullrequest_title', c.default_title, class_="medium autogenerated-title")}
42 </div>
42 </div>
43 </div>
43 </div>
44
44
45 <div class="field">
45 <div class="field">
46 <div class="label label-textarea">
46 <div class="label label-textarea">
47 <label for="pullrequest_desc">${_('Description')}:</label>
47 <label for="pullrequest_desc">${_('Description')}:</label>
48 </div>
48 </div>
49 <div class="textarea text-area editor">
49 <div class="textarea text-area editor">
50 ${h.textarea('pullrequest_desc',size=30, )}
50 ${h.textarea('pullrequest_desc',size=30, )}
51 <span class="help-block">
51 <span class="help-block">
52 ${_('Write a short description on this pull request')}
52 ${_('Write a short description on this pull request')}
53 </span>
53 </span>
54 </div>
54 </div>
55 </div>
55 </div>
56
56
57 <div class="field">
57 <div class="field">
58 <div class="label label-textarea">
58 <div class="label label-textarea">
59 <label for="pullrequest_desc">${_('Commit flow')}:</label>
59 <label for="pullrequest_desc">${_('Commit flow')}:</label>
60 </div>
60 </div>
61
61
62 ## TODO: johbo: Abusing the "content" class here to get the
62 ## TODO: johbo: Abusing the "content" class here to get the
63 ## desired effect. Should be replaced by a proper solution.
63 ## desired effect. Should be replaced by a proper solution.
64
64
65 ##ORG
65 ##ORG
66 <div class="content">
66 <div class="content">
67 <strong>${_('Origin repository')}:</strong>
67 <strong>${_('Origin repository')}:</strong>
68 ${c.rhodecode_db_repo.description}
68 ${c.rhodecode_db_repo.description}
69 </div>
69 </div>
70 <div class="content">
70 <div class="content">
71 ${h.hidden('source_repo')}
71 ${h.hidden('source_repo')}
72 ${h.hidden('source_ref')}
72 ${h.hidden('source_ref')}
73 </div>
73 </div>
74
74
75 ##OTHER, most Probably the PARENT OF THIS FORK
75 ##OTHER, most Probably the PARENT OF THIS FORK
76 <div class="content">
76 <div class="content">
77 ## filled with JS
77 ## filled with JS
78 <div id="target_repo_desc"></div>
78 <div id="target_repo_desc"></div>
79 </div>
79 </div>
80
80
81 <div class="content">
81 <div class="content">
82 ${h.hidden('target_repo')}
82 ${h.hidden('target_repo')}
83 ${h.hidden('target_ref')}
83 ${h.hidden('target_ref')}
84 <span id="target_ref_loading" style="display: none">
84 <span id="target_ref_loading" style="display: none">
85 ${_('Loading refs...')}
85 ${_('Loading refs...')}
86 </span>
86 </span>
87 </div>
87 </div>
88 </div>
88 </div>
89
89
90 <div class="field">
90 <div class="field">
91 <div class="label label-textarea">
91 <div class="label label-textarea">
92 <label for="pullrequest_submit"></label>
92 <label for="pullrequest_submit"></label>
93 </div>
93 </div>
94 <div class="input">
94 <div class="input">
95 <div class="pr-submit-button">
95 <div class="pr-submit-button">
96 ${h.submit('save',_('Submit Pull Request'),class_="btn")}
96 ${h.submit('save',_('Submit Pull Request'),class_="btn")}
97 </div>
97 </div>
98 <div id="pr_open_message"></div>
98 <div id="pr_open_message"></div>
99 </div>
99 </div>
100 </div>
100 </div>
101
101
102 <div class="pr-spacing-container"></div>
102 <div class="pr-spacing-container"></div>
103 </div>
103 </div>
104 </div>
104 </div>
105 </div>
105 </div>
106 <div>
106 <div>
107 <div class="reviewers-title block-right">
107 <div class="reviewers-title block-right">
108 <div class="pr-details-title">
108 <div class="pr-details-title">
109 ${_('Pull request reviewers')}
109 ${_('Pull request reviewers')}
110 </div>
110 </div>
111 </div>
111 </div>
112 <div id="reviewers" class="block-right pr-details-content reviewers">
112 <div id="reviewers" class="block-right pr-details-content reviewers">
113 ## members goes here, filled via JS based on initial selection !
113 ## members goes here, filled via JS based on initial selection !
114 <ul id="review_members" class="group_members"></ul>
114 <ul id="review_members" class="group_members"></ul>
115 <div id="add_reviewer_input" class='ac'>
115 <div id="add_reviewer_input" class='ac'>
116 <div class="reviewer_ac">
116 <div class="reviewer_ac">
117 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer'))}
117 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer'))}
118 <div id="reviewers_container"></div>
118 <div id="reviewers_container"></div>
119 </div>
119 </div>
120 </div>
120 </div>
121 </div>
121 </div>
122 </div>
122 </div>
123 </div>
123 </div>
124 <div class="box">
124 <div class="box">
125 <div>
125 <div>
126 ## overview pulled by ajax
126 ## overview pulled by ajax
127 <div id="pull_request_overview"></div>
127 <div id="pull_request_overview"></div>
128 </div>
128 </div>
129 </div>
129 </div>
130 ${h.end_form()}
130 ${h.end_form()}
131 </div>
131 </div>
132
132
133 <script type="text/javascript">
133 <script type="text/javascript">
134 $(function(){
134 $(function(){
135 var defaultSourceRepo = '${c.default_repo_data['source_repo_name']}';
135 var defaultSourceRepo = '${c.default_repo_data['source_repo_name']}';
136 var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n};
136 var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n};
137 var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}';
137 var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}';
138 var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n};
138 var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n};
139 var targetRepoName = '${c.repo_name}';
139 var targetRepoName = '${c.repo_name}';
140
140
141 var $pullRequestForm = $('#pull_request_form');
141 var $pullRequestForm = $('#pull_request_form');
142 var $sourceRepo = $('#source_repo', $pullRequestForm);
142 var $sourceRepo = $('#source_repo', $pullRequestForm);
143 var $targetRepo = $('#target_repo', $pullRequestForm);
143 var $targetRepo = $('#target_repo', $pullRequestForm);
144 var $sourceRef = $('#source_ref', $pullRequestForm);
144 var $sourceRef = $('#source_ref', $pullRequestForm);
145 var $targetRef = $('#target_ref', $pullRequestForm);
145 var $targetRef = $('#target_ref', $pullRequestForm);
146
146
147 var calculateContainerWidth = function() {
147 var calculateContainerWidth = function() {
148 var maxWidth = 0;
148 var maxWidth = 0;
149 var repoSelect2Containers = ['#source_repo', '#target_repo'];
149 var repoSelect2Containers = ['#source_repo', '#target_repo'];
150 $.each(repoSelect2Containers, function(idx, value) {
150 $.each(repoSelect2Containers, function(idx, value) {
151 $(value).select2('container').width('auto');
151 $(value).select2('container').width('auto');
152 var curWidth = $(value).select2('container').width();
152 var curWidth = $(value).select2('container').width();
153 if (maxWidth <= curWidth) {
153 if (maxWidth <= curWidth) {
154 maxWidth = curWidth;
154 maxWidth = curWidth;
155 }
155 }
156 $.each(repoSelect2Containers, function(idx, value) {
156 $.each(repoSelect2Containers, function(idx, value) {
157 $(value).select2('container').width(maxWidth + 10);
157 $(value).select2('container').width(maxWidth + 10);
158 });
158 });
159 });
159 });
160 };
160 };
161
161
162 var initRefSelection = function(selectedRef) {
162 var initRefSelection = function(selectedRef) {
163 return function(element, callback) {
163 return function(element, callback) {
164 // translate our select2 id into a text, it's a mapping to show
164 // translate our select2 id into a text, it's a mapping to show
165 // simple label when selecting by internal ID.
165 // simple label when selecting by internal ID.
166 var id, refData;
166 var id, refData;
167 if (selectedRef === undefined) {
167 if (selectedRef === undefined) {
168 id = element.val();
168 id = element.val();
169 refData = element.val().split(':');
169 refData = element.val().split(':');
170 } else {
170 } else {
171 id = selectedRef;
171 id = selectedRef;
172 refData = selectedRef.split(':');
172 refData = selectedRef.split(':');
173 }
173 }
174
174
175 var text = refData[1];
175 var text = refData[1];
176 if (refData[0] === 'rev') {
176 if (refData[0] === 'rev') {
177 text = text.substring(0, 12);
177 text = text.substring(0, 12);
178 }
178 }
179
179
180 var data = {id: id, text: text};
180 var data = {id: id, text: text};
181
181
182 callback(data);
182 callback(data);
183 };
183 };
184 };
184 };
185
185
186 var formatRefSelection = function(item) {
186 var formatRefSelection = function(item) {
187 var prefix = '';
187 var prefix = '';
188 var refData = item.id.split(':');
188 var refData = item.id.split(':');
189 if (refData[0] === 'branch') {
189 if (refData[0] === 'branch') {
190 prefix = '<i class="icon-branch"></i>';
190 prefix = '<i class="icon-branch"></i>';
191 }
191 }
192 else if (refData[0] === 'book') {
192 else if (refData[0] === 'book') {
193 prefix = '<i class="icon-bookmark"></i>';
193 prefix = '<i class="icon-bookmark"></i>';
194 }
194 }
195 else if (refData[0] === 'tag') {
195 else if (refData[0] === 'tag') {
196 prefix = '<i class="icon-tag"></i>';
196 prefix = '<i class="icon-tag"></i>';
197 }
197 }
198
198
199 var originalOption = item.element;
199 var originalOption = item.element;
200 return prefix + item.text;
200 return prefix + item.text;
201 };
201 };
202
202
203 // custom code mirror
203 // custom code mirror
204 var codeMirrorInstance = initPullRequestsCodeMirror('#pullrequest_desc');
204 var codeMirrorInstance = initPullRequestsCodeMirror('#pullrequest_desc');
205
205
206 var queryTargetRepo = function(self, query) {
206 var queryTargetRepo = function(self, query) {
207 // cache ALL results if query is empty
207 // cache ALL results if query is empty
208 var cacheKey = query.term || '__';
208 var cacheKey = query.term || '__';
209 var cachedData = self.cachedDataSource[cacheKey];
209 var cachedData = self.cachedDataSource[cacheKey];
210
210
211 if (cachedData) {
211 if (cachedData) {
212 query.callback({results: cachedData.results});
212 query.callback({results: cachedData.results});
213 } else {
213 } else {
214 $.ajax({
214 $.ajax({
215 url: pyroutes.url('pullrequest_repo_destinations', {'repo_name': targetRepoName}),
215 url: pyroutes.url('pullrequest_repo_destinations', {'repo_name': targetRepoName}),
216 data: {query: query.term},
216 data: {query: query.term},
217 dataType: 'json',
217 dataType: 'json',
218 type: 'GET',
218 type: 'GET',
219 success: function(data) {
219 success: function(data) {
220 self.cachedDataSource[cacheKey] = data;
220 self.cachedDataSource[cacheKey] = data;
221 query.callback({results: data.results});
221 query.callback({results: data.results});
222 },
222 },
223 error: function(data, textStatus, errorThrown) {
223 error: function(data, textStatus, errorThrown) {
224 alert(
224 alert(
225 "Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
225 "Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
226 }
226 }
227 });
227 });
228 }
228 }
229 };
229 };
230
230
231 var queryTargetRefs = function(initialData, query) {
231 var queryTargetRefs = function(initialData, query) {
232 var data = {results: []};
232 var data = {results: []};
233 // filter initialData
233 // filter initialData
234 $.each(initialData, function() {
234 $.each(initialData, function() {
235 var section = this.text;
235 var section = this.text;
236 var children = [];
236 var children = [];
237 $.each(this.children, function() {
237 $.each(this.children, function() {
238 if (query.term.length === 0 ||
238 if (query.term.length === 0 ||
239 this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) {
239 this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) {
240 children.push({'id': this.id, 'text': this.text})
240 children.push({'id': this.id, 'text': this.text})
241 }
241 }
242 });
242 });
243 data.results.push({'text': section, 'children': children})
243 data.results.push({'text': section, 'children': children})
244 });
244 });
245 query.callback({results: data.results});
245 query.callback({results: data.results});
246 };
246 };
247
247
248 var prButtonLock = function(lockEnabled, msg) {
248 var prButtonLock = function(lockEnabled, msg) {
249 if (lockEnabled) {
249 if (lockEnabled) {
250 $('#save').attr('disabled', 'disabled');
250 $('#save').attr('disabled', 'disabled');
251 }
251 }
252 else {
252 else {
253 $('#save').removeAttr('disabled');
253 $('#save').removeAttr('disabled');
254 }
254 }
255
255
256 $('#pr_open_message').html(msg);
256 $('#pr_open_message').html(msg);
257
257
258 };
258 };
259
259
260 var loadRepoRefDiffPreview = function() {
260 var loadRepoRefDiffPreview = function() {
261 var sourceRepo = $sourceRepo.eq(0).val();
261 var sourceRepo = $sourceRepo.eq(0).val();
262 var sourceRef = $sourceRef.eq(0).val().split(':');
262 var sourceRef = $sourceRef.eq(0).val().split(':');
263
263
264 var targetRepo = $targetRepo.eq(0).val();
264 var targetRepo = $targetRepo.eq(0).val();
265 var targetRef = $targetRef.eq(0).val().split(':');
265 var targetRef = $targetRef.eq(0).val().split(':');
266
266
267 var url_data = {
267 var url_data = {
268 'repo_name': targetRepo,
268 'repo_name': targetRepo,
269 'target_repo': sourceRepo,
269 'target_repo': sourceRepo,
270 'source_ref': targetRef[2],
270 'source_ref': targetRef[2],
271 'source_ref_type': 'rev',
271 'source_ref_type': 'rev',
272 'target_ref': sourceRef[2],
272 'target_ref': sourceRef[2],
273 'target_ref_type': 'rev',
273 'target_ref_type': 'rev',
274 'merge': true,
274 'merge': true,
275 '_': Date.now() // bypass browser caching
275 '_': Date.now() // bypass browser caching
276 }; // gather the source/target ref and repo here
276 }; // gather the source/target ref and repo here
277
277
278 if (sourceRef.length !== 3 || targetRef.length !== 3) {
278 if (sourceRef.length !== 3 || targetRef.length !== 3) {
279 prButtonLock(true, "${_('Please select origin and destination')}");
279 prButtonLock(true, "${_('Please select origin and destination')}");
280 return;
280 return;
281 }
281 }
282 var url = pyroutes.url('compare_url', url_data);
282 var url = pyroutes.url('compare_url', url_data);
283
283
284 // lock PR button, so we cannot send PR before it's calculated
284 // lock PR button, so we cannot send PR before it's calculated
285 prButtonLock(true, "${_('Loading compare ...')}");
285 prButtonLock(true, "${_('Loading compare ...')}");
286
286
287 if (loadRepoRefDiffPreview._currentRequest) {
287 if (loadRepoRefDiffPreview._currentRequest) {
288 loadRepoRefDiffPreview._currentRequest.abort();
288 loadRepoRefDiffPreview._currentRequest.abort();
289 }
289 }
290
290
291 loadRepoRefDiffPreview._currentRequest = $.get(url)
291 loadRepoRefDiffPreview._currentRequest = $.get(url)
292 .error(function(data, textStatus, errorThrown) {
292 .error(function(data, textStatus, errorThrown) {
293 alert(
293 alert(
294 "Error while processing request.\nError code {0} ({1}).".format(
294 "Error while processing request.\nError code {0} ({1}).".format(
295 data.status, data.statusText));
295 data.status, data.statusText));
296 })
296 })
297 .done(function(data) {
297 .done(function(data) {
298 loadRepoRefDiffPreview._currentRequest = null;
298 loadRepoRefDiffPreview._currentRequest = null;
299 $('#pull_request_overview').html(data);
299 $('#pull_request_overview').html(data);
300 var commitElements = $(data).find('tr[commit_id]');
300 var commitElements = $(data).find('tr[commit_id]');
301
301
302 var prTitleAndDesc = getTitleAndDescription(sourceRef[1],
302 var prTitleAndDesc = getTitleAndDescription(sourceRef[1],
303 commitElements, 5);
303 commitElements, 5);
304
304
305 var title = prTitleAndDesc[0];
305 var title = prTitleAndDesc[0];
306 var proposedDescription = prTitleAndDesc[1];
306 var proposedDescription = prTitleAndDesc[1];
307
307
308 var useGeneratedTitle = (
308 var useGeneratedTitle = (
309 $('#pullrequest_title').hasClass('autogenerated-title') ||
309 $('#pullrequest_title').hasClass('autogenerated-title') ||
310 $('#pullrequest_title').val() === "");
310 $('#pullrequest_title').val() === "");
311
311
312 if (title && useGeneratedTitle) {
312 if (title && useGeneratedTitle) {
313 // use generated title if we haven't specified our own
313 // use generated title if we haven't specified our own
314 $('#pullrequest_title').val(title);
314 $('#pullrequest_title').val(title);
315 $('#pullrequest_title').addClass('autogenerated-title');
315 $('#pullrequest_title').addClass('autogenerated-title');
316
316
317 }
317 }
318
318
319 var useGeneratedDescription = (
319 var useGeneratedDescription = (
320 !codeMirrorInstance._userDefinedDesc ||
320 !codeMirrorInstance._userDefinedDesc ||
321 codeMirrorInstance.getValue() === "");
321 codeMirrorInstance.getValue() === "");
322
322
323 if (proposedDescription && useGeneratedDescription) {
323 if (proposedDescription && useGeneratedDescription) {
324 // set proposed content, if we haven't defined our own,
324 // set proposed content, if we haven't defined our own,
325 // or we don't have description written
325 // or we don't have description written
326 codeMirrorInstance._userDefinedDesc = false; // reset state
326 codeMirrorInstance._userDefinedDesc = false; // reset state
327 codeMirrorInstance.setValue(proposedDescription);
327 codeMirrorInstance.setValue(proposedDescription);
328 }
328 }
329
329
330 var msg = '';
330 var msg = '';
331 if (commitElements.length === 1) {
331 if (commitElements.length === 1) {
332 msg = "${ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 1)}";
332 msg = "${ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 1)}";
333 } else {
333 } else {
334 msg = "${ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 2)}";
334 msg = "${ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 2)}";
335 }
335 }
336
336
337 msg += ' <a id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
337 msg += ' <a id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
338
338
339 if (commitElements.length) {
339 if (commitElements.length) {
340 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
340 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
341 prButtonLock(false, msg.replace('__COMMITS__', commitsLink));
341 prButtonLock(false, msg.replace('__COMMITS__', commitsLink));
342 }
342 }
343 else {
343 else {
344 prButtonLock(true, "${_('There are no commits to merge.')}");
344 prButtonLock(true, "${_('There are no commits to merge.')}");
345 }
345 }
346
346
347
347
348 });
348 });
349 };
349 };
350
350
351 /**
351 /**
352 Generate Title and Description for a PullRequest.
352 Generate Title and Description for a PullRequest.
353 In case of 1 commits, the title and description is that one commit
353 In case of 1 commits, the title and description is that one commit
354 in case of multiple commits, we iterate on them with max N number of commits,
354 in case of multiple commits, we iterate on them with max N number of commits,
355 and build description in a form
355 and build description in a form
356 - commitN
356 - commitN
357 - commitN+1
357 - commitN+1
358 ...
358 ...
359
359
360 Title is then constructed from branch names, or other references,
360 Title is then constructed from branch names, or other references,
361 replacing '-' and '_' into spaces
361 replacing '-' and '_' into spaces
362
362
363 * @param sourceRef
363 * @param sourceRef
364 * @param elements
364 * @param elements
365 * @param limit
365 * @param limit
366 * @returns {*[]}
366 * @returns {*[]}
367 */
367 */
368 var getTitleAndDescription = function(sourceRef, elements, limit) {
368 var getTitleAndDescription = function(sourceRef, elements, limit) {
369 var title = '';
369 var title = '';
370 var desc = '';
370 var desc = '';
371
371
372 $.each($(elements).get().reverse().slice(0, limit), function(idx, value) {
372 $.each($(elements).get().reverse().slice(0, limit), function(idx, value) {
373 var rawMessage = $(value).find('td.td-description .message').data('messageRaw');
373 var rawMessage = $(value).find('td.td-description .message').data('messageRaw');
374 desc += '- ' + rawMessage.split('\n')[0].replace(/\n+$/, "") + '\n';
374 desc += '- ' + rawMessage.split('\n')[0].replace(/\n+$/, "") + '\n';
375 });
375 });
376 // only 1 commit, use commit message as title
376 // only 1 commit, use commit message as title
377 if (elements.length == 1) {
377 if (elements.length == 1) {
378 title = $(elements[0]).find('td.td-description .message').data('messageRaw').split('\n')[0];
378 title = $(elements[0]).find('td.td-description .message').data('messageRaw').split('\n')[0];
379 }
379 }
380 else {
380 else {
381 // use reference name
381 // use reference name
382 title = sourceRef.replace(/-/g, ' ').replace(/_/g, ' ').capitalizeFirstLetter();
382 title = sourceRef.replace(/-/g, ' ').replace(/_/g, ' ').capitalizeFirstLetter();
383 }
383 }
384
384
385 return [title, desc]
385 return [title, desc]
386 };
386 };
387
387
388 var Select2Box = function(element, overrides) {
388 var Select2Box = function(element, overrides) {
389 var globalDefaults = {
389 var globalDefaults = {
390 dropdownAutoWidth: true,
390 dropdownAutoWidth: true,
391 containerCssClass: "drop-menu",
391 containerCssClass: "drop-menu",
392 dropdownCssClass: "drop-menu-dropdown",
392 dropdownCssClass: "drop-menu-dropdown",
393 };
393 };
394
394
395 var initSelect2 = function(defaultOptions) {
395 var initSelect2 = function(defaultOptions) {
396 var options = jQuery.extend(globalDefaults, defaultOptions, overrides);
396 var options = jQuery.extend(globalDefaults, defaultOptions, overrides);
397 element.select2(options);
397 element.select2(options);
398 };
398 };
399
399
400 return {
400 return {
401 initRef: function() {
401 initRef: function() {
402 var defaultOptions = {
402 var defaultOptions = {
403 minimumResultsForSearch: 5,
403 minimumResultsForSearch: 5,
404 formatSelection: formatRefSelection
404 formatSelection: formatRefSelection
405 };
405 };
406
406
407 initSelect2(defaultOptions);
407 initSelect2(defaultOptions);
408 },
408 },
409
409
410 initRepo: function(defaultValue, readOnly) {
410 initRepo: function(defaultValue, readOnly) {
411 var defaultOptions = {
411 var defaultOptions = {
412 initSelection : function (element, callback) {
412 initSelection : function (element, callback) {
413 var data = {id: defaultValue, text: defaultValue};
413 var data = {id: defaultValue, text: defaultValue};
414 callback(data);
414 callback(data);
415 }
415 }
416 };
416 };
417
417
418 initSelect2(defaultOptions);
418 initSelect2(defaultOptions);
419
419
420 element.select2('val', defaultSourceRepo);
420 element.select2('val', defaultSourceRepo);
421 if (readOnly === true) {
421 if (readOnly === true) {
422 element.select2('readonly', true);
422 element.select2('readonly', true);
423 };
423 };
424 }
424 }
425 };
425 };
426 };
426 };
427
427
428 var initTargetRefs = function(refsData, selectedRef){
428 var initTargetRefs = function(refsData, selectedRef){
429 Select2Box($targetRef, {
429 Select2Box($targetRef, {
430 query: function(query) {
430 query: function(query) {
431 queryTargetRefs(refsData, query);
431 queryTargetRefs(refsData, query);
432 },
432 },
433 initSelection : initRefSelection(selectedRef)
433 initSelection : initRefSelection(selectedRef)
434 }).initRef();
434 }).initRef();
435
435
436 if (!(selectedRef === undefined)) {
436 if (!(selectedRef === undefined)) {
437 $targetRef.select2('val', selectedRef);
437 $targetRef.select2('val', selectedRef);
438 }
438 }
439 };
439 };
440
440
441 var targetRepoChanged = function(repoData) {
441 var targetRepoChanged = function(repoData) {
442 // reset && add the reviewer based on selected repo
443 $('#review_members').html('');
444 addReviewMember(
445 repoData.user.user_id, repoData.user.firstname,
446 repoData.user.lastname, repoData.user.username,
447 repoData.user.gravatar_link);
448
449 // generate new DESC of target repo displayed next to select
442 // generate new DESC of target repo displayed next to select
450 $('#target_repo_desc').html(
443 $('#target_repo_desc').html(
451 "<strong>${_('Destination repository')}</strong>: {0}".format(repoData['description'])
444 "<strong>${_('Destination repository')}</strong>: {0}".format(repoData['description'])
452 );
445 );
453
446
454 // generate dynamic select2 for refs.
447 // generate dynamic select2 for refs.
455 initTargetRefs(repoData['refs']['select2_refs'],
448 initTargetRefs(repoData['refs']['select2_refs'],
456 repoData['refs']['selected_ref']);
449 repoData['refs']['selected_ref']);
457
450
458 };
451 };
459
452
460 var sourceRefSelect2 = Select2Box(
453 var sourceRefSelect2 = Select2Box(
461 $sourceRef, {
454 $sourceRef, {
462 placeholder: "${_('Select commit reference')}",
455 placeholder: "${_('Select commit reference')}",
463 query: function(query) {
456 query: function(query) {
464 var initialData = defaultSourceRepoData['refs']['select2_refs'];
457 var initialData = defaultSourceRepoData['refs']['select2_refs'];
465 queryTargetRefs(initialData, query)
458 queryTargetRefs(initialData, query)
466 },
459 },
467 initSelection: initRefSelection()
460 initSelection: initRefSelection()
468 }
461 }
469 );
462 );
470
463
471 var sourceRepoSelect2 = Select2Box($sourceRepo, {
464 var sourceRepoSelect2 = Select2Box($sourceRepo, {
472 query: function(query) {}
465 query: function(query) {}
473 });
466 });
474
467
475 var targetRepoSelect2 = Select2Box($targetRepo, {
468 var targetRepoSelect2 = Select2Box($targetRepo, {
476 cachedDataSource: {},
469 cachedDataSource: {},
477 query: $.debounce(250, function(query) {
470 query: $.debounce(250, function(query) {
478 queryTargetRepo(this, query);
471 queryTargetRepo(this, query);
479 }),
472 }),
480 formatResult: formatResult
473 formatResult: formatResult
481 });
474 });
482
475
483 sourceRefSelect2.initRef();
476 sourceRefSelect2.initRef();
484
477
485 sourceRepoSelect2.initRepo(defaultSourceRepo, true);
478 sourceRepoSelect2.initRepo(defaultSourceRepo, true);
486
479
487 targetRepoSelect2.initRepo(defaultTargetRepo, false);
480 targetRepoSelect2.initRepo(defaultTargetRepo, false);
488
481
489 $sourceRef.on('change', function(e){
482 $sourceRef.on('change', function(e){
490 loadRepoRefDiffPreview();
483 loadRepoRefDiffPreview();
484 loadDefaultReviewers();
491 });
485 });
492
486
493 $targetRef.on('change', function(e){
487 $targetRef.on('change', function(e){
494 loadRepoRefDiffPreview();
488 loadRepoRefDiffPreview();
489 loadDefaultReviewers();
495 });
490 });
496
491
497 $targetRepo.on('change', function(e){
492 $targetRepo.on('change', function(e){
498 var repoName = $(this).val();
493 var repoName = $(this).val();
499 calculateContainerWidth();
494 calculateContainerWidth();
500 $targetRef.select2('destroy');
495 $targetRef.select2('destroy');
501 $('#target_ref_loading').show();
496 $('#target_ref_loading').show();
502
497
503 $.ajax({
498 $.ajax({
504 url: pyroutes.url('pullrequest_repo_refs',
499 url: pyroutes.url('pullrequest_repo_refs',
505 {'repo_name': targetRepoName, 'target_repo_name':repoName}),
500 {'repo_name': targetRepoName, 'target_repo_name':repoName}),
506 data: {},
501 data: {},
507 dataType: 'json',
502 dataType: 'json',
508 type: 'GET',
503 type: 'GET',
509 success: function(data) {
504 success: function(data) {
510 $('#target_ref_loading').hide();
505 $('#target_ref_loading').hide();
511 targetRepoChanged(data);
506 targetRepoChanged(data);
512 loadRepoRefDiffPreview();
507 loadRepoRefDiffPreview();
513 },
508 },
514 error: function(data, textStatus, errorThrown) {
509 error: function(data, textStatus, errorThrown) {
515 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
510 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
516 }
511 }
517 })
512 })
518
513
519 });
514 });
520
515
516 var loadDefaultReviewers = function() {
517 if (loadDefaultReviewers._currentRequest) {
518 loadDefaultReviewers._currentRequest.abort();
519 }
520 var url = pyroutes.url('repo_default_reviewers_data', {'repo_name': targetRepoName});
521
522 var sourceRepo = $sourceRepo.eq(0).val();
523 var sourceRef = $sourceRef.eq(0).val().split(':');
524 var targetRepo = $targetRepo.eq(0).val();
525 var targetRef = $targetRef.eq(0).val().split(':');
526 url += '?source_repo=' + sourceRepo;
527 url += '&source_ref=' + sourceRef[2];
528 url += '&target_repo=' + targetRepo;
529 url += '&target_ref=' + targetRef[2];
530
531 loadDefaultReviewers._currentRequest = $.get(url)
532 .done(function(data) {
533 loadDefaultReviewers._currentRequest = null;
534
535 // reset && add the reviewer based on selected repo
536 $('#review_members').html('');
537 for (var i = 0; i < data.reviewers.length; i++) {
538 var reviewer = data.reviewers[i];
539 addReviewMember(
540 reviewer.user_id, reviewer.firstname,
541 reviewer.lastname, reviewer.username,
542 reviewer.gravatar_link, reviewer.reasons);
543 }
544 });
545 };
521 prButtonLock(true, "${_('Please select origin and destination')}");
546 prButtonLock(true, "${_('Please select origin and destination')}");
522
547
523 // auto-load on init, the target refs select2
548 // auto-load on init, the target refs select2
524 calculateContainerWidth();
549 calculateContainerWidth();
525 targetRepoChanged(defaultTargetRepoData);
550 targetRepoChanged(defaultTargetRepoData);
526
551
527 $('#pullrequest_title').on('keyup', function(e){
552 $('#pullrequest_title').on('keyup', function(e){
528 $(this).removeClass('autogenerated-title');
553 $(this).removeClass('autogenerated-title');
529 });
554 });
530
555
531 %if c.default_source_ref:
556 %if c.default_source_ref:
532 // in case we have a pre-selected value, use it now
557 // in case we have a pre-selected value, use it now
533 $sourceRef.select2('val', '${c.default_source_ref}');
558 $sourceRef.select2('val', '${c.default_source_ref}');
534 loadRepoRefDiffPreview();
559 loadRepoRefDiffPreview();
560 loadDefaultReviewers();
535 %endif
561 %endif
536
562
537 ReviewerAutoComplete('user');
563 ReviewerAutoComplete('user');
538 });
564 });
539 </script>
565 </script>
540
566
541 </%def>
567 </%def>
General Comments 0
You need to be logged in to leave comments. Login now