##// END OF EJS Templates
annotation: added shortcut links to browse the annotation view with previous commits.
marcink -
r1413:44a048ec default
parent child Browse files
Show More
@@ -1,1167 +1,1173 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 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', jsroute=True)
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']}, jsroute=True)
308 action='edit', conditions={'method': ['GET']}, jsroute=True)
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='user_group_members', conditions={'method': ['GET']})
392 action='user_group_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_supervisor', '/settings/supervisor',
506 m.connect('admin_settings_supervisor', '/settings/supervisor',
507 action='settings_supervisor', conditions={'method': ['GET']})
507 action='settings_supervisor', conditions={'method': ['GET']})
508 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
508 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
509 action='settings_supervisor_log', conditions={'method': ['GET']})
509 action='settings_supervisor_log', conditions={'method': ['GET']})
510
510
511 m.connect('admin_settings_labs', '/settings/labs',
511 m.connect('admin_settings_labs', '/settings/labs',
512 action='settings_labs_update',
512 action='settings_labs_update',
513 conditions={'method': ['POST']})
513 conditions={'method': ['POST']})
514 m.connect('admin_settings_labs', '/settings/labs',
514 m.connect('admin_settings_labs', '/settings/labs',
515 action='settings_labs', conditions={'method': ['GET']})
515 action='settings_labs', conditions={'method': ['GET']})
516
516
517 # ADMIN MY ACCOUNT
517 # ADMIN MY ACCOUNT
518 with rmap.submapper(path_prefix=ADMIN_PREFIX,
518 with rmap.submapper(path_prefix=ADMIN_PREFIX,
519 controller='admin/my_account') as m:
519 controller='admin/my_account') as m:
520
520
521 m.connect('my_account', '/my_account',
521 m.connect('my_account', '/my_account',
522 action='my_account', conditions={'method': ['GET']})
522 action='my_account', conditions={'method': ['GET']})
523 m.connect('my_account_edit', '/my_account/edit',
523 m.connect('my_account_edit', '/my_account/edit',
524 action='my_account_edit', conditions={'method': ['GET']})
524 action='my_account_edit', conditions={'method': ['GET']})
525 m.connect('my_account', '/my_account',
525 m.connect('my_account', '/my_account',
526 action='my_account_update', conditions={'method': ['POST']})
526 action='my_account_update', conditions={'method': ['POST']})
527
527
528 m.connect('my_account_password', '/my_account/password',
528 m.connect('my_account_password', '/my_account/password',
529 action='my_account_password', conditions={'method': ['GET', 'POST']})
529 action='my_account_password', conditions={'method': ['GET', 'POST']})
530
530
531 m.connect('my_account_repos', '/my_account/repos',
531 m.connect('my_account_repos', '/my_account/repos',
532 action='my_account_repos', conditions={'method': ['GET']})
532 action='my_account_repos', conditions={'method': ['GET']})
533
533
534 m.connect('my_account_watched', '/my_account/watched',
534 m.connect('my_account_watched', '/my_account/watched',
535 action='my_account_watched', conditions={'method': ['GET']})
535 action='my_account_watched', conditions={'method': ['GET']})
536
536
537 m.connect('my_account_pullrequests', '/my_account/pull_requests',
537 m.connect('my_account_pullrequests', '/my_account/pull_requests',
538 action='my_account_pullrequests', conditions={'method': ['GET']})
538 action='my_account_pullrequests', conditions={'method': ['GET']})
539
539
540 m.connect('my_account_perms', '/my_account/perms',
540 m.connect('my_account_perms', '/my_account/perms',
541 action='my_account_perms', conditions={'method': ['GET']})
541 action='my_account_perms', conditions={'method': ['GET']})
542
542
543 m.connect('my_account_emails', '/my_account/emails',
543 m.connect('my_account_emails', '/my_account/emails',
544 action='my_account_emails', conditions={'method': ['GET']})
544 action='my_account_emails', conditions={'method': ['GET']})
545 m.connect('my_account_emails', '/my_account/emails',
545 m.connect('my_account_emails', '/my_account/emails',
546 action='my_account_emails_add', conditions={'method': ['POST']})
546 action='my_account_emails_add', conditions={'method': ['POST']})
547 m.connect('my_account_emails', '/my_account/emails',
547 m.connect('my_account_emails', '/my_account/emails',
548 action='my_account_emails_delete', conditions={'method': ['DELETE']})
548 action='my_account_emails_delete', conditions={'method': ['DELETE']})
549
549
550 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
550 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
551 action='my_account_auth_tokens', conditions={'method': ['GET']})
551 action='my_account_auth_tokens', conditions={'method': ['GET']})
552 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
552 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
553 action='my_account_auth_tokens_add', conditions={'method': ['POST']})
553 action='my_account_auth_tokens_add', conditions={'method': ['POST']})
554 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
554 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
555 action='my_account_auth_tokens_delete', conditions={'method': ['DELETE']})
555 action='my_account_auth_tokens_delete', conditions={'method': ['DELETE']})
556 m.connect('my_account_notifications', '/my_account/notifications',
556 m.connect('my_account_notifications', '/my_account/notifications',
557 action='my_notifications',
557 action='my_notifications',
558 conditions={'method': ['GET']})
558 conditions={'method': ['GET']})
559 m.connect('my_account_notifications_toggle_visibility',
559 m.connect('my_account_notifications_toggle_visibility',
560 '/my_account/toggle_visibility',
560 '/my_account/toggle_visibility',
561 action='my_notifications_toggle_visibility',
561 action='my_notifications_toggle_visibility',
562 conditions={'method': ['POST']})
562 conditions={'method': ['POST']})
563 m.connect('my_account_notifications_test_channelstream',
563 m.connect('my_account_notifications_test_channelstream',
564 '/my_account/test_channelstream',
564 '/my_account/test_channelstream',
565 action='my_account_notifications_test_channelstream',
565 action='my_account_notifications_test_channelstream',
566 conditions={'method': ['POST']})
566 conditions={'method': ['POST']})
567
567
568 # NOTIFICATION REST ROUTES
568 # NOTIFICATION REST ROUTES
569 with rmap.submapper(path_prefix=ADMIN_PREFIX,
569 with rmap.submapper(path_prefix=ADMIN_PREFIX,
570 controller='admin/notifications') as m:
570 controller='admin/notifications') as m:
571 m.connect('notifications', '/notifications',
571 m.connect('notifications', '/notifications',
572 action='index', conditions={'method': ['GET']})
572 action='index', conditions={'method': ['GET']})
573 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
573 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
574 action='mark_all_read', conditions={'method': ['POST']})
574 action='mark_all_read', conditions={'method': ['POST']})
575 m.connect('/notifications/{notification_id}',
575 m.connect('/notifications/{notification_id}',
576 action='update', conditions={'method': ['PUT']})
576 action='update', conditions={'method': ['PUT']})
577 m.connect('/notifications/{notification_id}',
577 m.connect('/notifications/{notification_id}',
578 action='delete', conditions={'method': ['DELETE']})
578 action='delete', conditions={'method': ['DELETE']})
579 m.connect('notification', '/notifications/{notification_id}',
579 m.connect('notification', '/notifications/{notification_id}',
580 action='show', conditions={'method': ['GET']})
580 action='show', conditions={'method': ['GET']})
581
581
582 # ADMIN GIST
582 # ADMIN GIST
583 with rmap.submapper(path_prefix=ADMIN_PREFIX,
583 with rmap.submapper(path_prefix=ADMIN_PREFIX,
584 controller='admin/gists') as m:
584 controller='admin/gists') as m:
585 m.connect('gists', '/gists',
585 m.connect('gists', '/gists',
586 action='create', conditions={'method': ['POST']})
586 action='create', conditions={'method': ['POST']})
587 m.connect('gists', '/gists', jsroute=True,
587 m.connect('gists', '/gists', jsroute=True,
588 action='index', conditions={'method': ['GET']})
588 action='index', conditions={'method': ['GET']})
589 m.connect('new_gist', '/gists/new', jsroute=True,
589 m.connect('new_gist', '/gists/new', jsroute=True,
590 action='new', conditions={'method': ['GET']})
590 action='new', conditions={'method': ['GET']})
591
591
592 m.connect('/gists/{gist_id}',
592 m.connect('/gists/{gist_id}',
593 action='delete', conditions={'method': ['DELETE']})
593 action='delete', conditions={'method': ['DELETE']})
594 m.connect('edit_gist', '/gists/{gist_id}/edit',
594 m.connect('edit_gist', '/gists/{gist_id}/edit',
595 action='edit_form', conditions={'method': ['GET']})
595 action='edit_form', conditions={'method': ['GET']})
596 m.connect('edit_gist', '/gists/{gist_id}/edit',
596 m.connect('edit_gist', '/gists/{gist_id}/edit',
597 action='edit', conditions={'method': ['POST']})
597 action='edit', conditions={'method': ['POST']})
598 m.connect(
598 m.connect(
599 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
599 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
600 action='check_revision', conditions={'method': ['GET']})
600 action='check_revision', conditions={'method': ['GET']})
601
601
602 m.connect('gist', '/gists/{gist_id}',
602 m.connect('gist', '/gists/{gist_id}',
603 action='show', conditions={'method': ['GET']})
603 action='show', conditions={'method': ['GET']})
604 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
604 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
605 revision='tip',
605 revision='tip',
606 action='show', conditions={'method': ['GET']})
606 action='show', conditions={'method': ['GET']})
607 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
607 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
608 revision='tip',
608 revision='tip',
609 action='show', conditions={'method': ['GET']})
609 action='show', conditions={'method': ['GET']})
610 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
610 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
611 revision='tip',
611 revision='tip',
612 action='show', conditions={'method': ['GET']},
612 action='show', conditions={'method': ['GET']},
613 requirements=URL_NAME_REQUIREMENTS)
613 requirements=URL_NAME_REQUIREMENTS)
614
614
615 # ADMIN MAIN PAGES
615 # ADMIN MAIN PAGES
616 with rmap.submapper(path_prefix=ADMIN_PREFIX,
616 with rmap.submapper(path_prefix=ADMIN_PREFIX,
617 controller='admin/admin') as m:
617 controller='admin/admin') as m:
618 m.connect('admin_home', '', action='index')
618 m.connect('admin_home', '', action='index')
619 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
619 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
620 action='add_repo')
620 action='add_repo')
621 m.connect(
621 m.connect(
622 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
622 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
623 action='pull_requests')
623 action='pull_requests')
624 m.connect(
624 m.connect(
625 'pull_requests_global_1', '/pull-requests/{pull_request_id:[0-9]+}',
625 'pull_requests_global_1', '/pull-requests/{pull_request_id:[0-9]+}',
626 action='pull_requests')
626 action='pull_requests')
627 m.connect(
627 m.connect(
628 'pull_requests_global', '/pull-request/{pull_request_id:[0-9]+}',
628 'pull_requests_global', '/pull-request/{pull_request_id:[0-9]+}',
629 action='pull_requests')
629 action='pull_requests')
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',
697 controller='summary', action='repo_refs_data',
698 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
698 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
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',
702 rmap.connect('repo_default_reviewers_data', '/{repo_name}/default-reviewers',
703 controller='summary', action='repo_default_reviewers_data',
703 controller='summary', action='repo_default_reviewers_data',
704 jsroute=True, requirements=URL_NAME_REQUIREMENTS)
704 jsroute=True, requirements=URL_NAME_REQUIREMENTS)
705
705
706 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
706 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
707 controller='changeset', revision='tip',
707 controller='changeset', revision='tip',
708 conditions={'function': check_repo},
708 conditions={'function': check_repo},
709 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
709 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
710 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
710 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
711 controller='changeset', revision='tip', action='changeset_children',
711 controller='changeset', revision='tip', action='changeset_children',
712 conditions={'function': check_repo},
712 conditions={'function': check_repo},
713 requirements=URL_NAME_REQUIREMENTS)
713 requirements=URL_NAME_REQUIREMENTS)
714 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
714 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
715 controller='changeset', revision='tip', action='changeset_parents',
715 controller='changeset', revision='tip', action='changeset_parents',
716 conditions={'function': check_repo},
716 conditions={'function': check_repo},
717 requirements=URL_NAME_REQUIREMENTS)
717 requirements=URL_NAME_REQUIREMENTS)
718
718
719 # repo edit options
719 # repo edit options
720 rmap.connect('edit_repo', '/{repo_name}/settings', jsroute=True,
720 rmap.connect('edit_repo', '/{repo_name}/settings', jsroute=True,
721 controller='admin/repos', action='edit',
721 controller='admin/repos', action='edit',
722 conditions={'method': ['GET'], 'function': check_repo},
722 conditions={'method': ['GET'], 'function': check_repo},
723 requirements=URL_NAME_REQUIREMENTS)
723 requirements=URL_NAME_REQUIREMENTS)
724
724
725 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
725 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
726 jsroute=True,
726 jsroute=True,
727 controller='admin/repos', action='edit_permissions',
727 controller='admin/repos', action='edit_permissions',
728 conditions={'method': ['GET'], 'function': check_repo},
728 conditions={'method': ['GET'], 'function': check_repo},
729 requirements=URL_NAME_REQUIREMENTS)
729 requirements=URL_NAME_REQUIREMENTS)
730 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
730 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
731 controller='admin/repos', action='edit_permissions_update',
731 controller='admin/repos', action='edit_permissions_update',
732 conditions={'method': ['PUT'], 'function': check_repo},
732 conditions={'method': ['PUT'], 'function': check_repo},
733 requirements=URL_NAME_REQUIREMENTS)
733 requirements=URL_NAME_REQUIREMENTS)
734
734
735 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
735 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
736 controller='admin/repos', action='edit_fields',
736 controller='admin/repos', action='edit_fields',
737 conditions={'method': ['GET'], 'function': check_repo},
737 conditions={'method': ['GET'], 'function': check_repo},
738 requirements=URL_NAME_REQUIREMENTS)
738 requirements=URL_NAME_REQUIREMENTS)
739 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
739 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
740 controller='admin/repos', action='create_repo_field',
740 controller='admin/repos', action='create_repo_field',
741 conditions={'method': ['PUT'], 'function': check_repo},
741 conditions={'method': ['PUT'], 'function': check_repo},
742 requirements=URL_NAME_REQUIREMENTS)
742 requirements=URL_NAME_REQUIREMENTS)
743 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
743 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
744 controller='admin/repos', action='delete_repo_field',
744 controller='admin/repos', action='delete_repo_field',
745 conditions={'method': ['DELETE'], 'function': check_repo},
745 conditions={'method': ['DELETE'], 'function': check_repo},
746 requirements=URL_NAME_REQUIREMENTS)
746 requirements=URL_NAME_REQUIREMENTS)
747
747
748 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
748 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
749 controller='admin/repos', action='edit_advanced',
749 controller='admin/repos', action='edit_advanced',
750 conditions={'method': ['GET'], 'function': check_repo},
750 conditions={'method': ['GET'], 'function': check_repo},
751 requirements=URL_NAME_REQUIREMENTS)
751 requirements=URL_NAME_REQUIREMENTS)
752
752
753 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
753 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
754 controller='admin/repos', action='edit_advanced_locking',
754 controller='admin/repos', action='edit_advanced_locking',
755 conditions={'method': ['PUT'], 'function': check_repo},
755 conditions={'method': ['PUT'], 'function': check_repo},
756 requirements=URL_NAME_REQUIREMENTS)
756 requirements=URL_NAME_REQUIREMENTS)
757 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
757 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
758 controller='admin/repos', action='toggle_locking',
758 controller='admin/repos', action='toggle_locking',
759 conditions={'method': ['GET'], 'function': check_repo},
759 conditions={'method': ['GET'], 'function': check_repo},
760 requirements=URL_NAME_REQUIREMENTS)
760 requirements=URL_NAME_REQUIREMENTS)
761
761
762 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
762 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
763 controller='admin/repos', action='edit_advanced_journal',
763 controller='admin/repos', action='edit_advanced_journal',
764 conditions={'method': ['PUT'], 'function': check_repo},
764 conditions={'method': ['PUT'], 'function': check_repo},
765 requirements=URL_NAME_REQUIREMENTS)
765 requirements=URL_NAME_REQUIREMENTS)
766
766
767 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
767 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
768 controller='admin/repos', action='edit_advanced_fork',
768 controller='admin/repos', action='edit_advanced_fork',
769 conditions={'method': ['PUT'], 'function': check_repo},
769 conditions={'method': ['PUT'], 'function': check_repo},
770 requirements=URL_NAME_REQUIREMENTS)
770 requirements=URL_NAME_REQUIREMENTS)
771
771
772 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
772 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
773 controller='admin/repos', action='edit_caches_form',
773 controller='admin/repos', action='edit_caches_form',
774 conditions={'method': ['GET'], 'function': check_repo},
774 conditions={'method': ['GET'], 'function': check_repo},
775 requirements=URL_NAME_REQUIREMENTS)
775 requirements=URL_NAME_REQUIREMENTS)
776 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
776 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
777 controller='admin/repos', action='edit_caches',
777 controller='admin/repos', action='edit_caches',
778 conditions={'method': ['PUT'], 'function': check_repo},
778 conditions={'method': ['PUT'], 'function': check_repo},
779 requirements=URL_NAME_REQUIREMENTS)
779 requirements=URL_NAME_REQUIREMENTS)
780
780
781 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
781 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
782 controller='admin/repos', action='edit_remote_form',
782 controller='admin/repos', action='edit_remote_form',
783 conditions={'method': ['GET'], 'function': check_repo},
783 conditions={'method': ['GET'], 'function': check_repo},
784 requirements=URL_NAME_REQUIREMENTS)
784 requirements=URL_NAME_REQUIREMENTS)
785 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
785 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
786 controller='admin/repos', action='edit_remote',
786 controller='admin/repos', action='edit_remote',
787 conditions={'method': ['PUT'], 'function': check_repo},
787 conditions={'method': ['PUT'], 'function': check_repo},
788 requirements=URL_NAME_REQUIREMENTS)
788 requirements=URL_NAME_REQUIREMENTS)
789
789
790 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
790 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
791 controller='admin/repos', action='edit_statistics_form',
791 controller='admin/repos', action='edit_statistics_form',
792 conditions={'method': ['GET'], 'function': check_repo},
792 conditions={'method': ['GET'], 'function': check_repo},
793 requirements=URL_NAME_REQUIREMENTS)
793 requirements=URL_NAME_REQUIREMENTS)
794 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
794 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
795 controller='admin/repos', action='edit_statistics',
795 controller='admin/repos', action='edit_statistics',
796 conditions={'method': ['PUT'], 'function': check_repo},
796 conditions={'method': ['PUT'], 'function': check_repo},
797 requirements=URL_NAME_REQUIREMENTS)
797 requirements=URL_NAME_REQUIREMENTS)
798 rmap.connect('repo_settings_issuetracker',
798 rmap.connect('repo_settings_issuetracker',
799 '/{repo_name}/settings/issue-tracker',
799 '/{repo_name}/settings/issue-tracker',
800 controller='admin/repos', action='repo_issuetracker',
800 controller='admin/repos', action='repo_issuetracker',
801 conditions={'method': ['GET'], 'function': check_repo},
801 conditions={'method': ['GET'], 'function': check_repo},
802 requirements=URL_NAME_REQUIREMENTS)
802 requirements=URL_NAME_REQUIREMENTS)
803 rmap.connect('repo_issuetracker_test',
803 rmap.connect('repo_issuetracker_test',
804 '/{repo_name}/settings/issue-tracker/test',
804 '/{repo_name}/settings/issue-tracker/test',
805 controller='admin/repos', action='repo_issuetracker_test',
805 controller='admin/repos', action='repo_issuetracker_test',
806 conditions={'method': ['POST'], 'function': check_repo},
806 conditions={'method': ['POST'], 'function': check_repo},
807 requirements=URL_NAME_REQUIREMENTS)
807 requirements=URL_NAME_REQUIREMENTS)
808 rmap.connect('repo_issuetracker_delete',
808 rmap.connect('repo_issuetracker_delete',
809 '/{repo_name}/settings/issue-tracker/delete',
809 '/{repo_name}/settings/issue-tracker/delete',
810 controller='admin/repos', action='repo_issuetracker_delete',
810 controller='admin/repos', action='repo_issuetracker_delete',
811 conditions={'method': ['DELETE'], 'function': check_repo},
811 conditions={'method': ['DELETE'], 'function': check_repo},
812 requirements=URL_NAME_REQUIREMENTS)
812 requirements=URL_NAME_REQUIREMENTS)
813 rmap.connect('repo_issuetracker_save',
813 rmap.connect('repo_issuetracker_save',
814 '/{repo_name}/settings/issue-tracker/save',
814 '/{repo_name}/settings/issue-tracker/save',
815 controller='admin/repos', action='repo_issuetracker_save',
815 controller='admin/repos', action='repo_issuetracker_save',
816 conditions={'method': ['POST'], 'function': check_repo},
816 conditions={'method': ['POST'], 'function': check_repo},
817 requirements=URL_NAME_REQUIREMENTS)
817 requirements=URL_NAME_REQUIREMENTS)
818 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
818 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
819 controller='admin/repos', action='repo_settings_vcs_update',
819 controller='admin/repos', action='repo_settings_vcs_update',
820 conditions={'method': ['POST'], 'function': check_repo},
820 conditions={'method': ['POST'], 'function': check_repo},
821 requirements=URL_NAME_REQUIREMENTS)
821 requirements=URL_NAME_REQUIREMENTS)
822 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
822 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
823 controller='admin/repos', action='repo_settings_vcs',
823 controller='admin/repos', action='repo_settings_vcs',
824 conditions={'method': ['GET'], 'function': check_repo},
824 conditions={'method': ['GET'], 'function': check_repo},
825 requirements=URL_NAME_REQUIREMENTS)
825 requirements=URL_NAME_REQUIREMENTS)
826 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
826 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
827 controller='admin/repos', action='repo_delete_svn_pattern',
827 controller='admin/repos', action='repo_delete_svn_pattern',
828 conditions={'method': ['DELETE'], 'function': check_repo},
828 conditions={'method': ['DELETE'], 'function': check_repo},
829 requirements=URL_NAME_REQUIREMENTS)
829 requirements=URL_NAME_REQUIREMENTS)
830 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
830 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
831 controller='admin/repos', action='repo_settings_pullrequest',
831 controller='admin/repos', action='repo_settings_pullrequest',
832 conditions={'method': ['GET', 'POST'], 'function': check_repo},
832 conditions={'method': ['GET', 'POST'], 'function': check_repo},
833 requirements=URL_NAME_REQUIREMENTS)
833 requirements=URL_NAME_REQUIREMENTS)
834
834
835 # still working url for backward compat.
835 # still working url for backward compat.
836 rmap.connect('raw_changeset_home_depraced',
836 rmap.connect('raw_changeset_home_depraced',
837 '/{repo_name}/raw-changeset/{revision}',
837 '/{repo_name}/raw-changeset/{revision}',
838 controller='changeset', action='changeset_raw',
838 controller='changeset', action='changeset_raw',
839 revision='tip', conditions={'function': check_repo},
839 revision='tip', conditions={'function': check_repo},
840 requirements=URL_NAME_REQUIREMENTS)
840 requirements=URL_NAME_REQUIREMENTS)
841
841
842 # new URLs
842 # new URLs
843 rmap.connect('changeset_raw_home',
843 rmap.connect('changeset_raw_home',
844 '/{repo_name}/changeset-diff/{revision}',
844 '/{repo_name}/changeset-diff/{revision}',
845 controller='changeset', action='changeset_raw',
845 controller='changeset', action='changeset_raw',
846 revision='tip', conditions={'function': check_repo},
846 revision='tip', conditions={'function': check_repo},
847 requirements=URL_NAME_REQUIREMENTS)
847 requirements=URL_NAME_REQUIREMENTS)
848
848
849 rmap.connect('changeset_patch_home',
849 rmap.connect('changeset_patch_home',
850 '/{repo_name}/changeset-patch/{revision}',
850 '/{repo_name}/changeset-patch/{revision}',
851 controller='changeset', action='changeset_patch',
851 controller='changeset', action='changeset_patch',
852 revision='tip', conditions={'function': check_repo},
852 revision='tip', conditions={'function': check_repo},
853 requirements=URL_NAME_REQUIREMENTS)
853 requirements=URL_NAME_REQUIREMENTS)
854
854
855 rmap.connect('changeset_download_home',
855 rmap.connect('changeset_download_home',
856 '/{repo_name}/changeset-download/{revision}',
856 '/{repo_name}/changeset-download/{revision}',
857 controller='changeset', action='changeset_download',
857 controller='changeset', action='changeset_download',
858 revision='tip', conditions={'function': check_repo},
858 revision='tip', conditions={'function': check_repo},
859 requirements=URL_NAME_REQUIREMENTS)
859 requirements=URL_NAME_REQUIREMENTS)
860
860
861 rmap.connect('changeset_comment',
861 rmap.connect('changeset_comment',
862 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
862 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
863 controller='changeset', revision='tip', action='comment',
863 controller='changeset', revision='tip', action='comment',
864 conditions={'function': check_repo},
864 conditions={'function': check_repo},
865 requirements=URL_NAME_REQUIREMENTS)
865 requirements=URL_NAME_REQUIREMENTS)
866
866
867 rmap.connect('changeset_comment_preview',
867 rmap.connect('changeset_comment_preview',
868 '/{repo_name}/changeset/comment/preview', jsroute=True,
868 '/{repo_name}/changeset/comment/preview', jsroute=True,
869 controller='changeset', action='preview_comment',
869 controller='changeset', action='preview_comment',
870 conditions={'function': check_repo, 'method': ['POST']},
870 conditions={'function': check_repo, 'method': ['POST']},
871 requirements=URL_NAME_REQUIREMENTS)
871 requirements=URL_NAME_REQUIREMENTS)
872
872
873 rmap.connect('changeset_comment_delete',
873 rmap.connect('changeset_comment_delete',
874 '/{repo_name}/changeset/comment/{comment_id}/delete',
874 '/{repo_name}/changeset/comment/{comment_id}/delete',
875 controller='changeset', action='delete_comment',
875 controller='changeset', action='delete_comment',
876 conditions={'function': check_repo, 'method': ['DELETE']},
876 conditions={'function': check_repo, 'method': ['DELETE']},
877 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
877 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
878
878
879 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
879 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
880 controller='changeset', action='changeset_info',
880 controller='changeset', action='changeset_info',
881 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
881 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
882
882
883 rmap.connect('compare_home',
883 rmap.connect('compare_home',
884 '/{repo_name}/compare',
884 '/{repo_name}/compare',
885 controller='compare', action='index',
885 controller='compare', action='index',
886 conditions={'function': check_repo},
886 conditions={'function': check_repo},
887 requirements=URL_NAME_REQUIREMENTS)
887 requirements=URL_NAME_REQUIREMENTS)
888
888
889 rmap.connect('compare_url',
889 rmap.connect('compare_url',
890 '/{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:.*?}',
891 controller='compare', action='compare',
891 controller='compare', action='compare',
892 conditions={'function': check_repo},
892 conditions={'function': check_repo},
893 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
893 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
894
894
895 rmap.connect('pullrequest_home',
895 rmap.connect('pullrequest_home',
896 '/{repo_name}/pull-request/new', controller='pullrequests',
896 '/{repo_name}/pull-request/new', controller='pullrequests',
897 action='index', conditions={'function': check_repo,
897 action='index', conditions={'function': check_repo,
898 'method': ['GET']},
898 'method': ['GET']},
899 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
899 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
900
900
901 rmap.connect('pullrequest',
901 rmap.connect('pullrequest',
902 '/{repo_name}/pull-request/new', controller='pullrequests',
902 '/{repo_name}/pull-request/new', controller='pullrequests',
903 action='create', conditions={'function': check_repo,
903 action='create', conditions={'function': check_repo,
904 'method': ['POST']},
904 'method': ['POST']},
905 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
905 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
906
906
907 rmap.connect('pullrequest_repo_refs',
907 rmap.connect('pullrequest_repo_refs',
908 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
908 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
909 controller='pullrequests',
909 controller='pullrequests',
910 action='get_repo_refs',
910 action='get_repo_refs',
911 conditions={'function': check_repo, 'method': ['GET']},
911 conditions={'function': check_repo, 'method': ['GET']},
912 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
912 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
913
913
914 rmap.connect('pullrequest_repo_destinations',
914 rmap.connect('pullrequest_repo_destinations',
915 '/{repo_name}/pull-request/repo-destinations',
915 '/{repo_name}/pull-request/repo-destinations',
916 controller='pullrequests',
916 controller='pullrequests',
917 action='get_repo_destinations',
917 action='get_repo_destinations',
918 conditions={'function': check_repo, 'method': ['GET']},
918 conditions={'function': check_repo, 'method': ['GET']},
919 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
919 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
920
920
921 rmap.connect('pullrequest_show',
921 rmap.connect('pullrequest_show',
922 '/{repo_name}/pull-request/{pull_request_id}',
922 '/{repo_name}/pull-request/{pull_request_id}',
923 controller='pullrequests',
923 controller='pullrequests',
924 action='show', conditions={'function': check_repo,
924 action='show', conditions={'function': check_repo,
925 'method': ['GET']},
925 'method': ['GET']},
926 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
926 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
927
927
928 rmap.connect('pullrequest_update',
928 rmap.connect('pullrequest_update',
929 '/{repo_name}/pull-request/{pull_request_id}',
929 '/{repo_name}/pull-request/{pull_request_id}',
930 controller='pullrequests',
930 controller='pullrequests',
931 action='update', conditions={'function': check_repo,
931 action='update', conditions={'function': check_repo,
932 'method': ['PUT']},
932 'method': ['PUT']},
933 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
933 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
934
934
935 rmap.connect('pullrequest_merge',
935 rmap.connect('pullrequest_merge',
936 '/{repo_name}/pull-request/{pull_request_id}',
936 '/{repo_name}/pull-request/{pull_request_id}',
937 controller='pullrequests',
937 controller='pullrequests',
938 action='merge', conditions={'function': check_repo,
938 action='merge', conditions={'function': check_repo,
939 'method': ['POST']},
939 'method': ['POST']},
940 requirements=URL_NAME_REQUIREMENTS)
940 requirements=URL_NAME_REQUIREMENTS)
941
941
942 rmap.connect('pullrequest_delete',
942 rmap.connect('pullrequest_delete',
943 '/{repo_name}/pull-request/{pull_request_id}',
943 '/{repo_name}/pull-request/{pull_request_id}',
944 controller='pullrequests',
944 controller='pullrequests',
945 action='delete', conditions={'function': check_repo,
945 action='delete', conditions={'function': check_repo,
946 'method': ['DELETE']},
946 'method': ['DELETE']},
947 requirements=URL_NAME_REQUIREMENTS)
947 requirements=URL_NAME_REQUIREMENTS)
948
948
949 rmap.connect('pullrequest_show_all',
949 rmap.connect('pullrequest_show_all',
950 '/{repo_name}/pull-request',
950 '/{repo_name}/pull-request',
951 controller='pullrequests',
951 controller='pullrequests',
952 action='show_all', conditions={'function': check_repo,
952 action='show_all', conditions={'function': check_repo,
953 'method': ['GET']},
953 'method': ['GET']},
954 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
954 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
955
955
956 rmap.connect('pullrequest_comment',
956 rmap.connect('pullrequest_comment',
957 '/{repo_name}/pull-request-comment/{pull_request_id}',
957 '/{repo_name}/pull-request-comment/{pull_request_id}',
958 controller='pullrequests',
958 controller='pullrequests',
959 action='comment', conditions={'function': check_repo,
959 action='comment', conditions={'function': check_repo,
960 'method': ['POST']},
960 'method': ['POST']},
961 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
961 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
962
962
963 rmap.connect('pullrequest_comment_delete',
963 rmap.connect('pullrequest_comment_delete',
964 '/{repo_name}/pull-request-comment/{comment_id}/delete',
964 '/{repo_name}/pull-request-comment/{comment_id}/delete',
965 controller='pullrequests', action='delete_comment',
965 controller='pullrequests', action='delete_comment',
966 conditions={'function': check_repo, 'method': ['DELETE']},
966 conditions={'function': check_repo, 'method': ['DELETE']},
967 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
967 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
968
968
969 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
969 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
970 controller='summary', conditions={'function': check_repo},
970 controller='summary', conditions={'function': check_repo},
971 requirements=URL_NAME_REQUIREMENTS)
971 requirements=URL_NAME_REQUIREMENTS)
972
972
973 rmap.connect('branches_home', '/{repo_name}/branches',
973 rmap.connect('branches_home', '/{repo_name}/branches',
974 controller='branches', conditions={'function': check_repo},
974 controller='branches', conditions={'function': check_repo},
975 requirements=URL_NAME_REQUIREMENTS)
975 requirements=URL_NAME_REQUIREMENTS)
976
976
977 rmap.connect('tags_home', '/{repo_name}/tags',
977 rmap.connect('tags_home', '/{repo_name}/tags',
978 controller='tags', conditions={'function': check_repo},
978 controller='tags', conditions={'function': check_repo},
979 requirements=URL_NAME_REQUIREMENTS)
979 requirements=URL_NAME_REQUIREMENTS)
980
980
981 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
981 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
982 controller='bookmarks', conditions={'function': check_repo},
982 controller='bookmarks', conditions={'function': check_repo},
983 requirements=URL_NAME_REQUIREMENTS)
983 requirements=URL_NAME_REQUIREMENTS)
984
984
985 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
985 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
986 controller='changelog', conditions={'function': check_repo},
986 controller='changelog', conditions={'function': check_repo},
987 requirements=URL_NAME_REQUIREMENTS)
987 requirements=URL_NAME_REQUIREMENTS)
988
988
989 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
989 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
990 controller='changelog', action='changelog_summary',
990 controller='changelog', action='changelog_summary',
991 conditions={'function': check_repo},
991 conditions={'function': check_repo},
992 requirements=URL_NAME_REQUIREMENTS)
992 requirements=URL_NAME_REQUIREMENTS)
993
993
994 rmap.connect('changelog_file_home',
994 rmap.connect('changelog_file_home',
995 '/{repo_name}/changelog/{revision}/{f_path}',
995 '/{repo_name}/changelog/{revision}/{f_path}',
996 controller='changelog', f_path=None,
996 controller='changelog', f_path=None,
997 conditions={'function': check_repo},
997 conditions={'function': check_repo},
998 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
998 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
999
999
1000 rmap.connect('changelog_elements', '/{repo_name}/changelog_details',
1000 rmap.connect('changelog_elements', '/{repo_name}/changelog_details',
1001 controller='changelog', action='changelog_elements',
1001 controller='changelog', action='changelog_elements',
1002 conditions={'function': check_repo},
1002 conditions={'function': check_repo},
1003 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1003 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1004
1004
1005 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
1005 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
1006 controller='files', revision='tip', f_path='',
1006 controller='files', revision='tip', f_path='',
1007 conditions={'function': check_repo},
1007 conditions={'function': check_repo},
1008 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1008 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1009
1009
1010 rmap.connect('files_home_simple_catchrev',
1010 rmap.connect('files_home_simple_catchrev',
1011 '/{repo_name}/files/{revision}',
1011 '/{repo_name}/files/{revision}',
1012 controller='files', revision='tip', f_path='',
1012 controller='files', revision='tip', f_path='',
1013 conditions={'function': check_repo},
1013 conditions={'function': check_repo},
1014 requirements=URL_NAME_REQUIREMENTS)
1014 requirements=URL_NAME_REQUIREMENTS)
1015
1015
1016 rmap.connect('files_home_simple_catchall',
1016 rmap.connect('files_home_simple_catchall',
1017 '/{repo_name}/files',
1017 '/{repo_name}/files',
1018 controller='files', revision='tip', f_path='',
1018 controller='files', revision='tip', f_path='',
1019 conditions={'function': check_repo},
1019 conditions={'function': check_repo},
1020 requirements=URL_NAME_REQUIREMENTS)
1020 requirements=URL_NAME_REQUIREMENTS)
1021
1021
1022 rmap.connect('files_history_home',
1022 rmap.connect('files_history_home',
1023 '/{repo_name}/history/{revision}/{f_path}',
1023 '/{repo_name}/history/{revision}/{f_path}',
1024 controller='files', action='history', revision='tip', f_path='',
1024 controller='files', action='history', revision='tip', f_path='',
1025 conditions={'function': check_repo},
1025 conditions={'function': check_repo},
1026 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1026 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1027
1027
1028 rmap.connect('files_authors_home',
1028 rmap.connect('files_authors_home',
1029 '/{repo_name}/authors/{revision}/{f_path}',
1029 '/{repo_name}/authors/{revision}/{f_path}',
1030 controller='files', action='authors', revision='tip', f_path='',
1030 controller='files', action='authors', revision='tip', f_path='',
1031 conditions={'function': check_repo},
1031 conditions={'function': check_repo},
1032 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1032 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1033
1033
1034 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
1034 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
1035 controller='files', action='diff', f_path='',
1035 controller='files', action='diff', f_path='',
1036 conditions={'function': check_repo},
1036 conditions={'function': check_repo},
1037 requirements=URL_NAME_REQUIREMENTS)
1037 requirements=URL_NAME_REQUIREMENTS)
1038
1038
1039 rmap.connect('files_diff_2way_home',
1039 rmap.connect('files_diff_2way_home',
1040 '/{repo_name}/diff-2way/{f_path}',
1040 '/{repo_name}/diff-2way/{f_path}',
1041 controller='files', action='diff_2way', f_path='',
1041 controller='files', action='diff_2way', f_path='',
1042 conditions={'function': check_repo},
1042 conditions={'function': check_repo},
1043 requirements=URL_NAME_REQUIREMENTS)
1043 requirements=URL_NAME_REQUIREMENTS)
1044
1044
1045 rmap.connect('files_rawfile_home',
1045 rmap.connect('files_rawfile_home',
1046 '/{repo_name}/rawfile/{revision}/{f_path}',
1046 '/{repo_name}/rawfile/{revision}/{f_path}',
1047 controller='files', action='rawfile', revision='tip',
1047 controller='files', action='rawfile', revision='tip',
1048 f_path='', conditions={'function': check_repo},
1048 f_path='', conditions={'function': check_repo},
1049 requirements=URL_NAME_REQUIREMENTS)
1049 requirements=URL_NAME_REQUIREMENTS)
1050
1050
1051 rmap.connect('files_raw_home',
1051 rmap.connect('files_raw_home',
1052 '/{repo_name}/raw/{revision}/{f_path}',
1052 '/{repo_name}/raw/{revision}/{f_path}',
1053 controller='files', action='raw', revision='tip', f_path='',
1053 controller='files', action='raw', revision='tip', f_path='',
1054 conditions={'function': check_repo},
1054 conditions={'function': check_repo},
1055 requirements=URL_NAME_REQUIREMENTS)
1055 requirements=URL_NAME_REQUIREMENTS)
1056
1056
1057 rmap.connect('files_render_home',
1057 rmap.connect('files_render_home',
1058 '/{repo_name}/render/{revision}/{f_path}',
1058 '/{repo_name}/render/{revision}/{f_path}',
1059 controller='files', action='index', revision='tip', f_path='',
1059 controller='files', action='index', revision='tip', f_path='',
1060 rendered=True, conditions={'function': check_repo},
1060 rendered=True, conditions={'function': check_repo},
1061 requirements=URL_NAME_REQUIREMENTS)
1061 requirements=URL_NAME_REQUIREMENTS)
1062
1062
1063 rmap.connect('files_annotate_home',
1063 rmap.connect('files_annotate_home',
1064 '/{repo_name}/annotate/{revision}/{f_path}',
1064 '/{repo_name}/annotate/{revision}/{f_path}',
1065 controller='files', action='index', revision='tip',
1065 controller='files', action='index', revision='tip',
1066 f_path='', annotate=True, conditions={'function': check_repo},
1066 f_path='', annotate=True, conditions={'function': check_repo},
1067 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1067 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1068
1068
1069 rmap.connect('files_annotate_previous',
1070 '/{repo_name}/annotate-previous/{revision}/{f_path}',
1071 controller='files', action='annotate_previous', revision='tip',
1072 f_path='', annotate=True, conditions={'function': check_repo},
1073 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1074
1069 rmap.connect('files_edit',
1075 rmap.connect('files_edit',
1070 '/{repo_name}/edit/{revision}/{f_path}',
1076 '/{repo_name}/edit/{revision}/{f_path}',
1071 controller='files', action='edit', revision='tip',
1077 controller='files', action='edit', revision='tip',
1072 f_path='',
1078 f_path='',
1073 conditions={'function': check_repo, 'method': ['POST']},
1079 conditions={'function': check_repo, 'method': ['POST']},
1074 requirements=URL_NAME_REQUIREMENTS)
1080 requirements=URL_NAME_REQUIREMENTS)
1075
1081
1076 rmap.connect('files_edit_home',
1082 rmap.connect('files_edit_home',
1077 '/{repo_name}/edit/{revision}/{f_path}',
1083 '/{repo_name}/edit/{revision}/{f_path}',
1078 controller='files', action='edit_home', revision='tip',
1084 controller='files', action='edit_home', revision='tip',
1079 f_path='', conditions={'function': check_repo},
1085 f_path='', conditions={'function': check_repo},
1080 requirements=URL_NAME_REQUIREMENTS)
1086 requirements=URL_NAME_REQUIREMENTS)
1081
1087
1082 rmap.connect('files_add',
1088 rmap.connect('files_add',
1083 '/{repo_name}/add/{revision}/{f_path}',
1089 '/{repo_name}/add/{revision}/{f_path}',
1084 controller='files', action='add', revision='tip',
1090 controller='files', action='add', revision='tip',
1085 f_path='',
1091 f_path='',
1086 conditions={'function': check_repo, 'method': ['POST']},
1092 conditions={'function': check_repo, 'method': ['POST']},
1087 requirements=URL_NAME_REQUIREMENTS)
1093 requirements=URL_NAME_REQUIREMENTS)
1088
1094
1089 rmap.connect('files_add_home',
1095 rmap.connect('files_add_home',
1090 '/{repo_name}/add/{revision}/{f_path}',
1096 '/{repo_name}/add/{revision}/{f_path}',
1091 controller='files', action='add_home', revision='tip',
1097 controller='files', action='add_home', revision='tip',
1092 f_path='', conditions={'function': check_repo},
1098 f_path='', conditions={'function': check_repo},
1093 requirements=URL_NAME_REQUIREMENTS)
1099 requirements=URL_NAME_REQUIREMENTS)
1094
1100
1095 rmap.connect('files_delete',
1101 rmap.connect('files_delete',
1096 '/{repo_name}/delete/{revision}/{f_path}',
1102 '/{repo_name}/delete/{revision}/{f_path}',
1097 controller='files', action='delete', revision='tip',
1103 controller='files', action='delete', revision='tip',
1098 f_path='',
1104 f_path='',
1099 conditions={'function': check_repo, 'method': ['POST']},
1105 conditions={'function': check_repo, 'method': ['POST']},
1100 requirements=URL_NAME_REQUIREMENTS)
1106 requirements=URL_NAME_REQUIREMENTS)
1101
1107
1102 rmap.connect('files_delete_home',
1108 rmap.connect('files_delete_home',
1103 '/{repo_name}/delete/{revision}/{f_path}',
1109 '/{repo_name}/delete/{revision}/{f_path}',
1104 controller='files', action='delete_home', revision='tip',
1110 controller='files', action='delete_home', revision='tip',
1105 f_path='', conditions={'function': check_repo},
1111 f_path='', conditions={'function': check_repo},
1106 requirements=URL_NAME_REQUIREMENTS)
1112 requirements=URL_NAME_REQUIREMENTS)
1107
1113
1108 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1114 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1109 controller='files', action='archivefile',
1115 controller='files', action='archivefile',
1110 conditions={'function': check_repo},
1116 conditions={'function': check_repo},
1111 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1117 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1112
1118
1113 rmap.connect('files_nodelist_home',
1119 rmap.connect('files_nodelist_home',
1114 '/{repo_name}/nodelist/{revision}/{f_path}',
1120 '/{repo_name}/nodelist/{revision}/{f_path}',
1115 controller='files', action='nodelist',
1121 controller='files', action='nodelist',
1116 conditions={'function': check_repo},
1122 conditions={'function': check_repo},
1117 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1123 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1118
1124
1119 rmap.connect('files_nodetree_full',
1125 rmap.connect('files_nodetree_full',
1120 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1126 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1121 controller='files', action='nodetree_full',
1127 controller='files', action='nodetree_full',
1122 conditions={'function': check_repo},
1128 conditions={'function': check_repo},
1123 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1129 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1124
1130
1125 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1131 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1126 controller='forks', action='fork_create',
1132 controller='forks', action='fork_create',
1127 conditions={'function': check_repo, 'method': ['POST']},
1133 conditions={'function': check_repo, 'method': ['POST']},
1128 requirements=URL_NAME_REQUIREMENTS)
1134 requirements=URL_NAME_REQUIREMENTS)
1129
1135
1130 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1136 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1131 controller='forks', action='fork',
1137 controller='forks', action='fork',
1132 conditions={'function': check_repo},
1138 conditions={'function': check_repo},
1133 requirements=URL_NAME_REQUIREMENTS)
1139 requirements=URL_NAME_REQUIREMENTS)
1134
1140
1135 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1141 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1136 controller='forks', action='forks',
1142 controller='forks', action='forks',
1137 conditions={'function': check_repo},
1143 conditions={'function': check_repo},
1138 requirements=URL_NAME_REQUIREMENTS)
1144 requirements=URL_NAME_REQUIREMENTS)
1139
1145
1140 rmap.connect('repo_followers_home', '/{repo_name}/followers',
1146 rmap.connect('repo_followers_home', '/{repo_name}/followers',
1141 controller='followers', action='followers',
1147 controller='followers', action='followers',
1142 conditions={'function': check_repo},
1148 conditions={'function': check_repo},
1143 requirements=URL_NAME_REQUIREMENTS)
1149 requirements=URL_NAME_REQUIREMENTS)
1144
1150
1145 # must be here for proper group/repo catching pattern
1151 # must be here for proper group/repo catching pattern
1146 _connect_with_slash(
1152 _connect_with_slash(
1147 rmap, 'repo_group_home', '/{group_name}',
1153 rmap, 'repo_group_home', '/{group_name}',
1148 controller='home', action='index_repo_group',
1154 controller='home', action='index_repo_group',
1149 conditions={'function': check_group},
1155 conditions={'function': check_group},
1150 requirements=URL_NAME_REQUIREMENTS)
1156 requirements=URL_NAME_REQUIREMENTS)
1151
1157
1152 # catch all, at the end
1158 # catch all, at the end
1153 _connect_with_slash(
1159 _connect_with_slash(
1154 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1160 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1155 controller='summary', action='index',
1161 controller='summary', action='index',
1156 conditions={'function': check_repo},
1162 conditions={'function': check_repo},
1157 requirements=URL_NAME_REQUIREMENTS)
1163 requirements=URL_NAME_REQUIREMENTS)
1158
1164
1159 return rmap
1165 return rmap
1160
1166
1161
1167
1162 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1168 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1163 """
1169 """
1164 Connect a route with an optional trailing slash in `path`.
1170 Connect a route with an optional trailing slash in `path`.
1165 """
1171 """
1166 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1172 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1167 mapper.connect(name, path, *args, **kwargs)
1173 mapper.connect(name, path, *args, **kwargs)
@@ -1,1061 +1,1087 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 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 Files controller for RhodeCode Enterprise
22 Files controller for RhodeCode Enterprise
23 """
23 """
24
24
25 import itertools
25 import itertools
26 import logging
26 import logging
27 import os
27 import os
28 import shutil
28 import shutil
29 import tempfile
29 import tempfile
30
30
31 from pylons import request, response, tmpl_context as c, url
31 from pylons import request, response, tmpl_context as c, url
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from webob.exc import HTTPNotFound, HTTPBadRequest
34 from webob.exc import HTTPNotFound, HTTPBadRequest
35
35
36 from rhodecode.controllers.utils import parse_path_ref
36 from rhodecode.controllers.utils import parse_path_ref
37 from rhodecode.lib import diffs, helpers as h, caches
37 from rhodecode.lib import diffs, helpers as h, caches
38 from rhodecode.lib.compat import OrderedDict
38 from rhodecode.lib.compat import OrderedDict
39 from rhodecode.lib.codeblocks import (
39 from rhodecode.lib.codeblocks import (
40 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
40 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
41 from rhodecode.lib.utils import jsonify, action_logger
41 from rhodecode.lib.utils import jsonify, action_logger
42 from rhodecode.lib.utils2 import (
42 from rhodecode.lib.utils2 import (
43 convert_line_endings, detect_mode, safe_str, str2bool)
43 convert_line_endings, detect_mode, safe_str, str2bool)
44 from rhodecode.lib.auth import (
44 from rhodecode.lib.auth import (
45 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired, XHRRequired)
45 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired, XHRRequired)
46 from rhodecode.lib.base import BaseRepoController, render
46 from rhodecode.lib.base import BaseRepoController, render
47 from rhodecode.lib.vcs import path as vcspath
47 from rhodecode.lib.vcs import path as vcspath
48 from rhodecode.lib.vcs.backends.base import EmptyCommit
48 from rhodecode.lib.vcs.backends.base import EmptyCommit
49 from rhodecode.lib.vcs.conf import settings
49 from rhodecode.lib.vcs.conf import settings
50 from rhodecode.lib.vcs.exceptions import (
50 from rhodecode.lib.vcs.exceptions import (
51 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
51 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
52 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
52 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
53 NodeDoesNotExistError, CommitError, NodeError)
53 NodeDoesNotExistError, CommitError, NodeError)
54 from rhodecode.lib.vcs.nodes import FileNode
54 from rhodecode.lib.vcs.nodes import FileNode
55
55
56 from rhodecode.model.repo import RepoModel
56 from rhodecode.model.repo import RepoModel
57 from rhodecode.model.scm import ScmModel
57 from rhodecode.model.scm import ScmModel
58 from rhodecode.model.db import Repository
58 from rhodecode.model.db import Repository
59
59
60 from rhodecode.controllers.changeset import (
60 from rhodecode.controllers.changeset import (
61 _ignorews_url, _context_url, get_line_ctx, get_ignore_ws)
61 _ignorews_url, _context_url, get_line_ctx, get_ignore_ws)
62 from rhodecode.lib.exceptions import NonRelativePathError
62 from rhodecode.lib.exceptions import NonRelativePathError
63
63
64 log = logging.getLogger(__name__)
64 log = logging.getLogger(__name__)
65
65
66
66
67 class FilesController(BaseRepoController):
67 class FilesController(BaseRepoController):
68
68
69 def __before__(self):
69 def __before__(self):
70 super(FilesController, self).__before__()
70 super(FilesController, self).__before__()
71 c.cut_off_limit = self.cut_off_limit_file
71 c.cut_off_limit = self.cut_off_limit_file
72
72
73 def _get_default_encoding(self):
73 def _get_default_encoding(self):
74 enc_list = getattr(c, 'default_encodings', [])
74 enc_list = getattr(c, 'default_encodings', [])
75 return enc_list[0] if enc_list else 'UTF-8'
75 return enc_list[0] if enc_list else 'UTF-8'
76
76
77 def __get_commit_or_redirect(self, commit_id, repo_name,
77 def __get_commit_or_redirect(self, commit_id, repo_name,
78 redirect_after=True):
78 redirect_after=True):
79 """
79 """
80 This is a safe way to get commit. If an error occurs it redirects to
80 This is a safe way to get commit. If an error occurs it redirects to
81 tip with proper message
81 tip with proper message
82
82
83 :param commit_id: id of commit to fetch
83 :param commit_id: id of commit to fetch
84 :param repo_name: repo name to redirect after
84 :param repo_name: repo name to redirect after
85 :param redirect_after: toggle redirection
85 :param redirect_after: toggle redirection
86 """
86 """
87 try:
87 try:
88 return c.rhodecode_repo.get_commit(commit_id)
88 return c.rhodecode_repo.get_commit(commit_id)
89 except EmptyRepositoryError:
89 except EmptyRepositoryError:
90 if not redirect_after:
90 if not redirect_after:
91 return None
91 return None
92 url_ = url('files_add_home',
92 url_ = url('files_add_home',
93 repo_name=c.repo_name,
93 repo_name=c.repo_name,
94 revision=0, f_path='', anchor='edit')
94 revision=0, f_path='', anchor='edit')
95 if h.HasRepoPermissionAny(
95 if h.HasRepoPermissionAny(
96 'repository.write', 'repository.admin')(c.repo_name):
96 'repository.write', 'repository.admin')(c.repo_name):
97 add_new = h.link_to(
97 add_new = h.link_to(
98 _('Click here to add a new file.'),
98 _('Click here to add a new file.'),
99 url_, class_="alert-link")
99 url_, class_="alert-link")
100 else:
100 else:
101 add_new = ""
101 add_new = ""
102 h.flash(h.literal(
102 h.flash(h.literal(
103 _('There are no files yet. %s') % add_new), category='warning')
103 _('There are no files yet. %s') % add_new), category='warning')
104 redirect(h.url('summary_home', repo_name=repo_name))
104 redirect(h.url('summary_home', repo_name=repo_name))
105 except (CommitDoesNotExistError, LookupError):
105 except (CommitDoesNotExistError, LookupError):
106 msg = _('No such commit exists for this repository')
106 msg = _('No such commit exists for this repository')
107 h.flash(msg, category='error')
107 h.flash(msg, category='error')
108 raise HTTPNotFound()
108 raise HTTPNotFound()
109 except RepositoryError as e:
109 except RepositoryError as e:
110 h.flash(safe_str(e), category='error')
110 h.flash(safe_str(e), category='error')
111 raise HTTPNotFound()
111 raise HTTPNotFound()
112
112
113 def __get_filenode_or_redirect(self, repo_name, commit, path):
113 def __get_filenode_or_redirect(self, repo_name, commit, path):
114 """
114 """
115 Returns file_node, if error occurs or given path is directory,
115 Returns file_node, if error occurs or given path is directory,
116 it'll redirect to top level path
116 it'll redirect to top level path
117
117
118 :param repo_name: repo_name
118 :param repo_name: repo_name
119 :param commit: given commit
119 :param commit: given commit
120 :param path: path to lookup
120 :param path: path to lookup
121 """
121 """
122 try:
122 try:
123 file_node = commit.get_node(path)
123 file_node = commit.get_node(path)
124 if file_node.is_dir():
124 if file_node.is_dir():
125 raise RepositoryError('The given path is a directory')
125 raise RepositoryError('The given path is a directory')
126 except CommitDoesNotExistError:
126 except CommitDoesNotExistError:
127 msg = _('No such commit exists for this repository')
127 msg = _('No such commit exists for this repository')
128 log.exception(msg)
128 log.exception(msg)
129 h.flash(msg, category='error')
129 h.flash(msg, category='error')
130 raise HTTPNotFound()
130 raise HTTPNotFound()
131 except RepositoryError as e:
131 except RepositoryError as e:
132 h.flash(safe_str(e), category='error')
132 h.flash(safe_str(e), category='error')
133 raise HTTPNotFound()
133 raise HTTPNotFound()
134
134
135 return file_node
135 return file_node
136
136
137 def __get_tree_cache_manager(self, repo_name, namespace_type):
137 def __get_tree_cache_manager(self, repo_name, namespace_type):
138 _namespace = caches.get_repo_namespace_key(namespace_type, repo_name)
138 _namespace = caches.get_repo_namespace_key(namespace_type, repo_name)
139 return caches.get_cache_manager('repo_cache_long', _namespace)
139 return caches.get_cache_manager('repo_cache_long', _namespace)
140
140
141 def _get_tree_at_commit(self, repo_name, commit_id, f_path,
141 def _get_tree_at_commit(self, repo_name, commit_id, f_path,
142 full_load=False, force=False):
142 full_load=False, force=False):
143 def _cached_tree():
143 def _cached_tree():
144 log.debug('Generating cached file tree for %s, %s, %s',
144 log.debug('Generating cached file tree for %s, %s, %s',
145 repo_name, commit_id, f_path)
145 repo_name, commit_id, f_path)
146 c.full_load = full_load
146 c.full_load = full_load
147 return render('files/files_browser_tree.mako')
147 return render('files/files_browser_tree.mako')
148
148
149 cache_manager = self.__get_tree_cache_manager(
149 cache_manager = self.__get_tree_cache_manager(
150 repo_name, caches.FILE_TREE)
150 repo_name, caches.FILE_TREE)
151
151
152 cache_key = caches.compute_key_from_params(
152 cache_key = caches.compute_key_from_params(
153 repo_name, commit_id, f_path)
153 repo_name, commit_id, f_path)
154
154
155 if force:
155 if force:
156 # we want to force recompute of caches
156 # we want to force recompute of caches
157 cache_manager.remove_value(cache_key)
157 cache_manager.remove_value(cache_key)
158
158
159 return cache_manager.get(cache_key, createfunc=_cached_tree)
159 return cache_manager.get(cache_key, createfunc=_cached_tree)
160
160
161 def _get_nodelist_at_commit(self, repo_name, commit_id, f_path):
161 def _get_nodelist_at_commit(self, repo_name, commit_id, f_path):
162 def _cached_nodes():
162 def _cached_nodes():
163 log.debug('Generating cached nodelist for %s, %s, %s',
163 log.debug('Generating cached nodelist for %s, %s, %s',
164 repo_name, commit_id, f_path)
164 repo_name, commit_id, f_path)
165 _d, _f = ScmModel().get_nodes(
165 _d, _f = ScmModel().get_nodes(
166 repo_name, commit_id, f_path, flat=False)
166 repo_name, commit_id, f_path, flat=False)
167 return _d + _f
167 return _d + _f
168
168
169 cache_manager = self.__get_tree_cache_manager(
169 cache_manager = self.__get_tree_cache_manager(
170 repo_name, caches.FILE_SEARCH_TREE_META)
170 repo_name, caches.FILE_SEARCH_TREE_META)
171
171
172 cache_key = caches.compute_key_from_params(
172 cache_key = caches.compute_key_from_params(
173 repo_name, commit_id, f_path)
173 repo_name, commit_id, f_path)
174 return cache_manager.get(cache_key, createfunc=_cached_nodes)
174 return cache_manager.get(cache_key, createfunc=_cached_nodes)
175
175
176 @LoginRequired()
176 @LoginRequired()
177 @HasRepoPermissionAnyDecorator(
177 @HasRepoPermissionAnyDecorator(
178 'repository.read', 'repository.write', 'repository.admin')
178 'repository.read', 'repository.write', 'repository.admin')
179 def index(
179 def index(
180 self, repo_name, revision, f_path, annotate=False, rendered=False):
180 self, repo_name, revision, f_path, annotate=False, rendered=False):
181 commit_id = revision
181 commit_id = revision
182
182
183 # redirect to given commit_id from form if given
183 # redirect to given commit_id from form if given
184 get_commit_id = request.GET.get('at_rev', None)
184 get_commit_id = request.GET.get('at_rev', None)
185 if get_commit_id:
185 if get_commit_id:
186 self.__get_commit_or_redirect(get_commit_id, repo_name)
186 self.__get_commit_or_redirect(get_commit_id, repo_name)
187
187
188 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
188 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
189 c.branch = request.GET.get('branch', None)
189 c.branch = request.GET.get('branch', None)
190 c.f_path = f_path
190 c.f_path = f_path
191 c.annotate = annotate
191 c.annotate = annotate
192 # default is false, but .rst/.md files later are autorendered, we can
192 # default is false, but .rst/.md files later are autorendered, we can
193 # overwrite autorendering by setting this GET flag
193 # overwrite autorendering by setting this GET flag
194 c.renderer = rendered or not request.GET.get('no-render', False)
194 c.renderer = rendered or not request.GET.get('no-render', False)
195
195
196 # prev link
196 # prev link
197 try:
197 try:
198 prev_commit = c.commit.prev(c.branch)
198 prev_commit = c.commit.prev(c.branch)
199 c.prev_commit = prev_commit
199 c.prev_commit = prev_commit
200 c.url_prev = url('files_home', repo_name=c.repo_name,
200 c.url_prev = url('files_home', repo_name=c.repo_name,
201 revision=prev_commit.raw_id, f_path=f_path)
201 revision=prev_commit.raw_id, f_path=f_path)
202 if c.branch:
202 if c.branch:
203 c.url_prev += '?branch=%s' % c.branch
203 c.url_prev += '?branch=%s' % c.branch
204 except (CommitDoesNotExistError, VCSError):
204 except (CommitDoesNotExistError, VCSError):
205 c.url_prev = '#'
205 c.url_prev = '#'
206 c.prev_commit = EmptyCommit()
206 c.prev_commit = EmptyCommit()
207
207
208 # next link
208 # next link
209 try:
209 try:
210 next_commit = c.commit.next(c.branch)
210 next_commit = c.commit.next(c.branch)
211 c.next_commit = next_commit
211 c.next_commit = next_commit
212 c.url_next = url('files_home', repo_name=c.repo_name,
212 c.url_next = url('files_home', repo_name=c.repo_name,
213 revision=next_commit.raw_id, f_path=f_path)
213 revision=next_commit.raw_id, f_path=f_path)
214 if c.branch:
214 if c.branch:
215 c.url_next += '?branch=%s' % c.branch
215 c.url_next += '?branch=%s' % c.branch
216 except (CommitDoesNotExistError, VCSError):
216 except (CommitDoesNotExistError, VCSError):
217 c.url_next = '#'
217 c.url_next = '#'
218 c.next_commit = EmptyCommit()
218 c.next_commit = EmptyCommit()
219
219
220 # files or dirs
220 # files or dirs
221 try:
221 try:
222 c.file = c.commit.get_node(f_path)
222 c.file = c.commit.get_node(f_path)
223 c.file_author = True
223 c.file_author = True
224 c.file_tree = ''
224 c.file_tree = ''
225 if c.file.is_file():
225 if c.file.is_file():
226 c.file_source_page = 'true'
226 c.file_source_page = 'true'
227 c.file_last_commit = c.file.last_commit
227 c.file_last_commit = c.file.last_commit
228 if c.file.size < self.cut_off_limit_file:
228 if c.file.size < self.cut_off_limit_file:
229 if c.annotate: # annotation has precedence over renderer
229 if c.annotate: # annotation has precedence over renderer
230 c.annotated_lines = filenode_as_annotated_lines_tokens(
230 c.annotated_lines = filenode_as_annotated_lines_tokens(
231 c.file
231 c.file
232 )
232 )
233 else:
233 else:
234 c.renderer = (
234 c.renderer = (
235 c.renderer and h.renderer_from_filename(c.file.path)
235 c.renderer and h.renderer_from_filename(c.file.path)
236 )
236 )
237 if not c.renderer:
237 if not c.renderer:
238 c.lines = filenode_as_lines_tokens(c.file)
238 c.lines = filenode_as_lines_tokens(c.file)
239
239
240 c.on_branch_head = self._is_valid_head(
240 c.on_branch_head = self._is_valid_head(
241 commit_id, c.rhodecode_repo)
241 commit_id, c.rhodecode_repo)
242 c.branch_or_raw_id = c.commit.branch or c.commit.raw_id
242 c.branch_or_raw_id = c.commit.branch or c.commit.raw_id
243
243
244 author = c.file_last_commit.author
244 author = c.file_last_commit.author
245 c.authors = [(h.email(author),
245 c.authors = [(h.email(author),
246 h.person(author, 'username_or_name_or_email'))]
246 h.person(author, 'username_or_name_or_email'))]
247 else:
247 else:
248 c.file_source_page = 'false'
248 c.file_source_page = 'false'
249 c.authors = []
249 c.authors = []
250 c.file_tree = self._get_tree_at_commit(
250 c.file_tree = self._get_tree_at_commit(
251 repo_name, c.commit.raw_id, f_path)
251 repo_name, c.commit.raw_id, f_path)
252
252
253 except RepositoryError as e:
253 except RepositoryError as e:
254 h.flash(safe_str(e), category='error')
254 h.flash(safe_str(e), category='error')
255 raise HTTPNotFound()
255 raise HTTPNotFound()
256
256
257 if request.environ.get('HTTP_X_PJAX'):
257 if request.environ.get('HTTP_X_PJAX'):
258 return render('files/files_pjax.mako')
258 return render('files/files_pjax.mako')
259
259
260 return render('files/files.mako')
260 return render('files/files.mako')
261
261
262 @LoginRequired()
262 @LoginRequired()
263 @HasRepoPermissionAnyDecorator(
264 'repository.read', 'repository.write', 'repository.admin')
265 def annotate_previous(self, repo_name, revision, f_path):
266
267 commit_id = revision
268 commit = self.__get_commit_or_redirect(commit_id, repo_name)
269 prev_commit_id = commit.raw_id
270
271 f_path = f_path
272 is_file = False
273 try:
274 _file = commit.get_node(f_path)
275 is_file = _file.is_file()
276 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
277 pass
278
279 if is_file:
280 history = commit.get_file_history(f_path)
281 prev_commit_id = history[1].raw_id \
282 if len(history) > 1 else prev_commit_id
283
284 return redirect(h.url(
285 'files_annotate_home', repo_name=repo_name,
286 revision=prev_commit_id, f_path=f_path))
287
288 @LoginRequired()
263 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
289 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
264 'repository.admin')
290 'repository.admin')
265 @jsonify
291 @jsonify
266 def history(self, repo_name, revision, f_path):
292 def history(self, repo_name, revision, f_path):
267 commit = self.__get_commit_or_redirect(revision, repo_name)
293 commit = self.__get_commit_or_redirect(revision, repo_name)
268 f_path = f_path
294 f_path = f_path
269 _file = commit.get_node(f_path)
295 _file = commit.get_node(f_path)
270 if _file.is_file():
296 if _file.is_file():
271 file_history, _hist = self._get_node_history(commit, f_path)
297 file_history, _hist = self._get_node_history(commit, f_path)
272
298
273 res = []
299 res = []
274 for obj in file_history:
300 for obj in file_history:
275 res.append({
301 res.append({
276 'text': obj[1],
302 'text': obj[1],
277 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
303 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
278 })
304 })
279
305
280 data = {
306 data = {
281 'more': False,
307 'more': False,
282 'results': res
308 'results': res
283 }
309 }
284 return data
310 return data
285
311
286 @LoginRequired()
312 @LoginRequired()
287 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
313 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
288 'repository.admin')
314 'repository.admin')
289 def authors(self, repo_name, revision, f_path):
315 def authors(self, repo_name, revision, f_path):
290 commit = self.__get_commit_or_redirect(revision, repo_name)
316 commit = self.__get_commit_or_redirect(revision, repo_name)
291 file_node = commit.get_node(f_path)
317 file_node = commit.get_node(f_path)
292 if file_node.is_file():
318 if file_node.is_file():
293 c.file_last_commit = file_node.last_commit
319 c.file_last_commit = file_node.last_commit
294 if request.GET.get('annotate') == '1':
320 if request.GET.get('annotate') == '1':
295 # use _hist from annotation if annotation mode is on
321 # use _hist from annotation if annotation mode is on
296 commit_ids = set(x[1] for x in file_node.annotate)
322 commit_ids = set(x[1] for x in file_node.annotate)
297 _hist = (
323 _hist = (
298 c.rhodecode_repo.get_commit(commit_id)
324 c.rhodecode_repo.get_commit(commit_id)
299 for commit_id in commit_ids)
325 for commit_id in commit_ids)
300 else:
326 else:
301 _f_history, _hist = self._get_node_history(commit, f_path)
327 _f_history, _hist = self._get_node_history(commit, f_path)
302 c.file_author = False
328 c.file_author = False
303 c.authors = []
329 c.authors = []
304 for author in set(commit.author for commit in _hist):
330 for author in set(commit.author for commit in _hist):
305 c.authors.append((
331 c.authors.append((
306 h.email(author),
332 h.email(author),
307 h.person(author, 'username_or_name_or_email')))
333 h.person(author, 'username_or_name_or_email')))
308 return render('files/file_authors_box.mako')
334 return render('files/file_authors_box.mako')
309
335
310 @LoginRequired()
336 @LoginRequired()
311 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
337 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
312 'repository.admin')
338 'repository.admin')
313 def rawfile(self, repo_name, revision, f_path):
339 def rawfile(self, repo_name, revision, f_path):
314 """
340 """
315 Action for download as raw
341 Action for download as raw
316 """
342 """
317 commit = self.__get_commit_or_redirect(revision, repo_name)
343 commit = self.__get_commit_or_redirect(revision, repo_name)
318 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
344 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
319
345
320 response.content_disposition = 'attachment; filename=%s' % \
346 response.content_disposition = 'attachment; filename=%s' % \
321 safe_str(f_path.split(Repository.NAME_SEP)[-1])
347 safe_str(f_path.split(Repository.NAME_SEP)[-1])
322
348
323 response.content_type = file_node.mimetype
349 response.content_type = file_node.mimetype
324 charset = self._get_default_encoding()
350 charset = self._get_default_encoding()
325 if charset:
351 if charset:
326 response.charset = charset
352 response.charset = charset
327
353
328 return file_node.content
354 return file_node.content
329
355
330 @LoginRequired()
356 @LoginRequired()
331 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
357 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
332 'repository.admin')
358 'repository.admin')
333 def raw(self, repo_name, revision, f_path):
359 def raw(self, repo_name, revision, f_path):
334 """
360 """
335 Action for show as raw, some mimetypes are "rendered",
361 Action for show as raw, some mimetypes are "rendered",
336 those include images, icons.
362 those include images, icons.
337 """
363 """
338 commit = self.__get_commit_or_redirect(revision, repo_name)
364 commit = self.__get_commit_or_redirect(revision, repo_name)
339 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
365 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
340
366
341 raw_mimetype_mapping = {
367 raw_mimetype_mapping = {
342 # map original mimetype to a mimetype used for "show as raw"
368 # map original mimetype to a mimetype used for "show as raw"
343 # you can also provide a content-disposition to override the
369 # you can also provide a content-disposition to override the
344 # default "attachment" disposition.
370 # default "attachment" disposition.
345 # orig_type: (new_type, new_dispo)
371 # orig_type: (new_type, new_dispo)
346
372
347 # show images inline:
373 # show images inline:
348 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
374 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
349 # for example render an SVG with javascript inside or even render
375 # for example render an SVG with javascript inside or even render
350 # HTML.
376 # HTML.
351 'image/x-icon': ('image/x-icon', 'inline'),
377 'image/x-icon': ('image/x-icon', 'inline'),
352 'image/png': ('image/png', 'inline'),
378 'image/png': ('image/png', 'inline'),
353 'image/gif': ('image/gif', 'inline'),
379 'image/gif': ('image/gif', 'inline'),
354 'image/jpeg': ('image/jpeg', 'inline'),
380 'image/jpeg': ('image/jpeg', 'inline'),
355 }
381 }
356
382
357 mimetype = file_node.mimetype
383 mimetype = file_node.mimetype
358 try:
384 try:
359 mimetype, dispo = raw_mimetype_mapping[mimetype]
385 mimetype, dispo = raw_mimetype_mapping[mimetype]
360 except KeyError:
386 except KeyError:
361 # we don't know anything special about this, handle it safely
387 # we don't know anything special about this, handle it safely
362 if file_node.is_binary:
388 if file_node.is_binary:
363 # do same as download raw for binary files
389 # do same as download raw for binary files
364 mimetype, dispo = 'application/octet-stream', 'attachment'
390 mimetype, dispo = 'application/octet-stream', 'attachment'
365 else:
391 else:
366 # do not just use the original mimetype, but force text/plain,
392 # do not just use the original mimetype, but force text/plain,
367 # otherwise it would serve text/html and that might be unsafe.
393 # otherwise it would serve text/html and that might be unsafe.
368 # Note: underlying vcs library fakes text/plain mimetype if the
394 # Note: underlying vcs library fakes text/plain mimetype if the
369 # mimetype can not be determined and it thinks it is not
395 # mimetype can not be determined and it thinks it is not
370 # binary.This might lead to erroneous text display in some
396 # binary.This might lead to erroneous text display in some
371 # cases, but helps in other cases, like with text files
397 # cases, but helps in other cases, like with text files
372 # without extension.
398 # without extension.
373 mimetype, dispo = 'text/plain', 'inline'
399 mimetype, dispo = 'text/plain', 'inline'
374
400
375 if dispo == 'attachment':
401 if dispo == 'attachment':
376 dispo = 'attachment; filename=%s' % safe_str(
402 dispo = 'attachment; filename=%s' % safe_str(
377 f_path.split(os.sep)[-1])
403 f_path.split(os.sep)[-1])
378
404
379 response.content_disposition = dispo
405 response.content_disposition = dispo
380 response.content_type = mimetype
406 response.content_type = mimetype
381 charset = self._get_default_encoding()
407 charset = self._get_default_encoding()
382 if charset:
408 if charset:
383 response.charset = charset
409 response.charset = charset
384 return file_node.content
410 return file_node.content
385
411
386 @CSRFRequired()
412 @CSRFRequired()
387 @LoginRequired()
413 @LoginRequired()
388 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
414 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
389 def delete(self, repo_name, revision, f_path):
415 def delete(self, repo_name, revision, f_path):
390 commit_id = revision
416 commit_id = revision
391
417
392 repo = c.rhodecode_db_repo
418 repo = c.rhodecode_db_repo
393 if repo.enable_locking and repo.locked[0]:
419 if repo.enable_locking and repo.locked[0]:
394 h.flash(_('This repository has been locked by %s on %s')
420 h.flash(_('This repository has been locked by %s on %s')
395 % (h.person_by_id(repo.locked[0]),
421 % (h.person_by_id(repo.locked[0]),
396 h.format_date(h.time_to_datetime(repo.locked[1]))),
422 h.format_date(h.time_to_datetime(repo.locked[1]))),
397 'warning')
423 'warning')
398 return redirect(h.url('files_home',
424 return redirect(h.url('files_home',
399 repo_name=repo_name, revision='tip'))
425 repo_name=repo_name, revision='tip'))
400
426
401 if not self._is_valid_head(commit_id, repo.scm_instance()):
427 if not self._is_valid_head(commit_id, repo.scm_instance()):
402 h.flash(_('You can only delete files with revision '
428 h.flash(_('You can only delete files with revision '
403 'being a valid branch '), category='warning')
429 'being a valid branch '), category='warning')
404 return redirect(h.url('files_home',
430 return redirect(h.url('files_home',
405 repo_name=repo_name, revision='tip',
431 repo_name=repo_name, revision='tip',
406 f_path=f_path))
432 f_path=f_path))
407
433
408 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
434 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
409 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
435 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
410
436
411 c.default_message = _(
437 c.default_message = _(
412 'Deleted file %s via RhodeCode Enterprise') % (f_path)
438 'Deleted file %s via RhodeCode Enterprise') % (f_path)
413 c.f_path = f_path
439 c.f_path = f_path
414 node_path = f_path
440 node_path = f_path
415 author = c.rhodecode_user.full_contact
441 author = c.rhodecode_user.full_contact
416 message = request.POST.get('message') or c.default_message
442 message = request.POST.get('message') or c.default_message
417 try:
443 try:
418 nodes = {
444 nodes = {
419 node_path: {
445 node_path: {
420 'content': ''
446 'content': ''
421 }
447 }
422 }
448 }
423 self.scm_model.delete_nodes(
449 self.scm_model.delete_nodes(
424 user=c.rhodecode_user.user_id, repo=c.rhodecode_db_repo,
450 user=c.rhodecode_user.user_id, repo=c.rhodecode_db_repo,
425 message=message,
451 message=message,
426 nodes=nodes,
452 nodes=nodes,
427 parent_commit=c.commit,
453 parent_commit=c.commit,
428 author=author,
454 author=author,
429 )
455 )
430
456
431 h.flash(_('Successfully deleted file %s') % f_path,
457 h.flash(_('Successfully deleted file %s') % f_path,
432 category='success')
458 category='success')
433 except Exception:
459 except Exception:
434 msg = _('Error occurred during commit')
460 msg = _('Error occurred during commit')
435 log.exception(msg)
461 log.exception(msg)
436 h.flash(msg, category='error')
462 h.flash(msg, category='error')
437 return redirect(url('changeset_home',
463 return redirect(url('changeset_home',
438 repo_name=c.repo_name, revision='tip'))
464 repo_name=c.repo_name, revision='tip'))
439
465
440 @LoginRequired()
466 @LoginRequired()
441 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
467 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
442 def delete_home(self, repo_name, revision, f_path):
468 def delete_home(self, repo_name, revision, f_path):
443 commit_id = revision
469 commit_id = revision
444
470
445 repo = c.rhodecode_db_repo
471 repo = c.rhodecode_db_repo
446 if repo.enable_locking and repo.locked[0]:
472 if repo.enable_locking and repo.locked[0]:
447 h.flash(_('This repository has been locked by %s on %s')
473 h.flash(_('This repository has been locked by %s on %s')
448 % (h.person_by_id(repo.locked[0]),
474 % (h.person_by_id(repo.locked[0]),
449 h.format_date(h.time_to_datetime(repo.locked[1]))),
475 h.format_date(h.time_to_datetime(repo.locked[1]))),
450 'warning')
476 'warning')
451 return redirect(h.url('files_home',
477 return redirect(h.url('files_home',
452 repo_name=repo_name, revision='tip'))
478 repo_name=repo_name, revision='tip'))
453
479
454 if not self._is_valid_head(commit_id, repo.scm_instance()):
480 if not self._is_valid_head(commit_id, repo.scm_instance()):
455 h.flash(_('You can only delete files with revision '
481 h.flash(_('You can only delete files with revision '
456 'being a valid branch '), category='warning')
482 'being a valid branch '), category='warning')
457 return redirect(h.url('files_home',
483 return redirect(h.url('files_home',
458 repo_name=repo_name, revision='tip',
484 repo_name=repo_name, revision='tip',
459 f_path=f_path))
485 f_path=f_path))
460
486
461 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
487 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
462 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
488 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
463
489
464 c.default_message = _(
490 c.default_message = _(
465 'Deleted file %s via RhodeCode Enterprise') % (f_path)
491 'Deleted file %s via RhodeCode Enterprise') % (f_path)
466 c.f_path = f_path
492 c.f_path = f_path
467
493
468 return render('files/files_delete.mako')
494 return render('files/files_delete.mako')
469
495
470 @CSRFRequired()
496 @CSRFRequired()
471 @LoginRequired()
497 @LoginRequired()
472 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
498 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
473 def edit(self, repo_name, revision, f_path):
499 def edit(self, repo_name, revision, f_path):
474 commit_id = revision
500 commit_id = revision
475
501
476 repo = c.rhodecode_db_repo
502 repo = c.rhodecode_db_repo
477 if repo.enable_locking and repo.locked[0]:
503 if repo.enable_locking and repo.locked[0]:
478 h.flash(_('This repository has been locked by %s on %s')
504 h.flash(_('This repository has been locked by %s on %s')
479 % (h.person_by_id(repo.locked[0]),
505 % (h.person_by_id(repo.locked[0]),
480 h.format_date(h.time_to_datetime(repo.locked[1]))),
506 h.format_date(h.time_to_datetime(repo.locked[1]))),
481 'warning')
507 'warning')
482 return redirect(h.url('files_home',
508 return redirect(h.url('files_home',
483 repo_name=repo_name, revision='tip'))
509 repo_name=repo_name, revision='tip'))
484
510
485 if not self._is_valid_head(commit_id, repo.scm_instance()):
511 if not self._is_valid_head(commit_id, repo.scm_instance()):
486 h.flash(_('You can only edit files with revision '
512 h.flash(_('You can only edit files with revision '
487 'being a valid branch '), category='warning')
513 'being a valid branch '), category='warning')
488 return redirect(h.url('files_home',
514 return redirect(h.url('files_home',
489 repo_name=repo_name, revision='tip',
515 repo_name=repo_name, revision='tip',
490 f_path=f_path))
516 f_path=f_path))
491
517
492 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
518 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
493 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
519 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
494
520
495 if c.file.is_binary:
521 if c.file.is_binary:
496 return redirect(url('files_home', repo_name=c.repo_name,
522 return redirect(url('files_home', repo_name=c.repo_name,
497 revision=c.commit.raw_id, f_path=f_path))
523 revision=c.commit.raw_id, f_path=f_path))
498 c.default_message = _(
524 c.default_message = _(
499 'Edited file %s via RhodeCode Enterprise') % (f_path)
525 'Edited file %s via RhodeCode Enterprise') % (f_path)
500 c.f_path = f_path
526 c.f_path = f_path
501 old_content = c.file.content
527 old_content = c.file.content
502 sl = old_content.splitlines(1)
528 sl = old_content.splitlines(1)
503 first_line = sl[0] if sl else ''
529 first_line = sl[0] if sl else ''
504
530
505 # modes: 0 - Unix, 1 - Mac, 2 - DOS
531 # modes: 0 - Unix, 1 - Mac, 2 - DOS
506 mode = detect_mode(first_line, 0)
532 mode = detect_mode(first_line, 0)
507 content = convert_line_endings(request.POST.get('content', ''), mode)
533 content = convert_line_endings(request.POST.get('content', ''), mode)
508
534
509 message = request.POST.get('message') or c.default_message
535 message = request.POST.get('message') or c.default_message
510 org_f_path = c.file.unicode_path
536 org_f_path = c.file.unicode_path
511 filename = request.POST['filename']
537 filename = request.POST['filename']
512 org_filename = c.file.name
538 org_filename = c.file.name
513
539
514 if content == old_content and filename == org_filename:
540 if content == old_content and filename == org_filename:
515 h.flash(_('No changes'), category='warning')
541 h.flash(_('No changes'), category='warning')
516 return redirect(url('changeset_home', repo_name=c.repo_name,
542 return redirect(url('changeset_home', repo_name=c.repo_name,
517 revision='tip'))
543 revision='tip'))
518 try:
544 try:
519 mapping = {
545 mapping = {
520 org_f_path: {
546 org_f_path: {
521 'org_filename': org_f_path,
547 'org_filename': org_f_path,
522 'filename': os.path.join(c.file.dir_path, filename),
548 'filename': os.path.join(c.file.dir_path, filename),
523 'content': content,
549 'content': content,
524 'lexer': '',
550 'lexer': '',
525 'op': 'mod',
551 'op': 'mod',
526 }
552 }
527 }
553 }
528
554
529 ScmModel().update_nodes(
555 ScmModel().update_nodes(
530 user=c.rhodecode_user.user_id,
556 user=c.rhodecode_user.user_id,
531 repo=c.rhodecode_db_repo,
557 repo=c.rhodecode_db_repo,
532 message=message,
558 message=message,
533 nodes=mapping,
559 nodes=mapping,
534 parent_commit=c.commit,
560 parent_commit=c.commit,
535 )
561 )
536
562
537 h.flash(_('Successfully committed to %s') % f_path,
563 h.flash(_('Successfully committed to %s') % f_path,
538 category='success')
564 category='success')
539 except Exception:
565 except Exception:
540 msg = _('Error occurred during commit')
566 msg = _('Error occurred during commit')
541 log.exception(msg)
567 log.exception(msg)
542 h.flash(msg, category='error')
568 h.flash(msg, category='error')
543 return redirect(url('changeset_home',
569 return redirect(url('changeset_home',
544 repo_name=c.repo_name, revision='tip'))
570 repo_name=c.repo_name, revision='tip'))
545
571
546 @LoginRequired()
572 @LoginRequired()
547 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
573 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
548 def edit_home(self, repo_name, revision, f_path):
574 def edit_home(self, repo_name, revision, f_path):
549 commit_id = revision
575 commit_id = revision
550
576
551 repo = c.rhodecode_db_repo
577 repo = c.rhodecode_db_repo
552 if repo.enable_locking and repo.locked[0]:
578 if repo.enable_locking and repo.locked[0]:
553 h.flash(_('This repository has been locked by %s on %s')
579 h.flash(_('This repository has been locked by %s on %s')
554 % (h.person_by_id(repo.locked[0]),
580 % (h.person_by_id(repo.locked[0]),
555 h.format_date(h.time_to_datetime(repo.locked[1]))),
581 h.format_date(h.time_to_datetime(repo.locked[1]))),
556 'warning')
582 'warning')
557 return redirect(h.url('files_home',
583 return redirect(h.url('files_home',
558 repo_name=repo_name, revision='tip'))
584 repo_name=repo_name, revision='tip'))
559
585
560 if not self._is_valid_head(commit_id, repo.scm_instance()):
586 if not self._is_valid_head(commit_id, repo.scm_instance()):
561 h.flash(_('You can only edit files with revision '
587 h.flash(_('You can only edit files with revision '
562 'being a valid branch '), category='warning')
588 'being a valid branch '), category='warning')
563 return redirect(h.url('files_home',
589 return redirect(h.url('files_home',
564 repo_name=repo_name, revision='tip',
590 repo_name=repo_name, revision='tip',
565 f_path=f_path))
591 f_path=f_path))
566
592
567 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
593 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
568 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
594 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
569
595
570 if c.file.is_binary:
596 if c.file.is_binary:
571 return redirect(url('files_home', repo_name=c.repo_name,
597 return redirect(url('files_home', repo_name=c.repo_name,
572 revision=c.commit.raw_id, f_path=f_path))
598 revision=c.commit.raw_id, f_path=f_path))
573 c.default_message = _(
599 c.default_message = _(
574 'Edited file %s via RhodeCode Enterprise') % (f_path)
600 'Edited file %s via RhodeCode Enterprise') % (f_path)
575 c.f_path = f_path
601 c.f_path = f_path
576
602
577 return render('files/files_edit.mako')
603 return render('files/files_edit.mako')
578
604
579 def _is_valid_head(self, commit_id, repo):
605 def _is_valid_head(self, commit_id, repo):
580 # check if commit is a branch identifier- basically we cannot
606 # check if commit is a branch identifier- basically we cannot
581 # create multiple heads via file editing
607 # create multiple heads via file editing
582 valid_heads = repo.branches.keys() + repo.branches.values()
608 valid_heads = repo.branches.keys() + repo.branches.values()
583
609
584 if h.is_svn(repo) and not repo.is_empty():
610 if h.is_svn(repo) and not repo.is_empty():
585 # Note: Subversion only has one head, we add it here in case there
611 # Note: Subversion only has one head, we add it here in case there
586 # is no branch matched.
612 # is no branch matched.
587 valid_heads.append(repo.get_commit(commit_idx=-1).raw_id)
613 valid_heads.append(repo.get_commit(commit_idx=-1).raw_id)
588
614
589 # check if commit is a branch name or branch hash
615 # check if commit is a branch name or branch hash
590 return commit_id in valid_heads
616 return commit_id in valid_heads
591
617
592 @CSRFRequired()
618 @CSRFRequired()
593 @LoginRequired()
619 @LoginRequired()
594 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
620 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
595 def add(self, repo_name, revision, f_path):
621 def add(self, repo_name, revision, f_path):
596 repo = Repository.get_by_repo_name(repo_name)
622 repo = Repository.get_by_repo_name(repo_name)
597 if repo.enable_locking and repo.locked[0]:
623 if repo.enable_locking and repo.locked[0]:
598 h.flash(_('This repository has been locked by %s on %s')
624 h.flash(_('This repository has been locked by %s on %s')
599 % (h.person_by_id(repo.locked[0]),
625 % (h.person_by_id(repo.locked[0]),
600 h.format_date(h.time_to_datetime(repo.locked[1]))),
626 h.format_date(h.time_to_datetime(repo.locked[1]))),
601 'warning')
627 'warning')
602 return redirect(h.url('files_home',
628 return redirect(h.url('files_home',
603 repo_name=repo_name, revision='tip'))
629 repo_name=repo_name, revision='tip'))
604
630
605 r_post = request.POST
631 r_post = request.POST
606
632
607 c.commit = self.__get_commit_or_redirect(
633 c.commit = self.__get_commit_or_redirect(
608 revision, repo_name, redirect_after=False)
634 revision, repo_name, redirect_after=False)
609 if c.commit is None:
635 if c.commit is None:
610 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
636 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
611 c.default_message = (_('Added file via RhodeCode Enterprise'))
637 c.default_message = (_('Added file via RhodeCode Enterprise'))
612 c.f_path = f_path
638 c.f_path = f_path
613 unix_mode = 0
639 unix_mode = 0
614 content = convert_line_endings(r_post.get('content', ''), unix_mode)
640 content = convert_line_endings(r_post.get('content', ''), unix_mode)
615
641
616 message = r_post.get('message') or c.default_message
642 message = r_post.get('message') or c.default_message
617 filename = r_post.get('filename')
643 filename = r_post.get('filename')
618 location = r_post.get('location', '') # dir location
644 location = r_post.get('location', '') # dir location
619 file_obj = r_post.get('upload_file', None)
645 file_obj = r_post.get('upload_file', None)
620
646
621 if file_obj is not None and hasattr(file_obj, 'filename'):
647 if file_obj is not None and hasattr(file_obj, 'filename'):
622 filename = file_obj.filename
648 filename = file_obj.filename
623 content = file_obj.file
649 content = file_obj.file
624
650
625 if hasattr(content, 'file'):
651 if hasattr(content, 'file'):
626 # non posix systems store real file under file attr
652 # non posix systems store real file under file attr
627 content = content.file
653 content = content.file
628
654
629 # If there's no commit, redirect to repo summary
655 # If there's no commit, redirect to repo summary
630 if type(c.commit) is EmptyCommit:
656 if type(c.commit) is EmptyCommit:
631 redirect_url = "summary_home"
657 redirect_url = "summary_home"
632 else:
658 else:
633 redirect_url = "changeset_home"
659 redirect_url = "changeset_home"
634
660
635 if not filename:
661 if not filename:
636 h.flash(_('No filename'), category='warning')
662 h.flash(_('No filename'), category='warning')
637 return redirect(url(redirect_url, repo_name=c.repo_name,
663 return redirect(url(redirect_url, repo_name=c.repo_name,
638 revision='tip'))
664 revision='tip'))
639
665
640 # extract the location from filename,
666 # extract the location from filename,
641 # allows using foo/bar.txt syntax to create subdirectories
667 # allows using foo/bar.txt syntax to create subdirectories
642 subdir_loc = filename.rsplit('/', 1)
668 subdir_loc = filename.rsplit('/', 1)
643 if len(subdir_loc) == 2:
669 if len(subdir_loc) == 2:
644 location = os.path.join(location, subdir_loc[0])
670 location = os.path.join(location, subdir_loc[0])
645
671
646 # strip all crap out of file, just leave the basename
672 # strip all crap out of file, just leave the basename
647 filename = os.path.basename(filename)
673 filename = os.path.basename(filename)
648 node_path = os.path.join(location, filename)
674 node_path = os.path.join(location, filename)
649 author = c.rhodecode_user.full_contact
675 author = c.rhodecode_user.full_contact
650
676
651 try:
677 try:
652 nodes = {
678 nodes = {
653 node_path: {
679 node_path: {
654 'content': content
680 'content': content
655 }
681 }
656 }
682 }
657 self.scm_model.create_nodes(
683 self.scm_model.create_nodes(
658 user=c.rhodecode_user.user_id,
684 user=c.rhodecode_user.user_id,
659 repo=c.rhodecode_db_repo,
685 repo=c.rhodecode_db_repo,
660 message=message,
686 message=message,
661 nodes=nodes,
687 nodes=nodes,
662 parent_commit=c.commit,
688 parent_commit=c.commit,
663 author=author,
689 author=author,
664 )
690 )
665
691
666 h.flash(_('Successfully committed to %s') % node_path,
692 h.flash(_('Successfully committed to %s') % node_path,
667 category='success')
693 category='success')
668 except NonRelativePathError as e:
694 except NonRelativePathError as e:
669 h.flash(_(
695 h.flash(_(
670 'The location specified must be a relative path and must not '
696 'The location specified must be a relative path and must not '
671 'contain .. in the path'), category='warning')
697 'contain .. in the path'), category='warning')
672 return redirect(url('changeset_home', repo_name=c.repo_name,
698 return redirect(url('changeset_home', repo_name=c.repo_name,
673 revision='tip'))
699 revision='tip'))
674 except (NodeError, NodeAlreadyExistsError) as e:
700 except (NodeError, NodeAlreadyExistsError) as e:
675 h.flash(_(e), category='error')
701 h.flash(_(e), category='error')
676 except Exception:
702 except Exception:
677 msg = _('Error occurred during commit')
703 msg = _('Error occurred during commit')
678 log.exception(msg)
704 log.exception(msg)
679 h.flash(msg, category='error')
705 h.flash(msg, category='error')
680 return redirect(url('changeset_home',
706 return redirect(url('changeset_home',
681 repo_name=c.repo_name, revision='tip'))
707 repo_name=c.repo_name, revision='tip'))
682
708
683 @LoginRequired()
709 @LoginRequired()
684 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
710 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
685 def add_home(self, repo_name, revision, f_path):
711 def add_home(self, repo_name, revision, f_path):
686
712
687 repo = Repository.get_by_repo_name(repo_name)
713 repo = Repository.get_by_repo_name(repo_name)
688 if repo.enable_locking and repo.locked[0]:
714 if repo.enable_locking and repo.locked[0]:
689 h.flash(_('This repository has been locked by %s on %s')
715 h.flash(_('This repository has been locked by %s on %s')
690 % (h.person_by_id(repo.locked[0]),
716 % (h.person_by_id(repo.locked[0]),
691 h.format_date(h.time_to_datetime(repo.locked[1]))),
717 h.format_date(h.time_to_datetime(repo.locked[1]))),
692 'warning')
718 'warning')
693 return redirect(h.url('files_home',
719 return redirect(h.url('files_home',
694 repo_name=repo_name, revision='tip'))
720 repo_name=repo_name, revision='tip'))
695
721
696 c.commit = self.__get_commit_or_redirect(
722 c.commit = self.__get_commit_or_redirect(
697 revision, repo_name, redirect_after=False)
723 revision, repo_name, redirect_after=False)
698 if c.commit is None:
724 if c.commit is None:
699 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
725 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
700 c.default_message = (_('Added file via RhodeCode Enterprise'))
726 c.default_message = (_('Added file via RhodeCode Enterprise'))
701 c.f_path = f_path
727 c.f_path = f_path
702
728
703 return render('files/files_add.mako')
729 return render('files/files_add.mako')
704
730
705 @LoginRequired()
731 @LoginRequired()
706 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
732 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
707 'repository.admin')
733 'repository.admin')
708 def archivefile(self, repo_name, fname):
734 def archivefile(self, repo_name, fname):
709 fileformat = None
735 fileformat = None
710 commit_id = None
736 commit_id = None
711 ext = None
737 ext = None
712 subrepos = request.GET.get('subrepos') == 'true'
738 subrepos = request.GET.get('subrepos') == 'true'
713
739
714 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
740 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
715 archive_spec = fname.split(ext_data[1])
741 archive_spec = fname.split(ext_data[1])
716 if len(archive_spec) == 2 and archive_spec[1] == '':
742 if len(archive_spec) == 2 and archive_spec[1] == '':
717 fileformat = a_type or ext_data[1]
743 fileformat = a_type or ext_data[1]
718 commit_id = archive_spec[0]
744 commit_id = archive_spec[0]
719 ext = ext_data[1]
745 ext = ext_data[1]
720
746
721 dbrepo = RepoModel().get_by_repo_name(repo_name)
747 dbrepo = RepoModel().get_by_repo_name(repo_name)
722 if not dbrepo.enable_downloads:
748 if not dbrepo.enable_downloads:
723 return _('Downloads disabled')
749 return _('Downloads disabled')
724
750
725 try:
751 try:
726 commit = c.rhodecode_repo.get_commit(commit_id)
752 commit = c.rhodecode_repo.get_commit(commit_id)
727 content_type = settings.ARCHIVE_SPECS[fileformat][0]
753 content_type = settings.ARCHIVE_SPECS[fileformat][0]
728 except CommitDoesNotExistError:
754 except CommitDoesNotExistError:
729 return _('Unknown revision %s') % commit_id
755 return _('Unknown revision %s') % commit_id
730 except EmptyRepositoryError:
756 except EmptyRepositoryError:
731 return _('Empty repository')
757 return _('Empty repository')
732 except KeyError:
758 except KeyError:
733 return _('Unknown archive type')
759 return _('Unknown archive type')
734
760
735 # archive cache
761 # archive cache
736 from rhodecode import CONFIG
762 from rhodecode import CONFIG
737
763
738 archive_name = '%s-%s%s%s' % (
764 archive_name = '%s-%s%s%s' % (
739 safe_str(repo_name.replace('/', '_')),
765 safe_str(repo_name.replace('/', '_')),
740 '-sub' if subrepos else '',
766 '-sub' if subrepos else '',
741 safe_str(commit.short_id), ext)
767 safe_str(commit.short_id), ext)
742
768
743 use_cached_archive = False
769 use_cached_archive = False
744 archive_cache_enabled = CONFIG.get(
770 archive_cache_enabled = CONFIG.get(
745 'archive_cache_dir') and not request.GET.get('no_cache')
771 'archive_cache_dir') and not request.GET.get('no_cache')
746
772
747 if archive_cache_enabled:
773 if archive_cache_enabled:
748 # check if we it's ok to write
774 # check if we it's ok to write
749 if not os.path.isdir(CONFIG['archive_cache_dir']):
775 if not os.path.isdir(CONFIG['archive_cache_dir']):
750 os.makedirs(CONFIG['archive_cache_dir'])
776 os.makedirs(CONFIG['archive_cache_dir'])
751 cached_archive_path = os.path.join(
777 cached_archive_path = os.path.join(
752 CONFIG['archive_cache_dir'], archive_name)
778 CONFIG['archive_cache_dir'], archive_name)
753 if os.path.isfile(cached_archive_path):
779 if os.path.isfile(cached_archive_path):
754 log.debug('Found cached archive in %s', cached_archive_path)
780 log.debug('Found cached archive in %s', cached_archive_path)
755 fd, archive = None, cached_archive_path
781 fd, archive = None, cached_archive_path
756 use_cached_archive = True
782 use_cached_archive = True
757 else:
783 else:
758 log.debug('Archive %s is not yet cached', archive_name)
784 log.debug('Archive %s is not yet cached', archive_name)
759
785
760 if not use_cached_archive:
786 if not use_cached_archive:
761 # generate new archive
787 # generate new archive
762 fd, archive = tempfile.mkstemp()
788 fd, archive = tempfile.mkstemp()
763 log.debug('Creating new temp archive in %s' % (archive,))
789 log.debug('Creating new temp archive in %s' % (archive,))
764 try:
790 try:
765 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
791 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
766 except ImproperArchiveTypeError:
792 except ImproperArchiveTypeError:
767 return _('Unknown archive type')
793 return _('Unknown archive type')
768 if archive_cache_enabled:
794 if archive_cache_enabled:
769 # if we generated the archive and we have cache enabled
795 # if we generated the archive and we have cache enabled
770 # let's use this for future
796 # let's use this for future
771 log.debug('Storing new archive in %s' % (cached_archive_path,))
797 log.debug('Storing new archive in %s' % (cached_archive_path,))
772 shutil.move(archive, cached_archive_path)
798 shutil.move(archive, cached_archive_path)
773 archive = cached_archive_path
799 archive = cached_archive_path
774
800
775 def get_chunked_archive(archive):
801 def get_chunked_archive(archive):
776 with open(archive, 'rb') as stream:
802 with open(archive, 'rb') as stream:
777 while True:
803 while True:
778 data = stream.read(16 * 1024)
804 data = stream.read(16 * 1024)
779 if not data:
805 if not data:
780 if fd: # fd means we used temporary file
806 if fd: # fd means we used temporary file
781 os.close(fd)
807 os.close(fd)
782 if not archive_cache_enabled:
808 if not archive_cache_enabled:
783 log.debug('Destroying temp archive %s', archive)
809 log.debug('Destroying temp archive %s', archive)
784 os.remove(archive)
810 os.remove(archive)
785 break
811 break
786 yield data
812 yield data
787
813
788 # store download action
814 # store download action
789 action_logger(user=c.rhodecode_user,
815 action_logger(user=c.rhodecode_user,
790 action='user_downloaded_archive:%s' % archive_name,
816 action='user_downloaded_archive:%s' % archive_name,
791 repo=repo_name, ipaddr=self.ip_addr, commit=True)
817 repo=repo_name, ipaddr=self.ip_addr, commit=True)
792 response.content_disposition = str(
818 response.content_disposition = str(
793 'attachment; filename=%s' % archive_name)
819 'attachment; filename=%s' % archive_name)
794 response.content_type = str(content_type)
820 response.content_type = str(content_type)
795
821
796 return get_chunked_archive(archive)
822 return get_chunked_archive(archive)
797
823
798 @LoginRequired()
824 @LoginRequired()
799 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
825 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
800 'repository.admin')
826 'repository.admin')
801 def diff(self, repo_name, f_path):
827 def diff(self, repo_name, f_path):
802
828
803 c.action = request.GET.get('diff')
829 c.action = request.GET.get('diff')
804 diff1 = request.GET.get('diff1', '')
830 diff1 = request.GET.get('diff1', '')
805 diff2 = request.GET.get('diff2', '')
831 diff2 = request.GET.get('diff2', '')
806
832
807 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
833 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
808
834
809 ignore_whitespace = str2bool(request.GET.get('ignorews'))
835 ignore_whitespace = str2bool(request.GET.get('ignorews'))
810 line_context = request.GET.get('context', 3)
836 line_context = request.GET.get('context', 3)
811
837
812 if not any((diff1, diff2)):
838 if not any((diff1, diff2)):
813 h.flash(
839 h.flash(
814 'Need query parameter "diff1" or "diff2" to generate a diff.',
840 'Need query parameter "diff1" or "diff2" to generate a diff.',
815 category='error')
841 category='error')
816 raise HTTPBadRequest()
842 raise HTTPBadRequest()
817
843
818 if c.action not in ['download', 'raw']:
844 if c.action not in ['download', 'raw']:
819 # redirect to new view if we render diff
845 # redirect to new view if we render diff
820 return redirect(
846 return redirect(
821 url('compare_url', repo_name=repo_name,
847 url('compare_url', repo_name=repo_name,
822 source_ref_type='rev',
848 source_ref_type='rev',
823 source_ref=diff1,
849 source_ref=diff1,
824 target_repo=c.repo_name,
850 target_repo=c.repo_name,
825 target_ref_type='rev',
851 target_ref_type='rev',
826 target_ref=diff2,
852 target_ref=diff2,
827 f_path=f_path))
853 f_path=f_path))
828
854
829 try:
855 try:
830 node1 = self._get_file_node(diff1, path1)
856 node1 = self._get_file_node(diff1, path1)
831 node2 = self._get_file_node(diff2, f_path)
857 node2 = self._get_file_node(diff2, f_path)
832 except (RepositoryError, NodeError):
858 except (RepositoryError, NodeError):
833 log.exception("Exception while trying to get node from repository")
859 log.exception("Exception while trying to get node from repository")
834 return redirect(url(
860 return redirect(url(
835 'files_home', repo_name=c.repo_name, f_path=f_path))
861 'files_home', repo_name=c.repo_name, f_path=f_path))
836
862
837 if all(isinstance(node.commit, EmptyCommit)
863 if all(isinstance(node.commit, EmptyCommit)
838 for node in (node1, node2)):
864 for node in (node1, node2)):
839 raise HTTPNotFound
865 raise HTTPNotFound
840
866
841 c.commit_1 = node1.commit
867 c.commit_1 = node1.commit
842 c.commit_2 = node2.commit
868 c.commit_2 = node2.commit
843
869
844 if c.action == 'download':
870 if c.action == 'download':
845 _diff = diffs.get_gitdiff(node1, node2,
871 _diff = diffs.get_gitdiff(node1, node2,
846 ignore_whitespace=ignore_whitespace,
872 ignore_whitespace=ignore_whitespace,
847 context=line_context)
873 context=line_context)
848 diff = diffs.DiffProcessor(_diff, format='gitdiff')
874 diff = diffs.DiffProcessor(_diff, format='gitdiff')
849
875
850 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
876 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
851 response.content_type = 'text/plain'
877 response.content_type = 'text/plain'
852 response.content_disposition = (
878 response.content_disposition = (
853 'attachment; filename=%s' % (diff_name,)
879 'attachment; filename=%s' % (diff_name,)
854 )
880 )
855 charset = self._get_default_encoding()
881 charset = self._get_default_encoding()
856 if charset:
882 if charset:
857 response.charset = charset
883 response.charset = charset
858 return diff.as_raw()
884 return diff.as_raw()
859
885
860 elif c.action == 'raw':
886 elif c.action == 'raw':
861 _diff = diffs.get_gitdiff(node1, node2,
887 _diff = diffs.get_gitdiff(node1, node2,
862 ignore_whitespace=ignore_whitespace,
888 ignore_whitespace=ignore_whitespace,
863 context=line_context)
889 context=line_context)
864 diff = diffs.DiffProcessor(_diff, format='gitdiff')
890 diff = diffs.DiffProcessor(_diff, format='gitdiff')
865 response.content_type = 'text/plain'
891 response.content_type = 'text/plain'
866 charset = self._get_default_encoding()
892 charset = self._get_default_encoding()
867 if charset:
893 if charset:
868 response.charset = charset
894 response.charset = charset
869 return diff.as_raw()
895 return diff.as_raw()
870
896
871 else:
897 else:
872 return redirect(
898 return redirect(
873 url('compare_url', repo_name=repo_name,
899 url('compare_url', repo_name=repo_name,
874 source_ref_type='rev',
900 source_ref_type='rev',
875 source_ref=diff1,
901 source_ref=diff1,
876 target_repo=c.repo_name,
902 target_repo=c.repo_name,
877 target_ref_type='rev',
903 target_ref_type='rev',
878 target_ref=diff2,
904 target_ref=diff2,
879 f_path=f_path))
905 f_path=f_path))
880
906
881 @LoginRequired()
907 @LoginRequired()
882 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
908 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
883 'repository.admin')
909 'repository.admin')
884 def diff_2way(self, repo_name, f_path):
910 def diff_2way(self, repo_name, f_path):
885 """
911 """
886 Kept only to make OLD links work
912 Kept only to make OLD links work
887 """
913 """
888 diff1 = request.GET.get('diff1', '')
914 diff1 = request.GET.get('diff1', '')
889 diff2 = request.GET.get('diff2', '')
915 diff2 = request.GET.get('diff2', '')
890
916
891 if not any((diff1, diff2)):
917 if not any((diff1, diff2)):
892 h.flash(
918 h.flash(
893 'Need query parameter "diff1" or "diff2" to generate a diff.',
919 'Need query parameter "diff1" or "diff2" to generate a diff.',
894 category='error')
920 category='error')
895 raise HTTPBadRequest()
921 raise HTTPBadRequest()
896
922
897 return redirect(
923 return redirect(
898 url('compare_url', repo_name=repo_name,
924 url('compare_url', repo_name=repo_name,
899 source_ref_type='rev',
925 source_ref_type='rev',
900 source_ref=diff1,
926 source_ref=diff1,
901 target_repo=c.repo_name,
927 target_repo=c.repo_name,
902 target_ref_type='rev',
928 target_ref_type='rev',
903 target_ref=diff2,
929 target_ref=diff2,
904 f_path=f_path,
930 f_path=f_path,
905 diffmode='sideside'))
931 diffmode='sideside'))
906
932
907 def _get_file_node(self, commit_id, f_path):
933 def _get_file_node(self, commit_id, f_path):
908 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
934 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
909 commit = c.rhodecode_repo.get_commit(commit_id=commit_id)
935 commit = c.rhodecode_repo.get_commit(commit_id=commit_id)
910 try:
936 try:
911 node = commit.get_node(f_path)
937 node = commit.get_node(f_path)
912 if node.is_dir():
938 if node.is_dir():
913 raise NodeError('%s path is a %s not a file'
939 raise NodeError('%s path is a %s not a file'
914 % (node, type(node)))
940 % (node, type(node)))
915 except NodeDoesNotExistError:
941 except NodeDoesNotExistError:
916 commit = EmptyCommit(
942 commit = EmptyCommit(
917 commit_id=commit_id,
943 commit_id=commit_id,
918 idx=commit.idx,
944 idx=commit.idx,
919 repo=commit.repository,
945 repo=commit.repository,
920 alias=commit.repository.alias,
946 alias=commit.repository.alias,
921 message=commit.message,
947 message=commit.message,
922 author=commit.author,
948 author=commit.author,
923 date=commit.date)
949 date=commit.date)
924 node = FileNode(f_path, '', commit=commit)
950 node = FileNode(f_path, '', commit=commit)
925 else:
951 else:
926 commit = EmptyCommit(
952 commit = EmptyCommit(
927 repo=c.rhodecode_repo,
953 repo=c.rhodecode_repo,
928 alias=c.rhodecode_repo.alias)
954 alias=c.rhodecode_repo.alias)
929 node = FileNode(f_path, '', commit=commit)
955 node = FileNode(f_path, '', commit=commit)
930 return node
956 return node
931
957
932 def _get_node_history(self, commit, f_path, commits=None):
958 def _get_node_history(self, commit, f_path, commits=None):
933 """
959 """
934 get commit history for given node
960 get commit history for given node
935
961
936 :param commit: commit to calculate history
962 :param commit: commit to calculate history
937 :param f_path: path for node to calculate history for
963 :param f_path: path for node to calculate history for
938 :param commits: if passed don't calculate history and take
964 :param commits: if passed don't calculate history and take
939 commits defined in this list
965 commits defined in this list
940 """
966 """
941 # calculate history based on tip
967 # calculate history based on tip
942 tip = c.rhodecode_repo.get_commit()
968 tip = c.rhodecode_repo.get_commit()
943 if commits is None:
969 if commits is None:
944 pre_load = ["author", "branch"]
970 pre_load = ["author", "branch"]
945 try:
971 try:
946 commits = tip.get_file_history(f_path, pre_load=pre_load)
972 commits = tip.get_file_history(f_path, pre_load=pre_load)
947 except (NodeDoesNotExistError, CommitError):
973 except (NodeDoesNotExistError, CommitError):
948 # this node is not present at tip!
974 # this node is not present at tip!
949 commits = commit.get_file_history(f_path, pre_load=pre_load)
975 commits = commit.get_file_history(f_path, pre_load=pre_load)
950
976
951 history = []
977 history = []
952 commits_group = ([], _("Changesets"))
978 commits_group = ([], _("Changesets"))
953 for commit in commits:
979 for commit in commits:
954 branch = ' (%s)' % commit.branch if commit.branch else ''
980 branch = ' (%s)' % commit.branch if commit.branch else ''
955 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
981 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
956 commits_group[0].append((commit.raw_id, n_desc,))
982 commits_group[0].append((commit.raw_id, n_desc,))
957 history.append(commits_group)
983 history.append(commits_group)
958
984
959 symbolic_reference = self._symbolic_reference
985 symbolic_reference = self._symbolic_reference
960
986
961 if c.rhodecode_repo.alias == 'svn':
987 if c.rhodecode_repo.alias == 'svn':
962 adjusted_f_path = self._adjust_file_path_for_svn(
988 adjusted_f_path = self._adjust_file_path_for_svn(
963 f_path, c.rhodecode_repo)
989 f_path, c.rhodecode_repo)
964 if adjusted_f_path != f_path:
990 if adjusted_f_path != f_path:
965 log.debug(
991 log.debug(
966 'Recognized svn tag or branch in file "%s", using svn '
992 'Recognized svn tag or branch in file "%s", using svn '
967 'specific symbolic references', f_path)
993 'specific symbolic references', f_path)
968 f_path = adjusted_f_path
994 f_path = adjusted_f_path
969 symbolic_reference = self._symbolic_reference_svn
995 symbolic_reference = self._symbolic_reference_svn
970
996
971 branches = self._create_references(
997 branches = self._create_references(
972 c.rhodecode_repo.branches, symbolic_reference, f_path)
998 c.rhodecode_repo.branches, symbolic_reference, f_path)
973 branches_group = (branches, _("Branches"))
999 branches_group = (branches, _("Branches"))
974
1000
975 tags = self._create_references(
1001 tags = self._create_references(
976 c.rhodecode_repo.tags, symbolic_reference, f_path)
1002 c.rhodecode_repo.tags, symbolic_reference, f_path)
977 tags_group = (tags, _("Tags"))
1003 tags_group = (tags, _("Tags"))
978
1004
979 history.append(branches_group)
1005 history.append(branches_group)
980 history.append(tags_group)
1006 history.append(tags_group)
981
1007
982 return history, commits
1008 return history, commits
983
1009
984 def _adjust_file_path_for_svn(self, f_path, repo):
1010 def _adjust_file_path_for_svn(self, f_path, repo):
985 """
1011 """
986 Computes the relative path of `f_path`.
1012 Computes the relative path of `f_path`.
987
1013
988 This is mainly based on prefix matching of the recognized tags and
1014 This is mainly based on prefix matching of the recognized tags and
989 branches in the underlying repository.
1015 branches in the underlying repository.
990 """
1016 """
991 tags_and_branches = itertools.chain(
1017 tags_and_branches = itertools.chain(
992 repo.branches.iterkeys(),
1018 repo.branches.iterkeys(),
993 repo.tags.iterkeys())
1019 repo.tags.iterkeys())
994 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
1020 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
995
1021
996 for name in tags_and_branches:
1022 for name in tags_and_branches:
997 if f_path.startswith(name + '/'):
1023 if f_path.startswith(name + '/'):
998 f_path = vcspath.relpath(f_path, name)
1024 f_path = vcspath.relpath(f_path, name)
999 break
1025 break
1000 return f_path
1026 return f_path
1001
1027
1002 def _create_references(
1028 def _create_references(
1003 self, branches_or_tags, symbolic_reference, f_path):
1029 self, branches_or_tags, symbolic_reference, f_path):
1004 items = []
1030 items = []
1005 for name, commit_id in branches_or_tags.items():
1031 for name, commit_id in branches_or_tags.items():
1006 sym_ref = symbolic_reference(commit_id, name, f_path)
1032 sym_ref = symbolic_reference(commit_id, name, f_path)
1007 items.append((sym_ref, name))
1033 items.append((sym_ref, name))
1008 return items
1034 return items
1009
1035
1010 def _symbolic_reference(self, commit_id, name, f_path):
1036 def _symbolic_reference(self, commit_id, name, f_path):
1011 return commit_id
1037 return commit_id
1012
1038
1013 def _symbolic_reference_svn(self, commit_id, name, f_path):
1039 def _symbolic_reference_svn(self, commit_id, name, f_path):
1014 new_f_path = vcspath.join(name, f_path)
1040 new_f_path = vcspath.join(name, f_path)
1015 return u'%s@%s' % (new_f_path, commit_id)
1041 return u'%s@%s' % (new_f_path, commit_id)
1016
1042
1017 @LoginRequired()
1043 @LoginRequired()
1018 @XHRRequired()
1044 @XHRRequired()
1019 @HasRepoPermissionAnyDecorator(
1045 @HasRepoPermissionAnyDecorator(
1020 'repository.read', 'repository.write', 'repository.admin')
1046 'repository.read', 'repository.write', 'repository.admin')
1021 @jsonify
1047 @jsonify
1022 def nodelist(self, repo_name, revision, f_path):
1048 def nodelist(self, repo_name, revision, f_path):
1023 commit = self.__get_commit_or_redirect(revision, repo_name)
1049 commit = self.__get_commit_or_redirect(revision, repo_name)
1024
1050
1025 metadata = self._get_nodelist_at_commit(
1051 metadata = self._get_nodelist_at_commit(
1026 repo_name, commit.raw_id, f_path)
1052 repo_name, commit.raw_id, f_path)
1027 return {'nodes': metadata}
1053 return {'nodes': metadata}
1028
1054
1029 @LoginRequired()
1055 @LoginRequired()
1030 @XHRRequired()
1056 @XHRRequired()
1031 @HasRepoPermissionAnyDecorator(
1057 @HasRepoPermissionAnyDecorator(
1032 'repository.read', 'repository.write', 'repository.admin')
1058 'repository.read', 'repository.write', 'repository.admin')
1033 def nodetree_full(self, repo_name, commit_id, f_path):
1059 def nodetree_full(self, repo_name, commit_id, f_path):
1034 """
1060 """
1035 Returns rendered html of file tree that contains commit date,
1061 Returns rendered html of file tree that contains commit date,
1036 author, revision for the specified combination of
1062 author, revision for the specified combination of
1037 repo, commit_id and file path
1063 repo, commit_id and file path
1038
1064
1039 :param repo_name: name of the repository
1065 :param repo_name: name of the repository
1040 :param commit_id: commit_id of file tree
1066 :param commit_id: commit_id of file tree
1041 :param f_path: file path of the requested directory
1067 :param f_path: file path of the requested directory
1042 """
1068 """
1043
1069
1044 commit = self.__get_commit_or_redirect(commit_id, repo_name)
1070 commit = self.__get_commit_or_redirect(commit_id, repo_name)
1045 try:
1071 try:
1046 dir_node = commit.get_node(f_path)
1072 dir_node = commit.get_node(f_path)
1047 except RepositoryError as e:
1073 except RepositoryError as e:
1048 return 'error {}'.format(safe_str(e))
1074 return 'error {}'.format(safe_str(e))
1049
1075
1050 if dir_node.is_file():
1076 if dir_node.is_file():
1051 return ''
1077 return ''
1052
1078
1053 c.file = dir_node
1079 c.file = dir_node
1054 c.commit = commit
1080 c.commit = commit
1055
1081
1056 # using force=True here, make a little trick. We flush the cache and
1082 # using force=True here, make a little trick. We flush the cache and
1057 # compute it using the same key as without full_load, so the fully
1083 # compute it using the same key as without full_load, so the fully
1058 # loaded cached tree is now returned instead of partial
1084 # loaded cached tree is now returned instead of partial
1059 return self._get_tree_at_commit(
1085 return self._get_tree_at_commit(
1060 repo_name, commit.raw_id, dir_node.path, full_load=True,
1086 repo_name, commit.raw_id, dir_node.path, full_load=True,
1061 force=True)
1087 force=True)
@@ -1,1135 +1,1136 b''
1 // Default styles
1 // Default styles
2
2
3 .diff-collapse {
3 .diff-collapse {
4 margin: @padding 0;
4 margin: @padding 0;
5 text-align: right;
5 text-align: right;
6 }
6 }
7
7
8 .diff-container {
8 .diff-container {
9 margin-bottom: @space;
9 margin-bottom: @space;
10
10
11 .diffblock {
11 .diffblock {
12 margin-bottom: @space;
12 margin-bottom: @space;
13 }
13 }
14
14
15 &.hidden {
15 &.hidden {
16 display: none;
16 display: none;
17 overflow: hidden;
17 overflow: hidden;
18 }
18 }
19 }
19 }
20
20
21 .compare_view_files {
21 .compare_view_files {
22
22
23 .diff-container {
23 .diff-container {
24
24
25 .diffblock {
25 .diffblock {
26 margin-bottom: 0;
26 margin-bottom: 0;
27 }
27 }
28 }
28 }
29 }
29 }
30
30
31 div.diffblock .sidebyside {
31 div.diffblock .sidebyside {
32 background: #ffffff;
32 background: #ffffff;
33 }
33 }
34
34
35 div.diffblock {
35 div.diffblock {
36 overflow-x: auto;
36 overflow-x: auto;
37 overflow-y: hidden;
37 overflow-y: hidden;
38 clear: both;
38 clear: both;
39 padding: 0px;
39 padding: 0px;
40 background: @grey6;
40 background: @grey6;
41 border: @border-thickness solid @grey5;
41 border: @border-thickness solid @grey5;
42 -webkit-border-radius: @border-radius @border-radius 0px 0px;
42 -webkit-border-radius: @border-radius @border-radius 0px 0px;
43 border-radius: @border-radius @border-radius 0px 0px;
43 border-radius: @border-radius @border-radius 0px 0px;
44
44
45
45
46 .comments-number {
46 .comments-number {
47 float: right;
47 float: right;
48 }
48 }
49
49
50 // BEGIN CODE-HEADER STYLES
50 // BEGIN CODE-HEADER STYLES
51
51
52 .code-header {
52 .code-header {
53 background: @grey6;
53 background: @grey6;
54 padding: 10px 0 10px 0;
54 padding: 10px 0 10px 0;
55 height: auto;
55 height: auto;
56 width: 100%;
56 width: 100%;
57
57
58 .hash {
58 .hash {
59 float: left;
59 float: left;
60 padding: 2px 0 0 2px;
60 padding: 2px 0 0 2px;
61 }
61 }
62
62
63 .date {
63 .date {
64 float: left;
64 float: left;
65 text-transform: uppercase;
65 text-transform: uppercase;
66 padding: 4px 0px 0px 2px;
66 padding: 4px 0px 0px 2px;
67 }
67 }
68
68
69 div {
69 div {
70 margin-left: 4px;
70 margin-left: 4px;
71 }
71 }
72
72
73 div.compare_header {
73 div.compare_header {
74 min-height: 40px;
74 min-height: 40px;
75 margin: 0;
75 margin: 0;
76 padding: 0 @padding;
76 padding: 0 @padding;
77
77
78 .drop-menu {
78 .drop-menu {
79 float:left;
79 float:left;
80 display: block;
80 display: block;
81 margin:0 0 @padding 0;
81 margin:0 0 @padding 0;
82 }
82 }
83
83
84 .compare-label {
84 .compare-label {
85 float: left;
85 float: left;
86 clear: both;
86 clear: both;
87 display: inline-block;
87 display: inline-block;
88 min-width: 5em;
88 min-width: 5em;
89 margin: 0;
89 margin: 0;
90 padding: @button-padding @button-padding @button-padding 0;
90 padding: @button-padding @button-padding @button-padding 0;
91 font-family: @text-semibold;
91 font-family: @text-semibold;
92 }
92 }
93
93
94 .compare-buttons {
94 .compare-buttons {
95 float: left;
95 float: left;
96 margin: 0;
96 margin: 0;
97 padding: 0 0 @padding;
97 padding: 0 0 @padding;
98
98
99 .btn {
99 .btn {
100 margin: 0 @padding 0 0;
100 margin: 0 @padding 0 0;
101 }
101 }
102 }
102 }
103 }
103 }
104
104
105 }
105 }
106
106
107 .parents {
107 .parents {
108 float: left;
108 float: left;
109 width: 100px;
109 width: 100px;
110 font-weight: 400;
110 font-weight: 400;
111 vertical-align: middle;
111 vertical-align: middle;
112 padding: 0px 2px 0px 2px;
112 padding: 0px 2px 0px 2px;
113 background-color: @grey6;
113 background-color: @grey6;
114
114
115 #parent_link {
115 #parent_link {
116 margin: 00px 2px;
116 margin: 00px 2px;
117
117
118 &.double {
118 &.double {
119 margin: 0px 2px;
119 margin: 0px 2px;
120 }
120 }
121
121
122 &.disabled{
122 &.disabled{
123 margin-right: @padding;
123 margin-right: @padding;
124 }
124 }
125 }
125 }
126 }
126 }
127
127
128 .children {
128 .children {
129 float: right;
129 float: right;
130 width: 100px;
130 width: 100px;
131 font-weight: 400;
131 font-weight: 400;
132 vertical-align: middle;
132 vertical-align: middle;
133 text-align: right;
133 text-align: right;
134 padding: 0px 2px 0px 2px;
134 padding: 0px 2px 0px 2px;
135 background-color: @grey6;
135 background-color: @grey6;
136
136
137 #child_link {
137 #child_link {
138 margin: 0px 2px;
138 margin: 0px 2px;
139
139
140 &.double {
140 &.double {
141 margin: 0px 2px;
141 margin: 0px 2px;
142 }
142 }
143
143
144 &.disabled{
144 &.disabled{
145 margin-right: @padding;
145 margin-right: @padding;
146 }
146 }
147 }
147 }
148 }
148 }
149
149
150 .changeset_header {
150 .changeset_header {
151 height: 16px;
151 height: 16px;
152
152
153 & > div{
153 & > div{
154 margin-right: @padding;
154 margin-right: @padding;
155 }
155 }
156 }
156 }
157
157
158 .changeset_file {
158 .changeset_file {
159 text-align: left;
159 text-align: left;
160 float: left;
160 float: left;
161 padding: 0;
161 padding: 0;
162
162
163 a{
163 a{
164 display: inline-block;
164 display: inline-block;
165 margin-right: 0.5em;
165 margin-right: 0.5em;
166 }
166 }
167
167
168 #selected_mode{
168 #selected_mode{
169 margin-left: 0;
169 margin-left: 0;
170 }
170 }
171 }
171 }
172
172
173 .diff-menu-wrapper {
173 .diff-menu-wrapper {
174 float: left;
174 float: left;
175 }
175 }
176
176
177 .diff-menu {
177 .diff-menu {
178 position: absolute;
178 position: absolute;
179 background: none repeat scroll 0 0 #FFFFFF;
179 background: none repeat scroll 0 0 #FFFFFF;
180 border-color: #003367 @grey3 @grey3;
180 border-color: #003367 @grey3 @grey3;
181 border-right: 1px solid @grey3;
181 border-right: 1px solid @grey3;
182 border-style: solid solid solid;
182 border-style: solid solid solid;
183 border-width: @border-thickness;
183 border-width: @border-thickness;
184 box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
184 box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
185 margin-top: 5px;
185 margin-top: 5px;
186 margin-left: 1px;
186 margin-left: 1px;
187 }
187 }
188
188
189 .diff-actions, .editor-actions {
189 .diff-actions, .editor-actions {
190 float: left;
190 float: left;
191
191
192 input{
192 input{
193 margin: 0 0.5em 0 0;
193 margin: 0 0.5em 0 0;
194 }
194 }
195 }
195 }
196
196
197 // END CODE-HEADER STYLES
197 // END CODE-HEADER STYLES
198
198
199 // BEGIN CODE-BODY STYLES
199 // BEGIN CODE-BODY STYLES
200
200
201 .code-body {
201 .code-body {
202 background: white;
202 background: white;
203 padding: 0;
203 padding: 0;
204 background-color: #ffffff;
204 background-color: #ffffff;
205 position: relative;
205 position: relative;
206 max-width: none;
206 max-width: none;
207 box-sizing: border-box;
207 box-sizing: border-box;
208 // TODO: johbo: Parent has overflow: auto, this forces the child here
208 // TODO: johbo: Parent has overflow: auto, this forces the child here
209 // to have the intended size and to scroll. Should be simplified.
209 // to have the intended size and to scroll. Should be simplified.
210 width: 100%;
210 width: 100%;
211 overflow-x: auto;
211 overflow-x: auto;
212 }
212 }
213
213
214 pre.raw {
214 pre.raw {
215 background: white;
215 background: white;
216 color: @grey1;
216 color: @grey1;
217 }
217 }
218 // END CODE-BODY STYLES
218 // END CODE-BODY STYLES
219
219
220 }
220 }
221
221
222
222
223 table.code-difftable {
223 table.code-difftable {
224 border-collapse: collapse;
224 border-collapse: collapse;
225 width: 99%;
225 width: 99%;
226 border-radius: 0px !important;
226 border-radius: 0px !important;
227
227
228 td {
228 td {
229 padding: 0 !important;
229 padding: 0 !important;
230 background: none !important;
230 background: none !important;
231 border: 0 !important;
231 border: 0 !important;
232 }
232 }
233
233
234 .context {
234 .context {
235 background: none repeat scroll 0 0 #DDE7EF;
235 background: none repeat scroll 0 0 #DDE7EF;
236 }
236 }
237
237
238 .add {
238 .add {
239 background: none repeat scroll 0 0 #DDFFDD;
239 background: none repeat scroll 0 0 #DDFFDD;
240
240
241 ins {
241 ins {
242 background: none repeat scroll 0 0 #AAFFAA;
242 background: none repeat scroll 0 0 #AAFFAA;
243 text-decoration: none;
243 text-decoration: none;
244 }
244 }
245 }
245 }
246
246
247 .del {
247 .del {
248 background: none repeat scroll 0 0 #FFDDDD;
248 background: none repeat scroll 0 0 #FFDDDD;
249
249
250 del {
250 del {
251 background: none repeat scroll 0 0 #FFAAAA;
251 background: none repeat scroll 0 0 #FFAAAA;
252 text-decoration: none;
252 text-decoration: none;
253 }
253 }
254 }
254 }
255
255
256 /** LINE NUMBERS **/
256 /** LINE NUMBERS **/
257 .lineno {
257 .lineno {
258 padding-left: 2px !important;
258 padding-left: 2px !important;
259 padding-right: 2px;
259 padding-right: 2px;
260 text-align: right;
260 text-align: right;
261 width: 32px;
261 width: 32px;
262 -moz-user-select: none;
262 -moz-user-select: none;
263 -webkit-user-select: none;
263 -webkit-user-select: none;
264 border-right: @border-thickness solid @grey5 !important;
264 border-right: @border-thickness solid @grey5 !important;
265 border-left: 0px solid #CCC !important;
265 border-left: 0px solid #CCC !important;
266 border-top: 0px solid #CCC !important;
266 border-top: 0px solid #CCC !important;
267 border-bottom: none !important;
267 border-bottom: none !important;
268
268
269 a {
269 a {
270 &:extend(pre);
270 &:extend(pre);
271 text-align: right;
271 text-align: right;
272 padding-right: 2px;
272 padding-right: 2px;
273 cursor: pointer;
273 cursor: pointer;
274 display: block;
274 display: block;
275 width: 32px;
275 width: 32px;
276 }
276 }
277 }
277 }
278
278
279 .context {
279 .context {
280 cursor: auto;
280 cursor: auto;
281 &:extend(pre);
281 &:extend(pre);
282 }
282 }
283
283
284 .lineno-inline {
284 .lineno-inline {
285 background: none repeat scroll 0 0 #FFF !important;
285 background: none repeat scroll 0 0 #FFF !important;
286 padding-left: 2px;
286 padding-left: 2px;
287 padding-right: 2px;
287 padding-right: 2px;
288 text-align: right;
288 text-align: right;
289 width: 30px;
289 width: 30px;
290 -moz-user-select: none;
290 -moz-user-select: none;
291 -webkit-user-select: none;
291 -webkit-user-select: none;
292 }
292 }
293
293
294 /** CODE **/
294 /** CODE **/
295 .code {
295 .code {
296 display: block;
296 display: block;
297 width: 100%;
297 width: 100%;
298
298
299 td {
299 td {
300 margin: 0;
300 margin: 0;
301 padding: 0;
301 padding: 0;
302 }
302 }
303
303
304 pre {
304 pre {
305 margin: 0;
305 margin: 0;
306 padding: 0;
306 padding: 0;
307 margin-left: .5em;
307 margin-left: .5em;
308 }
308 }
309 }
309 }
310 }
310 }
311
311
312
312
313 // Comments
313 // Comments
314
314
315 div.comment:target {
315 div.comment:target {
316 border-left: 6px solid @comment-highlight-color !important;
316 border-left: 6px solid @comment-highlight-color !important;
317 padding-left: 3px;
317 padding-left: 3px;
318 margin-left: -9px;
318 margin-left: -9px;
319 }
319 }
320
320
321 //TODO: anderson: can't get an absolute number out of anything, so had to put the
321 //TODO: anderson: can't get an absolute number out of anything, so had to put the
322 //current values that might change. But to make it clear I put as a calculation
322 //current values that might change. But to make it clear I put as a calculation
323 @comment-max-width: 1065px;
323 @comment-max-width: 1065px;
324 @pr-extra-margin: 34px;
324 @pr-extra-margin: 34px;
325 @pr-border-spacing: 4px;
325 @pr-border-spacing: 4px;
326 @pr-comment-width: @comment-max-width - @pr-extra-margin - @pr-border-spacing;
326 @pr-comment-width: @comment-max-width - @pr-extra-margin - @pr-border-spacing;
327
327
328 // Pull Request
328 // Pull Request
329 .cs_files .code-difftable {
329 .cs_files .code-difftable {
330 border: @border-thickness solid @grey5; //borders only on PRs
330 border: @border-thickness solid @grey5; //borders only on PRs
331
331
332 .comment-inline-form,
332 .comment-inline-form,
333 div.comment {
333 div.comment {
334 width: @pr-comment-width;
334 width: @pr-comment-width;
335 }
335 }
336 }
336 }
337
337
338 // Changeset
338 // Changeset
339 .code-difftable {
339 .code-difftable {
340 .comment-inline-form,
340 .comment-inline-form,
341 div.comment {
341 div.comment {
342 width: @comment-max-width;
342 width: @comment-max-width;
343 }
343 }
344 }
344 }
345
345
346 //Style page
346 //Style page
347 @style-extra-margin: @sidebar-width + (@sidebarpadding * 3) + @padding;
347 @style-extra-margin: @sidebar-width + (@sidebarpadding * 3) + @padding;
348 #style-page .code-difftable{
348 #style-page .code-difftable{
349 .comment-inline-form,
349 .comment-inline-form,
350 div.comment {
350 div.comment {
351 width: @comment-max-width - @style-extra-margin;
351 width: @comment-max-width - @style-extra-margin;
352 }
352 }
353 }
353 }
354
354
355 #context-bar > h2 {
355 #context-bar > h2 {
356 font-size: 20px;
356 font-size: 20px;
357 }
357 }
358
358
359 #context-bar > h2> a {
359 #context-bar > h2> a {
360 font-size: 20px;
360 font-size: 20px;
361 }
361 }
362 // end of defaults
362 // end of defaults
363
363
364 .file_diff_buttons {
364 .file_diff_buttons {
365 padding: 0 0 @padding;
365 padding: 0 0 @padding;
366
366
367 .drop-menu {
367 .drop-menu {
368 float: left;
368 float: left;
369 margin: 0 @padding 0 0;
369 margin: 0 @padding 0 0;
370 }
370 }
371 .btn {
371 .btn {
372 margin: 0 @padding 0 0;
372 margin: 0 @padding 0 0;
373 }
373 }
374 }
374 }
375
375
376 .code-body.textarea.editor {
376 .code-body.textarea.editor {
377 max-width: none;
377 max-width: none;
378 padding: 15px;
378 padding: 15px;
379 }
379 }
380
380
381 td.injected_diff{
381 td.injected_diff{
382 max-width: 1178px;
382 max-width: 1178px;
383 overflow-x: auto;
383 overflow-x: auto;
384 overflow-y: hidden;
384 overflow-y: hidden;
385
385
386 div.diff-container,
386 div.diff-container,
387 div.diffblock{
387 div.diffblock{
388 max-width: 100%;
388 max-width: 100%;
389 }
389 }
390
390
391 div.code-body {
391 div.code-body {
392 max-width: 1124px;
392 max-width: 1124px;
393 overflow-x: auto;
393 overflow-x: auto;
394 overflow-y: hidden;
394 overflow-y: hidden;
395 padding: 0;
395 padding: 0;
396 }
396 }
397 div.diffblock {
397 div.diffblock {
398 border: none;
398 border: none;
399 }
399 }
400
400
401 &.inline-form {
401 &.inline-form {
402 width: 99%
402 width: 99%
403 }
403 }
404 }
404 }
405
405
406
406
407 table.code-difftable {
407 table.code-difftable {
408 width: 100%;
408 width: 100%;
409 }
409 }
410
410
411 /** PYGMENTS COLORING **/
411 /** PYGMENTS COLORING **/
412 div.codeblock {
412 div.codeblock {
413
413
414 // TODO: johbo: Added interim to get rid of the margin around
414 // TODO: johbo: Added interim to get rid of the margin around
415 // Select2 widgets. This needs further cleanup.
415 // Select2 widgets. This needs further cleanup.
416 margin-top: @padding;
416 margin-top: @padding;
417
417
418 overflow: auto;
418 overflow: auto;
419 padding: 0px;
419 padding: 0px;
420 border: @border-thickness solid @grey5;
420 border: @border-thickness solid @grey5;
421 background: @grey6;
421 background: @grey6;
422 .border-radius(@border-radius);
422 .border-radius(@border-radius);
423
423
424 #remove_gist {
424 #remove_gist {
425 float: right;
425 float: right;
426 }
426 }
427
427
428 .author {
428 .author {
429 clear: both;
429 clear: both;
430 vertical-align: middle;
430 vertical-align: middle;
431 font-family: @text-bold;
431 font-family: @text-bold;
432 }
432 }
433
433
434 .btn-mini {
434 .btn-mini {
435 float: left;
435 float: left;
436 margin: 0 5px 0 0;
436 margin: 0 5px 0 0;
437 }
437 }
438
438
439 .code-header {
439 .code-header {
440 padding: @padding;
440 padding: @padding;
441 border-bottom: @border-thickness solid @grey5;
441 border-bottom: @border-thickness solid @grey5;
442
442
443 .rc-user {
443 .rc-user {
444 min-width: 0;
444 min-width: 0;
445 margin-right: .5em;
445 margin-right: .5em;
446 }
446 }
447
447
448 .stats {
448 .stats {
449 clear: both;
449 clear: both;
450 margin: 0 0 @padding 0;
450 margin: 0 0 @padding 0;
451 padding: 0;
451 padding: 0;
452 .left {
452 .left {
453 float: left;
453 float: left;
454 clear: left;
454 clear: left;
455 max-width: 75%;
455 max-width: 75%;
456 margin: 0 0 @padding 0;
456 margin: 0 0 @padding 0;
457
457
458 &.item {
458 &.item {
459 margin-right: @padding;
459 margin-right: @padding;
460 &.last { border-right: none; }
460 &.last { border-right: none; }
461 }
461 }
462 }
462 }
463 .buttons { float: right; }
463 .buttons { float: right; }
464 .author {
464 .author {
465 height: 25px; margin-left: 15px; font-weight: bold;
465 height: 25px; margin-left: 15px; font-weight: bold;
466 }
466 }
467 }
467 }
468
468
469 .commit {
469 .commit {
470 margin: 5px 0 0 26px;
470 margin: 5px 0 0 26px;
471 font-weight: normal;
471 font-weight: normal;
472 white-space: pre-wrap;
472 white-space: pre-wrap;
473 }
473 }
474 }
474 }
475
475
476 .message {
476 .message {
477 position: relative;
477 position: relative;
478 margin: @padding;
478 margin: @padding;
479
479
480 .codeblock-label {
480 .codeblock-label {
481 margin: 0 0 1em 0;
481 margin: 0 0 1em 0;
482 }
482 }
483 }
483 }
484
484
485 .code-body {
485 .code-body {
486 padding: @padding;
486 padding: @padding;
487 background-color: #ffffff;
487 background-color: #ffffff;
488 min-width: 100%;
488 min-width: 100%;
489 box-sizing: border-box;
489 box-sizing: border-box;
490 // TODO: johbo: Parent has overflow: auto, this forces the child here
490 // TODO: johbo: Parent has overflow: auto, this forces the child here
491 // to have the intended size and to scroll. Should be simplified.
491 // to have the intended size and to scroll. Should be simplified.
492 width: 100%;
492 width: 100%;
493 overflow-x: auto;
493 overflow-x: auto;
494 }
494 }
495 }
495 }
496
496
497 .code-highlighttable,
497 .code-highlighttable,
498 div.codeblock {
498 div.codeblock {
499
499
500 &.readme {
500 &.readme {
501 background-color: white;
501 background-color: white;
502 }
502 }
503
503
504 .markdown-block table {
504 .markdown-block table {
505 border-collapse: collapse;
505 border-collapse: collapse;
506
506
507 th,
507 th,
508 td {
508 td {
509 padding: .5em;
509 padding: .5em;
510 border: @border-thickness solid @border-default-color;
510 border: @border-thickness solid @border-default-color;
511 }
511 }
512 }
512 }
513
513
514 table {
514 table {
515 border: 0px;
515 border: 0px;
516 margin: 0;
516 margin: 0;
517 letter-spacing: normal;
517 letter-spacing: normal;
518
518
519
519
520 td {
520 td {
521 border: 0px;
521 border: 0px;
522 vertical-align: top;
522 vertical-align: top;
523 }
523 }
524 }
524 }
525 }
525 }
526
526
527 div.codeblock .code-header .search-path { padding: 0 0 0 10px; }
527 div.codeblock .code-header .search-path { padding: 0 0 0 10px; }
528 div.search-code-body {
528 div.search-code-body {
529 background-color: #ffffff; padding: 5px 0 5px 10px;
529 background-color: #ffffff; padding: 5px 0 5px 10px;
530 pre {
530 pre {
531 .match { background-color: #faffa6;}
531 .match { background-color: #faffa6;}
532 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
532 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
533 }
533 }
534 .code-highlighttable {
534 .code-highlighttable {
535 border-collapse: collapse;
535 border-collapse: collapse;
536
536
537 tr:hover {
537 tr:hover {
538 background: #fafafa;
538 background: #fafafa;
539 }
539 }
540 td.code {
540 td.code {
541 padding-left: 10px;
541 padding-left: 10px;
542 }
542 }
543 td.line {
543 td.line {
544 border-right: 1px solid #ccc !important;
544 border-right: 1px solid #ccc !important;
545 padding-right: 10px;
545 padding-right: 10px;
546 text-align: right;
546 text-align: right;
547 font-family: "Lucida Console",Monaco,monospace;
547 font-family: "Lucida Console",Monaco,monospace;
548 span {
548 span {
549 white-space: pre-wrap;
549 white-space: pre-wrap;
550 color: #666666;
550 color: #666666;
551 }
551 }
552 }
552 }
553 }
553 }
554 }
554 }
555
555
556 div.annotatediv { margin-left: 2px; margin-right: 4px; }
556 div.annotatediv { margin-left: 2px; margin-right: 4px; }
557 .code-highlight {
557 .code-highlight {
558 margin: 0; padding: 0; border-left: @border-thickness solid @grey5;
558 margin: 0; padding: 0; border-left: @border-thickness solid @grey5;
559 pre, .linenodiv pre { padding: 0 5px; margin: 0; }
559 pre, .linenodiv pre { padding: 0 5px; margin: 0; }
560 pre div:target {background-color: @comment-highlight-color !important;}
560 pre div:target {background-color: @comment-highlight-color !important;}
561 }
561 }
562
562
563 .linenos a { text-decoration: none; }
563 .linenos a { text-decoration: none; }
564
564
565 .CodeMirror-selected { background: @rchighlightblue; }
565 .CodeMirror-selected { background: @rchighlightblue; }
566 .CodeMirror-focused .CodeMirror-selected { background: @rchighlightblue; }
566 .CodeMirror-focused .CodeMirror-selected { background: @rchighlightblue; }
567 .CodeMirror ::selection { background: @rchighlightblue; }
567 .CodeMirror ::selection { background: @rchighlightblue; }
568 .CodeMirror ::-moz-selection { background: @rchighlightblue; }
568 .CodeMirror ::-moz-selection { background: @rchighlightblue; }
569
569
570 .code { display: block; border:0px !important; }
570 .code { display: block; border:0px !important; }
571 .code-highlight, /* TODO: dan: merge codehilite into code-highlight */
571 .code-highlight, /* TODO: dan: merge codehilite into code-highlight */
572 .codehilite {
572 .codehilite {
573 .hll { background-color: #ffffcc }
573 .hll { background-color: #ffffcc }
574 .c { color: #408080; font-style: italic } /* Comment */
574 .c { color: #408080; font-style: italic } /* Comment */
575 .err, .codehilite .err { border: @border-thickness solid #FF0000 } /* Error */
575 .err, .codehilite .err { border: @border-thickness solid #FF0000 } /* Error */
576 .k { color: #008000; font-weight: bold } /* Keyword */
576 .k { color: #008000; font-weight: bold } /* Keyword */
577 .o { color: #666666 } /* Operator */
577 .o { color: #666666 } /* Operator */
578 .cm { color: #408080; font-style: italic } /* Comment.Multiline */
578 .cm { color: #408080; font-style: italic } /* Comment.Multiline */
579 .cp { color: #BC7A00 } /* Comment.Preproc */
579 .cp { color: #BC7A00 } /* Comment.Preproc */
580 .c1 { color: #408080; font-style: italic } /* Comment.Single */
580 .c1 { color: #408080; font-style: italic } /* Comment.Single */
581 .cs { color: #408080; font-style: italic } /* Comment.Special */
581 .cs { color: #408080; font-style: italic } /* Comment.Special */
582 .gd { color: #A00000 } /* Generic.Deleted */
582 .gd { color: #A00000 } /* Generic.Deleted */
583 .ge { font-style: italic } /* Generic.Emph */
583 .ge { font-style: italic } /* Generic.Emph */
584 .gr { color: #FF0000 } /* Generic.Error */
584 .gr { color: #FF0000 } /* Generic.Error */
585 .gh { color: #000080; font-weight: bold } /* Generic.Heading */
585 .gh { color: #000080; font-weight: bold } /* Generic.Heading */
586 .gi { color: #00A000 } /* Generic.Inserted */
586 .gi { color: #00A000 } /* Generic.Inserted */
587 .go { color: #808080 } /* Generic.Output */
587 .go { color: #808080 } /* Generic.Output */
588 .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
588 .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
589 .gs { font-weight: bold } /* Generic.Strong */
589 .gs { font-weight: bold } /* Generic.Strong */
590 .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
590 .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
591 .gt { color: #0040D0 } /* Generic.Traceback */
591 .gt { color: #0040D0 } /* Generic.Traceback */
592 .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
592 .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
593 .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
593 .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
594 .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
594 .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
595 .kp { color: #008000 } /* Keyword.Pseudo */
595 .kp { color: #008000 } /* Keyword.Pseudo */
596 .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
596 .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
597 .kt { color: #B00040 } /* Keyword.Type */
597 .kt { color: #B00040 } /* Keyword.Type */
598 .m { color: #666666 } /* Literal.Number */
598 .m { color: #666666 } /* Literal.Number */
599 .s { color: #BA2121 } /* Literal.String */
599 .s { color: #BA2121 } /* Literal.String */
600 .na { color: #7D9029 } /* Name.Attribute */
600 .na { color: #7D9029 } /* Name.Attribute */
601 .nb { color: #008000 } /* Name.Builtin */
601 .nb { color: #008000 } /* Name.Builtin */
602 .nc { color: #0000FF; font-weight: bold } /* Name.Class */
602 .nc { color: #0000FF; font-weight: bold } /* Name.Class */
603 .no { color: #880000 } /* Name.Constant */
603 .no { color: #880000 } /* Name.Constant */
604 .nd { color: #AA22FF } /* Name.Decorator */
604 .nd { color: #AA22FF } /* Name.Decorator */
605 .ni { color: #999999; font-weight: bold } /* Name.Entity */
605 .ni { color: #999999; font-weight: bold } /* Name.Entity */
606 .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
606 .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
607 .nf { color: #0000FF } /* Name.Function */
607 .nf { color: #0000FF } /* Name.Function */
608 .nl { color: #A0A000 } /* Name.Label */
608 .nl { color: #A0A000 } /* Name.Label */
609 .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
609 .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
610 .nt { color: #008000; font-weight: bold } /* Name.Tag */
610 .nt { color: #008000; font-weight: bold } /* Name.Tag */
611 .nv { color: #19177C } /* Name.Variable */
611 .nv { color: #19177C } /* Name.Variable */
612 .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
612 .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
613 .w { color: #bbbbbb } /* Text.Whitespace */
613 .w { color: #bbbbbb } /* Text.Whitespace */
614 .mf { color: #666666 } /* Literal.Number.Float */
614 .mf { color: #666666 } /* Literal.Number.Float */
615 .mh { color: #666666 } /* Literal.Number.Hex */
615 .mh { color: #666666 } /* Literal.Number.Hex */
616 .mi { color: #666666 } /* Literal.Number.Integer */
616 .mi { color: #666666 } /* Literal.Number.Integer */
617 .mo { color: #666666 } /* Literal.Number.Oct */
617 .mo { color: #666666 } /* Literal.Number.Oct */
618 .sb { color: #BA2121 } /* Literal.String.Backtick */
618 .sb { color: #BA2121 } /* Literal.String.Backtick */
619 .sc { color: #BA2121 } /* Literal.String.Char */
619 .sc { color: #BA2121 } /* Literal.String.Char */
620 .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
620 .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
621 .s2 { color: #BA2121 } /* Literal.String.Double */
621 .s2 { color: #BA2121 } /* Literal.String.Double */
622 .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
622 .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
623 .sh { color: #BA2121 } /* Literal.String.Heredoc */
623 .sh { color: #BA2121 } /* Literal.String.Heredoc */
624 .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
624 .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
625 .sx { color: #008000 } /* Literal.String.Other */
625 .sx { color: #008000 } /* Literal.String.Other */
626 .sr { color: #BB6688 } /* Literal.String.Regex */
626 .sr { color: #BB6688 } /* Literal.String.Regex */
627 .s1 { color: #BA2121 } /* Literal.String.Single */
627 .s1 { color: #BA2121 } /* Literal.String.Single */
628 .ss { color: #19177C } /* Literal.String.Symbol */
628 .ss { color: #19177C } /* Literal.String.Symbol */
629 .bp { color: #008000 } /* Name.Builtin.Pseudo */
629 .bp { color: #008000 } /* Name.Builtin.Pseudo */
630 .vc { color: #19177C } /* Name.Variable.Class */
630 .vc { color: #19177C } /* Name.Variable.Class */
631 .vg { color: #19177C } /* Name.Variable.Global */
631 .vg { color: #19177C } /* Name.Variable.Global */
632 .vi { color: #19177C } /* Name.Variable.Instance */
632 .vi { color: #19177C } /* Name.Variable.Instance */
633 .il { color: #666666 } /* Literal.Number.Integer.Long */
633 .il { color: #666666 } /* Literal.Number.Integer.Long */
634 }
634 }
635
635
636 /* customized pre blocks for markdown/rst */
636 /* customized pre blocks for markdown/rst */
637 pre.literal-block, .codehilite pre{
637 pre.literal-block, .codehilite pre{
638 padding: @padding;
638 padding: @padding;
639 border: 1px solid @grey6;
639 border: 1px solid @grey6;
640 .border-radius(@border-radius);
640 .border-radius(@border-radius);
641 background-color: @grey7;
641 background-color: @grey7;
642 }
642 }
643
643
644
644
645 /* START NEW CODE BLOCK CSS */
645 /* START NEW CODE BLOCK CSS */
646
646
647 @cb-line-height: 18px;
647 @cb-line-height: 18px;
648 @cb-line-code-padding: 10px;
648 @cb-line-code-padding: 10px;
649 @cb-text-padding: 5px;
649 @cb-text-padding: 5px;
650
650
651 @pill-padding: 2px 7px;
651 @pill-padding: 2px 7px;
652
652
653 input.filediff-collapse-state {
653 input.filediff-collapse-state {
654 display: none;
654 display: none;
655
655
656 &:checked + .filediff { /* file diff is collapsed */
656 &:checked + .filediff { /* file diff is collapsed */
657 .cb {
657 .cb {
658 display: none
658 display: none
659 }
659 }
660 .filediff-collapse-indicator {
660 .filediff-collapse-indicator {
661 width: 0;
661 width: 0;
662 height: 0;
662 height: 0;
663 border-style: solid;
663 border-style: solid;
664 border-width: 6.5px 0 6.5px 11.3px;
664 border-width: 6.5px 0 6.5px 11.3px;
665 border-color: transparent transparent transparent #ccc;
665 border-color: transparent transparent transparent #ccc;
666 }
666 }
667 .filediff-menu {
667 .filediff-menu {
668 display: none;
668 display: none;
669 }
669 }
670 margin: 10px 0 0 0;
670 margin: 10px 0 0 0;
671 }
671 }
672
672
673 &+ .filediff { /* file diff is expanded */
673 &+ .filediff { /* file diff is expanded */
674 .filediff-collapse-indicator {
674 .filediff-collapse-indicator {
675 width: 0;
675 width: 0;
676 height: 0;
676 height: 0;
677 border-style: solid;
677 border-style: solid;
678 border-width: 11.3px 6.5px 0 6.5px;
678 border-width: 11.3px 6.5px 0 6.5px;
679 border-color: #ccc transparent transparent transparent;
679 border-color: #ccc transparent transparent transparent;
680 }
680 }
681 .filediff-menu {
681 .filediff-menu {
682 display: block;
682 display: block;
683 }
683 }
684 margin: 10px 0;
684 margin: 10px 0;
685 &:nth-child(2) {
685 &:nth-child(2) {
686 margin: 0;
686 margin: 0;
687 }
687 }
688 }
688 }
689 }
689 }
690 .cs_files {
690 .cs_files {
691 clear: both;
691 clear: both;
692 }
692 }
693
693
694 .diffset-menu {
694 .diffset-menu {
695 margin-bottom: 20px;
695 margin-bottom: 20px;
696 }
696 }
697 .diffset {
697 .diffset {
698 margin: 20px auto;
698 margin: 20px auto;
699 .diffset-heading {
699 .diffset-heading {
700 border: 1px solid @grey5;
700 border: 1px solid @grey5;
701 margin-bottom: -1px;
701 margin-bottom: -1px;
702 // margin-top: 20px;
702 // margin-top: 20px;
703 h2 {
703 h2 {
704 margin: 0;
704 margin: 0;
705 line-height: 38px;
705 line-height: 38px;
706 padding-left: 10px;
706 padding-left: 10px;
707 }
707 }
708 .btn {
708 .btn {
709 margin: 0;
709 margin: 0;
710 }
710 }
711 background: @grey6;
711 background: @grey6;
712 display: block;
712 display: block;
713 padding: 5px;
713 padding: 5px;
714 }
714 }
715 .diffset-heading-warning {
715 .diffset-heading-warning {
716 background: @alert3-inner;
716 background: @alert3-inner;
717 border: 1px solid @alert3;
717 border: 1px solid @alert3;
718 }
718 }
719 &.diffset-comments-disabled {
719 &.diffset-comments-disabled {
720 .cb-comment-box-opener, .comment-inline-form, .cb-comment-add-button {
720 .cb-comment-box-opener, .comment-inline-form, .cb-comment-add-button {
721 display: none !important;
721 display: none !important;
722 }
722 }
723 }
723 }
724 }
724 }
725
725
726 .pill {
726 .pill {
727 display: block;
727 display: block;
728 float: left;
728 float: left;
729 padding: @pill-padding;
729 padding: @pill-padding;
730 }
730 }
731 .pill-group {
731 .pill-group {
732 .pill {
732 .pill {
733 opacity: .8;
733 opacity: .8;
734 &:first-child {
734 &:first-child {
735 border-radius: @border-radius 0 0 @border-radius;
735 border-radius: @border-radius 0 0 @border-radius;
736 }
736 }
737 &:last-child {
737 &:last-child {
738 border-radius: 0 @border-radius @border-radius 0;
738 border-radius: 0 @border-radius @border-radius 0;
739 }
739 }
740 &:only-child {
740 &:only-child {
741 border-radius: @border-radius;
741 border-radius: @border-radius;
742 }
742 }
743 }
743 }
744 }
744 }
745
745
746 /* Main comments*/
746 /* Main comments*/
747 #comments {
747 #comments {
748 .comment-selected {
748 .comment-selected {
749 border-left: 6px solid @comment-highlight-color;
749 border-left: 6px solid @comment-highlight-color;
750 padding-left: 3px;
750 padding-left: 3px;
751 margin-left: -9px;
751 margin-left: -9px;
752 }
752 }
753 }
753 }
754
754
755 .filediff {
755 .filediff {
756 border: 1px solid @grey5;
756 border: 1px solid @grey5;
757
757
758 /* START OVERRIDES */
758 /* START OVERRIDES */
759 .code-highlight {
759 .code-highlight {
760 border: none; // TODO: remove this border from the global
760 border: none; // TODO: remove this border from the global
761 // .code-highlight, it doesn't belong there
761 // .code-highlight, it doesn't belong there
762 }
762 }
763 label {
763 label {
764 margin: 0; // TODO: remove this margin definition from global label
764 margin: 0; // TODO: remove this margin definition from global label
765 // it doesn't belong there - if margin on labels
765 // it doesn't belong there - if margin on labels
766 // are needed for a form they should be defined
766 // are needed for a form they should be defined
767 // in the form's class
767 // in the form's class
768 }
768 }
769 /* END OVERRIDES */
769 /* END OVERRIDES */
770
770
771 * {
771 * {
772 box-sizing: border-box;
772 box-sizing: border-box;
773 }
773 }
774 .filediff-anchor {
774 .filediff-anchor {
775 visibility: hidden;
775 visibility: hidden;
776 }
776 }
777 &:hover {
777 &:hover {
778 .filediff-anchor {
778 .filediff-anchor {
779 visibility: visible;
779 visibility: visible;
780 }
780 }
781 }
781 }
782
782
783 .filediff-collapse-indicator {
783 .filediff-collapse-indicator {
784 border-style: solid;
784 border-style: solid;
785 float: left;
785 float: left;
786 margin: 4px 0px 0 0;
786 margin: 4px 0px 0 0;
787 cursor: pointer;
787 cursor: pointer;
788 }
788 }
789
789
790 .filediff-heading {
790 .filediff-heading {
791 background: @grey7;
791 background: @grey7;
792 cursor: pointer;
792 cursor: pointer;
793 display: block;
793 display: block;
794 padding: 5px 10px;
794 padding: 5px 10px;
795 }
795 }
796 .filediff-heading:after {
796 .filediff-heading:after {
797 content: "";
797 content: "";
798 display: table;
798 display: table;
799 clear: both;
799 clear: both;
800 }
800 }
801 .filediff-heading:hover {
801 .filediff-heading:hover {
802 background: #e1e9f4 !important;
802 background: #e1e9f4 !important;
803 }
803 }
804
804
805 .filediff-menu {
805 .filediff-menu {
806 float: right;
806 float: right;
807 text-align: right;
807 text-align: right;
808 padding: 5px 5px 5px 0px;
808 padding: 5px 5px 5px 0px;
809
809
810 &> a,
810 &> a,
811 &> span {
811 &> span {
812 padding: 1px;
812 padding: 1px;
813 }
813 }
814 }
814 }
815
815
816 .pill {
816 .pill {
817 &[op="name"] {
817 &[op="name"] {
818 background: none;
818 background: none;
819 color: @grey2;
819 color: @grey2;
820 opacity: 1;
820 opacity: 1;
821 color: white;
821 color: white;
822 }
822 }
823 &[op="limited"] {
823 &[op="limited"] {
824 background: @grey2;
824 background: @grey2;
825 color: white;
825 color: white;
826 }
826 }
827 &[op="binary"] {
827 &[op="binary"] {
828 background: @color7;
828 background: @color7;
829 color: white;
829 color: white;
830 }
830 }
831 &[op="modified"] {
831 &[op="modified"] {
832 background: @alert1;
832 background: @alert1;
833 color: white;
833 color: white;
834 }
834 }
835 &[op="renamed"] {
835 &[op="renamed"] {
836 background: @color4;
836 background: @color4;
837 color: white;
837 color: white;
838 }
838 }
839 &[op="mode"] {
839 &[op="mode"] {
840 background: @grey3;
840 background: @grey3;
841 color: white;
841 color: white;
842 }
842 }
843 &[op="symlink"] {
843 &[op="symlink"] {
844 background: @color8;
844 background: @color8;
845 color: white;
845 color: white;
846 }
846 }
847
847
848 &[op="added"] { /* added lines */
848 &[op="added"] { /* added lines */
849 background: @alert1;
849 background: @alert1;
850 color: white;
850 color: white;
851 }
851 }
852 &[op="deleted"] { /* deleted lines */
852 &[op="deleted"] { /* deleted lines */
853 background: @alert2;
853 background: @alert2;
854 color: white;
854 color: white;
855 }
855 }
856
856
857 &[op="created"] { /* created file */
857 &[op="created"] { /* created file */
858 background: @alert1;
858 background: @alert1;
859 color: white;
859 color: white;
860 }
860 }
861 &[op="removed"] { /* deleted file */
861 &[op="removed"] { /* deleted file */
862 background: @color5;
862 background: @color5;
863 color: white;
863 color: white;
864 }
864 }
865 }
865 }
866
866
867 .filediff-collapse-button, .filediff-expand-button {
867 .filediff-collapse-button, .filediff-expand-button {
868 cursor: pointer;
868 cursor: pointer;
869 }
869 }
870 .filediff-collapse-button {
870 .filediff-collapse-button {
871 display: inline;
871 display: inline;
872 }
872 }
873 .filediff-expand-button {
873 .filediff-expand-button {
874 display: none;
874 display: none;
875 }
875 }
876 .filediff-collapsed .filediff-collapse-button {
876 .filediff-collapsed .filediff-collapse-button {
877 display: none;
877 display: none;
878 }
878 }
879 .filediff-collapsed .filediff-expand-button {
879 .filediff-collapsed .filediff-expand-button {
880 display: inline;
880 display: inline;
881 }
881 }
882
882
883 /**** COMMENTS ****/
883 /**** COMMENTS ****/
884
884
885 .filediff-menu {
885 .filediff-menu {
886 .show-comment-button {
886 .show-comment-button {
887 display: none;
887 display: none;
888 }
888 }
889 }
889 }
890 &.hide-comments {
890 &.hide-comments {
891 .inline-comments {
891 .inline-comments {
892 display: none;
892 display: none;
893 }
893 }
894 .filediff-menu {
894 .filediff-menu {
895 .show-comment-button {
895 .show-comment-button {
896 display: inline;
896 display: inline;
897 }
897 }
898 .hide-comment-button {
898 .hide-comment-button {
899 display: none;
899 display: none;
900 }
900 }
901 }
901 }
902 }
902 }
903
903
904 .hide-line-comments {
904 .hide-line-comments {
905 .inline-comments {
905 .inline-comments {
906 display: none;
906 display: none;
907 }
907 }
908 }
908 }
909
909
910 /**** END COMMENTS ****/
910 /**** END COMMENTS ****/
911
911
912 }
912 }
913
913
914 .filediff-outdated {
914 .filediff-outdated {
915 padding: 8px 0;
915 padding: 8px 0;
916
916
917 .filediff-heading {
917 .filediff-heading {
918 opacity: .5;
918 opacity: .5;
919 }
919 }
920 }
920 }
921
921
922 table.cb {
922 table.cb {
923 width: 100%;
923 width: 100%;
924 border-collapse: collapse;
924 border-collapse: collapse;
925
925
926 .cb-text {
926 .cb-text {
927 padding: @cb-text-padding;
927 padding: @cb-text-padding;
928 }
928 }
929 .cb-hunk {
929 .cb-hunk {
930 padding: @cb-text-padding;
930 padding: @cb-text-padding;
931 }
931 }
932 .cb-expand {
932 .cb-expand {
933 display: none;
933 display: none;
934 }
934 }
935 .cb-collapse {
935 .cb-collapse {
936 display: inline;
936 display: inline;
937 }
937 }
938 &.cb-collapsed {
938 &.cb-collapsed {
939 .cb-line {
939 .cb-line {
940 display: none;
940 display: none;
941 }
941 }
942 .cb-expand {
942 .cb-expand {
943 display: inline;
943 display: inline;
944 }
944 }
945 .cb-collapse {
945 .cb-collapse {
946 display: none;
946 display: none;
947 }
947 }
948 }
948 }
949
949
950 /* intentionally general selector since .cb-line-selected must override it
950 /* intentionally general selector since .cb-line-selected must override it
951 and they both use !important since the td itself may have a random color
951 and they both use !important since the td itself may have a random color
952 generated by annotation blocks. TLDR: if you change it, make sure
952 generated by annotation blocks. TLDR: if you change it, make sure
953 annotated block selection and line selection in file view still work */
953 annotated block selection and line selection in file view still work */
954 .cb-line-fresh .cb-content {
954 .cb-line-fresh .cb-content {
955 background: white !important;
955 background: white !important;
956 }
956 }
957 .cb-warning {
957 .cb-warning {
958 background: #fff4dd;
958 background: #fff4dd;
959 }
959 }
960
960
961 &.cb-diff-sideside {
961 &.cb-diff-sideside {
962 td {
962 td {
963 &.cb-content {
963 &.cb-content {
964 width: 50%;
964 width: 50%;
965 }
965 }
966 }
966 }
967 }
967 }
968
968
969 tr {
969 tr {
970 &.cb-annotate {
970 &.cb-annotate {
971 border-top: 1px solid #eee;
971 border-top: 1px solid #eee;
972 }
972 }
973
973
974 &.cb-hunk {
974 &.cb-hunk {
975 font-family: @font-family-monospace;
975 font-family: @font-family-monospace;
976 color: rgba(0, 0, 0, 0.3);
976 color: rgba(0, 0, 0, 0.3);
977
977
978 td {
978 td {
979 &:first-child {
979 &:first-child {
980 background: #edf2f9;
980 background: #edf2f9;
981 }
981 }
982 &:last-child {
982 &:last-child {
983 background: #f4f7fb;
983 background: #f4f7fb;
984 }
984 }
985 }
985 }
986 }
986 }
987 }
987 }
988
988
989
989
990 td {
990 td {
991 vertical-align: top;
991 vertical-align: top;
992 padding: 0;
992 padding: 0;
993
993
994 &.cb-content {
994 &.cb-content {
995 font-size: 12.35px;
995 font-size: 12.35px;
996
996
997 &.cb-line-selected .cb-code {
997 &.cb-line-selected .cb-code {
998 background: @comment-highlight-color !important;
998 background: @comment-highlight-color !important;
999 }
999 }
1000
1000
1001 span.cb-code {
1001 span.cb-code {
1002 line-height: @cb-line-height;
1002 line-height: @cb-line-height;
1003 padding-left: @cb-line-code-padding;
1003 padding-left: @cb-line-code-padding;
1004 padding-right: @cb-line-code-padding;
1004 padding-right: @cb-line-code-padding;
1005 display: block;
1005 display: block;
1006 white-space: pre-wrap;
1006 white-space: pre-wrap;
1007 font-family: @font-family-monospace;
1007 font-family: @font-family-monospace;
1008 word-break: break-all;
1008 word-break: break-all;
1009 .nonl {
1009 .nonl {
1010 color: @color5;
1010 color: @color5;
1011 }
1011 }
1012 }
1012 }
1013
1013
1014 &> button.cb-comment-box-opener {
1014 &> button.cb-comment-box-opener {
1015
1015
1016 padding: 2px 2px 1px 3px;
1016 padding: 2px 2px 1px 3px;
1017 margin-left: -6px;
1017 margin-left: -6px;
1018 margin-top: -1px;
1018 margin-top: -1px;
1019
1019
1020 border-radius: @border-radius;
1020 border-radius: @border-radius;
1021 position: absolute;
1021 position: absolute;
1022 display: none;
1022 display: none;
1023 }
1023 }
1024 .cb-comment {
1024 .cb-comment {
1025 margin-top: 10px;
1025 margin-top: 10px;
1026 white-space: normal;
1026 white-space: normal;
1027 }
1027 }
1028 }
1028 }
1029 &:hover {
1029 &:hover {
1030 button.cb-comment-box-opener {
1030 button.cb-comment-box-opener {
1031 display: block;
1031 display: block;
1032 }
1032 }
1033 &+ td button.cb-comment-box-opener {
1033 &+ td button.cb-comment-box-opener {
1034 display: block
1034 display: block
1035 }
1035 }
1036 }
1036 }
1037
1037
1038 &.cb-data {
1038 &.cb-data {
1039 text-align: right;
1039 text-align: right;
1040 width: 30px;
1040 width: 30px;
1041 font-family: @font-family-monospace;
1041 font-family: @font-family-monospace;
1042
1042
1043 .icon-comment {
1043 .icon-comment {
1044 cursor: pointer;
1044 cursor: pointer;
1045 }
1045 }
1046 &.cb-line-selected > div {
1046 &.cb-line-selected > div {
1047 display: block;
1047 display: block;
1048 background: @comment-highlight-color !important;
1048 background: @comment-highlight-color !important;
1049 line-height: @cb-line-height;
1049 line-height: @cb-line-height;
1050 color: rgba(0, 0, 0, 0.3);
1050 color: rgba(0, 0, 0, 0.3);
1051 }
1051 }
1052 }
1052 }
1053
1053
1054 &.cb-lineno {
1054 &.cb-lineno {
1055 padding: 0;
1055 padding: 0;
1056 width: 50px;
1056 width: 50px;
1057 color: rgba(0, 0, 0, 0.3);
1057 color: rgba(0, 0, 0, 0.3);
1058 text-align: right;
1058 text-align: right;
1059 border-right: 1px solid #eee;
1059 border-right: 1px solid #eee;
1060 font-family: @font-family-monospace;
1060 font-family: @font-family-monospace;
1061
1061
1062 a::before {
1062 a::before {
1063 content: attr(data-line-no);
1063 content: attr(data-line-no);
1064 }
1064 }
1065 &.cb-line-selected a {
1065 &.cb-line-selected a {
1066 background: @comment-highlight-color !important;
1066 background: @comment-highlight-color !important;
1067 }
1067 }
1068
1068
1069 a {
1069 a {
1070 display: block;
1070 display: block;
1071 padding-right: @cb-line-code-padding;
1071 padding-right: @cb-line-code-padding;
1072 padding-left: @cb-line-code-padding;
1072 padding-left: @cb-line-code-padding;
1073 line-height: @cb-line-height;
1073 line-height: @cb-line-height;
1074 color: rgba(0, 0, 0, 0.3);
1074 color: rgba(0, 0, 0, 0.3);
1075 }
1075 }
1076 }
1076 }
1077
1077
1078 &.cb-empty {
1078 &.cb-empty {
1079 background: @grey7;
1079 background: @grey7;
1080 }
1080 }
1081
1081
1082 ins {
1082 ins {
1083 color: black;
1083 color: black;
1084 background: #a6f3a6;
1084 background: #a6f3a6;
1085 text-decoration: none;
1085 text-decoration: none;
1086 }
1086 }
1087 del {
1087 del {
1088 color: black;
1088 color: black;
1089 background: #f8cbcb;
1089 background: #f8cbcb;
1090 text-decoration: none;
1090 text-decoration: none;
1091 }
1091 }
1092 &.cb-addition {
1092 &.cb-addition {
1093 background: #ecffec;
1093 background: #ecffec;
1094
1094
1095 &.blob-lineno {
1095 &.blob-lineno {
1096 background: #ddffdd;
1096 background: #ddffdd;
1097 }
1097 }
1098 }
1098 }
1099 &.cb-deletion {
1099 &.cb-deletion {
1100 background: #ffecec;
1100 background: #ffecec;
1101
1101
1102 &.blob-lineno {
1102 &.blob-lineno {
1103 background: #ffdddd;
1103 background: #ffdddd;
1104 }
1104 }
1105 }
1105 }
1106 &.cb-annotate-message-spacer {
1106 &.cb-annotate-message-spacer {
1107 width:8px;
1107 width:8px;
1108 padding: 1px 0px 0px 3px;
1108 }
1109 }
1109 &.cb-annotate-info {
1110 &.cb-annotate-info {
1110 width: 320px;
1111 width: 320px;
1111 min-width: 320px;
1112 min-width: 320px;
1112 max-width: 320px;
1113 max-width: 320px;
1113 padding: 5px 2px;
1114 padding: 5px 2px;
1114 font-size: 13px;
1115 font-size: 13px;
1115
1116
1116 .cb-annotate-message {
1117 .cb-annotate-message {
1117 padding: 2px 0px 0px 0px;
1118 padding: 2px 0px 0px 0px;
1118 white-space: pre-line;
1119 white-space: pre-line;
1119 overflow: hidden;
1120 overflow: hidden;
1120 }
1121 }
1121 .rc-user {
1122 .rc-user {
1122 float: none;
1123 float: none;
1123 padding: 0 6px 0 17px;
1124 padding: 0 6px 0 17px;
1124 min-width: unset;
1125 min-width: unset;
1125 min-height: unset;
1126 min-height: unset;
1126 }
1127 }
1127 }
1128 }
1128
1129
1129 &.cb-annotate-revision {
1130 &.cb-annotate-revision {
1130 cursor: pointer;
1131 cursor: pointer;
1131 text-align: right;
1132 text-align: right;
1132 padding: 1px 3px 0px 3px;
1133 padding: 1px 3px 0px 3px;
1133 }
1134 }
1134 }
1135 }
1135 }
1136 }
@@ -1,57 +1,58 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('user_group_autocomplete_data', '/_user_groups', []);
18 pyroutes.register('new_repo', '/_admin/create_repository', []);
18 pyroutes.register('new_repo', '/_admin/create_repository', []);
19 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
19 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
20 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
20 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
21 pyroutes.register('gists', '/_admin/gists', []);
21 pyroutes.register('gists', '/_admin/gists', []);
22 pyroutes.register('new_gist', '/_admin/gists/new', []);
22 pyroutes.register('new_gist', '/_admin/gists/new', []);
23 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
23 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
24 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
24 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
25 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
25 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
26 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
26 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
27 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/default-reviewers', ['repo_name']);
27 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/default-reviewers', ['repo_name']);
28 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
28 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
29 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
29 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
30 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
30 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
31 pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']);
31 pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']);
32 pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']);
32 pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']);
33 pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
33 pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
34 pyroutes.register('changeset_info', '/%(repo_name)s/changeset_info/%(revision)s', ['repo_name', 'revision']);
34 pyroutes.register('changeset_info', '/%(repo_name)s/changeset_info/%(revision)s', ['repo_name', 'revision']);
35 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']);
35 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']);
36 pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']);
36 pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']);
37 pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']);
37 pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']);
38 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
38 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
39 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
39 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
40 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
40 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
41 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
41 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
42 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
42 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
43 pyroutes.register('pullrequest_comment', '/%(repo_name)s/pull-request-comment/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
43 pyroutes.register('pullrequest_comment', '/%(repo_name)s/pull-request-comment/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
44 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request-comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
44 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request-comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
45 pyroutes.register('changelog_home', '/%(repo_name)s/changelog', ['repo_name']);
45 pyroutes.register('changelog_home', '/%(repo_name)s/changelog', ['repo_name']);
46 pyroutes.register('changelog_file_home', '/%(repo_name)s/changelog/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
46 pyroutes.register('changelog_file_home', '/%(repo_name)s/changelog/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
47 pyroutes.register('changelog_elements', '/%(repo_name)s/changelog_details', ['repo_name']);
47 pyroutes.register('changelog_elements', '/%(repo_name)s/changelog_details', ['repo_name']);
48 pyroutes.register('files_home', '/%(repo_name)s/files/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
48 pyroutes.register('files_home', '/%(repo_name)s/files/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
49 pyroutes.register('files_history_home', '/%(repo_name)s/history/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
49 pyroutes.register('files_history_home', '/%(repo_name)s/history/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
50 pyroutes.register('files_authors_home', '/%(repo_name)s/authors/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
50 pyroutes.register('files_authors_home', '/%(repo_name)s/authors/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
51 pyroutes.register('files_annotate_home', '/%(repo_name)s/annotate/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
51 pyroutes.register('files_annotate_home', '/%(repo_name)s/annotate/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
52 pyroutes.register('files_annotate_previous', '/%(repo_name)s/annotate-previous/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
52 pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
53 pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
53 pyroutes.register('files_nodelist_home', '/%(repo_name)s/nodelist/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
54 pyroutes.register('files_nodelist_home', '/%(repo_name)s/nodelist/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
54 pyroutes.register('files_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
55 pyroutes.register('files_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
55 pyroutes.register('summary_home_slash', '/%(repo_name)s/', ['repo_name']);
56 pyroutes.register('summary_home_slash', '/%(repo_name)s/', ['repo_name']);
56 pyroutes.register('summary_home', '/%(repo_name)s', ['repo_name']);
57 pyroutes.register('summary_home', '/%(repo_name)s', ['repo_name']);
57 }
58 }
@@ -1,71 +1,90 b''
1 <%def name="render_line(line_num, tokens,
1 <%def name="render_line(line_num, tokens,
2 annotation=None,
2 annotation=None,
3 bgcolor=None, show_annotation=None)">
3 bgcolor=None, show_annotation=None)">
4 <%
4 <%
5 from rhodecode.lib.codeblocks import render_tokenstream
5 from rhodecode.lib.codeblocks import render_tokenstream
6 # avoid module lookup for performance
6 # avoid module lookup for performance
7 html_escape = h.html_escape
7 html_escape = h.html_escape
8 %>
8 %>
9 <tr class="cb-line cb-line-fresh ${'cb-annotate' if show_annotation else ''}"
9 <tr class="cb-line cb-line-fresh ${'cb-annotate' if show_annotation else ''}"
10 %if annotation:
10 %if annotation:
11 data-revision="${annotation.revision}"
11 data-revision="${annotation.revision}"
12 %endif
12 %endif
13 >
13 >
14
14
15 % if annotation:
15 % if annotation:
16 % if show_annotation:
16 % if show_annotation:
17 <td class="cb-annotate-info tooltip"
17 <td class="cb-annotate-info tooltip"
18 title="Author: ${annotation.author | entity}<br>Date: ${annotation.date}<br>Message: ${annotation.message | entity}"
18 title="Author: ${annotation.author | entity}<br>Date: ${annotation.date}<br>Message: ${annotation.message | entity}"
19 >
19 >
20 ${h.gravatar_with_user(annotation.author, 16) | n}
20 ${h.gravatar_with_user(annotation.author, 16) | n}
21 <div class="cb-annotate-message truncate-wrap">${h.chop_at_smart(annotation.message, '\n', suffix_if_chopped='...')}</div>
21 <div class="cb-annotate-message truncate-wrap">${h.chop_at_smart(annotation.message, '\n', suffix_if_chopped='...')}</div>
22 </td>
22 </td>
23 <td class="cb-annotate-message-spacer"></td>
23 <td class="cb-annotate-message-spacer">
24 <a class="tooltip" href="#show-previous-annotation" onclick="return annotationController.previousAnnotation('${annotation.raw_id}', '${c.f_path}')" title="${_('view annotation from before this change')}">
25 <i class="icon-left"></i>
26 </a>
27 </td>
24 <td
28 <td
25 class="cb-annotate-revision"
29 class="cb-annotate-revision"
26 data-revision="${annotation.revision}"
30 data-revision="${annotation.revision}"
27 onclick="$('[data-revision=${annotation.revision}]').toggleClass('cb-line-fresh')"
31 onclick="$('[data-revision=${annotation.revision}]').toggleClass('cb-line-fresh')"
28 style="background: ${bgcolor}">
32 style="background: ${bgcolor}">
29 <a class="cb-annotate" href="${h.url('changeset_home',repo_name=c.repo_name,revision=annotation.raw_id)}">
33 <a class="cb-annotate" href="${h.url('changeset_home',repo_name=c.repo_name,revision=annotation.raw_id)}">
30 r${annotation.revision}
34 r${annotation.revision}
31 </a>
35 </a>
32 </td>
36 </td>
33 % else:
37 % else:
34 <td></td>
38 <td></td>
35 <td class="cb-annotate-message-spacer"></td>
39 <td class="cb-annotate-message-spacer"></td>
36 <td
40 <td
37 class="cb-annotate-revision"
41 class="cb-annotate-revision"
38 data-revision="${annotation.revision}"
42 data-revision="${annotation.revision}"
39 onclick="$('[data-revision=${annotation.revision}]').toggleClass('cb-line-fresh')"
43 onclick="$('[data-revision=${annotation.revision}]').toggleClass('cb-line-fresh')"
40 style="background: ${bgcolor}">
44 style="background: ${bgcolor}">
41 </td>
45 </td>
42 % endif
46 % endif
43 % else:
47 % else:
44 <td colspan="3"></td>
48 <td colspan="3"></td>
45 % endif
49 % endif
46
50
47
51
48 <td class="cb-lineno" id="L${line_num}">
52 <td class="cb-lineno" id="L${line_num}">
49 <a data-line-no="${line_num}" href="#L${line_num}"></a>
53 <a data-line-no="${line_num}" href="#L${line_num}"></a>
50 </td>
54 </td>
51 <td class="cb-content cb-content-fresh"
55 <td class="cb-content cb-content-fresh"
52 %if bgcolor:
56 %if bgcolor:
53 style="background: ${bgcolor}"
57 style="background: ${bgcolor}"
54 %endif
58 %endif
55 >
59 >
56 ## newline at end is necessary for highlight to work when line is empty
60 ## newline at end is necessary for highlight to work when line is empty
57 ## and for copy pasting code to work as expected
61 ## and for copy pasting code to work as expected
58 <span class="cb-code">${render_tokenstream(tokens)|n}${'\n'}</span>
62 <span class="cb-code">${render_tokenstream(tokens)|n}${'\n'}</span>
59 </td>
63 </td>
60 </tr>
64 </tr>
61 </%def>
65 </%def>
62
66
63 <%def name="render_annotation_lines(annotation, lines, color_hasher)">
67 <%def name="render_annotation_lines(annotation, lines, color_hasher)">
64 % for line_num, tokens in lines:
68 % for line_num, tokens in lines:
65 ${render_line(line_num, tokens,
69 ${render_line(line_num, tokens,
66 bgcolor=color_hasher(annotation and annotation.raw_id or ''),
70 bgcolor=color_hasher(annotation and annotation.raw_id or ''),
67 annotation=annotation, show_annotation=loop.first
71 annotation=annotation, show_annotation=loop.first
68 )}
72 )}
69 % endfor
73 % endfor
74 <script>
75 var AnnotationController = function() {
76 var self = this;
70
77
78 this.previousAnnotation = function(commitId, fPath) {
79 var params = {
80 'repo_name': templateContext.repo_name,
81 'revision': commitId,
82 'f_path': fPath
83 };
84 window.location = pyroutes.url('files_annotate_previous', params);
85 return false;
86 };
87 };
88 var annotationController = new AnnotationController();
89 </script>
71 </%def>
90 </%def>
General Comments 0
You need to be logged in to leave comments. Login now