##// END OF EJS Templates
repositories: ported repo_creating checks to pyramid....
marcink -
r1985:f5a73a5e default
parent child Browse files
Show More
@@ -0,0 +1,110 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22
23 from pyramid.view import view_config
24 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
25
26 from rhodecode.apps._base import BaseAppView
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib.auth import (NotAnonymous, HasRepoPermissionAny)
29 from rhodecode.model.db import Repository
30
31 log = logging.getLogger(__name__)
32
33
34 class RepoChecksView(BaseAppView):
35 def load_default_context(self):
36 c = self._get_local_tmpl_context()
37 self._register_global_c(c)
38 return c
39
40 @NotAnonymous()
41 @view_config(
42 route_name='repo_creating', request_method='GET',
43 renderer='rhodecode:templates/admin/repos/repo_creating.mako')
44 def repo_creating(self):
45 c = self.load_default_context()
46
47 repo_name = self.request.matchdict['repo_name']
48 db_repo = Repository.get_by_repo_name(repo_name)
49 if not db_repo:
50 raise HTTPNotFound()
51
52 # check if maybe repo is already created
53 if db_repo.repo_state in [Repository.STATE_CREATED]:
54 # re-check permissions before redirecting to prevent resource
55 # discovery by checking the 302 code
56 perm_set = ['repository.read', 'repository.write', 'repository.admin']
57 has_perm = HasRepoPermissionAny(*perm_set)(
58 db_repo.repo_name, 'Repo Creating check')
59 if not has_perm:
60 raise HTTPNotFound()
61
62 raise HTTPFound(h.route_path(
63 'repo_summary', repo_name=db_repo.repo_name))
64
65 c.task_id = self.request.GET.get('task_id')
66 c.repo_name = repo_name
67
68 return self._get_template_context(c)
69
70 @NotAnonymous()
71 @view_config(
72 route_name='repo_creating_check', request_method='GET',
73 renderer='json_ext')
74 def repo_creating_check(self):
75 _ = self.request.translate
76 task_id = self.request.GET.get('task_id')
77 self.load_default_context()
78
79 repo_name = self.request.matchdict['repo_name']
80
81 if task_id and task_id not in ['None']:
82 import rhodecode
83 from celery.result import AsyncResult
84 if rhodecode.CELERY_ENABLED:
85 task = AsyncResult(task_id)
86 if task.failed():
87 msg = self._log_creation_exception(task.result, repo_name)
88 h.flash(msg, category='error')
89 raise HTTPFound(h.route_path('home'), code=501)
90
91 db_repo = Repository.get_by_repo_name(repo_name)
92 if db_repo and db_repo.repo_state == Repository.STATE_CREATED:
93 if db_repo.clone_uri:
94 clone_uri = db_repo.clone_uri_hidden
95 h.flash(_('Created repository %s from %s')
96 % (db_repo.repo_name, clone_uri), category='success')
97 else:
98 repo_url = h.link_to(
99 db_repo.repo_name,
100 h.route_path('repo_summary', repo_name=db_repo.repo_name))
101 fork = db_repo.fork
102 if fork:
103 fork_name = fork.repo_name
104 h.flash(h.literal(_('Forked repository %s as %s')
105 % (fork_name, repo_url)), category='success')
106 else:
107 h.flash(h.literal(_('Created repository %s') % repo_url),
108 category='success')
109 return {'result': True}
110 return {'result': False}
@@ -1,446 +1,454 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 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 time
22 22 import logging
23 23
24 24 from pyramid.httpexceptions import HTTPFound
25 25
26 26 from rhodecode.lib import helpers as h
27 27 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
28 28 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
29 29 from rhodecode.model import repo
30 30 from rhodecode.model import repo_group
31 31 from rhodecode.model.db import User
32 32 from rhodecode.model.scm import ScmModel
33 33
34 34 log = logging.getLogger(__name__)
35 35
36 36
37 37 ADMIN_PREFIX = '/_admin'
38 38 STATIC_FILE_PREFIX = '/_static'
39 39
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_group_name': r'.*?[^/]',
44 44 # repo names can have a slash in them, but they must not end with a slash
45 45 'repo_name': r'.*?[^/]',
46 46 # file path eats up everything at the end
47 47 'f_path': r'.*',
48 48 # reference types
49 49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
50 50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
51 51 }
52 52
53 53
54 54 def add_route_with_slash(config,name, pattern, **kw):
55 55 config.add_route(name, pattern, **kw)
56 56 if not pattern.endswith('/'):
57 57 config.add_route(name + '_slash', pattern + '/', **kw)
58 58
59 59
60 60 def add_route_requirements(route_path, requirements=URL_NAME_REQUIREMENTS):
61 61 """
62 62 Adds regex requirements to pyramid routes using a mapping dict
63 63 e.g::
64 64 add_route_requirements('{repo_name}/settings')
65 65 """
66 66 for key, regex in requirements.items():
67 67 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
68 68 return route_path
69 69
70 70
71 71 def get_format_ref_id(repo):
72 72 """Returns a `repo` specific reference formatter function"""
73 73 if h.is_svn(repo):
74 74 return _format_ref_id_svn
75 75 else:
76 76 return _format_ref_id
77 77
78 78
79 79 def _format_ref_id(name, raw_id):
80 80 """Default formatting of a given reference `name`"""
81 81 return name
82 82
83 83
84 84 def _format_ref_id_svn(name, raw_id):
85 85 """Special way of formatting a reference for Subversion including path"""
86 86 return '%s@%s' % (name, raw_id)
87 87
88 88
89 89 class TemplateArgs(StrictAttributeDict):
90 90 pass
91 91
92 92
93 93 class BaseAppView(object):
94 94
95 95 def __init__(self, context, request):
96 96 self.request = request
97 97 self.context = context
98 98 self.session = request.session
99 99 self._rhodecode_user = request.user # auth user
100 100 self._rhodecode_db_user = self._rhodecode_user.get_instance()
101 101 self._maybe_needs_password_change(
102 102 request.matched_route.name, self._rhodecode_db_user)
103 103
104 104 def _maybe_needs_password_change(self, view_name, user_obj):
105 105 log.debug('Checking if user %s needs password change on view %s',
106 106 user_obj, view_name)
107 107 skip_user_views = [
108 108 'logout', 'login',
109 109 'my_account_password', 'my_account_password_update'
110 110 ]
111 111
112 112 if not user_obj:
113 113 return
114 114
115 115 if user_obj.username == User.DEFAULT_USER:
116 116 return
117 117
118 118 now = time.time()
119 119 should_change = user_obj.user_data.get('force_password_change')
120 120 change_after = safe_int(should_change) or 0
121 121 if should_change and now > change_after:
122 122 log.debug('User %s requires password change', user_obj)
123 123 h.flash('You are required to change your password', 'warning',
124 124 ignore_duplicate=True)
125 125
126 126 if view_name not in skip_user_views:
127 127 raise HTTPFound(
128 128 self.request.route_path('my_account_password'))
129 129
130 130 def _log_creation_exception(self, e, repo_name):
131 131 _ = self.request.translate
132 132 reason = None
133 133 if len(e.args) == 2:
134 134 reason = e.args[1]
135 135
136 136 if reason == 'INVALID_CERTIFICATE':
137 137 log.exception(
138 138 'Exception creating a repository: invalid certificate')
139 139 msg = (_('Error creating repository %s: invalid certificate')
140 140 % repo_name)
141 141 else:
142 142 log.exception("Exception creating a repository")
143 143 msg = (_('Error creating repository %s')
144 144 % repo_name)
145 145 return msg
146 146
147 147 def _get_local_tmpl_context(self, include_app_defaults=False):
148 148 c = TemplateArgs()
149 149 c.auth_user = self.request.user
150 150 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
151 151 c.rhodecode_user = self.request.user
152 152
153 153 if include_app_defaults:
154 154 # NOTE(marcink): after full pyramid migration include_app_defaults
155 155 # should be turned on by default
156 156 from rhodecode.lib.base import attach_context_attributes
157 157 attach_context_attributes(c, self.request, self.request.user.user_id)
158 158
159 159 return c
160 160
161 161 def _register_global_c(self, tmpl_args):
162 162 """
163 163 Registers attributes to pylons global `c`
164 164 """
165 165
166 166 # TODO(marcink): remove once pyramid migration is finished
167 167 from pylons import tmpl_context as c
168 168 try:
169 169 for k, v in tmpl_args.items():
170 170 setattr(c, k, v)
171 171 except TypeError:
172 172 log.exception('Failed to register pylons C')
173 173 pass
174 174
175 175 def _get_template_context(self, tmpl_args):
176 176 self._register_global_c(tmpl_args)
177 177
178 178 local_tmpl_args = {
179 179 'defaults': {},
180 180 'errors': {},
181 181 # register a fake 'c' to be used in templates instead of global
182 182 # pylons c, after migration to pyramid we should rename it to 'c'
183 183 # make sure we replace usage of _c in templates too
184 184 '_c': tmpl_args
185 185 }
186 186 local_tmpl_args.update(tmpl_args)
187 187 return local_tmpl_args
188 188
189 189 def load_default_context(self):
190 190 """
191 191 example:
192 192
193 193 def load_default_context(self):
194 194 c = self._get_local_tmpl_context()
195 195 c.custom_var = 'foobar'
196 196 self._register_global_c(c)
197 197 return c
198 198 """
199 199 raise NotImplementedError('Needs implementation in view class')
200 200
201 201
202 202 class RepoAppView(BaseAppView):
203 203
204 204 def __init__(self, context, request):
205 205 super(RepoAppView, self).__init__(context, request)
206 206 self.db_repo = request.db_repo
207 207 self.db_repo_name = self.db_repo.repo_name
208 208 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
209 209
210 210 def _handle_missing_requirements(self, error):
211 211 log.error(
212 212 'Requirements are missing for repository %s: %s',
213 213 self.db_repo_name, error.message)
214 214
215 215 def _get_local_tmpl_context(self, include_app_defaults=False):
216 216 _ = self.request.translate
217 217 c = super(RepoAppView, self)._get_local_tmpl_context(
218 218 include_app_defaults=include_app_defaults)
219 219
220 220 # register common vars for this type of view
221 221 c.rhodecode_db_repo = self.db_repo
222 222 c.repo_name = self.db_repo_name
223 223 c.repository_pull_requests = self.db_repo_pull_requests
224 224
225 225 c.repository_requirements_missing = False
226 226 try:
227 227 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
228 228 except RepositoryRequirementError as e:
229 229 c.repository_requirements_missing = True
230 230 self._handle_missing_requirements(e)
231 231 self.rhodecode_vcs_repo = None
232 232
233 233 if (not c.repository_requirements_missing
234 234 and self.rhodecode_vcs_repo is None):
235 235 # unable to fetch this repo as vcs instance, report back to user
236 236 h.flash(_(
237 237 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
238 238 "Please check if it exist, or is not damaged.") %
239 239 {'repo_name': c.repo_name},
240 240 category='error', ignore_duplicate=True)
241 241 raise HTTPFound(h.route_path('home'))
242 242
243 243 return c
244 244
245 245 def _get_f_path(self, matchdict, default=None):
246 246 f_path = matchdict.get('f_path')
247 247 if f_path:
248 248 # fix for multiple initial slashes that causes errors for GIT
249 249 return f_path.lstrip('/')
250 250
251 251 return default
252 252
253 253
254 254 class DataGridAppView(object):
255 255 """
256 256 Common class to have re-usable grid rendering components
257 257 """
258 258
259 259 def _extract_ordering(self, request, column_map=None):
260 260 column_map = column_map or {}
261 261 column_index = safe_int(request.GET.get('order[0][column]'))
262 262 order_dir = request.GET.get(
263 263 'order[0][dir]', 'desc')
264 264 order_by = request.GET.get(
265 265 'columns[%s][data][sort]' % column_index, 'name_raw')
266 266
267 267 # translate datatable to DB columns
268 268 order_by = column_map.get(order_by) or order_by
269 269
270 270 search_q = request.GET.get('search[value]')
271 271 return search_q, order_by, order_dir
272 272
273 273 def _extract_chunk(self, request):
274 274 start = safe_int(request.GET.get('start'), 0)
275 275 length = safe_int(request.GET.get('length'), 25)
276 276 draw = safe_int(request.GET.get('draw'))
277 277 return draw, start, length
278 278
279 279
280 280 class BaseReferencesView(RepoAppView):
281 281 """
282 282 Base for reference view for branches, tags and bookmarks.
283 283 """
284 284 def load_default_context(self):
285 285 c = self._get_local_tmpl_context()
286 286
287 287 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
288 288 c.repo_info = self.db_repo
289 289
290 290 self._register_global_c(c)
291 291 return c
292 292
293 293 def load_refs_context(self, ref_items, partials_template):
294 294 _render = self.request.get_partial_renderer(partials_template)
295 295 pre_load = ["author", "date", "message"]
296 296
297 297 is_svn = h.is_svn(self.rhodecode_vcs_repo)
298 298 is_hg = h.is_hg(self.rhodecode_vcs_repo)
299 299
300 300 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
301 301
302 302 closed_refs = {}
303 303 if is_hg:
304 304 closed_refs = self.rhodecode_vcs_repo.branches_closed
305 305
306 306 data = []
307 307 for ref_name, commit_id in ref_items:
308 308 commit = self.rhodecode_vcs_repo.get_commit(
309 309 commit_id=commit_id, pre_load=pre_load)
310 310 closed = ref_name in closed_refs
311 311
312 312 # TODO: johbo: Unify generation of reference links
313 313 use_commit_id = '/' in ref_name or is_svn
314 314
315 315 if use_commit_id:
316 316 files_url = h.route_path(
317 317 'repo_files',
318 318 repo_name=self.db_repo_name,
319 319 f_path=ref_name if is_svn else '',
320 320 commit_id=commit_id)
321 321
322 322 else:
323 323 files_url = h.route_path(
324 324 'repo_files',
325 325 repo_name=self.db_repo_name,
326 326 f_path=ref_name if is_svn else '',
327 327 commit_id=ref_name,
328 328 _query=dict(at=ref_name))
329 329
330 330 data.append({
331 331 "name": _render('name', ref_name, files_url, closed),
332 332 "name_raw": ref_name,
333 333 "date": _render('date', commit.date),
334 334 "date_raw": datetime_to_time(commit.date),
335 335 "author": _render('author', commit.author),
336 336 "commit": _render(
337 337 'commit', commit.message, commit.raw_id, commit.idx),
338 338 "commit_raw": commit.idx,
339 339 "compare": _render(
340 340 'compare', format_ref_id(ref_name, commit.raw_id)),
341 341 })
342 342
343 343 return data
344 344
345 345
346 346 class RepoRoutePredicate(object):
347 347 def __init__(self, val, config):
348 348 self.val = val
349 349
350 350 def text(self):
351 351 return 'repo_route = %s' % self.val
352 352
353 353 phash = text
354 354
355 355 def __call__(self, info, request):
356 356
357 357 if hasattr(request, 'vcs_call'):
358 358 # skip vcs calls
359 359 return
360 360
361 361 repo_name = info['match']['repo_name']
362 362 repo_model = repo.RepoModel()
363 363 by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
364 364
365 def redirect_if_creating(db_repo):
366 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
367 raise HTTPFound(
368 request.route_path('repo_creating',
369 repo_name=db_repo.repo_name))
370
365 371 if by_name_match:
366 372 # register this as request object we can re-use later
367 373 request.db_repo = by_name_match
374 redirect_if_creating(by_name_match)
368 375 return True
369 376
370 377 by_id_match = repo_model.get_repo_by_id(repo_name)
371 378 if by_id_match:
372 379 request.db_repo = by_id_match
380 redirect_if_creating(by_id_match)
373 381 return True
374 382
375 383 return False
376 384
377 385
378 386 class RepoTypeRoutePredicate(object):
379 387 def __init__(self, val, config):
380 388 self.val = val or ['hg', 'git', 'svn']
381 389
382 390 def text(self):
383 391 return 'repo_accepted_type = %s' % self.val
384 392
385 393 phash = text
386 394
387 395 def __call__(self, info, request):
388 396 if hasattr(request, 'vcs_call'):
389 397 # skip vcs calls
390 398 return
391 399
392 400 rhodecode_db_repo = request.db_repo
393 401
394 402 log.debug(
395 403 '%s checking repo type for %s in %s',
396 404 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
397 405
398 406 if rhodecode_db_repo.repo_type in self.val:
399 407 return True
400 408 else:
401 409 log.warning('Current view is not supported for repo type:%s',
402 410 rhodecode_db_repo.repo_type)
403 411 #
404 412 # h.flash(h.literal(
405 413 # _('Action not supported for %s.' % rhodecode_repo.alias)),
406 414 # category='warning')
407 415 # return redirect(
408 416 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
409 417
410 418 return False
411 419
412 420
413 421 class RepoGroupRoutePredicate(object):
414 422 def __init__(self, val, config):
415 423 self.val = val
416 424
417 425 def text(self):
418 426 return 'repo_group_route = %s' % self.val
419 427
420 428 phash = text
421 429
422 430 def __call__(self, info, request):
423 431 if hasattr(request, 'vcs_call'):
424 432 # skip vcs calls
425 433 return
426 434
427 435 repo_group_name = info['match']['repo_group_name']
428 436 repo_group_model = repo_group.RepoGroupModel()
429 437 by_name_match = repo_group_model.get_by_group_name(
430 438 repo_group_name, cache=True)
431 439
432 440 if by_name_match:
433 441 # register this as request object we can re-use later
434 442 request.db_repo_group = by_name_match
435 443 return True
436 444
437 445 return False
438 446
439 447
440 448 def includeme(config):
441 449 config.add_route_predicate(
442 450 'repo_route', RepoRoutePredicate)
443 451 config.add_route_predicate(
444 452 'repo_accepted_types', RepoTypeRoutePredicate)
445 453 config.add_route_predicate(
446 454 'repo_group_route', RepoGroupRoutePredicate)
@@ -1,357 +1,366 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 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 from rhodecode.apps._base import add_route_with_slash
21 21
22 22
23 23 def includeme(config):
24 24
25 # repo creating checks, special cases that aren't repo routes
26 config.add_route(
27 name='repo_creating',
28 pattern='/{repo_name:.*?[^/]}/repo_creating')
29
30 config.add_route(
31 name='repo_creating_check',
32 pattern='/{repo_name:.*?[^/]}/repo_creating_check')
33
25 34 # Summary
26 35 # NOTE(marcink): one additional route is defined in very bottom, catch
27 36 # all pattern
28 37 config.add_route(
29 38 name='repo_summary_explicit',
30 39 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
31 40 config.add_route(
32 41 name='repo_summary_commits',
33 42 pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True)
34 43
35 44 # Commits
36 45 config.add_route(
37 46 name='repo_commit',
38 47 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
39 48
40 49 config.add_route(
41 50 name='repo_commit_children',
42 51 pattern='/{repo_name:.*?[^/]}/changeset_children/{commit_id}', repo_route=True)
43 52
44 53 config.add_route(
45 54 name='repo_commit_parents',
46 55 pattern='/{repo_name:.*?[^/]}/changeset_parents/{commit_id}', repo_route=True)
47 56
48 57 config.add_route(
49 58 name='repo_commit_raw',
50 59 pattern='/{repo_name:.*?[^/]}/changeset-diff/{commit_id}', repo_route=True)
51 60
52 61 config.add_route(
53 62 name='repo_commit_patch',
54 63 pattern='/{repo_name:.*?[^/]}/changeset-patch/{commit_id}', repo_route=True)
55 64
56 65 config.add_route(
57 66 name='repo_commit_download',
58 67 pattern='/{repo_name:.*?[^/]}/changeset-download/{commit_id}', repo_route=True)
59 68
60 69 config.add_route(
61 70 name='repo_commit_data',
62 71 pattern='/{repo_name:.*?[^/]}/changeset-data/{commit_id}', repo_route=True)
63 72
64 73 config.add_route(
65 74 name='repo_commit_comment_create',
66 75 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/create', repo_route=True)
67 76
68 77 config.add_route(
69 78 name='repo_commit_comment_preview',
70 79 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True)
71 80
72 81 config.add_route(
73 82 name='repo_commit_comment_delete',
74 83 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/delete', repo_route=True)
75 84
76 85 # still working url for backward compat.
77 86 config.add_route(
78 87 name='repo_commit_raw_deprecated',
79 88 pattern='/{repo_name:.*?[^/]}/raw-changeset/{commit_id}', repo_route=True)
80 89
81 90 # Files
82 91 config.add_route(
83 92 name='repo_archivefile',
84 93 pattern='/{repo_name:.*?[^/]}/archive/{fname}', repo_route=True)
85 94
86 95 config.add_route(
87 96 name='repo_files_diff',
88 97 pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True)
89 98 config.add_route( # legacy route to make old links work
90 99 name='repo_files_diff_2way_redirect',
91 100 pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True)
92 101
93 102 config.add_route(
94 103 name='repo_files',
95 104 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True)
96 105 config.add_route(
97 106 name='repo_files:default_path',
98 107 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True)
99 108 config.add_route(
100 109 name='repo_files:default_commit',
101 110 pattern='/{repo_name:.*?[^/]}/files', repo_route=True)
102 111
103 112 config.add_route(
104 113 name='repo_files:rendered',
105 114 pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True)
106 115
107 116 config.add_route(
108 117 name='repo_files:annotated',
109 118 pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True)
110 119 config.add_route(
111 120 name='repo_files:annotated_previous',
112 121 pattern='/{repo_name:.*?[^/]}/annotate-previous/{commit_id}/{f_path:.*}', repo_route=True)
113 122
114 123 config.add_route(
115 124 name='repo_nodetree_full',
116 125 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/{f_path:.*}', repo_route=True)
117 126 config.add_route(
118 127 name='repo_nodetree_full:default_path',
119 128 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True)
120 129
121 130 config.add_route(
122 131 name='repo_files_nodelist',
123 132 pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True)
124 133
125 134 config.add_route(
126 135 name='repo_file_raw',
127 136 pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True)
128 137
129 138 config.add_route(
130 139 name='repo_file_download',
131 140 pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True)
132 141 config.add_route( # backward compat to keep old links working
133 142 name='repo_file_download:legacy',
134 143 pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}',
135 144 repo_route=True)
136 145
137 146 config.add_route(
138 147 name='repo_file_history',
139 148 pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True)
140 149
141 150 config.add_route(
142 151 name='repo_file_authors',
143 152 pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True)
144 153
145 154 config.add_route(
146 155 name='repo_files_remove_file',
147 156 pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}',
148 157 repo_route=True)
149 158 config.add_route(
150 159 name='repo_files_delete_file',
151 160 pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}',
152 161 repo_route=True)
153 162 config.add_route(
154 163 name='repo_files_edit_file',
155 164 pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}',
156 165 repo_route=True)
157 166 config.add_route(
158 167 name='repo_files_update_file',
159 168 pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}',
160 169 repo_route=True)
161 170 config.add_route(
162 171 name='repo_files_add_file',
163 172 pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}',
164 173 repo_route=True)
165 174 config.add_route(
166 175 name='repo_files_create_file',
167 176 pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}',
168 177 repo_route=True)
169 178
170 179 # Refs data
171 180 config.add_route(
172 181 name='repo_refs_data',
173 182 pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True)
174 183
175 184 config.add_route(
176 185 name='repo_refs_changelog_data',
177 186 pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True)
178 187
179 188 config.add_route(
180 189 name='repo_stats',
181 190 pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True)
182 191
183 192 # Changelog
184 193 config.add_route(
185 194 name='repo_changelog',
186 195 pattern='/{repo_name:.*?[^/]}/changelog', repo_route=True)
187 196 config.add_route(
188 197 name='repo_changelog_file',
189 198 pattern='/{repo_name:.*?[^/]}/changelog/{commit_id}/{f_path:.*}', repo_route=True)
190 199 config.add_route(
191 200 name='repo_changelog_elements',
192 201 pattern='/{repo_name:.*?[^/]}/changelog_elements', repo_route=True)
193 202
194 203 # Compare
195 204 config.add_route(
196 205 name='repo_compare_select',
197 206 pattern='/{repo_name:.*?[^/]}/compare', repo_route=True)
198 207
199 208 config.add_route(
200 209 name='repo_compare',
201 210 pattern='/{repo_name:.*?[^/]}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}', repo_route=True)
202 211
203 212 # Tags
204 213 config.add_route(
205 214 name='tags_home',
206 215 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
207 216
208 217 # Branches
209 218 config.add_route(
210 219 name='branches_home',
211 220 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
212 221
213 222 config.add_route(
214 223 name='bookmarks_home',
215 224 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
216 225
217 226 # Pull Requests
218 227 config.add_route(
219 228 name='pullrequest_show',
220 229 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}',
221 230 repo_route=True)
222 231
223 232 config.add_route(
224 233 name='pullrequest_show_all',
225 234 pattern='/{repo_name:.*?[^/]}/pull-request',
226 235 repo_route=True, repo_accepted_types=['hg', 'git'])
227 236
228 237 config.add_route(
229 238 name='pullrequest_show_all_data',
230 239 pattern='/{repo_name:.*?[^/]}/pull-request-data',
231 240 repo_route=True, repo_accepted_types=['hg', 'git'])
232 241
233 242 config.add_route(
234 243 name='pullrequest_repo_refs',
235 244 pattern='/{repo_name:.*?[^/]}/pull-request/refs/{target_repo_name:.*?[^/]}',
236 245 repo_route=True)
237 246
238 247 config.add_route(
239 248 name='pullrequest_repo_destinations',
240 249 pattern='/{repo_name:.*?[^/]}/pull-request/repo-destinations',
241 250 repo_route=True)
242 251
243 252 config.add_route(
244 253 name='pullrequest_new',
245 254 pattern='/{repo_name:.*?[^/]}/pull-request/new',
246 255 repo_route=True, repo_accepted_types=['hg', 'git'])
247 256
248 257 config.add_route(
249 258 name='pullrequest_create',
250 259 pattern='/{repo_name:.*?[^/]}/pull-request/create',
251 260 repo_route=True, repo_accepted_types=['hg', 'git'])
252 261
253 262 config.add_route(
254 263 name='pullrequest_update',
255 264 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/update',
256 265 repo_route=True)
257 266
258 267 config.add_route(
259 268 name='pullrequest_merge',
260 269 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/merge',
261 270 repo_route=True)
262 271
263 272 config.add_route(
264 273 name='pullrequest_delete',
265 274 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/delete',
266 275 repo_route=True)
267 276
268 277 config.add_route(
269 278 name='pullrequest_comment_create',
270 279 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment',
271 280 repo_route=True)
272 281
273 282 config.add_route(
274 283 name='pullrequest_comment_delete',
275 284 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete',
276 285 repo_route=True, repo_accepted_types=['hg', 'git'])
277 286
278 287 # Settings
279 288 config.add_route(
280 289 name='edit_repo',
281 290 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
282 291
283 292 # Settings advanced
284 293 config.add_route(
285 294 name='edit_repo_advanced',
286 295 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
287 296 config.add_route(
288 297 name='edit_repo_advanced_delete',
289 298 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
290 299 config.add_route(
291 300 name='edit_repo_advanced_locking',
292 301 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
293 302 config.add_route(
294 303 name='edit_repo_advanced_journal',
295 304 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
296 305 config.add_route(
297 306 name='edit_repo_advanced_fork',
298 307 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
299 308
300 309 # Caches
301 310 config.add_route(
302 311 name='edit_repo_caches',
303 312 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
304 313
305 314 # Permissions
306 315 config.add_route(
307 316 name='edit_repo_perms',
308 317 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
309 318
310 319 # Repo Review Rules
311 320 config.add_route(
312 321 name='repo_reviewers',
313 322 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
314 323
315 324 config.add_route(
316 325 name='repo_default_reviewers_data',
317 326 pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True)
318 327
319 328 # Maintenance
320 329 config.add_route(
321 330 name='repo_maintenance',
322 331 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
323 332
324 333 config.add_route(
325 334 name='repo_maintenance_execute',
326 335 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
327 336
328 337 # Strip
329 338 config.add_route(
330 339 name='strip',
331 340 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
332 341
333 342 config.add_route(
334 343 name='strip_check',
335 344 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
336 345
337 346 config.add_route(
338 347 name='strip_execute',
339 348 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
340 349
341 350 # ATOM/RSS Feed
342 351 config.add_route(
343 352 name='rss_feed_home',
344 353 pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True)
345 354
346 355 config.add_route(
347 356 name='atom_feed_home',
348 357 pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True)
349 358
350 359 # NOTE(marcink): needs to be at the end for catch-all
351 360 add_route_with_slash(
352 361 config,
353 362 name='repo_summary',
354 363 pattern='/{repo_name:.*?[^/]}', repo_route=True)
355 364
356 365 # Scan module for configuration decorators.
357 366 config.scan()
@@ -1,523 +1,523 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 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 re
22 22
23 23 import mock
24 24 import pytest
25 25
26 26 from rhodecode.apps.repository.views.repo_summary import RepoSummaryView
27 27 from rhodecode.lib import helpers as h
28 28 from rhodecode.lib.compat import OrderedDict
29 29 from rhodecode.lib.utils2 import AttributeDict, safe_str
30 30 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
31 31 from rhodecode.model.db import Repository
32 32 from rhodecode.model.meta import Session
33 33 from rhodecode.model.repo import RepoModel
34 34 from rhodecode.model.scm import ScmModel
35 35 from rhodecode.tests import assert_session_flash
36 36 from rhodecode.tests.fixture import Fixture
37 37 from rhodecode.tests.utils import AssertResponse, repo_on_filesystem
38 38
39 39
40 40 fixture = Fixture()
41 41
42 42
43 43 def route_path(name, params=None, **kwargs):
44 44 import urllib
45 45
46 46 base_url = {
47 47 'repo_summary': '/{repo_name}',
48 48 'repo_stats': '/{repo_name}/repo_stats/{commit_id}',
49 49 'repo_refs_data': '/{repo_name}/refs-data',
50 'repo_refs_changelog_data': '/{repo_name}/refs-data-changelog'
51
50 'repo_refs_changelog_data': '/{repo_name}/refs-data-changelog',
51 'repo_creating_check': '/{repo_name}/repo_creating_check',
52 52 }[name].format(**kwargs)
53 53
54 54 if params:
55 55 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
56 56 return base_url
57 57
58 58
59 59 @pytest.mark.usefixtures('app')
60 60 class TestSummaryView(object):
61 61 def test_index(self, autologin_user, backend, http_host_only_stub):
62 62 repo_id = backend.repo.repo_id
63 63 repo_name = backend.repo_name
64 64 with mock.patch('rhodecode.lib.helpers.is_svn_without_proxy',
65 65 return_value=False):
66 66 response = self.app.get(
67 67 route_path('repo_summary', repo_name=repo_name))
68 68
69 69 # repo type
70 70 response.mustcontain(
71 71 '<i class="icon-%s">' % (backend.alias, )
72 72 )
73 73 # public/private
74 74 response.mustcontain(
75 75 """<i class="icon-unlock-alt">"""
76 76 )
77 77
78 78 # clone url...
79 79 response.mustcontain(
80 80 'id="clone_url" readonly="readonly"'
81 81 ' value="http://test_admin@%s/%s"' % (http_host_only_stub, repo_name, ))
82 82 response.mustcontain(
83 83 'id="clone_url_id" readonly="readonly"'
84 84 ' value="http://test_admin@%s/_%s"' % (http_host_only_stub, repo_id, ))
85 85
86 86 def test_index_svn_without_proxy(
87 87 self, autologin_user, backend_svn, http_host_only_stub):
88 88 repo_id = backend_svn.repo.repo_id
89 89 repo_name = backend_svn.repo_name
90 90 response = self.app.get(route_path('repo_summary', repo_name=repo_name))
91 91 # clone url...
92 92 response.mustcontain(
93 93 'id="clone_url" disabled'
94 94 ' value="http://test_admin@%s/%s"' % (http_host_only_stub, repo_name, ))
95 95 response.mustcontain(
96 96 'id="clone_url_id" disabled'
97 97 ' value="http://test_admin@%s/_%s"' % (http_host_only_stub, repo_id, ))
98 98
99 99 def test_index_with_trailing_slash(
100 100 self, autologin_user, backend, http_host_only_stub):
101 101
102 102 repo_id = backend.repo.repo_id
103 103 repo_name = backend.repo_name
104 104 with mock.patch('rhodecode.lib.helpers.is_svn_without_proxy',
105 105 return_value=False):
106 106 response = self.app.get(
107 107 route_path('repo_summary', repo_name=repo_name) + '/',
108 108 status=200)
109 109
110 110 # clone url...
111 111 response.mustcontain(
112 112 'id="clone_url" readonly="readonly"'
113 113 ' value="http://test_admin@%s/%s"' % (http_host_only_stub, repo_name, ))
114 114 response.mustcontain(
115 115 'id="clone_url_id" readonly="readonly"'
116 116 ' value="http://test_admin@%s/_%s"' % (http_host_only_stub, repo_id, ))
117 117
118 118 def test_index_by_id(self, autologin_user, backend):
119 119 repo_id = backend.repo.repo_id
120 120 response = self.app.get(
121 121 route_path('repo_summary', repo_name='_%s' % (repo_id,)))
122 122
123 123 # repo type
124 124 response.mustcontain(
125 125 '<i class="icon-%s">' % (backend.alias, )
126 126 )
127 127 # public/private
128 128 response.mustcontain(
129 129 """<i class="icon-unlock-alt">"""
130 130 )
131 131
132 132 def test_index_by_repo_having_id_path_in_name_hg(self, autologin_user):
133 133 fixture.create_repo(name='repo_1')
134 134 response = self.app.get(route_path('repo_summary', repo_name='repo_1'))
135 135
136 136 try:
137 137 response.mustcontain("repo_1")
138 138 finally:
139 139 RepoModel().delete(Repository.get_by_repo_name('repo_1'))
140 140 Session().commit()
141 141
142 142 def test_index_with_anonymous_access_disabled(
143 143 self, backend, disable_anonymous_user):
144 144 response = self.app.get(
145 145 route_path('repo_summary', repo_name=backend.repo_name), status=302)
146 146 assert 'login' in response.location
147 147
148 148 def _enable_stats(self, repo):
149 149 r = Repository.get_by_repo_name(repo)
150 150 r.enable_statistics = True
151 151 Session().add(r)
152 152 Session().commit()
153 153
154 154 expected_trending = {
155 155 'hg': {
156 156 "py": {"count": 68, "desc": ["Python"]},
157 157 "rst": {"count": 16, "desc": ["Rst"]},
158 158 "css": {"count": 2, "desc": ["Css"]},
159 159 "sh": {"count": 2, "desc": ["Bash"]},
160 160 "bat": {"count": 1, "desc": ["Batch"]},
161 161 "cfg": {"count": 1, "desc": ["Ini"]},
162 162 "html": {"count": 1, "desc": ["EvoqueHtml", "Html"]},
163 163 "ini": {"count": 1, "desc": ["Ini"]},
164 164 "js": {"count": 1, "desc": ["Javascript"]},
165 165 "makefile": {"count": 1, "desc": ["Makefile", "Makefile"]}
166 166 },
167 167 'git': {
168 168 "py": {"count": 68, "desc": ["Python"]},
169 169 "rst": {"count": 16, "desc": ["Rst"]},
170 170 "css": {"count": 2, "desc": ["Css"]},
171 171 "sh": {"count": 2, "desc": ["Bash"]},
172 172 "bat": {"count": 1, "desc": ["Batch"]},
173 173 "cfg": {"count": 1, "desc": ["Ini"]},
174 174 "html": {"count": 1, "desc": ["EvoqueHtml", "Html"]},
175 175 "ini": {"count": 1, "desc": ["Ini"]},
176 176 "js": {"count": 1, "desc": ["Javascript"]},
177 177 "makefile": {"count": 1, "desc": ["Makefile", "Makefile"]}
178 178 },
179 179 'svn': {
180 180 "py": {"count": 75, "desc": ["Python"]},
181 181 "rst": {"count": 16, "desc": ["Rst"]},
182 182 "html": {"count": 11, "desc": ["EvoqueHtml", "Html"]},
183 183 "css": {"count": 2, "desc": ["Css"]},
184 184 "bat": {"count": 1, "desc": ["Batch"]},
185 185 "cfg": {"count": 1, "desc": ["Ini"]},
186 186 "ini": {"count": 1, "desc": ["Ini"]},
187 187 "js": {"count": 1, "desc": ["Javascript"]},
188 188 "makefile": {"count": 1, "desc": ["Makefile", "Makefile"]},
189 189 "sh": {"count": 1, "desc": ["Bash"]}
190 190 },
191 191 }
192 192
193 193 def test_repo_stats(self, autologin_user, backend, xhr_header):
194 194 response = self.app.get(
195 195 route_path(
196 196 'repo_stats', repo_name=backend.repo_name, commit_id='tip'),
197 197 extra_environ=xhr_header,
198 198 status=200)
199 199 assert re.match(r'6[\d\.]+ KiB', response.json['size'])
200 200
201 201 def test_repo_stats_code_stats_enabled(self, autologin_user, backend, xhr_header):
202 202 repo_name = backend.repo_name
203 203
204 204 # codes stats
205 205 self._enable_stats(repo_name)
206 206 ScmModel().mark_for_invalidation(repo_name)
207 207
208 208 response = self.app.get(
209 209 route_path(
210 210 'repo_stats', repo_name=backend.repo_name, commit_id='tip'),
211 211 extra_environ=xhr_header,
212 212 status=200)
213 213
214 214 expected_data = self.expected_trending[backend.alias]
215 215 returned_stats = response.json['code_stats']
216 216 for k, v in expected_data.items():
217 217 assert v == returned_stats[k]
218 218
219 219 def test_repo_refs_data(self, backend):
220 220 response = self.app.get(
221 221 route_path('repo_refs_data', repo_name=backend.repo_name),
222 222 status=200)
223 223
224 224 # Ensure that there is the correct amount of items in the result
225 225 repo = backend.repo.scm_instance()
226 226 data = response.json['results']
227 227 items = sum(len(section['children']) for section in data)
228 228 repo_refs = len(repo.branches) + len(repo.tags) + len(repo.bookmarks)
229 229 assert items == repo_refs
230 230
231 231 def test_index_shows_missing_requirements_message(
232 232 self, backend, autologin_user):
233 233 repo_name = backend.repo_name
234 234 scm_patcher = mock.patch.object(
235 235 Repository, 'scm_instance', side_effect=RepositoryRequirementError)
236 236
237 237 with scm_patcher:
238 238 response = self.app.get(route_path('repo_summary', repo_name=repo_name))
239 239 assert_response = AssertResponse(response)
240 240 assert_response.element_contains(
241 241 '.main .alert-warning strong', 'Missing requirements')
242 242 assert_response.element_contains(
243 243 '.main .alert-warning',
244 244 'Commits cannot be displayed, because this repository '
245 245 'uses one or more extensions, which was not enabled.')
246 246
247 247 def test_missing_requirements_page_does_not_contains_switch_to(
248 248 self, autologin_user, backend):
249 249 repo_name = backend.repo_name
250 250 scm_patcher = mock.patch.object(
251 251 Repository, 'scm_instance', side_effect=RepositoryRequirementError)
252 252
253 253 with scm_patcher:
254 254 response = self.app.get(route_path('repo_summary', repo_name=repo_name))
255 255 response.mustcontain(no='Switch To')
256 256
257 257
258 258 @pytest.mark.usefixtures('app')
259 259 class TestRepoLocation(object):
260 260
261 261 @pytest.mark.parametrize("suffix", [u'', u'Δ…Δ™Ε‚'], ids=['', 'non-ascii'])
262 262 def test_missing_filesystem_repo(
263 263 self, autologin_user, backend, suffix, csrf_token):
264 264 repo = backend.create_repo(name_suffix=suffix)
265 265 repo_name = repo.repo_name
266 266
267 267 # delete from file system
268 268 RepoModel()._delete_filesystem_repo(repo)
269 269
270 270 # test if the repo is still in the database
271 271 new_repo = RepoModel().get_by_repo_name(repo_name)
272 272 assert new_repo.repo_name == repo_name
273 273
274 274 # check if repo is not in the filesystem
275 275 assert not repo_on_filesystem(repo_name)
276 276
277 277 response = self.app.get(
278 278 route_path('repo_summary', repo_name=safe_str(repo_name)), status=302)
279 279
280 280 msg = 'The repository `%s` cannot be loaded in filesystem. ' \
281 281 'Please check if it exist, or is not damaged.' % repo_name
282 282 assert_session_flash(response, msg)
283 283
284 284 @pytest.mark.parametrize("suffix", [u'', u'Δ…Δ™Ε‚'], ids=['', 'non-ascii'])
285 285 def test_missing_filesystem_repo_on_repo_check(
286 286 self, autologin_user, backend, suffix, csrf_token):
287 287 repo = backend.create_repo(name_suffix=suffix)
288 288 repo_name = repo.repo_name
289 289
290 290 # delete from file system
291 291 RepoModel()._delete_filesystem_repo(repo)
292 292
293 293 # test if the repo is still in the database
294 294 new_repo = RepoModel().get_by_repo_name(repo_name)
295 295 assert new_repo.repo_name == repo_name
296 296
297 297 # check if repo is not in the filesystem
298 298 assert not repo_on_filesystem(repo_name)
299 299
300 300 # flush the session
301 301 self.app.get(
302 302 route_path('repo_summary', repo_name=safe_str(repo_name)),
303 303 status=302)
304 304
305 305 response = self.app.get(
306 306 route_path('repo_creating_check', repo_name=safe_str(repo_name)),
307 307 status=200)
308 308 msg = 'The repository `%s` cannot be loaded in filesystem. ' \
309 309 'Please check if it exist, or is not damaged.' % repo_name
310 310 assert_session_flash(response, msg )
311 311
312 312
313 313 @pytest.fixture()
314 314 def summary_view(context_stub, request_stub, user_util):
315 315 """
316 316 Bootstrap view to test the view functions
317 317 """
318 318 request_stub.matched_route = AttributeDict(name='test_view')
319 319
320 320 request_stub.user = user_util.create_user().AuthUser
321 321 request_stub.db_repo = user_util.create_repo()
322 322
323 323 view = RepoSummaryView(context=context_stub, request=request_stub)
324 324 return view
325 325
326 326
327 327 @pytest.mark.usefixtures('app')
328 328 class TestCreateReferenceData(object):
329 329
330 330 @pytest.fixture
331 331 def example_refs(self):
332 332 section_1_refs = OrderedDict((('a', 'a_id'), ('b', 'b_id')))
333 333 example_refs = [
334 334 ('section_1', section_1_refs, 't1'),
335 335 ('section_2', {'c': 'c_id'}, 't2'),
336 336 ]
337 337 return example_refs
338 338
339 339 def test_generates_refs_based_on_commit_ids(self, example_refs, summary_view):
340 340 repo = mock.Mock()
341 341 repo.name = 'test-repo'
342 342 repo.alias = 'git'
343 343 full_repo_name = 'pytest-repo-group/' + repo.name
344 344
345 345 result = summary_view._create_reference_data(
346 346 repo, full_repo_name, example_refs)
347 347
348 348 expected_files_url = '/{}/files/'.format(full_repo_name)
349 349 expected_result = [
350 350 {
351 351 'children': [
352 352 {
353 353 'id': 'a', 'raw_id': 'a_id', 'text': 'a', 'type': 't1',
354 354 'files_url': expected_files_url + 'a/?at=a',
355 355 },
356 356 {
357 357 'id': 'b', 'raw_id': 'b_id', 'text': 'b', 'type': 't1',
358 358 'files_url': expected_files_url + 'b/?at=b',
359 359 }
360 360 ],
361 361 'text': 'section_1'
362 362 },
363 363 {
364 364 'children': [
365 365 {
366 366 'id': 'c', 'raw_id': 'c_id', 'text': 'c', 'type': 't2',
367 367 'files_url': expected_files_url + 'c/?at=c',
368 368 }
369 369 ],
370 370 'text': 'section_2'
371 371 }]
372 372 assert result == expected_result
373 373
374 374 def test_generates_refs_with_path_for_svn(self, example_refs, summary_view):
375 375 repo = mock.Mock()
376 376 repo.name = 'test-repo'
377 377 repo.alias = 'svn'
378 378 full_repo_name = 'pytest-repo-group/' + repo.name
379 379
380 380 result = summary_view._create_reference_data(
381 381 repo, full_repo_name, example_refs)
382 382
383 383 expected_files_url = '/{}/files/'.format(full_repo_name)
384 384 expected_result = [
385 385 {
386 386 'children': [
387 387 {
388 388 'id': 'a@a_id', 'raw_id': 'a_id',
389 389 'text': 'a', 'type': 't1',
390 390 'files_url': expected_files_url + 'a_id/a?at=a',
391 391 },
392 392 {
393 393 'id': 'b@b_id', 'raw_id': 'b_id',
394 394 'text': 'b', 'type': 't1',
395 395 'files_url': expected_files_url + 'b_id/b?at=b',
396 396 }
397 397 ],
398 398 'text': 'section_1'
399 399 },
400 400 {
401 401 'children': [
402 402 {
403 403 'id': 'c@c_id', 'raw_id': 'c_id',
404 404 'text': 'c', 'type': 't2',
405 405 'files_url': expected_files_url + 'c_id/c?at=c',
406 406 }
407 407 ],
408 408 'text': 'section_2'
409 409 }
410 410 ]
411 411 assert result == expected_result
412 412
413 413
414 414 class TestCreateFilesUrl(object):
415 415
416 416 def test_creates_non_svn_url(self, app, summary_view):
417 417 repo = mock.Mock()
418 418 repo.name = 'abcde'
419 419 full_repo_name = 'test-repo-group/' + repo.name
420 420 ref_name = 'branch1'
421 421 raw_id = 'deadbeef0123456789'
422 422 is_svn = False
423 423
424 424 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
425 425 result = summary_view._create_files_url(
426 426 repo, full_repo_name, ref_name, raw_id, is_svn)
427 427 url_mock.assert_called_once_with(
428 428 'repo_files', repo_name=full_repo_name, commit_id=ref_name,
429 429 f_path='', _query=dict(at=ref_name))
430 430 assert result == url_mock.return_value
431 431
432 432 def test_creates_svn_url(self, app, summary_view):
433 433 repo = mock.Mock()
434 434 repo.name = 'abcde'
435 435 full_repo_name = 'test-repo-group/' + repo.name
436 436 ref_name = 'branch1'
437 437 raw_id = 'deadbeef0123456789'
438 438 is_svn = True
439 439
440 440 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
441 441 result = summary_view._create_files_url(
442 442 repo, full_repo_name, ref_name, raw_id, is_svn)
443 443 url_mock.assert_called_once_with(
444 444 'repo_files', repo_name=full_repo_name, f_path=ref_name,
445 445 commit_id=raw_id, _query=dict(at=ref_name))
446 446 assert result == url_mock.return_value
447 447
448 448 def test_name_has_slashes(self, app, summary_view):
449 449 repo = mock.Mock()
450 450 repo.name = 'abcde'
451 451 full_repo_name = 'test-repo-group/' + repo.name
452 452 ref_name = 'branch1/branch2'
453 453 raw_id = 'deadbeef0123456789'
454 454 is_svn = False
455 455
456 456 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
457 457 result = summary_view._create_files_url(
458 458 repo, full_repo_name, ref_name, raw_id, is_svn)
459 459 url_mock.assert_called_once_with(
460 460 'repo_files', repo_name=full_repo_name, commit_id=raw_id,
461 461 f_path='', _query=dict(at=ref_name))
462 462 assert result == url_mock.return_value
463 463
464 464
465 465 class TestReferenceItems(object):
466 466 repo = mock.Mock()
467 467 repo.name = 'pytest-repo'
468 468 repo_full_name = 'pytest-repo-group/' + repo.name
469 469 ref_type = 'branch'
470 470 fake_url = '/abcde/'
471 471
472 472 @staticmethod
473 473 def _format_function(name, id_):
474 474 return 'format_function_{}_{}'.format(name, id_)
475 475
476 476 def test_creates_required_amount_of_items(self, summary_view):
477 477 amount = 100
478 478 refs = {
479 479 'ref{}'.format(i): '{0:040d}'.format(i)
480 480 for i in range(amount)
481 481 }
482 482
483 483 url_patcher = mock.patch.object(summary_view, '_create_files_url')
484 484 svn_patcher = mock.patch('rhodecode.lib.helpers.is_svn',
485 485 return_value=False)
486 486
487 487 with url_patcher as url_mock, svn_patcher:
488 488 result = summary_view._create_reference_items(
489 489 self.repo, self.repo_full_name, refs, self.ref_type,
490 490 self._format_function)
491 491 assert len(result) == amount
492 492 assert url_mock.call_count == amount
493 493
494 494 def test_single_item_details(self, summary_view):
495 495 ref_name = 'ref1'
496 496 ref_id = 'deadbeef'
497 497 refs = {
498 498 ref_name: ref_id
499 499 }
500 500
501 501 svn_patcher = mock.patch('rhodecode.lib.helpers.is_svn',
502 502 return_value=False)
503 503
504 504 url_patcher = mock.patch.object(
505 505 summary_view, '_create_files_url', return_value=self.fake_url)
506 506
507 507 with url_patcher as url_mock, svn_patcher:
508 508 result = summary_view._create_reference_items(
509 509 self.repo, self.repo_full_name, refs, self.ref_type,
510 510 self._format_function)
511 511
512 512 url_mock.assert_called_once_with(
513 513 self.repo, self.repo_full_name, ref_name, ref_id, False)
514 514 expected_result = [
515 515 {
516 516 'text': ref_name,
517 517 'id': self._format_function(ref_name, ref_id),
518 518 'raw_id': ref_id,
519 519 'type': self.ref_type,
520 520 'files_url': self.fake_url
521 521 }
522 522 ]
523 523 assert result == expected_result
@@ -1,515 +1,508 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 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 import re
33 33 from routes import Mapper
34 34
35 35 # prefix for non repository related links needs to be prefixed with `/`
36 36 ADMIN_PREFIX = '/_admin'
37 37 STATIC_FILE_PREFIX = '/_static'
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_group_name': r'.*?[^/]',
44 44 # repo names can have a slash in them, but they must not end with a slash
45 45 'repo_name': r'.*?[^/]',
46 46 # file path eats up everything at the end
47 47 'f_path': r'.*',
48 48 # reference types
49 49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
50 50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
51 51 }
52 52
53 53
54 54 class JSRoutesMapper(Mapper):
55 55 """
56 56 Wrapper for routes.Mapper to make pyroutes compatible url definitions
57 57 """
58 58 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
59 59 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
60 60 def __init__(self, *args, **kw):
61 61 super(JSRoutesMapper, self).__init__(*args, **kw)
62 62 self._jsroutes = []
63 63
64 64 def connect(self, *args, **kw):
65 65 """
66 66 Wrapper for connect to take an extra argument jsroute=True
67 67
68 68 :param jsroute: boolean, if True will add the route to the pyroutes list
69 69 """
70 70 if kw.pop('jsroute', False):
71 71 if not self._named_route_regex.match(args[0]):
72 72 raise Exception('only named routes can be added to pyroutes')
73 73 self._jsroutes.append(args[0])
74 74
75 75 super(JSRoutesMapper, self).connect(*args, **kw)
76 76
77 77 def _extract_route_information(self, route):
78 78 """
79 79 Convert a route into tuple(name, path, args), eg:
80 80 ('show_user', '/profile/%(username)s', ['username'])
81 81 """
82 82 routepath = route.routepath
83 83 def replace(matchobj):
84 84 if matchobj.group(1):
85 85 return "%%(%s)s" % matchobj.group(1).split(':')[0]
86 86 else:
87 87 return "%%(%s)s" % matchobj.group(2)
88 88
89 89 routepath = self._argument_prog.sub(replace, routepath)
90 90 return (
91 91 route.name,
92 92 routepath,
93 93 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
94 94 for arg in self._argument_prog.findall(route.routepath)]
95 95 )
96 96
97 97 def jsroutes(self):
98 98 """
99 99 Return a list of pyroutes.js compatible routes
100 100 """
101 101 for route_name in self._jsroutes:
102 102 yield self._extract_route_information(self._routenames[route_name])
103 103
104 104
105 105 def make_map(config):
106 106 """Create, configure and return the routes Mapper"""
107 107 rmap = JSRoutesMapper(
108 108 directory=config['pylons.paths']['controllers'],
109 109 always_scan=config['debug'])
110 110 rmap.minimization = False
111 111 rmap.explicit = False
112 112
113 113 from rhodecode.lib.utils2 import str2bool
114 114 from rhodecode.model import repo, repo_group
115 115
116 116 def check_repo(environ, match_dict):
117 117 """
118 118 check for valid repository for proper 404 handling
119 119
120 120 :param environ:
121 121 :param match_dict:
122 122 """
123 123 repo_name = match_dict.get('repo_name')
124 124
125 125 if match_dict.get('f_path'):
126 126 # fix for multiple initial slashes that causes errors
127 127 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
128 128 repo_model = repo.RepoModel()
129 129 by_name_match = repo_model.get_by_repo_name(repo_name)
130 130 # if we match quickly from database, short circuit the operation,
131 131 # and validate repo based on the type.
132 132 if by_name_match:
133 133 return True
134 134
135 135 by_id_match = repo_model.get_repo_by_id(repo_name)
136 136 if by_id_match:
137 137 repo_name = by_id_match.repo_name
138 138 match_dict['repo_name'] = repo_name
139 139 return True
140 140
141 141 return False
142 142
143 143 def check_group(environ, match_dict):
144 144 """
145 145 check for valid repository group path for proper 404 handling
146 146
147 147 :param environ:
148 148 :param match_dict:
149 149 """
150 150 repo_group_name = match_dict.get('group_name')
151 151 repo_group_model = repo_group.RepoGroupModel()
152 152 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
153 153 if by_name_match:
154 154 return True
155 155
156 156 return False
157 157
158 158 def check_user_group(environ, match_dict):
159 159 """
160 160 check for valid user group for proper 404 handling
161 161
162 162 :param environ:
163 163 :param match_dict:
164 164 """
165 165 return True
166 166
167 167 def check_int(environ, match_dict):
168 168 return match_dict.get('id').isdigit()
169 169
170 170
171 171 #==========================================================================
172 172 # CUSTOM ROUTES HERE
173 173 #==========================================================================
174 174
175 175 # ping and pylons error test
176 176 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
177 177 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
178 178
179 179 # ADMIN REPOSITORY ROUTES
180 180 with rmap.submapper(path_prefix=ADMIN_PREFIX,
181 181 controller='admin/repos') as m:
182 182 m.connect('repos', '/repos',
183 183 action='create', conditions={'method': ['POST']})
184 184 m.connect('repos', '/repos',
185 185 action='index', conditions={'method': ['GET']})
186 186 m.connect('new_repo', '/create_repository', jsroute=True,
187 187 action='create_repository', conditions={'method': ['GET']})
188 188 m.connect('delete_repo', '/repos/{repo_name}',
189 189 action='delete', conditions={'method': ['DELETE']},
190 190 requirements=URL_NAME_REQUIREMENTS)
191 191 m.connect('repo', '/repos/{repo_name}',
192 192 action='show', conditions={'method': ['GET'],
193 193 'function': check_repo},
194 194 requirements=URL_NAME_REQUIREMENTS)
195 195
196 196 # ADMIN REPOSITORY GROUPS ROUTES
197 197 with rmap.submapper(path_prefix=ADMIN_PREFIX,
198 198 controller='admin/repo_groups') as m:
199 199 m.connect('repo_groups', '/repo_groups',
200 200 action='create', conditions={'method': ['POST']})
201 201 m.connect('repo_groups', '/repo_groups',
202 202 action='index', conditions={'method': ['GET']})
203 203 m.connect('new_repo_group', '/repo_groups/new',
204 204 action='new', conditions={'method': ['GET']})
205 205 m.connect('update_repo_group', '/repo_groups/{group_name}',
206 206 action='update', conditions={'method': ['PUT'],
207 207 'function': check_group},
208 208 requirements=URL_NAME_REQUIREMENTS)
209 209
210 210 # EXTRAS REPO GROUP ROUTES
211 211 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
212 212 action='edit',
213 213 conditions={'method': ['GET'], 'function': check_group},
214 214 requirements=URL_NAME_REQUIREMENTS)
215 215 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
216 216 action='edit',
217 217 conditions={'method': ['PUT'], 'function': check_group},
218 218 requirements=URL_NAME_REQUIREMENTS)
219 219
220 220 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
221 221 action='edit_repo_group_advanced',
222 222 conditions={'method': ['GET'], 'function': check_group},
223 223 requirements=URL_NAME_REQUIREMENTS)
224 224 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
225 225 action='edit_repo_group_advanced',
226 226 conditions={'method': ['PUT'], 'function': check_group},
227 227 requirements=URL_NAME_REQUIREMENTS)
228 228
229 229 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
230 230 action='edit_repo_group_perms',
231 231 conditions={'method': ['GET'], 'function': check_group},
232 232 requirements=URL_NAME_REQUIREMENTS)
233 233 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
234 234 action='update_perms',
235 235 conditions={'method': ['PUT'], 'function': check_group},
236 236 requirements=URL_NAME_REQUIREMENTS)
237 237
238 238 m.connect('delete_repo_group', '/repo_groups/{group_name}',
239 239 action='delete', conditions={'method': ['DELETE'],
240 240 'function': check_group},
241 241 requirements=URL_NAME_REQUIREMENTS)
242 242
243 243 # ADMIN USER ROUTES
244 244 with rmap.submapper(path_prefix=ADMIN_PREFIX,
245 245 controller='admin/users') as m:
246 246 m.connect('users', '/users',
247 247 action='create', conditions={'method': ['POST']})
248 248 m.connect('new_user', '/users/new',
249 249 action='new', conditions={'method': ['GET']})
250 250 m.connect('update_user', '/users/{user_id}',
251 251 action='update', conditions={'method': ['PUT']})
252 252 m.connect('delete_user', '/users/{user_id}',
253 253 action='delete', conditions={'method': ['DELETE']})
254 254 m.connect('edit_user', '/users/{user_id}/edit',
255 255 action='edit', conditions={'method': ['GET']}, jsroute=True)
256 256 m.connect('user', '/users/{user_id}',
257 257 action='show', conditions={'method': ['GET']})
258 258 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
259 259 action='reset_password', conditions={'method': ['POST']})
260 260 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
261 261 action='create_personal_repo_group', conditions={'method': ['POST']})
262 262
263 263 # EXTRAS USER ROUTES
264 264 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
265 265 action='edit_advanced', conditions={'method': ['GET']})
266 266 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
267 267 action='update_advanced', conditions={'method': ['PUT']})
268 268
269 269 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
270 270 action='edit_global_perms', conditions={'method': ['GET']})
271 271 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
272 272 action='update_global_perms', conditions={'method': ['PUT']})
273 273
274 274 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
275 275 action='edit_perms_summary', conditions={'method': ['GET']})
276 276
277 277 # ADMIN USER GROUPS REST ROUTES
278 278 with rmap.submapper(path_prefix=ADMIN_PREFIX,
279 279 controller='admin/user_groups') as m:
280 280 m.connect('users_groups', '/user_groups',
281 281 action='create', conditions={'method': ['POST']})
282 282 m.connect('new_users_group', '/user_groups/new',
283 283 action='new', conditions={'method': ['GET']})
284 284 m.connect('update_users_group', '/user_groups/{user_group_id}',
285 285 action='update', conditions={'method': ['PUT']})
286 286 m.connect('delete_users_group', '/user_groups/{user_group_id}',
287 287 action='delete', conditions={'method': ['DELETE']})
288 288 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
289 289 action='edit', conditions={'method': ['GET']},
290 290 function=check_user_group)
291 291
292 292 # EXTRAS USER GROUP ROUTES
293 293 m.connect('edit_user_group_global_perms',
294 294 '/user_groups/{user_group_id}/edit/global_permissions',
295 295 action='edit_global_perms', conditions={'method': ['GET']})
296 296 m.connect('edit_user_group_global_perms',
297 297 '/user_groups/{user_group_id}/edit/global_permissions',
298 298 action='update_global_perms', conditions={'method': ['PUT']})
299 299 m.connect('edit_user_group_perms_summary',
300 300 '/user_groups/{user_group_id}/edit/permissions_summary',
301 301 action='edit_perms_summary', conditions={'method': ['GET']})
302 302
303 303 m.connect('edit_user_group_perms',
304 304 '/user_groups/{user_group_id}/edit/permissions',
305 305 action='edit_perms', conditions={'method': ['GET']})
306 306 m.connect('edit_user_group_perms',
307 307 '/user_groups/{user_group_id}/edit/permissions',
308 308 action='update_perms', conditions={'method': ['PUT']})
309 309
310 310 m.connect('edit_user_group_advanced',
311 311 '/user_groups/{user_group_id}/edit/advanced',
312 312 action='edit_advanced', conditions={'method': ['GET']})
313 313
314 314 m.connect('edit_user_group_advanced_sync',
315 315 '/user_groups/{user_group_id}/edit/advanced/sync',
316 316 action='edit_advanced_set_synchronization', conditions={'method': ['POST']})
317 317
318 318 # ADMIN DEFAULTS REST ROUTES
319 319 with rmap.submapper(path_prefix=ADMIN_PREFIX,
320 320 controller='admin/defaults') as m:
321 321 m.connect('admin_defaults_repositories', '/defaults/repositories',
322 322 action='update_repository_defaults', conditions={'method': ['POST']})
323 323 m.connect('admin_defaults_repositories', '/defaults/repositories',
324 324 action='index', conditions={'method': ['GET']})
325 325
326 326 # ADMIN SETTINGS ROUTES
327 327 with rmap.submapper(path_prefix=ADMIN_PREFIX,
328 328 controller='admin/settings') as m:
329 329
330 330 # default
331 331 m.connect('admin_settings', '/settings',
332 332 action='settings_global_update',
333 333 conditions={'method': ['POST']})
334 334 m.connect('admin_settings', '/settings',
335 335 action='settings_global', conditions={'method': ['GET']})
336 336
337 337 m.connect('admin_settings_vcs', '/settings/vcs',
338 338 action='settings_vcs_update',
339 339 conditions={'method': ['POST']})
340 340 m.connect('admin_settings_vcs', '/settings/vcs',
341 341 action='settings_vcs',
342 342 conditions={'method': ['GET']})
343 343 m.connect('admin_settings_vcs', '/settings/vcs',
344 344 action='delete_svn_pattern',
345 345 conditions={'method': ['DELETE']})
346 346
347 347 m.connect('admin_settings_mapping', '/settings/mapping',
348 348 action='settings_mapping_update',
349 349 conditions={'method': ['POST']})
350 350 m.connect('admin_settings_mapping', '/settings/mapping',
351 351 action='settings_mapping', conditions={'method': ['GET']})
352 352
353 353 m.connect('admin_settings_global', '/settings/global',
354 354 action='settings_global_update',
355 355 conditions={'method': ['POST']})
356 356 m.connect('admin_settings_global', '/settings/global',
357 357 action='settings_global', conditions={'method': ['GET']})
358 358
359 359 m.connect('admin_settings_visual', '/settings/visual',
360 360 action='settings_visual_update',
361 361 conditions={'method': ['POST']})
362 362 m.connect('admin_settings_visual', '/settings/visual',
363 363 action='settings_visual', conditions={'method': ['GET']})
364 364
365 365 m.connect('admin_settings_issuetracker',
366 366 '/settings/issue-tracker', action='settings_issuetracker',
367 367 conditions={'method': ['GET']})
368 368 m.connect('admin_settings_issuetracker_save',
369 369 '/settings/issue-tracker/save',
370 370 action='settings_issuetracker_save',
371 371 conditions={'method': ['POST']})
372 372 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
373 373 action='settings_issuetracker_test',
374 374 conditions={'method': ['POST']})
375 375 m.connect('admin_issuetracker_delete',
376 376 '/settings/issue-tracker/delete',
377 377 action='settings_issuetracker_delete',
378 378 conditions={'method': ['DELETE']})
379 379
380 380 m.connect('admin_settings_email', '/settings/email',
381 381 action='settings_email_update',
382 382 conditions={'method': ['POST']})
383 383 m.connect('admin_settings_email', '/settings/email',
384 384 action='settings_email', conditions={'method': ['GET']})
385 385
386 386 m.connect('admin_settings_hooks', '/settings/hooks',
387 387 action='settings_hooks_update',
388 388 conditions={'method': ['POST', 'DELETE']})
389 389 m.connect('admin_settings_hooks', '/settings/hooks',
390 390 action='settings_hooks', conditions={'method': ['GET']})
391 391
392 392 m.connect('admin_settings_search', '/settings/search',
393 393 action='settings_search', conditions={'method': ['GET']})
394 394
395 395 m.connect('admin_settings_supervisor', '/settings/supervisor',
396 396 action='settings_supervisor', conditions={'method': ['GET']})
397 397 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
398 398 action='settings_supervisor_log', conditions={'method': ['GET']})
399 399
400 400 m.connect('admin_settings_labs', '/settings/labs',
401 401 action='settings_labs_update',
402 402 conditions={'method': ['POST']})
403 403 m.connect('admin_settings_labs', '/settings/labs',
404 404 action='settings_labs', conditions={'method': ['GET']})
405 405
406 406 # ADMIN MY ACCOUNT
407 407 with rmap.submapper(path_prefix=ADMIN_PREFIX,
408 408 controller='admin/my_account') as m:
409 409
410 410 # NOTE(marcink): this needs to be kept for password force flag to be
411 411 # handled in pylons controllers, remove after full migration to pyramid
412 412 m.connect('my_account_password', '/my_account/password',
413 413 action='my_account_password', conditions={'method': ['GET']})
414 414
415 415 #==========================================================================
416 416 # REPOSITORY ROUTES
417 417 #==========================================================================
418 418
419 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
420 controller='admin/repos', action='repo_creating',
421 requirements=URL_NAME_REQUIREMENTS)
422 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
423 controller='admin/repos', action='repo_check',
424 requirements=URL_NAME_REQUIREMENTS)
425
426 419 # repo edit options
427 420 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
428 421 controller='admin/repos', action='edit_fields',
429 422 conditions={'method': ['GET'], 'function': check_repo},
430 423 requirements=URL_NAME_REQUIREMENTS)
431 424 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
432 425 controller='admin/repos', action='create_repo_field',
433 426 conditions={'method': ['PUT'], 'function': check_repo},
434 427 requirements=URL_NAME_REQUIREMENTS)
435 428 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
436 429 controller='admin/repos', action='delete_repo_field',
437 430 conditions={'method': ['DELETE'], 'function': check_repo},
438 431 requirements=URL_NAME_REQUIREMENTS)
439 432
440 433 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
441 434 controller='admin/repos', action='toggle_locking',
442 435 conditions={'method': ['GET'], 'function': check_repo},
443 436 requirements=URL_NAME_REQUIREMENTS)
444 437
445 438 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
446 439 controller='admin/repos', action='edit_remote_form',
447 440 conditions={'method': ['GET'], 'function': check_repo},
448 441 requirements=URL_NAME_REQUIREMENTS)
449 442 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
450 443 controller='admin/repos', action='edit_remote',
451 444 conditions={'method': ['PUT'], 'function': check_repo},
452 445 requirements=URL_NAME_REQUIREMENTS)
453 446
454 447 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
455 448 controller='admin/repos', action='edit_statistics_form',
456 449 conditions={'method': ['GET'], 'function': check_repo},
457 450 requirements=URL_NAME_REQUIREMENTS)
458 451 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
459 452 controller='admin/repos', action='edit_statistics',
460 453 conditions={'method': ['PUT'], 'function': check_repo},
461 454 requirements=URL_NAME_REQUIREMENTS)
462 455 rmap.connect('repo_settings_issuetracker',
463 456 '/{repo_name}/settings/issue-tracker',
464 457 controller='admin/repos', action='repo_issuetracker',
465 458 conditions={'method': ['GET'], 'function': check_repo},
466 459 requirements=URL_NAME_REQUIREMENTS)
467 460 rmap.connect('repo_issuetracker_test',
468 461 '/{repo_name}/settings/issue-tracker/test',
469 462 controller='admin/repos', action='repo_issuetracker_test',
470 463 conditions={'method': ['POST'], 'function': check_repo},
471 464 requirements=URL_NAME_REQUIREMENTS)
472 465 rmap.connect('repo_issuetracker_delete',
473 466 '/{repo_name}/settings/issue-tracker/delete',
474 467 controller='admin/repos', action='repo_issuetracker_delete',
475 468 conditions={'method': ['DELETE'], 'function': check_repo},
476 469 requirements=URL_NAME_REQUIREMENTS)
477 470 rmap.connect('repo_issuetracker_save',
478 471 '/{repo_name}/settings/issue-tracker/save',
479 472 controller='admin/repos', action='repo_issuetracker_save',
480 473 conditions={'method': ['POST'], 'function': check_repo},
481 474 requirements=URL_NAME_REQUIREMENTS)
482 475 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
483 476 controller='admin/repos', action='repo_settings_vcs_update',
484 477 conditions={'method': ['POST'], 'function': check_repo},
485 478 requirements=URL_NAME_REQUIREMENTS)
486 479 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
487 480 controller='admin/repos', action='repo_settings_vcs',
488 481 conditions={'method': ['GET'], 'function': check_repo},
489 482 requirements=URL_NAME_REQUIREMENTS)
490 483 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
491 484 controller='admin/repos', action='repo_delete_svn_pattern',
492 485 conditions={'method': ['DELETE'], 'function': check_repo},
493 486 requirements=URL_NAME_REQUIREMENTS)
494 487 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
495 488 controller='admin/repos', action='repo_settings_pullrequest',
496 489 conditions={'method': ['GET', 'POST'], 'function': check_repo},
497 490 requirements=URL_NAME_REQUIREMENTS)
498 491
499 492
500 493 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
501 494 controller='forks', action='fork_create',
502 495 conditions={'function': check_repo, 'method': ['POST']},
503 496 requirements=URL_NAME_REQUIREMENTS)
504 497
505 498 rmap.connect('repo_fork_home', '/{repo_name}/fork',
506 499 controller='forks', action='fork',
507 500 conditions={'function': check_repo},
508 501 requirements=URL_NAME_REQUIREMENTS)
509 502
510 503 rmap.connect('repo_forks_home', '/{repo_name}/forks',
511 504 controller='forks', action='forks',
512 505 conditions={'function': check_repo},
513 506 requirements=URL_NAME_REQUIREMENTS)
514 507
515 508 return rmap
@@ -1,606 +1,563 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2013-2017 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 """
23 23 Repositories controller for RhodeCode
24 24 """
25 25
26 26 import logging
27 27 import traceback
28 28
29 29 import formencode
30 30 from formencode import htmlfill
31 31 from pylons import request, tmpl_context as c, url
32 32 from pylons.controllers.util import redirect
33 33 from pylons.i18n.translation import _
34 from webob.exc import HTTPForbidden, HTTPNotFound, HTTPBadRequest
34 from webob.exc import HTTPForbidden, HTTPBadRequest
35 35
36 from pyramid.httpexceptions import HTTPFound
36 37 import rhodecode
37 38 from rhodecode.lib import auth, helpers as h
38 39 from rhodecode.lib.auth import (
39 40 LoginRequired, HasPermissionAllDecorator,
40 41 HasRepoPermissionAllDecorator, NotAnonymous, HasPermissionAny,
41 42 HasRepoGroupPermissionAny, HasRepoPermissionAnyDecorator)
42 43 from rhodecode.lib.base import BaseRepoController, render
43 44 from rhodecode.lib.ext_json import json
44 45 from rhodecode.lib.utils import repo_name_slug, jsonify
45 46 from rhodecode.lib.utils2 import safe_int, str2bool
46 47 from rhodecode.model.db import (Repository, RepoGroup, RepositoryField)
47 48 from rhodecode.model.forms import (
48 49 RepoForm, RepoFieldForm, RepoVcsSettingsForm, IssueTrackerPatternsForm)
49 50 from rhodecode.model.meta import Session
50 51 from rhodecode.model.repo import RepoModel
51 52 from rhodecode.model.scm import ScmModel, RepoGroupList, RepoList
52 53 from rhodecode.model.settings import (
53 54 SettingsModel, IssueTrackerSettingsModel, VcsSettingsModel,
54 55 SettingNotFound)
55 56
56 57 log = logging.getLogger(__name__)
57 58
58 59
59 60 class ReposController(BaseRepoController):
60 61 """
61 62 REST Controller styled on the Atom Publishing Protocol"""
62 63 # To properly map this controller, ensure your config/routing.py
63 64 # file has a resource setup:
64 65 # map.resource('repo', 'repos')
65 66
66 67 @LoginRequired()
67 68 def __before__(self):
68 69 super(ReposController, self).__before__()
69 70
70 71 def _load_repo(self, repo_name):
71 72 repo_obj = Repository.get_by_repo_name(repo_name)
72 73
73 74 if repo_obj is None:
74 75 h.not_mapped_error(repo_name)
75 76 return redirect(url('repos'))
76 77
77 78 return repo_obj
78 79
79 80 def __load_defaults(self, repo=None):
80 81 acl_groups = RepoGroupList(RepoGroup.query().all(),
81 82 perm_set=['group.write', 'group.admin'])
82 83 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
83 84 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
84 85
85 86 # in case someone no longer have a group.write access to a repository
86 87 # pre fill the list with this entry, we don't care if this is the same
87 88 # but it will allow saving repo data properly.
88 89
89 90 repo_group = None
90 91 if repo:
91 92 repo_group = repo.group
92 93 if repo_group and unicode(repo_group.group_id) not in c.repo_groups_choices:
93 94 c.repo_groups_choices.append(unicode(repo_group.group_id))
94 95 c.repo_groups.append(RepoGroup._generate_choice(repo_group))
95 96
96 97 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
97 98 c.landing_revs_choices = choices
98 99
99 100 def __load_data(self, repo_name=None):
100 101 """
101 102 Load defaults settings for edit, and update
102 103
103 104 :param repo_name:
104 105 """
105 106 c.repo_info = self._load_repo(repo_name)
106 107 self.__load_defaults(c.repo_info)
107 108
108 109 # override defaults for exact repo info here git/hg etc
109 110 if not c.repository_requirements_missing:
110 111 choices, c.landing_revs = ScmModel().get_repo_landing_revs(
111 112 c.repo_info)
112 113 c.landing_revs_choices = choices
113 114 defaults = RepoModel()._get_defaults(repo_name)
114 115
115 116 return defaults
116 117
117 118 def _log_creation_exception(self, e, repo_name):
118 119 reason = None
119 120 if len(e.args) == 2:
120 121 reason = e.args[1]
121 122
122 123 if reason == 'INVALID_CERTIFICATE':
123 124 log.exception(
124 125 'Exception creating a repository: invalid certificate')
125 126 msg = (_('Error creating repository %s: invalid certificate')
126 127 % repo_name)
127 128 else:
128 129 log.exception("Exception creating a repository")
129 130 msg = (_('Error creating repository %s')
130 131 % repo_name)
131 132
132 133 return msg
133 134
134 135 @NotAnonymous()
135 136 def index(self, format='html'):
136 137 """GET /repos: All items in the collection"""
137 138 # url('repos')
138 139
139 140 repo_list = Repository.get_all_repos()
140 141 c.repo_list = RepoList(repo_list, perm_set=['repository.admin'])
141 142 repos_data = RepoModel().get_repos_as_dict(
142 143 repo_list=c.repo_list, admin=True, super_user_actions=True)
143 144 # json used to render the grid
144 145 c.data = json.dumps(repos_data)
145 146
146 147 return render('admin/repos/repos.mako')
147 148
148 149 # perms check inside
149 150 @NotAnonymous()
150 151 @auth.CSRFRequired()
151 152 def create(self):
152 153 """
153 154 POST /repos: Create a new item"""
154 155 # url('repos')
155 156
156 157 self.__load_defaults()
157 158 form_result = {}
158 159 task_id = None
159 160 c.personal_repo_group = c.rhodecode_user.personal_repo_group
160 161 try:
161 162 # CanWriteToGroup validators checks permissions of this POST
162 163 form_result = RepoForm(repo_groups=c.repo_groups_choices,
163 164 landing_revs=c.landing_revs_choices)()\
164 165 .to_python(dict(request.POST))
165 166
166 167 # create is done sometimes async on celery, db transaction
167 168 # management is handled there.
168 169 task = RepoModel().create(form_result, c.rhodecode_user.user_id)
169 170 from celery.result import BaseAsyncResult
170 171 if isinstance(task, BaseAsyncResult):
171 172 task_id = task.task_id
172 173 except formencode.Invalid as errors:
173 174 return htmlfill.render(
174 175 render('admin/repos/repo_add.mako'),
175 176 defaults=errors.value,
176 177 errors=errors.error_dict or {},
177 178 prefix_error=False,
178 179 encoding="UTF-8",
179 180 force_defaults=False)
180 181
181 182 except Exception as e:
182 183 msg = self._log_creation_exception(e, form_result.get('repo_name'))
183 184 h.flash(msg, category='error')
184 185 return redirect(h.route_path('home'))
185 186
186 return redirect(h.url('repo_creating_home',
187 repo_name=form_result['repo_name_full'],
188 task_id=task_id))
187 raise HTTPFound(
188 h.route_path('repo_creating',
189 repo_name=form_result['repo_name_full'],
190 _query=dict(task_id=task_id)))
189 191
190 192 # perms check inside
191 193 @NotAnonymous()
192 194 def create_repository(self):
193 195 """GET /_admin/create_repository: Form to create a new item"""
194 196 new_repo = request.GET.get('repo', '')
195 197 parent_group = safe_int(request.GET.get('parent_group'))
196 198 _gr = RepoGroup.get(parent_group)
197 199
198 200 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
199 201 # you're not super admin nor have global create permissions,
200 202 # but maybe you have at least write permission to a parent group ?
201 203
202 204 gr_name = _gr.group_name if _gr else None
203 205 # create repositories with write permission on group is set to true
204 206 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
205 207 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
206 208 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
207 209 if not (group_admin or (group_write and create_on_write)):
208 210 raise HTTPForbidden
209 211
210 212 acl_groups = RepoGroupList(RepoGroup.query().all(),
211 213 perm_set=['group.write', 'group.admin'])
212 214 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
213 215 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
214 216 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
215 217 c.personal_repo_group = c.rhodecode_user.personal_repo_group
216 218 c.new_repo = repo_name_slug(new_repo)
217 219
218 220 # apply the defaults from defaults page
219 221 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
220 222 # set checkbox to autochecked
221 223 defaults['repo_copy_permissions'] = True
222 224
223 225 parent_group_choice = '-1'
224 226 if not c.rhodecode_user.is_admin and c.rhodecode_user.personal_repo_group:
225 227 parent_group_choice = c.rhodecode_user.personal_repo_group
226 228
227 229 if parent_group and _gr:
228 230 if parent_group in [x[0] for x in c.repo_groups]:
229 231 parent_group_choice = unicode(parent_group)
230 232
231 233 defaults.update({'repo_group': parent_group_choice})
232 234
233 235 return htmlfill.render(
234 236 render('admin/repos/repo_add.mako'),
235 237 defaults=defaults,
236 238 errors={},
237 239 prefix_error=False,
238 240 encoding="UTF-8",
239 241 force_defaults=False
240 242 )
241 243
242 @NotAnonymous()
243 def repo_creating(self, repo_name):
244 c.repo = repo_name
245 c.task_id = request.GET.get('task_id')
246 if not c.repo:
247 raise HTTPNotFound()
248 return render('admin/repos/repo_creating.mako')
249
250 @NotAnonymous()
251 @jsonify
252 def repo_check(self, repo_name):
253 c.repo = repo_name
254 task_id = request.GET.get('task_id')
255
256 if task_id and task_id not in ['None']:
257 import rhodecode
258 from celery.result import AsyncResult
259 if rhodecode.CELERY_ENABLED:
260 task = AsyncResult(task_id)
261 if task.failed():
262 msg = self._log_creation_exception(task.result, c.repo)
263 h.flash(msg, category='error')
264 return redirect(h.route_path('home'), code=501)
265
266 repo = Repository.get_by_repo_name(repo_name)
267 if repo and repo.repo_state == Repository.STATE_CREATED:
268 if repo.clone_uri:
269 clone_uri = repo.clone_uri_hidden
270 h.flash(_('Created repository %s from %s')
271 % (repo.repo_name, clone_uri), category='success')
272 else:
273 repo_url = h.link_to(
274 repo.repo_name,
275 h.route_path('repo_summary',repo_name=repo.repo_name))
276 fork = repo.fork
277 if fork:
278 fork_name = fork.repo_name
279 h.flash(h.literal(_('Forked repository %s as %s')
280 % (fork_name, repo_url)), category='success')
281 else:
282 h.flash(h.literal(_('Created repository %s') % repo_url),
283 category='success')
284 return {'result': True}
285 return {'result': False}
286
287 244 @HasPermissionAllDecorator('hg.admin')
288 245 def show(self, repo_name, format='html'):
289 246 """GET /repos/repo_name: Show a specific item"""
290 247 # url('repo', repo_name=ID)
291 248
292 249 @HasRepoPermissionAllDecorator('repository.admin')
293 250 def edit_fields(self, repo_name):
294 251 """GET /repo_name/settings: Form to edit an existing item"""
295 252 c.repo_info = self._load_repo(repo_name)
296 253 c.repo_fields = RepositoryField.query()\
297 254 .filter(RepositoryField.repository == c.repo_info).all()
298 255 c.active = 'fields'
299 256 if request.POST:
300 257
301 258 return redirect(url('repo_edit_fields'))
302 259 return render('admin/repos/repo_edit.mako')
303 260
304 261 @HasRepoPermissionAllDecorator('repository.admin')
305 262 @auth.CSRFRequired()
306 263 def create_repo_field(self, repo_name):
307 264 try:
308 265 form_result = RepoFieldForm()().to_python(dict(request.POST))
309 266 RepoModel().add_repo_field(
310 267 repo_name, form_result['new_field_key'],
311 268 field_type=form_result['new_field_type'],
312 269 field_value=form_result['new_field_value'],
313 270 field_label=form_result['new_field_label'],
314 271 field_desc=form_result['new_field_desc'])
315 272
316 273 Session().commit()
317 274 except Exception as e:
318 275 log.exception("Exception creating field")
319 276 msg = _('An error occurred during creation of field')
320 277 if isinstance(e, formencode.Invalid):
321 278 msg += ". " + e.msg
322 279 h.flash(msg, category='error')
323 280 return redirect(url('edit_repo_fields', repo_name=repo_name))
324 281
325 282 @HasRepoPermissionAllDecorator('repository.admin')
326 283 @auth.CSRFRequired()
327 284 def delete_repo_field(self, repo_name, field_id):
328 285 field = RepositoryField.get_or_404(field_id)
329 286 try:
330 287 RepoModel().delete_repo_field(repo_name, field.field_key)
331 288 Session().commit()
332 289 except Exception as e:
333 290 log.exception("Exception during removal of field")
334 291 msg = _('An error occurred during removal of field')
335 292 h.flash(msg, category='error')
336 293 return redirect(url('edit_repo_fields', repo_name=repo_name))
337 294
338 295 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
339 296 @auth.CSRFRequired()
340 297 def toggle_locking(self, repo_name):
341 298 """
342 299 Toggle locking of repository by simple GET call to url
343 300
344 301 :param repo_name:
345 302 """
346 303
347 304 try:
348 305 repo = Repository.get_by_repo_name(repo_name)
349 306
350 307 if repo.enable_locking:
351 308 if repo.locked[0]:
352 309 Repository.unlock(repo)
353 310 action = _('Unlocked')
354 311 else:
355 312 Repository.lock(repo, c.rhodecode_user.user_id,
356 313 lock_reason=Repository.LOCK_WEB)
357 314 action = _('Locked')
358 315
359 316 h.flash(_('Repository has been %s') % action,
360 317 category='success')
361 318 except Exception:
362 319 log.exception("Exception during unlocking")
363 320 h.flash(_('An error occurred during unlocking'),
364 321 category='error')
365 322 return redirect(h.route_path('repo_summary', repo_name=repo_name))
366 323
367 324 @HasRepoPermissionAllDecorator('repository.admin')
368 325 @auth.CSRFRequired()
369 326 def edit_remote(self, repo_name):
370 327 """PUT /{repo_name}/settings/remote: edit the repo remote."""
371 328 try:
372 329 ScmModel().pull_changes(repo_name, c.rhodecode_user.username)
373 330 h.flash(_('Pulled from remote location'), category='success')
374 331 except Exception:
375 332 log.exception("Exception during pull from remote")
376 333 h.flash(_('An error occurred during pull from remote location'),
377 334 category='error')
378 335 return redirect(url('edit_repo_remote', repo_name=c.repo_name))
379 336
380 337 @HasRepoPermissionAllDecorator('repository.admin')
381 338 def edit_remote_form(self, repo_name):
382 339 """GET /repo_name/settings: Form to edit an existing item"""
383 340 c.repo_info = self._load_repo(repo_name)
384 341 c.active = 'remote'
385 342
386 343 return render('admin/repos/repo_edit.mako')
387 344
388 345 @HasRepoPermissionAllDecorator('repository.admin')
389 346 @auth.CSRFRequired()
390 347 def edit_statistics(self, repo_name):
391 348 """PUT /{repo_name}/settings/statistics: reset the repo statistics."""
392 349 try:
393 350 RepoModel().delete_stats(repo_name)
394 351 Session().commit()
395 352 except Exception as e:
396 353 log.error(traceback.format_exc())
397 354 h.flash(_('An error occurred during deletion of repository stats'),
398 355 category='error')
399 356 return redirect(url('edit_repo_statistics', repo_name=c.repo_name))
400 357
401 358 @HasRepoPermissionAllDecorator('repository.admin')
402 359 def edit_statistics_form(self, repo_name):
403 360 """GET /repo_name/settings: Form to edit an existing item"""
404 361 c.repo_info = self._load_repo(repo_name)
405 362 repo = c.repo_info.scm_instance()
406 363
407 364 if c.repo_info.stats:
408 365 # this is on what revision we ended up so we add +1 for count
409 366 last_rev = c.repo_info.stats.stat_on_revision + 1
410 367 else:
411 368 last_rev = 0
412 369 c.stats_revision = last_rev
413 370
414 371 c.repo_last_rev = repo.count()
415 372
416 373 if last_rev == 0 or c.repo_last_rev == 0:
417 374 c.stats_percentage = 0
418 375 else:
419 376 c.stats_percentage = '%.2f' % ((float((last_rev)) / c.repo_last_rev) * 100)
420 377
421 378 c.active = 'statistics'
422 379
423 380 return render('admin/repos/repo_edit.mako')
424 381
425 382 @HasRepoPermissionAllDecorator('repository.admin')
426 383 @auth.CSRFRequired()
427 384 def repo_issuetracker_test(self, repo_name):
428 385 if request.is_xhr:
429 386 return h.urlify_commit_message(
430 387 request.POST.get('test_text', ''),
431 388 repo_name)
432 389 else:
433 390 raise HTTPBadRequest()
434 391
435 392 @HasRepoPermissionAllDecorator('repository.admin')
436 393 @auth.CSRFRequired()
437 394 def repo_issuetracker_delete(self, repo_name):
438 395 uid = request.POST.get('uid')
439 396 repo_settings = IssueTrackerSettingsModel(repo=repo_name)
440 397 try:
441 398 repo_settings.delete_entries(uid)
442 399 except Exception:
443 400 h.flash(_('Error occurred during deleting issue tracker entry'),
444 401 category='error')
445 402 else:
446 403 h.flash(_('Removed issue tracker entry'), category='success')
447 404 return redirect(url('repo_settings_issuetracker',
448 405 repo_name=repo_name))
449 406
450 407 def _update_patterns(self, form, repo_settings):
451 408 for uid in form['delete_patterns']:
452 409 repo_settings.delete_entries(uid)
453 410
454 411 for pattern in form['patterns']:
455 412 for setting, value, type_ in pattern:
456 413 sett = repo_settings.create_or_update_setting(
457 414 setting, value, type_)
458 415 Session().add(sett)
459 416
460 417 Session().commit()
461 418
462 419 @HasRepoPermissionAllDecorator('repository.admin')
463 420 @auth.CSRFRequired()
464 421 def repo_issuetracker_save(self, repo_name):
465 422 # Save inheritance
466 423 repo_settings = IssueTrackerSettingsModel(repo=repo_name)
467 424 inherited = (request.POST.get('inherit_global_issuetracker')
468 425 == "inherited")
469 426 repo_settings.inherit_global_settings = inherited
470 427 Session().commit()
471 428
472 429 form = IssueTrackerPatternsForm()().to_python(request.POST)
473 430 if form:
474 431 self._update_patterns(form, repo_settings)
475 432
476 433 h.flash(_('Updated issue tracker entries'), category='success')
477 434 return redirect(url('repo_settings_issuetracker',
478 435 repo_name=repo_name))
479 436
480 437 @HasRepoPermissionAllDecorator('repository.admin')
481 438 def repo_issuetracker(self, repo_name):
482 439 """GET /admin/settings/issue-tracker: All items in the collection"""
483 440 c.active = 'issuetracker'
484 441 c.data = 'data'
485 442 c.repo_info = self._load_repo(repo_name)
486 443
487 444 repo = Repository.get_by_repo_name(repo_name)
488 445 c.settings_model = IssueTrackerSettingsModel(repo=repo)
489 446 c.global_patterns = c.settings_model.get_global_settings()
490 447 c.repo_patterns = c.settings_model.get_repo_settings()
491 448
492 449 return render('admin/repos/repo_edit.mako')
493 450
494 451 @HasRepoPermissionAllDecorator('repository.admin')
495 452 def repo_settings_vcs(self, repo_name):
496 453 """GET /{repo_name}/settings/vcs/: All items in the collection"""
497 454
498 455 model = VcsSettingsModel(repo=repo_name)
499 456
500 457 c.active = 'vcs'
501 458 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
502 459 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
503 460 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
504 461 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
505 462 c.repo_info = self._load_repo(repo_name)
506 463 defaults = self._vcs_form_defaults(repo_name)
507 464 c.inherit_global_settings = defaults['inherit_global_settings']
508 465 c.labs_active = str2bool(
509 466 rhodecode.CONFIG.get('labs_settings_active', 'true'))
510 467
511 468 return htmlfill.render(
512 469 render('admin/repos/repo_edit.mako'),
513 470 defaults=defaults,
514 471 encoding="UTF-8",
515 472 force_defaults=False)
516 473
517 474 @HasRepoPermissionAllDecorator('repository.admin')
518 475 @auth.CSRFRequired()
519 476 def repo_settings_vcs_update(self, repo_name):
520 477 """POST /{repo_name}/settings/vcs/: All items in the collection"""
521 478 c.active = 'vcs'
522 479
523 480 model = VcsSettingsModel(repo=repo_name)
524 481 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
525 482 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
526 483 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
527 484 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
528 485 c.repo_info = self._load_repo(repo_name)
529 486 defaults = self._vcs_form_defaults(repo_name)
530 487 c.inherit_global_settings = defaults['inherit_global_settings']
531 488
532 489 application_form = RepoVcsSettingsForm(repo_name)()
533 490 try:
534 491 form_result = application_form.to_python(dict(request.POST))
535 492 except formencode.Invalid as errors:
536 493 h.flash(
537 494 _("Some form inputs contain invalid data."),
538 495 category='error')
539 496 return htmlfill.render(
540 497 render('admin/repos/repo_edit.mako'),
541 498 defaults=errors.value,
542 499 errors=errors.error_dict or {},
543 500 prefix_error=False,
544 501 encoding="UTF-8",
545 502 force_defaults=False
546 503 )
547 504
548 505 try:
549 506 inherit_global_settings = form_result['inherit_global_settings']
550 507 model.create_or_update_repo_settings(
551 508 form_result, inherit_global_settings=inherit_global_settings)
552 509 except Exception:
553 510 log.exception("Exception while updating settings")
554 511 h.flash(
555 512 _('Error occurred during updating repository VCS settings'),
556 513 category='error')
557 514 else:
558 515 Session().commit()
559 516 h.flash(_('Updated VCS settings'), category='success')
560 517 return redirect(url('repo_vcs_settings', repo_name=repo_name))
561 518
562 519 return htmlfill.render(
563 520 render('admin/repos/repo_edit.mako'),
564 521 defaults=self._vcs_form_defaults(repo_name),
565 522 encoding="UTF-8",
566 523 force_defaults=False)
567 524
568 525 @HasRepoPermissionAllDecorator('repository.admin')
569 526 @auth.CSRFRequired()
570 527 @jsonify
571 528 def repo_delete_svn_pattern(self, repo_name):
572 529 if not request.is_xhr:
573 530 return False
574 531
575 532 delete_pattern_id = request.POST.get('delete_svn_pattern')
576 533 model = VcsSettingsModel(repo=repo_name)
577 534 try:
578 535 model.delete_repo_svn_pattern(delete_pattern_id)
579 536 except SettingNotFound:
580 537 raise HTTPBadRequest()
581 538
582 539 Session().commit()
583 540 return True
584 541
585 542 def _vcs_form_defaults(self, repo_name):
586 543 model = VcsSettingsModel(repo=repo_name)
587 544 global_defaults = model.get_global_settings()
588 545
589 546 repo_defaults = {}
590 547 repo_defaults.update(global_defaults)
591 548 repo_defaults.update(model.get_repo_settings())
592 549
593 550 global_defaults = {
594 551 '{}_inherited'.format(k): global_defaults[k]
595 552 for k in global_defaults}
596 553
597 554 defaults = {
598 555 'inherit_global_settings': model.inherit_global_settings
599 556 }
600 557 defaults.update(global_defaults)
601 558 defaults.update(repo_defaults)
602 559 defaults.update({
603 560 'new_svn_branch': '',
604 561 'new_svn_tag': '',
605 562 })
606 563 return defaults
@@ -1,196 +1,198 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2017 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 forks controller for rhodecode
23 23 """
24 24
25 25 import formencode
26 26 import logging
27 27 from formencode import htmlfill
28 28
29 29 from pylons import tmpl_context as c, request, url
30 30 from pylons.controllers.util import redirect
31 31 from pylons.i18n.translation import _
32 32
33 from pyramid.httpexceptions import HTTPFound
33 34 import rhodecode.lib.helpers as h
34 35
35 36 from rhodecode.lib import auth
36 37 from rhodecode.lib.helpers import Page
37 38 from rhodecode.lib.auth import (
38 39 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous,
39 40 HasRepoPermissionAny, HasPermissionAnyDecorator, HasAcceptedRepoType)
40 41 from rhodecode.lib.base import BaseRepoController, render
41 42 from rhodecode.model.db import Repository, RepoGroup, UserFollowing, User
42 43 from rhodecode.model.repo import RepoModel
43 44 from rhodecode.model.forms import RepoForkForm
44 45 from rhodecode.model.scm import ScmModel, RepoGroupList
45 46 from rhodecode.lib.utils2 import safe_int
46 47
47 48 log = logging.getLogger(__name__)
48 49
49 50
50 51 class ForksController(BaseRepoController):
51 52
52 53 def __before__(self):
53 54 super(ForksController, self).__before__()
54 55
55 56 def __load_defaults(self):
56 57 acl_groups = RepoGroupList(
57 58 RepoGroup.query().all(),
58 59 perm_set=['group.write', 'group.admin'])
59 60 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
60 61 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
61 62 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
62 63 c.landing_revs_choices = choices
63 64 c.personal_repo_group = c.rhodecode_user.personal_repo_group
64 65
65 66 def __load_data(self, repo_name=None):
66 67 """
67 68 Load defaults settings for edit, and update
68 69
69 70 :param repo_name:
70 71 """
71 72 self.__load_defaults()
72 73
73 74 c.repo_info = Repository.get_by_repo_name(repo_name)
74 75 repo = c.repo_info.scm_instance()
75 76
76 77 if c.repo_info is None:
77 78 h.not_mapped_error(repo_name)
78 79 return redirect(url('repos'))
79 80
80 81 c.default_user_id = User.get_default_user().user_id
81 82 c.in_public_journal = UserFollowing.query()\
82 83 .filter(UserFollowing.user_id == c.default_user_id)\
83 84 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
84 85
85 86 if c.repo_info.stats:
86 87 last_rev = c.repo_info.stats.stat_on_revision+1
87 88 else:
88 89 last_rev = 0
89 90 c.stats_revision = last_rev
90 91
91 92 c.repo_last_rev = repo.count()
92 93
93 94 if last_rev == 0 or c.repo_last_rev == 0:
94 95 c.stats_percentage = 0
95 96 else:
96 97 c.stats_percentage = '%.2f' % ((float((last_rev)) /
97 98 c.repo_last_rev) * 100)
98 99
99 100 defaults = RepoModel()._get_defaults(repo_name)
100 101 # alter the description to indicate a fork
101 102 defaults['description'] = ('fork of repository: %s \n%s'
102 103 % (defaults['repo_name'],
103 104 defaults['description']))
104 105 # add suffix to fork
105 106 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
106 107
107 108 return defaults
108 109
109 110 @LoginRequired()
110 111 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
111 112 'repository.admin')
112 113 @HasAcceptedRepoType('git', 'hg')
113 114 def forks(self, repo_name):
114 115 p = safe_int(request.GET.get('page', 1), 1)
115 116 repo_id = c.rhodecode_db_repo.repo_id
116 117 d = []
117 118 for r in Repository.get_repo_forks(repo_id):
118 119 if not HasRepoPermissionAny(
119 120 'repository.read', 'repository.write', 'repository.admin'
120 121 )(r.repo_name, 'get forks check'):
121 122 continue
122 123 d.append(r)
123 124 c.forks_pager = Page(d, page=p, items_per_page=20)
124 125
125 126 c.forks_data = render('/forks/forks_data.mako')
126 127
127 128 if request.environ.get('HTTP_X_PJAX'):
128 129 return c.forks_data
129 130
130 131 return render('/forks/forks.mako')
131 132
132 133 @LoginRequired()
133 134 @NotAnonymous()
134 135 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
135 136 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
136 137 'repository.admin')
137 138 @HasAcceptedRepoType('git', 'hg')
138 139 def fork(self, repo_name):
139 140 c.repo_info = Repository.get_by_repo_name(repo_name)
140 141 if not c.repo_info:
141 142 h.not_mapped_error(repo_name)
142 143 return redirect(h.route_path('home'))
143 144
144 145 defaults = self.__load_data(repo_name)
145 146
146 147 return htmlfill.render(
147 148 render('forks/fork.mako'),
148 149 defaults=defaults,
149 150 encoding="UTF-8",
150 151 force_defaults=False
151 152 )
152 153
153 154 @LoginRequired()
154 155 @NotAnonymous()
155 156 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
156 157 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
157 158 'repository.admin')
158 159 @HasAcceptedRepoType('git', 'hg')
159 160 @auth.CSRFRequired()
160 161 def fork_create(self, repo_name):
161 162 self.__load_defaults()
162 163 c.repo_info = Repository.get_by_repo_name(repo_name)
163 164 _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type},
164 165 repo_groups=c.repo_groups_choices,
165 166 landing_revs=c.landing_revs_choices)()
166 167 form_result = {}
167 168 task_id = None
168 169 try:
169 170 form_result = _form.to_python(dict(request.POST))
170 171 # create fork is done sometimes async on celery, db transaction
171 172 # management is handled there.
172 173 task = RepoModel().create_fork(
173 174 form_result, c.rhodecode_user.user_id)
174 175 from celery.result import BaseAsyncResult
175 176 if isinstance(task, BaseAsyncResult):
176 177 task_id = task.task_id
177 178 except formencode.Invalid as errors:
178 179 c.new_repo = errors.value['repo_name']
179 180 return htmlfill.render(
180 181 render('forks/fork.mako'),
181 182 defaults=errors.value,
182 183 errors=errors.error_dict or {},
183 184 prefix_error=False,
184 185 encoding="UTF-8",
185 186 force_defaults=False)
186 187 except Exception:
187 188 log.exception(
188 189 u'Exception while trying to fork the repository %s', repo_name)
189 190 msg = (
190 191 _('An error occurred during repository forking %s') %
191 192 (repo_name, ))
192 193 h.flash(msg, category='error')
193 194
194 return redirect(h.url('repo_creating_home',
195 repo_name=form_result['repo_name_full'],
196 task_id=task_id))
195 raise HTTPFound(
196 h.route_path('repo_creating',
197 repo_name=form_result['repo_name_full'],
198 _query=dict(task_id=task_id)))
@@ -1,2392 +1,2397 b''
1 1 //Primary CSS
2 2
3 3 //--- IMPORTS ------------------//
4 4
5 5 @import 'helpers';
6 6 @import 'mixins';
7 7 @import 'rcicons';
8 8 @import 'fonts';
9 9 @import 'variables';
10 10 @import 'bootstrap-variables';
11 11 @import 'form-bootstrap';
12 12 @import 'codemirror';
13 13 @import 'legacy_code_styles';
14 14 @import 'readme-box';
15 15 @import 'progress-bar';
16 16
17 17 @import 'type';
18 18 @import 'alerts';
19 19 @import 'buttons';
20 20 @import 'tags';
21 21 @import 'code-block';
22 22 @import 'examples';
23 23 @import 'login';
24 24 @import 'main-content';
25 25 @import 'select2';
26 26 @import 'comments';
27 27 @import 'panels-bootstrap';
28 28 @import 'panels';
29 29 @import 'deform';
30 30
31 31 //--- BASE ------------------//
32 32 .noscript-error {
33 33 top: 0;
34 34 left: 0;
35 35 width: 100%;
36 36 z-index: 101;
37 37 text-align: center;
38 38 font-family: @text-semibold;
39 39 font-size: 120%;
40 40 color: white;
41 41 background-color: @alert2;
42 42 padding: 5px 0 5px 0;
43 43 }
44 44
45 45 html {
46 46 display: table;
47 47 height: 100%;
48 48 width: 100%;
49 49 }
50 50
51 51 body {
52 52 display: table-cell;
53 53 width: 100%;
54 54 }
55 55
56 56 //--- LAYOUT ------------------//
57 57
58 58 .hidden{
59 59 display: none !important;
60 60 }
61 61
62 62 .box{
63 63 float: left;
64 64 width: 100%;
65 65 }
66 66
67 67 .browser-header {
68 68 clear: both;
69 69 }
70 70 .main {
71 71 clear: both;
72 72 padding:0 0 @pagepadding;
73 73 height: auto;
74 74
75 75 &:after { //clearfix
76 76 content:"";
77 77 clear:both;
78 78 width:100%;
79 79 display:block;
80 80 }
81 81 }
82 82
83 83 .action-link{
84 84 margin-left: @padding;
85 85 padding-left: @padding;
86 86 border-left: @border-thickness solid @border-default-color;
87 87 }
88 88
89 89 input + .action-link, .action-link.first{
90 90 border-left: none;
91 91 }
92 92
93 93 .action-link.last{
94 94 margin-right: @padding;
95 95 padding-right: @padding;
96 96 }
97 97
98 98 .action-link.active,
99 99 .action-link.active a{
100 100 color: @grey4;
101 101 }
102 102
103 103 .clipboard-action {
104 104 cursor: pointer;
105 105 }
106 106
107 107 ul.simple-list{
108 108 list-style: none;
109 109 margin: 0;
110 110 padding: 0;
111 111 }
112 112
113 113 .main-content {
114 114 padding-bottom: @pagepadding;
115 115 }
116 116
117 117 .wide-mode-wrapper {
118 118 max-width:4000px !important;
119 119 }
120 120
121 121 .wrapper {
122 122 position: relative;
123 123 max-width: @wrapper-maxwidth;
124 124 margin: 0 auto;
125 125 }
126 126
127 127 #content {
128 128 clear: both;
129 129 padding: 0 @contentpadding;
130 130 }
131 131
132 132 .advanced-settings-fields{
133 133 input{
134 134 margin-left: @textmargin;
135 135 margin-right: @padding/2;
136 136 }
137 137 }
138 138
139 139 .cs_files_title {
140 140 margin: @pagepadding 0 0;
141 141 }
142 142
143 143 input.inline[type="file"] {
144 144 display: inline;
145 145 }
146 146
147 147 .error_page {
148 148 margin: 10% auto;
149 149
150 150 h1 {
151 151 color: @grey2;
152 152 }
153 153
154 154 .alert {
155 155 margin: @padding 0;
156 156 }
157 157
158 158 .error-branding {
159 159 font-family: @text-semibold;
160 160 color: @grey4;
161 161 }
162 162
163 163 .error_message {
164 164 font-family: @text-regular;
165 165 }
166 166
167 167 .sidebar {
168 168 min-height: 275px;
169 169 margin: 0;
170 170 padding: 0 0 @sidebarpadding @sidebarpadding;
171 171 border: none;
172 172 }
173 173
174 174 .main-content {
175 175 position: relative;
176 176 margin: 0 @sidebarpadding @sidebarpadding;
177 177 padding: 0 0 0 @sidebarpadding;
178 178 border-left: @border-thickness solid @grey5;
179 179
180 180 @media (max-width:767px) {
181 181 clear: both;
182 182 width: 100%;
183 183 margin: 0;
184 184 border: none;
185 185 }
186 186 }
187 187
188 188 .inner-column {
189 189 float: left;
190 190 width: 29.75%;
191 191 min-height: 150px;
192 192 margin: @sidebarpadding 2% 0 0;
193 193 padding: 0 2% 0 0;
194 194 border-right: @border-thickness solid @grey5;
195 195
196 196 @media (max-width:767px) {
197 197 clear: both;
198 198 width: 100%;
199 199 border: none;
200 200 }
201 201
202 202 ul {
203 203 padding-left: 1.25em;
204 204 }
205 205
206 206 &:last-child {
207 207 margin: @sidebarpadding 0 0;
208 208 border: none;
209 209 }
210 210
211 211 h4 {
212 212 margin: 0 0 @padding;
213 213 font-family: @text-semibold;
214 214 }
215 215 }
216 216 }
217 217 .error-page-logo {
218 218 width: 130px;
219 219 height: 160px;
220 220 }
221 221
222 222 // HEADER
223 223 .header {
224 224
225 225 // TODO: johbo: Fix login pages, so that they work without a min-height
226 226 // for the header and then remove the min-height. I chose a smaller value
227 227 // intentionally here to avoid rendering issues in the main navigation.
228 228 min-height: 49px;
229 229
230 230 position: relative;
231 231 vertical-align: bottom;
232 232 padding: 0 @header-padding;
233 233 background-color: @grey2;
234 234 color: @grey5;
235 235
236 236 .title {
237 237 overflow: visible;
238 238 }
239 239
240 240 &:before,
241 241 &:after {
242 242 content: "";
243 243 clear: both;
244 244 width: 100%;
245 245 }
246 246
247 247 // TODO: johbo: Avoids breaking "Repositories" chooser
248 248 .select2-container .select2-choice .select2-arrow {
249 249 display: none;
250 250 }
251 251 }
252 252
253 253 #header-inner {
254 254 &.title {
255 255 margin: 0;
256 256 }
257 257 &:before,
258 258 &:after {
259 259 content: "";
260 260 clear: both;
261 261 }
262 262 }
263 263
264 264 // Gists
265 265 #files_data {
266 266 clear: both; //for firefox
267 267 }
268 268 #gistid {
269 269 margin-right: @padding;
270 270 }
271 271
272 272 // Global Settings Editor
273 273 .textarea.editor {
274 274 float: left;
275 275 position: relative;
276 276 max-width: @texteditor-width;
277 277
278 278 select {
279 279 position: absolute;
280 280 top:10px;
281 281 right:0;
282 282 }
283 283
284 284 .CodeMirror {
285 285 margin: 0;
286 286 }
287 287
288 288 .help-block {
289 289 margin: 0 0 @padding;
290 290 padding:.5em;
291 291 background-color: @grey6;
292 292 &.pre-formatting {
293 293 white-space: pre;
294 294 }
295 295 }
296 296 }
297 297
298 298 ul.auth_plugins {
299 299 margin: @padding 0 @padding @legend-width;
300 300 padding: 0;
301 301
302 302 li {
303 303 margin-bottom: @padding;
304 304 line-height: 1em;
305 305 list-style-type: none;
306 306
307 307 .auth_buttons .btn {
308 308 margin-right: @padding;
309 309 }
310 310
311 311 &:before { content: none; }
312 312 }
313 313 }
314 314
315 315
316 316 // My Account PR list
317 317
318 318 #show_closed {
319 319 margin: 0 1em 0 0;
320 320 }
321 321
322 322 .pullrequestlist {
323 323 .closed {
324 324 background-color: @grey6;
325 325 }
326 326 .td-status {
327 327 padding-left: .5em;
328 328 }
329 329 .log-container .truncate {
330 330 height: 2.75em;
331 331 white-space: pre-line;
332 332 }
333 333 table.rctable .user {
334 334 padding-left: 0;
335 335 }
336 336 table.rctable {
337 337 td.td-description,
338 338 .rc-user {
339 339 min-width: auto;
340 340 }
341 341 }
342 342 }
343 343
344 344 // Pull Requests
345 345
346 346 .pullrequests_section_head {
347 347 display: block;
348 348 clear: both;
349 349 margin: @padding 0;
350 350 font-family: @text-bold;
351 351 }
352 352
353 353 .pr-origininfo, .pr-targetinfo {
354 354 position: relative;
355 355
356 356 .tag {
357 357 display: inline-block;
358 358 margin: 0 1em .5em 0;
359 359 }
360 360
361 361 .clone-url {
362 362 display: inline-block;
363 363 margin: 0 0 .5em 0;
364 364 padding: 0;
365 365 line-height: 1.2em;
366 366 }
367 367 }
368 368
369 369 .pr-mergeinfo {
370 370 min-width: 95% !important;
371 371 padding: 0 !important;
372 372 border: 0;
373 373 }
374 374 .pr-mergeinfo-copy {
375 375 padding: 0 0;
376 376 }
377 377
378 378 .pr-pullinfo {
379 379 min-width: 95% !important;
380 380 padding: 0 !important;
381 381 border: 0;
382 382 }
383 383 .pr-pullinfo-copy {
384 384 padding: 0 0;
385 385 }
386 386
387 387
388 388 #pr-title-input {
389 389 width: 72%;
390 390 font-size: 1em;
391 391 font-family: @text-bold;
392 392 margin: 0;
393 393 padding: 0 0 0 @padding/4;
394 394 line-height: 1.7em;
395 395 color: @text-color;
396 396 letter-spacing: .02em;
397 397 }
398 398
399 399 #pullrequest_title {
400 400 width: 100%;
401 401 box-sizing: border-box;
402 402 }
403 403
404 404 #pr_open_message {
405 405 border: @border-thickness solid #fff;
406 406 border-radius: @border-radius;
407 407 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
408 408 text-align: left;
409 409 overflow: hidden;
410 410 }
411 411
412 412 .pr-submit-button {
413 413 float: right;
414 414 margin: 0 0 0 5px;
415 415 }
416 416
417 417 .pr-spacing-container {
418 418 padding: 20px;
419 419 clear: both
420 420 }
421 421
422 422 #pr-description-input {
423 423 margin-bottom: 0;
424 424 }
425 425
426 426 .pr-description-label {
427 427 vertical-align: top;
428 428 }
429 429
430 430 .perms_section_head {
431 431 min-width: 625px;
432 432
433 433 h2 {
434 434 margin-bottom: 0;
435 435 }
436 436
437 437 .label-checkbox {
438 438 float: left;
439 439 }
440 440
441 441 &.field {
442 442 margin: @space 0 @padding;
443 443 }
444 444
445 445 &:first-child.field {
446 446 margin-top: 0;
447 447
448 448 .label {
449 449 margin-top: 0;
450 450 padding-top: 0;
451 451 }
452 452
453 453 .radios {
454 454 padding-top: 0;
455 455 }
456 456 }
457 457
458 458 .radios {
459 459 float: right;
460 460 position: relative;
461 461 width: 405px;
462 462 }
463 463 }
464 464
465 465 //--- MODULES ------------------//
466 466
467 467
468 468 // Server Announcement
469 469 #server-announcement {
470 470 width: 95%;
471 471 margin: @padding auto;
472 472 padding: @padding;
473 473 border-width: 2px;
474 474 border-style: solid;
475 475 .border-radius(2px);
476 476 font-family: @text-bold;
477 477
478 478 &.info { border-color: @alert4; background-color: @alert4-inner; }
479 479 &.warning { border-color: @alert3; background-color: @alert3-inner; }
480 480 &.error { border-color: @alert2; background-color: @alert2-inner; }
481 481 &.success { border-color: @alert1; background-color: @alert1-inner; }
482 482 &.neutral { border-color: @grey3; background-color: @grey6; }
483 483 }
484 484
485 485 // Fixed Sidebar Column
486 486 .sidebar-col-wrapper {
487 487 padding-left: @sidebar-all-width;
488 488
489 489 .sidebar {
490 490 width: @sidebar-width;
491 491 margin-left: -@sidebar-all-width;
492 492 }
493 493 }
494 494
495 495 .sidebar-col-wrapper.scw-small {
496 496 padding-left: @sidebar-small-all-width;
497 497
498 498 .sidebar {
499 499 width: @sidebar-small-width;
500 500 margin-left: -@sidebar-small-all-width;
501 501 }
502 502 }
503 503
504 504
505 505 // FOOTER
506 506 #footer {
507 507 padding: 0;
508 508 text-align: center;
509 509 vertical-align: middle;
510 510 color: @grey2;
511 511 background-color: @grey6;
512 512
513 513 p {
514 514 margin: 0;
515 515 padding: 1em;
516 516 line-height: 1em;
517 517 }
518 518
519 519 .server-instance { //server instance
520 520 display: none;
521 521 }
522 522
523 523 .title {
524 524 float: none;
525 525 margin: 0 auto;
526 526 }
527 527 }
528 528
529 529 button.close {
530 530 padding: 0;
531 531 cursor: pointer;
532 532 background: transparent;
533 533 border: 0;
534 534 .box-shadow(none);
535 535 -webkit-appearance: none;
536 536 }
537 537
538 538 .close {
539 539 float: right;
540 540 font-size: 21px;
541 541 font-family: @text-bootstrap;
542 542 line-height: 1em;
543 543 font-weight: bold;
544 544 color: @grey2;
545 545
546 546 &:hover,
547 547 &:focus {
548 548 color: @grey1;
549 549 text-decoration: none;
550 550 cursor: pointer;
551 551 }
552 552 }
553 553
554 554 // GRID
555 555 .sorting,
556 556 .sorting_desc,
557 557 .sorting_asc {
558 558 cursor: pointer;
559 559 }
560 560 .sorting_desc:after {
561 561 content: "\00A0\25B2";
562 562 font-size: .75em;
563 563 }
564 564 .sorting_asc:after {
565 565 content: "\00A0\25BC";
566 566 font-size: .68em;
567 567 }
568 568
569 569
570 570 .user_auth_tokens {
571 571
572 572 &.truncate {
573 573 white-space: nowrap;
574 574 overflow: hidden;
575 575 text-overflow: ellipsis;
576 576 }
577 577
578 578 .fields .field .input {
579 579 margin: 0;
580 580 }
581 581
582 582 input#description {
583 583 width: 100px;
584 584 margin: 0;
585 585 }
586 586
587 587 .drop-menu {
588 588 // TODO: johbo: Remove this, should work out of the box when
589 589 // having multiple inputs inline
590 590 margin: 0 0 0 5px;
591 591 }
592 592 }
593 593 #user_list_table {
594 594 .closed {
595 595 background-color: @grey6;
596 596 }
597 597 }
598 598
599 599
600 600 input {
601 601 &.disabled {
602 602 opacity: .5;
603 603 }
604 604 }
605 605
606 606 // remove extra padding in firefox
607 607 input::-moz-focus-inner { border:0; padding:0 }
608 608
609 609 .adjacent input {
610 610 margin-bottom: @padding;
611 611 }
612 612
613 613 .permissions_boxes {
614 614 display: block;
615 615 }
616 616
617 617 //TODO: lisa: this should be in tables
618 618 .show_more_col {
619 619 width: 20px;
620 620 }
621 621
622 622 //FORMS
623 623
624 624 .medium-inline,
625 625 input#description.medium-inline {
626 626 display: inline;
627 627 width: @medium-inline-input-width;
628 628 min-width: 100px;
629 629 }
630 630
631 631 select {
632 632 //reset
633 633 -webkit-appearance: none;
634 634 -moz-appearance: none;
635 635
636 636 display: inline-block;
637 637 height: 28px;
638 638 width: auto;
639 639 margin: 0 @padding @padding 0;
640 640 padding: 0 18px 0 8px;
641 641 line-height:1em;
642 642 font-size: @basefontsize;
643 643 border: @border-thickness solid @rcblue;
644 644 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
645 645 color: @rcblue;
646 646
647 647 &:after {
648 648 content: "\00A0\25BE";
649 649 }
650 650
651 651 &:focus {
652 652 outline: none;
653 653 }
654 654 }
655 655
656 656 option {
657 657 &:focus {
658 658 outline: none;
659 659 }
660 660 }
661 661
662 662 input,
663 663 textarea {
664 664 padding: @input-padding;
665 665 border: @input-border-thickness solid @border-highlight-color;
666 666 .border-radius (@border-radius);
667 667 font-family: @text-light;
668 668 font-size: @basefontsize;
669 669
670 670 &.input-sm {
671 671 padding: 5px;
672 672 }
673 673
674 674 &#description {
675 675 min-width: @input-description-minwidth;
676 676 min-height: 1em;
677 677 padding: 10px;
678 678 }
679 679 }
680 680
681 681 .field-sm {
682 682 input,
683 683 textarea {
684 684 padding: 5px;
685 685 }
686 686 }
687 687
688 688 textarea {
689 689 display: block;
690 690 clear: both;
691 691 width: 100%;
692 692 min-height: 100px;
693 693 margin-bottom: @padding;
694 694 .box-sizing(border-box);
695 695 overflow: auto;
696 696 }
697 697
698 698 label {
699 699 font-family: @text-light;
700 700 }
701 701
702 702 // GRAVATARS
703 703 // centers gravatar on username to the right
704 704
705 705 .gravatar {
706 706 display: inline;
707 707 min-width: 16px;
708 708 min-height: 16px;
709 709 margin: -5px 0;
710 710 padding: 0;
711 711 line-height: 1em;
712 712 border: 1px solid @grey4;
713 713 box-sizing: content-box;
714 714
715 715 &.gravatar-large {
716 716 margin: -0.5em .25em -0.5em 0;
717 717 }
718 718
719 719 & + .user {
720 720 display: inline;
721 721 margin: 0;
722 722 padding: 0 0 0 .17em;
723 723 line-height: 1em;
724 724 }
725 725 }
726 726
727 727 .user-inline-data {
728 728 display: inline-block;
729 729 float: left;
730 730 padding-left: .5em;
731 731 line-height: 1.3em;
732 732 }
733 733
734 734 .rc-user { // gravatar + user wrapper
735 735 float: left;
736 736 position: relative;
737 737 min-width: 100px;
738 738 max-width: 200px;
739 739 min-height: (@gravatar-size + @border-thickness * 2); // account for border
740 740 display: block;
741 741 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
742 742
743 743
744 744 .gravatar {
745 745 display: block;
746 746 position: absolute;
747 747 top: 0;
748 748 left: 0;
749 749 min-width: @gravatar-size;
750 750 min-height: @gravatar-size;
751 751 margin: 0;
752 752 }
753 753
754 754 .user {
755 755 display: block;
756 756 max-width: 175px;
757 757 padding-top: 2px;
758 758 overflow: hidden;
759 759 text-overflow: ellipsis;
760 760 }
761 761 }
762 762
763 763 .gist-gravatar,
764 764 .journal_container {
765 765 .gravatar-large {
766 766 margin: 0 .5em -10px 0;
767 767 }
768 768 }
769 769
770 770
771 771 // ADMIN SETTINGS
772 772
773 773 // Tag Patterns
774 774 .tag_patterns {
775 775 .tag_input {
776 776 margin-bottom: @padding;
777 777 }
778 778 }
779 779
780 780 .locked_input {
781 781 position: relative;
782 782
783 783 input {
784 784 display: inline;
785 785 margin: 3px 5px 0px 0px;
786 786 }
787 787
788 788 br {
789 789 display: none;
790 790 }
791 791
792 792 .error-message {
793 793 float: left;
794 794 width: 100%;
795 795 }
796 796
797 797 .lock_input_button {
798 798 display: inline;
799 799 }
800 800
801 801 .help-block {
802 802 clear: both;
803 803 }
804 804 }
805 805
806 806 // Notifications
807 807
808 808 .notifications_buttons {
809 809 margin: 0 0 @space 0;
810 810 padding: 0;
811 811
812 812 .btn {
813 813 display: inline-block;
814 814 }
815 815 }
816 816
817 817 .notification-list {
818 818
819 819 div {
820 820 display: inline-block;
821 821 vertical-align: middle;
822 822 }
823 823
824 824 .container {
825 825 display: block;
826 826 margin: 0 0 @padding 0;
827 827 }
828 828
829 829 .delete-notifications {
830 830 margin-left: @padding;
831 831 text-align: right;
832 832 cursor: pointer;
833 833 }
834 834
835 835 .read-notifications {
836 836 margin-left: @padding/2;
837 837 text-align: right;
838 838 width: 35px;
839 839 cursor: pointer;
840 840 }
841 841
842 842 .icon-minus-sign {
843 843 color: @alert2;
844 844 }
845 845
846 846 .icon-ok-sign {
847 847 color: @alert1;
848 848 }
849 849 }
850 850
851 851 .user_settings {
852 852 float: left;
853 853 clear: both;
854 854 display: block;
855 855 width: 100%;
856 856
857 857 .gravatar_box {
858 858 margin-bottom: @padding;
859 859
860 860 &:after {
861 861 content: " ";
862 862 clear: both;
863 863 width: 100%;
864 864 }
865 865 }
866 866
867 867 .fields .field {
868 868 clear: both;
869 869 }
870 870 }
871 871
872 872 .advanced_settings {
873 873 margin-bottom: @space;
874 874
875 875 .help-block {
876 876 margin-left: 0;
877 877 }
878 878
879 879 button + .help-block {
880 880 margin-top: @padding;
881 881 }
882 882 }
883 883
884 884 // admin settings radio buttons and labels
885 885 .label-2 {
886 886 float: left;
887 887 width: @label2-width;
888 888
889 889 label {
890 890 color: @grey1;
891 891 }
892 892 }
893 893 .checkboxes {
894 894 float: left;
895 895 width: @checkboxes-width;
896 896 margin-bottom: @padding;
897 897
898 898 .checkbox {
899 899 width: 100%;
900 900
901 901 label {
902 902 margin: 0;
903 903 padding: 0;
904 904 }
905 905 }
906 906
907 907 .checkbox + .checkbox {
908 908 display: inline-block;
909 909 }
910 910
911 911 label {
912 912 margin-right: 1em;
913 913 }
914 914 }
915 915
916 916 // CHANGELOG
917 917 .container_header {
918 918 float: left;
919 919 display: block;
920 920 width: 100%;
921 921 margin: @padding 0 @padding;
922 922
923 923 #filter_changelog {
924 924 float: left;
925 925 margin-right: @padding;
926 926 }
927 927
928 928 .breadcrumbs_light {
929 929 display: inline-block;
930 930 }
931 931 }
932 932
933 933 .info_box {
934 934 float: right;
935 935 }
936 936
937 937
938 938 #graph_nodes {
939 939 padding-top: 43px;
940 940 }
941 941
942 942 #graph_content{
943 943
944 944 // adjust for table headers so that graph renders properly
945 945 // #graph_nodes padding - table cell padding
946 946 padding-top: (@space - (@basefontsize * 2.4));
947 947
948 948 &.graph_full_width {
949 949 width: 100%;
950 950 max-width: 100%;
951 951 }
952 952 }
953 953
954 954 #graph {
955 955 .flag_status {
956 956 margin: 0;
957 957 }
958 958
959 959 .pagination-left {
960 960 float: left;
961 961 clear: both;
962 962 }
963 963
964 964 .log-container {
965 965 max-width: 345px;
966 966
967 967 .message{
968 968 max-width: 340px;
969 969 }
970 970 }
971 971
972 972 .graph-col-wrapper {
973 973 padding-left: 110px;
974 974
975 975 #graph_nodes {
976 976 width: 100px;
977 977 margin-left: -110px;
978 978 float: left;
979 979 clear: left;
980 980 }
981 981 }
982 982
983 983 .load-more-commits {
984 984 text-align: center;
985 985 }
986 986 .load-more-commits:hover {
987 987 background-color: @grey7;
988 988 }
989 989 .load-more-commits {
990 990 a {
991 991 display: block;
992 992 }
993 993 }
994 994 }
995 995
996 996 #filter_changelog {
997 997 float: left;
998 998 }
999 999
1000 1000
1001 1001 //--- THEME ------------------//
1002 1002
1003 1003 #logo {
1004 1004 float: left;
1005 1005 margin: 9px 0 0 0;
1006 1006
1007 1007 .header {
1008 1008 background-color: transparent;
1009 1009 }
1010 1010
1011 1011 a {
1012 1012 display: inline-block;
1013 1013 }
1014 1014
1015 1015 img {
1016 1016 height:30px;
1017 1017 }
1018 1018 }
1019 1019
1020 1020 .logo-wrapper {
1021 1021 float:left;
1022 1022 }
1023 1023
1024 1024 .branding{
1025 1025 float: left;
1026 1026 padding: 9px 2px;
1027 1027 line-height: 1em;
1028 1028 font-size: @navigation-fontsize;
1029 1029 }
1030 1030
1031 1031 img {
1032 1032 border: none;
1033 1033 outline: none;
1034 1034 }
1035 1035 user-profile-header
1036 1036 label {
1037 1037
1038 1038 input[type="checkbox"] {
1039 1039 margin-right: 1em;
1040 1040 }
1041 1041 input[type="radio"] {
1042 1042 margin-right: 1em;
1043 1043 }
1044 1044 }
1045 1045
1046 1046 .flag_status {
1047 1047 margin: 2px 8px 6px 2px;
1048 1048 &.under_review {
1049 1049 .circle(5px, @alert3);
1050 1050 }
1051 1051 &.approved {
1052 1052 .circle(5px, @alert1);
1053 1053 }
1054 1054 &.rejected,
1055 1055 &.forced_closed{
1056 1056 .circle(5px, @alert2);
1057 1057 }
1058 1058 &.not_reviewed {
1059 1059 .circle(5px, @grey5);
1060 1060 }
1061 1061 }
1062 1062
1063 1063 .flag_status_comment_box {
1064 1064 margin: 5px 6px 0px 2px;
1065 1065 }
1066 1066 .test_pattern_preview {
1067 1067 margin: @space 0;
1068 1068
1069 1069 p {
1070 1070 margin-bottom: 0;
1071 1071 border-bottom: @border-thickness solid @border-default-color;
1072 1072 color: @grey3;
1073 1073 }
1074 1074
1075 1075 .btn {
1076 1076 margin-bottom: @padding;
1077 1077 }
1078 1078 }
1079 1079 #test_pattern_result {
1080 1080 display: none;
1081 1081 &:extend(pre);
1082 1082 padding: .9em;
1083 1083 color: @grey3;
1084 1084 background-color: @grey7;
1085 1085 border-right: @border-thickness solid @border-default-color;
1086 1086 border-bottom: @border-thickness solid @border-default-color;
1087 1087 border-left: @border-thickness solid @border-default-color;
1088 1088 }
1089 1089
1090 1090 #repo_vcs_settings {
1091 1091 #inherit_overlay_vcs_default {
1092 1092 display: none;
1093 1093 }
1094 1094 #inherit_overlay_vcs_custom {
1095 1095 display: custom;
1096 1096 }
1097 1097 &.inherited {
1098 1098 #inherit_overlay_vcs_default {
1099 1099 display: block;
1100 1100 }
1101 1101 #inherit_overlay_vcs_custom {
1102 1102 display: none;
1103 1103 }
1104 1104 }
1105 1105 }
1106 1106
1107 1107 .issue-tracker-link {
1108 1108 color: @rcblue;
1109 1109 }
1110 1110
1111 1111 // Issue Tracker Table Show/Hide
1112 1112 #repo_issue_tracker {
1113 1113 #inherit_overlay {
1114 1114 display: none;
1115 1115 }
1116 1116 #custom_overlay {
1117 1117 display: custom;
1118 1118 }
1119 1119 &.inherited {
1120 1120 #inherit_overlay {
1121 1121 display: block;
1122 1122 }
1123 1123 #custom_overlay {
1124 1124 display: none;
1125 1125 }
1126 1126 }
1127 1127 }
1128 1128 table.issuetracker {
1129 1129 &.readonly {
1130 1130 tr, td {
1131 1131 color: @grey3;
1132 1132 }
1133 1133 }
1134 1134 .edit {
1135 1135 display: none;
1136 1136 }
1137 1137 .editopen {
1138 1138 .edit {
1139 1139 display: inline;
1140 1140 }
1141 1141 .entry {
1142 1142 display: none;
1143 1143 }
1144 1144 }
1145 1145 tr td.td-action {
1146 1146 min-width: 117px;
1147 1147 }
1148 1148 td input {
1149 1149 max-width: none;
1150 1150 min-width: 30px;
1151 1151 width: 80%;
1152 1152 }
1153 1153 .issuetracker_pref input {
1154 1154 width: 40%;
1155 1155 }
1156 1156 input.edit_issuetracker_update {
1157 1157 margin-right: 0;
1158 1158 width: auto;
1159 1159 }
1160 1160 }
1161 1161
1162 1162 table.integrations {
1163 1163 .td-icon {
1164 1164 width: 20px;
1165 1165 .integration-icon {
1166 1166 height: 20px;
1167 1167 width: 20px;
1168 1168 }
1169 1169 }
1170 1170 }
1171 1171
1172 1172 .integrations {
1173 1173 a.integration-box {
1174 1174 color: @text-color;
1175 1175 &:hover {
1176 1176 .panel {
1177 1177 background: #fbfbfb;
1178 1178 }
1179 1179 }
1180 1180 .integration-icon {
1181 1181 width: 30px;
1182 1182 height: 30px;
1183 1183 margin-right: 20px;
1184 1184 float: left;
1185 1185 }
1186 1186
1187 1187 .panel-body {
1188 1188 padding: 10px;
1189 1189 }
1190 1190 .panel {
1191 1191 margin-bottom: 10px;
1192 1192 }
1193 1193 h2 {
1194 1194 display: inline-block;
1195 1195 margin: 0;
1196 1196 min-width: 140px;
1197 1197 }
1198 1198 }
1199 1199 }
1200 1200
1201 1201 //Permissions Settings
1202 1202 #add_perm {
1203 1203 margin: 0 0 @padding;
1204 1204 cursor: pointer;
1205 1205 }
1206 1206
1207 1207 .perm_ac {
1208 1208 input {
1209 1209 width: 95%;
1210 1210 }
1211 1211 }
1212 1212
1213 1213 .autocomplete-suggestions {
1214 1214 width: auto !important; // overrides autocomplete.js
1215 1215 margin: 0;
1216 1216 border: @border-thickness solid @rcblue;
1217 1217 border-radius: @border-radius;
1218 1218 color: @rcblue;
1219 1219 background-color: white;
1220 1220 }
1221 1221 .autocomplete-selected {
1222 1222 background: #F0F0F0;
1223 1223 }
1224 1224 .ac-container-wrap {
1225 1225 margin: 0;
1226 1226 padding: 8px;
1227 1227 border-bottom: @border-thickness solid @rclightblue;
1228 1228 list-style-type: none;
1229 1229 cursor: pointer;
1230 1230
1231 1231 &:hover {
1232 1232 background-color: @rclightblue;
1233 1233 }
1234 1234
1235 1235 img {
1236 1236 height: @gravatar-size;
1237 1237 width: @gravatar-size;
1238 1238 margin-right: 1em;
1239 1239 }
1240 1240
1241 1241 strong {
1242 1242 font-weight: normal;
1243 1243 }
1244 1244 }
1245 1245
1246 1246 // Settings Dropdown
1247 1247 .user-menu .container {
1248 1248 padding: 0 4px;
1249 1249 margin: 0;
1250 1250 }
1251 1251
1252 1252 .user-menu .gravatar {
1253 1253 cursor: pointer;
1254 1254 }
1255 1255
1256 1256 .codeblock {
1257 1257 margin-bottom: @padding;
1258 1258 clear: both;
1259 1259
1260 1260 .stats{
1261 1261 overflow: hidden;
1262 1262 }
1263 1263
1264 1264 .message{
1265 1265 textarea{
1266 1266 margin: 0;
1267 1267 }
1268 1268 }
1269 1269
1270 1270 .code-header {
1271 1271 .stats {
1272 1272 line-height: 2em;
1273 1273
1274 1274 .revision_id {
1275 1275 margin-left: 0;
1276 1276 }
1277 1277 .buttons {
1278 1278 padding-right: 0;
1279 1279 }
1280 1280 }
1281 1281
1282 1282 .item{
1283 1283 margin-right: 0.5em;
1284 1284 }
1285 1285 }
1286 1286
1287 1287 #editor_container{
1288 1288 position: relative;
1289 1289 margin: @padding;
1290 1290 }
1291 1291 }
1292 1292
1293 1293 #file_history_container {
1294 1294 display: none;
1295 1295 }
1296 1296
1297 1297 .file-history-inner {
1298 1298 margin-bottom: 10px;
1299 1299 }
1300 1300
1301 1301 // Pull Requests
1302 1302 .summary-details {
1303 1303 width: 72%;
1304 1304 }
1305 1305 .pr-summary {
1306 1306 border-bottom: @border-thickness solid @grey5;
1307 1307 margin-bottom: @space;
1308 1308 }
1309 1309 .reviewers-title {
1310 1310 width: 25%;
1311 1311 min-width: 200px;
1312 1312 }
1313 1313 .reviewers {
1314 1314 width: 25%;
1315 1315 min-width: 200px;
1316 1316 }
1317 1317 .reviewers ul li {
1318 1318 position: relative;
1319 1319 width: 100%;
1320 1320 margin-bottom: 8px;
1321 1321 }
1322 1322
1323 1323 .reviewer_entry {
1324 1324 min-height: 55px;
1325 1325 }
1326 1326
1327 1327 .reviewers_member {
1328 1328 width: 100%;
1329 1329 overflow: auto;
1330 1330 }
1331 1331 .reviewer_reason {
1332 1332 padding-left: 20px;
1333 1333 }
1334 1334 .reviewer_status {
1335 1335 display: inline-block;
1336 1336 vertical-align: top;
1337 1337 width: 7%;
1338 1338 min-width: 20px;
1339 1339 height: 1.2em;
1340 1340 margin-top: 3px;
1341 1341 line-height: 1em;
1342 1342 }
1343 1343
1344 1344 .reviewer_name {
1345 1345 display: inline-block;
1346 1346 max-width: 83%;
1347 1347 padding-right: 20px;
1348 1348 vertical-align: middle;
1349 1349 line-height: 1;
1350 1350
1351 1351 .rc-user {
1352 1352 min-width: 0;
1353 1353 margin: -2px 1em 0 0;
1354 1354 }
1355 1355
1356 1356 .reviewer {
1357 1357 float: left;
1358 1358 }
1359 1359 }
1360 1360
1361 1361 .reviewer_member_mandatory,
1362 1362 .reviewer_member_mandatory_remove,
1363 1363 .reviewer_member_remove {
1364 1364 position: absolute;
1365 1365 right: 0;
1366 1366 top: 0;
1367 1367 width: 16px;
1368 1368 margin-bottom: 10px;
1369 1369 padding: 0;
1370 1370 color: black;
1371 1371 }
1372 1372
1373 1373 .reviewer_member_mandatory_remove {
1374 1374 color: @grey4;
1375 1375 }
1376 1376
1377 1377 .reviewer_member_mandatory {
1378 1378 padding-top:20px;
1379 1379 }
1380 1380
1381 1381 .reviewer_member_status {
1382 1382 margin-top: 5px;
1383 1383 }
1384 1384 .pr-summary #summary{
1385 1385 width: 100%;
1386 1386 }
1387 1387 .pr-summary .action_button:hover {
1388 1388 border: 0;
1389 1389 cursor: pointer;
1390 1390 }
1391 1391 .pr-details-title {
1392 1392 padding-bottom: 8px;
1393 1393 border-bottom: @border-thickness solid @grey5;
1394 1394
1395 1395 .action_button.disabled {
1396 1396 color: @grey4;
1397 1397 cursor: inherit;
1398 1398 }
1399 1399 .action_button {
1400 1400 color: @rcblue;
1401 1401 }
1402 1402 }
1403 1403 .pr-details-content {
1404 1404 margin-top: @textmargin;
1405 1405 margin-bottom: @textmargin;
1406 1406 }
1407 1407 .pr-description {
1408 1408 white-space:pre-wrap;
1409 1409 }
1410 1410
1411 1411 .pr-reviewer-rules {
1412 1412 padding: 10px 0px 20px 0px;
1413 1413 }
1414 1414
1415 1415 .group_members {
1416 1416 margin-top: 0;
1417 1417 padding: 0;
1418 1418 list-style: outside none none;
1419 1419
1420 1420 img {
1421 1421 height: @gravatar-size;
1422 1422 width: @gravatar-size;
1423 1423 margin-right: .5em;
1424 1424 margin-left: 3px;
1425 1425 }
1426 1426
1427 1427 .to-delete {
1428 1428 .user {
1429 1429 text-decoration: line-through;
1430 1430 }
1431 1431 }
1432 1432 }
1433 1433
1434 1434 .compare_view_commits_title {
1435 1435 .disabled {
1436 1436 cursor: inherit;
1437 1437 &:hover{
1438 1438 background-color: inherit;
1439 1439 color: inherit;
1440 1440 }
1441 1441 }
1442 1442 }
1443 1443
1444 1444 .subtitle-compare {
1445 1445 margin: -15px 0px 0px 0px;
1446 1446 }
1447 1447
1448 1448 .comments-summary-td {
1449 1449 border-top: 1px dashed @grey5;
1450 1450 }
1451 1451
1452 1452 // new entry in group_members
1453 1453 .td-author-new-entry {
1454 1454 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1455 1455 }
1456 1456
1457 1457 .usergroup_member_remove {
1458 1458 width: 16px;
1459 1459 margin-bottom: 10px;
1460 1460 padding: 0;
1461 1461 color: black !important;
1462 1462 cursor: pointer;
1463 1463 }
1464 1464
1465 1465 .reviewer_ac .ac-input {
1466 1466 width: 92%;
1467 1467 margin-bottom: 1em;
1468 1468 }
1469 1469
1470 1470 .compare_view_commits tr{
1471 1471 height: 20px;
1472 1472 }
1473 1473 .compare_view_commits td {
1474 1474 vertical-align: top;
1475 1475 padding-top: 10px;
1476 1476 }
1477 1477 .compare_view_commits .author {
1478 1478 margin-left: 5px;
1479 1479 }
1480 1480
1481 1481 .compare_view_commits {
1482 1482 .color-a {
1483 1483 color: @alert1;
1484 1484 }
1485 1485
1486 1486 .color-c {
1487 1487 color: @color3;
1488 1488 }
1489 1489
1490 1490 .color-r {
1491 1491 color: @color5;
1492 1492 }
1493 1493
1494 1494 .color-a-bg {
1495 1495 background-color: @alert1;
1496 1496 }
1497 1497
1498 1498 .color-c-bg {
1499 1499 background-color: @alert3;
1500 1500 }
1501 1501
1502 1502 .color-r-bg {
1503 1503 background-color: @alert2;
1504 1504 }
1505 1505
1506 1506 .color-a-border {
1507 1507 border: 1px solid @alert1;
1508 1508 }
1509 1509
1510 1510 .color-c-border {
1511 1511 border: 1px solid @alert3;
1512 1512 }
1513 1513
1514 1514 .color-r-border {
1515 1515 border: 1px solid @alert2;
1516 1516 }
1517 1517
1518 1518 .commit-change-indicator {
1519 1519 width: 15px;
1520 1520 height: 15px;
1521 1521 position: relative;
1522 1522 left: 15px;
1523 1523 }
1524 1524
1525 1525 .commit-change-content {
1526 1526 text-align: center;
1527 1527 vertical-align: middle;
1528 1528 line-height: 15px;
1529 1529 }
1530 1530 }
1531 1531
1532 1532 .compare_view_files {
1533 1533 width: 100%;
1534 1534
1535 1535 td {
1536 1536 vertical-align: middle;
1537 1537 }
1538 1538 }
1539 1539
1540 1540 .compare_view_filepath {
1541 1541 color: @grey1;
1542 1542 }
1543 1543
1544 1544 .show_more {
1545 1545 display: inline-block;
1546 1546 position: relative;
1547 1547 vertical-align: middle;
1548 1548 width: 4px;
1549 1549 height: @basefontsize;
1550 1550
1551 1551 &:after {
1552 1552 content: "\00A0\25BE";
1553 1553 display: inline-block;
1554 1554 width:10px;
1555 1555 line-height: 5px;
1556 1556 font-size: 12px;
1557 1557 cursor: pointer;
1558 1558 }
1559 1559 }
1560 1560
1561 1561 .journal_more .show_more {
1562 1562 display: inline;
1563 1563
1564 1564 &:after {
1565 1565 content: none;
1566 1566 }
1567 1567 }
1568 1568
1569 1569 .open .show_more:after,
1570 1570 .select2-dropdown-open .show_more:after {
1571 1571 .rotate(180deg);
1572 1572 margin-left: 4px;
1573 1573 }
1574 1574
1575 1575
1576 1576 .compare_view_commits .collapse_commit:after {
1577 1577 cursor: pointer;
1578 1578 content: "\00A0\25B4";
1579 1579 margin-left: -3px;
1580 1580 font-size: 17px;
1581 1581 color: @grey4;
1582 1582 }
1583 1583
1584 1584 .diff_links {
1585 1585 margin-left: 8px;
1586 1586 }
1587 1587
1588 1588 div.ancestor {
1589 1589 margin: -30px 0px;
1590 1590 }
1591 1591
1592 1592 .cs_icon_td input[type="checkbox"] {
1593 1593 display: none;
1594 1594 }
1595 1595
1596 1596 .cs_icon_td .expand_file_icon:after {
1597 1597 cursor: pointer;
1598 1598 content: "\00A0\25B6";
1599 1599 font-size: 12px;
1600 1600 color: @grey4;
1601 1601 }
1602 1602
1603 1603 .cs_icon_td .collapse_file_icon:after {
1604 1604 cursor: pointer;
1605 1605 content: "\00A0\25BC";
1606 1606 font-size: 12px;
1607 1607 color: @grey4;
1608 1608 }
1609 1609
1610 1610 /*new binary
1611 1611 NEW_FILENODE = 1
1612 1612 DEL_FILENODE = 2
1613 1613 MOD_FILENODE = 3
1614 1614 RENAMED_FILENODE = 4
1615 1615 COPIED_FILENODE = 5
1616 1616 CHMOD_FILENODE = 6
1617 1617 BIN_FILENODE = 7
1618 1618 */
1619 1619 .cs_files_expand {
1620 1620 font-size: @basefontsize + 5px;
1621 1621 line-height: 1.8em;
1622 1622 float: right;
1623 1623 }
1624 1624
1625 1625 .cs_files_expand span{
1626 1626 color: @rcblue;
1627 1627 cursor: pointer;
1628 1628 }
1629 1629 .cs_files {
1630 1630 clear: both;
1631 1631 padding-bottom: @padding;
1632 1632
1633 1633 .cur_cs {
1634 1634 margin: 10px 2px;
1635 1635 font-weight: bold;
1636 1636 }
1637 1637
1638 1638 .node {
1639 1639 float: left;
1640 1640 }
1641 1641
1642 1642 .changes {
1643 1643 float: right;
1644 1644 color: white;
1645 1645 font-size: @basefontsize - 4px;
1646 1646 margin-top: 4px;
1647 1647 opacity: 0.6;
1648 1648 filter: Alpha(opacity=60); /* IE8 and earlier */
1649 1649
1650 1650 .added {
1651 1651 background-color: @alert1;
1652 1652 float: left;
1653 1653 text-align: center;
1654 1654 }
1655 1655
1656 1656 .deleted {
1657 1657 background-color: @alert2;
1658 1658 float: left;
1659 1659 text-align: center;
1660 1660 }
1661 1661
1662 1662 .bin {
1663 1663 background-color: @alert1;
1664 1664 text-align: center;
1665 1665 }
1666 1666
1667 1667 /*new binary*/
1668 1668 .bin.bin1 {
1669 1669 background-color: @alert1;
1670 1670 text-align: center;
1671 1671 }
1672 1672
1673 1673 /*deleted binary*/
1674 1674 .bin.bin2 {
1675 1675 background-color: @alert2;
1676 1676 text-align: center;
1677 1677 }
1678 1678
1679 1679 /*mod binary*/
1680 1680 .bin.bin3 {
1681 1681 background-color: @grey2;
1682 1682 text-align: center;
1683 1683 }
1684 1684
1685 1685 /*rename file*/
1686 1686 .bin.bin4 {
1687 1687 background-color: @alert4;
1688 1688 text-align: center;
1689 1689 }
1690 1690
1691 1691 /*copied file*/
1692 1692 .bin.bin5 {
1693 1693 background-color: @alert4;
1694 1694 text-align: center;
1695 1695 }
1696 1696
1697 1697 /*chmod file*/
1698 1698 .bin.bin6 {
1699 1699 background-color: @grey2;
1700 1700 text-align: center;
1701 1701 }
1702 1702 }
1703 1703 }
1704 1704
1705 1705 .cs_files .cs_added, .cs_files .cs_A,
1706 1706 .cs_files .cs_added, .cs_files .cs_M,
1707 1707 .cs_files .cs_added, .cs_files .cs_D {
1708 1708 height: 16px;
1709 1709 padding-right: 10px;
1710 1710 margin-top: 7px;
1711 1711 text-align: left;
1712 1712 }
1713 1713
1714 1714 .cs_icon_td {
1715 1715 min-width: 16px;
1716 1716 width: 16px;
1717 1717 }
1718 1718
1719 1719 .pull-request-merge {
1720 1720 border: 1px solid @grey5;
1721 1721 padding: 10px 0px 20px;
1722 1722 margin-top: 10px;
1723 1723 margin-bottom: 20px;
1724 1724 }
1725 1725
1726 1726 .pull-request-merge ul {
1727 1727 padding: 0px 0px;
1728 1728 }
1729 1729
1730 1730 .pull-request-merge li:before{
1731 1731 content:none;
1732 1732 }
1733 1733
1734 1734 .pull-request-merge .pull-request-wrap {
1735 1735 height: auto;
1736 1736 padding: 0px 0px;
1737 1737 text-align: right;
1738 1738 }
1739 1739
1740 1740 .pull-request-merge span {
1741 1741 margin-right: 5px;
1742 1742 }
1743 1743
1744 1744 .pull-request-merge-actions {
1745 1745 height: 30px;
1746 1746 padding: 0px 0px;
1747 1747 }
1748 1748
1749 1749 .merge-status {
1750 1750 margin-right: 5px;
1751 1751 }
1752 1752
1753 1753 .merge-message {
1754 1754 font-size: 1.2em
1755 1755 }
1756 1756
1757 1757 .merge-message.success i,
1758 1758 .merge-icon.success i {
1759 1759 color:@alert1;
1760 1760 }
1761 1761
1762 1762 .merge-message.warning i,
1763 1763 .merge-icon.warning i {
1764 1764 color: @alert3;
1765 1765 }
1766 1766
1767 1767 .merge-message.error i,
1768 1768 .merge-icon.error i {
1769 1769 color:@alert2;
1770 1770 }
1771 1771
1772 1772 .pr-versions {
1773 1773 font-size: 1.1em;
1774 1774
1775 1775 table {
1776 1776 padding: 0px 5px;
1777 1777 }
1778 1778
1779 1779 td {
1780 1780 line-height: 15px;
1781 1781 }
1782 1782
1783 1783 .flag_status {
1784 1784 margin: 0;
1785 1785 }
1786 1786
1787 1787 .compare-radio-button {
1788 1788 position: relative;
1789 1789 top: -3px;
1790 1790 }
1791 1791 }
1792 1792
1793 1793
1794 1794 #close_pull_request {
1795 1795 margin-right: 0px;
1796 1796 }
1797 1797
1798 1798 .empty_data {
1799 1799 color: @grey4;
1800 1800 }
1801 1801
1802 1802 #changeset_compare_view_content {
1803 1803 margin-bottom: @space;
1804 1804 clear: both;
1805 1805 width: 100%;
1806 1806 box-sizing: border-box;
1807 1807 .border-radius(@border-radius);
1808 1808
1809 1809 .help-block {
1810 1810 margin: @padding 0;
1811 1811 color: @text-color;
1812 1812 &.pre-formatting {
1813 1813 white-space: pre;
1814 1814 }
1815 1815 }
1816 1816
1817 1817 .empty_data {
1818 1818 margin: @padding 0;
1819 1819 }
1820 1820
1821 1821 .alert {
1822 1822 margin-bottom: @space;
1823 1823 }
1824 1824 }
1825 1825
1826 1826 .table_disp {
1827 1827 .status {
1828 1828 width: auto;
1829 1829
1830 1830 .flag_status {
1831 1831 float: left;
1832 1832 }
1833 1833 }
1834 1834 }
1835 1835
1836
1837 .creation_in_progress {
1838 color: @grey4
1839 }
1840
1836 1841 .status_box_menu {
1837 1842 margin: 0;
1838 1843 }
1839 1844
1840 1845 .notification-table{
1841 1846 margin-bottom: @space;
1842 1847 display: table;
1843 1848 width: 100%;
1844 1849
1845 1850 .container{
1846 1851 display: table-row;
1847 1852
1848 1853 .notification-header{
1849 1854 border-bottom: @border-thickness solid @border-default-color;
1850 1855 }
1851 1856
1852 1857 .notification-subject{
1853 1858 display: table-cell;
1854 1859 }
1855 1860 }
1856 1861 }
1857 1862
1858 1863 // Notifications
1859 1864 .notification-header{
1860 1865 display: table;
1861 1866 width: 100%;
1862 1867 padding: floor(@basefontsize/2) 0;
1863 1868 line-height: 1em;
1864 1869
1865 1870 .desc, .delete-notifications, .read-notifications{
1866 1871 display: table-cell;
1867 1872 text-align: left;
1868 1873 }
1869 1874
1870 1875 .desc{
1871 1876 width: 1163px;
1872 1877 }
1873 1878
1874 1879 .delete-notifications, .read-notifications{
1875 1880 width: 35px;
1876 1881 min-width: 35px; //fixes when only one button is displayed
1877 1882 }
1878 1883 }
1879 1884
1880 1885 .notification-body {
1881 1886 .markdown-block,
1882 1887 .rst-block {
1883 1888 padding: @padding 0;
1884 1889 }
1885 1890
1886 1891 .notification-subject {
1887 1892 padding: @textmargin 0;
1888 1893 border-bottom: @border-thickness solid @border-default-color;
1889 1894 }
1890 1895 }
1891 1896
1892 1897
1893 1898 .notifications_buttons{
1894 1899 float: right;
1895 1900 }
1896 1901
1897 1902 #notification-status{
1898 1903 display: inline;
1899 1904 }
1900 1905
1901 1906 // Repositories
1902 1907
1903 1908 #summary.fields{
1904 1909 display: table;
1905 1910
1906 1911 .field{
1907 1912 display: table-row;
1908 1913
1909 1914 .label-summary{
1910 1915 display: table-cell;
1911 1916 min-width: @label-summary-minwidth;
1912 1917 padding-top: @padding/2;
1913 1918 padding-bottom: @padding/2;
1914 1919 padding-right: @padding/2;
1915 1920 }
1916 1921
1917 1922 .input{
1918 1923 display: table-cell;
1919 1924 padding: @padding/2;
1920 1925
1921 1926 input{
1922 1927 min-width: 29em;
1923 1928 padding: @padding/4;
1924 1929 }
1925 1930 }
1926 1931 .statistics, .downloads{
1927 1932 .disabled{
1928 1933 color: @grey4;
1929 1934 }
1930 1935 }
1931 1936 }
1932 1937 }
1933 1938
1934 1939 #summary{
1935 1940 width: 70%;
1936 1941 }
1937 1942
1938 1943
1939 1944 // Journal
1940 1945 .journal.title {
1941 1946 h5 {
1942 1947 float: left;
1943 1948 margin: 0;
1944 1949 width: 70%;
1945 1950 }
1946 1951
1947 1952 ul {
1948 1953 float: right;
1949 1954 display: inline-block;
1950 1955 margin: 0;
1951 1956 width: 30%;
1952 1957 text-align: right;
1953 1958
1954 1959 li {
1955 1960 display: inline;
1956 1961 font-size: @journal-fontsize;
1957 1962 line-height: 1em;
1958 1963
1959 1964 &:before { content: none; }
1960 1965 }
1961 1966 }
1962 1967 }
1963 1968
1964 1969 .filterexample {
1965 1970 position: absolute;
1966 1971 top: 95px;
1967 1972 left: @contentpadding;
1968 1973 color: @rcblue;
1969 1974 font-size: 11px;
1970 1975 font-family: @text-regular;
1971 1976 cursor: help;
1972 1977
1973 1978 &:hover {
1974 1979 color: @rcdarkblue;
1975 1980 }
1976 1981
1977 1982 @media (max-width:768px) {
1978 1983 position: relative;
1979 1984 top: auto;
1980 1985 left: auto;
1981 1986 display: block;
1982 1987 }
1983 1988 }
1984 1989
1985 1990
1986 1991 #journal{
1987 1992 margin-bottom: @space;
1988 1993
1989 1994 .journal_day{
1990 1995 margin-bottom: @textmargin/2;
1991 1996 padding-bottom: @textmargin/2;
1992 1997 font-size: @journal-fontsize;
1993 1998 border-bottom: @border-thickness solid @border-default-color;
1994 1999 }
1995 2000
1996 2001 .journal_container{
1997 2002 margin-bottom: @space;
1998 2003
1999 2004 .journal_user{
2000 2005 display: inline-block;
2001 2006 }
2002 2007 .journal_action_container{
2003 2008 display: block;
2004 2009 margin-top: @textmargin;
2005 2010
2006 2011 div{
2007 2012 display: inline;
2008 2013 }
2009 2014
2010 2015 div.journal_action_params{
2011 2016 display: block;
2012 2017 }
2013 2018
2014 2019 div.journal_repo:after{
2015 2020 content: "\A";
2016 2021 white-space: pre;
2017 2022 }
2018 2023
2019 2024 div.date{
2020 2025 display: block;
2021 2026 margin-bottom: @textmargin;
2022 2027 }
2023 2028 }
2024 2029 }
2025 2030 }
2026 2031
2027 2032 // Files
2028 2033 .edit-file-title {
2029 2034 border-bottom: @border-thickness solid @border-default-color;
2030 2035
2031 2036 .breadcrumbs {
2032 2037 margin-bottom: 0;
2033 2038 }
2034 2039 }
2035 2040
2036 2041 .edit-file-fieldset {
2037 2042 margin-top: @sidebarpadding;
2038 2043
2039 2044 .fieldset {
2040 2045 .left-label {
2041 2046 width: 13%;
2042 2047 }
2043 2048 .right-content {
2044 2049 width: 87%;
2045 2050 max-width: 100%;
2046 2051 }
2047 2052 .filename-label {
2048 2053 margin-top: 13px;
2049 2054 }
2050 2055 .commit-message-label {
2051 2056 margin-top: 4px;
2052 2057 }
2053 2058 .file-upload-input {
2054 2059 input {
2055 2060 display: none;
2056 2061 }
2057 2062 margin-top: 10px;
2058 2063 }
2059 2064 .file-upload-label {
2060 2065 margin-top: 10px;
2061 2066 }
2062 2067 p {
2063 2068 margin-top: 5px;
2064 2069 }
2065 2070
2066 2071 }
2067 2072 .custom-path-link {
2068 2073 margin-left: 5px;
2069 2074 }
2070 2075 #commit {
2071 2076 resize: vertical;
2072 2077 }
2073 2078 }
2074 2079
2075 2080 .delete-file-preview {
2076 2081 max-height: 250px;
2077 2082 }
2078 2083
2079 2084 .new-file,
2080 2085 #filter_activate,
2081 2086 #filter_deactivate {
2082 2087 float: left;
2083 2088 margin: 0 0 0 15px;
2084 2089 }
2085 2090
2086 2091 h3.files_location{
2087 2092 line-height: 2.4em;
2088 2093 }
2089 2094
2090 2095 .browser-nav {
2091 2096 display: table;
2092 2097 margin-bottom: @space;
2093 2098
2094 2099
2095 2100 .info_box {
2096 2101 display: inline-table;
2097 2102 height: 2.5em;
2098 2103
2099 2104 .browser-cur-rev, .info_box_elem {
2100 2105 display: table-cell;
2101 2106 vertical-align: middle;
2102 2107 }
2103 2108
2104 2109 .info_box_elem {
2105 2110 border-top: @border-thickness solid @rcblue;
2106 2111 border-bottom: @border-thickness solid @rcblue;
2107 2112
2108 2113 #at_rev, a {
2109 2114 padding: 0.6em 0.9em;
2110 2115 margin: 0;
2111 2116 .box-shadow(none);
2112 2117 border: 0;
2113 2118 height: 12px;
2114 2119 }
2115 2120
2116 2121 input#at_rev {
2117 2122 max-width: 50px;
2118 2123 text-align: right;
2119 2124 }
2120 2125
2121 2126 &.previous {
2122 2127 border: @border-thickness solid @rcblue;
2123 2128 .disabled {
2124 2129 color: @grey4;
2125 2130 cursor: not-allowed;
2126 2131 }
2127 2132 }
2128 2133
2129 2134 &.next {
2130 2135 border: @border-thickness solid @rcblue;
2131 2136 .disabled {
2132 2137 color: @grey4;
2133 2138 cursor: not-allowed;
2134 2139 }
2135 2140 }
2136 2141 }
2137 2142
2138 2143 .browser-cur-rev {
2139 2144
2140 2145 span{
2141 2146 margin: 0;
2142 2147 color: @rcblue;
2143 2148 height: 12px;
2144 2149 display: inline-block;
2145 2150 padding: 0.7em 1em ;
2146 2151 border: @border-thickness solid @rcblue;
2147 2152 margin-right: @padding;
2148 2153 }
2149 2154 }
2150 2155 }
2151 2156
2152 2157 .search_activate {
2153 2158 display: table-cell;
2154 2159 vertical-align: middle;
2155 2160
2156 2161 input, label{
2157 2162 margin: 0;
2158 2163 padding: 0;
2159 2164 }
2160 2165
2161 2166 input{
2162 2167 margin-left: @textmargin;
2163 2168 }
2164 2169
2165 2170 }
2166 2171 }
2167 2172
2168 2173 .browser-cur-rev{
2169 2174 margin-bottom: @textmargin;
2170 2175 }
2171 2176
2172 2177 #node_filter_box_loading{
2173 2178 .info_text;
2174 2179 }
2175 2180
2176 2181 .browser-search {
2177 2182 margin: -25px 0px 5px 0px;
2178 2183 }
2179 2184
2180 2185 .node-filter {
2181 2186 font-size: @repo-title-fontsize;
2182 2187 padding: 4px 0px 0px 0px;
2183 2188
2184 2189 .node-filter-path {
2185 2190 float: left;
2186 2191 color: @grey4;
2187 2192 }
2188 2193 .node-filter-input {
2189 2194 float: left;
2190 2195 margin: -2px 0px 0px 2px;
2191 2196 input {
2192 2197 padding: 2px;
2193 2198 border: none;
2194 2199 font-size: @repo-title-fontsize;
2195 2200 }
2196 2201 }
2197 2202 }
2198 2203
2199 2204
2200 2205 .browser-result{
2201 2206 td a{
2202 2207 margin-left: 0.5em;
2203 2208 display: inline-block;
2204 2209
2205 2210 em{
2206 2211 font-family: @text-bold;
2207 2212 }
2208 2213 }
2209 2214 }
2210 2215
2211 2216 .browser-highlight{
2212 2217 background-color: @grey5-alpha;
2213 2218 }
2214 2219
2215 2220
2216 2221 // Search
2217 2222
2218 2223 .search-form{
2219 2224 #q {
2220 2225 width: @search-form-width;
2221 2226 }
2222 2227 .fields{
2223 2228 margin: 0 0 @space;
2224 2229 }
2225 2230
2226 2231 label{
2227 2232 display: inline-block;
2228 2233 margin-right: @textmargin;
2229 2234 padding-top: 0.25em;
2230 2235 }
2231 2236
2232 2237
2233 2238 .results{
2234 2239 clear: both;
2235 2240 margin: 0 0 @padding;
2236 2241 }
2237 2242 }
2238 2243
2239 2244 div.search-feedback-items {
2240 2245 display: inline-block;
2241 2246 padding:0px 0px 0px 96px;
2242 2247 }
2243 2248
2244 2249 div.search-code-body {
2245 2250 background-color: #ffffff; padding: 5px 0 5px 10px;
2246 2251 pre {
2247 2252 .match { background-color: #faffa6;}
2248 2253 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2249 2254 }
2250 2255 }
2251 2256
2252 2257 .expand_commit.search {
2253 2258 .show_more.open {
2254 2259 height: auto;
2255 2260 max-height: none;
2256 2261 }
2257 2262 }
2258 2263
2259 2264 .search-results {
2260 2265
2261 2266 h2 {
2262 2267 margin-bottom: 0;
2263 2268 }
2264 2269 .codeblock {
2265 2270 border: none;
2266 2271 background: transparent;
2267 2272 }
2268 2273
2269 2274 .codeblock-header {
2270 2275 border: none;
2271 2276 background: transparent;
2272 2277 }
2273 2278
2274 2279 .code-body {
2275 2280 border: @border-thickness solid @border-default-color;
2276 2281 .border-radius(@border-radius);
2277 2282 }
2278 2283
2279 2284 .td-commit {
2280 2285 &:extend(pre);
2281 2286 border-bottom: @border-thickness solid @border-default-color;
2282 2287 }
2283 2288
2284 2289 .message {
2285 2290 height: auto;
2286 2291 max-width: 350px;
2287 2292 white-space: normal;
2288 2293 text-overflow: initial;
2289 2294 overflow: visible;
2290 2295
2291 2296 .match { background-color: #faffa6;}
2292 2297 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2293 2298 }
2294 2299
2295 2300 }
2296 2301
2297 2302 table.rctable td.td-search-results div {
2298 2303 max-width: 100%;
2299 2304 }
2300 2305
2301 2306 #tip-box, .tip-box{
2302 2307 padding: @menupadding/2;
2303 2308 display: block;
2304 2309 border: @border-thickness solid @border-highlight-color;
2305 2310 .border-radius(@border-radius);
2306 2311 background-color: white;
2307 2312 z-index: 99;
2308 2313 white-space: pre-wrap;
2309 2314 }
2310 2315
2311 2316 #linktt {
2312 2317 width: 79px;
2313 2318 }
2314 2319
2315 2320 #help_kb .modal-content{
2316 2321 max-width: 750px;
2317 2322 margin: 10% auto;
2318 2323
2319 2324 table{
2320 2325 td,th{
2321 2326 border-bottom: none;
2322 2327 line-height: 2.5em;
2323 2328 }
2324 2329 th{
2325 2330 padding-bottom: @textmargin/2;
2326 2331 }
2327 2332 td.keys{
2328 2333 text-align: center;
2329 2334 }
2330 2335 }
2331 2336
2332 2337 .block-left{
2333 2338 width: 45%;
2334 2339 margin-right: 5%;
2335 2340 }
2336 2341 .modal-footer{
2337 2342 clear: both;
2338 2343 }
2339 2344 .key.tag{
2340 2345 padding: 0.5em;
2341 2346 background-color: @rcblue;
2342 2347 color: white;
2343 2348 border-color: @rcblue;
2344 2349 .box-shadow(none);
2345 2350 }
2346 2351 }
2347 2352
2348 2353
2349 2354
2350 2355 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2351 2356
2352 2357 @import 'statistics-graph';
2353 2358 @import 'tables';
2354 2359 @import 'forms';
2355 2360 @import 'diff';
2356 2361 @import 'summary';
2357 2362 @import 'navigation';
2358 2363
2359 2364 //--- SHOW/HIDE SECTIONS --//
2360 2365
2361 2366 .btn-collapse {
2362 2367 float: right;
2363 2368 text-align: right;
2364 2369 font-family: @text-light;
2365 2370 font-size: @basefontsize;
2366 2371 cursor: pointer;
2367 2372 border: none;
2368 2373 color: @rcblue;
2369 2374 }
2370 2375
2371 2376 table.rctable,
2372 2377 table.dataTable {
2373 2378 .btn-collapse {
2374 2379 float: right;
2375 2380 text-align: right;
2376 2381 }
2377 2382 }
2378 2383
2379 2384
2380 2385 // TODO: johbo: Fix for IE10, this avoids that we see a border
2381 2386 // and padding around checkboxes and radio boxes. Move to the right place,
2382 2387 // or better: Remove this once we did the form refactoring.
2383 2388 input[type=checkbox],
2384 2389 input[type=radio] {
2385 2390 padding: 0;
2386 2391 border: none;
2387 2392 }
2388 2393
2389 2394 .toggle-ajax-spinner{
2390 2395 height: 16px;
2391 2396 width: 16px;
2392 2397 }
@@ -1,220 +1,222 b''
1 1
2 2 /******************************************************************************
3 3 * *
4 4 * DO NOT CHANGE THIS FILE MANUALLY *
5 5 * *
6 6 * *
7 7 * This file is automatically generated when the app starts up with *
8 8 * generate_js_files = true *
9 9 * *
10 10 * To add a route here pass jsroute=True to the route definition in the app *
11 11 * *
12 12 ******************************************************************************/
13 13 function registerRCRoutes() {
14 14 // routes registration
15 15 pyroutes.register('new_repo', '/_admin/create_repository', []);
16 16 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
17 17 pyroutes.register('favicon', '/favicon.ico', []);
18 18 pyroutes.register('robots', '/robots.txt', []);
19 19 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
20 20 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
21 21 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
22 22 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
23 23 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
24 24 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
25 25 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/settings/integrations', ['repo_group_name']);
26 26 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
27 27 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
28 28 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
29 29 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
30 30 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
31 31 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
32 32 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
33 33 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
34 34 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
35 35 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
36 36 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
37 37 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
38 38 pyroutes.register('admin_home', '/_admin', []);
39 39 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
40 40 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
41 41 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
42 42 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
43 43 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
44 44 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
45 45 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
46 46 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
47 47 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
48 48 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
49 49 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
50 50 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
51 51 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
52 52 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
53 53 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
54 54 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
55 55 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
56 56 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
57 57 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
58 58 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
59 59 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
60 60 pyroutes.register('users', '/_admin/users', []);
61 61 pyroutes.register('users_data', '/_admin/users_data', []);
62 62 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
63 63 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
64 64 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
65 65 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
66 66 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
67 67 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
68 68 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
69 69 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
70 70 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
71 71 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
72 72 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
73 73 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
74 74 pyroutes.register('user_groups', '/_admin/user_groups', []);
75 75 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
76 76 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
77 77 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
78 78 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
79 79 pyroutes.register('channelstream_proxy', '/_channelstream', []);
80 80 pyroutes.register('login', '/_admin/login', []);
81 81 pyroutes.register('logout', '/_admin/logout', []);
82 82 pyroutes.register('register', '/_admin/register', []);
83 83 pyroutes.register('reset_password', '/_admin/password_reset', []);
84 84 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
85 85 pyroutes.register('home', '/', []);
86 86 pyroutes.register('user_autocomplete_data', '/_users', []);
87 87 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
88 88 pyroutes.register('repo_list_data', '/_repos', []);
89 89 pyroutes.register('goto_switcher_data', '/_goto_data', []);
90 90 pyroutes.register('journal', '/_admin/journal', []);
91 91 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
92 92 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
93 93 pyroutes.register('journal_public', '/_admin/public_journal', []);
94 94 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
95 95 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
96 96 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
97 97 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
98 98 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
99 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
100 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
99 101 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
100 102 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
101 103 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
102 104 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
103 105 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
104 106 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
105 107 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
106 108 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
107 109 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
108 110 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
109 111 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
110 112 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
111 113 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
112 114 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
113 115 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
114 116 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
115 117 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
116 118 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
117 119 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
118 120 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
119 121 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
120 122 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
121 123 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
122 124 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
123 125 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
124 126 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
125 127 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
126 128 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
127 129 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
128 130 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
129 131 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
130 132 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
131 133 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
132 134 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
133 135 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
134 136 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
135 137 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
136 138 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
137 139 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
138 140 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
139 141 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
140 142 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
141 143 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
142 144 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
143 145 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
144 146 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
145 147 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
146 148 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
147 149 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
148 150 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
149 151 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
150 152 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
151 153 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
152 154 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
153 155 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
154 156 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
155 157 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
156 158 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
157 159 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
158 160 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
159 161 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
160 162 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
161 163 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
162 164 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
163 165 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
164 166 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
165 167 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
166 168 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
167 169 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
168 170 pyroutes.register('repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
169 171 pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
170 172 pyroutes.register('strip', '/%(repo_name)s/settings/strip', ['repo_name']);
171 173 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
172 174 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
173 175 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
174 176 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
175 177 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
176 178 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
177 179 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
178 180 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
179 181 pyroutes.register('search', '/_admin/search', []);
180 182 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
181 183 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
182 184 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
183 185 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
184 186 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
185 187 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
186 188 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
187 189 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
188 190 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
189 191 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
190 192 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
191 193 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
192 194 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
193 195 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
194 196 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
195 197 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
196 198 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
197 199 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
198 200 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
199 201 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
200 202 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
201 203 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
202 204 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
203 205 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
204 206 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
205 207 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
206 208 pyroutes.register('gists_show', '/_admin/gists', []);
207 209 pyroutes.register('gists_new', '/_admin/gists/new', []);
208 210 pyroutes.register('gists_create', '/_admin/gists/create', []);
209 211 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
210 212 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
211 213 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
212 214 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
213 215 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
214 216 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
215 217 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
216 218 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
217 219 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
218 220 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
219 221 pyroutes.register('apiv2', '/_admin/api', []);
220 222 }
@@ -1,70 +1,77 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 ${_('%s Creating repository') % c.repo_name}
5 ${_('{} Creating repository').format(c.repo_name)}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 ${_('Creating repository')} ${c.repo}
12 ${_('Creating repository')} ${c.repo_name}
13 13 </%def>
14 14
15 15 <%def name="menu_bar_nav()">
16 16 ${self.menu_items(active='repositories')}
17 17 </%def>
18 18 <%def name="main()">
19 19 <div class="box">
20 20 <!-- box / title -->
21 21 <div class="title">
22 22 ${self.breadcrumbs()}
23 23 </div>
24 24
25 25 <div id="progress-message">
26 26 ${_('Repository "%(repo_name)s" is being created, you will be redirected when this process is finished.' % {'repo_name':c.repo_name})}
27 27 </div>
28 28
29 29 <div id="progress">
30 30 <div class="progress progress-striped active">
31 31 <div class="progress-bar progress-bar" role="progressbar"
32 32 aria-valuenow="100" aria-valuemin="0" aria-valuemax="100">
33 33 </div>
34 34 </div>
35 35 </div>
36 36 </div>
37 37
38 38 <script>
39 39 (function worker() {
40 40 var skipCheck = false;
41 var url = "${h.url('repo_check_home', repo_name=c.repo_name, repo=c.repo, task_id=c.task_id)}";
41 var url = "${h.route_path('repo_creating_check', repo_name=c.repo_name, _query=dict(task_id=c.task_id))}";
42 42 $.ajax({
43 43 url: url,
44 44 complete: function(resp) {
45 45 if (resp.status == 200) {
46 46 var jsonResponse = resp.responseJSON;
47 47
48 48 if (jsonResponse === undefined) {
49 49 setTimeout(function () {
50 50 // we might have a backend problem, try dashboard again
51 window.location = "${h.route_path('repo_summary', repo_name = c.repo)}";
51 window.location = "${h.route_path('repo_summary', repo_name = c.repo_name)}";
52 52 }, 3000);
53 53 } else {
54 54 if (skipCheck || jsonResponse.result === true) {
55 55 // success, means go to dashboard
56 window.location = "${h.route_path('repo_summary', repo_name = c.repo)}";
56 window.location = "${h.route_path('repo_summary', repo_name = c.repo_name)}";
57 57 } else {
58 58 // Schedule the next request when the current one's complete
59 59 setTimeout(worker, 1000);
60 60 }
61 61 }
62 62 }
63 63 else {
64 window.location = "${h.route_path('home')}";
64 var payload = {
65 message: {
66 message: _gettext('Fetching repository state failed. Error code: {0} {1}. Try refreshing this page.').format(resp.status, resp.statusText),
67 level: 'error',
68 force: true
69 }
70 };
71 $.Topic('/notifications').publish(payload);
65 72 }
66 73 }
67 74 });
68 75 })();
69 76 </script>
70 </%def> No newline at end of file
77 </%def>
@@ -1,317 +1,319 b''
1 1 ## DATA TABLE RE USABLE ELEMENTS
2 2 ## usage:
3 3 ## <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
4 4 <%namespace name="base" file="/base/base.mako"/>
5 5
6 6 ## REPOSITORY RENDERERS
7 7 <%def name="quick_menu(repo_name)">
8 8 <i class="icon-more"></i>
9 9 <div class="menu_items_container hidden">
10 10 <ul class="menu_items">
11 11 <li>
12 12 <a title="${_('Summary')}" href="${h.route_path('repo_summary',repo_name=repo_name)}">
13 13 <span>${_('Summary')}</span>
14 14 </a>
15 15 </li>
16 16 <li>
17 17 <a title="${_('Changelog')}" href="${h.route_path('repo_changelog',repo_name=repo_name)}">
18 18 <span>${_('Changelog')}</span>
19 19 </a>
20 20 </li>
21 21 <li>
22 22 <a title="${_('Files')}" href="${h.route_path('repo_files:default_commit',repo_name=repo_name)}">
23 23 <span>${_('Files')}</span>
24 24 </a>
25 25 </li>
26 26 <li>
27 27 <a title="${_('Fork')}" href="${h.url('repo_fork_home',repo_name=repo_name)}">
28 28 <span>${_('Fork')}</span>
29 29 </a>
30 30 </li>
31 31 </ul>
32 32 </div>
33 33 </%def>
34 34
35 35 <%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False,admin=False)">
36 36 <%
37 37 def get_name(name,short_name=short_name):
38 38 if short_name:
39 39 return name.split('/')[-1]
40 40 else:
41 41 return name
42 42 %>
43 43 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
44 44 ##NAME
45 45 <a href="${h.route_path('edit_repo',repo_name=name) if admin else h.route_path('repo_summary',repo_name=name)}">
46 46
47 47 ##TYPE OF REPO
48 48 %if h.is_hg(rtype):
49 49 <span title="${_('Mercurial repository')}"><i class="icon-hg"></i></span>
50 50 %elif h.is_git(rtype):
51 51 <span title="${_('Git repository')}"><i class="icon-git"></i></span>
52 52 %elif h.is_svn(rtype):
53 53 <span title="${_('Subversion repository')}"><i class="icon-svn"></i></span>
54 54 %endif
55 55
56 56 ##PRIVATE/PUBLIC
57 57 %if private and c.visual.show_private_icon:
58 58 <i class="icon-lock" title="${_('Private repository')}"></i>
59 59 %elif not private and c.visual.show_public_icon:
60 60 <i class="icon-unlock-alt" title="${_('Public repository')}"></i>
61 61 %else:
62 62 <span></span>
63 63 %endif
64 64 ${get_name(name)}
65 65 </a>
66 66 %if fork_of:
67 67 <a href="${h.route_path('repo_summary',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
68 68 %endif
69 69 %if rstate == 'repo_state_pending':
70 <i class="icon-cogs" title="${_('Repository creating in progress...')}"></i>
70 <span class="creation_in_progress tooltip" title="${_('This repository is being created in a background task')}">
71 (${_('creating...')})
72 </span>
71 73 %endif
72 74 </div>
73 75 </%def>
74 76
75 77 <%def name="repo_desc(description)">
76 78 <div class="truncate-wrap">${description}</div>
77 79 </%def>
78 80
79 81 <%def name="last_change(last_change)">
80 82 ${h.age_component(last_change)}
81 83 </%def>
82 84
83 85 <%def name="revision(name,rev,tip,author,last_msg)">
84 86 <div>
85 87 %if rev >= 0:
86 88 <code><a title="${h.tooltip('%s:\n\n%s' % (author,last_msg))}" class="tooltip" href="${h.route_path('repo_commit',repo_name=name,commit_id=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></code>
87 89 %else:
88 90 ${_('No commits yet')}
89 91 %endif
90 92 </div>
91 93 </%def>
92 94
93 95 <%def name="rss(name)">
94 96 %if c.rhodecode_user.username != h.DEFAULT_USER:
95 97 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
96 98 %else:
97 99 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
98 100 %endif
99 101 </%def>
100 102
101 103 <%def name="atom(name)">
102 104 %if c.rhodecode_user.username != h.DEFAULT_USER:
103 105 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
104 106 %else:
105 107 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
106 108 %endif
107 109 </%def>
108 110
109 111 <%def name="user_gravatar(email, size=16)">
110 112 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
111 113 ${base.gravatar(email, 16)}
112 114 </div>
113 115 </%def>
114 116
115 117 <%def name="repo_actions(repo_name, super_user=True)">
116 118 <div>
117 119 <div class="grid_edit">
118 120 <a href="${h.route_path('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
119 121 <i class="icon-pencil"></i>Edit</a>
120 122 </div>
121 123 <div class="grid_delete">
122 124 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=repo_name), method='POST', request=request)}
123 125 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
124 126 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
125 127 ${h.end_form()}
126 128 </div>
127 129 </div>
128 130 </%def>
129 131
130 132 <%def name="repo_state(repo_state)">
131 133 <div>
132 134 %if repo_state == 'repo_state_pending':
133 135 <div class="tag tag4">${_('Creating')}</div>
134 136 %elif repo_state == 'repo_state_created':
135 137 <div class="tag tag1">${_('Created')}</div>
136 138 %else:
137 139 <div class="tag alert2" title="${h.tooltip(repo_state)}">invalid</div>
138 140 %endif
139 141 </div>
140 142 </%def>
141 143
142 144
143 145 ## REPO GROUP RENDERERS
144 146 <%def name="quick_repo_group_menu(repo_group_name)">
145 147 <i class="icon-more"></i>
146 148 <div class="menu_items_container hidden">
147 149 <ul class="menu_items">
148 150 <li>
149 151 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
150 152 <span class="icon">
151 153 <i class="icon-file-text"></i>
152 154 </span>
153 155 <span>${_('Summary')}</span>
154 156 </a>
155 157 </li>
156 158
157 159 </ul>
158 160 </div>
159 161 </%def>
160 162
161 163 <%def name="repo_group_name(repo_group_name, children_groups=None)">
162 164 <div>
163 165 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
164 166 <i class="icon-folder-close" title="${_('Repository group')}"></i>
165 167 %if children_groups:
166 168 ${h.literal(' &raquo; '.join(children_groups))}
167 169 %else:
168 170 ${repo_group_name}
169 171 %endif
170 172 </a>
171 173 </div>
172 174 </%def>
173 175
174 176 <%def name="repo_group_desc(description)">
175 177 <div class="truncate-wrap">${description}</div>
176 178 </%def>
177 179
178 180 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
179 181 <div class="grid_edit">
180 182 <a href="${h.url('edit_repo_group',group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
181 183 </div>
182 184 <div class="grid_delete">
183 185 ${h.secure_form(h.url('delete_repo_group', group_name=repo_group_name),method='delete')}
184 186 ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger",
185 187 onclick="return confirm('"+_ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
186 188 ${h.end_form()}
187 189 </div>
188 190 </%def>
189 191
190 192
191 193 <%def name="user_actions(user_id, username)">
192 194 <div class="grid_edit">
193 195 <a href="${h.url('edit_user',user_id=user_id)}" title="${_('Edit')}">
194 196 <i class="icon-pencil"></i>Edit</a>
195 197 </div>
196 198 <div class="grid_delete">
197 199 ${h.secure_form(h.url('delete_user', user_id=user_id),method='delete')}
198 200 ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger",
199 201 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
200 202 ${h.end_form()}
201 203 </div>
202 204 </%def>
203 205
204 206 <%def name="user_group_actions(user_group_id, user_group_name)">
205 207 <div class="grid_edit">
206 208 <a href="${h.url('edit_users_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
207 209 </div>
208 210 <div class="grid_delete">
209 211 ${h.secure_form(h.url('delete_users_group', user_group_id=user_group_id),method='delete')}
210 212 ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger",
211 213 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
212 214 ${h.end_form()}
213 215 </div>
214 216 </%def>
215 217
216 218
217 219 <%def name="user_name(user_id, username)">
218 220 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.url('edit_user', user_id=user_id))}
219 221 </%def>
220 222
221 223 <%def name="user_profile(username)">
222 224 ${base.gravatar_with_user(username, 16)}
223 225 </%def>
224 226
225 227 <%def name="user_group_name(user_group_id, user_group_name)">
226 228 <div>
227 229 <a href="${h.url('edit_users_group', user_group_id=user_group_id)}">
228 230 <i class="icon-group" title="${_('User group')}"></i> ${user_group_name}</a>
229 231 </div>
230 232 </%def>
231 233
232 234
233 235 ## GISTS
234 236
235 237 <%def name="gist_gravatar(full_contact)">
236 238 <div class="gist_gravatar">
237 239 ${base.gravatar(full_contact, 30)}
238 240 </div>
239 241 </%def>
240 242
241 243 <%def name="gist_access_id(gist_access_id, full_contact)">
242 244 <div>
243 245 <b>
244 246 <a href="${h.route_path('gist_show', gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
245 247 </b>
246 248 </div>
247 249 </%def>
248 250
249 251 <%def name="gist_author(full_contact, created_on, expires)">
250 252 ${base.gravatar_with_user(full_contact, 16)}
251 253 </%def>
252 254
253 255
254 256 <%def name="gist_created(created_on)">
255 257 <div class="created">
256 258 ${h.age_component(created_on, time_is_local=True)}
257 259 </div>
258 260 </%def>
259 261
260 262 <%def name="gist_expires(expires)">
261 263 <div class="created">
262 264 %if expires == -1:
263 265 ${_('never')}
264 266 %else:
265 267 ${h.age_component(h.time_to_utcdatetime(expires))}
266 268 %endif
267 269 </div>
268 270 </%def>
269 271
270 272 <%def name="gist_type(gist_type)">
271 273 %if gist_type != 'public':
272 274 <div class="tag">${_('Private')}</div>
273 275 %endif
274 276 </%def>
275 277
276 278 <%def name="gist_description(gist_description)">
277 279 ${gist_description}
278 280 </%def>
279 281
280 282
281 283 ## PULL REQUESTS GRID RENDERERS
282 284
283 285 <%def name="pullrequest_target_repo(repo_name)">
284 286 <div class="truncate">
285 287 ${h.link_to(repo_name,h.route_path('repo_summary',repo_name=repo_name))}
286 288 </div>
287 289 </%def>
288 290 <%def name="pullrequest_status(status)">
289 291 <div class="${'flag_status %s' % status} pull-left"></div>
290 292 </%def>
291 293
292 294 <%def name="pullrequest_title(title, description)">
293 295 ${title} <br/>
294 296 ${h.shorter(description, 40)}
295 297 </%def>
296 298
297 299 <%def name="pullrequest_comments(comments_nr)">
298 300 <i class="icon-comment"></i> ${comments_nr}
299 301 </%def>
300 302
301 303 <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)">
302 304 <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
303 305 % if short:
304 306 #${pull_request_id}
305 307 % else:
306 308 ${_('Pull request #%(pr_number)s') % {'pr_number': pull_request_id,}}
307 309 % endif
308 310 </a>
309 311 </%def>
310 312
311 313 <%def name="pullrequest_updated_on(updated_on)">
312 314 ${h.age_component(h.time_to_utcdatetime(updated_on))}
313 315 </%def>
314 316
315 317 <%def name="pullrequest_author(full_contact)">
316 318 ${base.gravatar_with_user(full_contact, 16)}
317 319 </%def>
@@ -1,229 +1,227 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 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 from rhodecode.lib import helpers as h
24 24 from rhodecode.model.meta import Session
25 25 from rhodecode.model.repo_group import RepoGroupModel
26 26 from rhodecode.tests import (
27 27 url, TestController, assert_session_flash, GIT_REPO, HG_REPO,
28 28 TESTS_TMP_PATH, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
29 29 from rhodecode.tests.fixture import Fixture
30 30
31 31 fixture = Fixture()
32 32
33 33
34 34
35 35 def test_update(app, csrf_token, autologin_user, user_util):
36 36 repo_group = user_util.create_repo_group()
37 37 description = 'description for newly created repo group'
38 38 Session().commit()
39 39 response = app.post(
40 40 url('update_repo_group', group_name=repo_group.group_name),
41 41 fixture._get_group_create_params(
42 42 group_name=repo_group.group_name,
43 43 group_description=description,
44 44 csrf_token=csrf_token,
45 45 _method='PUT')
46 46 )
47 47 # TODO: anderson: johbo: we believe that this update should return
48 48 # a redirect instead of rendering the template.
49 49 assert response.status_code == 200
50 50
51 51
52 52 def test_edit(app, user_util, autologin_user):
53 53 repo_group = user_util.create_repo_group()
54 54 Session().commit()
55 55 response = app.get(
56 56 url('edit_repo_group', group_name=repo_group.group_name))
57 57 assert response.status_code == 200
58 58
59 59
60 60 def test_edit_repo_group_perms(app, user_util, autologin_user):
61 61 repo_group = user_util.create_repo_group()
62 62 Session().commit()
63 63 response = app.get(
64 64 url('edit_repo_group_perms', group_name=repo_group.group_name))
65 65 assert response.status_code == 200
66 66
67 67
68 68 def test_update_fails_when_parent_pointing_to_self(
69 69 app, csrf_token, user_util, autologin_user):
70 70 group = user_util.create_repo_group()
71 71 response = app.post(
72 72 url('update_repo_group', group_name=group.group_name),
73 73 fixture._get_group_create_params(
74 74 group_parent_id=group.group_id,
75 75 csrf_token=csrf_token,
76 76 _method='PUT')
77 77 )
78 78 response.mustcontain(
79 79 '<select class="medium error" id="group_parent_id"'
80 80 ' name="group_parent_id">')
81 81 response.mustcontain('<span class="error-message">Value must be one of:')
82 82
83 83
84 84 class _BaseTest(TestController):
85 85
86 86 REPO_GROUP = None
87 87 NEW_REPO_GROUP = None
88 88 REPO = None
89 89 REPO_TYPE = None
90 90
91 91 def test_index(self):
92 92 self.log_user()
93 93 response = self.app.get(url('repo_groups'))
94 94 response.mustcontain('data: []')
95 95
96 96 def test_index_after_creating_group(self):
97 97 self.log_user()
98 98 fixture.create_repo_group('test_repo_group')
99 99 response = self.app.get(url('repo_groups'))
100 100 response.mustcontain('"name_raw": "test_repo_group"')
101 101 fixture.destroy_repo_group('test_repo_group')
102 102
103 103 def test_new(self):
104 104 self.log_user()
105 105 self.app.get(url('new_repo_group'))
106 106
107 107 def test_new_by_regular_user_no_permission(self):
108 108 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
109 109 self.app.get(url('new_repo_group'), status=403)
110 110
111 111 def test_create(self):
112 112 self.log_user()
113 113 repo_group_name = self.NEW_REPO_GROUP
114 114 repo_group_name_unicode = repo_group_name.decode('utf8')
115 115 description = 'description for newly created repo group'
116 116
117 117 response = self.app.post(
118 118 url('repo_groups'),
119 119 fixture._get_group_create_params(
120 120 group_name=repo_group_name,
121 121 group_description=description,
122 122 csrf_token=self.csrf_token))
123 123
124 124 # run the check page that triggers the flash message
125 # response = self.app.get(url('repo_check_home', repo_name=repo_name))
126 # assert response.json == {u'result': True}
127 125 repo_gr_url = h.route_path(
128 126 'repo_group_home', repo_group_name=repo_group_name)
129 127
130 128 assert_session_flash(
131 129 response,
132 130 'Created repository group <a href="%s">%s</a>' % (
133 131 repo_gr_url, repo_group_name_unicode))
134 132
135 133 # # test if the repo group was created in the database
136 134 new_repo_group = RepoGroupModel()._get_repo_group(
137 135 repo_group_name_unicode)
138 136 assert new_repo_group is not None
139 137
140 138 assert new_repo_group.group_name == repo_group_name_unicode
141 139 assert new_repo_group.group_description == description
142 140
143 141 # test if the repository is visible in the list ?
144 142 response = self.app.get(repo_gr_url)
145 143 response.mustcontain(repo_group_name)
146 144
147 145 # test if the repository group was created on filesystem
148 146 is_on_filesystem = os.path.isdir(
149 147 os.path.join(TESTS_TMP_PATH, repo_group_name))
150 148 if not is_on_filesystem:
151 149 self.fail('no repo group %s in filesystem' % repo_group_name)
152 150
153 151 RepoGroupModel().delete(repo_group_name_unicode)
154 152 Session().commit()
155 153
156 154 def test_create_subgroup(self, user_util):
157 155 self.log_user()
158 156 repo_group_name = self.NEW_REPO_GROUP
159 157 parent_group = user_util.create_repo_group()
160 158 parent_group_name = parent_group.group_name
161 159
162 160 expected_group_name = '{}/{}'.format(
163 161 parent_group_name, repo_group_name)
164 162 expected_group_name_unicode = expected_group_name.decode('utf8')
165 163
166 164 try:
167 165 response = self.app.post(
168 166 url('repo_groups'),
169 167 fixture._get_group_create_params(
170 168 group_name=repo_group_name,
171 169 group_parent_id=parent_group.group_id,
172 170 group_description='Test desciption',
173 171 csrf_token=self.csrf_token))
174 172
175 173 assert_session_flash(
176 174 response,
177 175 u'Created repository group <a href="%s">%s</a>' % (
178 176 h.route_path('repo_group_home', repo_group_name=expected_group_name),
179 177 expected_group_name_unicode))
180 178 finally:
181 179 RepoGroupModel().delete(expected_group_name_unicode)
182 180 Session().commit()
183 181
184 182 def test_user_with_creation_permissions_cannot_create_subgroups(
185 183 self, user_util):
186 184
187 185 user_util.grant_user_permission(
188 186 TEST_USER_REGULAR_LOGIN, 'hg.repogroup.create.true')
189 187 parent_group = user_util.create_repo_group()
190 188 parent_group_id = parent_group.group_id
191 189 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
192 190 self.app.get(
193 191 url('new_repo_group', parent_group=parent_group_id,),
194 192 status=403)
195 193
196 194
197 195 class TestRepoGroupsControllerGIT(_BaseTest):
198 196 REPO_GROUP = None
199 197 NEW_REPO_GROUP = 'git_repo'
200 198 REPO = GIT_REPO
201 199 REPO_TYPE = 'git'
202 200
203 201
204 202 class TestRepoGroupsControllerNonAsciiGit(_BaseTest):
205 203 REPO_GROUP = None
206 204 NEW_REPO_GROUP = 'git_repo_Δ…Δ‡'
207 205 REPO = GIT_REPO
208 206 REPO_TYPE = 'git'
209 207
210 208
211 209 class TestRepoGroupsControllerHG(_BaseTest):
212 210 REPO_GROUP = None
213 211 NEW_REPO_GROUP = 'hg_repo'
214 212 REPO = HG_REPO
215 213 REPO_TYPE = 'hg'
216 214
217 215
218 216 class TestRepoGroupsControllerNumericalHG(_BaseTest):
219 217 REPO_GROUP = None
220 218 NEW_REPO_GROUP = '12345'
221 219 REPO = HG_REPO
222 220 REPO_TYPE = 'hg'
223 221
224 222
225 223 class TestRepoGroupsControllerNonAsciiHG(_BaseTest):
226 224 REPO_GROUP = None
227 225 NEW_REPO_GROUP = 'hg_repo_Δ…Δ‡'
228 226 REPO = HG_REPO
229 227 REPO_TYPE = 'hg'
@@ -1,1117 +1,1131 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 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 urllib
22 22
23 23 import mock
24 24 import pytest
25 25
26 26 from rhodecode.lib import auth
27 27 from rhodecode.lib.utils2 import safe_str, str2bool
28 28 from rhodecode.lib import helpers as h
29 29 from rhodecode.model.db import (
30 30 Repository, RepoGroup, UserRepoToPerm, User, Permission)
31 31 from rhodecode.model.meta import Session
32 32 from rhodecode.model.repo import RepoModel
33 33 from rhodecode.model.repo_group import RepoGroupModel
34 34 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
35 35 from rhodecode.model.user import UserModel
36 36 from rhodecode.tests import (
37 37 login_user_session, url, assert_session_flash, TEST_USER_ADMIN_LOGIN,
38 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, HG_REPO, GIT_REPO,
39 logout_user_session)
38 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, logout_user_session)
40 39 from rhodecode.tests.fixture import Fixture, error_function
41 40 from rhodecode.tests.utils import AssertResponse, repo_on_filesystem
42 41
43 42 fixture = Fixture()
44 43
45 44
45 def route_path(name, params=None, **kwargs):
46 import urllib
47
48 base_url = {
49 'repo_summary': '/{repo_name}',
50 'repo_creating_check': '/{repo_name}/repo_creating_check',
51 }[name].format(**kwargs)
52
53 if params:
54 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
55 return base_url
56
57
46 58 @pytest.mark.usefixtures("app")
47 59 class TestAdminRepos(object):
48 60
49 61 def test_index(self):
50 62 self.app.get(url('repos'))
51 63
52 64 def test_create_page_restricted(self, autologin_user, backend):
53 65 with mock.patch('rhodecode.BACKENDS', {'git': 'git'}):
54 66 response = self.app.get(url('new_repo'), status=200)
55 67 assert_response = AssertResponse(response)
56 68 element = assert_response.get_element('#repo_type')
57 69 assert element.text_content() == '\ngit\n'
58 70
59 71 def test_create_page_non_restricted(self, autologin_user, backend):
60 72 response = self.app.get(url('new_repo'), status=200)
61 73 assert_response = AssertResponse(response)
62 74 assert_response.element_contains('#repo_type', 'git')
63 75 assert_response.element_contains('#repo_type', 'svn')
64 76 assert_response.element_contains('#repo_type', 'hg')
65 77
66 78 @pytest.mark.parametrize("suffix",
67 79 [u'', u'xxa'], ids=['', 'non-ascii'])
68 80 def test_create(self, autologin_user, backend, suffix, csrf_token):
69 81 repo_name_unicode = backend.new_repo_name(suffix=suffix)
70 82 repo_name = repo_name_unicode.encode('utf8')
71 83 description_unicode = u'description for newly created repo' + suffix
72 84 description = description_unicode.encode('utf8')
73 85 response = self.app.post(
74 86 url('repos'),
75 87 fixture._get_repo_create_params(
76 88 repo_private=False,
77 89 repo_name=repo_name,
78 90 repo_type=backend.alias,
79 91 repo_description=description,
80 92 csrf_token=csrf_token),
81 93 status=302)
82 94
83 95 self.assert_repository_is_created_correctly(
84 96 repo_name, description, backend)
85 97
86 98 def test_create_numeric(self, autologin_user, backend, csrf_token):
87 99 numeric_repo = '1234'
88 100 repo_name = numeric_repo
89 101 description = 'description for newly created repo' + numeric_repo
90 102 self.app.post(
91 103 url('repos'),
92 104 fixture._get_repo_create_params(
93 105 repo_private=False,
94 106 repo_name=repo_name,
95 107 repo_type=backend.alias,
96 108 repo_description=description,
97 109 csrf_token=csrf_token))
98 110
99 111 self.assert_repository_is_created_correctly(
100 112 repo_name, description, backend)
101 113
102 114 @pytest.mark.parametrize("suffix", [u'', u'Δ…Δ‡Δ™'], ids=['', 'non-ascii'])
103 115 def test_create_in_group(
104 116 self, autologin_user, backend, suffix, csrf_token):
105 117 # create GROUP
106 118 group_name = 'sometest_%s' % backend.alias
107 119 gr = RepoGroupModel().create(group_name=group_name,
108 120 group_description='test',
109 121 owner=TEST_USER_ADMIN_LOGIN)
110 122 Session().commit()
111 123
112 124 repo_name = u'ingroup' + suffix
113 125 repo_name_full = RepoGroup.url_sep().join(
114 126 [group_name, repo_name])
115 127 description = u'description for newly created repo'
116 128 self.app.post(
117 129 url('repos'),
118 130 fixture._get_repo_create_params(
119 131 repo_private=False,
120 132 repo_name=safe_str(repo_name),
121 133 repo_type=backend.alias,
122 134 repo_description=description,
123 135 repo_group=gr.group_id,
124 136 csrf_token=csrf_token))
125 137
126 138 # TODO: johbo: Cleanup work to fixture
127 139 try:
128 140 self.assert_repository_is_created_correctly(
129 141 repo_name_full, description, backend)
130 142
131 143 new_repo = RepoModel().get_by_repo_name(repo_name_full)
132 144 inherited_perms = UserRepoToPerm.query().filter(
133 145 UserRepoToPerm.repository_id == new_repo.repo_id).all()
134 146 assert len(inherited_perms) == 1
135 147 finally:
136 148 RepoModel().delete(repo_name_full)
137 149 RepoGroupModel().delete(group_name)
138 150 Session().commit()
139 151
140 152 def test_create_in_group_numeric(
141 153 self, autologin_user, backend, csrf_token):
142 154 # create GROUP
143 155 group_name = 'sometest_%s' % backend.alias
144 156 gr = RepoGroupModel().create(group_name=group_name,
145 157 group_description='test',
146 158 owner=TEST_USER_ADMIN_LOGIN)
147 159 Session().commit()
148 160
149 161 repo_name = '12345'
150 162 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
151 163 description = 'description for newly created repo'
152 164 self.app.post(
153 165 url('repos'),
154 166 fixture._get_repo_create_params(
155 167 repo_private=False,
156 168 repo_name=repo_name,
157 169 repo_type=backend.alias,
158 170 repo_description=description,
159 171 repo_group=gr.group_id,
160 172 csrf_token=csrf_token))
161 173
162 174 # TODO: johbo: Cleanup work to fixture
163 175 try:
164 176 self.assert_repository_is_created_correctly(
165 177 repo_name_full, description, backend)
166 178
167 179 new_repo = RepoModel().get_by_repo_name(repo_name_full)
168 180 inherited_perms = UserRepoToPerm.query()\
169 181 .filter(UserRepoToPerm.repository_id == new_repo.repo_id).all()
170 182 assert len(inherited_perms) == 1
171 183 finally:
172 184 RepoModel().delete(repo_name_full)
173 185 RepoGroupModel().delete(group_name)
174 186 Session().commit()
175 187
176 188 def test_create_in_group_without_needed_permissions(self, backend):
177 189 session = login_user_session(
178 190 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
179 191 csrf_token = auth.get_csrf_token(session)
180 192 # revoke
181 193 user_model = UserModel()
182 194 # disable fork and create on default user
183 195 user_model.revoke_perm(User.DEFAULT_USER, 'hg.create.repository')
184 196 user_model.grant_perm(User.DEFAULT_USER, 'hg.create.none')
185 197 user_model.revoke_perm(User.DEFAULT_USER, 'hg.fork.repository')
186 198 user_model.grant_perm(User.DEFAULT_USER, 'hg.fork.none')
187 199
188 200 # disable on regular user
189 201 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
190 202 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.none')
191 203 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
192 204 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
193 205 Session().commit()
194 206
195 207 # create GROUP
196 208 group_name = 'reg_sometest_%s' % backend.alias
197 209 gr = RepoGroupModel().create(group_name=group_name,
198 210 group_description='test',
199 211 owner=TEST_USER_ADMIN_LOGIN)
200 212 Session().commit()
201 213
202 214 group_name_allowed = 'reg_sometest_allowed_%s' % backend.alias
203 215 gr_allowed = RepoGroupModel().create(
204 216 group_name=group_name_allowed,
205 217 group_description='test',
206 218 owner=TEST_USER_REGULAR_LOGIN)
207 219 Session().commit()
208 220
209 221 repo_name = 'ingroup'
210 222 description = 'description for newly created repo'
211 223 response = self.app.post(
212 224 url('repos'),
213 225 fixture._get_repo_create_params(
214 226 repo_private=False,
215 227 repo_name=repo_name,
216 228 repo_type=backend.alias,
217 229 repo_description=description,
218 230 repo_group=gr.group_id,
219 231 csrf_token=csrf_token))
220 232
221 233 response.mustcontain('Invalid value')
222 234
223 235 # user is allowed to create in this group
224 236 repo_name = 'ingroup'
225 237 repo_name_full = RepoGroup.url_sep().join(
226 238 [group_name_allowed, repo_name])
227 239 description = 'description for newly created repo'
228 240 response = self.app.post(
229 241 url('repos'),
230 242 fixture._get_repo_create_params(
231 243 repo_private=False,
232 244 repo_name=repo_name,
233 245 repo_type=backend.alias,
234 246 repo_description=description,
235 247 repo_group=gr_allowed.group_id,
236 248 csrf_token=csrf_token))
237 249
238 250 # TODO: johbo: Cleanup in pytest fixture
239 251 try:
240 252 self.assert_repository_is_created_correctly(
241 253 repo_name_full, description, backend)
242 254
243 255 new_repo = RepoModel().get_by_repo_name(repo_name_full)
244 256 inherited_perms = UserRepoToPerm.query().filter(
245 257 UserRepoToPerm.repository_id == new_repo.repo_id).all()
246 258 assert len(inherited_perms) == 1
247 259
248 260 assert repo_on_filesystem(repo_name_full)
249 261 finally:
250 262 RepoModel().delete(repo_name_full)
251 263 RepoGroupModel().delete(group_name)
252 264 RepoGroupModel().delete(group_name_allowed)
253 265 Session().commit()
254 266
255 267 def test_create_in_group_inherit_permissions(self, autologin_user, backend,
256 268 csrf_token):
257 269 # create GROUP
258 270 group_name = 'sometest_%s' % backend.alias
259 271 gr = RepoGroupModel().create(group_name=group_name,
260 272 group_description='test',
261 273 owner=TEST_USER_ADMIN_LOGIN)
262 274 perm = Permission.get_by_key('repository.write')
263 275 RepoGroupModel().grant_user_permission(
264 276 gr, TEST_USER_REGULAR_LOGIN, perm)
265 277
266 278 # add repo permissions
267 279 Session().commit()
268 280
269 281 repo_name = 'ingroup_inherited_%s' % backend.alias
270 282 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
271 283 description = 'description for newly created repo'
272 284 self.app.post(
273 285 url('repos'),
274 286 fixture._get_repo_create_params(
275 287 repo_private=False,
276 288 repo_name=repo_name,
277 289 repo_type=backend.alias,
278 290 repo_description=description,
279 291 repo_group=gr.group_id,
280 292 repo_copy_permissions=True,
281 293 csrf_token=csrf_token))
282 294
283 295 # TODO: johbo: Cleanup to pytest fixture
284 296 try:
285 297 self.assert_repository_is_created_correctly(
286 298 repo_name_full, description, backend)
287 299 except Exception:
288 300 RepoGroupModel().delete(group_name)
289 301 Session().commit()
290 302 raise
291 303
292 304 # check if inherited permissions are applied
293 305 new_repo = RepoModel().get_by_repo_name(repo_name_full)
294 306 inherited_perms = UserRepoToPerm.query().filter(
295 307 UserRepoToPerm.repository_id == new_repo.repo_id).all()
296 308 assert len(inherited_perms) == 2
297 309
298 310 assert TEST_USER_REGULAR_LOGIN in [
299 311 x.user.username for x in inherited_perms]
300 312 assert 'repository.write' in [
301 313 x.permission.permission_name for x in inherited_perms]
302 314
303 315 RepoModel().delete(repo_name_full)
304 316 RepoGroupModel().delete(group_name)
305 317 Session().commit()
306 318
307 319 @pytest.mark.xfail_backends(
308 320 "git", "hg", reason="Missing reposerver support")
309 321 def test_create_with_clone_uri(self, autologin_user, backend, reposerver,
310 322 csrf_token):
311 323 source_repo = backend.create_repo(number_of_commits=2)
312 324 source_repo_name = source_repo.repo_name
313 325 reposerver.serve(source_repo.scm_instance())
314 326
315 327 repo_name = backend.new_repo_name()
316 328 response = self.app.post(
317 329 url('repos'),
318 330 fixture._get_repo_create_params(
319 331 repo_private=False,
320 332 repo_name=repo_name,
321 333 repo_type=backend.alias,
322 334 repo_description='',
323 335 clone_uri=reposerver.url,
324 336 csrf_token=csrf_token),
325 337 status=302)
326 338
327 339 # Should be redirected to the creating page
328 340 response.mustcontain('repo_creating')
329 341
330 342 # Expecting that both repositories have same history
331 343 source_repo = RepoModel().get_by_repo_name(source_repo_name)
332 344 source_vcs = source_repo.scm_instance()
333 345 repo = RepoModel().get_by_repo_name(repo_name)
334 346 repo_vcs = repo.scm_instance()
335 347 assert source_vcs[0].message == repo_vcs[0].message
336 348 assert source_vcs.count() == repo_vcs.count()
337 349 assert source_vcs.commit_ids == repo_vcs.commit_ids
338 350
339 351 @pytest.mark.xfail_backends("svn", reason="Depends on import support")
340 352 def test_create_remote_repo_wrong_clone_uri(self, autologin_user, backend,
341 353 csrf_token):
342 354 repo_name = backend.new_repo_name()
343 355 description = 'description for newly created repo'
344 356 response = self.app.post(
345 357 url('repos'),
346 358 fixture._get_repo_create_params(
347 359 repo_private=False,
348 360 repo_name=repo_name,
349 361 repo_type=backend.alias,
350 362 repo_description=description,
351 363 clone_uri='http://repo.invalid/repo',
352 364 csrf_token=csrf_token))
353 365 response.mustcontain('invalid clone url')
354 366
355 367 @pytest.mark.xfail_backends("svn", reason="Depends on import support")
356 368 def test_create_remote_repo_wrong_clone_uri_hg_svn(
357 369 self, autologin_user, backend, csrf_token):
358 370 repo_name = backend.new_repo_name()
359 371 description = 'description for newly created repo'
360 372 response = self.app.post(
361 373 url('repos'),
362 374 fixture._get_repo_create_params(
363 375 repo_private=False,
364 376 repo_name=repo_name,
365 377 repo_type=backend.alias,
366 378 repo_description=description,
367 379 clone_uri='svn+http://svn.invalid/repo',
368 380 csrf_token=csrf_token))
369 381 response.mustcontain('invalid clone url')
370 382
371 383 def test_create_with_git_suffix(
372 384 self, autologin_user, backend, csrf_token):
373 385 repo_name = backend.new_repo_name() + ".git"
374 386 description = 'description for newly created repo'
375 387 response = self.app.post(
376 388 url('repos'),
377 389 fixture._get_repo_create_params(
378 390 repo_private=False,
379 391 repo_name=repo_name,
380 392 repo_type=backend.alias,
381 393 repo_description=description,
382 394 csrf_token=csrf_token))
383 395 response.mustcontain('Repository name cannot end with .git')
384 396
385 397 def test_show(self, autologin_user, backend):
386 398 self.app.get(url('repo', repo_name=backend.repo_name))
387 399
388 400 def test_default_user_cannot_access_private_repo_in_a_group(
389 401 self, autologin_user, user_util, backend, csrf_token):
390 402
391 403 group = user_util.create_repo_group()
392 404
393 405 repo = backend.create_repo(
394 406 repo_private=True, repo_group=group, repo_copy_permissions=True)
395 407
396 408 permissions = _get_permission_for_user(
397 409 user='default', repo=repo.repo_name)
398 410 assert len(permissions) == 1
399 411 assert permissions[0].permission.permission_name == 'repository.none'
400 412 assert permissions[0].repository.private is True
401 413
402 414 def test_create_on_top_level_without_permissions(self, backend):
403 415 session = login_user_session(
404 416 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
405 417 csrf_token = auth.get_csrf_token(session)
406 418
407 419 # revoke
408 420 user_model = UserModel()
409 421 # disable fork and create on default user
410 422 user_model.revoke_perm(User.DEFAULT_USER, 'hg.create.repository')
411 423 user_model.grant_perm(User.DEFAULT_USER, 'hg.create.none')
412 424 user_model.revoke_perm(User.DEFAULT_USER, 'hg.fork.repository')
413 425 user_model.grant_perm(User.DEFAULT_USER, 'hg.fork.none')
414 426
415 427 # disable on regular user
416 428 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
417 429 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.none')
418 430 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
419 431 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
420 432 Session().commit()
421 433
422 434 repo_name = backend.new_repo_name()
423 435 description = 'description for newly created repo'
424 436 response = self.app.post(
425 437 url('repos'),
426 438 fixture._get_repo_create_params(
427 439 repo_private=False,
428 440 repo_name=repo_name,
429 441 repo_type=backend.alias,
430 442 repo_description=description,
431 443 csrf_token=csrf_token))
432 444
433 445 response.mustcontain(
434 446 u"You do not have the permission to store repositories in "
435 447 u"the root location.")
436 448
437 449 @mock.patch.object(RepoModel, '_create_filesystem_repo', error_function)
438 450 def test_create_repo_when_filesystem_op_fails(
439 451 self, autologin_user, backend, csrf_token):
440 452 repo_name = backend.new_repo_name()
441 453 description = 'description for newly created repo'
442 454
443 455 response = self.app.post(
444 456 url('repos'),
445 457 fixture._get_repo_create_params(
446 458 repo_private=False,
447 459 repo_name=repo_name,
448 460 repo_type=backend.alias,
449 461 repo_description=description,
450 462 csrf_token=csrf_token))
451 463
452 464 assert_session_flash(
453 465 response, 'Error creating repository %s' % repo_name)
454 466 # repo must not be in db
455 467 assert backend.repo is None
456 468 # repo must not be in filesystem !
457 469 assert not repo_on_filesystem(repo_name)
458 470
459 471 def assert_repository_is_created_correctly(
460 472 self, repo_name, description, backend):
461 473 repo_name_utf8 = safe_str(repo_name)
462 474
463 475 # run the check page that triggers the flash message
464 response = self.app.get(url('repo_check_home', repo_name=repo_name))
476 response = self.app.get(
477 route_path('repo_creating_check', repo_name=safe_str(repo_name)))
465 478 assert response.json == {u'result': True}
466 479
467 480 flash_msg = u'Created repository <a href="/{}">{}</a>'.format(
468 481 urllib.quote(repo_name_utf8), repo_name)
469 482 assert_session_flash(response, flash_msg)
470 483
471 484 # test if the repo was created in the database
472 485 new_repo = RepoModel().get_by_repo_name(repo_name)
473 486
474 487 assert new_repo.repo_name == repo_name
475 488 assert new_repo.description == description
476 489
477 490 # test if the repository is visible in the list ?
478 response = self.app.get(h.route_path('repo_summary', repo_name=repo_name))
491 response = self.app.get(
492 h.route_path('repo_summary', repo_name=safe_str(repo_name)))
479 493 response.mustcontain(repo_name)
480 494 response.mustcontain(backend.alias)
481 495
482 496 assert repo_on_filesystem(repo_name)
483 497
484 498
485 499 @pytest.mark.usefixtures("app")
486 500 class TestVcsSettings(object):
487 501 FORM_DATA = {
488 502 'inherit_global_settings': False,
489 503 'hooks_changegroup_repo_size': False,
490 504 'hooks_changegroup_push_logger': False,
491 505 'hooks_outgoing_pull_logger': False,
492 506 'extensions_largefiles': False,
493 507 'extensions_evolve': False,
494 508 'phases_publish': 'False',
495 509 'rhodecode_pr_merge_enabled': False,
496 510 'rhodecode_use_outdated_comments': False,
497 511 'new_svn_branch': '',
498 512 'new_svn_tag': ''
499 513 }
500 514
501 515 @pytest.mark.skip_backends('svn')
502 516 def test_global_settings_initial_values(self, autologin_user, backend):
503 517 repo_name = backend.repo_name
504 518 response = self.app.get(url('repo_vcs_settings', repo_name=repo_name))
505 519
506 520 expected_settings = (
507 521 'rhodecode_use_outdated_comments', 'rhodecode_pr_merge_enabled',
508 522 'hooks_changegroup_repo_size', 'hooks_changegroup_push_logger',
509 523 'hooks_outgoing_pull_logger'
510 524 )
511 525 for setting in expected_settings:
512 526 self.assert_repo_value_equals_global_value(response, setting)
513 527
514 528 def test_show_settings_requires_repo_admin_permission(
515 529 self, backend, user_util, settings_util):
516 530 repo = backend.create_repo()
517 531 repo_name = repo.repo_name
518 532 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
519 533 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
520 534 login_user_session(
521 535 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
522 536 self.app.get(url('repo_vcs_settings', repo_name=repo_name), status=200)
523 537
524 538 def test_inherit_global_settings_flag_is_true_by_default(
525 539 self, autologin_user, backend):
526 540 repo_name = backend.repo_name
527 541 response = self.app.get(url('repo_vcs_settings', repo_name=repo_name))
528 542
529 543 assert_response = AssertResponse(response)
530 544 element = assert_response.get_element('#inherit_global_settings')
531 545 assert element.checked
532 546
533 547 @pytest.mark.parametrize('checked_value', [True, False])
534 548 def test_inherit_global_settings_value(
535 549 self, autologin_user, backend, checked_value, settings_util):
536 550 repo = backend.create_repo()
537 551 repo_name = repo.repo_name
538 552 settings_util.create_repo_rhodecode_setting(
539 553 repo, 'inherit_vcs_settings', checked_value, 'bool')
540 554 response = self.app.get(url('repo_vcs_settings', repo_name=repo_name))
541 555
542 556 assert_response = AssertResponse(response)
543 557 element = assert_response.get_element('#inherit_global_settings')
544 558 assert element.checked == checked_value
545 559
546 560 @pytest.mark.skip_backends('svn')
547 561 def test_hooks_settings_are_created(
548 562 self, autologin_user, backend, csrf_token):
549 563 repo_name = backend.repo_name
550 564 data = self.FORM_DATA.copy()
551 565 data['csrf_token'] = csrf_token
552 566 self.app.post(
553 567 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
554 568 settings = SettingsModel(repo=repo_name)
555 569 try:
556 570 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
557 571 ui = settings.get_ui_by_section_and_key(section, key)
558 572 assert ui.ui_active is False
559 573 finally:
560 574 self._cleanup_repo_settings(settings)
561 575
562 576 def test_hooks_settings_are_not_created_for_svn(
563 577 self, autologin_user, backend_svn, csrf_token):
564 578 repo_name = backend_svn.repo_name
565 579 data = self.FORM_DATA.copy()
566 580 data['csrf_token'] = csrf_token
567 581 self.app.post(
568 582 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
569 583 settings = SettingsModel(repo=repo_name)
570 584 try:
571 585 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
572 586 ui = settings.get_ui_by_section_and_key(section, key)
573 587 assert ui is None
574 588 finally:
575 589 self._cleanup_repo_settings(settings)
576 590
577 591 @pytest.mark.skip_backends('svn')
578 592 def test_hooks_settings_are_updated(
579 593 self, autologin_user, backend, csrf_token):
580 594 repo_name = backend.repo_name
581 595 settings = SettingsModel(repo=repo_name)
582 596 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
583 597 settings.create_ui_section_value(section, '', key=key, active=True)
584 598
585 599 data = self.FORM_DATA.copy()
586 600 data['csrf_token'] = csrf_token
587 601 self.app.post(
588 602 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
589 603 try:
590 604 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
591 605 ui = settings.get_ui_by_section_and_key(section, key)
592 606 assert ui.ui_active is False
593 607 finally:
594 608 self._cleanup_repo_settings(settings)
595 609
596 610 def test_hooks_settings_are_not_updated_for_svn(
597 611 self, autologin_user, backend_svn, csrf_token):
598 612 repo_name = backend_svn.repo_name
599 613 settings = SettingsModel(repo=repo_name)
600 614 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
601 615 settings.create_ui_section_value(section, '', key=key, active=True)
602 616
603 617 data = self.FORM_DATA.copy()
604 618 data['csrf_token'] = csrf_token
605 619 self.app.post(
606 620 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
607 621 try:
608 622 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
609 623 ui = settings.get_ui_by_section_and_key(section, key)
610 624 assert ui.ui_active is True
611 625 finally:
612 626 self._cleanup_repo_settings(settings)
613 627
614 628 @pytest.mark.skip_backends('svn')
615 629 def test_pr_settings_are_created(
616 630 self, autologin_user, backend, csrf_token):
617 631 repo_name = backend.repo_name
618 632 data = self.FORM_DATA.copy()
619 633 data['csrf_token'] = csrf_token
620 634 self.app.post(
621 635 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
622 636 settings = SettingsModel(repo=repo_name)
623 637 try:
624 638 for name in VcsSettingsModel.GENERAL_SETTINGS:
625 639 setting = settings.get_setting_by_name(name)
626 640 assert setting.app_settings_value is False
627 641 finally:
628 642 self._cleanup_repo_settings(settings)
629 643
630 644 def test_pr_settings_are_not_created_for_svn(
631 645 self, autologin_user, backend_svn, csrf_token):
632 646 repo_name = backend_svn.repo_name
633 647 data = self.FORM_DATA.copy()
634 648 data['csrf_token'] = csrf_token
635 649 self.app.post(
636 650 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
637 651 settings = SettingsModel(repo=repo_name)
638 652 try:
639 653 for name in VcsSettingsModel.GENERAL_SETTINGS:
640 654 setting = settings.get_setting_by_name(name)
641 655 assert setting is None
642 656 finally:
643 657 self._cleanup_repo_settings(settings)
644 658
645 659 def test_pr_settings_creation_requires_repo_admin_permission(
646 660 self, backend, user_util, settings_util, csrf_token):
647 661 repo = backend.create_repo()
648 662 repo_name = repo.repo_name
649 663
650 664 logout_user_session(self.app, csrf_token)
651 665 session = login_user_session(
652 666 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
653 667 new_csrf_token = auth.get_csrf_token(session)
654 668
655 669 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
656 670 repo = Repository.get_by_repo_name(repo_name)
657 671 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
658 672 data = self.FORM_DATA.copy()
659 673 data['csrf_token'] = new_csrf_token
660 674 settings = SettingsModel(repo=repo_name)
661 675
662 676 try:
663 677 self.app.post(
664 678 url('repo_vcs_settings', repo_name=repo_name), data,
665 679 status=302)
666 680 finally:
667 681 self._cleanup_repo_settings(settings)
668 682
669 683 @pytest.mark.skip_backends('svn')
670 684 def test_pr_settings_are_updated(
671 685 self, autologin_user, backend, csrf_token):
672 686 repo_name = backend.repo_name
673 687 settings = SettingsModel(repo=repo_name)
674 688 for name in VcsSettingsModel.GENERAL_SETTINGS:
675 689 settings.create_or_update_setting(name, True, 'bool')
676 690
677 691 data = self.FORM_DATA.copy()
678 692 data['csrf_token'] = csrf_token
679 693 self.app.post(
680 694 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
681 695 try:
682 696 for name in VcsSettingsModel.GENERAL_SETTINGS:
683 697 setting = settings.get_setting_by_name(name)
684 698 assert setting.app_settings_value is False
685 699 finally:
686 700 self._cleanup_repo_settings(settings)
687 701
688 702 def test_pr_settings_are_not_updated_for_svn(
689 703 self, autologin_user, backend_svn, csrf_token):
690 704 repo_name = backend_svn.repo_name
691 705 settings = SettingsModel(repo=repo_name)
692 706 for name in VcsSettingsModel.GENERAL_SETTINGS:
693 707 settings.create_or_update_setting(name, True, 'bool')
694 708
695 709 data = self.FORM_DATA.copy()
696 710 data['csrf_token'] = csrf_token
697 711 self.app.post(
698 712 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
699 713 try:
700 714 for name in VcsSettingsModel.GENERAL_SETTINGS:
701 715 setting = settings.get_setting_by_name(name)
702 716 assert setting.app_settings_value is True
703 717 finally:
704 718 self._cleanup_repo_settings(settings)
705 719
706 720 def test_svn_settings_are_created(
707 721 self, autologin_user, backend_svn, csrf_token, settings_util):
708 722 repo_name = backend_svn.repo_name
709 723 data = self.FORM_DATA.copy()
710 724 data['new_svn_tag'] = 'svn-tag'
711 725 data['new_svn_branch'] = 'svn-branch'
712 726 data['csrf_token'] = csrf_token
713 727
714 728 # Create few global settings to make sure that uniqueness validators
715 729 # are not triggered
716 730 settings_util.create_rhodecode_ui(
717 731 VcsSettingsModel.SVN_BRANCH_SECTION, 'svn-branch')
718 732 settings_util.create_rhodecode_ui(
719 733 VcsSettingsModel.SVN_TAG_SECTION, 'svn-tag')
720 734
721 735 self.app.post(
722 736 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
723 737 settings = SettingsModel(repo=repo_name)
724 738 try:
725 739 svn_branches = settings.get_ui_by_section(
726 740 VcsSettingsModel.SVN_BRANCH_SECTION)
727 741 svn_branch_names = [b.ui_value for b in svn_branches]
728 742 svn_tags = settings.get_ui_by_section(
729 743 VcsSettingsModel.SVN_TAG_SECTION)
730 744 svn_tag_names = [b.ui_value for b in svn_tags]
731 745 assert 'svn-branch' in svn_branch_names
732 746 assert 'svn-tag' in svn_tag_names
733 747 finally:
734 748 self._cleanup_repo_settings(settings)
735 749
736 750 def test_svn_settings_are_unique(
737 751 self, autologin_user, backend_svn, csrf_token, settings_util):
738 752 repo = backend_svn.repo
739 753 repo_name = repo.repo_name
740 754 data = self.FORM_DATA.copy()
741 755 data['new_svn_tag'] = 'test_tag'
742 756 data['new_svn_branch'] = 'test_branch'
743 757 data['csrf_token'] = csrf_token
744 758 settings_util.create_repo_rhodecode_ui(
745 759 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch')
746 760 settings_util.create_repo_rhodecode_ui(
747 761 repo, VcsSettingsModel.SVN_TAG_SECTION, 'test_tag')
748 762
749 763 response = self.app.post(
750 764 url('repo_vcs_settings', repo_name=repo_name), data, status=200)
751 765 response.mustcontain('Pattern already exists')
752 766
753 767 def test_svn_settings_with_empty_values_are_not_created(
754 768 self, autologin_user, backend_svn, csrf_token):
755 769 repo_name = backend_svn.repo_name
756 770 data = self.FORM_DATA.copy()
757 771 data['csrf_token'] = csrf_token
758 772 self.app.post(
759 773 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
760 774 settings = SettingsModel(repo=repo_name)
761 775 try:
762 776 svn_branches = settings.get_ui_by_section(
763 777 VcsSettingsModel.SVN_BRANCH_SECTION)
764 778 svn_tags = settings.get_ui_by_section(
765 779 VcsSettingsModel.SVN_TAG_SECTION)
766 780 assert len(svn_branches) == 0
767 781 assert len(svn_tags) == 0
768 782 finally:
769 783 self._cleanup_repo_settings(settings)
770 784
771 785 def test_svn_settings_are_shown_for_svn_repository(
772 786 self, autologin_user, backend_svn, csrf_token):
773 787 repo_name = backend_svn.repo_name
774 788 response = self.app.get(
775 789 url('repo_vcs_settings', repo_name=repo_name), status=200)
776 790 response.mustcontain('Subversion Settings')
777 791
778 792 @pytest.mark.skip_backends('svn')
779 793 def test_svn_settings_are_not_created_for_not_svn_repository(
780 794 self, autologin_user, backend, csrf_token):
781 795 repo_name = backend.repo_name
782 796 data = self.FORM_DATA.copy()
783 797 data['csrf_token'] = csrf_token
784 798 self.app.post(
785 799 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
786 800 settings = SettingsModel(repo=repo_name)
787 801 try:
788 802 svn_branches = settings.get_ui_by_section(
789 803 VcsSettingsModel.SVN_BRANCH_SECTION)
790 804 svn_tags = settings.get_ui_by_section(
791 805 VcsSettingsModel.SVN_TAG_SECTION)
792 806 assert len(svn_branches) == 0
793 807 assert len(svn_tags) == 0
794 808 finally:
795 809 self._cleanup_repo_settings(settings)
796 810
797 811 @pytest.mark.skip_backends('svn')
798 812 def test_svn_settings_are_shown_only_for_svn_repository(
799 813 self, autologin_user, backend, csrf_token):
800 814 repo_name = backend.repo_name
801 815 response = self.app.get(
802 816 url('repo_vcs_settings', repo_name=repo_name), status=200)
803 817 response.mustcontain(no='Subversion Settings')
804 818
805 819 def test_hg_settings_are_created(
806 820 self, autologin_user, backend_hg, csrf_token):
807 821 repo_name = backend_hg.repo_name
808 822 data = self.FORM_DATA.copy()
809 823 data['new_svn_tag'] = 'svn-tag'
810 824 data['new_svn_branch'] = 'svn-branch'
811 825 data['csrf_token'] = csrf_token
812 826 self.app.post(
813 827 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
814 828 settings = SettingsModel(repo=repo_name)
815 829 try:
816 830 largefiles_ui = settings.get_ui_by_section_and_key(
817 831 'extensions', 'largefiles')
818 832 assert largefiles_ui.ui_active is False
819 833 phases_ui = settings.get_ui_by_section_and_key(
820 834 'phases', 'publish')
821 835 assert str2bool(phases_ui.ui_value) is False
822 836 finally:
823 837 self._cleanup_repo_settings(settings)
824 838
825 839 def test_hg_settings_are_updated(
826 840 self, autologin_user, backend_hg, csrf_token):
827 841 repo_name = backend_hg.repo_name
828 842 settings = SettingsModel(repo=repo_name)
829 843 settings.create_ui_section_value(
830 844 'extensions', '', key='largefiles', active=True)
831 845 settings.create_ui_section_value(
832 846 'phases', '1', key='publish', active=True)
833 847
834 848 data = self.FORM_DATA.copy()
835 849 data['csrf_token'] = csrf_token
836 850 self.app.post(
837 851 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
838 852 try:
839 853 largefiles_ui = settings.get_ui_by_section_and_key(
840 854 'extensions', 'largefiles')
841 855 assert largefiles_ui.ui_active is False
842 856 phases_ui = settings.get_ui_by_section_and_key(
843 857 'phases', 'publish')
844 858 assert str2bool(phases_ui.ui_value) is False
845 859 finally:
846 860 self._cleanup_repo_settings(settings)
847 861
848 862 def test_hg_settings_are_shown_for_hg_repository(
849 863 self, autologin_user, backend_hg, csrf_token):
850 864 repo_name = backend_hg.repo_name
851 865 response = self.app.get(
852 866 url('repo_vcs_settings', repo_name=repo_name), status=200)
853 867 response.mustcontain('Mercurial Settings')
854 868
855 869 @pytest.mark.skip_backends('hg')
856 870 def test_hg_settings_are_created_only_for_hg_repository(
857 871 self, autologin_user, backend, csrf_token):
858 872 repo_name = backend.repo_name
859 873 data = self.FORM_DATA.copy()
860 874 data['csrf_token'] = csrf_token
861 875 self.app.post(
862 876 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
863 877 settings = SettingsModel(repo=repo_name)
864 878 try:
865 879 largefiles_ui = settings.get_ui_by_section_and_key(
866 880 'extensions', 'largefiles')
867 881 assert largefiles_ui is None
868 882 phases_ui = settings.get_ui_by_section_and_key(
869 883 'phases', 'publish')
870 884 assert phases_ui is None
871 885 finally:
872 886 self._cleanup_repo_settings(settings)
873 887
874 888 @pytest.mark.skip_backends('hg')
875 889 def test_hg_settings_are_shown_only_for_hg_repository(
876 890 self, autologin_user, backend, csrf_token):
877 891 repo_name = backend.repo_name
878 892 response = self.app.get(
879 893 url('repo_vcs_settings', repo_name=repo_name), status=200)
880 894 response.mustcontain(no='Mercurial Settings')
881 895
882 896 @pytest.mark.skip_backends('hg')
883 897 def test_hg_settings_are_updated_only_for_hg_repository(
884 898 self, autologin_user, backend, csrf_token):
885 899 repo_name = backend.repo_name
886 900 settings = SettingsModel(repo=repo_name)
887 901 settings.create_ui_section_value(
888 902 'extensions', '', key='largefiles', active=True)
889 903 settings.create_ui_section_value(
890 904 'phases', '1', key='publish', active=True)
891 905
892 906 data = self.FORM_DATA.copy()
893 907 data['csrf_token'] = csrf_token
894 908 self.app.post(
895 909 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
896 910 try:
897 911 largefiles_ui = settings.get_ui_by_section_and_key(
898 912 'extensions', 'largefiles')
899 913 assert largefiles_ui.ui_active is True
900 914 phases_ui = settings.get_ui_by_section_and_key(
901 915 'phases', 'publish')
902 916 assert phases_ui.ui_value == '1'
903 917 finally:
904 918 self._cleanup_repo_settings(settings)
905 919
906 920 def test_per_repo_svn_settings_are_displayed(
907 921 self, autologin_user, backend_svn, settings_util):
908 922 repo = backend_svn.create_repo()
909 923 repo_name = repo.repo_name
910 924 branches = [
911 925 settings_util.create_repo_rhodecode_ui(
912 926 repo, VcsSettingsModel.SVN_BRANCH_SECTION,
913 927 'branch_{}'.format(i))
914 928 for i in range(10)]
915 929 tags = [
916 930 settings_util.create_repo_rhodecode_ui(
917 931 repo, VcsSettingsModel.SVN_TAG_SECTION, 'tag_{}'.format(i))
918 932 for i in range(10)]
919 933
920 934 response = self.app.get(
921 935 url('repo_vcs_settings', repo_name=repo_name), status=200)
922 936 assert_response = AssertResponse(response)
923 937 for branch in branches:
924 938 css_selector = '[name=branch_value_{}]'.format(branch.ui_id)
925 939 element = assert_response.get_element(css_selector)
926 940 assert element.value == branch.ui_value
927 941 for tag in tags:
928 942 css_selector = '[name=tag_ui_value_new_{}]'.format(tag.ui_id)
929 943 element = assert_response.get_element(css_selector)
930 944 assert element.value == tag.ui_value
931 945
932 946 def test_per_repo_hg_and_pr_settings_are_not_displayed_for_svn(
933 947 self, autologin_user, backend_svn, settings_util):
934 948 repo = backend_svn.create_repo()
935 949 repo_name = repo.repo_name
936 950 response = self.app.get(
937 951 url('repo_vcs_settings', repo_name=repo_name), status=200)
938 952 response.mustcontain(no='<label>Hooks:</label>')
939 953 response.mustcontain(no='<label>Pull Request Settings:</label>')
940 954
941 955 def test_inherit_global_settings_value_is_saved(
942 956 self, autologin_user, backend, csrf_token):
943 957 repo_name = backend.repo_name
944 958 data = self.FORM_DATA.copy()
945 959 data['csrf_token'] = csrf_token
946 960 data['inherit_global_settings'] = True
947 961 self.app.post(
948 962 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
949 963
950 964 settings = SettingsModel(repo=repo_name)
951 965 vcs_settings = VcsSettingsModel(repo=repo_name)
952 966 try:
953 967 assert vcs_settings.inherit_global_settings is True
954 968 finally:
955 969 self._cleanup_repo_settings(settings)
956 970
957 971 def test_repo_cache_is_invalidated_when_settings_are_updated(
958 972 self, autologin_user, backend, csrf_token):
959 973 repo_name = backend.repo_name
960 974 data = self.FORM_DATA.copy()
961 975 data['csrf_token'] = csrf_token
962 976 data['inherit_global_settings'] = True
963 977 settings = SettingsModel(repo=repo_name)
964 978
965 979 invalidation_patcher = mock.patch(
966 980 'rhodecode.controllers.admin.repos.ScmModel.mark_for_invalidation')
967 981 with invalidation_patcher as invalidation_mock:
968 982 self.app.post(
969 983 url('repo_vcs_settings', repo_name=repo_name), data,
970 984 status=302)
971 985 try:
972 986 invalidation_mock.assert_called_once_with(repo_name, delete=True)
973 987 finally:
974 988 self._cleanup_repo_settings(settings)
975 989
976 990 def test_other_settings_not_saved_inherit_global_settings_is_true(
977 991 self, autologin_user, backend, csrf_token):
978 992 repo_name = backend.repo_name
979 993 data = self.FORM_DATA.copy()
980 994 data['csrf_token'] = csrf_token
981 995 data['inherit_global_settings'] = True
982 996 self.app.post(
983 997 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
984 998
985 999 settings = SettingsModel(repo=repo_name)
986 1000 ui_settings = (
987 1001 VcsSettingsModel.HOOKS_SETTINGS + VcsSettingsModel.HG_SETTINGS)
988 1002
989 1003 vcs_settings = []
990 1004 try:
991 1005 for section, key in ui_settings:
992 1006 ui = settings.get_ui_by_section_and_key(section, key)
993 1007 if ui:
994 1008 vcs_settings.append(ui)
995 1009 vcs_settings.extend(settings.get_ui_by_section(
996 1010 VcsSettingsModel.SVN_BRANCH_SECTION))
997 1011 vcs_settings.extend(settings.get_ui_by_section(
998 1012 VcsSettingsModel.SVN_TAG_SECTION))
999 1013 for name in VcsSettingsModel.GENERAL_SETTINGS:
1000 1014 setting = settings.get_setting_by_name(name)
1001 1015 if setting:
1002 1016 vcs_settings.append(setting)
1003 1017 assert vcs_settings == []
1004 1018 finally:
1005 1019 self._cleanup_repo_settings(settings)
1006 1020
1007 1021 def test_delete_svn_branch_and_tag_patterns(
1008 1022 self, autologin_user, backend_svn, settings_util, csrf_token):
1009 1023 repo = backend_svn.create_repo()
1010 1024 repo_name = repo.repo_name
1011 1025 branch = settings_util.create_repo_rhodecode_ui(
1012 1026 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch',
1013 1027 cleanup=False)
1014 1028 tag = settings_util.create_repo_rhodecode_ui(
1015 1029 repo, VcsSettingsModel.SVN_TAG_SECTION, 'test_tag', cleanup=False)
1016 1030 data = {
1017 1031 '_method': 'delete',
1018 1032 'csrf_token': csrf_token
1019 1033 }
1020 1034 for id_ in (branch.ui_id, tag.ui_id):
1021 1035 data['delete_svn_pattern'] = id_,
1022 1036 self.app.post(
1023 1037 url('repo_vcs_settings', repo_name=repo_name), data,
1024 1038 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
1025 1039 settings = VcsSettingsModel(repo=repo_name)
1026 1040 assert settings.get_repo_svn_branch_patterns() == []
1027 1041
1028 1042 def test_delete_svn_branch_requires_repo_admin_permission(
1029 1043 self, backend_svn, user_util, settings_util, csrf_token):
1030 1044 repo = backend_svn.create_repo()
1031 1045 repo_name = repo.repo_name
1032 1046
1033 1047 logout_user_session(self.app, csrf_token)
1034 1048 session = login_user_session(
1035 1049 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
1036 1050 csrf_token = auth.get_csrf_token(session)
1037 1051
1038 1052 repo = Repository.get_by_repo_name(repo_name)
1039 1053 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
1040 1054 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
1041 1055 branch = settings_util.create_repo_rhodecode_ui(
1042 1056 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch',
1043 1057 cleanup=False)
1044 1058 data = {
1045 1059 '_method': 'delete',
1046 1060 'csrf_token': csrf_token,
1047 1061 'delete_svn_pattern': branch.ui_id
1048 1062 }
1049 1063 self.app.post(
1050 1064 url('repo_vcs_settings', repo_name=repo_name), data,
1051 1065 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
1052 1066
1053 1067 def test_delete_svn_branch_raises_400_when_not_found(
1054 1068 self, autologin_user, backend_svn, settings_util, csrf_token):
1055 1069 repo_name = backend_svn.repo_name
1056 1070 data = {
1057 1071 '_method': 'delete',
1058 1072 'delete_svn_pattern': 123,
1059 1073 'csrf_token': csrf_token
1060 1074 }
1061 1075 self.app.post(
1062 1076 url('repo_vcs_settings', repo_name=repo_name), data,
1063 1077 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=400)
1064 1078
1065 1079 def test_delete_svn_branch_raises_400_when_no_id_specified(
1066 1080 self, autologin_user, backend_svn, settings_util, csrf_token):
1067 1081 repo_name = backend_svn.repo_name
1068 1082 data = {
1069 1083 '_method': 'delete',
1070 1084 'csrf_token': csrf_token
1071 1085 }
1072 1086 self.app.post(
1073 1087 url('repo_vcs_settings', repo_name=repo_name), data,
1074 1088 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=400)
1075 1089
1076 1090 def _cleanup_repo_settings(self, settings_model):
1077 1091 cleanup = []
1078 1092 ui_settings = (
1079 1093 VcsSettingsModel.HOOKS_SETTINGS + VcsSettingsModel.HG_SETTINGS)
1080 1094
1081 1095 for section, key in ui_settings:
1082 1096 ui = settings_model.get_ui_by_section_and_key(section, key)
1083 1097 if ui:
1084 1098 cleanup.append(ui)
1085 1099
1086 1100 cleanup.extend(settings_model.get_ui_by_section(
1087 1101 VcsSettingsModel.INHERIT_SETTINGS))
1088 1102 cleanup.extend(settings_model.get_ui_by_section(
1089 1103 VcsSettingsModel.SVN_BRANCH_SECTION))
1090 1104 cleanup.extend(settings_model.get_ui_by_section(
1091 1105 VcsSettingsModel.SVN_TAG_SECTION))
1092 1106
1093 1107 for name in VcsSettingsModel.GENERAL_SETTINGS:
1094 1108 setting = settings_model.get_setting_by_name(name)
1095 1109 if setting:
1096 1110 cleanup.append(setting)
1097 1111
1098 1112 for object_ in cleanup:
1099 1113 Session().delete(object_)
1100 1114 Session().commit()
1101 1115
1102 1116 def assert_repo_value_equals_global_value(self, response, setting):
1103 1117 assert_response = AssertResponse(response)
1104 1118 global_css_selector = '[name={}_inherited]'.format(setting)
1105 1119 repo_css_selector = '[name={}]'.format(setting)
1106 1120 repo_element = assert_response.get_element(repo_css_selector)
1107 1121 global_element = assert_response.get_element(global_css_selector)
1108 1122 assert repo_element.value == global_element.value
1109 1123
1110 1124
1111 1125 def _get_permission_for_user(user, repo):
1112 1126 perm = UserRepoToPerm.query()\
1113 1127 .filter(UserRepoToPerm.repository ==
1114 1128 Repository.get_by_repo_name(repo))\
1115 1129 .filter(UserRepoToPerm.user == User.get_by_username(user))\
1116 1130 .all()
1117 1131 return perm
@@ -1,273 +1,288 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 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 pytest
22 22
23 23 from rhodecode.tests import *
24 24 from rhodecode.tests.fixture import Fixture
25 25 from rhodecode.lib import helpers as h
26 26
27 27 from rhodecode.model.db import Repository
28 28 from rhodecode.model.repo import RepoModel
29 29 from rhodecode.model.user import UserModel
30 30 from rhodecode.model.meta import Session
31 31
32 32 fixture = Fixture()
33 33
34 34
35 def route_path(name, params=None, **kwargs):
36 import urllib
37
38 base_url = {
39 'repo_summary': '/{repo_name}',
40 'repo_creating_check': '/{repo_name}/repo_creating_check',
41 }[name].format(**kwargs)
42
43 if params:
44 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
45 return base_url
46
47
35 48 class _BaseTest(TestController):
36 49
37 50 REPO = None
38 51 REPO_TYPE = None
39 52 NEW_REPO = None
40 53 REPO_FORK = None
41 54
42 55 @pytest.fixture(autouse=True)
43 56 def prepare(self, request, pylonsapp):
44 57 self.username = u'forkuser'
45 58 self.password = u'qweqwe'
46 59 self.u1 = fixture.create_user(self.username, password=self.password,
47 60 email=u'fork_king@rhodecode.org')
48 61 Session().commit()
49 62 self.u1id = self.u1.user_id
50 63 request.addfinalizer(self.cleanup)
51 64
52 65 def cleanup(self):
53 66 u1 = UserModel().get(self.u1id)
54 67 Session().delete(u1)
55 68 Session().commit()
56 69
57 70 def test_index(self):
58 71 self.log_user()
59 72 repo_name = self.REPO
60 73 response = self.app.get(url(controller='forks', action='forks',
61 74 repo_name=repo_name))
62 75
63 76 response.mustcontain("""There are no forks yet""")
64 77
65 78 def test_no_permissions_to_fork(self):
66 79 usr = self.log_user(TEST_USER_REGULAR_LOGIN,
67 80 TEST_USER_REGULAR_PASS)['user_id']
68 81 user_model = UserModel()
69 82 user_model.revoke_perm(usr, 'hg.fork.repository')
70 83 user_model.grant_perm(usr, 'hg.fork.none')
71 84 u = UserModel().get(usr)
72 85 u.inherit_default_permissions = False
73 86 Session().commit()
74 87 # try create a fork
75 88 repo_name = self.REPO
76 89 self.app.post(
77 90 url(controller='forks', action='fork_create', repo_name=repo_name),
78 91 {'csrf_token': self.csrf_token}, status=404)
79 92
80 93 def test_index_with_fork(self):
81 94 self.log_user()
82 95
83 96 # create a fork
84 97 fork_name = self.REPO_FORK
85 98 description = 'fork of vcs test'
86 99 repo_name = self.REPO
87 100 source_repo = Repository.get_by_repo_name(repo_name)
88 101 creation_args = {
89 102 'repo_name': fork_name,
90 103 'repo_group': '',
91 104 'fork_parent_id': source_repo.repo_id,
92 105 'repo_type': self.REPO_TYPE,
93 106 'description': description,
94 107 'private': 'False',
95 108 'landing_rev': 'rev:tip',
96 109 'csrf_token': self.csrf_token,
97 110 }
98 111
99 112 self.app.post(url(controller='forks', action='fork_create',
100 113 repo_name=repo_name), creation_args)
101 114
102 115 response = self.app.get(url(controller='forks', action='forks',
103 116 repo_name=repo_name))
104 117
105 118 response.mustcontain(
106 119 """<a href="/%s">%s</a>""" % (fork_name, fork_name)
107 120 )
108 121
109 122 # remove this fork
110 123 fixture.destroy_repo(fork_name)
111 124
112 125 def test_fork_create_into_group(self):
113 126 self.log_user()
114 127 group = fixture.create_repo_group('vc')
115 128 group_id = group.group_id
116 129 fork_name = self.REPO_FORK
117 130 fork_name_full = 'vc/%s' % fork_name
118 131 description = 'fork of vcs test'
119 132 repo_name = self.REPO
120 133 source_repo = Repository.get_by_repo_name(repo_name)
121 134 creation_args = {
122 135 'repo_name': fork_name,
123 136 'repo_group': group_id,
124 137 'fork_parent_id': source_repo.repo_id,
125 138 'repo_type': self.REPO_TYPE,
126 139 'description': description,
127 140 'private': 'False',
128 141 'landing_rev': 'rev:tip',
129 142 'csrf_token': self.csrf_token,
130 143 }
131 144 self.app.post(url(controller='forks', action='fork_create',
132 145 repo_name=repo_name), creation_args)
133 146 repo = Repository.get_by_repo_name(fork_name_full)
134 147 assert repo.fork.repo_name == self.REPO
135 148
136 149 # run the check page that triggers the flash message
137 response = self.app.get(url('repo_check_home', repo_name=fork_name_full))
150 response = self.app.get(
151 route_path('repo_creating_check', repo_name=fork_name_full))
138 152 # test if we have a message that fork is ok
139 153 assert_session_flash(response,
140 154 'Forked repository %s as <a href="/%s">%s</a>'
141 155 % (repo_name, fork_name_full, fork_name_full))
142 156
143 157 # test if the fork was created in the database
144 158 fork_repo = Session().query(Repository)\
145 159 .filter(Repository.repo_name == fork_name_full).one()
146 160
147 161 assert fork_repo.repo_name == fork_name_full
148 162 assert fork_repo.fork.repo_name == repo_name
149 163
150 164 # test if the repository is visible in the list ?
151 165 response = self.app.get(h.route_path('repo_summary', repo_name=fork_name_full))
152 166 response.mustcontain(fork_name_full)
153 167 response.mustcontain(self.REPO_TYPE)
154 168
155 169 response.mustcontain('Fork of')
156 170 response.mustcontain('<a href="/%s">%s</a>' % (repo_name, repo_name))
157 171
158 172 fixture.destroy_repo(fork_name_full)
159 173 fixture.destroy_repo_group(group_id)
160 174
161 175 def test_z_fork_create(self):
162 176 self.log_user()
163 177 fork_name = self.REPO_FORK
164 178 description = 'fork of vcs test'
165 179 repo_name = self.REPO
166 180 source_repo = Repository.get_by_repo_name(repo_name)
167 181 creation_args = {
168 182 'repo_name': fork_name,
169 183 'repo_group': '',
170 184 'fork_parent_id': source_repo.repo_id,
171 185 'repo_type': self.REPO_TYPE,
172 186 'description': description,
173 187 'private': 'False',
174 188 'landing_rev': 'rev:tip',
175 189 'csrf_token': self.csrf_token,
176 190 }
177 191 self.app.post(url(controller='forks', action='fork_create',
178 192 repo_name=repo_name), creation_args)
179 193 repo = Repository.get_by_repo_name(self.REPO_FORK)
180 194 assert repo.fork.repo_name == self.REPO
181 195
182 196 # run the check page that triggers the flash message
183 response = self.app.get(url('repo_check_home', repo_name=fork_name))
197 response = self.app.get(
198 route_path('repo_creating_check', repo_name=fork_name))
184 199 # test if we have a message that fork is ok
185 200 assert_session_flash(response,
186 201 'Forked repository %s as <a href="/%s">%s</a>'
187 202 % (repo_name, fork_name, fork_name))
188 203
189 204 # test if the fork was created in the database
190 205 fork_repo = Session().query(Repository)\
191 206 .filter(Repository.repo_name == fork_name).one()
192 207
193 208 assert fork_repo.repo_name == fork_name
194 209 assert fork_repo.fork.repo_name == repo_name
195 210
196 211 # test if the repository is visible in the list ?
197 212 response = self.app.get(h.route_path('repo_summary', repo_name=fork_name))
198 213 response.mustcontain(fork_name)
199 214 response.mustcontain(self.REPO_TYPE)
200 215 response.mustcontain('Fork of')
201 216 response.mustcontain('<a href="/%s">%s</a>' % (repo_name, repo_name))
202 217
203 218 def test_zz_fork_permission_page(self):
204 219 usr = self.log_user(self.username, self.password)['user_id']
205 220 repo_name = self.REPO
206 221
207 222 forks = Repository.query()\
208 223 .filter(Repository.repo_type == self.REPO_TYPE)\
209 224 .filter(Repository.fork_id != None).all()
210 225 assert 1 == len(forks)
211 226
212 227 # set read permissions for this
213 228 RepoModel().grant_user_permission(repo=forks[0],
214 229 user=usr,
215 230 perm='repository.read')
216 231 Session().commit()
217 232
218 233 response = self.app.get(url(controller='forks', action='forks',
219 234 repo_name=repo_name))
220 235
221 236 response.mustcontain('fork of vcs test')
222 237
223 238 def test_zzz_fork_permission_page(self):
224 239 usr = self.log_user(self.username, self.password)['user_id']
225 240 repo_name = self.REPO
226 241
227 242 forks = Repository.query()\
228 243 .filter(Repository.repo_type == self.REPO_TYPE)\
229 244 .filter(Repository.fork_id != None).all()
230 245 assert 1 == len(forks)
231 246
232 247 # set none
233 248 RepoModel().grant_user_permission(repo=forks[0],
234 249 user=usr, perm='repository.none')
235 250 Session().commit()
236 251 # fork shouldn't be there
237 252 response = self.app.get(url(controller='forks', action='forks',
238 253 repo_name=repo_name))
239 254 response.mustcontain('There are no forks yet')
240 255
241 256
242 257 class TestGIT(_BaseTest):
243 258 REPO = GIT_REPO
244 259 NEW_REPO = NEW_GIT_REPO
245 260 REPO_TYPE = 'git'
246 261 REPO_FORK = GIT_FORK
247 262
248 263
249 264 class TestHG(_BaseTest):
250 265 REPO = HG_REPO
251 266 NEW_REPO = NEW_HG_REPO
252 267 REPO_TYPE = 'hg'
253 268 REPO_FORK = HG_FORK
254 269
255 270
256 271 @pytest.mark.usefixtures('app', 'autologin_user')
257 272 @pytest.mark.skip_backends('git','hg')
258 273 class TestSVNFork(object):
259 274
260 275 def test_fork_redirects(self, backend):
261 276 denied_actions = ['fork','fork_create']
262 277 for action in denied_actions:
263 278 response = self.app.get(url(
264 279 controller='forks', action=action,
265 280 repo_name=backend.repo_name))
266 281 assert response.status_int == 302
267 282
268 283 # Not allowed, redirect to the summary
269 284 redirected = response.follow()
270 285 summary_url = h.route_path('repo_summary', repo_name=backend.repo_name)
271 286
272 287 # URL adds leading slash and path doesn't have it
273 288 assert redirected.request.path == summary_url
General Comments 0
You need to be logged in to leave comments. Login now