##// END OF EJS Templates
feature: Go To switcher now searches commit hashes as well
dan -
r62:81398162 default
parent child Browse files
Show More
@@ -1,1085 +1,1085 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Routes configuration
23 23
24 24 The more specific and detailed routes should be defined first so they
25 25 may take precedent over the more generic routes. For more information
26 26 refer to the routes manual at http://routes.groovie.org/docs/
27 27
28 28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
29 29 and _route_name variable which uses some of stored naming here to do redirects.
30 30 """
31 31 import os
32 32 from routes import Mapper
33 33
34 34 from rhodecode.config import routing_links
35 35
36 36 # prefix for non repository related links needs to be prefixed with `/`
37 37 ADMIN_PREFIX = '/_admin'
38 38
39 39 # Default requirements for URL parts
40 40 URL_NAME_REQUIREMENTS = {
41 41 # group name can have a slash in them, but they must not end with a slash
42 42 'group_name': r'.*?[^/]',
43 43 # repo names can have a slash in them, but they must not end with a slash
44 44 'repo_name': r'.*?[^/]',
45 45 # file path eats up everything at the end
46 46 'f_path': r'.*',
47 47 # reference types
48 48 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
49 49 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
50 50 }
51 51
52 52
53 53 def make_map(config):
54 54 """Create, configure and return the routes Mapper"""
55 55 rmap = Mapper(directory=config['pylons.paths']['controllers'],
56 56 always_scan=config['debug'])
57 57 rmap.minimization = False
58 58 rmap.explicit = False
59 59
60 60 from rhodecode.lib.utils2 import str2bool
61 61 from rhodecode.model import repo, repo_group
62 62
63 63 def check_repo(environ, match_dict):
64 64 """
65 65 check for valid repository for proper 404 handling
66 66
67 67 :param environ:
68 68 :param match_dict:
69 69 """
70 70 repo_name = match_dict.get('repo_name')
71 71
72 72 if match_dict.get('f_path'):
73 73 # fix for multiple initial slashes that causes errors
74 74 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
75 75 repo_model = repo.RepoModel()
76 76 by_name_match = repo_model.get_by_repo_name(repo_name)
77 77 # if we match quickly from database, short circuit the operation,
78 78 # and validate repo based on the type.
79 79 if by_name_match:
80 80 return True
81 81
82 82 by_id_match = repo_model.get_repo_by_id(repo_name)
83 83 if by_id_match:
84 84 repo_name = by_id_match.repo_name
85 85 match_dict['repo_name'] = repo_name
86 86 return True
87 87
88 88 return False
89 89
90 90 def check_group(environ, match_dict):
91 91 """
92 92 check for valid repository group path for proper 404 handling
93 93
94 94 :param environ:
95 95 :param match_dict:
96 96 """
97 97 repo_group_name = match_dict.get('group_name')
98 98 repo_group_model = repo_group.RepoGroupModel()
99 99 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
100 100 if by_name_match:
101 101 return True
102 102
103 103 return False
104 104
105 105 def check_user_group(environ, match_dict):
106 106 """
107 107 check for valid user group for proper 404 handling
108 108
109 109 :param environ:
110 110 :param match_dict:
111 111 """
112 112 return True
113 113
114 114 def check_int(environ, match_dict):
115 115 return match_dict.get('id').isdigit()
116 116
117 117 # The ErrorController route (handles 404/500 error pages); it should
118 118 # likely stay at the top, ensuring it can always be resolved
119 119 rmap.connect('/error/{action}', controller='error')
120 120 rmap.connect('/error/{action}/{id}', controller='error')
121 121
122 122 #==========================================================================
123 123 # CUSTOM ROUTES HERE
124 124 #==========================================================================
125 125
126 126 # MAIN PAGE
127 127 rmap.connect('home', '/', controller='home', action='index')
128 rmap.connect('repo_switcher_data', '/_repos_and_groups', controller='home',
129 action='repo_switcher_data')
128 rmap.connect('goto_switcher_data', '/_goto_data', controller='home',
129 action='goto_switcher_data')
130 130 rmap.connect('repo_list_data', '/_repos', controller='home',
131 131 action='repo_list_data')
132 132
133 133 rmap.connect('user_autocomplete_data', '/_users', controller='home',
134 134 action='user_autocomplete_data')
135 135 rmap.connect('user_group_autocomplete_data', '/_user_groups', controller='home',
136 136 action='user_group_autocomplete_data')
137 137
138 138 rmap.connect(
139 139 'user_profile', '/_profiles/{username}', controller='users',
140 140 action='user_profile')
141 141
142 142 # TODO: johbo: Static links, to be replaced by our redirection mechanism
143 143 rmap.connect('rst_help',
144 144 'http://docutils.sourceforge.net/docs/user/rst/quickref.html',
145 145 _static=True)
146 146 rmap.connect('markdown_help',
147 147 'http://daringfireball.net/projects/markdown/syntax',
148 148 _static=True)
149 149 rmap.connect('rhodecode_official', 'https://rhodecode.com', _static=True)
150 150 rmap.connect('rhodecode_support', 'https://rhodecode.com/help/', _static=True)
151 151 rmap.connect('rhodecode_translations', 'https://rhodecode.com/translate/enterprise', _static=True)
152 152 # TODO: anderson - making this a static link since redirect won't play
153 153 # nice with POST requests
154 154 rmap.connect('enterprise_license_convert_from_old',
155 155 'https://rhodecode.com/u/license-upgrade',
156 156 _static=True)
157 157
158 158 routing_links.connect_redirection_links(rmap)
159 159
160 160 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
161 161 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
162 162
163 163 # ADMIN REPOSITORY ROUTES
164 164 with rmap.submapper(path_prefix=ADMIN_PREFIX,
165 165 controller='admin/repos') as m:
166 166 m.connect('repos', '/repos',
167 167 action='create', conditions={'method': ['POST']})
168 168 m.connect('repos', '/repos',
169 169 action='index', conditions={'method': ['GET']})
170 170 m.connect('new_repo', '/create_repository',
171 171 action='create_repository', conditions={'method': ['GET']})
172 172 m.connect('/repos/{repo_name}',
173 173 action='update', conditions={'method': ['PUT'],
174 174 'function': check_repo},
175 175 requirements=URL_NAME_REQUIREMENTS)
176 176 m.connect('delete_repo', '/repos/{repo_name}',
177 177 action='delete', conditions={'method': ['DELETE']},
178 178 requirements=URL_NAME_REQUIREMENTS)
179 179 m.connect('repo', '/repos/{repo_name}',
180 180 action='show', conditions={'method': ['GET'],
181 181 'function': check_repo},
182 182 requirements=URL_NAME_REQUIREMENTS)
183 183
184 184 # ADMIN REPOSITORY GROUPS ROUTES
185 185 with rmap.submapper(path_prefix=ADMIN_PREFIX,
186 186 controller='admin/repo_groups') as m:
187 187 m.connect('repo_groups', '/repo_groups',
188 188 action='create', conditions={'method': ['POST']})
189 189 m.connect('repo_groups', '/repo_groups',
190 190 action='index', conditions={'method': ['GET']})
191 191 m.connect('new_repo_group', '/repo_groups/new',
192 192 action='new', conditions={'method': ['GET']})
193 193 m.connect('update_repo_group', '/repo_groups/{group_name}',
194 194 action='update', conditions={'method': ['PUT'],
195 195 'function': check_group},
196 196 requirements=URL_NAME_REQUIREMENTS)
197 197
198 198 # EXTRAS REPO GROUP ROUTES
199 199 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
200 200 action='edit',
201 201 conditions={'method': ['GET'], 'function': check_group},
202 202 requirements=URL_NAME_REQUIREMENTS)
203 203 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
204 204 action='edit',
205 205 conditions={'method': ['PUT'], 'function': check_group},
206 206 requirements=URL_NAME_REQUIREMENTS)
207 207
208 208 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
209 209 action='edit_repo_group_advanced',
210 210 conditions={'method': ['GET'], 'function': check_group},
211 211 requirements=URL_NAME_REQUIREMENTS)
212 212 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
213 213 action='edit_repo_group_advanced',
214 214 conditions={'method': ['PUT'], 'function': check_group},
215 215 requirements=URL_NAME_REQUIREMENTS)
216 216
217 217 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
218 218 action='edit_repo_group_perms',
219 219 conditions={'method': ['GET'], 'function': check_group},
220 220 requirements=URL_NAME_REQUIREMENTS)
221 221 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
222 222 action='update_perms',
223 223 conditions={'method': ['PUT'], 'function': check_group},
224 224 requirements=URL_NAME_REQUIREMENTS)
225 225
226 226 m.connect('delete_repo_group', '/repo_groups/{group_name}',
227 227 action='delete', conditions={'method': ['DELETE'],
228 228 'function': check_group},
229 229 requirements=URL_NAME_REQUIREMENTS)
230 230
231 231 # ADMIN USER ROUTES
232 232 with rmap.submapper(path_prefix=ADMIN_PREFIX,
233 233 controller='admin/users') as m:
234 234 m.connect('users', '/users',
235 235 action='create', conditions={'method': ['POST']})
236 236 m.connect('users', '/users',
237 237 action='index', conditions={'method': ['GET']})
238 238 m.connect('new_user', '/users/new',
239 239 action='new', conditions={'method': ['GET']})
240 240 m.connect('update_user', '/users/{user_id}',
241 241 action='update', conditions={'method': ['PUT']})
242 242 m.connect('delete_user', '/users/{user_id}',
243 243 action='delete', conditions={'method': ['DELETE']})
244 244 m.connect('edit_user', '/users/{user_id}/edit',
245 245 action='edit', conditions={'method': ['GET']})
246 246 m.connect('user', '/users/{user_id}',
247 247 action='show', conditions={'method': ['GET']})
248 248 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
249 249 action='reset_password', conditions={'method': ['POST']})
250 250 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
251 251 action='create_personal_repo_group', conditions={'method': ['POST']})
252 252
253 253 # EXTRAS USER ROUTES
254 254 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
255 255 action='edit_advanced', conditions={'method': ['GET']})
256 256 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
257 257 action='update_advanced', conditions={'method': ['PUT']})
258 258
259 259 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
260 260 action='edit_auth_tokens', conditions={'method': ['GET']})
261 261 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
262 262 action='add_auth_token', conditions={'method': ['PUT']})
263 263 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
264 264 action='delete_auth_token', conditions={'method': ['DELETE']})
265 265
266 266 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
267 267 action='edit_global_perms', conditions={'method': ['GET']})
268 268 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
269 269 action='update_global_perms', conditions={'method': ['PUT']})
270 270
271 271 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
272 272 action='edit_perms_summary', conditions={'method': ['GET']})
273 273
274 274 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
275 275 action='edit_emails', conditions={'method': ['GET']})
276 276 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
277 277 action='add_email', conditions={'method': ['PUT']})
278 278 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
279 279 action='delete_email', conditions={'method': ['DELETE']})
280 280
281 281 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
282 282 action='edit_ips', conditions={'method': ['GET']})
283 283 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
284 284 action='add_ip', conditions={'method': ['PUT']})
285 285 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
286 286 action='delete_ip', conditions={'method': ['DELETE']})
287 287
288 288 # ADMIN USER GROUPS REST ROUTES
289 289 with rmap.submapper(path_prefix=ADMIN_PREFIX,
290 290 controller='admin/user_groups') as m:
291 291 m.connect('users_groups', '/user_groups',
292 292 action='create', conditions={'method': ['POST']})
293 293 m.connect('users_groups', '/user_groups',
294 294 action='index', conditions={'method': ['GET']})
295 295 m.connect('new_users_group', '/user_groups/new',
296 296 action='new', conditions={'method': ['GET']})
297 297 m.connect('update_users_group', '/user_groups/{user_group_id}',
298 298 action='update', conditions={'method': ['PUT']})
299 299 m.connect('delete_users_group', '/user_groups/{user_group_id}',
300 300 action='delete', conditions={'method': ['DELETE']})
301 301 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
302 302 action='edit', conditions={'method': ['GET']},
303 303 function=check_user_group)
304 304
305 305 # EXTRAS USER GROUP ROUTES
306 306 m.connect('edit_user_group_global_perms', '/user_groups/{user_group_id}/edit/global_permissions',
307 307 action='edit_global_perms', conditions={'method': ['GET']})
308 308 m.connect('edit_user_group_global_perms', '/user_groups/{user_group_id}/edit/global_permissions',
309 309 action='update_global_perms', conditions={'method': ['PUT']})
310 310 m.connect('edit_user_group_perms_summary', '/user_groups/{user_group_id}/edit/permissions_summary',
311 311 action='edit_perms_summary', conditions={'method': ['GET']})
312 312
313 313 m.connect('edit_user_group_perms', '/user_groups/{user_group_id}/edit/permissions',
314 314 action='edit_perms', conditions={'method': ['GET']})
315 315 m.connect('edit_user_group_perms', '/user_groups/{user_group_id}/edit/permissions',
316 316 action='update_perms', conditions={'method': ['PUT']})
317 317
318 318 m.connect('edit_user_group_advanced', '/user_groups/{user_group_id}/edit/advanced',
319 319 action='edit_advanced', conditions={'method': ['GET']})
320 320
321 321 m.connect('edit_user_group_members', '/user_groups/{user_group_id}/edit/members',
322 322 action='edit_members', conditions={'method': ['GET']})
323 323
324 324 # ADMIN PERMISSIONS ROUTES
325 325 with rmap.submapper(path_prefix=ADMIN_PREFIX,
326 326 controller='admin/permissions') as m:
327 327 m.connect('admin_permissions_application', '/permissions/application',
328 328 action='permission_application_update', conditions={'method': ['POST']})
329 329 m.connect('admin_permissions_application', '/permissions/application',
330 330 action='permission_application', conditions={'method': ['GET']})
331 331
332 332 m.connect('admin_permissions_global', '/permissions/global',
333 333 action='permission_global_update', conditions={'method': ['POST']})
334 334 m.connect('admin_permissions_global', '/permissions/global',
335 335 action='permission_global', conditions={'method': ['GET']})
336 336
337 337 m.connect('admin_permissions_object', '/permissions/object',
338 338 action='permission_objects_update', conditions={'method': ['POST']})
339 339 m.connect('admin_permissions_object', '/permissions/object',
340 340 action='permission_objects', conditions={'method': ['GET']})
341 341
342 342 m.connect('admin_permissions_ips', '/permissions/ips',
343 343 action='permission_ips', conditions={'method': ['POST']})
344 344 m.connect('admin_permissions_ips', '/permissions/ips',
345 345 action='permission_ips', conditions={'method': ['GET']})
346 346
347 347 m.connect('admin_permissions_overview', '/permissions/overview',
348 348 action='permission_perms', conditions={'method': ['GET']})
349 349
350 350 # ADMIN DEFAULTS REST ROUTES
351 351 with rmap.submapper(path_prefix=ADMIN_PREFIX,
352 352 controller='admin/defaults') as m:
353 353 m.connect('admin_defaults_repositories', '/defaults/repositories',
354 354 action='update_repository_defaults', conditions={'method': ['POST']})
355 355 m.connect('admin_defaults_repositories', '/defaults/repositories',
356 356 action='index', conditions={'method': ['GET']})
357 357
358 358 # ADMIN DEBUG STYLE ROUTES
359 359 if str2bool(config.get('debug_style')):
360 360 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
361 361 controller='debug_style') as m:
362 362 m.connect('debug_style_home', '',
363 363 action='index', conditions={'method': ['GET']})
364 364 m.connect('debug_style_template', '/t/{t_path}',
365 365 action='template', conditions={'method': ['GET']})
366 366
367 367 # ADMIN SETTINGS ROUTES
368 368 with rmap.submapper(path_prefix=ADMIN_PREFIX,
369 369 controller='admin/settings') as m:
370 370
371 371 # default
372 372 m.connect('admin_settings', '/settings',
373 373 action='settings_global_update',
374 374 conditions={'method': ['POST']})
375 375 m.connect('admin_settings', '/settings',
376 376 action='settings_global', conditions={'method': ['GET']})
377 377
378 378 m.connect('admin_settings_vcs', '/settings/vcs',
379 379 action='settings_vcs_update',
380 380 conditions={'method': ['POST']})
381 381 m.connect('admin_settings_vcs', '/settings/vcs',
382 382 action='settings_vcs',
383 383 conditions={'method': ['GET']})
384 384 m.connect('admin_settings_vcs', '/settings/vcs',
385 385 action='delete_svn_pattern',
386 386 conditions={'method': ['DELETE']})
387 387
388 388 m.connect('admin_settings_mapping', '/settings/mapping',
389 389 action='settings_mapping_update',
390 390 conditions={'method': ['POST']})
391 391 m.connect('admin_settings_mapping', '/settings/mapping',
392 392 action='settings_mapping', conditions={'method': ['GET']})
393 393
394 394 m.connect('admin_settings_global', '/settings/global',
395 395 action='settings_global_update',
396 396 conditions={'method': ['POST']})
397 397 m.connect('admin_settings_global', '/settings/global',
398 398 action='settings_global', conditions={'method': ['GET']})
399 399
400 400 m.connect('admin_settings_visual', '/settings/visual',
401 401 action='settings_visual_update',
402 402 conditions={'method': ['POST']})
403 403 m.connect('admin_settings_visual', '/settings/visual',
404 404 action='settings_visual', conditions={'method': ['GET']})
405 405
406 406 m.connect('admin_settings_issuetracker',
407 407 '/settings/issue-tracker', action='settings_issuetracker',
408 408 conditions={'method': ['GET']})
409 409 m.connect('admin_settings_issuetracker_save',
410 410 '/settings/issue-tracker/save',
411 411 action='settings_issuetracker_save',
412 412 conditions={'method': ['POST']})
413 413 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
414 414 action='settings_issuetracker_test',
415 415 conditions={'method': ['POST']})
416 416 m.connect('admin_issuetracker_delete',
417 417 '/settings/issue-tracker/delete',
418 418 action='settings_issuetracker_delete',
419 419 conditions={'method': ['DELETE']})
420 420
421 421 m.connect('admin_settings_email', '/settings/email',
422 422 action='settings_email_update',
423 423 conditions={'method': ['POST']})
424 424 m.connect('admin_settings_email', '/settings/email',
425 425 action='settings_email', conditions={'method': ['GET']})
426 426
427 427 m.connect('admin_settings_hooks', '/settings/hooks',
428 428 action='settings_hooks_update',
429 429 conditions={'method': ['POST', 'DELETE']})
430 430 m.connect('admin_settings_hooks', '/settings/hooks',
431 431 action='settings_hooks', conditions={'method': ['GET']})
432 432
433 433 m.connect('admin_settings_search', '/settings/search',
434 434 action='settings_search', conditions={'method': ['GET']})
435 435
436 436 m.connect('admin_settings_system', '/settings/system',
437 437 action='settings_system', conditions={'method': ['GET']})
438 438
439 439 m.connect('admin_settings_system_update', '/settings/system/updates',
440 440 action='settings_system_update', conditions={'method': ['GET']})
441 441
442 442 m.connect('admin_settings_supervisor', '/settings/supervisor',
443 443 action='settings_supervisor', conditions={'method': ['GET']})
444 444 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
445 445 action='settings_supervisor_log', conditions={'method': ['GET']})
446 446
447 447 m.connect('admin_settings_labs', '/settings/labs',
448 448 action='settings_labs_update',
449 449 conditions={'method': ['POST']})
450 450 m.connect('admin_settings_labs', '/settings/labs',
451 451 action='settings_labs', conditions={'method': ['GET']})
452 452
453 453 m.connect('admin_settings_open_source', '/settings/open_source',
454 454 action='settings_open_source',
455 455 conditions={'method': ['GET']})
456 456
457 457 # ADMIN MY ACCOUNT
458 458 with rmap.submapper(path_prefix=ADMIN_PREFIX,
459 459 controller='admin/my_account') as m:
460 460
461 461 m.connect('my_account', '/my_account',
462 462 action='my_account', conditions={'method': ['GET']})
463 463 m.connect('my_account_edit', '/my_account/edit',
464 464 action='my_account_edit', conditions={'method': ['GET']})
465 465 m.connect('my_account', '/my_account',
466 466 action='my_account_update', conditions={'method': ['POST']})
467 467
468 468 m.connect('my_account_password', '/my_account/password',
469 469 action='my_account_password', conditions={'method': ['GET']})
470 470 m.connect('my_account_password', '/my_account/password',
471 471 action='my_account_password_update', conditions={'method': ['POST']})
472 472
473 473 m.connect('my_account_repos', '/my_account/repos',
474 474 action='my_account_repos', conditions={'method': ['GET']})
475 475
476 476 m.connect('my_account_watched', '/my_account/watched',
477 477 action='my_account_watched', conditions={'method': ['GET']})
478 478
479 479 m.connect('my_account_pullrequests', '/my_account/pull_requests',
480 480 action='my_account_pullrequests', conditions={'method': ['GET']})
481 481
482 482 m.connect('my_account_perms', '/my_account/perms',
483 483 action='my_account_perms', conditions={'method': ['GET']})
484 484
485 485 m.connect('my_account_emails', '/my_account/emails',
486 486 action='my_account_emails', conditions={'method': ['GET']})
487 487 m.connect('my_account_emails', '/my_account/emails',
488 488 action='my_account_emails_add', conditions={'method': ['POST']})
489 489 m.connect('my_account_emails', '/my_account/emails',
490 490 action='my_account_emails_delete', conditions={'method': ['DELETE']})
491 491
492 492 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
493 493 action='my_account_auth_tokens', conditions={'method': ['GET']})
494 494 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
495 495 action='my_account_auth_tokens_add', conditions={'method': ['POST']})
496 496 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
497 497 action='my_account_auth_tokens_delete', conditions={'method': ['DELETE']})
498 498
499 499 # NOTIFICATION REST ROUTES
500 500 with rmap.submapper(path_prefix=ADMIN_PREFIX,
501 501 controller='admin/notifications') as m:
502 502 m.connect('notifications', '/notifications',
503 503 action='index', conditions={'method': ['GET']})
504 504 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
505 505 action='mark_all_read', conditions={'method': ['POST']})
506 506
507 507 m.connect('/notifications/{notification_id}',
508 508 action='update', conditions={'method': ['PUT']})
509 509 m.connect('/notifications/{notification_id}',
510 510 action='delete', conditions={'method': ['DELETE']})
511 511 m.connect('notification', '/notifications/{notification_id}',
512 512 action='show', conditions={'method': ['GET']})
513 513
514 514 # ADMIN GIST
515 515 with rmap.submapper(path_prefix=ADMIN_PREFIX,
516 516 controller='admin/gists') as m:
517 517 m.connect('gists', '/gists',
518 518 action='create', conditions={'method': ['POST']})
519 519 m.connect('gists', '/gists',
520 520 action='index', conditions={'method': ['GET']})
521 521 m.connect('new_gist', '/gists/new',
522 522 action='new', conditions={'method': ['GET']})
523 523
524 524 m.connect('/gists/{gist_id}',
525 525 action='delete', conditions={'method': ['DELETE']})
526 526 m.connect('edit_gist', '/gists/{gist_id}/edit',
527 527 action='edit_form', conditions={'method': ['GET']})
528 528 m.connect('edit_gist', '/gists/{gist_id}/edit',
529 529 action='edit', conditions={'method': ['POST']})
530 530 m.connect(
531 531 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
532 532 action='check_revision', conditions={'method': ['GET']})
533 533
534 534 m.connect('gist', '/gists/{gist_id}',
535 535 action='show', conditions={'method': ['GET']})
536 536 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
537 537 revision='tip',
538 538 action='show', conditions={'method': ['GET']})
539 539 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
540 540 revision='tip',
541 541 action='show', conditions={'method': ['GET']})
542 542 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
543 543 revision='tip',
544 544 action='show', conditions={'method': ['GET']},
545 545 requirements=URL_NAME_REQUIREMENTS)
546 546
547 547 # ADMIN MAIN PAGES
548 548 with rmap.submapper(path_prefix=ADMIN_PREFIX,
549 549 controller='admin/admin') as m:
550 550 m.connect('admin_home', '', action='index')
551 551 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
552 552 action='add_repo')
553 553 m.connect(
554 554 'pull_requests_global', '/pull_requests/{pull_request_id:[0-9]+}',
555 555 action='pull_requests')
556 556
557 557 # USER JOURNAL
558 558 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
559 559 controller='journal', action='index')
560 560 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
561 561 controller='journal', action='journal_rss')
562 562 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
563 563 controller='journal', action='journal_atom')
564 564
565 565 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
566 566 controller='journal', action='public_journal')
567 567
568 568 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
569 569 controller='journal', action='public_journal_rss')
570 570
571 571 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
572 572 controller='journal', action='public_journal_rss')
573 573
574 574 rmap.connect('public_journal_atom',
575 575 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
576 576 action='public_journal_atom')
577 577
578 578 rmap.connect('public_journal_atom_old',
579 579 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
580 580 action='public_journal_atom')
581 581
582 582 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
583 583 controller='journal', action='toggle_following',
584 584 conditions={'method': ['POST']})
585 585
586 586 # FULL TEXT SEARCH
587 587 rmap.connect('search', '%s/search' % (ADMIN_PREFIX,),
588 588 controller='search')
589 589 rmap.connect('search_repo_home', '/{repo_name}/search',
590 590 controller='search',
591 591 action='index',
592 592 conditions={'function': check_repo},
593 593 requirements=URL_NAME_REQUIREMENTS)
594 594
595 595 # FEEDS
596 596 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
597 597 controller='feed', action='rss',
598 598 conditions={'function': check_repo},
599 599 requirements=URL_NAME_REQUIREMENTS)
600 600
601 601 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
602 602 controller='feed', action='atom',
603 603 conditions={'function': check_repo},
604 604 requirements=URL_NAME_REQUIREMENTS)
605 605
606 606 #==========================================================================
607 607 # REPOSITORY ROUTES
608 608 #==========================================================================
609 609
610 610 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
611 611 controller='admin/repos', action='repo_creating',
612 612 requirements=URL_NAME_REQUIREMENTS)
613 613 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
614 614 controller='admin/repos', action='repo_check',
615 615 requirements=URL_NAME_REQUIREMENTS)
616 616
617 617 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
618 618 controller='summary', action='repo_stats',
619 619 conditions={'function': check_repo},
620 620 requirements=URL_NAME_REQUIREMENTS)
621 621
622 622 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
623 623 controller='summary', action='repo_refs_data',
624 624 requirements=URL_NAME_REQUIREMENTS)
625 625 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
626 626 controller='summary', action='repo_refs_changelog_data',
627 627 requirements=URL_NAME_REQUIREMENTS)
628 628
629 629 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
630 630 controller='changeset', revision='tip',
631 631 conditions={'function': check_repo},
632 632 requirements=URL_NAME_REQUIREMENTS)
633 633 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
634 634 controller='changeset', revision='tip', action='changeset_children',
635 635 conditions={'function': check_repo},
636 636 requirements=URL_NAME_REQUIREMENTS)
637 637 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
638 638 controller='changeset', revision='tip', action='changeset_parents',
639 639 conditions={'function': check_repo},
640 640 requirements=URL_NAME_REQUIREMENTS)
641 641
642 642 # repo edit options
643 643 rmap.connect('edit_repo', '/{repo_name}/settings',
644 644 controller='admin/repos', action='edit',
645 645 conditions={'method': ['GET'], 'function': check_repo},
646 646 requirements=URL_NAME_REQUIREMENTS)
647 647
648 648 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
649 649 controller='admin/repos', action='edit_permissions',
650 650 conditions={'method': ['GET'], 'function': check_repo},
651 651 requirements=URL_NAME_REQUIREMENTS)
652 652 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
653 653 controller='admin/repos', action='edit_permissions_update',
654 654 conditions={'method': ['PUT'], 'function': check_repo},
655 655 requirements=URL_NAME_REQUIREMENTS)
656 656
657 657 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
658 658 controller='admin/repos', action='edit_fields',
659 659 conditions={'method': ['GET'], 'function': check_repo},
660 660 requirements=URL_NAME_REQUIREMENTS)
661 661 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
662 662 controller='admin/repos', action='create_repo_field',
663 663 conditions={'method': ['PUT'], 'function': check_repo},
664 664 requirements=URL_NAME_REQUIREMENTS)
665 665 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
666 666 controller='admin/repos', action='delete_repo_field',
667 667 conditions={'method': ['DELETE'], 'function': check_repo},
668 668 requirements=URL_NAME_REQUIREMENTS)
669 669
670 670 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
671 671 controller='admin/repos', action='edit_advanced',
672 672 conditions={'method': ['GET'], 'function': check_repo},
673 673 requirements=URL_NAME_REQUIREMENTS)
674 674
675 675 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
676 676 controller='admin/repos', action='edit_advanced_locking',
677 677 conditions={'method': ['PUT'], 'function': check_repo},
678 678 requirements=URL_NAME_REQUIREMENTS)
679 679 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
680 680 controller='admin/repos', action='toggle_locking',
681 681 conditions={'method': ['GET'], 'function': check_repo},
682 682 requirements=URL_NAME_REQUIREMENTS)
683 683
684 684 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
685 685 controller='admin/repos', action='edit_advanced_journal',
686 686 conditions={'method': ['PUT'], 'function': check_repo},
687 687 requirements=URL_NAME_REQUIREMENTS)
688 688
689 689 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
690 690 controller='admin/repos', action='edit_advanced_fork',
691 691 conditions={'method': ['PUT'], 'function': check_repo},
692 692 requirements=URL_NAME_REQUIREMENTS)
693 693
694 694 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
695 695 controller='admin/repos', action='edit_caches_form',
696 696 conditions={'method': ['GET'], 'function': check_repo},
697 697 requirements=URL_NAME_REQUIREMENTS)
698 698 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
699 699 controller='admin/repos', action='edit_caches',
700 700 conditions={'method': ['PUT'], 'function': check_repo},
701 701 requirements=URL_NAME_REQUIREMENTS)
702 702
703 703 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
704 704 controller='admin/repos', action='edit_remote_form',
705 705 conditions={'method': ['GET'], 'function': check_repo},
706 706 requirements=URL_NAME_REQUIREMENTS)
707 707 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
708 708 controller='admin/repos', action='edit_remote',
709 709 conditions={'method': ['PUT'], 'function': check_repo},
710 710 requirements=URL_NAME_REQUIREMENTS)
711 711
712 712 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
713 713 controller='admin/repos', action='edit_statistics_form',
714 714 conditions={'method': ['GET'], 'function': check_repo},
715 715 requirements=URL_NAME_REQUIREMENTS)
716 716 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
717 717 controller='admin/repos', action='edit_statistics',
718 718 conditions={'method': ['PUT'], 'function': check_repo},
719 719 requirements=URL_NAME_REQUIREMENTS)
720 720 rmap.connect('repo_settings_issuetracker',
721 721 '/{repo_name}/settings/issue-tracker',
722 722 controller='admin/repos', action='repo_issuetracker',
723 723 conditions={'method': ['GET'], 'function': check_repo},
724 724 requirements=URL_NAME_REQUIREMENTS)
725 725 rmap.connect('repo_issuetracker_test',
726 726 '/{repo_name}/settings/issue-tracker/test',
727 727 controller='admin/repos', action='repo_issuetracker_test',
728 728 conditions={'method': ['POST'], 'function': check_repo},
729 729 requirements=URL_NAME_REQUIREMENTS)
730 730 rmap.connect('repo_issuetracker_delete',
731 731 '/{repo_name}/settings/issue-tracker/delete',
732 732 controller='admin/repos', action='repo_issuetracker_delete',
733 733 conditions={'method': ['DELETE'], 'function': check_repo},
734 734 requirements=URL_NAME_REQUIREMENTS)
735 735 rmap.connect('repo_issuetracker_save',
736 736 '/{repo_name}/settings/issue-tracker/save',
737 737 controller='admin/repos', action='repo_issuetracker_save',
738 738 conditions={'method': ['POST'], 'function': check_repo},
739 739 requirements=URL_NAME_REQUIREMENTS)
740 740 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
741 741 controller='admin/repos', action='repo_settings_vcs_update',
742 742 conditions={'method': ['POST'], 'function': check_repo},
743 743 requirements=URL_NAME_REQUIREMENTS)
744 744 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
745 745 controller='admin/repos', action='repo_settings_vcs',
746 746 conditions={'method': ['GET'], 'function': check_repo},
747 747 requirements=URL_NAME_REQUIREMENTS)
748 748 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
749 749 controller='admin/repos', action='repo_delete_svn_pattern',
750 750 conditions={'method': ['DELETE'], 'function': check_repo},
751 751 requirements=URL_NAME_REQUIREMENTS)
752 752
753 753 # still working url for backward compat.
754 754 rmap.connect('raw_changeset_home_depraced',
755 755 '/{repo_name}/raw-changeset/{revision}',
756 756 controller='changeset', action='changeset_raw',
757 757 revision='tip', conditions={'function': check_repo},
758 758 requirements=URL_NAME_REQUIREMENTS)
759 759
760 760 # new URLs
761 761 rmap.connect('changeset_raw_home',
762 762 '/{repo_name}/changeset-diff/{revision}',
763 763 controller='changeset', action='changeset_raw',
764 764 revision='tip', conditions={'function': check_repo},
765 765 requirements=URL_NAME_REQUIREMENTS)
766 766
767 767 rmap.connect('changeset_patch_home',
768 768 '/{repo_name}/changeset-patch/{revision}',
769 769 controller='changeset', action='changeset_patch',
770 770 revision='tip', conditions={'function': check_repo},
771 771 requirements=URL_NAME_REQUIREMENTS)
772 772
773 773 rmap.connect('changeset_download_home',
774 774 '/{repo_name}/changeset-download/{revision}',
775 775 controller='changeset', action='changeset_download',
776 776 revision='tip', conditions={'function': check_repo},
777 777 requirements=URL_NAME_REQUIREMENTS)
778 778
779 779 rmap.connect('changeset_comment',
780 780 '/{repo_name}/changeset/{revision}/comment',
781 781 controller='changeset', revision='tip', action='comment',
782 782 conditions={'function': check_repo},
783 783 requirements=URL_NAME_REQUIREMENTS)
784 784
785 785 rmap.connect('changeset_comment_preview',
786 786 '/{repo_name}/changeset/comment/preview',
787 787 controller='changeset', action='preview_comment',
788 788 conditions={'function': check_repo, 'method': ['POST']},
789 789 requirements=URL_NAME_REQUIREMENTS)
790 790
791 791 rmap.connect('changeset_comment_delete',
792 792 '/{repo_name}/changeset/comment/{comment_id}/delete',
793 793 controller='changeset', action='delete_comment',
794 794 conditions={'function': check_repo, 'method': ['DELETE']},
795 795 requirements=URL_NAME_REQUIREMENTS)
796 796
797 797 rmap.connect('changeset_info', '/changeset_info/{repo_name}/{revision}',
798 798 controller='changeset', action='changeset_info',
799 799 requirements=URL_NAME_REQUIREMENTS)
800 800
801 801 rmap.connect('compare_home',
802 802 '/{repo_name}/compare',
803 803 controller='compare', action='index',
804 804 conditions={'function': check_repo},
805 805 requirements=URL_NAME_REQUIREMENTS)
806 806
807 807 rmap.connect('compare_url',
808 808 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
809 809 controller='compare', action='compare',
810 810 conditions={'function': check_repo},
811 811 requirements=URL_NAME_REQUIREMENTS)
812 812
813 813 rmap.connect('pullrequest_home',
814 814 '/{repo_name}/pull-request/new', controller='pullrequests',
815 815 action='index', conditions={'function': check_repo,
816 816 'method': ['GET']},
817 817 requirements=URL_NAME_REQUIREMENTS)
818 818
819 819 rmap.connect('pullrequest',
820 820 '/{repo_name}/pull-request/new', controller='pullrequests',
821 821 action='create', conditions={'function': check_repo,
822 822 'method': ['POST']},
823 823 requirements=URL_NAME_REQUIREMENTS)
824 824
825 825 rmap.connect('pullrequest_repo_refs',
826 826 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
827 827 controller='pullrequests',
828 828 action='get_repo_refs',
829 829 conditions={'function': check_repo, 'method': ['GET']},
830 830 requirements=URL_NAME_REQUIREMENTS)
831 831
832 832 rmap.connect('pullrequest_repo_destinations',
833 833 '/{repo_name}/pull-request/repo-destinations',
834 834 controller='pullrequests',
835 835 action='get_repo_destinations',
836 836 conditions={'function': check_repo, 'method': ['GET']},
837 837 requirements=URL_NAME_REQUIREMENTS)
838 838
839 839 rmap.connect('pullrequest_show',
840 840 '/{repo_name}/pull-request/{pull_request_id}',
841 841 controller='pullrequests',
842 842 action='show', conditions={'function': check_repo,
843 843 'method': ['GET']},
844 844 requirements=URL_NAME_REQUIREMENTS)
845 845
846 846 rmap.connect('pullrequest_update',
847 847 '/{repo_name}/pull-request/{pull_request_id}',
848 848 controller='pullrequests',
849 849 action='update', conditions={'function': check_repo,
850 850 'method': ['PUT']},
851 851 requirements=URL_NAME_REQUIREMENTS)
852 852
853 853 rmap.connect('pullrequest_merge',
854 854 '/{repo_name}/pull-request/{pull_request_id}',
855 855 controller='pullrequests',
856 856 action='merge', conditions={'function': check_repo,
857 857 'method': ['POST']},
858 858 requirements=URL_NAME_REQUIREMENTS)
859 859
860 860 rmap.connect('pullrequest_delete',
861 861 '/{repo_name}/pull-request/{pull_request_id}',
862 862 controller='pullrequests',
863 863 action='delete', conditions={'function': check_repo,
864 864 'method': ['DELETE']},
865 865 requirements=URL_NAME_REQUIREMENTS)
866 866
867 867 rmap.connect('pullrequest_show_all',
868 868 '/{repo_name}/pull-request',
869 869 controller='pullrequests',
870 870 action='show_all', conditions={'function': check_repo,
871 871 'method': ['GET']},
872 872 requirements=URL_NAME_REQUIREMENTS)
873 873
874 874 rmap.connect('pullrequest_comment',
875 875 '/{repo_name}/pull-request-comment/{pull_request_id}',
876 876 controller='pullrequests',
877 877 action='comment', conditions={'function': check_repo,
878 878 'method': ['POST']},
879 879 requirements=URL_NAME_REQUIREMENTS)
880 880
881 881 rmap.connect('pullrequest_comment_delete',
882 882 '/{repo_name}/pull-request-comment/{comment_id}/delete',
883 883 controller='pullrequests', action='delete_comment',
884 884 conditions={'function': check_repo, 'method': ['DELETE']},
885 885 requirements=URL_NAME_REQUIREMENTS)
886 886
887 887 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
888 888 controller='summary', conditions={'function': check_repo},
889 889 requirements=URL_NAME_REQUIREMENTS)
890 890
891 891 rmap.connect('branches_home', '/{repo_name}/branches',
892 892 controller='branches', conditions={'function': check_repo},
893 893 requirements=URL_NAME_REQUIREMENTS)
894 894
895 895 rmap.connect('tags_home', '/{repo_name}/tags',
896 896 controller='tags', conditions={'function': check_repo},
897 897 requirements=URL_NAME_REQUIREMENTS)
898 898
899 899 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
900 900 controller='bookmarks', conditions={'function': check_repo},
901 901 requirements=URL_NAME_REQUIREMENTS)
902 902
903 903 rmap.connect('changelog_home', '/{repo_name}/changelog',
904 904 controller='changelog', conditions={'function': check_repo},
905 905 requirements=URL_NAME_REQUIREMENTS)
906 906
907 907 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
908 908 controller='changelog', action='changelog_summary',
909 909 conditions={'function': check_repo},
910 910 requirements=URL_NAME_REQUIREMENTS)
911 911
912 912 rmap.connect('changelog_file_home', '/{repo_name}/changelog/{revision}/{f_path}',
913 913 controller='changelog', f_path=None,
914 914 conditions={'function': check_repo},
915 915 requirements=URL_NAME_REQUIREMENTS)
916 916
917 917 rmap.connect('changelog_details', '/{repo_name}/changelog_details/{cs}',
918 918 controller='changelog', action='changelog_details',
919 919 conditions={'function': check_repo},
920 920 requirements=URL_NAME_REQUIREMENTS)
921 921
922 922 rmap.connect('files_home',
923 923 '/{repo_name}/files/{revision}/{f_path}',
924 924 controller='files', revision='tip', f_path='',
925 925 conditions={'function': check_repo},
926 926 requirements=URL_NAME_REQUIREMENTS)
927 927
928 928 rmap.connect('files_home_simple_catchrev',
929 929 '/{repo_name}/files/{revision}',
930 930 controller='files', revision='tip', f_path='',
931 931 conditions={'function': check_repo},
932 932 requirements=URL_NAME_REQUIREMENTS)
933 933
934 934 rmap.connect('files_home_simple_catchall',
935 935 '/{repo_name}/files',
936 936 controller='files', revision='tip', f_path='',
937 937 conditions={'function': check_repo},
938 938 requirements=URL_NAME_REQUIREMENTS)
939 939
940 940 rmap.connect('files_history_home',
941 941 '/{repo_name}/history/{revision}/{f_path}',
942 942 controller='files', action='history', revision='tip', f_path='',
943 943 conditions={'function': check_repo},
944 944 requirements=URL_NAME_REQUIREMENTS)
945 945
946 946 rmap.connect('files_authors_home',
947 947 '/{repo_name}/authors/{revision}/{f_path}',
948 948 controller='files', action='authors', revision='tip', f_path='',
949 949 conditions={'function': check_repo},
950 950 requirements=URL_NAME_REQUIREMENTS)
951 951
952 952 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
953 953 controller='files', action='diff', f_path='',
954 954 conditions={'function': check_repo},
955 955 requirements=URL_NAME_REQUIREMENTS)
956 956
957 957 rmap.connect('files_diff_2way_home',
958 958 '/{repo_name}/diff-2way/{f_path}',
959 959 controller='files', action='diff_2way', f_path='',
960 960 conditions={'function': check_repo},
961 961 requirements=URL_NAME_REQUIREMENTS)
962 962
963 963 rmap.connect('files_rawfile_home',
964 964 '/{repo_name}/rawfile/{revision}/{f_path}',
965 965 controller='files', action='rawfile', revision='tip',
966 966 f_path='', conditions={'function': check_repo},
967 967 requirements=URL_NAME_REQUIREMENTS)
968 968
969 969 rmap.connect('files_raw_home',
970 970 '/{repo_name}/raw/{revision}/{f_path}',
971 971 controller='files', action='raw', revision='tip', f_path='',
972 972 conditions={'function': check_repo},
973 973 requirements=URL_NAME_REQUIREMENTS)
974 974
975 975 rmap.connect('files_render_home',
976 976 '/{repo_name}/render/{revision}/{f_path}',
977 977 controller='files', action='index', revision='tip', f_path='',
978 978 rendered=True, conditions={'function': check_repo},
979 979 requirements=URL_NAME_REQUIREMENTS)
980 980
981 981 rmap.connect('files_annotate_home',
982 982 '/{repo_name}/annotate/{revision}/{f_path}',
983 983 controller='files', action='index', revision='tip',
984 984 f_path='', annotate=True, conditions={'function': check_repo},
985 985 requirements=URL_NAME_REQUIREMENTS)
986 986
987 987 rmap.connect('files_edit',
988 988 '/{repo_name}/edit/{revision}/{f_path}',
989 989 controller='files', action='edit', revision='tip',
990 990 f_path='',
991 991 conditions={'function': check_repo, 'method': ['POST']},
992 992 requirements=URL_NAME_REQUIREMENTS)
993 993
994 994 rmap.connect('files_edit_home',
995 995 '/{repo_name}/edit/{revision}/{f_path}',
996 996 controller='files', action='edit_home', revision='tip',
997 997 f_path='', conditions={'function': check_repo},
998 998 requirements=URL_NAME_REQUIREMENTS)
999 999
1000 1000 rmap.connect('files_add',
1001 1001 '/{repo_name}/add/{revision}/{f_path}',
1002 1002 controller='files', action='add', revision='tip',
1003 1003 f_path='',
1004 1004 conditions={'function': check_repo, 'method': ['POST']},
1005 1005 requirements=URL_NAME_REQUIREMENTS)
1006 1006
1007 1007 rmap.connect('files_add_home',
1008 1008 '/{repo_name}/add/{revision}/{f_path}',
1009 1009 controller='files', action='add_home', revision='tip',
1010 1010 f_path='', conditions={'function': check_repo},
1011 1011 requirements=URL_NAME_REQUIREMENTS)
1012 1012
1013 1013 rmap.connect('files_delete',
1014 1014 '/{repo_name}/delete/{revision}/{f_path}',
1015 1015 controller='files', action='delete', revision='tip',
1016 1016 f_path='',
1017 1017 conditions={'function': check_repo, 'method': ['POST']},
1018 1018 requirements=URL_NAME_REQUIREMENTS)
1019 1019
1020 1020 rmap.connect('files_delete_home',
1021 1021 '/{repo_name}/delete/{revision}/{f_path}',
1022 1022 controller='files', action='delete_home', revision='tip',
1023 1023 f_path='', conditions={'function': check_repo},
1024 1024 requirements=URL_NAME_REQUIREMENTS)
1025 1025
1026 1026 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1027 1027 controller='files', action='archivefile',
1028 1028 conditions={'function': check_repo},
1029 1029 requirements=URL_NAME_REQUIREMENTS)
1030 1030
1031 1031 rmap.connect('files_nodelist_home',
1032 1032 '/{repo_name}/nodelist/{revision}/{f_path}',
1033 1033 controller='files', action='nodelist',
1034 1034 conditions={'function': check_repo},
1035 1035 requirements=URL_NAME_REQUIREMENTS)
1036 1036
1037 1037 rmap.connect('files_metadata_list_home',
1038 1038 '/{repo_name}/metadata_list/{revision}/{f_path}',
1039 1039 controller='files', action='metadata_list',
1040 1040 conditions={'function': check_repo},
1041 1041 requirements=URL_NAME_REQUIREMENTS)
1042 1042
1043 1043 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1044 1044 controller='forks', action='fork_create',
1045 1045 conditions={'function': check_repo, 'method': ['POST']},
1046 1046 requirements=URL_NAME_REQUIREMENTS)
1047 1047
1048 1048 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1049 1049 controller='forks', action='fork',
1050 1050 conditions={'function': check_repo},
1051 1051 requirements=URL_NAME_REQUIREMENTS)
1052 1052
1053 1053 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1054 1054 controller='forks', action='forks',
1055 1055 conditions={'function': check_repo},
1056 1056 requirements=URL_NAME_REQUIREMENTS)
1057 1057
1058 1058 rmap.connect('repo_followers_home', '/{repo_name}/followers',
1059 1059 controller='followers', action='followers',
1060 1060 conditions={'function': check_repo},
1061 1061 requirements=URL_NAME_REQUIREMENTS)
1062 1062
1063 1063 # must be here for proper group/repo catching pattern
1064 1064 _connect_with_slash(
1065 1065 rmap, 'repo_group_home', '/{group_name}',
1066 1066 controller='home', action='index_repo_group',
1067 1067 conditions={'function': check_group},
1068 1068 requirements=URL_NAME_REQUIREMENTS)
1069 1069
1070 1070 # catch all, at the end
1071 1071 _connect_with_slash(
1072 1072 rmap, 'summary_home', '/{repo_name}',
1073 1073 controller='summary', action='index',
1074 1074 conditions={'function': check_repo},
1075 1075 requirements=URL_NAME_REQUIREMENTS)
1076 1076
1077 1077 return rmap
1078 1078
1079 1079
1080 1080 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1081 1081 """
1082 1082 Connect a route with an optional trailing slash in `path`.
1083 1083 """
1084 1084 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1085 1085 mapper.connect(name, path, *args, **kwargs)
@@ -1,232 +1,277 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Home controller for RhodeCode Enterprise
23 23 """
24 24
25 25 import logging
26 26 import time
27
27 import re
28 28
29 from pylons import tmpl_context as c, request
29 from pylons import tmpl_context as c, request, url, config
30 30 from pylons.i18n.translation import _
31 31 from sqlalchemy.sql import func
32 32
33 33 from rhodecode.lib.auth import (
34 LoginRequired, HasPermissionAllDecorator,
34 LoginRequired, HasPermissionAllDecorator, AuthUser,
35 35 HasRepoGroupPermissionAnyDecorator, XHRRequired)
36 36 from rhodecode.lib.base import BaseController, render
37 from rhodecode.lib.index import searcher_from_config
37 38 from rhodecode.lib.ext_json import json
38 39 from rhodecode.lib.utils import jsonify
39 40 from rhodecode.lib.utils2 import safe_unicode
40 41 from rhodecode.model.db import Repository, RepoGroup
41 42 from rhodecode.model.repo import RepoModel
42 43 from rhodecode.model.repo_group import RepoGroupModel
43 44 from rhodecode.model.scm import RepoList, RepoGroupList
44 45
45 46
46 47 log = logging.getLogger(__name__)
47 48
48 49
49 50 class HomeController(BaseController):
50 51 def __before__(self):
51 52 super(HomeController, self).__before__()
52 53
53 54 def ping(self):
54 55 """
55 56 Ping, doesn't require login, good for checking out the platform
56 57 """
57 58 instance_id = getattr(c, 'rhodecode_instanceid', '')
58 59 return 'pong[%s] => %s' % (instance_id, self.ip_addr,)
59 60
60 61 @LoginRequired()
61 62 @HasPermissionAllDecorator('hg.admin')
62 63 def error_test(self):
63 64 """
64 65 Test exception handling and emails on errors
65 66 """
66 67 class TestException(Exception):
67 68 pass
68 69
69 70 msg = ('RhodeCode Enterprise %s test exception. Generation time: %s'
70 71 % (c.rhodecode_name, time.time()))
71 72 raise TestException(msg)
72 73
73 74 def _get_groups_and_repos(self, repo_group_id=None):
74 75 # repo groups groups
75 76 repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id)
76 77 _perms = ['group.read', 'group.write', 'group.admin']
77 78 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
78 79 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
79 80 repo_group_list=repo_group_list_acl, admin=False)
80 81
81 82 # repositories
82 83 repo_list = Repository.get_all_repos(group_id=repo_group_id)
83 84 _perms = ['repository.read', 'repository.write', 'repository.admin']
84 85 repo_list_acl = RepoList(repo_list, perm_set=_perms)
85 86 repo_data = RepoModel().get_repos_as_dict(
86 87 repo_list=repo_list_acl, admin=False)
87 88
88 89 return repo_data, repo_group_data
89 90
90 91 @LoginRequired()
91 92 def index(self):
92 93 c.repo_group = None
93 94
94 95 repo_data, repo_group_data = self._get_groups_and_repos()
95 96 # json used to render the grids
96 97 c.repos_data = json.dumps(repo_data)
97 98 c.repo_groups_data = json.dumps(repo_group_data)
98 99
99 100 return render('/index.html')
100 101
101 102 @LoginRequired()
102 103 @HasRepoGroupPermissionAnyDecorator('group.read', 'group.write',
103 104 'group.admin')
104 105 def index_repo_group(self, group_name):
105 106 """GET /repo_group_name: Show a specific item"""
106 107 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
107 108 repo_data, repo_group_data = self._get_groups_and_repos(
108 109 c.repo_group.group_id)
109 110
110 111 # json used to render the grids
111 112 c.repos_data = json.dumps(repo_data)
112 113 c.repo_groups_data = json.dumps(repo_group_data)
113 114
114 115 return render('index_repo_group.html')
115 116
116 117 def _get_repo_list(self, name_contains=None, repo_type=None, limit=20):
117 118 query = Repository.query()\
118 119 .order_by(func.length(Repository.repo_name))\
119 120 .order_by(Repository.repo_name)
120 121
121 122 if repo_type:
122 123 query = query.filter(Repository.repo_type == repo_type)
123 124
124 125 if name_contains:
125 126 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
126 127 query = query.filter(
127 128 Repository.repo_name.ilike(ilike_expression))
128 129 query = query.limit(limit)
129 130
130 131 all_repos = query.all()
131 132 repo_iter = self.scm_model.get_repos(all_repos)
132 133 return [
133 134 {
134 135 'id': obj['name'],
135 136 'text': obj['name'],
136 137 'type': 'repo',
137 'obj': obj['dbrepo']
138 'obj': obj['dbrepo'],
139 'url': url('summary_home', repo_name=obj['name'])
138 140 }
139 141 for obj in repo_iter]
140 142
141 143 def _get_repo_group_list(self, name_contains=None, limit=20):
142 144 query = RepoGroup.query()\
143 145 .order_by(func.length(RepoGroup.group_name))\
144 146 .order_by(RepoGroup.group_name)
145 147
146 148 if name_contains:
147 149 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
148 150 query = query.filter(
149 151 RepoGroup.group_name.ilike(ilike_expression))
150 152 query = query.limit(limit)
151 153
152 154 all_groups = query.all()
153 155 repo_groups_iter = self.scm_model.get_repo_groups(all_groups)
154 156 return [
155 157 {
156 158 'id': obj.group_name,
157 159 'text': obj.group_name,
158 160 'type': 'group',
159 'obj': {}
161 'obj': {},
162 'url': url('repo_group_home', group_name=obj.group_name)
160 163 }
161 164 for obj in repo_groups_iter]
162 165
166 def _get_hash_commit_list(self, hash_starts_with=None, limit=20):
167 if not hash_starts_with or len(hash_starts_with) < 3:
168 return []
169
170 commit_hashes = re.compile('([0-9a-f]{2,40})').findall(hash_starts_with)
171
172 if len(commit_hashes) != 1:
173 return []
174
175 commit_hash_prefix = commit_hashes[0]
176
177 auth_user = AuthUser(
178 user_id=c.rhodecode_user.user_id, ip_addr=self.ip_addr)
179 searcher = searcher_from_config(config)
180 result = searcher.search(
181 'commit_id:%s*' % commit_hash_prefix, 'commit', auth_user)
182
183 return [
184 {
185 'id': entry['commit_id'],
186 'text': entry['commit_id'],
187 'type': 'commit',
188 'obj': {'repo': entry['repository']},
189 'url': url('changeset_home',
190 repo_name=entry['repository'], revision=entry['commit_id'])
191 }
192 for entry in result['results']]
193
163 194 @LoginRequired()
164 195 @XHRRequired()
165 196 @jsonify
166 def repo_switcher_data(self):
197 def goto_switcher_data(self):
167 198 query = request.GET.get('query')
168 log.debug('generating switcher repo/groups list, query %s', query)
199 log.debug('generating goto switcher list, query %s', query)
169 200
170 201 res = []
171 202 repo_groups = self._get_repo_group_list(query)
172 203 if repo_groups:
173 204 res.append({
174 205 'text': _('Groups'),
175 206 'children': repo_groups
176 207 })
177 208
178 209 repos = self._get_repo_list(query)
179 210 if repos:
180 211 res.append({
181 212 'text': _('Repositories'),
182 213 'children': repos
183 214 })
184 215
216 commits = self._get_hash_commit_list(query)
217 if commits:
218 unique_repos = {}
219 for commit in commits:
220 unique_repos.setdefault(commit['obj']['repo'], []
221 ).append(commit)
222
223 for repo in unique_repos:
224 res.append({
225 'text': _('Commits in %(repo)s') % {'repo': repo},
226 'children': unique_repos[repo]
227 })
228
185 229 data = {
186 230 'more': False,
187 231 'results': res
188 232 }
189 233 return data
190 234
191 235 @LoginRequired()
192 236 @XHRRequired()
193 237 @jsonify
194 238 def repo_list_data(self):
195 239 query = request.GET.get('query')
196 240 repo_type = request.GET.get('repo_type')
197 241 log.debug('generating repo list, query:%s', query)
198 242
199 243 res = []
200 244 repos = self._get_repo_list(query, repo_type=repo_type)
201 245 if repos:
202 246 res.append({
203 247 'text': _('Repositories'),
204 248 'children': repos
205 249 })
250
206 251 data = {
207 252 'more': False,
208 253 'results': res
209 254 }
210 255 return data
211 256
212 257 @LoginRequired()
213 258 @XHRRequired()
214 259 @jsonify
215 260 def user_autocomplete_data(self):
216 261 query = request.GET.get('query')
217 262
218 263 repo_model = RepoModel()
219 264 _users = repo_model.get_users(name_contains=query)
220 265
221 266 if request.GET.get('user_groups'):
222 267 # extend with user groups
223 268 _user_groups = repo_model.get_user_groups(name_contains=query)
224 269 _users = _users + _user_groups
225 270
226 271 return {'suggestions': _users}
227 272
228 273 @LoginRequired()
229 274 @XHRRequired()
230 275 @jsonify
231 276 def user_group_autocomplete_data(self):
232 277 return {'suggestions': []}
@@ -1,56 +1,55 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Index schema for RhodeCode
23 23 """
24 24
25 25 import importlib
26 26 import logging
27 27
28 28 log = logging.getLogger(__name__)
29 29
30 30 # leave defaults for backward compat
31 31 default_searcher = 'rhodecode.lib.index.whoosh'
32 32 default_location = '%(here)s/data/index'
33 33
34 34
35 35 class BaseSearch(object):
36 36 def __init__(self):
37 37 pass
38 38
39 39 def cleanup(self):
40 40 pass
41 41
42 42 def search(self, query, document_type, search_user, repo_name=None):
43 43 raise Exception('NotImplemented')
44 44
45
46 45 def searcher_from_config(config, prefix='search.'):
47 46 _config = {}
48 47 for key in config.keys():
49 48 if key.startswith(prefix):
50 49 _config[key[len(prefix):]] = config[key]
51 50
52 51 if 'location' not in _config:
53 52 _config['location'] = default_location
54 53 imported = importlib.import_module(_config.get('module', default_searcher))
55 54 searcher = imported.Search(config=_config)
56 55 return searcher
@@ -1,261 +1,274 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Index schema for RhodeCode
23 23 """
24 24
25 25 from __future__ import absolute_import
26 26 import logging
27 27 import os
28 import re
28 29
29 30 from pylons.i18n.translation import _
30 31
31 32 from whoosh import query as query_lib, sorting
32 33 from whoosh.highlight import HtmlFormatter, ContextFragmenter
33 34 from whoosh.index import create_in, open_dir, exists_in, EmptyIndexError
34 35 from whoosh.qparser import QueryParser, QueryParserError
35 36
36 37 import rhodecode.lib.helpers as h
37 38 from rhodecode.lib.index import BaseSearch
38 39
39 40 log = logging.getLogger(__name__)
40 41
41 42
42 43 try:
43 44 # we first try to import from rhodecode tools, fallback to copies if
44 45 # we're unable to
45 46 from rhodecode_tools.lib.fts_index.whoosh_schema import (
46 47 ANALYZER, FILE_INDEX_NAME, FILE_SCHEMA, COMMIT_INDEX_NAME,
47 48 COMMIT_SCHEMA)
48 49 except ImportError:
49 50 log.warning('rhodecode_tools schema not available, doing a fallback '
50 51 'import from `rhodecode.lib.index.whoosh_fallback_schema`')
51 52 from rhodecode.lib.index.whoosh_fallback_schema import (
52 53 ANALYZER, FILE_INDEX_NAME, FILE_SCHEMA, COMMIT_INDEX_NAME,
53 54 COMMIT_SCHEMA)
54 55
55 56
56 57 FORMATTER = HtmlFormatter('span', between='\n<span class="break">...</span>\n')
57 58 FRAGMENTER = ContextFragmenter(200)
58 59
59 60 log = logging.getLogger(__name__)
60 61
61 62
63
62 64 class Search(BaseSearch):
63 65
64 66 name = 'whoosh'
65 67
66 68 def __init__(self, config):
67 69 self.config = config
68 70 if not os.path.isdir(self.config['location']):
69 71 os.makedirs(self.config['location'])
70 72
71 73 opener = create_in
72 74 if exists_in(self.config['location'], indexname=FILE_INDEX_NAME):
73 75 opener = open_dir
74 76 file_index = opener(self.config['location'], schema=FILE_SCHEMA,
75 77 indexname=FILE_INDEX_NAME)
76 78
77 79 opener = create_in
78 80 if exists_in(self.config['location'], indexname=COMMIT_INDEX_NAME):
79 81 opener = open_dir
80 82 changeset_index = opener(self.config['location'], schema=COMMIT_SCHEMA,
81 83 indexname=COMMIT_INDEX_NAME)
82 84
83 85 self.commit_schema = COMMIT_SCHEMA
84 86 self.commit_index = changeset_index
85 87 self.file_schema = FILE_SCHEMA
86 88 self.file_index = file_index
87 89 self.searcher = None
88 90
89 91 def cleanup(self):
90 92 if self.searcher:
91 93 self.searcher.close()
92 94
95 def _extend_query(self, query):
96 hashes = re.compile('([0-9a-f]{5,40})').findall(query)
97 if hashes:
98 hashes_or_query = ' OR '.join('commit_id:%s*' % h for h in hashes)
99 query = u'(%s) OR %s' % (query, hashes_or_query)
100 return query
101
93 102 def search(self, query, document_type, search_user, repo_name=None,
94 103 requested_page=1, page_limit=10):
104
105 original_query = query
106 query = self._extend_query(query)
107
95 108 log.debug(u'QUERY: %s on %s', query, document_type)
96 109 result = {
97 110 'results': [],
98 111 'count': 0,
99 112 'error': None,
100 113 'runtime': 0
101 114 }
102 115 search_type, index_name, schema_defn = self._prepare_for_search(
103 116 document_type)
104 117 self._init_searcher(index_name)
105 118 try:
106 119 qp = QueryParser(search_type, schema=schema_defn)
107 120 allowed_repos_filter = self._get_repo_filter(
108 121 search_user, repo_name)
109 122 try:
110 123 query = qp.parse(unicode(query))
111 124 log.debug('query: %s (%s)' % (query, repr(query)))
112 125
113 126 sortedby = None
114 127 if search_type == 'message':
115 128 sortedby = sorting.FieldFacet('commit_idx', reverse=True)
116 129
117 130 whoosh_results = self.searcher.search(
118 131 query, filter=allowed_repos_filter, limit=None,
119 132 sortedby=sortedby,)
120 133
121 134 # fixes for 32k limit that whoosh uses for highlight
122 135 whoosh_results.fragmenter.charlimit = None
123 136 res_ln = whoosh_results.scored_length()
124 137 result['runtime'] = whoosh_results.runtime
125 138 result['count'] = res_ln
126 139 result['results'] = WhooshResultWrapper(
127 140 search_type, res_ln, whoosh_results)
128 141
129 142 except QueryParserError:
130 143 result['error'] = _('Invalid search query. Try quoting it.')
131 144 except (EmptyIndexError, IOError, OSError):
132 145 msg = _('There is no index to search in. '
133 146 'Please run whoosh indexer')
134 147 log.exception(msg)
135 148 result['error'] = msg
136 149 except Exception:
137 150 msg = _('An error occurred during this search operation')
138 151 log.exception(msg)
139 152 result['error'] = msg
140 153
141 154 return result
142 155
143 156 def statistics(self):
144 157 stats = [
145 158 {'key': _('Index Type'), 'value': 'Whoosh'},
146 159 {'key': _('File Index'), 'value': str(self.file_index)},
147 160 {'key': _('Indexed documents'),
148 161 'value': self.file_index.doc_count()},
149 162 {'key': _('Last update'),
150 163 'value': h.time_to_datetime(self.file_index.last_modified())},
151 164 {'key': _('Commit index'), 'value': str(self.commit_index)},
152 165 {'key': _('Indexed documents'),
153 166 'value': str(self.commit_index.doc_count())},
154 167 {'key': _('Last update'),
155 168 'value': h.time_to_datetime(self.commit_index.last_modified())}
156 169 ]
157 170 return stats
158 171
159 172 def _get_repo_filter(self, auth_user, repo_name):
160 173
161 174 allowed_to_search = [
162 175 repo for repo, perm in
163 176 auth_user.permissions['repositories'].items()
164 177 if perm != 'repository.none']
165 178
166 179 if repo_name:
167 180 repo_filter = [query_lib.Term('repository', repo_name)]
168 181
169 182 elif 'hg.admin' in auth_user.permissions.get('global', []):
170 183 return None
171 184
172 185 else:
173 186 repo_filter = [query_lib.Term('repository', _rn)
174 187 for _rn in allowed_to_search]
175 188 # in case we're not allowed to search anywhere, it's a trick
176 189 # to tell whoosh we're filtering, on ALL results
177 190 repo_filter = repo_filter or [query_lib.Term('repository', '')]
178 191
179 192 return query_lib.Or(repo_filter)
180 193
181 194 def _prepare_for_search(self, cur_type):
182 195 search_type = {
183 196 'content': 'content',
184 197 'commit': 'message',
185 198 'path': 'path',
186 199 'repository': 'repository'
187 200 }.get(cur_type, 'content')
188 201
189 202 index_name = {
190 203 'content': FILE_INDEX_NAME,
191 204 'commit': COMMIT_INDEX_NAME,
192 205 'path': FILE_INDEX_NAME
193 206 }.get(cur_type, FILE_INDEX_NAME)
194 207
195 208 schema_defn = {
196 209 'content': self.file_schema,
197 210 'commit': self.commit_schema,
198 211 'path': self.file_schema
199 212 }.get(cur_type, self.file_schema)
200 213
201 214 log.debug('IDX: %s' % index_name)
202 215 log.debug('SCHEMA: %s' % schema_defn)
203 216 return search_type, index_name, schema_defn
204 217
205 218 def _init_searcher(self, index_name):
206 219 idx = open_dir(self.config['location'], indexname=index_name)
207 220 self.searcher = idx.searcher()
208 221 return self.searcher
209 222
210 223
211 224 class WhooshResultWrapper(object):
212 225 def __init__(self, search_type, total_hits, results):
213 226 self.search_type = search_type
214 227 self.results = results
215 228 self.total_hits = total_hits
216 229
217 230 def __str__(self):
218 231 return '<%s at %s>' % (self.__class__.__name__, len(self))
219 232
220 233 def __repr__(self):
221 234 return self.__str__()
222 235
223 236 def __len__(self):
224 237 return self.total_hits
225 238
226 239 def __iter__(self):
227 240 """
228 241 Allows Iteration over results,and lazy generate content
229 242
230 243 *Requires* implementation of ``__getitem__`` method.
231 244 """
232 245 for hit in self.results:
233 246 yield self.get_full_content(hit)
234 247
235 248 def __getitem__(self, key):
236 249 """
237 250 Slicing of resultWrapper
238 251 """
239 252 i, j = key.start, key.stop
240 253 for hit in self.results[i:j]:
241 254 yield self.get_full_content(hit)
242 255
243 256 def get_full_content(self, hit):
244 257 # TODO: marcink: this feels like an overkill, there's a lot of data
245 258 # inside hit object, and we don't need all
246 259 res = dict(hit)
247 260
248 261 f_path = '' # noqa
249 262 if self.search_type in ['content', 'path']:
250 263 f_path = res['path'].split(res['repository'])[-1]
251 264 f_path = f_path.lstrip(os.sep)
252 265
253 266 if self.search_type == 'content':
254 267 res.update({'content_short_hl': hit.highlights('content'),
255 268 'f_path': f_path})
256 269 elif self.search_type == 'path':
257 270 res.update({'f_path': f_path})
258 271 elif self.search_type == 'message':
259 272 res.update({'message_hl': hit.highlights('message')})
260 273
261 274 return res
@@ -1,652 +1,655 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="root.html"/>
3 3
4 4 <div class="outerwrapper">
5 5 <!-- HEADER -->
6 6 <div class="header">
7 7 <div id="header-inner" class="wrapper">
8 8 <div id="logo">
9 9 <div class="logo-wrapper">
10 10 <a href="${h.url('home')}"><img src="${h.url('/images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
11 11 </div>
12 12 %if c.rhodecode_name:
13 13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
14 14 %endif
15 15 </div>
16 16 <!-- MENU BAR NAV -->
17 17 ${self.menu_bar_nav()}
18 18 <!-- END MENU BAR NAV -->
19 19 ${self.body()}
20 20 </div>
21 21 </div>
22 22 ${self.menu_bar_subnav()}
23 23 <!-- END HEADER -->
24 24
25 25 <!-- CONTENT -->
26 26 <div id="content" class="wrapper">
27 27 ${self.flash_msg()}
28 28 <div class="main">
29 29 ${next.main()}
30 30 </div>
31 31 </div>
32 32 <!-- END CONTENT -->
33 33
34 34 </div>
35 35 <!-- FOOTER -->
36 36 <div id="footer">
37 37 <div id="footer-inner" class="title wrapper">
38 38 <div>
39 39 <p class="footer-link-right">
40 40 % if c.visual.show_version:
41 41 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
42 42 % endif
43 43 &copy; 2010-${h.datetime.today().year}, <a href="${h.url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
44 44 % if c.visual.rhodecode_support_url:
45 45 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
46 46 % endif
47 47 </p>
48 48 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
49 49 <p class="server-instance" style="display:${sid}">
50 50 ## display hidden instance ID if specially defined
51 51 % if c.rhodecode_instanceid:
52 52 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
53 53 % endif
54 54 </p>
55 55 </div>
56 56 </div>
57 57 </div>
58 58
59 59 <!-- END FOOTER -->
60 60
61 61 ### MAKO DEFS ###
62 62
63 63 <%def name="menu_bar_subnav()">
64 64 </%def>
65 65
66 66 <%def name="flash_msg()">
67 67 <%include file="/base/flash_msg.html"/>
68 68 </%def>
69 69
70 70 <%def name="breadcrumbs(class_='breadcrumbs')">
71 71 <div class="${class_}">
72 72 ${self.breadcrumbs_links()}
73 73 </div>
74 74 </%def>
75 75
76 76 <%def name="admin_menu()">
77 77 <ul class="admin_menu submenu">
78 78 <li><a href="${h.url('admin_home')}">${_('Admin journal')}</a></li>
79 79 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
80 80 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
81 81 <li><a href="${h.url('users')}">${_('Users')}</a></li>
82 82 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
83 83 <li><a href="${h.url('admin_permissions_application')}">${_('Permissions')}</a></li>
84 84 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
85 85 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
86 86 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
87 87 </ul>
88 88 </%def>
89 89
90 90
91 91 <%def name="dt_info_panel(elements)">
92 92 <dl class="dl-horizontal">
93 93 %for dt, dd, title, show_items in elements:
94 94 <dt>${dt}:</dt>
95 95 <dd title="${title}">
96 96 %if callable(dd):
97 97 ## allow lazy evaluation of elements
98 98 ${dd()}
99 99 %else:
100 100 ${dd}
101 101 %endif
102 102 %if show_items:
103 103 <span class="btn-collapse" data-toggle="item-${h.md5(dt)[:6]}-details">${_('Show More')} </span>
104 104 %endif
105 105 </dd>
106 106
107 107 %if show_items:
108 108 <div class="collapsable-content" data-toggle="item-${h.md5(dt)[:6]}-details" style="display: none">
109 109 %for item in show_items:
110 110 <dt></dt>
111 111 <dd>${item}</dd>
112 112 %endfor
113 113 </div>
114 114 %endif
115 115
116 116 %endfor
117 117 </dl>
118 118 </%def>
119 119
120 120
121 121 <%def name="gravatar(email, size=16)">
122 122 <%
123 123 if (size > 16):
124 124 gravatar_class = 'gravatar gravatar-large'
125 125 else:
126 126 gravatar_class = 'gravatar'
127 127 %>
128 128 <%doc>
129 129 TODO: johbo: For now we serve double size images to make it smooth
130 130 for retina. This is how it worked until now. Should be replaced
131 131 with a better solution at some point.
132 132 </%doc>
133 133 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
134 134 </%def>
135 135
136 136
137 137 <%def name="gravatar_with_user(contact, size=16)">
138 138 <div class="rc-user tooltip" title="${contact}">
139 139 ${self.gravatar(h.email_or_none(contact), size)}
140 140 <span class="user"> ${h.link_to_user(contact)}</span>
141 141 </div>
142 142 </%def>
143 143
144 144
145 145 ## admin menu used for people that have some admin resources
146 146 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
147 147 <ul class="submenu">
148 148 %if repositories:
149 149 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
150 150 %endif
151 151 %if repository_groups:
152 152 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
153 153 %endif
154 154 %if user_groups:
155 155 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
156 156 %endif
157 157 </ul>
158 158 </%def>
159 159
160 160 <%def name="repo_page_title(repo_instance)">
161 161 <div class="title-content">
162 162 <div class="title-main">
163 163 ## SVN/HG/GIT icons
164 164 %if h.is_hg(repo_instance):
165 165 <i class="icon-hg"></i>
166 166 %endif
167 167 %if h.is_git(repo_instance):
168 168 <i class="icon-git"></i>
169 169 %endif
170 170 %if h.is_svn(repo_instance):
171 171 <i class="icon-svn"></i>
172 172 %endif
173 173
174 174 ## public/private
175 175 %if repo_instance.private:
176 176 <i class="icon-repo-private"></i>
177 177 %else:
178 178 <i class="icon-repo-public"></i>
179 179 %endif
180 180
181 181 ## repo name with group name
182 182 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
183 183
184 184 </div>
185 185
186 186 ## FORKED
187 187 %if repo_instance.fork:
188 188 <p>
189 189 <i class="icon-code-fork"></i> ${_('Fork of')}
190 190 <a href="${h.url('summary_home',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
191 191 </p>
192 192 %endif
193 193
194 194 ## IMPORTED FROM REMOTE
195 195 %if repo_instance.clone_uri:
196 196 <p>
197 197 <i class="icon-code-fork"></i> ${_('Clone from')}
198 198 <a href="${h.url(str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
199 199 </p>
200 200 %endif
201 201
202 202 ## LOCKING STATUS
203 203 %if repo_instance.locked[0]:
204 204 <p class="locking_locked">
205 205 <i class="icon-repo-lock"></i>
206 206 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
207 207 </p>
208 208 %elif repo_instance.enable_locking:
209 209 <p class="locking_unlocked">
210 210 <i class="icon-repo-unlock"></i>
211 211 ${_('Repository not locked. Pull repository to lock it.')}
212 212 </p>
213 213 %endif
214 214
215 215 </div>
216 216 </%def>
217 217
218 218 <%def name="repo_menu(active=None)">
219 219 <%
220 220 def is_active(selected):
221 221 if selected == active:
222 222 return "active"
223 223 %>
224 224
225 225 <!--- CONTEXT BAR -->
226 226 <div id="context-bar">
227 227 <div class="wrapper">
228 228 <ul id="context-pages" class="horizontal-list navigation">
229 229 <li class="${is_active('summary')}"><a class="menulink" href="${h.url('summary_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
230 230 <li class="${is_active('changelog')}"><a class="menulink" href="${h.url('changelog_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
231 231 <li class="${is_active('files')}"><a class="menulink" href="${h.url('files_home', repo_name=c.repo_name, revision=c.rhodecode_db_repo.landing_rev[1])}"><div class="menulabel">${_('Files')}</div></a></li>
232 232 <li class="${is_active('compare')}">
233 233 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
234 234 </li>
235 235 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
236 236 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
237 237 <li class="${is_active('showpullrequest')}">
238 238 <a class="menulink" href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests for %s') % c.repo_name}">
239 239 %if c.repository_pull_requests:
240 240 <span class="pr_notifications">${c.repository_pull_requests}</span>
241 241 %endif
242 242 <div class="menulabel">${_('Pull Requests')}</div>
243 243 </a>
244 244 </li>
245 245 %endif
246 246 <li class="${is_active('options')}">
247 247 <a class="menulink" href="#" class="dropdown"><div class="menulabel">${_('Options')} <div class="show_more"></div></div></a>
248 248 <ul class="submenu">
249 249 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
250 250 <li><a href="${h.url('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
251 251 %endif
252 252 %if c.rhodecode_db_repo.fork:
253 253 <li><a href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1], target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], merge=1)}">
254 254 ${_('Compare fork')}</a></li>
255 255 %endif
256 256
257 257 <li><a href="${h.url('search_repo_home',repo_name=c.repo_name)}">${_('Search')}</a></li>
258 258
259 259 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
260 260 %if c.rhodecode_db_repo.locked[0]:
261 261 <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
262 262 %else:
263 263 <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
264 264 %endif
265 265 %endif
266 266 %if c.rhodecode_user.username != h.DEFAULT_USER:
267 267 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
268 268 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
269 269 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
270 270 %endif
271 271 %endif
272 272 </ul>
273 273 </li>
274 274 </ul>
275 275 </div>
276 276 <div class="clear"></div>
277 277 </div>
278 278 <!--- END CONTEXT BAR -->
279 279
280 280 </%def>
281 281
282 282 <%def name="usermenu()">
283 283 ## USER MENU
284 284 <li id="quick_login_li">
285 285 <a id="quick_login_link" class="menulink childs">
286 286 ${gravatar(c.rhodecode_user.email, 20)}
287 287 <span class="user">
288 288 %if c.rhodecode_user.username != h.DEFAULT_USER:
289 289 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
290 290 %else:
291 291 <span>${_('Sign in')}</span>
292 292 %endif
293 293 </span>
294 294 </a>
295 295
296 296 <div class="user-menu submenu">
297 297 <div id="quick_login">
298 298 %if c.rhodecode_user.username == h.DEFAULT_USER:
299 299 <h4>${_('Sign in to your account')}</h4>
300 300 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
301 301 <div class="form form-vertical">
302 302 <div class="fields">
303 303 <div class="field">
304 304 <div class="label">
305 305 <label for="username">${_('Username')}:</label>
306 306 </div>
307 307 <div class="input">
308 308 ${h.text('username',class_='focus',tabindex=1)}
309 309 </div>
310 310
311 311 </div>
312 312 <div class="field">
313 313 <div class="label">
314 314 <label for="password">${_('Password')}:</label>
315 315 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'))}</span>
316 316 </div>
317 317 <div class="input">
318 318 ${h.password('password',class_='focus',tabindex=2)}
319 319 </div>
320 320 </div>
321 321 <div class="buttons">
322 322 <div class="register">
323 323 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
324 324 ${h.link_to(_("Don't have an account ?"),h.route_path('register'))}
325 325 %endif
326 326 </div>
327 327 <div class="submit">
328 328 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
329 329 </div>
330 330 </div>
331 331 </div>
332 332 </div>
333 333 ${h.end_form()}
334 334 %else:
335 335 <div class="">
336 336 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
337 337 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
338 338 <div class="email">${c.rhodecode_user.email}</div>
339 339 </div>
340 340 <div class="">
341 341 <ol class="links">
342 342 <li>${h.link_to(_(u'My account'),h.url('my_account'))}</li>
343 343 <li class="logout">
344 344 ${h.secure_form(h.route_path('logout'))}
345 345 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
346 346 ${h.end_form()}
347 347 </li>
348 348 </ol>
349 349 </div>
350 350 %endif
351 351 </div>
352 352 </div>
353 353 %if c.rhodecode_user.username != h.DEFAULT_USER:
354 354 <div class="pill_container">
355 355 % if c.unread_notifications == 0:
356 356 <a class="menu_link_notifications empty" href="${h.url('notifications')}">${c.unread_notifications}</a>
357 357 % else:
358 358 <a class="menu_link_notifications" href="${h.url('notifications')}">${c.unread_notifications}</a>
359 359 % endif
360 360 </div>
361 361 % endif
362 362 </li>
363 363 </%def>
364 364
365 365 <%def name="menu_items(active=None)">
366 366 <%
367 367 def is_active(selected):
368 368 if selected == active:
369 369 return "active"
370 370 return ""
371 371 %>
372 372 <ul id="quick" class="main_nav navigation horizontal-list">
373 373 <!-- repo switcher -->
374 374 <li class="${is_active('repositories')} repo_switcher_li has_select2">
375 375 <input id="repo_switcher" name="repo_switcher" type="hidden">
376 376 </li>
377 377
378 378 ## ROOT MENU
379 379 %if c.rhodecode_user.username != h.DEFAULT_USER:
380 380 <li class="${is_active('journal')}">
381 381 <a class="menulink" title="${_('Show activity journal')}" href="${h.url('journal')}">
382 382 <div class="menulabel">${_('Journal')}</div>
383 383 </a>
384 384 </li>
385 385 %else:
386 386 <li class="${is_active('journal')}">
387 387 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.url('public_journal')}">
388 388 <div class="menulabel">${_('Public journal')}</div>
389 389 </a>
390 390 </li>
391 391 %endif
392 392 <li class="${is_active('gists')}">
393 393 <a class="menulink childs" title="${_('Show Gists')}" href="${h.url('gists')}">
394 394 <div class="menulabel">${_('Gists')}</div>
395 395 </a>
396 396 </li>
397 397 <li class="${is_active('search')}">
398 398 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.url('search')}">
399 399 <div class="menulabel">${_('Search')}</div>
400 400 </a>
401 401 </li>
402 402 % if h.HasPermissionAll('hg.admin')('access admin main page'):
403 403 <li class="${is_active('admin')}">
404 404 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
405 405 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
406 406 </a>
407 407 ${admin_menu()}
408 408 </li>
409 409 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
410 410 <li class="${is_active('admin')}">
411 411 <a class="menulink childs" title="${_('Delegated Admin settings')}">
412 412 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
413 413 </a>
414 414 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
415 415 c.rhodecode_user.repository_groups_admin,
416 416 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
417 417 </li>
418 418 % endif
419 419 % if c.debug_style:
420 420 <li class="${is_active('debug_style')}">
421 421 <a class="menulink" title="${_('Style')}" href="${h.url('debug_style_home')}">
422 422 <div class="menulabel">${_('Style')}</div>
423 423 </a>
424 424 </li>
425 425 % endif
426 426 ## render extra user menu
427 427 ${usermenu()}
428 428 </ul>
429 429
430 430 <script type="text/javascript">
431 431 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
432 432
433 433 /*format the look of items in the list*/
434 434 var format = function(state, escapeMarkup){
435 435 if (!state.id){
436 436 return state.text; // optgroup
437 437 }
438 438 var obj_dict = state.obj;
439 439 var tmpl = '';
440 440
441 441 if(obj_dict && state.type == 'repo'){
442 442 if(obj_dict['repo_type'] === 'hg'){
443 443 tmpl += '<i class="icon-hg"></i> ';
444 444 }
445 445 else if(obj_dict['repo_type'] === 'git'){
446 446 tmpl += '<i class="icon-git"></i> ';
447 447 }
448 448 else if(obj_dict['repo_type'] === 'svn'){
449 449 tmpl += '<i class="icon-svn"></i> ';
450 450 }
451 451 if(obj_dict['private']){
452 452 tmpl += '<i class="icon-lock" ></i> ';
453 453 }
454 454 else if(visual_show_public_icon){
455 455 tmpl += '<i class="icon-unlock-alt"></i> ';
456 456 }
457 457 }
458 if(obj_dict && state.type == 'commit') {
459 tmpl += '<i class="icon-tag"></i>';
460 }
458 461 if(obj_dict && state.type == 'group'){
459 tmpl += '<i class="icon-folder-close"></i> ';
462 tmpl += '<i class="icon-folder-close"></i> ';
460 463 }
461 464 tmpl += escapeMarkup(state.text);
462 465 return tmpl;
463 466 };
464 467
465 468 var formatResult = function(result, container, query, escapeMarkup) {
466 469 return format(result, escapeMarkup);
467 470 };
468 471
469 472 var formatSelection = function(data, container, escapeMarkup) {
470 473 return format(data, escapeMarkup);
471 474 };
472 475
473 476 $("#repo_switcher").select2({
474 477 cachedDataSource: {},
475 478 minimumInputLength: 2,
476 479 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
477 480 dropdownAutoWidth: true,
478 481 formatResult: formatResult,
479 482 formatSelection: formatSelection,
480 483 containerCssClass: "repo-switcher",
481 484 dropdownCssClass: "repo-switcher-dropdown",
482 485 escapeMarkup: function(m){
483 486 // don't escape our custom placeholder
484 487 if(m.substr(0,23) == '<div class="menulabel">'){
485 488 return m;
486 489 }
487 490
488 491 return Select2.util.escapeMarkup(m);
489 492 },
490 493 query: $.debounce(250, function(query){
491 494 self = this;
492 495 var cacheKey = query.term;
493 496 var cachedData = self.cachedDataSource[cacheKey];
494 497
495 498 if (cachedData) {
496 499 query.callback({results: cachedData.results});
497 500 } else {
498 501 $.ajax({
499 url: "${h.url('repo_switcher_data')}",
502 url: "${h.url('goto_switcher_data')}",
500 503 data: {'query': query.term},
501 504 dataType: 'json',
502 505 type: 'GET',
503 506 success: function(data) {
504 507 self.cachedDataSource[cacheKey] = data;
505 508 query.callback({results: data.results});
506 509 },
507 510 error: function(data, textStatus, errorThrown) {
508 511 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
509 512 }
510 513 })
511 514 }
512 515 })
513 516 });
514 517
515 518 $("#repo_switcher").on('select2-selecting', function(e){
516 519 e.preventDefault();
517 window.location = pyroutes.url('summary_home', {'repo_name': e.val});
520 window.location = e.choice.url;
518 521 });
519 522
520 523 ## Global mouse bindings ##
521 524
522 525 // general help "?"
523 526 Mousetrap.bind(['?'], function(e) {
524 527 $('#help_kb').modal({})
525 528 });
526 529
527 530 // / open the quick filter
528 531 Mousetrap.bind(['/'], function(e) {
529 532 $("#repo_switcher").select2("open");
530 533
531 534 // return false to prevent default browser behavior
532 535 // and stop event from bubbling
533 536 return false;
534 537 });
535 538
536 539 // general nav g + action
537 540 Mousetrap.bind(['g h'], function(e) {
538 541 window.location = pyroutes.url('home');
539 542 });
540 543 Mousetrap.bind(['g g'], function(e) {
541 544 window.location = pyroutes.url('gists', {'private':1});
542 545 });
543 546 Mousetrap.bind(['g G'], function(e) {
544 547 window.location = pyroutes.url('gists', {'public':1});
545 548 });
546 549 Mousetrap.bind(['n g'], function(e) {
547 550 window.location = pyroutes.url('new_gist');
548 551 });
549 552 Mousetrap.bind(['n r'], function(e) {
550 553 window.location = pyroutes.url('new_repo');
551 554 });
552 555
553 556 % if hasattr(c, 'repo_name') and hasattr(c, 'rhodecode_db_repo'):
554 557 // nav in repo context
555 558 Mousetrap.bind(['g s'], function(e) {
556 559 window.location = pyroutes.url('summary_home', {'repo_name': REPO_NAME});
557 560 });
558 561 Mousetrap.bind(['g c'], function(e) {
559 562 window.location = pyroutes.url('changelog_home', {'repo_name': REPO_NAME});
560 563 });
561 564 Mousetrap.bind(['g F'], function(e) {
562 565 window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.rhodecode_db_repo.landing_rev[1]}', 'f_path': '', 'search': '1'});
563 566 });
564 567 Mousetrap.bind(['g f'], function(e) {
565 568 window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.rhodecode_db_repo.landing_rev[1]}', 'f_path': ''});
566 569 });
567 570 Mousetrap.bind(['g p'], function(e) {
568 571 window.location = pyroutes.url('pullrequest_show_all', {'repo_name': REPO_NAME});
569 572 });
570 573 Mousetrap.bind(['g o'], function(e) {
571 574 window.location = pyroutes.url('edit_repo', {'repo_name': REPO_NAME});
572 575 });
573 576 Mousetrap.bind(['g O'], function(e) {
574 577 window.location = pyroutes.url('edit_repo_perms', {'repo_name': REPO_NAME});
575 578 });
576 579 % endif
577 580
578 581 </script>
579 582 <script src="${h.url('/js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
580 583 </%def>
581 584
582 585 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
583 586 <div class="modal-dialog">
584 587 <div class="modal-content">
585 588 <div class="modal-header">
586 589 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
587 590 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
588 591 </div>
589 592 <div class="modal-body">
590 593 <div class="block-left">
591 594 <table class="keyboard-mappings">
592 595 <tbody>
593 596 <tr>
594 597 <th></th>
595 598 <th>${_('Site-wide shortcuts')}</th>
596 599 </tr>
597 600 <%
598 601 elems = [
599 602 ('/', 'Open quick search box'),
600 603 ('g h', 'Goto home page'),
601 604 ('g g', 'Goto my private gists page'),
602 605 ('g G', 'Goto my public gists page'),
603 606 ('n r', 'New repository page'),
604 607 ('n g', 'New gist page'),
605 608 ]
606 609 %>
607 610 %for key, desc in elems:
608 611 <tr>
609 612 <td class="keys">
610 613 <span class="key tag">${key}</span>
611 614 </td>
612 615 <td>${desc}</td>
613 616 </tr>
614 617 %endfor
615 618 </tbody>
616 619 </table>
617 620 </div>
618 621 <div class="block-left">
619 622 <table class="keyboard-mappings">
620 623 <tbody>
621 624 <tr>
622 625 <th></th>
623 626 <th>${_('Repositories')}</th>
624 627 </tr>
625 628 <%
626 629 elems = [
627 630 ('g s', 'Goto summary page'),
628 631 ('g c', 'Goto changelog page'),
629 632 ('g f', 'Goto files page'),
630 633 ('g F', 'Goto files page with file search activated'),
631 634 ('g p', 'Goto pull requests page'),
632 635 ('g o', 'Goto repository settings'),
633 636 ('g O', 'Goto repository permissions settings'),
634 637 ]
635 638 %>
636 639 %for key, desc in elems:
637 640 <tr>
638 641 <td class="keys">
639 642 <span class="key tag">${key}</span>
640 643 </td>
641 644 <td>${desc}</td>
642 645 </tr>
643 646 %endfor
644 647 </tbody>
645 648 </table>
646 649 </div>
647 650 </div>
648 651 <div class="modal-footer">
649 652 </div>
650 653 </div><!-- /.modal-content -->
651 654 </div><!-- /.modal-dialog -->
652 655 </div><!-- /.modal -->
@@ -1,353 +1,365 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import json
22 22
23 23 from mock import patch
24 24 import pytest
25 25
26 26 import rhodecode
27 27 from rhodecode.lib.utils import map_groups
28 28 from rhodecode.model.db import Repository, User, RepoGroup
29 29 from rhodecode.model.meta import Session
30 30 from rhodecode.model.repo import RepoModel
31 31 from rhodecode.model.repo_group import RepoGroupModel
32 32 from rhodecode.model.settings import SettingsModel
33 33 from rhodecode.tests import TestController, url, TEST_USER_ADMIN_LOGIN
34 34 from rhodecode.tests.fixture import Fixture
35 35
36 36
37 37 fixture = Fixture()
38 38
39 39
40 40 class TestHomeController(TestController):
41 41
42 42 def test_index(self):
43 43 self.log_user()
44 44 response = self.app.get(url(controller='home', action='index'))
45 45 # if global permission is set
46 46 response.mustcontain('Add Repository')
47 47
48 48 # search for objects inside the JavaScript JSON
49 49 for repo in Repository.getAll():
50 50 response.mustcontain('"name_raw": "%s"' % repo.repo_name)
51 51
52 52 def test_index_contains_backend_specific_details(self, backend):
53 53 self.log_user()
54 54 response = self.app.get(url(controller='home', action='index'))
55 55 tip = backend.repo.get_commit().raw_id
56 56
57 57 # html in javascript variable:
58 58 response.mustcontain(r'<i class=\"icon-%s\"' % (backend.alias, ))
59 59 response.mustcontain(r'href=\"/%s\"' % (backend.repo_name, ))
60 60
61 61 response.mustcontain("""/%s/changeset/%s""" % (backend.repo_name, tip))
62 62 response.mustcontain("""Added a symlink""")
63 63
64 64 def test_index_with_anonymous_access_disabled(self):
65 65 with fixture.anon_access(False):
66 66 response = self.app.get(url(controller='home', action='index'),
67 67 status=302)
68 68 assert 'login' in response.location
69 69
70 70 def test_index_page_on_groups(self, autologin_user, repo_group):
71 71 response = self.app.get(url('repo_group_home', group_name='gr1'))
72 72 response.mustcontain("gr1/repo_in_group")
73 73
74 74 def test_index_page_on_group_with_trailing_slash(
75 75 self, autologin_user, repo_group):
76 76 response = self.app.get(url('repo_group_home', group_name='gr1') + '/')
77 77 response.mustcontain("gr1/repo_in_group")
78 78
79 79 @pytest.fixture(scope='class')
80 80 def repo_group(self, request):
81 81 gr = fixture.create_repo_group('gr1')
82 82 fixture.create_repo(name='gr1/repo_in_group', repo_group=gr)
83 83
84 84 @request.addfinalizer
85 85 def cleanup():
86 86 RepoModel().delete('gr1/repo_in_group')
87 87 RepoGroupModel().delete(repo_group='gr1', force_delete=True)
88 88 Session().commit()
89 89
90 90 def test_index_with_name_with_tags(self, autologin_user):
91 91 user = User.get_by_username('test_admin')
92 92 user.name = '<img src="/image1" onload="alert(\'Hello, World!\');">'
93 93 user.lastname = (
94 94 '<img src="/image2" onload="alert(\'Hello, World!\');">')
95 95 Session().add(user)
96 96 Session().commit()
97 97
98 98 response = self.app.get(url(controller='home', action='index'))
99 99 response.mustcontain(
100 100 '&lt;img src=&#34;/image1&#34; onload=&#34;'
101 101 'alert(&#39;Hello, World!&#39;);&#34;&gt;')
102 102 response.mustcontain(
103 103 '&lt;img src=&#34;/image2&#34; onload=&#34;'
104 104 'alert(&#39;Hello, World!&#39;);&#34;&gt;')
105 105
106 106 @pytest.mark.parametrize("name, state", [
107 107 ('Disabled', False),
108 108 ('Enabled', True),
109 109 ])
110 110 def test_index_show_version(self, autologin_user, name, state):
111 111 version_string = 'RhodeCode Enterprise %s' % rhodecode.__version__
112 112
113 113 show = SettingsModel().get_setting_by_name('show_version')
114 114 show.app_settings_value = state
115 115 Session().add(show)
116 116 Session().commit()
117 117 response = self.app.get(url(controller='home', action='index'))
118 118 if state is True:
119 119 response.mustcontain(version_string)
120 120 if state is False:
121 121 response.mustcontain(no=[version_string])
122 122
123 123
124 124 class TestUserAutocompleteData(TestController):
125 125 def test_returns_list_of_users(self, user_util):
126 126 self.log_user()
127 127 user = user_util.create_user(is_active=True)
128 128 user_name = user.username
129 129 response = self.app.get(
130 130 url(controller='home', action='user_autocomplete_data'),
131 131 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
132 132 result = json.loads(response.body)
133 133 values = [suggestion['value'] for suggestion in result['suggestions']]
134 134 assert user_name in values
135 135
136 136 def test_returns_groups_when_user_groups_sent(self, user_util):
137 137 self.log_user()
138 138 group = user_util.create_user_group(user_groups_active=True)
139 139 group_name = group.users_group_name
140 140 response = self.app.get(
141 141 url(controller='home', action='user_autocomplete_data',
142 142 user_groups='true'),
143 143 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
144 144 result = json.loads(response.body)
145 145 values = [suggestion['value'] for suggestion in result['suggestions']]
146 146 assert group_name in values
147 147
148 148 def test_result_is_limited_when_query_is_sent(self):
149 149 self.log_user()
150 150 fake_result = [
151 151 {
152 152 'first_name': 'John',
153 153 'value_display': 'hello{} (John Smith)'.format(i),
154 154 'icon_link': '/images/user14.png',
155 155 'value': 'hello{}'.format(i),
156 156 'last_name': 'Smith',
157 157 'username': 'hello{}'.format(i),
158 158 'id': i,
159 159 'value_type': u'user'
160 160 }
161 161 for i in range(10)
162 162 ]
163 163 users_patcher = patch.object(
164 164 RepoModel, 'get_users', return_value=fake_result)
165 165 groups_patcher = patch.object(
166 166 RepoModel, 'get_user_groups', return_value=fake_result)
167 167
168 168 query = 'hello'
169 169 with users_patcher as users_mock, groups_patcher as groups_mock:
170 170 response = self.app.get(
171 171 url(controller='home', action='user_autocomplete_data',
172 172 user_groups='true', query=query),
173 173 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
174 174
175 175 result = json.loads(response.body)
176 176 users_mock.assert_called_once_with(name_contains=query)
177 177 groups_mock.assert_called_once_with(name_contains=query)
178 178 assert len(result['suggestions']) == 20
179 179
180 180
181 181 def assert_and_get_content(result):
182 182 repos = []
183 183 groups = []
184 commits = []
184 185 for data in result:
185 186 for data_item in data['children']:
186 187 assert data_item['id']
187 188 assert data_item['text']
189 assert data_item['url']
188 190 if data_item['type'] == 'repo':
189 191 repos.append(data_item)
190 else:
192 elif data_item['type'] == 'group':
191 193 groups.append(data_item)
194 elif data_item['type'] == 'commit':
195 commits.append(data_item)
196 else:
197 raise Exception('invalid type %s' % data_item['type'])
192 198
193 return repos, groups
199 return repos, groups, commits
194 200
195 201
196 class TestRepoSwitcherData(TestController):
202 class TestGotoSwitcherData(TestController):
197 203 required_repos_with_groups = [
198 204 'abc',
199 205 'abc-fork',
200 206 'forks/abcd',
201 207 'abcd',
202 208 'abcde',
203 209 'a/abc',
204 210 'aa/abc',
205 211 'aaa/abc',
206 212 'aaaa/abc',
207 213 'repos_abc/aaa/abc',
208 214 'abc_repos/abc',
209 215 'abc_repos/abcd',
210 216 'xxx/xyz',
211 217 'forked-abc/a/abc'
212 218 ]
213 219
214 220 @pytest.fixture(autouse=True, scope='class')
215 221 def prepare(self, request, pylonsapp):
216 222 for repo_and_group in self.required_repos_with_groups:
217 223 # create structure of groups and return the last group
218 224
219 225 repo_group = map_groups(repo_and_group)
220 226
221 227 RepoModel()._create_repo(
222 228 repo_and_group, 'hg', 'test-ac', TEST_USER_ADMIN_LOGIN,
223 229 repo_group=getattr(repo_group, 'group_id', None))
224 230
225 231 Session().commit()
226 232
227 233 request.addfinalizer(self.cleanup)
228 234
229 235 def cleanup(self):
230 236 # first delete all repos
231 237 for repo_and_groups in self.required_repos_with_groups:
232 238 repo = Repository.get_by_repo_name(repo_and_groups)
233 239 if repo:
234 240 RepoModel().delete(repo)
235 241 Session().commit()
236 242
237 243 # then delete all empty groups
238 244 for repo_and_groups in self.required_repos_with_groups:
239 245 if '/' in repo_and_groups:
240 246 r_group = repo_and_groups.rsplit('/', 1)[0]
241 247 repo_group = RepoGroup.get_by_group_name(r_group)
242 248 if not repo_group:
243 249 continue
244 250 parents = repo_group.parents
245 251 RepoGroupModel().delete(repo_group, force_delete=True)
246 252 Session().commit()
247 253
248 254 for el in reversed(parents):
249 255 RepoGroupModel().delete(el, force_delete=True)
250 256 Session().commit()
251 257
252 258 def test_returns_list_of_repos_and_groups(self):
253 259 self.log_user()
254 260
255 261 response = self.app.get(
256 url(controller='home', action='repo_switcher_data'),
262 url(controller='home', action='goto_switcher_data'),
257 263 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
258 264 result = json.loads(response.body)['results']
259 265
260 repos, groups = assert_and_get_content(result)
266 repos, groups, commits = assert_and_get_content(result)
261 267
262 268 assert len(repos) == len(Repository.get_all())
263 269 assert len(groups) == len(RepoGroup.get_all())
270 assert len(commits) == 0
264 271
265 272 def test_returns_list_of_repos_and_groups_filtered(self):
266 273 self.log_user()
267 274
268 275 response = self.app.get(
269 url(controller='home', action='repo_switcher_data'),
276 url(controller='home', action='goto_switcher_data'),
270 277 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', },
271 278 params={'query': 'abc'}, status=200)
272 279 result = json.loads(response.body)['results']
273 280
274 repos, groups = assert_and_get_content(result)
281 repos, groups, commits = assert_and_get_content(result)
275 282
276 283 assert len(repos) == 13
277 284 assert len(groups) == 5
285 assert len(commits) == 0
278 286
279 287 def test_returns_list_of_properly_sorted_and_filtered(self):
280 288 self.log_user()
281 289
282 290 response = self.app.get(
283 url(controller='home', action='repo_switcher_data'),
291 url(controller='home', action='goto_switcher_data'),
284 292 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', },
285 293 params={'query': 'abc'}, status=200)
286 294 result = json.loads(response.body)['results']
287 295
288 repos, groups = assert_and_get_content(result)
296 repos, groups, commits = assert_and_get_content(result)
289 297
290 298 test_repos = [x['text'] for x in repos[:4]]
291 299 assert ['abc', 'abcd', 'a/abc', 'abcde'] == test_repos
292 300
293 301 test_groups = [x['text'] for x in groups[:4]]
294 302 assert ['abc_repos', 'repos_abc',
295 303 'forked-abc', 'forked-abc/a'] == test_groups
296 304
297 305
298 306 class TestRepoListData(TestController):
299 307 def test_returns_list_of_repos_and_groups(self, user_util):
300 308 self.log_user()
301 309
302 310 response = self.app.get(
303 url(controller='home', action='repo_switcher_data'),
311 url(controller='home', action='repo_list_data'),
304 312 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
305 313 result = json.loads(response.body)['results']
306 314
307 repos, groups = assert_and_get_content(result)
315 repos, groups, commits = assert_and_get_content(result)
308 316
309 317 assert len(repos) == len(Repository.get_all())
310 318 assert len(groups) == 0
319 assert len(commits) == 0
311 320
312 321 def test_returns_list_of_repos_and_groups_filtered(self):
313 322 self.log_user()
314 323
315 324 response = self.app.get(
316 url(controller='home', action='repo_switcher_data'),
325 url(controller='home', action='repo_list_data'),
317 326 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', },
318 327 params={'query': 'vcs_test_git'}, status=200)
319 328 result = json.loads(response.body)['results']
320 329
321 repos, groups = assert_and_get_content(result)
330 repos, groups, commits = assert_and_get_content(result)
322 331
323 332 assert len(repos) == len(Repository.query().filter(
324 333 Repository.repo_name.ilike('%vcs_test_git%')).all())
325 334 assert len(groups) == 0
335 assert len(commits) == 0
326 336
327 337 def test_returns_list_of_repos_and_groups_filtered_with_type(self):
328 338 self.log_user()
329 339
330 340 response = self.app.get(
331 url(controller='home', action='repo_switcher_data'),
341 url(controller='home', action='repo_list_data'),
332 342 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', },
333 343 params={'query': 'vcs_test_git', 'repo_type': 'git'}, status=200)
334 344 result = json.loads(response.body)['results']
335 345
336 repos, groups = assert_and_get_content(result)
346 repos, groups, commits = assert_and_get_content(result)
337 347
338 348 assert len(repos) == len(Repository.query().filter(
339 349 Repository.repo_name.ilike('%vcs_test_git%')).all())
340 350 assert len(groups) == 0
351 assert len(commits) == 0
341 352
342 353 def test_returns_list_of_repos_non_ascii_query(self):
343 354 self.log_user()
344 355 response = self.app.get(
345 url(controller='home', action='repo_switcher_data'),
356 url(controller='home', action='repo_list_data'),
346 357 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', },
347 358 params={'query': 'Δ‡_vcs_test_Δ…', 'repo_type': 'git'}, status=200)
348 359 result = json.loads(response.body)['results']
349 360
350 repos, groups = assert_and_get_content(result)
361 repos, groups, commits = assert_and_get_content(result)
351 362
352 363 assert len(repos) == 0
353 364 assert len(groups) == 0
365 assert len(commits) == 0
@@ -1,198 +1,202 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22
23 23 import mock
24 24 import pytest
25 25 from whoosh import query
26 26
27 27 from rhodecode.tests import (
28 28 TestController, url, SkipTest, HG_REPO,
29 29 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
30 30 from rhodecode.tests.utils import AssertResponse
31 31
32 32
33 33 class TestSearchController(TestController):
34 34
35 35 def test_index(self):
36 36 self.log_user()
37 37 response = self.app.get(url(controller='search', action='index'))
38 38 assert_response = AssertResponse(response)
39 39 assert_response.one_element_exists('input#q')
40 40
41 41 def test_search_files_empty_search(self):
42 42 if os.path.isdir(self.index_location):
43 43 raise SkipTest('skipped due to existing index')
44 44 else:
45 45 self.log_user()
46 46 response = self.app.get(url(controller='search', action='index'),
47 47 {'q': HG_REPO})
48 48 response.mustcontain('There is no index to search in. '
49 49 'Please run whoosh indexer')
50 50
51 51 def test_search_validation(self):
52 52 self.log_user()
53 53 response = self.app.get(
54 54 url(controller='search', action='index'), {'q': query,
55 55 'type': 'content',
56 56 'page_limit': 1000})
57 57
58 58 response.mustcontain(
59 59 'page_limit - 1000 is greater than maximum value 500')
60 60
61 61 @pytest.mark.parametrize("query, expected_hits, expected_paths", [
62 62 ('todo', 23, [
63 63 'vcs/backends/hg/inmemory.py',
64 64 'vcs/tests/test_git.py']),
65 65 ('extension:rst installation', 6, [
66 66 'docs/index.rst',
67 67 'docs/installation.rst']),
68 68 ('def repo', 87, [
69 69 'vcs/tests/test_git.py',
70 70 'vcs/tests/test_changesets.py']),
71 71 ('repository:%s def test' % HG_REPO, 18, [
72 72 'vcs/tests/test_git.py',
73 73 'vcs/tests/test_changesets.py']),
74 74 ('"def main"', 9, [
75 75 'vcs/__init__.py',
76 76 'vcs/tests/__init__.py',
77 77 'vcs/utils/progressbar.py']),
78 78 ('owner:test_admin', 358, [
79 79 'vcs/tests/base.py',
80 80 'MANIFEST.in',
81 81 'vcs/utils/termcolors.py',
82 82 'docs/theme/ADC/static/documentation.png']),
83 83 ('owner:test_admin def main', 72, [
84 84 'vcs/__init__.py',
85 85 'vcs/tests/test_utils_filesize.py',
86 86 'vcs/tests/test_cli.py']),
87 87 ('owner:michaΕ‚ test', 0, []),
88 88 ])
89 89 def test_search_files(self, query, expected_hits, expected_paths):
90 90 self.log_user()
91 91 response = self.app.get(
92 92 url(controller='search', action='index'), {'q': query,
93 93 'type': 'content',
94 94 'page_limit': 500})
95 95
96 96 response.mustcontain('%s results' % expected_hits)
97 97 for path in expected_paths:
98 98 response.mustcontain(path)
99 99
100 100 @pytest.mark.parametrize("query, expected_hits, expected_commits", [
101 101 ('bother to ask where to fetch repo during tests', 3, [
102 102 ('hg', 'a00c1b6f5d7a6ae678fd553a8b81d92367f7ecf1'),
103 103 ('git', 'c6eb379775c578a95dad8ddab53f963b80894850'),
104 104 ('svn', '98')]),
105 105 ('michaΕ‚', 0, []),
106 106 ('changed:tests/utils.py', 36, [
107 107 ('hg', 'a00c1b6f5d7a6ae678fd553a8b81d92367f7ecf1')]),
108 108 ('changed:vcs/utils/archivers.py', 11, [
109 109 ('hg', '25213a5fbb048dff8ba65d21e466a835536e5b70'),
110 110 ('hg', '47aedd538bf616eedcb0e7d630ea476df0e159c7'),
111 111 ('hg', 'f5d23247fad4856a1dabd5838afade1e0eed24fb'),
112 112 ('hg', '04ad456aefd6461aea24f90b63954b6b1ce07b3e'),
113 113 ('git', 'c994f0de03b2a0aa848a04fc2c0d7e737dba31fc'),
114 114 ('git', 'd1f898326327e20524fe22417c22d71064fe54a1'),
115 115 ('git', 'fe568b4081755c12abf6ba673ba777fc02a415f3'),
116 116 ('git', 'bafe786f0d8c2ff7da5c1dcfcfa577de0b5e92f1')]),
117 117 ('added:README.rst', 3, [
118 118 ('hg', '3803844fdbd3b711175fc3da9bdacfcd6d29a6fb'),
119 119 ('git', 'ff7ca51e58c505fec0dd2491de52c622bb7a806b'),
120 120 ('svn', '8')]),
121 121 ('changed:lazy.py', 15, [
122 122 ('hg', 'eaa291c5e6ae6126a203059de9854ccf7b5baa12'),
123 123 ('git', '17438a11f72b93f56d0e08e7d1fa79a378578a82'),
124 124 ('svn', '82'),
125 125 ('svn', '262'),
126 126 ('hg', 'f5d23247fad4856a1dabd5838afade1e0eed24fb'),
127 127 ('git', '33fa3223355104431402a888fa77a4e9956feb3e')
128 128 ]),
129 129 ('author:marcin@python-blog.com '
130 130 'commit_id:b986218ba1c9b0d6a259fac9b050b1724ed8e545', 1, [
131 131 ('hg', 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]),
132 ('b986218ba1c9b0d6a259fac9b050b1724ed8e545', 1, [
133 ('hg', 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]),
134 ('b986218b', 1, [
135 ('hg', 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]),
132 136 ])
133 137 def test_search_commit_messages(
134 138 self, query, expected_hits, expected_commits, enabled_backends):
135 139 self.log_user()
136 140 response = self.app.get(
137 141 url(controller='search', action='index'), {'q': query,
138 142 'type': 'commit',
139 143 'page_limit': 500})
140 144
141 145 response.mustcontain('%s results' % expected_hits)
142 146 for backend, commit_id in expected_commits:
143 147 if backend in enabled_backends:
144 148 response.mustcontain(commit_id)
145 149
146 150 @pytest.mark.parametrize("query, expected_hits, expected_paths", [
147 151 ('readme.rst', 3, []),
148 152 ('test*', 75, []),
149 153 ('*model*', 1, []),
150 154 ('extension:rst', 48, []),
151 155 ('extension:rst api', 24, []),
152 156 ])
153 157 def test_search_file_paths(self, query, expected_hits, expected_paths):
154 158 self.log_user()
155 159 response = self.app.get(
156 160 url(controller='search', action='index'), {'q': query,
157 161 'type': 'path',
158 162 'page_limit': 500})
159 163
160 164 response.mustcontain('%s results' % expected_hits)
161 165 for path in expected_paths:
162 166 response.mustcontain(path)
163 167
164 168 def test_search_commit_message_specific_repo(self, backend):
165 169 self.log_user()
166 170 response = self.app.get(
167 171 url(controller='search', action='index',
168 172 repo_name=backend.repo_name),
169 173 {'q': 'bother to ask where to fetch repo during tests',
170 174 'type': 'commit'})
171 175
172 176 response.mustcontain('1 results')
173 177
174 178 def test_filters_are_not_applied_for_admin_user(self):
175 179 self.log_user()
176 180 with mock.patch('whoosh.searching.Searcher.search') as search_mock:
177 181 self.app.get(
178 182 url(controller='search', action='index'),
179 183 {'q': 'test query', 'type': 'commit'})
180 184 assert search_mock.call_count == 1
181 185 _, kwargs = search_mock.call_args
182 186 assert kwargs['filter'] is None
183 187
184 188 def test_filters_are_applied_for_normal_user(self, enabled_backends):
185 189 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
186 190 with mock.patch('whoosh.searching.Searcher.search') as search_mock:
187 191 self.app.get(
188 192 url(controller='search', action='index'),
189 193 {'q': 'test query', 'type': 'commit'})
190 194 assert search_mock.call_count == 1
191 195 _, kwargs = search_mock.call_args
192 196 assert isinstance(kwargs['filter'], query.Or)
193 197 expected_repositories = [
194 198 'vcs_test_{}'.format(b) for b in enabled_backends]
195 199 queried_repositories = [
196 200 name for type_, name in kwargs['filter'].all_terms()]
197 201 for repository in expected_repositories:
198 202 assert repository in queried_repositories
General Comments 0
You need to be logged in to leave comments. Login now