##// END OF EJS Templates
apps: removed deprecated usage of c.repo_info
marcink -
r2081:63c02c14 default
parent child Browse files
Show More
@@ -1,508 +1,505 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 import operator
24 24
25 25 from pyramid.httpexceptions import HTTPFound
26 26
27 27 from rhodecode.lib import helpers as h
28 28 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
29 29 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
30 30 from rhodecode.model import repo
31 31 from rhodecode.model import repo_group
32 32 from rhodecode.model import user_group
33 33 from rhodecode.model.db import User
34 34 from rhodecode.model.scm import ScmModel
35 35
36 36 log = logging.getLogger(__name__)
37 37
38 38
39 39 ADMIN_PREFIX = '/_admin'
40 40 STATIC_FILE_PREFIX = '/_static'
41 41
42 42 URL_NAME_REQUIREMENTS = {
43 43 # group name can have a slash in them, but they must not end with a slash
44 44 'group_name': r'.*?[^/]',
45 45 'repo_group_name': r'.*?[^/]',
46 46 # repo names can have a slash in them, but they must not end with a slash
47 47 'repo_name': r'.*?[^/]',
48 48 # file path eats up everything at the end
49 49 'f_path': r'.*',
50 50 # reference types
51 51 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
52 52 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
53 53 }
54 54
55 55
56 56 def add_route_with_slash(config,name, pattern, **kw):
57 57 config.add_route(name, pattern, **kw)
58 58 if not pattern.endswith('/'):
59 59 config.add_route(name + '_slash', pattern + '/', **kw)
60 60
61 61
62 62 def add_route_requirements(route_path, requirements=URL_NAME_REQUIREMENTS):
63 63 """
64 64 Adds regex requirements to pyramid routes using a mapping dict
65 65 e.g::
66 66 add_route_requirements('{repo_name}/settings')
67 67 """
68 68 for key, regex in requirements.items():
69 69 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
70 70 return route_path
71 71
72 72
73 73 def get_format_ref_id(repo):
74 74 """Returns a `repo` specific reference formatter function"""
75 75 if h.is_svn(repo):
76 76 return _format_ref_id_svn
77 77 else:
78 78 return _format_ref_id
79 79
80 80
81 81 def _format_ref_id(name, raw_id):
82 82 """Default formatting of a given reference `name`"""
83 83 return name
84 84
85 85
86 86 def _format_ref_id_svn(name, raw_id):
87 87 """Special way of formatting a reference for Subversion including path"""
88 88 return '%s@%s' % (name, raw_id)
89 89
90 90
91 91 class TemplateArgs(StrictAttributeDict):
92 92 pass
93 93
94 94
95 95 class BaseAppView(object):
96 96
97 97 def __init__(self, context, request):
98 98 self.request = request
99 99 self.context = context
100 100 self.session = request.session
101 101 self._rhodecode_user = request.user # auth user
102 102 self._rhodecode_db_user = self._rhodecode_user.get_instance()
103 103 self._maybe_needs_password_change(
104 104 request.matched_route.name, self._rhodecode_db_user)
105 105
106 106 def _maybe_needs_password_change(self, view_name, user_obj):
107 107 log.debug('Checking if user %s needs password change on view %s',
108 108 user_obj, view_name)
109 109 skip_user_views = [
110 110 'logout', 'login',
111 111 'my_account_password', 'my_account_password_update'
112 112 ]
113 113
114 114 if not user_obj:
115 115 return
116 116
117 117 if user_obj.username == User.DEFAULT_USER:
118 118 return
119 119
120 120 now = time.time()
121 121 should_change = user_obj.user_data.get('force_password_change')
122 122 change_after = safe_int(should_change) or 0
123 123 if should_change and now > change_after:
124 124 log.debug('User %s requires password change', user_obj)
125 125 h.flash('You are required to change your password', 'warning',
126 126 ignore_duplicate=True)
127 127
128 128 if view_name not in skip_user_views:
129 129 raise HTTPFound(
130 130 self.request.route_path('my_account_password'))
131 131
132 132 def _log_creation_exception(self, e, repo_name):
133 133 _ = self.request.translate
134 134 reason = None
135 135 if len(e.args) == 2:
136 136 reason = e.args[1]
137 137
138 138 if reason == 'INVALID_CERTIFICATE':
139 139 log.exception(
140 140 'Exception creating a repository: invalid certificate')
141 141 msg = (_('Error creating repository %s: invalid certificate')
142 142 % repo_name)
143 143 else:
144 144 log.exception("Exception creating a repository")
145 145 msg = (_('Error creating repository %s')
146 146 % repo_name)
147 147 return msg
148 148
149 149 def _get_local_tmpl_context(self, include_app_defaults=False):
150 150 c = TemplateArgs()
151 151 c.auth_user = self.request.user
152 152 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
153 153 c.rhodecode_user = self.request.user
154 154
155 155 if include_app_defaults:
156 156 # NOTE(marcink): after full pyramid migration include_app_defaults
157 157 # should be turned on by default
158 158 from rhodecode.lib.base import attach_context_attributes
159 159 attach_context_attributes(c, self.request, self.request.user.user_id)
160 160
161 161 return c
162 162
163 163 def _register_global_c(self, tmpl_args):
164 164 """
165 165 Registers attributes to pylons global `c`
166 166 """
167 167
168 168 # TODO(marcink): remove once pyramid migration is finished
169 169 from pylons import tmpl_context as c
170 170 try:
171 171 for k, v in tmpl_args.items():
172 172 setattr(c, k, v)
173 173 except TypeError:
174 174 log.exception('Failed to register pylons C')
175 175 pass
176 176
177 177 def _get_template_context(self, tmpl_args):
178 178 self._register_global_c(tmpl_args)
179 179
180 180 local_tmpl_args = {
181 181 'defaults': {},
182 182 'errors': {},
183 183 # register a fake 'c' to be used in templates instead of global
184 184 # pylons c, after migration to pyramid we should rename it to 'c'
185 185 # make sure we replace usage of _c in templates too
186 186 '_c': tmpl_args
187 187 }
188 188 local_tmpl_args.update(tmpl_args)
189 189 return local_tmpl_args
190 190
191 191 def load_default_context(self):
192 192 """
193 193 example:
194 194
195 195 def load_default_context(self):
196 196 c = self._get_local_tmpl_context()
197 197 c.custom_var = 'foobar'
198 198 self._register_global_c(c)
199 199 return c
200 200 """
201 201 raise NotImplementedError('Needs implementation in view class')
202 202
203 203
204 204 class RepoAppView(BaseAppView):
205 205
206 206 def __init__(self, context, request):
207 207 super(RepoAppView, self).__init__(context, request)
208 208 self.db_repo = request.db_repo
209 209 self.db_repo_name = self.db_repo.repo_name
210 210 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
211 211
212 212 def _handle_missing_requirements(self, error):
213 213 log.error(
214 214 'Requirements are missing for repository %s: %s',
215 215 self.db_repo_name, error.message)
216 216
217 217 def _get_local_tmpl_context(self, include_app_defaults=False):
218 218 _ = self.request.translate
219 219 c = super(RepoAppView, self)._get_local_tmpl_context(
220 220 include_app_defaults=include_app_defaults)
221 221
222 222 # register common vars for this type of view
223 223 c.rhodecode_db_repo = self.db_repo
224 224 c.repo_name = self.db_repo_name
225 225 c.repository_pull_requests = self.db_repo_pull_requests
226 226
227 227 c.repository_requirements_missing = False
228 228 try:
229 229 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
230 230 except RepositoryRequirementError as e:
231 231 c.repository_requirements_missing = True
232 232 self._handle_missing_requirements(e)
233 233 self.rhodecode_vcs_repo = None
234 234
235 235 if (not c.repository_requirements_missing
236 236 and self.rhodecode_vcs_repo is None):
237 237 # unable to fetch this repo as vcs instance, report back to user
238 238 h.flash(_(
239 239 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
240 240 "Please check if it exist, or is not damaged.") %
241 241 {'repo_name': c.repo_name},
242 242 category='error', ignore_duplicate=True)
243 243 raise HTTPFound(h.route_path('home'))
244 244
245 245 return c
246 246
247 247 def _get_f_path(self, matchdict, default=None):
248 248 f_path = matchdict.get('f_path')
249 249 if f_path:
250 250 # fix for multiple initial slashes that causes errors for GIT
251 251 return f_path.lstrip('/')
252 252
253 253 return default
254 254
255 255
256 256 class RepoGroupAppView(BaseAppView):
257 257 def __init__(self, context, request):
258 258 super(RepoGroupAppView, self).__init__(context, request)
259 259 self.db_repo_group = request.db_repo_group
260 260 self.db_repo_group_name = self.db_repo_group.group_name
261 261
262 262
263 263 class UserGroupAppView(BaseAppView):
264 264 def __init__(self, context, request):
265 265 super(UserGroupAppView, self).__init__(context, request)
266 266 self.db_user_group = request.db_user_group
267 267 self.db_user_group_name = self.db_user_group.users_group_name
268 268
269 269
270 270 class DataGridAppView(object):
271 271 """
272 272 Common class to have re-usable grid rendering components
273 273 """
274 274
275 275 def _extract_ordering(self, request, column_map=None):
276 276 column_map = column_map or {}
277 277 column_index = safe_int(request.GET.get('order[0][column]'))
278 278 order_dir = request.GET.get(
279 279 'order[0][dir]', 'desc')
280 280 order_by = request.GET.get(
281 281 'columns[%s][data][sort]' % column_index, 'name_raw')
282 282
283 283 # translate datatable to DB columns
284 284 order_by = column_map.get(order_by) or order_by
285 285
286 286 search_q = request.GET.get('search[value]')
287 287 return search_q, order_by, order_dir
288 288
289 289 def _extract_chunk(self, request):
290 290 start = safe_int(request.GET.get('start'), 0)
291 291 length = safe_int(request.GET.get('length'), 25)
292 292 draw = safe_int(request.GET.get('draw'))
293 293 return draw, start, length
294 294
295 295 def _get_order_col(self, order_by, model):
296 296 if isinstance(order_by, basestring):
297 297 try:
298 298 return operator.attrgetter(order_by)(model)
299 299 except AttributeError:
300 300 return None
301 301 else:
302 302 return order_by
303 303
304 304
305 305 class BaseReferencesView(RepoAppView):
306 306 """
307 307 Base for reference view for branches, tags and bookmarks.
308 308 """
309 309 def load_default_context(self):
310 310 c = self._get_local_tmpl_context()
311 311
312 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
313 c.repo_info = self.db_repo
314
315 312 self._register_global_c(c)
316 313 return c
317 314
318 315 def load_refs_context(self, ref_items, partials_template):
319 316 _render = self.request.get_partial_renderer(partials_template)
320 317 pre_load = ["author", "date", "message"]
321 318
322 319 is_svn = h.is_svn(self.rhodecode_vcs_repo)
323 320 is_hg = h.is_hg(self.rhodecode_vcs_repo)
324 321
325 322 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
326 323
327 324 closed_refs = {}
328 325 if is_hg:
329 326 closed_refs = self.rhodecode_vcs_repo.branches_closed
330 327
331 328 data = []
332 329 for ref_name, commit_id in ref_items:
333 330 commit = self.rhodecode_vcs_repo.get_commit(
334 331 commit_id=commit_id, pre_load=pre_load)
335 332 closed = ref_name in closed_refs
336 333
337 334 # TODO: johbo: Unify generation of reference links
338 335 use_commit_id = '/' in ref_name or is_svn
339 336
340 337 if use_commit_id:
341 338 files_url = h.route_path(
342 339 'repo_files',
343 340 repo_name=self.db_repo_name,
344 341 f_path=ref_name if is_svn else '',
345 342 commit_id=commit_id)
346 343
347 344 else:
348 345 files_url = h.route_path(
349 346 'repo_files',
350 347 repo_name=self.db_repo_name,
351 348 f_path=ref_name if is_svn else '',
352 349 commit_id=ref_name,
353 350 _query=dict(at=ref_name))
354 351
355 352 data.append({
356 353 "name": _render('name', ref_name, files_url, closed),
357 354 "name_raw": ref_name,
358 355 "date": _render('date', commit.date),
359 356 "date_raw": datetime_to_time(commit.date),
360 357 "author": _render('author', commit.author),
361 358 "commit": _render(
362 359 'commit', commit.message, commit.raw_id, commit.idx),
363 360 "commit_raw": commit.idx,
364 361 "compare": _render(
365 362 'compare', format_ref_id(ref_name, commit.raw_id)),
366 363 })
367 364
368 365 return data
369 366
370 367
371 368 class RepoRoutePredicate(object):
372 369 def __init__(self, val, config):
373 370 self.val = val
374 371
375 372 def text(self):
376 373 return 'repo_route = %s' % self.val
377 374
378 375 phash = text
379 376
380 377 def __call__(self, info, request):
381 378
382 379 if hasattr(request, 'vcs_call'):
383 380 # skip vcs calls
384 381 return
385 382
386 383 repo_name = info['match']['repo_name']
387 384 repo_model = repo.RepoModel()
388 385 by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
389 386
390 387 def redirect_if_creating(db_repo):
391 388 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
392 389 raise HTTPFound(
393 390 request.route_path('repo_creating',
394 391 repo_name=db_repo.repo_name))
395 392
396 393 if by_name_match:
397 394 # register this as request object we can re-use later
398 395 request.db_repo = by_name_match
399 396 redirect_if_creating(by_name_match)
400 397 return True
401 398
402 399 by_id_match = repo_model.get_repo_by_id(repo_name)
403 400 if by_id_match:
404 401 request.db_repo = by_id_match
405 402 redirect_if_creating(by_id_match)
406 403 return True
407 404
408 405 return False
409 406
410 407
411 408 class RepoTypeRoutePredicate(object):
412 409 def __init__(self, val, config):
413 410 self.val = val or ['hg', 'git', 'svn']
414 411
415 412 def text(self):
416 413 return 'repo_accepted_type = %s' % self.val
417 414
418 415 phash = text
419 416
420 417 def __call__(self, info, request):
421 418 if hasattr(request, 'vcs_call'):
422 419 # skip vcs calls
423 420 return
424 421
425 422 rhodecode_db_repo = request.db_repo
426 423
427 424 log.debug(
428 425 '%s checking repo type for %s in %s',
429 426 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
430 427
431 428 if rhodecode_db_repo.repo_type in self.val:
432 429 return True
433 430 else:
434 431 log.warning('Current view is not supported for repo type:%s',
435 432 rhodecode_db_repo.repo_type)
436 433 #
437 434 # h.flash(h.literal(
438 435 # _('Action not supported for %s.' % rhodecode_repo.alias)),
439 436 # category='warning')
440 437 # return redirect(
441 438 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
442 439
443 440 return False
444 441
445 442
446 443 class RepoGroupRoutePredicate(object):
447 444 def __init__(self, val, config):
448 445 self.val = val
449 446
450 447 def text(self):
451 448 return 'repo_group_route = %s' % self.val
452 449
453 450 phash = text
454 451
455 452 def __call__(self, info, request):
456 453 if hasattr(request, 'vcs_call'):
457 454 # skip vcs calls
458 455 return
459 456
460 457 repo_group_name = info['match']['repo_group_name']
461 458 repo_group_model = repo_group.RepoGroupModel()
462 459 by_name_match = repo_group_model.get_by_group_name(
463 460 repo_group_name, cache=True)
464 461
465 462 if by_name_match:
466 463 # register this as request object we can re-use later
467 464 request.db_repo_group = by_name_match
468 465 return True
469 466
470 467 return False
471 468
472 469
473 470 class UserGroupRoutePredicate(object):
474 471 def __init__(self, val, config):
475 472 self.val = val
476 473
477 474 def text(self):
478 475 return 'user_group_route = %s' % self.val
479 476
480 477 phash = text
481 478
482 479 def __call__(self, info, request):
483 480 if hasattr(request, 'vcs_call'):
484 481 # skip vcs calls
485 482 return
486 483
487 484 user_group_id = info['match']['user_group_id']
488 485 user_group_model = user_group.UserGroup()
489 486 by_name_match = user_group_model.get(
490 487 user_group_id, cache=True)
491 488
492 489 if by_name_match:
493 490 # register this as request object we can re-use later
494 491 request.db_user_group = by_name_match
495 492 return True
496 493
497 494 return False
498 495
499 496
500 497 def includeme(config):
501 498 config.add_route_predicate(
502 499 'repo_route', RepoRoutePredicate)
503 500 config.add_route_predicate(
504 501 'repo_accepted_types', RepoTypeRoutePredicate)
505 502 config.add_route_predicate(
506 503 'repo_group_route', RepoGroupRoutePredicate)
507 504 config.add_route_predicate(
508 505 'user_group_route', UserGroupRoutePredicate)
@@ -1,78 +1,75 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 import logging
22 22
23 23 from pyramid.httpexceptions import HTTPFound
24 24 from pyramid.view import view_config
25 25
26 26 from rhodecode.apps._base import RepoAppView
27 27 from rhodecode.lib.auth import (
28 28 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
29 29 from rhodecode.lib import helpers as h
30 30 from rhodecode.model.meta import Session
31 31 from rhodecode.model.scm import ScmModel
32 32
33 33 log = logging.getLogger(__name__)
34 34
35 35
36 36 class RepoCachesView(RepoAppView):
37 37 def load_default_context(self):
38 38 c = self._get_local_tmpl_context()
39 39
40 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
41 c.repo_info = self.db_repo
42
43 40 self._register_global_c(c)
44 41 return c
45 42
46 43 @LoginRequired()
47 44 @HasRepoPermissionAnyDecorator('repository.admin')
48 45 @view_config(
49 46 route_name='edit_repo_caches', request_method='GET',
50 47 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
51 48 def repo_caches(self):
52 49 c = self.load_default_context()
53 50 c.active = 'caches'
54 51
55 52 return self._get_template_context(c)
56 53
57 54 @LoginRequired()
58 55 @HasRepoPermissionAnyDecorator('repository.admin')
59 56 @CSRFRequired()
60 57 @view_config(
61 58 route_name='edit_repo_caches', request_method='POST')
62 59 def repo_caches_purge(self):
63 60 _ = self.request.translate
64 61 c = self.load_default_context()
65 62 c.active = 'caches'
66 63
67 64 try:
68 65 ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
69 66 Session().commit()
70 67 h.flash(_('Cache invalidation successful'),
71 68 category='success')
72 69 except Exception:
73 70 log.exception("Exception during cache invalidation")
74 71 h.flash(_('An error occurred during cache invalidation'),
75 72 category='error')
76 73
77 74 raise HTTPFound(h.route_path(
78 75 'edit_repo_caches', repo_name=self.db_repo_name)) No newline at end of file
@@ -1,302 +1,299 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 import logging
23 23
24 24 from pyramid.httpexceptions import HTTPNotFound, HTTPFound
25 25 from pyramid.view import view_config
26 26 from pyramid.renderers import render
27 27 from pyramid.response import Response
28 28
29 29 from rhodecode.apps._base import RepoAppView
30 30 import rhodecode.lib.helpers as h
31 31 from rhodecode.lib.auth import (
32 32 LoginRequired, HasRepoPermissionAnyDecorator)
33 33
34 34 from rhodecode.lib.ext_json import json
35 35 from rhodecode.lib.graphmod import _colored, _dagwalker
36 36 from rhodecode.lib.helpers import RepoPage
37 37 from rhodecode.lib.utils2 import safe_int, safe_str
38 38 from rhodecode.lib.vcs.exceptions import (
39 39 RepositoryError, CommitDoesNotExistError,
40 40 CommitError, NodeDoesNotExistError, EmptyRepositoryError)
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44 DEFAULT_CHANGELOG_SIZE = 20
45 45
46 46
47 47 class RepoChangelogView(RepoAppView):
48 48
49 49 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
50 50 """
51 51 This is a safe way to get commit. If an error occurs it redirects to
52 52 tip with proper message
53 53
54 54 :param commit_id: id of commit to fetch
55 55 :param redirect_after: toggle redirection
56 56 """
57 57 _ = self.request.translate
58 58
59 59 try:
60 60 return self.rhodecode_vcs_repo.get_commit(commit_id)
61 61 except EmptyRepositoryError:
62 62 if not redirect_after:
63 63 return None
64 64
65 65 h.flash(h.literal(
66 66 _('There are no commits yet')), category='warning')
67 67 raise HTTPFound(
68 68 h.route_path('repo_summary', repo_name=self.db_repo_name))
69 69
70 70 except (CommitDoesNotExistError, LookupError):
71 71 msg = _('No such commit exists for this repository')
72 72 h.flash(msg, category='error')
73 73 raise HTTPNotFound()
74 74 except RepositoryError as e:
75 75 h.flash(safe_str(h.escape(e)), category='error')
76 76 raise HTTPNotFound()
77 77
78 78 def _graph(self, repo, commits, prev_data=None, next_data=None):
79 79 """
80 80 Generates a DAG graph for repo
81 81
82 82 :param repo: repo instance
83 83 :param commits: list of commits
84 84 """
85 85 if not commits:
86 86 return json.dumps([])
87 87
88 88 def serialize(commit, parents=True):
89 89 data = dict(
90 90 raw_id=commit.raw_id,
91 91 idx=commit.idx,
92 92 branch=commit.branch,
93 93 )
94 94 if parents:
95 95 data['parents'] = [
96 96 serialize(x, parents=False) for x in commit.parents]
97 97 return data
98 98
99 99 prev_data = prev_data or []
100 100 next_data = next_data or []
101 101
102 102 current = [serialize(x) for x in commits]
103 103 commits = prev_data + current + next_data
104 104
105 105 dag = _dagwalker(repo, commits)
106 106
107 107 data = [[commit_id, vtx, edges, branch]
108 108 for commit_id, vtx, edges, branch in _colored(dag)]
109 109 return json.dumps(data), json.dumps(current)
110 110
111 111 def _check_if_valid_branch(self, branch_name, repo_name, f_path):
112 112 if branch_name not in self.rhodecode_vcs_repo.branches_all:
113 113 h.flash('Branch {} is not found.'.format(h.escape(branch_name)),
114 114 category='warning')
115 115 redirect_url = h.route_path(
116 116 'repo_changelog_file', repo_name=repo_name,
117 117 commit_id=branch_name, f_path=f_path or '')
118 118 raise HTTPFound(redirect_url)
119 119
120 120 def _load_changelog_data(
121 121 self, c, collection, page, chunk_size, branch_name=None,
122 122 dynamic=False):
123 123
124 124 def url_generator(**kw):
125 125 query_params = {}
126 126 query_params.update(kw)
127 127 return h.route_path(
128 128 'repo_changelog',
129 129 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
130 130
131 131 c.total_cs = len(collection)
132 132 c.showing_commits = min(chunk_size, c.total_cs)
133 133 c.pagination = RepoPage(collection, page=page, item_count=c.total_cs,
134 134 items_per_page=chunk_size, branch=branch_name,
135 135 url=url_generator)
136 136
137 137 c.next_page = c.pagination.next_page
138 138 c.prev_page = c.pagination.previous_page
139 139
140 140 if dynamic:
141 141 if self.request.GET.get('chunk') != 'next':
142 142 c.next_page = None
143 143 if self.request.GET.get('chunk') != 'prev':
144 144 c.prev_page = None
145 145
146 146 page_commit_ids = [x.raw_id for x in c.pagination]
147 147 c.comments = c.rhodecode_db_repo.get_comments(page_commit_ids)
148 148 c.statuses = c.rhodecode_db_repo.statuses(page_commit_ids)
149 149
150 150 def load_default_context(self):
151 151 c = self._get_local_tmpl_context(include_app_defaults=True)
152 152
153 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
154 c.repo_info = self.db_repo
155 153 c.rhodecode_repo = self.rhodecode_vcs_repo
156
157 154 self._register_global_c(c)
158 155 return c
159 156
160 157 @LoginRequired()
161 158 @HasRepoPermissionAnyDecorator(
162 159 'repository.read', 'repository.write', 'repository.admin')
163 160 @view_config(
164 161 route_name='repo_changelog', request_method='GET',
165 162 renderer='rhodecode:templates/changelog/changelog.mako')
166 163 @view_config(
167 164 route_name='repo_changelog_file', request_method='GET',
168 165 renderer='rhodecode:templates/changelog/changelog.mako')
169 166 def repo_changelog(self):
170 167 c = self.load_default_context()
171 168
172 169 commit_id = self.request.matchdict.get('commit_id')
173 170 f_path = self._get_f_path(self.request.matchdict)
174 171
175 172 chunk_size = 20
176 173
177 174 c.branch_name = branch_name = self.request.GET.get('branch') or ''
178 175 c.book_name = book_name = self.request.GET.get('bookmark') or ''
179 176 hist_limit = safe_int(self.request.GET.get('limit')) or None
180 177
181 178 p = safe_int(self.request.GET.get('page', 1), 1)
182 179
183 180 c.selected_name = branch_name or book_name
184 181 if not commit_id and branch_name:
185 182 self._check_if_valid_branch(branch_name, self.db_repo_name, f_path)
186 183
187 184 c.changelog_for_path = f_path
188 185 pre_load = ['author', 'branch', 'date', 'message', 'parents']
189 186 commit_ids = []
190 187
191 188 partial_xhr = self.request.environ.get('HTTP_X_PARTIAL_XHR')
192 189
193 190 try:
194 191 if f_path:
195 192 log.debug('generating changelog for path %s', f_path)
196 193 # get the history for the file !
197 194 base_commit = self.rhodecode_vcs_repo.get_commit(commit_id)
198 195 try:
199 196 collection = base_commit.get_file_history(
200 197 f_path, limit=hist_limit, pre_load=pre_load)
201 198 if collection and partial_xhr:
202 199 # for ajax call we remove first one since we're looking
203 200 # at it right now in the context of a file commit
204 201 collection.pop(0)
205 202 except (NodeDoesNotExistError, CommitError):
206 203 # this node is not present at tip!
207 204 try:
208 205 commit = self._get_commit_or_redirect(commit_id)
209 206 collection = commit.get_file_history(f_path)
210 207 except RepositoryError as e:
211 208 h.flash(safe_str(e), category='warning')
212 209 redirect_url = h.route_path(
213 210 'repo_changelog', repo_name=self.db_repo_name)
214 211 raise HTTPFound(redirect_url)
215 212 collection = list(reversed(collection))
216 213 else:
217 214 collection = self.rhodecode_vcs_repo.get_commits(
218 215 branch_name=branch_name, pre_load=pre_load)
219 216
220 217 self._load_changelog_data(
221 218 c, collection, p, chunk_size, c.branch_name, dynamic=f_path)
222 219
223 220 except EmptyRepositoryError as e:
224 221 h.flash(safe_str(h.escape(e)), category='warning')
225 222 raise HTTPFound(
226 223 h.route_path('repo_summary', repo_name=self.db_repo_name))
227 224 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
228 225 log.exception(safe_str(e))
229 226 h.flash(safe_str(h.escape(e)), category='error')
230 227 raise HTTPFound(
231 228 h.route_path('repo_changelog', repo_name=self.db_repo_name))
232 229
233 230 if partial_xhr or self.request.environ.get('HTTP_X_PJAX'):
234 231 # loading from ajax, we don't want the first result, it's popped
235 232 # in the code above
236 233 html = render(
237 234 'rhodecode:templates/changelog/changelog_file_history.mako',
238 235 self._get_template_context(c), self.request)
239 236 return Response(html)
240 237
241 238 if not f_path:
242 239 commit_ids = c.pagination
243 240
244 241 c.graph_data, c.graph_commits = self._graph(
245 242 self.rhodecode_vcs_repo, commit_ids)
246 243
247 244 return self._get_template_context(c)
248 245
249 246 @LoginRequired()
250 247 @HasRepoPermissionAnyDecorator(
251 248 'repository.read', 'repository.write', 'repository.admin')
252 249 @view_config(
253 250 route_name='repo_changelog_elements', request_method=('GET', 'POST'),
254 251 renderer='rhodecode:templates/changelog/changelog_elements.mako',
255 252 xhr=True)
256 253 def repo_changelog_elements(self):
257 254 c = self.load_default_context()
258 255 chunk_size = 20
259 256
260 257 def wrap_for_error(err):
261 258 html = '<tr>' \
262 259 '<td colspan="9" class="alert alert-error">ERROR: {}</td>' \
263 260 '</tr>'.format(err)
264 261 return Response(html)
265 262
266 263 c.branch_name = branch_name = self.request.GET.get('branch') or ''
267 264 c.book_name = book_name = self.request.GET.get('bookmark') or ''
268 265
269 266 c.selected_name = branch_name or book_name
270 267 if branch_name and branch_name not in self.rhodecode_vcs_repo.branches_all:
271 268 return wrap_for_error(
272 269 safe_str('Branch: {} is not valid'.format(branch_name)))
273 270
274 271 pre_load = ['author', 'branch', 'date', 'message', 'parents']
275 272 collection = self.rhodecode_vcs_repo.get_commits(
276 273 branch_name=branch_name, pre_load=pre_load)
277 274
278 275 p = safe_int(self.request.GET.get('page', 1), 1)
279 276 try:
280 277 self._load_changelog_data(
281 278 c, collection, p, chunk_size, dynamic=True)
282 279 except EmptyRepositoryError as e:
283 280 return wrap_for_error(safe_str(e))
284 281 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
285 282 log.exception('Failed to fetch commits')
286 283 return wrap_for_error(safe_str(e))
287 284
288 285 prev_data = None
289 286 next_data = None
290 287
291 288 prev_graph = json.loads(self.request.POST.get('graph', ''))
292 289
293 290 if self.request.GET.get('chunk') == 'prev':
294 291 next_data = prev_graph
295 292 elif self.request.GET.get('chunk') == 'next':
296 293 prev_data = prev_graph
297 294
298 295 c.graph_data, c.graph_commits = self._graph(
299 296 self.rhodecode_vcs_repo, c.pagination,
300 297 prev_data=prev_data, next_data=next_data)
301 298
302 299 return self._get_template_context(c)
@@ -1,557 +1,554 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 import logging
23 23 import collections
24 24
25 25 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
26 26 from pyramid.view import view_config
27 27 from pyramid.renderers import render
28 28 from pyramid.response import Response
29 29
30 30 from rhodecode.apps._base import RepoAppView
31 31
32 32 from rhodecode.lib import diffs, codeblocks
33 33 from rhodecode.lib.auth import (
34 34 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
35 35
36 36 from rhodecode.lib.compat import OrderedDict
37 37 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
38 38 import rhodecode.lib.helpers as h
39 39 from rhodecode.lib.utils2 import safe_unicode
40 40 from rhodecode.lib.vcs.backends.base import EmptyCommit
41 41 from rhodecode.lib.vcs.exceptions import (
42 42 RepositoryError, CommitDoesNotExistError, NodeDoesNotExistError)
43 43 from rhodecode.model.db import ChangesetComment, ChangesetStatus
44 44 from rhodecode.model.changeset_status import ChangesetStatusModel
45 45 from rhodecode.model.comment import CommentsModel
46 46 from rhodecode.model.meta import Session
47 47
48 48
49 49 log = logging.getLogger(__name__)
50 50
51 51
52 52 def _update_with_GET(params, request):
53 53 for k in ['diff1', 'diff2', 'diff']:
54 54 params[k] += request.GET.getall(k)
55 55
56 56
57 57 def get_ignore_ws(fid, request):
58 58 ig_ws_global = request.GET.get('ignorews')
59 59 ig_ws = filter(lambda k: k.startswith('WS'), request.GET.getall(fid))
60 60 if ig_ws:
61 61 try:
62 62 return int(ig_ws[0].split(':')[-1])
63 63 except Exception:
64 64 pass
65 65 return ig_ws_global
66 66
67 67
68 68 def _ignorews_url(request, fileid=None):
69 69 _ = request.translate
70 70 fileid = str(fileid) if fileid else None
71 71 params = collections.defaultdict(list)
72 72 _update_with_GET(params, request)
73 73 label = _('Show whitespace')
74 74 tooltiplbl = _('Show whitespace for all diffs')
75 75 ig_ws = get_ignore_ws(fileid, request)
76 76 ln_ctx = get_line_ctx(fileid, request)
77 77
78 78 if ig_ws is None:
79 79 params['ignorews'] += [1]
80 80 label = _('Ignore whitespace')
81 81 tooltiplbl = _('Ignore whitespace for all diffs')
82 82 ctx_key = 'context'
83 83 ctx_val = ln_ctx
84 84
85 85 # if we have passed in ln_ctx pass it along to our params
86 86 if ln_ctx:
87 87 params[ctx_key] += [ctx_val]
88 88
89 89 if fileid:
90 90 params['anchor'] = 'a_' + fileid
91 91 return h.link_to(label, request.current_route_path(_query=params),
92 92 title=tooltiplbl, class_='tooltip')
93 93
94 94
95 95 def get_line_ctx(fid, request):
96 96 ln_ctx_global = request.GET.get('context')
97 97 if fid:
98 98 ln_ctx = filter(lambda k: k.startswith('C'), request.GET.getall(fid))
99 99 else:
100 100 _ln_ctx = filter(lambda k: k.startswith('C'), request.GET)
101 101 ln_ctx = request.GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global
102 102 if ln_ctx:
103 103 ln_ctx = [ln_ctx]
104 104
105 105 if ln_ctx:
106 106 retval = ln_ctx[0].split(':')[-1]
107 107 else:
108 108 retval = ln_ctx_global
109 109
110 110 try:
111 111 return int(retval)
112 112 except Exception:
113 113 return 3
114 114
115 115
116 116 def _context_url(request, fileid=None):
117 117 """
118 118 Generates a url for context lines.
119 119
120 120 :param fileid:
121 121 """
122 122
123 123 _ = request.translate
124 124 fileid = str(fileid) if fileid else None
125 125 ig_ws = get_ignore_ws(fileid, request)
126 126 ln_ctx = (get_line_ctx(fileid, request) or 3) * 2
127 127
128 128 params = collections.defaultdict(list)
129 129 _update_with_GET(params, request)
130 130
131 131 if ln_ctx > 0:
132 132 params['context'] += [ln_ctx]
133 133
134 134 if ig_ws:
135 135 ig_ws_key = 'ignorews'
136 136 ig_ws_val = 1
137 137 params[ig_ws_key] += [ig_ws_val]
138 138
139 139 lbl = _('Increase context')
140 140 tooltiplbl = _('Increase context for all diffs')
141 141
142 142 if fileid:
143 143 params['anchor'] = 'a_' + fileid
144 144 return h.link_to(lbl, request.current_route_path(_query=params),
145 145 title=tooltiplbl, class_='tooltip')
146 146
147 147
148 148 class RepoCommitsView(RepoAppView):
149 149 def load_default_context(self):
150 150 c = self._get_local_tmpl_context(include_app_defaults=True)
151
152 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
153 c.repo_info = self.db_repo
154 151 c.rhodecode_repo = self.rhodecode_vcs_repo
155 152
156 153 self._register_global_c(c)
157 154 return c
158 155
159 156 def _commit(self, commit_id_range, method):
160 157 _ = self.request.translate
161 158 c = self.load_default_context()
162 159 c.ignorews_url = _ignorews_url
163 160 c.context_url = _context_url
164 161 c.fulldiff = self.request.GET.get('fulldiff')
165 162
166 163 # fetch global flags of ignore ws or context lines
167 164 context_lcl = get_line_ctx('', self.request)
168 165 ign_whitespace_lcl = get_ignore_ws('', self.request)
169 166
170 167 # diff_limit will cut off the whole diff if the limit is applied
171 168 # otherwise it will just hide the big files from the front-end
172 169 diff_limit = c.visual.cut_off_limit_diff
173 170 file_limit = c.visual.cut_off_limit_file
174 171
175 172 # get ranges of commit ids if preset
176 173 commit_range = commit_id_range.split('...')[:2]
177 174
178 175 try:
179 176 pre_load = ['affected_files', 'author', 'branch', 'date',
180 177 'message', 'parents']
181 178
182 179 if len(commit_range) == 2:
183 180 commits = self.rhodecode_vcs_repo.get_commits(
184 181 start_id=commit_range[0], end_id=commit_range[1],
185 182 pre_load=pre_load)
186 183 commits = list(commits)
187 184 else:
188 185 commits = [self.rhodecode_vcs_repo.get_commit(
189 186 commit_id=commit_id_range, pre_load=pre_load)]
190 187
191 188 c.commit_ranges = commits
192 189 if not c.commit_ranges:
193 190 raise RepositoryError(
194 191 'The commit range returned an empty result')
195 192 except CommitDoesNotExistError:
196 193 msg = _('No such commit exists for this repository')
197 194 h.flash(msg, category='error')
198 195 raise HTTPNotFound()
199 196 except Exception:
200 197 log.exception("General failure")
201 198 raise HTTPNotFound()
202 199
203 200 c.changes = OrderedDict()
204 201 c.lines_added = 0
205 202 c.lines_deleted = 0
206 203
207 204 # auto collapse if we have more than limit
208 205 collapse_limit = diffs.DiffProcessor._collapse_commits_over
209 206 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
210 207
211 208 c.commit_statuses = ChangesetStatus.STATUSES
212 209 c.inline_comments = []
213 210 c.files = []
214 211
215 212 c.statuses = []
216 213 c.comments = []
217 214 c.unresolved_comments = []
218 215 if len(c.commit_ranges) == 1:
219 216 commit = c.commit_ranges[0]
220 217 c.comments = CommentsModel().get_comments(
221 218 self.db_repo.repo_id,
222 219 revision=commit.raw_id)
223 220 c.statuses.append(ChangesetStatusModel().get_status(
224 221 self.db_repo.repo_id, commit.raw_id))
225 222 # comments from PR
226 223 statuses = ChangesetStatusModel().get_statuses(
227 224 self.db_repo.repo_id, commit.raw_id,
228 225 with_revisions=True)
229 226 prs = set(st.pull_request for st in statuses
230 227 if st.pull_request is not None)
231 228 # from associated statuses, check the pull requests, and
232 229 # show comments from them
233 230 for pr in prs:
234 231 c.comments.extend(pr.comments)
235 232
236 233 c.unresolved_comments = CommentsModel()\
237 234 .get_commit_unresolved_todos(commit.raw_id)
238 235
239 236 diff = None
240 237 # Iterate over ranges (default commit view is always one commit)
241 238 for commit in c.commit_ranges:
242 239 c.changes[commit.raw_id] = []
243 240
244 241 commit2 = commit
245 242 commit1 = commit.parents[0] if commit.parents else EmptyCommit()
246 243
247 244 _diff = self.rhodecode_vcs_repo.get_diff(
248 245 commit1, commit2,
249 246 ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
250 247 diff_processor = diffs.DiffProcessor(
251 248 _diff, format='newdiff', diff_limit=diff_limit,
252 249 file_limit=file_limit, show_full_diff=c.fulldiff)
253 250
254 251 commit_changes = OrderedDict()
255 252 if method == 'show':
256 253 _parsed = diff_processor.prepare()
257 254 c.limited_diff = isinstance(_parsed, diffs.LimitedDiffContainer)
258 255
259 256 _parsed = diff_processor.prepare()
260 257
261 258 def _node_getter(commit):
262 259 def get_node(fname):
263 260 try:
264 261 return commit.get_node(fname)
265 262 except NodeDoesNotExistError:
266 263 return None
267 264 return get_node
268 265
269 266 inline_comments = CommentsModel().get_inline_comments(
270 267 self.db_repo.repo_id, revision=commit.raw_id)
271 268 c.inline_cnt = CommentsModel().get_inline_comments_count(
272 269 inline_comments)
273 270
274 271 diffset = codeblocks.DiffSet(
275 272 repo_name=self.db_repo_name,
276 273 source_node_getter=_node_getter(commit1),
277 274 target_node_getter=_node_getter(commit2),
278 275 comments=inline_comments)
279 276 diffset = diffset.render_patchset(
280 277 _parsed, commit1.raw_id, commit2.raw_id)
281 278
282 279 c.changes[commit.raw_id] = diffset
283 280 else:
284 281 # downloads/raw we only need RAW diff nothing else
285 282 diff = diff_processor.as_raw()
286 283 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
287 284
288 285 # sort comments by how they were generated
289 286 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
290 287
291 288 if len(c.commit_ranges) == 1:
292 289 c.commit = c.commit_ranges[0]
293 290 c.parent_tmpl = ''.join(
294 291 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
295 292
296 293 if method == 'download':
297 294 response = Response(diff)
298 295 response.content_type = 'text/plain'
299 296 response.content_disposition = (
300 297 'attachment; filename=%s.diff' % commit_id_range[:12])
301 298 return response
302 299 elif method == 'patch':
303 300 c.diff = safe_unicode(diff)
304 301 patch = render(
305 302 'rhodecode:templates/changeset/patch_changeset.mako',
306 303 self._get_template_context(c), self.request)
307 304 response = Response(patch)
308 305 response.content_type = 'text/plain'
309 306 return response
310 307 elif method == 'raw':
311 308 response = Response(diff)
312 309 response.content_type = 'text/plain'
313 310 return response
314 311 elif method == 'show':
315 312 if len(c.commit_ranges) == 1:
316 313 html = render(
317 314 'rhodecode:templates/changeset/changeset.mako',
318 315 self._get_template_context(c), self.request)
319 316 return Response(html)
320 317 else:
321 318 c.ancestor = None
322 319 c.target_repo = self.db_repo
323 320 html = render(
324 321 'rhodecode:templates/changeset/changeset_range.mako',
325 322 self._get_template_context(c), self.request)
326 323 return Response(html)
327 324
328 325 raise HTTPBadRequest()
329 326
330 327 @LoginRequired()
331 328 @HasRepoPermissionAnyDecorator(
332 329 'repository.read', 'repository.write', 'repository.admin')
333 330 @view_config(
334 331 route_name='repo_commit', request_method='GET',
335 332 renderer=None)
336 333 def repo_commit_show(self):
337 334 commit_id = self.request.matchdict['commit_id']
338 335 return self._commit(commit_id, method='show')
339 336
340 337 @LoginRequired()
341 338 @HasRepoPermissionAnyDecorator(
342 339 'repository.read', 'repository.write', 'repository.admin')
343 340 @view_config(
344 341 route_name='repo_commit_raw', request_method='GET',
345 342 renderer=None)
346 343 @view_config(
347 344 route_name='repo_commit_raw_deprecated', request_method='GET',
348 345 renderer=None)
349 346 def repo_commit_raw(self):
350 347 commit_id = self.request.matchdict['commit_id']
351 348 return self._commit(commit_id, method='raw')
352 349
353 350 @LoginRequired()
354 351 @HasRepoPermissionAnyDecorator(
355 352 'repository.read', 'repository.write', 'repository.admin')
356 353 @view_config(
357 354 route_name='repo_commit_patch', request_method='GET',
358 355 renderer=None)
359 356 def repo_commit_patch(self):
360 357 commit_id = self.request.matchdict['commit_id']
361 358 return self._commit(commit_id, method='patch')
362 359
363 360 @LoginRequired()
364 361 @HasRepoPermissionAnyDecorator(
365 362 'repository.read', 'repository.write', 'repository.admin')
366 363 @view_config(
367 364 route_name='repo_commit_download', request_method='GET',
368 365 renderer=None)
369 366 def repo_commit_download(self):
370 367 commit_id = self.request.matchdict['commit_id']
371 368 return self._commit(commit_id, method='download')
372 369
373 370 @LoginRequired()
374 371 @NotAnonymous()
375 372 @HasRepoPermissionAnyDecorator(
376 373 'repository.read', 'repository.write', 'repository.admin')
377 374 @CSRFRequired()
378 375 @view_config(
379 376 route_name='repo_commit_comment_create', request_method='POST',
380 377 renderer='json_ext')
381 378 def repo_commit_comment_create(self):
382 379 _ = self.request.translate
383 380 commit_id = self.request.matchdict['commit_id']
384 381
385 382 c = self.load_default_context()
386 383 status = self.request.POST.get('changeset_status', None)
387 384 text = self.request.POST.get('text')
388 385 comment_type = self.request.POST.get('comment_type')
389 386 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
390 387
391 388 if status:
392 389 text = text or (_('Status change %(transition_icon)s %(status)s')
393 390 % {'transition_icon': '>',
394 391 'status': ChangesetStatus.get_status_lbl(status)})
395 392
396 393 multi_commit_ids = []
397 394 for _commit_id in self.request.POST.get('commit_ids', '').split(','):
398 395 if _commit_id not in ['', None, EmptyCommit.raw_id]:
399 396 if _commit_id not in multi_commit_ids:
400 397 multi_commit_ids.append(_commit_id)
401 398
402 399 commit_ids = multi_commit_ids or [commit_id]
403 400
404 401 comment = None
405 402 for current_id in filter(None, commit_ids):
406 403 comment = CommentsModel().create(
407 404 text=text,
408 405 repo=self.db_repo.repo_id,
409 406 user=self._rhodecode_db_user.user_id,
410 407 commit_id=current_id,
411 408 f_path=self.request.POST.get('f_path'),
412 409 line_no=self.request.POST.get('line'),
413 410 status_change=(ChangesetStatus.get_status_lbl(status)
414 411 if status else None),
415 412 status_change_type=status,
416 413 comment_type=comment_type,
417 414 resolves_comment_id=resolves_comment_id
418 415 )
419 416
420 417 # get status if set !
421 418 if status:
422 419 # if latest status was from pull request and it's closed
423 420 # disallow changing status !
424 421 # dont_allow_on_closed_pull_request = True !
425 422
426 423 try:
427 424 ChangesetStatusModel().set_status(
428 425 self.db_repo.repo_id,
429 426 status,
430 427 self._rhodecode_db_user.user_id,
431 428 comment,
432 429 revision=current_id,
433 430 dont_allow_on_closed_pull_request=True
434 431 )
435 432 except StatusChangeOnClosedPullRequestError:
436 433 msg = _('Changing the status of a commit associated with '
437 434 'a closed pull request is not allowed')
438 435 log.exception(msg)
439 436 h.flash(msg, category='warning')
440 437 raise HTTPFound(h.route_path(
441 438 'repo_commit', repo_name=self.db_repo_name,
442 439 commit_id=current_id))
443 440
444 441 # finalize, commit and redirect
445 442 Session().commit()
446 443
447 444 data = {
448 445 'target_id': h.safeid(h.safe_unicode(
449 446 self.request.POST.get('f_path'))),
450 447 }
451 448 if comment:
452 449 c.co = comment
453 450 rendered_comment = render(
454 451 'rhodecode:templates/changeset/changeset_comment_block.mako',
455 452 self._get_template_context(c), self.request)
456 453
457 454 data.update(comment.get_dict())
458 455 data.update({'rendered_text': rendered_comment})
459 456
460 457 return data
461 458
462 459 @LoginRequired()
463 460 @NotAnonymous()
464 461 @HasRepoPermissionAnyDecorator(
465 462 'repository.read', 'repository.write', 'repository.admin')
466 463 @CSRFRequired()
467 464 @view_config(
468 465 route_name='repo_commit_comment_preview', request_method='POST',
469 466 renderer='string', xhr=True)
470 467 def repo_commit_comment_preview(self):
471 468 # Technically a CSRF token is not needed as no state changes with this
472 469 # call. However, as this is a POST is better to have it, so automated
473 470 # tools don't flag it as potential CSRF.
474 471 # Post is required because the payload could be bigger than the maximum
475 472 # allowed by GET.
476 473
477 474 text = self.request.POST.get('text')
478 475 renderer = self.request.POST.get('renderer') or 'rst'
479 476 if text:
480 477 return h.render(text, renderer=renderer, mentions=True)
481 478 return ''
482 479
483 480 @LoginRequired()
484 481 @NotAnonymous()
485 482 @HasRepoPermissionAnyDecorator(
486 483 'repository.read', 'repository.write', 'repository.admin')
487 484 @CSRFRequired()
488 485 @view_config(
489 486 route_name='repo_commit_comment_delete', request_method='POST',
490 487 renderer='json_ext')
491 488 def repo_commit_comment_delete(self):
492 489 commit_id = self.request.matchdict['commit_id']
493 490 comment_id = self.request.matchdict['comment_id']
494 491
495 492 comment = ChangesetComment.get_or_404(comment_id)
496 493 if not comment:
497 494 log.debug('Comment with id:%s not found, skipping', comment_id)
498 495 # comment already deleted in another call probably
499 496 return True
500 497
501 498 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
502 499 super_admin = h.HasPermissionAny('hg.admin')()
503 500 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
504 501 is_repo_comment = comment.repo.repo_name == self.db_repo_name
505 502 comment_repo_admin = is_repo_admin and is_repo_comment
506 503
507 504 if super_admin or comment_owner or comment_repo_admin:
508 505 CommentsModel().delete(comment=comment, user=self._rhodecode_db_user)
509 506 Session().commit()
510 507 return True
511 508 else:
512 509 log.warning('No permissions for user %s to delete comment_id: %s',
513 510 self._rhodecode_db_user, comment_id)
514 511 raise HTTPNotFound()
515 512
516 513 @LoginRequired()
517 514 @HasRepoPermissionAnyDecorator(
518 515 'repository.read', 'repository.write', 'repository.admin')
519 516 @view_config(
520 517 route_name='repo_commit_data', request_method='GET',
521 518 renderer='json_ext', xhr=True)
522 519 def repo_commit_data(self):
523 520 commit_id = self.request.matchdict['commit_id']
524 521 self.load_default_context()
525 522
526 523 try:
527 524 return self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
528 525 except CommitDoesNotExistError as e:
529 526 return EmptyCommit(message=str(e))
530 527
531 528 @LoginRequired()
532 529 @HasRepoPermissionAnyDecorator(
533 530 'repository.read', 'repository.write', 'repository.admin')
534 531 @view_config(
535 532 route_name='repo_commit_children', request_method='GET',
536 533 renderer='json_ext', xhr=True)
537 534 def repo_commit_children(self):
538 535 commit_id = self.request.matchdict['commit_id']
539 536 self.load_default_context()
540 537
541 538 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
542 539 result = {"results": commit.children}
543 540 return result
544 541
545 542 @LoginRequired()
546 543 @HasRepoPermissionAnyDecorator(
547 544 'repository.read', 'repository.write', 'repository.admin')
548 545 @view_config(
549 546 route_name='repo_commit_parents', request_method='GET',
550 547 renderer='json_ext')
551 548 def repo_commit_parents(self):
552 549 commit_id = self.request.matchdict['commit_id']
553 550 self.load_default_context()
554 551
555 552 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
556 553 result = {"results": commit.parents}
557 554 return result
@@ -1,324 +1,322 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-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 import logging
23 23
24 24 from pyramid.httpexceptions import HTTPBadRequest, HTTPNotFound, HTTPFound
25 25 from pyramid.view import view_config
26 26 from pyramid.renderers import render
27 27 from pyramid.response import Response
28 28
29 29 from rhodecode.apps._base import RepoAppView
30 30 from rhodecode.controllers.utils import parse_path_ref, get_commit_from_ref_name
31 31 from rhodecode.lib import helpers as h
32 32 from rhodecode.lib import diffs, codeblocks
33 33 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
34 34 from rhodecode.lib.utils import safe_str
35 35 from rhodecode.lib.utils2 import safe_unicode, str2bool
36 36 from rhodecode.lib.vcs.exceptions import (
37 37 EmptyRepositoryError, RepositoryError, RepositoryRequirementError,
38 38 NodeDoesNotExistError)
39 39 from rhodecode.model.db import Repository, ChangesetStatus
40 40
41 41 log = logging.getLogger(__name__)
42 42
43 43
44 44 class RepoCompareView(RepoAppView):
45 45 def load_default_context(self):
46 46 c = self._get_local_tmpl_context(include_app_defaults=True)
47 47
48 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
49 c.repo_info = self.db_repo
50 48 c.rhodecode_repo = self.rhodecode_vcs_repo
51 49
52 50 self._register_global_c(c)
53 51 return c
54 52
55 53 def _get_commit_or_redirect(
56 54 self, ref, ref_type, repo, redirect_after=True, partial=False):
57 55 """
58 56 This is a safe way to get a commit. If an error occurs it
59 57 redirects to a commit with a proper message. If partial is set
60 58 then it does not do redirect raise and throws an exception instead.
61 59 """
62 60 _ = self.request.translate
63 61 try:
64 62 return get_commit_from_ref_name(repo, safe_str(ref), ref_type)
65 63 except EmptyRepositoryError:
66 64 if not redirect_after:
67 65 return repo.scm_instance().EMPTY_COMMIT
68 66 h.flash(h.literal(_('There are no commits yet')),
69 67 category='warning')
70 68 if not partial:
71 69 raise HTTPFound(
72 70 h.route_path('repo_summary', repo_name=repo.repo_name))
73 71 raise HTTPBadRequest()
74 72
75 73 except RepositoryError as e:
76 74 log.exception(safe_str(e))
77 75 h.flash(safe_str(h.escape(e)), category='warning')
78 76 if not partial:
79 77 raise HTTPFound(
80 78 h.route_path('repo_summary', repo_name=repo.repo_name))
81 79 raise HTTPBadRequest()
82 80
83 81 @LoginRequired()
84 82 @HasRepoPermissionAnyDecorator(
85 83 'repository.read', 'repository.write', 'repository.admin')
86 84 @view_config(
87 85 route_name='repo_compare_select', request_method='GET',
88 86 renderer='rhodecode:templates/compare/compare_diff.mako')
89 87 def compare_select(self):
90 88 _ = self.request.translate
91 89 c = self.load_default_context()
92 90
93 91 source_repo = self.db_repo_name
94 92 target_repo = self.request.GET.get('target_repo', source_repo)
95 93 c.source_repo = Repository.get_by_repo_name(source_repo)
96 94 c.target_repo = Repository.get_by_repo_name(target_repo)
97 95
98 96 if c.source_repo is None or c.target_repo is None:
99 97 raise HTTPNotFound()
100 98
101 99 c.compare_home = True
102 100 c.commit_ranges = []
103 101 c.collapse_all_commits = False
104 102 c.diffset = None
105 103 c.limited_diff = False
106 104 c.source_ref = c.target_ref = _('Select commit')
107 105 c.source_ref_type = ""
108 106 c.target_ref_type = ""
109 107 c.commit_statuses = ChangesetStatus.STATUSES
110 108 c.preview_mode = False
111 109 c.file_path = None
112 110
113 111 return self._get_template_context(c)
114 112
115 113 @LoginRequired()
116 114 @HasRepoPermissionAnyDecorator(
117 115 'repository.read', 'repository.write', 'repository.admin')
118 116 @view_config(
119 117 route_name='repo_compare', request_method='GET',
120 118 renderer=None)
121 119 def compare(self):
122 120 _ = self.request.translate
123 121 c = self.load_default_context()
124 122
125 123 source_ref_type = self.request.matchdict['source_ref_type']
126 124 source_ref = self.request.matchdict['source_ref']
127 125 target_ref_type = self.request.matchdict['target_ref_type']
128 126 target_ref = self.request.matchdict['target_ref']
129 127
130 128 # source_ref will be evaluated in source_repo
131 129 source_repo_name = self.db_repo_name
132 130 source_path, source_id = parse_path_ref(source_ref)
133 131
134 132 # target_ref will be evaluated in target_repo
135 133 target_repo_name = self.request.GET.get('target_repo', source_repo_name)
136 134 target_path, target_id = parse_path_ref(
137 135 target_ref, default_path=self.request.GET.get('f_path', ''))
138 136
139 137 # if merge is True
140 138 # Show what changes since the shared ancestor commit of target/source
141 139 # the source would get if it was merged with target. Only commits
142 140 # which are in target but not in source will be shown.
143 141 merge = str2bool(self.request.GET.get('merge'))
144 142 # if merge is False
145 143 # Show a raw diff of source/target refs even if no ancestor exists
146 144
147 145 # c.fulldiff disables cut_off_limit
148 146 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
149 147
150 148 c.file_path = target_path
151 149 c.commit_statuses = ChangesetStatus.STATUSES
152 150
153 151 # if partial, returns just compare_commits.html (commits log)
154 152 partial = self.request.is_xhr
155 153
156 154 # swap url for compare_diff page
157 155 c.swap_url = h.route_path(
158 156 'repo_compare',
159 157 repo_name=target_repo_name,
160 158 source_ref_type=target_ref_type,
161 159 source_ref=target_ref,
162 160 target_repo=source_repo_name,
163 161 target_ref_type=source_ref_type,
164 162 target_ref=source_ref,
165 163 _query=dict(merge=merge and '1' or '', f_path=target_path))
166 164
167 165 source_repo = Repository.get_by_repo_name(source_repo_name)
168 166 target_repo = Repository.get_by_repo_name(target_repo_name)
169 167
170 168 if source_repo is None:
171 169 log.error('Could not find the source repo: {}'
172 170 .format(source_repo_name))
173 171 h.flash(_('Could not find the source repo: `{}`')
174 172 .format(h.escape(source_repo_name)), category='error')
175 173 raise HTTPFound(
176 174 h.route_path('repo_compare_select', repo_name=self.db_repo_name))
177 175
178 176 if target_repo is None:
179 177 log.error('Could not find the target repo: {}'
180 178 .format(source_repo_name))
181 179 h.flash(_('Could not find the target repo: `{}`')
182 180 .format(h.escape(target_repo_name)), category='error')
183 181 raise HTTPFound(
184 182 h.route_path('repo_compare_select', repo_name=self.db_repo_name))
185 183
186 184 source_scm = source_repo.scm_instance()
187 185 target_scm = target_repo.scm_instance()
188 186
189 187 source_alias = source_scm.alias
190 188 target_alias = target_scm.alias
191 189 if source_alias != target_alias:
192 190 msg = _('The comparison of two different kinds of remote repos '
193 191 'is not available')
194 192 log.error(msg)
195 193 h.flash(msg, category='error')
196 194 raise HTTPFound(
197 195 h.route_path('repo_compare_select', repo_name=self.db_repo_name))
198 196
199 197 source_commit = self._get_commit_or_redirect(
200 198 ref=source_id, ref_type=source_ref_type, repo=source_repo,
201 199 partial=partial)
202 200 target_commit = self._get_commit_or_redirect(
203 201 ref=target_id, ref_type=target_ref_type, repo=target_repo,
204 202 partial=partial)
205 203
206 204 c.compare_home = False
207 205 c.source_repo = source_repo
208 206 c.target_repo = target_repo
209 207 c.source_ref = source_ref
210 208 c.target_ref = target_ref
211 209 c.source_ref_type = source_ref_type
212 210 c.target_ref_type = target_ref_type
213 211
214 212 pre_load = ["author", "branch", "date", "message"]
215 213 c.ancestor = None
216 214
217 215 if c.file_path:
218 216 if source_commit == target_commit:
219 217 c.commit_ranges = []
220 218 else:
221 219 c.commit_ranges = [target_commit]
222 220 else:
223 221 try:
224 222 c.commit_ranges = source_scm.compare(
225 223 source_commit.raw_id, target_commit.raw_id,
226 224 target_scm, merge, pre_load=pre_load)
227 225 if merge:
228 226 c.ancestor = source_scm.get_common_ancestor(
229 227 source_commit.raw_id, target_commit.raw_id, target_scm)
230 228 except RepositoryRequirementError:
231 229 msg = _('Could not compare repos with different '
232 230 'large file settings')
233 231 log.error(msg)
234 232 if partial:
235 233 return Response(msg)
236 234 h.flash(msg, category='error')
237 235 raise HTTPFound(
238 236 h.route_path('repo_compare_select',
239 237 repo_name=self.db_repo_name))
240 238
241 239 c.statuses = self.db_repo.statuses(
242 240 [x.raw_id for x in c.commit_ranges])
243 241
244 242 # auto collapse if we have more than limit
245 243 collapse_limit = diffs.DiffProcessor._collapse_commits_over
246 244 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
247 245
248 246 if partial: # for PR ajax commits loader
249 247 if not c.ancestor:
250 248 return Response('') # cannot merge if there is no ancestor
251 249
252 250 html = render(
253 251 'rhodecode:templates/compare/compare_commits.mako',
254 252 self._get_template_context(c), self.request)
255 253 return Response(html)
256 254
257 255 if c.ancestor:
258 256 # case we want a simple diff without incoming commits,
259 257 # previewing what will be merged.
260 258 # Make the diff on target repo (which is known to have target_ref)
261 259 log.debug('Using ancestor %s as source_ref instead of %s'
262 260 % (c.ancestor, source_ref))
263 261 source_repo = target_repo
264 262 source_commit = target_repo.get_commit(commit_id=c.ancestor)
265 263
266 264 # diff_limit will cut off the whole diff if the limit is applied
267 265 # otherwise it will just hide the big files from the front-end
268 266 diff_limit = c.visual.cut_off_limit_diff
269 267 file_limit = c.visual.cut_off_limit_file
270 268
271 269 log.debug('calculating diff between '
272 270 'source_ref:%s and target_ref:%s for repo `%s`',
273 271 source_commit, target_commit,
274 272 safe_unicode(source_repo.scm_instance().path))
275 273
276 274 if source_commit.repository != target_commit.repository:
277 275 msg = _(
278 276 "Repositories unrelated. "
279 277 "Cannot compare commit %(commit1)s from repository %(repo1)s "
280 278 "with commit %(commit2)s from repository %(repo2)s.") % {
281 279 'commit1': h.show_id(source_commit),
282 280 'repo1': source_repo.repo_name,
283 281 'commit2': h.show_id(target_commit),
284 282 'repo2': target_repo.repo_name,
285 283 }
286 284 h.flash(msg, category='error')
287 285 raise HTTPFound(
288 286 h.route_path('repo_compare_select',
289 287 repo_name=self.db_repo_name))
290 288
291 289 txt_diff = source_repo.scm_instance().get_diff(
292 290 commit1=source_commit, commit2=target_commit,
293 291 path=target_path, path1=source_path)
294 292
295 293 diff_processor = diffs.DiffProcessor(
296 294 txt_diff, format='newdiff', diff_limit=diff_limit,
297 295 file_limit=file_limit, show_full_diff=c.fulldiff)
298 296 _parsed = diff_processor.prepare()
299 297
300 298 def _node_getter(commit):
301 299 """ Returns a function that returns a node for a commit or None """
302 300 def get_node(fname):
303 301 try:
304 302 return commit.get_node(fname)
305 303 except NodeDoesNotExistError:
306 304 return None
307 305 return get_node
308 306
309 307 diffset = codeblocks.DiffSet(
310 308 repo_name=source_repo.repo_name,
311 309 source_node_getter=_node_getter(source_commit),
312 310 target_node_getter=_node_getter(target_commit),
313 311 )
314 312 c.diffset = diffset.render_patchset(
315 313 _parsed, source_ref, target_ref)
316 314
317 315 c.preview_mode = merge
318 316 c.source_commit = source_commit
319 317 c.target_commit = target_commit
320 318
321 319 html = render(
322 320 'rhodecode:templates/compare/compare_diff.mako',
323 321 self._get_template_context(c), self.request)
324 322 return Response(html) No newline at end of file
@@ -1,207 +1,204 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2017-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 pytz
22 22 import logging
23 23
24 24 from beaker.cache import cache_region
25 25 from pyramid.view import view_config
26 26 from pyramid.response import Response
27 27 from webhelpers.feedgenerator import Rss201rev2Feed, Atom1Feed
28 28
29 29 from rhodecode.apps._base import RepoAppView
30 30 from rhodecode.lib import audit_logger
31 31 from rhodecode.lib import helpers as h
32 32 from rhodecode.lib.auth import (
33 33 LoginRequired, HasRepoPermissionAnyDecorator)
34 34 from rhodecode.lib.diffs import DiffProcessor, LimitedDiffContainer
35 35 from rhodecode.lib.utils2 import str2bool, safe_int, md5_safe
36 36 from rhodecode.model.db import UserApiKeys, CacheKey
37 37
38 38 log = logging.getLogger(__name__)
39 39
40 40
41 41 class RepoFeedView(RepoAppView):
42 42 def load_default_context(self):
43 43 c = self._get_local_tmpl_context()
44 44
45 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
46 c.repo_info = self.db_repo
47
48 45 self._register_global_c(c)
49 46 self._load_defaults()
50 47 return c
51 48
52 49 def _get_config(self):
53 50 import rhodecode
54 51 config = rhodecode.CONFIG
55 52
56 53 return {
57 54 'language': 'en-us',
58 55 'feed_ttl': '5', # TTL of feed,
59 56 'feed_include_diff':
60 57 str2bool(config.get('rss_include_diff', False)),
61 58 'feed_items_per_page':
62 59 safe_int(config.get('rss_items_per_page', 20)),
63 60 'feed_diff_limit':
64 61 # we need to protect from parsing huge diffs here other way
65 62 # we can kill the server
66 63 safe_int(config.get('rss_cut_off_limit', 32 * 1024)),
67 64 }
68 65
69 66 def _load_defaults(self):
70 67 _ = self.request.translate
71 68 config = self._get_config()
72 69 # common values for feeds
73 70 self.description = _('Changes on %s repository')
74 71 self.title = self.title = _('%s %s feed') % (self.db_repo_name, '%s')
75 72 self.language = config["language"]
76 73 self.ttl = config["feed_ttl"]
77 74 self.feed_include_diff = config['feed_include_diff']
78 75 self.feed_diff_limit = config['feed_diff_limit']
79 76 self.feed_items_per_page = config['feed_items_per_page']
80 77
81 78 def _changes(self, commit):
82 79 diff_processor = DiffProcessor(
83 80 commit.diff(), diff_limit=self.feed_diff_limit)
84 81 _parsed = diff_processor.prepare(inline_diff=False)
85 82 limited_diff = isinstance(_parsed, LimitedDiffContainer)
86 83
87 84 return _parsed, limited_diff
88 85
89 86 def _get_title(self, commit):
90 87 return h.shorter(commit.message, 160)
91 88
92 89 def _get_description(self, commit):
93 90 _renderer = self.request.get_partial_renderer(
94 91 'feed/atom_feed_entry.mako')
95 92 parsed_diff, limited_diff = self._changes(commit)
96 93 return _renderer(
97 94 'body',
98 95 commit=commit,
99 96 parsed_diff=parsed_diff,
100 97 limited_diff=limited_diff,
101 98 feed_include_diff=self.feed_include_diff,
102 99 )
103 100
104 101 def _set_timezone(self, date, tzinfo=pytz.utc):
105 102 if not getattr(date, "tzinfo", None):
106 103 date.replace(tzinfo=tzinfo)
107 104 return date
108 105
109 106 def _get_commits(self):
110 107 return list(self.rhodecode_vcs_repo[-self.feed_items_per_page:])
111 108
112 109 def uid(self, repo_id, commit_id):
113 110 return '{}:{}'.format(md5_safe(repo_id), md5_safe(commit_id))
114 111
115 112 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
116 113 @HasRepoPermissionAnyDecorator(
117 114 'repository.read', 'repository.write', 'repository.admin')
118 115 @view_config(
119 116 route_name='atom_feed_home', request_method='GET',
120 117 renderer=None)
121 118 def atom(self):
122 119 """
123 120 Produce an atom-1.0 feed via feedgenerator module
124 121 """
125 122 self.load_default_context()
126 123
127 124 @cache_region('long_term')
128 125 def _generate_feed(cache_key):
129 126 feed = Atom1Feed(
130 127 title=self.title % self.db_repo_name,
131 128 link=h.route_url('repo_summary', repo_name=self.db_repo_name),
132 129 description=self.description % self.db_repo_name,
133 130 language=self.language,
134 131 ttl=self.ttl
135 132 )
136 133
137 134 for commit in reversed(self._get_commits()):
138 135 date = self._set_timezone(commit.date)
139 136 feed.add_item(
140 137 unique_id=self.uid(self.db_repo.repo_id, commit.raw_id),
141 138 title=self._get_title(commit),
142 139 author_name=commit.author,
143 140 description=self._get_description(commit),
144 141 link=h.route_url(
145 142 'repo_commit', repo_name=self.db_repo_name,
146 143 commit_id=commit.raw_id),
147 144 pubdate=date,)
148 145
149 146 return feed.mime_type, feed.writeString('utf-8')
150 147
151 148 invalidator_context = CacheKey.repo_context_cache(
152 149 _generate_feed, self.db_repo_name, CacheKey.CACHE_TYPE_ATOM)
153 150
154 151 with invalidator_context as context:
155 152 context.invalidate()
156 153 mime_type, feed = context.compute()
157 154
158 155 response = Response(feed)
159 156 response.content_type = mime_type
160 157 return response
161 158
162 159 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
163 160 @HasRepoPermissionAnyDecorator(
164 161 'repository.read', 'repository.write', 'repository.admin')
165 162 @view_config(
166 163 route_name='rss_feed_home', request_method='GET',
167 164 renderer=None)
168 165 def rss(self):
169 166 """
170 167 Produce an rss2 feed via feedgenerator module
171 168 """
172 169 self.load_default_context()
173 170
174 171 @cache_region('long_term')
175 172 def _generate_feed(cache_key):
176 173 feed = Rss201rev2Feed(
177 174 title=self.title % self.db_repo_name,
178 175 link=h.route_url('repo_summary', repo_name=self.db_repo_name),
179 176 description=self.description % self.db_repo_name,
180 177 language=self.language,
181 178 ttl=self.ttl
182 179 )
183 180
184 181 for commit in reversed(self._get_commits()):
185 182 date = self._set_timezone(commit.date)
186 183 feed.add_item(
187 184 unique_id=self.uid(self.db_repo.repo_id, commit.raw_id),
188 185 title=self._get_title(commit),
189 186 author_name=commit.author,
190 187 description=self._get_description(commit),
191 188 link=h.route_url(
192 189 'repo_commit', repo_name=self.db_repo_name,
193 190 commit_id=commit.raw_id),
194 191 pubdate=date,)
195 192
196 193 return feed.mime_type, feed.writeString('utf-8')
197 194
198 195 invalidator_context = CacheKey.repo_context_cache(
199 196 _generate_feed, self.db_repo_name, CacheKey.CACHE_TYPE_RSS)
200 197
201 198 with invalidator_context as context:
202 199 context.invalidate()
203 200 mime_type, feed = context.compute()
204 201
205 202 response = Response(feed)
206 203 response.content_type = mime_type
207 204 return response
@@ -1,1284 +1,1282 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 import itertools
22 22 import logging
23 23 import os
24 24 import shutil
25 25 import tempfile
26 26 import collections
27 27
28 28 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
29 29 from pyramid.view import view_config
30 30 from pyramid.renderers import render
31 31 from pyramid.response import Response
32 32
33 33 from rhodecode.apps._base import RepoAppView
34 34
35 35 from rhodecode.controllers.utils import parse_path_ref
36 36 from rhodecode.lib import diffs, helpers as h, caches
37 37 from rhodecode.lib import audit_logger
38 38 from rhodecode.lib.exceptions import NonRelativePathError
39 39 from rhodecode.lib.codeblocks import (
40 40 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
41 41 from rhodecode.lib.utils2 import (
42 42 convert_line_endings, detect_mode, safe_str, str2bool)
43 43 from rhodecode.lib.auth import (
44 44 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
45 45 from rhodecode.lib.vcs import path as vcspath
46 46 from rhodecode.lib.vcs.backends.base import EmptyCommit
47 47 from rhodecode.lib.vcs.conf import settings
48 48 from rhodecode.lib.vcs.nodes import FileNode
49 49 from rhodecode.lib.vcs.exceptions import (
50 50 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
51 51 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
52 52 NodeDoesNotExistError, CommitError, NodeError)
53 53
54 54 from rhodecode.model.scm import ScmModel
55 55 from rhodecode.model.db import Repository
56 56
57 57 log = logging.getLogger(__name__)
58 58
59 59
60 60 class RepoFilesView(RepoAppView):
61 61
62 62 @staticmethod
63 63 def adjust_file_path_for_svn(f_path, repo):
64 64 """
65 65 Computes the relative path of `f_path`.
66 66
67 67 This is mainly based on prefix matching of the recognized tags and
68 68 branches in the underlying repository.
69 69 """
70 70 tags_and_branches = itertools.chain(
71 71 repo.branches.iterkeys(),
72 72 repo.tags.iterkeys())
73 73 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
74 74
75 75 for name in tags_and_branches:
76 76 if f_path.startswith('{}/'.format(name)):
77 77 f_path = vcspath.relpath(f_path, name)
78 78 break
79 79 return f_path
80 80
81 81 def load_default_context(self):
82 82 c = self._get_local_tmpl_context(include_app_defaults=True)
83 83
84 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
85 c.repo_info = self.db_repo
86 84 c.rhodecode_repo = self.rhodecode_vcs_repo
87 85
88 86 self._register_global_c(c)
89 87 return c
90 88
91 89 def _ensure_not_locked(self):
92 90 _ = self.request.translate
93 91
94 92 repo = self.db_repo
95 93 if repo.enable_locking and repo.locked[0]:
96 94 h.flash(_('This repository has been locked by %s on %s')
97 95 % (h.person_by_id(repo.locked[0]),
98 96 h.format_date(h.time_to_datetime(repo.locked[1]))),
99 97 'warning')
100 98 files_url = h.route_path(
101 99 'repo_files:default_path',
102 100 repo_name=self.db_repo_name, commit_id='tip')
103 101 raise HTTPFound(files_url)
104 102
105 103 def _get_commit_and_path(self):
106 104 default_commit_id = self.db_repo.landing_rev[1]
107 105 default_f_path = '/'
108 106
109 107 commit_id = self.request.matchdict.get(
110 108 'commit_id', default_commit_id)
111 109 f_path = self._get_f_path(self.request.matchdict, default_f_path)
112 110 return commit_id, f_path
113 111
114 112 def _get_default_encoding(self, c):
115 113 enc_list = getattr(c, 'default_encodings', [])
116 114 return enc_list[0] if enc_list else 'UTF-8'
117 115
118 116 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
119 117 """
120 118 This is a safe way to get commit. If an error occurs it redirects to
121 119 tip with proper message
122 120
123 121 :param commit_id: id of commit to fetch
124 122 :param redirect_after: toggle redirection
125 123 """
126 124 _ = self.request.translate
127 125
128 126 try:
129 127 return self.rhodecode_vcs_repo.get_commit(commit_id)
130 128 except EmptyRepositoryError:
131 129 if not redirect_after:
132 130 return None
133 131
134 132 _url = h.route_path(
135 133 'repo_files_add_file',
136 134 repo_name=self.db_repo_name, commit_id=0, f_path='',
137 135 _anchor='edit')
138 136
139 137 if h.HasRepoPermissionAny(
140 138 'repository.write', 'repository.admin')(self.db_repo_name):
141 139 add_new = h.link_to(
142 140 _('Click here to add a new file.'), _url, class_="alert-link")
143 141 else:
144 142 add_new = ""
145 143
146 144 h.flash(h.literal(
147 145 _('There are no files yet. %s') % add_new), category='warning')
148 146 raise HTTPFound(
149 147 h.route_path('repo_summary', repo_name=self.db_repo_name))
150 148
151 149 except (CommitDoesNotExistError, LookupError):
152 150 msg = _('No such commit exists for this repository')
153 151 h.flash(msg, category='error')
154 152 raise HTTPNotFound()
155 153 except RepositoryError as e:
156 154 h.flash(safe_str(h.escape(e)), category='error')
157 155 raise HTTPNotFound()
158 156
159 157 def _get_filenode_or_redirect(self, commit_obj, path):
160 158 """
161 159 Returns file_node, if error occurs or given path is directory,
162 160 it'll redirect to top level path
163 161 """
164 162 _ = self.request.translate
165 163
166 164 try:
167 165 file_node = commit_obj.get_node(path)
168 166 if file_node.is_dir():
169 167 raise RepositoryError('The given path is a directory')
170 168 except CommitDoesNotExistError:
171 169 log.exception('No such commit exists for this repository')
172 170 h.flash(_('No such commit exists for this repository'), category='error')
173 171 raise HTTPNotFound()
174 172 except RepositoryError as e:
175 173 log.warning('Repository error while fetching '
176 174 'filenode `%s`. Err:%s', path, e)
177 175 h.flash(safe_str(h.escape(e)), category='error')
178 176 raise HTTPNotFound()
179 177
180 178 return file_node
181 179
182 180 def _is_valid_head(self, commit_id, repo):
183 181 # check if commit is a branch identifier- basically we cannot
184 182 # create multiple heads via file editing
185 183 valid_heads = repo.branches.keys() + repo.branches.values()
186 184
187 185 if h.is_svn(repo) and not repo.is_empty():
188 186 # Note: Subversion only has one head, we add it here in case there
189 187 # is no branch matched.
190 188 valid_heads.append(repo.get_commit(commit_idx=-1).raw_id)
191 189
192 190 # check if commit is a branch name or branch hash
193 191 return commit_id in valid_heads
194 192
195 193 def _get_tree_cache_manager(self, namespace_type):
196 194 _namespace = caches.get_repo_namespace_key(
197 195 namespace_type, self.db_repo_name)
198 196 return caches.get_cache_manager('repo_cache_long', _namespace)
199 197
200 198 def _get_tree_at_commit(
201 199 self, c, commit_id, f_path, full_load=False, force=False):
202 200 def _cached_tree():
203 201 log.debug('Generating cached file tree for %s, %s, %s',
204 202 self.db_repo_name, commit_id, f_path)
205 203
206 204 c.full_load = full_load
207 205 return render(
208 206 'rhodecode:templates/files/files_browser_tree.mako',
209 207 self._get_template_context(c), self.request)
210 208
211 209 cache_manager = self._get_tree_cache_manager(caches.FILE_TREE)
212 210
213 211 cache_key = caches.compute_key_from_params(
214 212 self.db_repo_name, commit_id, f_path)
215 213
216 214 if force:
217 215 # we want to force recompute of caches
218 216 cache_manager.remove_value(cache_key)
219 217
220 218 return cache_manager.get(cache_key, createfunc=_cached_tree)
221 219
222 220 def _get_archive_spec(self, fname):
223 221 log.debug('Detecting archive spec for: `%s`', fname)
224 222
225 223 fileformat = None
226 224 ext = None
227 225 content_type = None
228 226 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
229 227 content_type, extension = ext_data
230 228
231 229 if fname.endswith(extension):
232 230 fileformat = a_type
233 231 log.debug('archive is of type: %s', fileformat)
234 232 ext = extension
235 233 break
236 234
237 235 if not fileformat:
238 236 raise ValueError()
239 237
240 238 # left over part of whole fname is the commit
241 239 commit_id = fname[:-len(ext)]
242 240
243 241 return commit_id, ext, fileformat, content_type
244 242
245 243 @LoginRequired()
246 244 @HasRepoPermissionAnyDecorator(
247 245 'repository.read', 'repository.write', 'repository.admin')
248 246 @view_config(
249 247 route_name='repo_archivefile', request_method='GET',
250 248 renderer=None)
251 249 def repo_archivefile(self):
252 250 # archive cache config
253 251 from rhodecode import CONFIG
254 252 _ = self.request.translate
255 253 self.load_default_context()
256 254
257 255 fname = self.request.matchdict['fname']
258 256 subrepos = self.request.GET.get('subrepos') == 'true'
259 257
260 258 if not self.db_repo.enable_downloads:
261 259 return Response(_('Downloads disabled'))
262 260
263 261 try:
264 262 commit_id, ext, fileformat, content_type = \
265 263 self._get_archive_spec(fname)
266 264 except ValueError:
267 265 return Response(_('Unknown archive type for: `{}`').format(fname))
268 266
269 267 try:
270 268 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
271 269 except CommitDoesNotExistError:
272 270 return Response(_('Unknown commit_id %s') % commit_id)
273 271 except EmptyRepositoryError:
274 272 return Response(_('Empty repository'))
275 273
276 274 archive_name = '%s-%s%s%s' % (
277 275 safe_str(self.db_repo_name.replace('/', '_')),
278 276 '-sub' if subrepos else '',
279 277 safe_str(commit.short_id), ext)
280 278
281 279 use_cached_archive = False
282 280 archive_cache_enabled = CONFIG.get(
283 281 'archive_cache_dir') and not self.request.GET.get('no_cache')
284 282
285 283 if archive_cache_enabled:
286 284 # check if we it's ok to write
287 285 if not os.path.isdir(CONFIG['archive_cache_dir']):
288 286 os.makedirs(CONFIG['archive_cache_dir'])
289 287 cached_archive_path = os.path.join(
290 288 CONFIG['archive_cache_dir'], archive_name)
291 289 if os.path.isfile(cached_archive_path):
292 290 log.debug('Found cached archive in %s', cached_archive_path)
293 291 fd, archive = None, cached_archive_path
294 292 use_cached_archive = True
295 293 else:
296 294 log.debug('Archive %s is not yet cached', archive_name)
297 295
298 296 if not use_cached_archive:
299 297 # generate new archive
300 298 fd, archive = tempfile.mkstemp()
301 299 log.debug('Creating new temp archive in %s', archive)
302 300 try:
303 301 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
304 302 except ImproperArchiveTypeError:
305 303 return _('Unknown archive type')
306 304 if archive_cache_enabled:
307 305 # if we generated the archive and we have cache enabled
308 306 # let's use this for future
309 307 log.debug('Storing new archive in %s', cached_archive_path)
310 308 shutil.move(archive, cached_archive_path)
311 309 archive = cached_archive_path
312 310
313 311 # store download action
314 312 audit_logger.store_web(
315 313 'repo.archive.download', action_data={
316 314 'user_agent': self.request.user_agent,
317 315 'archive_name': archive_name,
318 316 'archive_spec': fname,
319 317 'archive_cached': use_cached_archive},
320 318 user=self._rhodecode_user,
321 319 repo=self.db_repo,
322 320 commit=True
323 321 )
324 322
325 323 def get_chunked_archive(archive):
326 324 with open(archive, 'rb') as stream:
327 325 while True:
328 326 data = stream.read(16 * 1024)
329 327 if not data:
330 328 if fd: # fd means we used temporary file
331 329 os.close(fd)
332 330 if not archive_cache_enabled:
333 331 log.debug('Destroying temp archive %s', archive)
334 332 os.remove(archive)
335 333 break
336 334 yield data
337 335
338 336 response = Response(app_iter=get_chunked_archive(archive))
339 337 response.content_disposition = str(
340 338 'attachment; filename=%s' % archive_name)
341 339 response.content_type = str(content_type)
342 340
343 341 return response
344 342
345 343 def _get_file_node(self, commit_id, f_path):
346 344 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
347 345 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
348 346 try:
349 347 node = commit.get_node(f_path)
350 348 if node.is_dir():
351 349 raise NodeError('%s path is a %s not a file'
352 350 % (node, type(node)))
353 351 except NodeDoesNotExistError:
354 352 commit = EmptyCommit(
355 353 commit_id=commit_id,
356 354 idx=commit.idx,
357 355 repo=commit.repository,
358 356 alias=commit.repository.alias,
359 357 message=commit.message,
360 358 author=commit.author,
361 359 date=commit.date)
362 360 node = FileNode(f_path, '', commit=commit)
363 361 else:
364 362 commit = EmptyCommit(
365 363 repo=self.rhodecode_vcs_repo,
366 364 alias=self.rhodecode_vcs_repo.alias)
367 365 node = FileNode(f_path, '', commit=commit)
368 366 return node
369 367
370 368 @LoginRequired()
371 369 @HasRepoPermissionAnyDecorator(
372 370 'repository.read', 'repository.write', 'repository.admin')
373 371 @view_config(
374 372 route_name='repo_files_diff', request_method='GET',
375 373 renderer=None)
376 374 def repo_files_diff(self):
377 375 c = self.load_default_context()
378 376 f_path = self._get_f_path(self.request.matchdict)
379 377 diff1 = self.request.GET.get('diff1', '')
380 378 diff2 = self.request.GET.get('diff2', '')
381 379
382 380 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
383 381
384 382 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
385 383 line_context = self.request.GET.get('context', 3)
386 384
387 385 if not any((diff1, diff2)):
388 386 h.flash(
389 387 'Need query parameter "diff1" or "diff2" to generate a diff.',
390 388 category='error')
391 389 raise HTTPBadRequest()
392 390
393 391 c.action = self.request.GET.get('diff')
394 392 if c.action not in ['download', 'raw']:
395 393 compare_url = h.route_path(
396 394 'repo_compare',
397 395 repo_name=self.db_repo_name,
398 396 source_ref_type='rev',
399 397 source_ref=diff1,
400 398 target_repo=self.db_repo_name,
401 399 target_ref_type='rev',
402 400 target_ref=diff2,
403 401 _query=dict(f_path=f_path))
404 402 # redirect to new view if we render diff
405 403 raise HTTPFound(compare_url)
406 404
407 405 try:
408 406 node1 = self._get_file_node(diff1, path1)
409 407 node2 = self._get_file_node(diff2, f_path)
410 408 except (RepositoryError, NodeError):
411 409 log.exception("Exception while trying to get node from repository")
412 410 raise HTTPFound(
413 411 h.route_path('repo_files', repo_name=self.db_repo_name,
414 412 commit_id='tip', f_path=f_path))
415 413
416 414 if all(isinstance(node.commit, EmptyCommit)
417 415 for node in (node1, node2)):
418 416 raise HTTPNotFound()
419 417
420 418 c.commit_1 = node1.commit
421 419 c.commit_2 = node2.commit
422 420
423 421 if c.action == 'download':
424 422 _diff = diffs.get_gitdiff(node1, node2,
425 423 ignore_whitespace=ignore_whitespace,
426 424 context=line_context)
427 425 diff = diffs.DiffProcessor(_diff, format='gitdiff')
428 426
429 427 response = Response(diff.as_raw())
430 428 response.content_type = 'text/plain'
431 429 response.content_disposition = (
432 430 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
433 431 )
434 432 charset = self._get_default_encoding(c)
435 433 if charset:
436 434 response.charset = charset
437 435 return response
438 436
439 437 elif c.action == 'raw':
440 438 _diff = diffs.get_gitdiff(node1, node2,
441 439 ignore_whitespace=ignore_whitespace,
442 440 context=line_context)
443 441 diff = diffs.DiffProcessor(_diff, format='gitdiff')
444 442
445 443 response = Response(diff.as_raw())
446 444 response.content_type = 'text/plain'
447 445 charset = self._get_default_encoding(c)
448 446 if charset:
449 447 response.charset = charset
450 448 return response
451 449
452 450 # in case we ever end up here
453 451 raise HTTPNotFound()
454 452
455 453 @LoginRequired()
456 454 @HasRepoPermissionAnyDecorator(
457 455 'repository.read', 'repository.write', 'repository.admin')
458 456 @view_config(
459 457 route_name='repo_files_diff_2way_redirect', request_method='GET',
460 458 renderer=None)
461 459 def repo_files_diff_2way_redirect(self):
462 460 """
463 461 Kept only to make OLD links work
464 462 """
465 463 f_path = self._get_f_path(self.request.matchdict)
466 464 diff1 = self.request.GET.get('diff1', '')
467 465 diff2 = self.request.GET.get('diff2', '')
468 466
469 467 if not any((diff1, diff2)):
470 468 h.flash(
471 469 'Need query parameter "diff1" or "diff2" to generate a diff.',
472 470 category='error')
473 471 raise HTTPBadRequest()
474 472
475 473 compare_url = h.route_path(
476 474 'repo_compare',
477 475 repo_name=self.db_repo_name,
478 476 source_ref_type='rev',
479 477 source_ref=diff1,
480 478 target_ref_type='rev',
481 479 target_ref=diff2,
482 480 _query=dict(f_path=f_path, diffmode='sideside',
483 481 target_repo=self.db_repo_name,))
484 482 raise HTTPFound(compare_url)
485 483
486 484 @LoginRequired()
487 485 @HasRepoPermissionAnyDecorator(
488 486 'repository.read', 'repository.write', 'repository.admin')
489 487 @view_config(
490 488 route_name='repo_files', request_method='GET',
491 489 renderer=None)
492 490 @view_config(
493 491 route_name='repo_files:default_path', request_method='GET',
494 492 renderer=None)
495 493 @view_config(
496 494 route_name='repo_files:default_commit', request_method='GET',
497 495 renderer=None)
498 496 @view_config(
499 497 route_name='repo_files:rendered', request_method='GET',
500 498 renderer=None)
501 499 @view_config(
502 500 route_name='repo_files:annotated', request_method='GET',
503 501 renderer=None)
504 502 def repo_files(self):
505 503 c = self.load_default_context()
506 504
507 505 view_name = getattr(self.request.matched_route, 'name', None)
508 506
509 507 c.annotate = view_name == 'repo_files:annotated'
510 508 # default is false, but .rst/.md files later are auto rendered, we can
511 509 # overwrite auto rendering by setting this GET flag
512 510 c.renderer = view_name == 'repo_files:rendered' or \
513 511 not self.request.GET.get('no-render', False)
514 512
515 513 # redirect to given commit_id from form if given
516 514 get_commit_id = self.request.GET.get('at_rev', None)
517 515 if get_commit_id:
518 516 self._get_commit_or_redirect(get_commit_id)
519 517
520 518 commit_id, f_path = self._get_commit_and_path()
521 519 c.commit = self._get_commit_or_redirect(commit_id)
522 520 c.branch = self.request.GET.get('branch', None)
523 521 c.f_path = f_path
524 522
525 523 # prev link
526 524 try:
527 525 prev_commit = c.commit.prev(c.branch)
528 526 c.prev_commit = prev_commit
529 527 c.url_prev = h.route_path(
530 528 'repo_files', repo_name=self.db_repo_name,
531 529 commit_id=prev_commit.raw_id, f_path=f_path)
532 530 if c.branch:
533 531 c.url_prev += '?branch=%s' % c.branch
534 532 except (CommitDoesNotExistError, VCSError):
535 533 c.url_prev = '#'
536 534 c.prev_commit = EmptyCommit()
537 535
538 536 # next link
539 537 try:
540 538 next_commit = c.commit.next(c.branch)
541 539 c.next_commit = next_commit
542 540 c.url_next = h.route_path(
543 541 'repo_files', repo_name=self.db_repo_name,
544 542 commit_id=next_commit.raw_id, f_path=f_path)
545 543 if c.branch:
546 544 c.url_next += '?branch=%s' % c.branch
547 545 except (CommitDoesNotExistError, VCSError):
548 546 c.url_next = '#'
549 547 c.next_commit = EmptyCommit()
550 548
551 549 # files or dirs
552 550 try:
553 551 c.file = c.commit.get_node(f_path)
554 552 c.file_author = True
555 553 c.file_tree = ''
556 554
557 555 # load file content
558 556 if c.file.is_file():
559 557 c.lf_node = c.file.get_largefile_node()
560 558
561 559 c.file_source_page = 'true'
562 560 c.file_last_commit = c.file.last_commit
563 561 if c.file.size < c.visual.cut_off_limit_diff:
564 562 if c.annotate: # annotation has precedence over renderer
565 563 c.annotated_lines = filenode_as_annotated_lines_tokens(
566 564 c.file
567 565 )
568 566 else:
569 567 c.renderer = (
570 568 c.renderer and h.renderer_from_filename(c.file.path)
571 569 )
572 570 if not c.renderer:
573 571 c.lines = filenode_as_lines_tokens(c.file)
574 572
575 573 c.on_branch_head = self._is_valid_head(
576 574 commit_id, self.rhodecode_vcs_repo)
577 575
578 576 branch = c.commit.branch if (
579 577 c.commit.branch and '/' not in c.commit.branch) else None
580 578 c.branch_or_raw_id = branch or c.commit.raw_id
581 579 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
582 580
583 581 author = c.file_last_commit.author
584 582 c.authors = [[
585 583 h.email(author),
586 584 h.person(author, 'username_or_name_or_email'),
587 585 1
588 586 ]]
589 587
590 588 else: # load tree content at path
591 589 c.file_source_page = 'false'
592 590 c.authors = []
593 591 # this loads a simple tree without metadata to speed things up
594 592 # later via ajax we call repo_nodetree_full and fetch whole
595 593 c.file_tree = self._get_tree_at_commit(
596 594 c, c.commit.raw_id, f_path)
597 595
598 596 except RepositoryError as e:
599 597 h.flash(safe_str(h.escape(e)), category='error')
600 598 raise HTTPNotFound()
601 599
602 600 if self.request.environ.get('HTTP_X_PJAX'):
603 601 html = render('rhodecode:templates/files/files_pjax.mako',
604 602 self._get_template_context(c), self.request)
605 603 else:
606 604 html = render('rhodecode:templates/files/files.mako',
607 605 self._get_template_context(c), self.request)
608 606 return Response(html)
609 607
610 608 @HasRepoPermissionAnyDecorator(
611 609 'repository.read', 'repository.write', 'repository.admin')
612 610 @view_config(
613 611 route_name='repo_files:annotated_previous', request_method='GET',
614 612 renderer=None)
615 613 def repo_files_annotated_previous(self):
616 614 self.load_default_context()
617 615
618 616 commit_id, f_path = self._get_commit_and_path()
619 617 commit = self._get_commit_or_redirect(commit_id)
620 618 prev_commit_id = commit.raw_id
621 619 line_anchor = self.request.GET.get('line_anchor')
622 620 is_file = False
623 621 try:
624 622 _file = commit.get_node(f_path)
625 623 is_file = _file.is_file()
626 624 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
627 625 pass
628 626
629 627 if is_file:
630 628 history = commit.get_file_history(f_path)
631 629 prev_commit_id = history[1].raw_id \
632 630 if len(history) > 1 else prev_commit_id
633 631 prev_url = h.route_path(
634 632 'repo_files:annotated', repo_name=self.db_repo_name,
635 633 commit_id=prev_commit_id, f_path=f_path,
636 634 _anchor='L{}'.format(line_anchor))
637 635
638 636 raise HTTPFound(prev_url)
639 637
640 638 @LoginRequired()
641 639 @HasRepoPermissionAnyDecorator(
642 640 'repository.read', 'repository.write', 'repository.admin')
643 641 @view_config(
644 642 route_name='repo_nodetree_full', request_method='GET',
645 643 renderer=None, xhr=True)
646 644 @view_config(
647 645 route_name='repo_nodetree_full:default_path', request_method='GET',
648 646 renderer=None, xhr=True)
649 647 def repo_nodetree_full(self):
650 648 """
651 649 Returns rendered html of file tree that contains commit date,
652 650 author, commit_id for the specified combination of
653 651 repo, commit_id and file path
654 652 """
655 653 c = self.load_default_context()
656 654
657 655 commit_id, f_path = self._get_commit_and_path()
658 656 commit = self._get_commit_or_redirect(commit_id)
659 657 try:
660 658 dir_node = commit.get_node(f_path)
661 659 except RepositoryError as e:
662 660 return Response('error: {}'.format(safe_str(e)))
663 661
664 662 if dir_node.is_file():
665 663 return Response('')
666 664
667 665 c.file = dir_node
668 666 c.commit = commit
669 667
670 668 # using force=True here, make a little trick. We flush the cache and
671 669 # compute it using the same key as without previous full_load, so now
672 670 # the fully loaded tree is now returned instead of partial,
673 671 # and we store this in caches
674 672 html = self._get_tree_at_commit(
675 673 c, commit.raw_id, dir_node.path, full_load=True, force=True)
676 674
677 675 return Response(html)
678 676
679 677 def _get_attachement_disposition(self, f_path):
680 678 return 'attachment; filename=%s' % \
681 679 safe_str(f_path.split(Repository.NAME_SEP)[-1])
682 680
683 681 @LoginRequired()
684 682 @HasRepoPermissionAnyDecorator(
685 683 'repository.read', 'repository.write', 'repository.admin')
686 684 @view_config(
687 685 route_name='repo_file_raw', request_method='GET',
688 686 renderer=None)
689 687 def repo_file_raw(self):
690 688 """
691 689 Action for show as raw, some mimetypes are "rendered",
692 690 those include images, icons.
693 691 """
694 692 c = self.load_default_context()
695 693
696 694 commit_id, f_path = self._get_commit_and_path()
697 695 commit = self._get_commit_or_redirect(commit_id)
698 696 file_node = self._get_filenode_or_redirect(commit, f_path)
699 697
700 698 raw_mimetype_mapping = {
701 699 # map original mimetype to a mimetype used for "show as raw"
702 700 # you can also provide a content-disposition to override the
703 701 # default "attachment" disposition.
704 702 # orig_type: (new_type, new_dispo)
705 703
706 704 # show images inline:
707 705 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
708 706 # for example render an SVG with javascript inside or even render
709 707 # HTML.
710 708 'image/x-icon': ('image/x-icon', 'inline'),
711 709 'image/png': ('image/png', 'inline'),
712 710 'image/gif': ('image/gif', 'inline'),
713 711 'image/jpeg': ('image/jpeg', 'inline'),
714 712 'application/pdf': ('application/pdf', 'inline'),
715 713 }
716 714
717 715 mimetype = file_node.mimetype
718 716 try:
719 717 mimetype, disposition = raw_mimetype_mapping[mimetype]
720 718 except KeyError:
721 719 # we don't know anything special about this, handle it safely
722 720 if file_node.is_binary:
723 721 # do same as download raw for binary files
724 722 mimetype, disposition = 'application/octet-stream', 'attachment'
725 723 else:
726 724 # do not just use the original mimetype, but force text/plain,
727 725 # otherwise it would serve text/html and that might be unsafe.
728 726 # Note: underlying vcs library fakes text/plain mimetype if the
729 727 # mimetype can not be determined and it thinks it is not
730 728 # binary.This might lead to erroneous text display in some
731 729 # cases, but helps in other cases, like with text files
732 730 # without extension.
733 731 mimetype, disposition = 'text/plain', 'inline'
734 732
735 733 if disposition == 'attachment':
736 734 disposition = self._get_attachement_disposition(f_path)
737 735
738 736 def stream_node():
739 737 yield file_node.raw_bytes
740 738
741 739 response = Response(app_iter=stream_node())
742 740 response.content_disposition = disposition
743 741 response.content_type = mimetype
744 742
745 743 charset = self._get_default_encoding(c)
746 744 if charset:
747 745 response.charset = charset
748 746
749 747 return response
750 748
751 749 @LoginRequired()
752 750 @HasRepoPermissionAnyDecorator(
753 751 'repository.read', 'repository.write', 'repository.admin')
754 752 @view_config(
755 753 route_name='repo_file_download', request_method='GET',
756 754 renderer=None)
757 755 @view_config(
758 756 route_name='repo_file_download:legacy', request_method='GET',
759 757 renderer=None)
760 758 def repo_file_download(self):
761 759 c = self.load_default_context()
762 760
763 761 commit_id, f_path = self._get_commit_and_path()
764 762 commit = self._get_commit_or_redirect(commit_id)
765 763 file_node = self._get_filenode_or_redirect(commit, f_path)
766 764
767 765 if self.request.GET.get('lf'):
768 766 # only if lf get flag is passed, we download this file
769 767 # as LFS/Largefile
770 768 lf_node = file_node.get_largefile_node()
771 769 if lf_node:
772 770 # overwrite our pointer with the REAL large-file
773 771 file_node = lf_node
774 772
775 773 disposition = self._get_attachement_disposition(f_path)
776 774
777 775 def stream_node():
778 776 yield file_node.raw_bytes
779 777
780 778 response = Response(app_iter=stream_node())
781 779 response.content_disposition = disposition
782 780 response.content_type = file_node.mimetype
783 781
784 782 charset = self._get_default_encoding(c)
785 783 if charset:
786 784 response.charset = charset
787 785
788 786 return response
789 787
790 788 def _get_nodelist_at_commit(self, repo_name, commit_id, f_path):
791 789 def _cached_nodes():
792 790 log.debug('Generating cached nodelist for %s, %s, %s',
793 791 repo_name, commit_id, f_path)
794 792 _d, _f = ScmModel().get_nodes(
795 793 repo_name, commit_id, f_path, flat=False)
796 794 return _d + _f
797 795
798 796 cache_manager = self._get_tree_cache_manager(caches.FILE_SEARCH_TREE_META)
799 797
800 798 cache_key = caches.compute_key_from_params(
801 799 repo_name, commit_id, f_path)
802 800 return cache_manager.get(cache_key, createfunc=_cached_nodes)
803 801
804 802 @LoginRequired()
805 803 @HasRepoPermissionAnyDecorator(
806 804 'repository.read', 'repository.write', 'repository.admin')
807 805 @view_config(
808 806 route_name='repo_files_nodelist', request_method='GET',
809 807 renderer='json_ext', xhr=True)
810 808 def repo_nodelist(self):
811 809 self.load_default_context()
812 810
813 811 commit_id, f_path = self._get_commit_and_path()
814 812 commit = self._get_commit_or_redirect(commit_id)
815 813
816 814 metadata = self._get_nodelist_at_commit(
817 815 self.db_repo_name, commit.raw_id, f_path)
818 816 return {'nodes': metadata}
819 817
820 818 def _create_references(
821 819 self, branches_or_tags, symbolic_reference, f_path):
822 820 items = []
823 821 for name, commit_id in branches_or_tags.items():
824 822 sym_ref = symbolic_reference(commit_id, name, f_path)
825 823 items.append((sym_ref, name))
826 824 return items
827 825
828 826 def _symbolic_reference(self, commit_id, name, f_path):
829 827 return commit_id
830 828
831 829 def _symbolic_reference_svn(self, commit_id, name, f_path):
832 830 new_f_path = vcspath.join(name, f_path)
833 831 return u'%s@%s' % (new_f_path, commit_id)
834 832
835 833 def _get_node_history(self, commit_obj, f_path, commits=None):
836 834 """
837 835 get commit history for given node
838 836
839 837 :param commit_obj: commit to calculate history
840 838 :param f_path: path for node to calculate history for
841 839 :param commits: if passed don't calculate history and take
842 840 commits defined in this list
843 841 """
844 842 _ = self.request.translate
845 843
846 844 # calculate history based on tip
847 845 tip = self.rhodecode_vcs_repo.get_commit()
848 846 if commits is None:
849 847 pre_load = ["author", "branch"]
850 848 try:
851 849 commits = tip.get_file_history(f_path, pre_load=pre_load)
852 850 except (NodeDoesNotExistError, CommitError):
853 851 # this node is not present at tip!
854 852 commits = commit_obj.get_file_history(f_path, pre_load=pre_load)
855 853
856 854 history = []
857 855 commits_group = ([], _("Changesets"))
858 856 for commit in commits:
859 857 branch = ' (%s)' % commit.branch if commit.branch else ''
860 858 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
861 859 commits_group[0].append((commit.raw_id, n_desc,))
862 860 history.append(commits_group)
863 861
864 862 symbolic_reference = self._symbolic_reference
865 863
866 864 if self.rhodecode_vcs_repo.alias == 'svn':
867 865 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
868 866 f_path, self.rhodecode_vcs_repo)
869 867 if adjusted_f_path != f_path:
870 868 log.debug(
871 869 'Recognized svn tag or branch in file "%s", using svn '
872 870 'specific symbolic references', f_path)
873 871 f_path = adjusted_f_path
874 872 symbolic_reference = self._symbolic_reference_svn
875 873
876 874 branches = self._create_references(
877 875 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path)
878 876 branches_group = (branches, _("Branches"))
879 877
880 878 tags = self._create_references(
881 879 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path)
882 880 tags_group = (tags, _("Tags"))
883 881
884 882 history.append(branches_group)
885 883 history.append(tags_group)
886 884
887 885 return history, commits
888 886
889 887 @LoginRequired()
890 888 @HasRepoPermissionAnyDecorator(
891 889 'repository.read', 'repository.write', 'repository.admin')
892 890 @view_config(
893 891 route_name='repo_file_history', request_method='GET',
894 892 renderer='json_ext')
895 893 def repo_file_history(self):
896 894 self.load_default_context()
897 895
898 896 commit_id, f_path = self._get_commit_and_path()
899 897 commit = self._get_commit_or_redirect(commit_id)
900 898 file_node = self._get_filenode_or_redirect(commit, f_path)
901 899
902 900 if file_node.is_file():
903 901 file_history, _hist = self._get_node_history(commit, f_path)
904 902
905 903 res = []
906 904 for obj in file_history:
907 905 res.append({
908 906 'text': obj[1],
909 907 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
910 908 })
911 909
912 910 data = {
913 911 'more': False,
914 912 'results': res
915 913 }
916 914 return data
917 915
918 916 log.warning('Cannot fetch history for directory')
919 917 raise HTTPBadRequest()
920 918
921 919 @LoginRequired()
922 920 @HasRepoPermissionAnyDecorator(
923 921 'repository.read', 'repository.write', 'repository.admin')
924 922 @view_config(
925 923 route_name='repo_file_authors', request_method='GET',
926 924 renderer='rhodecode:templates/files/file_authors_box.mako')
927 925 def repo_file_authors(self):
928 926 c = self.load_default_context()
929 927
930 928 commit_id, f_path = self._get_commit_and_path()
931 929 commit = self._get_commit_or_redirect(commit_id)
932 930 file_node = self._get_filenode_or_redirect(commit, f_path)
933 931
934 932 if not file_node.is_file():
935 933 raise HTTPBadRequest()
936 934
937 935 c.file_last_commit = file_node.last_commit
938 936 if self.request.GET.get('annotate') == '1':
939 937 # use _hist from annotation if annotation mode is on
940 938 commit_ids = set(x[1] for x in file_node.annotate)
941 939 _hist = (
942 940 self.rhodecode_vcs_repo.get_commit(commit_id)
943 941 for commit_id in commit_ids)
944 942 else:
945 943 _f_history, _hist = self._get_node_history(commit, f_path)
946 944 c.file_author = False
947 945
948 946 unique = collections.OrderedDict()
949 947 for commit in _hist:
950 948 author = commit.author
951 949 if author not in unique:
952 950 unique[commit.author] = [
953 951 h.email(author),
954 952 h.person(author, 'username_or_name_or_email'),
955 953 1 # counter
956 954 ]
957 955
958 956 else:
959 957 # increase counter
960 958 unique[commit.author][2] += 1
961 959
962 960 c.authors = [val for val in unique.values()]
963 961
964 962 return self._get_template_context(c)
965 963
966 964 @LoginRequired()
967 965 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
968 966 @view_config(
969 967 route_name='repo_files_remove_file', request_method='GET',
970 968 renderer='rhodecode:templates/files/files_delete.mako')
971 969 def repo_files_remove_file(self):
972 970 _ = self.request.translate
973 971 c = self.load_default_context()
974 972 commit_id, f_path = self._get_commit_and_path()
975 973
976 974 self._ensure_not_locked()
977 975
978 976 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
979 977 h.flash(_('You can only delete files with commit '
980 978 'being a valid branch '), category='warning')
981 979 raise HTTPFound(
982 980 h.route_path('repo_files',
983 981 repo_name=self.db_repo_name, commit_id='tip',
984 982 f_path=f_path))
985 983
986 984 c.commit = self._get_commit_or_redirect(commit_id)
987 985 c.file = self._get_filenode_or_redirect(c.commit, f_path)
988 986
989 987 c.default_message = _(
990 988 'Deleted file {} via RhodeCode Enterprise').format(f_path)
991 989 c.f_path = f_path
992 990
993 991 return self._get_template_context(c)
994 992
995 993 @LoginRequired()
996 994 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
997 995 @CSRFRequired()
998 996 @view_config(
999 997 route_name='repo_files_delete_file', request_method='POST',
1000 998 renderer=None)
1001 999 def repo_files_delete_file(self):
1002 1000 _ = self.request.translate
1003 1001
1004 1002 c = self.load_default_context()
1005 1003 commit_id, f_path = self._get_commit_and_path()
1006 1004
1007 1005 self._ensure_not_locked()
1008 1006
1009 1007 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1010 1008 h.flash(_('You can only delete files with commit '
1011 1009 'being a valid branch '), category='warning')
1012 1010 raise HTTPFound(
1013 1011 h.route_path('repo_files',
1014 1012 repo_name=self.db_repo_name, commit_id='tip',
1015 1013 f_path=f_path))
1016 1014
1017 1015 c.commit = self._get_commit_or_redirect(commit_id)
1018 1016 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1019 1017
1020 1018 c.default_message = _(
1021 1019 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1022 1020 c.f_path = f_path
1023 1021 node_path = f_path
1024 1022 author = self._rhodecode_db_user.full_contact
1025 1023 message = self.request.POST.get('message') or c.default_message
1026 1024 try:
1027 1025 nodes = {
1028 1026 node_path: {
1029 1027 'content': ''
1030 1028 }
1031 1029 }
1032 1030 ScmModel().delete_nodes(
1033 1031 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1034 1032 message=message,
1035 1033 nodes=nodes,
1036 1034 parent_commit=c.commit,
1037 1035 author=author,
1038 1036 )
1039 1037
1040 1038 h.flash(
1041 1039 _('Successfully deleted file `{}`').format(
1042 1040 h.escape(f_path)), category='success')
1043 1041 except Exception:
1044 1042 log.exception('Error during commit operation')
1045 1043 h.flash(_('Error occurred during commit'), category='error')
1046 1044 raise HTTPFound(
1047 1045 h.route_path('repo_commit', repo_name=self.db_repo_name,
1048 1046 commit_id='tip'))
1049 1047
1050 1048 @LoginRequired()
1051 1049 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1052 1050 @view_config(
1053 1051 route_name='repo_files_edit_file', request_method='GET',
1054 1052 renderer='rhodecode:templates/files/files_edit.mako')
1055 1053 def repo_files_edit_file(self):
1056 1054 _ = self.request.translate
1057 1055 c = self.load_default_context()
1058 1056 commit_id, f_path = self._get_commit_and_path()
1059 1057
1060 1058 self._ensure_not_locked()
1061 1059
1062 1060 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1063 1061 h.flash(_('You can only edit files with commit '
1064 1062 'being a valid branch '), category='warning')
1065 1063 raise HTTPFound(
1066 1064 h.route_path('repo_files',
1067 1065 repo_name=self.db_repo_name, commit_id='tip',
1068 1066 f_path=f_path))
1069 1067
1070 1068 c.commit = self._get_commit_or_redirect(commit_id)
1071 1069 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1072 1070
1073 1071 if c.file.is_binary:
1074 1072 files_url = h.route_path(
1075 1073 'repo_files',
1076 1074 repo_name=self.db_repo_name,
1077 1075 commit_id=c.commit.raw_id, f_path=f_path)
1078 1076 raise HTTPFound(files_url)
1079 1077
1080 1078 c.default_message = _(
1081 1079 'Edited file {} via RhodeCode Enterprise').format(f_path)
1082 1080 c.f_path = f_path
1083 1081
1084 1082 return self._get_template_context(c)
1085 1083
1086 1084 @LoginRequired()
1087 1085 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1088 1086 @CSRFRequired()
1089 1087 @view_config(
1090 1088 route_name='repo_files_update_file', request_method='POST',
1091 1089 renderer=None)
1092 1090 def repo_files_update_file(self):
1093 1091 _ = self.request.translate
1094 1092 c = self.load_default_context()
1095 1093 commit_id, f_path = self._get_commit_and_path()
1096 1094
1097 1095 self._ensure_not_locked()
1098 1096
1099 1097 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1100 1098 h.flash(_('You can only edit files with commit '
1101 1099 'being a valid branch '), category='warning')
1102 1100 raise HTTPFound(
1103 1101 h.route_path('repo_files',
1104 1102 repo_name=self.db_repo_name, commit_id='tip',
1105 1103 f_path=f_path))
1106 1104
1107 1105 c.commit = self._get_commit_or_redirect(commit_id)
1108 1106 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1109 1107
1110 1108 if c.file.is_binary:
1111 1109 raise HTTPFound(
1112 1110 h.route_path('repo_files',
1113 1111 repo_name=self.db_repo_name,
1114 1112 commit_id=c.commit.raw_id,
1115 1113 f_path=f_path))
1116 1114
1117 1115 c.default_message = _(
1118 1116 'Edited file {} via RhodeCode Enterprise').format(f_path)
1119 1117 c.f_path = f_path
1120 1118 old_content = c.file.content
1121 1119 sl = old_content.splitlines(1)
1122 1120 first_line = sl[0] if sl else ''
1123 1121
1124 1122 r_post = self.request.POST
1125 1123 # modes: 0 - Unix, 1 - Mac, 2 - DOS
1126 1124 mode = detect_mode(first_line, 0)
1127 1125 content = convert_line_endings(r_post.get('content', ''), mode)
1128 1126
1129 1127 message = r_post.get('message') or c.default_message
1130 1128 org_f_path = c.file.unicode_path
1131 1129 filename = r_post['filename']
1132 1130 org_filename = c.file.name
1133 1131
1134 1132 if content == old_content and filename == org_filename:
1135 1133 h.flash(_('No changes'), category='warning')
1136 1134 raise HTTPFound(
1137 1135 h.route_path('repo_commit', repo_name=self.db_repo_name,
1138 1136 commit_id='tip'))
1139 1137 try:
1140 1138 mapping = {
1141 1139 org_f_path: {
1142 1140 'org_filename': org_f_path,
1143 1141 'filename': os.path.join(c.file.dir_path, filename),
1144 1142 'content': content,
1145 1143 'lexer': '',
1146 1144 'op': 'mod',
1147 1145 }
1148 1146 }
1149 1147
1150 1148 ScmModel().update_nodes(
1151 1149 user=self._rhodecode_db_user.user_id,
1152 1150 repo=self.db_repo,
1153 1151 message=message,
1154 1152 nodes=mapping,
1155 1153 parent_commit=c.commit,
1156 1154 )
1157 1155
1158 1156 h.flash(
1159 1157 _('Successfully committed changes to file `{}`').format(
1160 1158 h.escape(f_path)), category='success')
1161 1159 except Exception:
1162 1160 log.exception('Error occurred during commit')
1163 1161 h.flash(_('Error occurred during commit'), category='error')
1164 1162 raise HTTPFound(
1165 1163 h.route_path('repo_commit', repo_name=self.db_repo_name,
1166 1164 commit_id='tip'))
1167 1165
1168 1166 @LoginRequired()
1169 1167 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1170 1168 @view_config(
1171 1169 route_name='repo_files_add_file', request_method='GET',
1172 1170 renderer='rhodecode:templates/files/files_add.mako')
1173 1171 def repo_files_add_file(self):
1174 1172 _ = self.request.translate
1175 1173 c = self.load_default_context()
1176 1174 commit_id, f_path = self._get_commit_and_path()
1177 1175
1178 1176 self._ensure_not_locked()
1179 1177
1180 1178 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1181 1179 if c.commit is None:
1182 1180 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1183 1181 c.default_message = (_('Added file via RhodeCode Enterprise'))
1184 1182 c.f_path = f_path.lstrip('/') # ensure not relative path
1185 1183
1186 1184 return self._get_template_context(c)
1187 1185
1188 1186 @LoginRequired()
1189 1187 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1190 1188 @CSRFRequired()
1191 1189 @view_config(
1192 1190 route_name='repo_files_create_file', request_method='POST',
1193 1191 renderer=None)
1194 1192 def repo_files_create_file(self):
1195 1193 _ = self.request.translate
1196 1194 c = self.load_default_context()
1197 1195 commit_id, f_path = self._get_commit_and_path()
1198 1196
1199 1197 self._ensure_not_locked()
1200 1198
1201 1199 r_post = self.request.POST
1202 1200
1203 1201 c.commit = self._get_commit_or_redirect(
1204 1202 commit_id, redirect_after=False)
1205 1203 if c.commit is None:
1206 1204 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1207 1205 c.default_message = (_('Added file via RhodeCode Enterprise'))
1208 1206 c.f_path = f_path
1209 1207 unix_mode = 0
1210 1208 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1211 1209
1212 1210 message = r_post.get('message') or c.default_message
1213 1211 filename = r_post.get('filename')
1214 1212 location = r_post.get('location', '') # dir location
1215 1213 file_obj = r_post.get('upload_file', None)
1216 1214
1217 1215 if file_obj is not None and hasattr(file_obj, 'filename'):
1218 1216 filename = r_post.get('filename_upload')
1219 1217 content = file_obj.file
1220 1218
1221 1219 if hasattr(content, 'file'):
1222 1220 # non posix systems store real file under file attr
1223 1221 content = content.file
1224 1222
1225 1223 if self.rhodecode_vcs_repo.is_empty:
1226 1224 default_redirect_url = h.route_path(
1227 1225 'repo_summary', repo_name=self.db_repo_name)
1228 1226 else:
1229 1227 default_redirect_url = h.route_path(
1230 1228 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1231 1229
1232 1230 # If there's no commit, redirect to repo summary
1233 1231 if type(c.commit) is EmptyCommit:
1234 1232 redirect_url = h.route_path(
1235 1233 'repo_summary', repo_name=self.db_repo_name)
1236 1234 else:
1237 1235 redirect_url = default_redirect_url
1238 1236
1239 1237 if not filename:
1240 1238 h.flash(_('No filename'), category='warning')
1241 1239 raise HTTPFound(redirect_url)
1242 1240
1243 1241 # extract the location from filename,
1244 1242 # allows using foo/bar.txt syntax to create subdirectories
1245 1243 subdir_loc = filename.rsplit('/', 1)
1246 1244 if len(subdir_loc) == 2:
1247 1245 location = os.path.join(location, subdir_loc[0])
1248 1246
1249 1247 # strip all crap out of file, just leave the basename
1250 1248 filename = os.path.basename(filename)
1251 1249 node_path = os.path.join(location, filename)
1252 1250 author = self._rhodecode_db_user.full_contact
1253 1251
1254 1252 try:
1255 1253 nodes = {
1256 1254 node_path: {
1257 1255 'content': content
1258 1256 }
1259 1257 }
1260 1258 ScmModel().create_nodes(
1261 1259 user=self._rhodecode_db_user.user_id,
1262 1260 repo=self.db_repo,
1263 1261 message=message,
1264 1262 nodes=nodes,
1265 1263 parent_commit=c.commit,
1266 1264 author=author,
1267 1265 )
1268 1266
1269 1267 h.flash(
1270 1268 _('Successfully committed new file `{}`').format(
1271 1269 h.escape(node_path)), category='success')
1272 1270 except NonRelativePathError:
1273 1271 log.exception('Non Relative path found')
1274 1272 h.flash(_(
1275 1273 'The location specified must be a relative path and must not '
1276 1274 'contain .. in the path'), category='warning')
1277 1275 raise HTTPFound(default_redirect_url)
1278 1276 except (NodeError, NodeAlreadyExistsError) as e:
1279 1277 h.flash(_(h.escape(e)), category='error')
1280 1278 except Exception:
1281 1279 log.exception('Error occurred during commit')
1282 1280 h.flash(_('Error occurred during commit'), category='error')
1283 1281
1284 1282 raise HTTPFound(default_redirect_url)
@@ -1,256 +1,253 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 import logging
22 22 import datetime
23 23 import formencode
24 24 import formencode.htmlfill
25 25
26 26 from pyramid.httpexceptions import HTTPFound
27 27 from pyramid.view import view_config
28 28 from pyramid.renderers import render
29 29 from pyramid.response import Response
30 30
31 31 from rhodecode.apps._base import RepoAppView, DataGridAppView
32 32 from rhodecode.lib.auth import (
33 33 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous,
34 34 HasRepoPermissionAny, HasPermissionAnyDecorator, CSRFRequired)
35 35 import rhodecode.lib.helpers as h
36 36 from rhodecode.model.db import coalesce, or_, Repository, RepoGroup
37 37 from rhodecode.model.repo import RepoModel
38 38 from rhodecode.model.forms import RepoForkForm
39 39 from rhodecode.model.scm import ScmModel, RepoGroupList
40 40 from rhodecode.lib.utils2 import safe_int, safe_unicode
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44
45 45 class RepoForksView(RepoAppView, DataGridAppView):
46 46
47 47 def load_default_context(self):
48 48 c = self._get_local_tmpl_context(include_app_defaults=True)
49
50 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
51 c.repo_info = self.db_repo
52 49 c.rhodecode_repo = self.rhodecode_vcs_repo
53 50
54 51 acl_groups = RepoGroupList(
55 52 RepoGroup.query().all(),
56 53 perm_set=['group.write', 'group.admin'])
57 54 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
58 55 c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
59 56 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
60 57 c.landing_revs_choices = choices
61 58 c.personal_repo_group = c.rhodecode_user.personal_repo_group
62 59
63 60 self._register_global_c(c)
64 61 return c
65 62
66 63 @LoginRequired()
67 64 @HasRepoPermissionAnyDecorator(
68 65 'repository.read', 'repository.write', 'repository.admin')
69 66 @view_config(
70 67 route_name='repo_forks_show_all', request_method='GET',
71 68 renderer='rhodecode:templates/forks/forks.mako')
72 69 def repo_forks_show_all(self):
73 70 c = self.load_default_context()
74 71 return self._get_template_context(c)
75 72
76 73 @LoginRequired()
77 74 @HasRepoPermissionAnyDecorator(
78 75 'repository.read', 'repository.write', 'repository.admin')
79 76 @view_config(
80 77 route_name='repo_forks_data', request_method='GET',
81 78 renderer='json_ext', xhr=True)
82 79 def repo_forks_data(self):
83 80 _ = self.request.translate
84 81 column_map = {
85 82 'fork_name': 'repo_name',
86 83 'fork_date': 'created_on',
87 84 'last_activity': 'updated_on'
88 85 }
89 86 draw, start, limit = self._extract_chunk(self.request)
90 87 search_q, order_by, order_dir = self._extract_ordering(
91 88 self.request, column_map=column_map)
92 89
93 90 acl_check = HasRepoPermissionAny(
94 91 'repository.read', 'repository.write', 'repository.admin')
95 92 repo_id = self.db_repo.repo_id
96 93 allowed_ids = []
97 94 for f in Repository.query().filter(Repository.fork_id == repo_id):
98 95 if acl_check(f.repo_name, 'get forks check'):
99 96 allowed_ids.append(f.repo_id)
100 97
101 98 forks_data_total_count = Repository.query()\
102 99 .filter(Repository.fork_id == repo_id)\
103 100 .filter(Repository.repo_id.in_(allowed_ids))\
104 101 .count()
105 102
106 103 # json generate
107 104 base_q = Repository.query()\
108 105 .filter(Repository.fork_id == repo_id)\
109 106 .filter(Repository.repo_id.in_(allowed_ids))\
110 107
111 108 if search_q:
112 109 like_expression = u'%{}%'.format(safe_unicode(search_q))
113 110 base_q = base_q.filter(or_(
114 111 Repository.repo_name.ilike(like_expression),
115 112 Repository.description.ilike(like_expression),
116 113 ))
117 114
118 115 forks_data_total_filtered_count = base_q.count()
119 116
120 117 sort_col = getattr(Repository, order_by, None)
121 118 if sort_col:
122 119 if order_dir == 'asc':
123 120 # handle null values properly to order by NULL last
124 121 if order_by in ['last_activity']:
125 122 sort_col = coalesce(sort_col, datetime.date.max)
126 123 sort_col = sort_col.asc()
127 124 else:
128 125 # handle null values properly to order by NULL last
129 126 if order_by in ['last_activity']:
130 127 sort_col = coalesce(sort_col, datetime.date.min)
131 128 sort_col = sort_col.desc()
132 129
133 130 base_q = base_q.order_by(sort_col)
134 131 base_q = base_q.offset(start).limit(limit)
135 132
136 133 fork_list = base_q.all()
137 134
138 135 def fork_actions(fork):
139 136 url_link = h.route_path(
140 137 'repo_compare',
141 138 repo_name=fork.repo_name,
142 139 source_ref_type=self.db_repo.landing_rev[0],
143 140 source_ref=self.db_repo.landing_rev[1],
144 141 target_ref_type=self.db_repo.landing_rev[0],
145 142 target_ref=self.db_repo.landing_rev[1],
146 143 _query=dict(merge=1, target_repo=f.repo_name))
147 144 return h.link_to(_('Compare fork'), url_link, class_='btn-link')
148 145
149 146 def fork_name(fork):
150 147 return h.link_to(fork.repo_name,
151 148 h.route_path('repo_summary', repo_name=fork.repo_name))
152 149
153 150 forks_data = []
154 151 for fork in fork_list:
155 152 forks_data.append({
156 153 "username": h.gravatar_with_user(self.request, fork.user.username),
157 154 "fork_name": fork_name(fork),
158 155 "description": fork.description,
159 156 "fork_date": h.age_component(fork.created_on, time_is_local=True),
160 157 "last_activity": h.format_date(fork.updated_on),
161 158 "action": fork_actions(fork),
162 159 })
163 160
164 161 data = ({
165 162 'draw': draw,
166 163 'data': forks_data,
167 164 'recordsTotal': forks_data_total_count,
168 165 'recordsFiltered': forks_data_total_filtered_count,
169 166 })
170 167
171 168 return data
172 169
173 170 @LoginRequired()
174 171 @NotAnonymous()
175 172 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
176 173 @HasRepoPermissionAnyDecorator(
177 174 'repository.read', 'repository.write', 'repository.admin')
178 175 @view_config(
179 176 route_name='repo_fork_new', request_method='GET',
180 177 renderer='rhodecode:templates/forks/forks.mako')
181 178 def repo_fork_new(self):
182 179 c = self.load_default_context()
183 180
184 181 defaults = RepoModel()._get_defaults(self.db_repo_name)
185 182 # alter the description to indicate a fork
186 183 defaults['description'] = (
187 184 'fork of repository: %s \n%s' % (
188 185 defaults['repo_name'], defaults['description']))
189 186 # add suffix to fork
190 187 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
191 188
192 189 data = render('rhodecode:templates/forks/fork.mako',
193 190 self._get_template_context(c), self.request)
194 191 html = formencode.htmlfill.render(
195 192 data,
196 193 defaults=defaults,
197 194 encoding="UTF-8",
198 195 force_defaults=False
199 196 )
200 197 return Response(html)
201 198
202 199 @LoginRequired()
203 200 @NotAnonymous()
204 201 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
205 202 @HasRepoPermissionAnyDecorator(
206 203 'repository.read', 'repository.write', 'repository.admin')
207 204 @CSRFRequired()
208 205 @view_config(
209 206 route_name='repo_fork_create', request_method='POST',
210 207 renderer='rhodecode:templates/forks/fork.mako')
211 208 def repo_fork_create(self):
212 209 _ = self.request.translate
213 210 c = self.load_default_context()
214 211
215 212 _form = RepoForkForm(old_data={'repo_type': self.db_repo.repo_type},
216 213 repo_groups=c.repo_groups_choices,
217 214 landing_revs=c.landing_revs_choices)()
218 215 form_result = {}
219 216 task_id = None
220 217 try:
221 218 form_result = _form.to_python(dict(self.request.POST))
222 219 # create fork is done sometimes async on celery, db transaction
223 220 # management is handled there.
224 221 task = RepoModel().create_fork(
225 222 form_result, c.rhodecode_user.user_id)
226 223 from celery.result import BaseAsyncResult
227 224 if isinstance(task, BaseAsyncResult):
228 225 task_id = task.task_id
229 226 except formencode.Invalid as errors:
230 c.repo_info = self.db_repo
227 c.rhodecode_db_repo = self.db_repo
231 228
232 229 data = render('rhodecode:templates/forks/fork.mako',
233 230 self._get_template_context(c), self.request)
234 231 html = formencode.htmlfill.render(
235 232 data,
236 233 defaults=errors.value,
237 234 errors=errors.error_dict or {},
238 235 prefix_error=False,
239 236 encoding="UTF-8",
240 237 force_defaults=False
241 238 )
242 239 return Response(html)
243 240 except Exception:
244 241 log.exception(
245 242 u'Exception while trying to fork the repository %s',
246 243 self.db_repo_name)
247 244 msg = (
248 245 _('An error occurred during repository forking %s') % (
249 246 self.db_repo_name, ))
250 247 h.flash(msg, category='error')
251 248
252 249 repo_name = form_result.get('repo_name_full', self.db_repo_name)
253 250 raise HTTPFound(
254 251 h.route_path('repo_creating',
255 252 repo_name=repo_name,
256 253 _query=dict(task_id=task_id)))
@@ -1,67 +1,64 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 import logging
22 22
23 23 from pyramid.view import view_config
24 24
25 25 from rhodecode.apps._base import RepoAppView
26 26 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
27 27 from rhodecode.lib import repo_maintenance
28 28
29 29 log = logging.getLogger(__name__)
30 30
31 31
32 32 class RepoMaintenanceView(RepoAppView):
33 33 def load_default_context(self):
34 34 c = self._get_local_tmpl_context()
35 35
36 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
37 c.repo_info = self.db_repo
38
39 36 self._register_global_c(c)
40 37 return c
41 38
42 39 @LoginRequired()
43 40 @HasRepoPermissionAnyDecorator('repository.admin')
44 41 @view_config(
45 42 route_name='edit_repo_maintenance', request_method='GET',
46 43 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
47 44 def repo_maintenance(self):
48 45 c = self.load_default_context()
49 46 c.active = 'maintenance'
50 47 maintenance = repo_maintenance.RepoMaintenance()
51 48 c.executable_tasks = maintenance.get_tasks_for_repo(self.db_repo)
52 49 return self._get_template_context(c)
53 50
54 51 @LoginRequired()
55 52 @HasRepoPermissionAnyDecorator('repository.admin')
56 53 @view_config(
57 54 route_name='edit_repo_maintenance_execute', request_method='GET',
58 55 renderer='json', xhr=True)
59 56 def repo_maintenance_execute(self):
60 57 c = self.load_default_context()
61 58 c.active = 'maintenance'
62 59 _ = self.request.translate
63 60
64 61 maintenance = repo_maintenance.RepoMaintenance()
65 62 executed_types = maintenance.execute(self.db_repo)
66 63
67 64 return executed_types
@@ -1,92 +1,89 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 import logging
22 22
23 23 from pyramid.httpexceptions import HTTPFound
24 24 from pyramid.view import view_config
25 25
26 26 from rhodecode.apps._base import RepoAppView
27 27 from rhodecode.lib import helpers as h
28 28 from rhodecode.lib import audit_logger
29 29 from rhodecode.lib.auth import (
30 30 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
31 31 from rhodecode.model.forms import RepoPermsForm
32 32 from rhodecode.model.meta import Session
33 33 from rhodecode.model.repo import RepoModel
34 34
35 35 log = logging.getLogger(__name__)
36 36
37 37
38 38 class RepoSettingsPermissionsView(RepoAppView):
39 39
40 40 def load_default_context(self):
41 41 c = self._get_local_tmpl_context()
42 42
43 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
44 c.repo_info = self.db_repo
45
46 43 self._register_global_c(c)
47 44 return c
48 45
49 46 @LoginRequired()
50 47 @HasRepoPermissionAnyDecorator('repository.admin')
51 48 @view_config(
52 49 route_name='edit_repo_perms', request_method='GET',
53 50 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
54 51 def edit_permissions(self):
55 52 c = self.load_default_context()
56 53 c.active = 'permissions'
57 54 return self._get_template_context(c)
58 55
59 56 @LoginRequired()
60 57 @HasRepoPermissionAnyDecorator('repository.admin')
61 58 @CSRFRequired()
62 59 @view_config(
63 60 route_name='edit_repo_perms', request_method='POST',
64 61 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
65 62 def edit_permissions_update(self):
66 63 _ = self.request.translate
67 64 c = self.load_default_context()
68 65 c.active = 'permissions'
69 66 data = self.request.POST
70 67 # store private flag outside of HTML to verify if we can modify
71 68 # default user permissions, prevents submission of FAKE post data
72 69 # into the form for private repos
73 70 data['repo_private'] = self.db_repo.private
74 71 form = RepoPermsForm()().to_python(data)
75 72 changes = RepoModel().update_permissions(
76 73 self.db_repo_name, form['perm_additions'], form['perm_updates'],
77 74 form['perm_deletions'])
78 75
79 76 action_data = {
80 77 'added': changes['added'],
81 78 'updated': changes['updated'],
82 79 'deleted': changes['deleted'],
83 80 }
84 81 audit_logger.store_web(
85 82 'repo.edit.permissions', action_data=action_data,
86 83 user=self._rhodecode_user, repo=self.db_repo)
87 84
88 85 Session().commit()
89 86 h.flash(_('Repository permissions updated'), category='success')
90 87
91 88 raise HTTPFound(
92 89 h.route_path('edit_repo_perms', repo_name=self.db_repo_name))
@@ -1,1194 +1,1192 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 import logging
22 22 import collections
23 23
24 24 import formencode
25 25 import formencode.htmlfill
26 26 import peppercorn
27 27 from pyramid.httpexceptions import (
28 28 HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest)
29 29 from pyramid.view import view_config
30 30 from pyramid.renderers import render
31 31
32 32 from rhodecode import events
33 33 from rhodecode.apps._base import RepoAppView, DataGridAppView
34 34
35 35 from rhodecode.lib import helpers as h, diffs, codeblocks, channelstream
36 36 from rhodecode.lib.base import vcs_operation_context
37 37 from rhodecode.lib.ext_json import json
38 38 from rhodecode.lib.auth import (
39 39 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
40 40 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode
41 41 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
42 42 from rhodecode.lib.vcs.exceptions import (CommitDoesNotExistError,
43 43 RepositoryRequirementError, NodeDoesNotExistError, EmptyRepositoryError)
44 44 from rhodecode.model.changeset_status import ChangesetStatusModel
45 45 from rhodecode.model.comment import CommentsModel
46 46 from rhodecode.model.db import (func, or_, PullRequest, PullRequestVersion,
47 47 ChangesetComment, ChangesetStatus, Repository)
48 48 from rhodecode.model.forms import PullRequestForm
49 49 from rhodecode.model.meta import Session
50 50 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
51 51 from rhodecode.model.scm import ScmModel
52 52
53 53 log = logging.getLogger(__name__)
54 54
55 55
56 56 class RepoPullRequestsView(RepoAppView, DataGridAppView):
57 57
58 58 def load_default_context(self):
59 59 c = self._get_local_tmpl_context(include_app_defaults=True)
60 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
61 c.repo_info = self.db_repo
62 60 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
63 61 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
64 62 self._register_global_c(c)
65 63 return c
66 64
67 65 def _get_pull_requests_list(
68 66 self, repo_name, source, filter_type, opened_by, statuses):
69 67
70 68 draw, start, limit = self._extract_chunk(self.request)
71 69 search_q, order_by, order_dir = self._extract_ordering(self.request)
72 70 _render = self.request.get_partial_renderer(
73 71 'data_table/_dt_elements.mako')
74 72
75 73 # pagination
76 74
77 75 if filter_type == 'awaiting_review':
78 76 pull_requests = PullRequestModel().get_awaiting_review(
79 77 repo_name, source=source, opened_by=opened_by,
80 78 statuses=statuses, offset=start, length=limit,
81 79 order_by=order_by, order_dir=order_dir)
82 80 pull_requests_total_count = PullRequestModel().count_awaiting_review(
83 81 repo_name, source=source, statuses=statuses,
84 82 opened_by=opened_by)
85 83 elif filter_type == 'awaiting_my_review':
86 84 pull_requests = PullRequestModel().get_awaiting_my_review(
87 85 repo_name, source=source, opened_by=opened_by,
88 86 user_id=self._rhodecode_user.user_id, statuses=statuses,
89 87 offset=start, length=limit, order_by=order_by,
90 88 order_dir=order_dir)
91 89 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
92 90 repo_name, source=source, user_id=self._rhodecode_user.user_id,
93 91 statuses=statuses, opened_by=opened_by)
94 92 else:
95 93 pull_requests = PullRequestModel().get_all(
96 94 repo_name, source=source, opened_by=opened_by,
97 95 statuses=statuses, offset=start, length=limit,
98 96 order_by=order_by, order_dir=order_dir)
99 97 pull_requests_total_count = PullRequestModel().count_all(
100 98 repo_name, source=source, statuses=statuses,
101 99 opened_by=opened_by)
102 100
103 101 data = []
104 102 comments_model = CommentsModel()
105 103 for pr in pull_requests:
106 104 comments = comments_model.get_all_comments(
107 105 self.db_repo.repo_id, pull_request=pr)
108 106
109 107 data.append({
110 108 'name': _render('pullrequest_name',
111 109 pr.pull_request_id, pr.target_repo.repo_name),
112 110 'name_raw': pr.pull_request_id,
113 111 'status': _render('pullrequest_status',
114 112 pr.calculated_review_status()),
115 113 'title': _render(
116 114 'pullrequest_title', pr.title, pr.description),
117 115 'description': h.escape(pr.description),
118 116 'updated_on': _render('pullrequest_updated_on',
119 117 h.datetime_to_time(pr.updated_on)),
120 118 'updated_on_raw': h.datetime_to_time(pr.updated_on),
121 119 'created_on': _render('pullrequest_updated_on',
122 120 h.datetime_to_time(pr.created_on)),
123 121 'created_on_raw': h.datetime_to_time(pr.created_on),
124 122 'author': _render('pullrequest_author',
125 123 pr.author.full_contact, ),
126 124 'author_raw': pr.author.full_name,
127 125 'comments': _render('pullrequest_comments', len(comments)),
128 126 'comments_raw': len(comments),
129 127 'closed': pr.is_closed(),
130 128 })
131 129
132 130 data = ({
133 131 'draw': draw,
134 132 'data': data,
135 133 'recordsTotal': pull_requests_total_count,
136 134 'recordsFiltered': pull_requests_total_count,
137 135 })
138 136 return data
139 137
140 138 @LoginRequired()
141 139 @HasRepoPermissionAnyDecorator(
142 140 'repository.read', 'repository.write', 'repository.admin')
143 141 @view_config(
144 142 route_name='pullrequest_show_all', request_method='GET',
145 143 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
146 144 def pull_request_list(self):
147 145 c = self.load_default_context()
148 146
149 147 req_get = self.request.GET
150 148 c.source = str2bool(req_get.get('source'))
151 149 c.closed = str2bool(req_get.get('closed'))
152 150 c.my = str2bool(req_get.get('my'))
153 151 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
154 152 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
155 153
156 154 c.active = 'open'
157 155 if c.my:
158 156 c.active = 'my'
159 157 if c.closed:
160 158 c.active = 'closed'
161 159 if c.awaiting_review and not c.source:
162 160 c.active = 'awaiting'
163 161 if c.source and not c.awaiting_review:
164 162 c.active = 'source'
165 163 if c.awaiting_my_review:
166 164 c.active = 'awaiting_my'
167 165
168 166 return self._get_template_context(c)
169 167
170 168 @LoginRequired()
171 169 @HasRepoPermissionAnyDecorator(
172 170 'repository.read', 'repository.write', 'repository.admin')
173 171 @view_config(
174 172 route_name='pullrequest_show_all_data', request_method='GET',
175 173 renderer='json_ext', xhr=True)
176 174 def pull_request_list_data(self):
177 175
178 176 # additional filters
179 177 req_get = self.request.GET
180 178 source = str2bool(req_get.get('source'))
181 179 closed = str2bool(req_get.get('closed'))
182 180 my = str2bool(req_get.get('my'))
183 181 awaiting_review = str2bool(req_get.get('awaiting_review'))
184 182 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
185 183
186 184 filter_type = 'awaiting_review' if awaiting_review \
187 185 else 'awaiting_my_review' if awaiting_my_review \
188 186 else None
189 187
190 188 opened_by = None
191 189 if my:
192 190 opened_by = [self._rhodecode_user.user_id]
193 191
194 192 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
195 193 if closed:
196 194 statuses = [PullRequest.STATUS_CLOSED]
197 195
198 196 data = self._get_pull_requests_list(
199 197 repo_name=self.db_repo_name, source=source,
200 198 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
201 199
202 200 return data
203 201
204 202 def _get_pr_version(self, pull_request_id, version=None):
205 203 at_version = None
206 204
207 205 if version and version == 'latest':
208 206 pull_request_ver = PullRequest.get(pull_request_id)
209 207 pull_request_obj = pull_request_ver
210 208 _org_pull_request_obj = pull_request_obj
211 209 at_version = 'latest'
212 210 elif version:
213 211 pull_request_ver = PullRequestVersion.get_or_404(version)
214 212 pull_request_obj = pull_request_ver
215 213 _org_pull_request_obj = pull_request_ver.pull_request
216 214 at_version = pull_request_ver.pull_request_version_id
217 215 else:
218 216 _org_pull_request_obj = pull_request_obj = PullRequest.get_or_404(
219 217 pull_request_id)
220 218
221 219 pull_request_display_obj = PullRequest.get_pr_display_object(
222 220 pull_request_obj, _org_pull_request_obj)
223 221
224 222 return _org_pull_request_obj, pull_request_obj, \
225 223 pull_request_display_obj, at_version
226 224
227 225 def _get_diffset(self, source_repo_name, source_repo,
228 226 source_ref_id, target_ref_id,
229 227 target_commit, source_commit, diff_limit, fulldiff,
230 228 file_limit, display_inline_comments):
231 229
232 230 vcs_diff = PullRequestModel().get_diff(
233 231 source_repo, source_ref_id, target_ref_id)
234 232
235 233 diff_processor = diffs.DiffProcessor(
236 234 vcs_diff, format='newdiff', diff_limit=diff_limit,
237 235 file_limit=file_limit, show_full_diff=fulldiff)
238 236
239 237 _parsed = diff_processor.prepare()
240 238
241 239 def _node_getter(commit):
242 240 def get_node(fname):
243 241 try:
244 242 return commit.get_node(fname)
245 243 except NodeDoesNotExistError:
246 244 return None
247 245
248 246 return get_node
249 247
250 248 diffset = codeblocks.DiffSet(
251 249 repo_name=self.db_repo_name,
252 250 source_repo_name=source_repo_name,
253 251 source_node_getter=_node_getter(target_commit),
254 252 target_node_getter=_node_getter(source_commit),
255 253 comments=display_inline_comments
256 254 )
257 255 diffset = diffset.render_patchset(
258 256 _parsed, target_commit.raw_id, source_commit.raw_id)
259 257
260 258 return diffset
261 259
262 260 @LoginRequired()
263 261 @HasRepoPermissionAnyDecorator(
264 262 'repository.read', 'repository.write', 'repository.admin')
265 263 @view_config(
266 264 route_name='pullrequest_show', request_method='GET',
267 265 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
268 266 def pull_request_show(self):
269 267 pull_request_id = self.request.matchdict['pull_request_id']
270 268
271 269 c = self.load_default_context()
272 270
273 271 version = self.request.GET.get('version')
274 272 from_version = self.request.GET.get('from_version') or version
275 273 merge_checks = self.request.GET.get('merge_checks')
276 274 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
277 275
278 276 (pull_request_latest,
279 277 pull_request_at_ver,
280 278 pull_request_display_obj,
281 279 at_version) = self._get_pr_version(
282 280 pull_request_id, version=version)
283 281 pr_closed = pull_request_latest.is_closed()
284 282
285 283 if pr_closed and (version or from_version):
286 284 # not allow to browse versions
287 285 raise HTTPFound(h.route_path(
288 286 'pullrequest_show', repo_name=self.db_repo_name,
289 287 pull_request_id=pull_request_id))
290 288
291 289 versions = pull_request_display_obj.versions()
292 290
293 291 c.at_version = at_version
294 292 c.at_version_num = (at_version
295 293 if at_version and at_version != 'latest'
296 294 else None)
297 295 c.at_version_pos = ChangesetComment.get_index_from_version(
298 296 c.at_version_num, versions)
299 297
300 298 (prev_pull_request_latest,
301 299 prev_pull_request_at_ver,
302 300 prev_pull_request_display_obj,
303 301 prev_at_version) = self._get_pr_version(
304 302 pull_request_id, version=from_version)
305 303
306 304 c.from_version = prev_at_version
307 305 c.from_version_num = (prev_at_version
308 306 if prev_at_version and prev_at_version != 'latest'
309 307 else None)
310 308 c.from_version_pos = ChangesetComment.get_index_from_version(
311 309 c.from_version_num, versions)
312 310
313 311 # define if we're in COMPARE mode or VIEW at version mode
314 312 compare = at_version != prev_at_version
315 313
316 314 # pull_requests repo_name we opened it against
317 315 # ie. target_repo must match
318 316 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
319 317 raise HTTPNotFound()
320 318
321 319 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
322 320 pull_request_at_ver)
323 321
324 322 c.pull_request = pull_request_display_obj
325 323 c.pull_request_latest = pull_request_latest
326 324
327 325 if compare or (at_version and not at_version == 'latest'):
328 326 c.allowed_to_change_status = False
329 327 c.allowed_to_update = False
330 328 c.allowed_to_merge = False
331 329 c.allowed_to_delete = False
332 330 c.allowed_to_comment = False
333 331 c.allowed_to_close = False
334 332 else:
335 333 can_change_status = PullRequestModel().check_user_change_status(
336 334 pull_request_at_ver, self._rhodecode_user)
337 335 c.allowed_to_change_status = can_change_status and not pr_closed
338 336
339 337 c.allowed_to_update = PullRequestModel().check_user_update(
340 338 pull_request_latest, self._rhodecode_user) and not pr_closed
341 339 c.allowed_to_merge = PullRequestModel().check_user_merge(
342 340 pull_request_latest, self._rhodecode_user) and not pr_closed
343 341 c.allowed_to_delete = PullRequestModel().check_user_delete(
344 342 pull_request_latest, self._rhodecode_user) and not pr_closed
345 343 c.allowed_to_comment = not pr_closed
346 344 c.allowed_to_close = c.allowed_to_merge and not pr_closed
347 345
348 346 c.forbid_adding_reviewers = False
349 347 c.forbid_author_to_review = False
350 348 c.forbid_commit_author_to_review = False
351 349
352 350 if pull_request_latest.reviewer_data and \
353 351 'rules' in pull_request_latest.reviewer_data:
354 352 rules = pull_request_latest.reviewer_data['rules'] or {}
355 353 try:
356 354 c.forbid_adding_reviewers = rules.get(
357 355 'forbid_adding_reviewers')
358 356 c.forbid_author_to_review = rules.get(
359 357 'forbid_author_to_review')
360 358 c.forbid_commit_author_to_review = rules.get(
361 359 'forbid_commit_author_to_review')
362 360 except Exception:
363 361 pass
364 362
365 363 # check merge capabilities
366 364 _merge_check = MergeCheck.validate(
367 365 pull_request_latest, user=self._rhodecode_user)
368 366 c.pr_merge_errors = _merge_check.error_details
369 367 c.pr_merge_possible = not _merge_check.failed
370 368 c.pr_merge_message = _merge_check.merge_msg
371 369
372 370 c.pr_merge_info = MergeCheck.get_merge_conditions(pull_request_latest)
373 371
374 372 c.pull_request_review_status = _merge_check.review_status
375 373 if merge_checks:
376 374 self.request.override_renderer = \
377 375 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
378 376 return self._get_template_context(c)
379 377
380 378 comments_model = CommentsModel()
381 379
382 380 # reviewers and statuses
383 381 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
384 382 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
385 383
386 384 # GENERAL COMMENTS with versions #
387 385 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
388 386 q = q.order_by(ChangesetComment.comment_id.asc())
389 387 general_comments = q
390 388
391 389 # pick comments we want to render at current version
392 390 c.comment_versions = comments_model.aggregate_comments(
393 391 general_comments, versions, c.at_version_num)
394 392 c.comments = c.comment_versions[c.at_version_num]['until']
395 393
396 394 # INLINE COMMENTS with versions #
397 395 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
398 396 q = q.order_by(ChangesetComment.comment_id.asc())
399 397 inline_comments = q
400 398
401 399 c.inline_versions = comments_model.aggregate_comments(
402 400 inline_comments, versions, c.at_version_num, inline=True)
403 401
404 402 # inject latest version
405 403 latest_ver = PullRequest.get_pr_display_object(
406 404 pull_request_latest, pull_request_latest)
407 405
408 406 c.versions = versions + [latest_ver]
409 407
410 408 # if we use version, then do not show later comments
411 409 # than current version
412 410 display_inline_comments = collections.defaultdict(
413 411 lambda: collections.defaultdict(list))
414 412 for co in inline_comments:
415 413 if c.at_version_num:
416 414 # pick comments that are at least UPTO given version, so we
417 415 # don't render comments for higher version
418 416 should_render = co.pull_request_version_id and \
419 417 co.pull_request_version_id <= c.at_version_num
420 418 else:
421 419 # showing all, for 'latest'
422 420 should_render = True
423 421
424 422 if should_render:
425 423 display_inline_comments[co.f_path][co.line_no].append(co)
426 424
427 425 # load diff data into template context, if we use compare mode then
428 426 # diff is calculated based on changes between versions of PR
429 427
430 428 source_repo = pull_request_at_ver.source_repo
431 429 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
432 430
433 431 target_repo = pull_request_at_ver.target_repo
434 432 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
435 433
436 434 if compare:
437 435 # in compare switch the diff base to latest commit from prev version
438 436 target_ref_id = prev_pull_request_display_obj.revisions[0]
439 437
440 438 # despite opening commits for bookmarks/branches/tags, we always
441 439 # convert this to rev to prevent changes after bookmark or branch change
442 440 c.source_ref_type = 'rev'
443 441 c.source_ref = source_ref_id
444 442
445 443 c.target_ref_type = 'rev'
446 444 c.target_ref = target_ref_id
447 445
448 446 c.source_repo = source_repo
449 447 c.target_repo = target_repo
450 448
451 449 c.commit_ranges = []
452 450 source_commit = EmptyCommit()
453 451 target_commit = EmptyCommit()
454 452 c.missing_requirements = False
455 453
456 454 source_scm = source_repo.scm_instance()
457 455 target_scm = target_repo.scm_instance()
458 456
459 457 # try first shadow repo, fallback to regular repo
460 458 try:
461 459 commits_source_repo = pull_request_latest.get_shadow_repo()
462 460 except Exception:
463 461 log.debug('Failed to get shadow repo', exc_info=True)
464 462 commits_source_repo = source_scm
465 463
466 464 c.commits_source_repo = commits_source_repo
467 465 commit_cache = {}
468 466 try:
469 467 pre_load = ["author", "branch", "date", "message"]
470 468 show_revs = pull_request_at_ver.revisions
471 469 for rev in show_revs:
472 470 comm = commits_source_repo.get_commit(
473 471 commit_id=rev, pre_load=pre_load)
474 472 c.commit_ranges.append(comm)
475 473 commit_cache[comm.raw_id] = comm
476 474
477 475 # Order here matters, we first need to get target, and then
478 476 # the source
479 477 target_commit = commits_source_repo.get_commit(
480 478 commit_id=safe_str(target_ref_id))
481 479
482 480 source_commit = commits_source_repo.get_commit(
483 481 commit_id=safe_str(source_ref_id))
484 482
485 483 except CommitDoesNotExistError:
486 484 log.warning(
487 485 'Failed to get commit from `{}` repo'.format(
488 486 commits_source_repo), exc_info=True)
489 487 except RepositoryRequirementError:
490 488 log.warning(
491 489 'Failed to get all required data from repo', exc_info=True)
492 490 c.missing_requirements = True
493 491
494 492 c.ancestor = None # set it to None, to hide it from PR view
495 493
496 494 try:
497 495 ancestor_id = source_scm.get_common_ancestor(
498 496 source_commit.raw_id, target_commit.raw_id, target_scm)
499 497 c.ancestor_commit = source_scm.get_commit(ancestor_id)
500 498 except Exception:
501 499 c.ancestor_commit = None
502 500
503 501 c.statuses = source_repo.statuses(
504 502 [x.raw_id for x in c.commit_ranges])
505 503
506 504 # auto collapse if we have more than limit
507 505 collapse_limit = diffs.DiffProcessor._collapse_commits_over
508 506 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
509 507 c.compare_mode = compare
510 508
511 509 # diff_limit is the old behavior, will cut off the whole diff
512 510 # if the limit is applied otherwise will just hide the
513 511 # big files from the front-end
514 512 diff_limit = c.visual.cut_off_limit_diff
515 513 file_limit = c.visual.cut_off_limit_file
516 514
517 515 c.missing_commits = False
518 516 if (c.missing_requirements
519 517 or isinstance(source_commit, EmptyCommit)
520 518 or source_commit == target_commit):
521 519
522 520 c.missing_commits = True
523 521 else:
524 522
525 523 c.diffset = self._get_diffset(
526 524 c.source_repo.repo_name, commits_source_repo,
527 525 source_ref_id, target_ref_id,
528 526 target_commit, source_commit,
529 527 diff_limit, c.fulldiff, file_limit, display_inline_comments)
530 528
531 529 c.limited_diff = c.diffset.limited_diff
532 530
533 531 # calculate removed files that are bound to comments
534 532 comment_deleted_files = [
535 533 fname for fname in display_inline_comments
536 534 if fname not in c.diffset.file_stats]
537 535
538 536 c.deleted_files_comments = collections.defaultdict(dict)
539 537 for fname, per_line_comments in display_inline_comments.items():
540 538 if fname in comment_deleted_files:
541 539 c.deleted_files_comments[fname]['stats'] = 0
542 540 c.deleted_files_comments[fname]['comments'] = list()
543 541 for lno, comments in per_line_comments.items():
544 542 c.deleted_files_comments[fname]['comments'].extend(
545 543 comments)
546 544
547 545 # this is a hack to properly display links, when creating PR, the
548 546 # compare view and others uses different notation, and
549 547 # compare_commits.mako renders links based on the target_repo.
550 548 # We need to swap that here to generate it properly on the html side
551 549 c.target_repo = c.source_repo
552 550
553 551 c.commit_statuses = ChangesetStatus.STATUSES
554 552
555 553 c.show_version_changes = not pr_closed
556 554 if c.show_version_changes:
557 555 cur_obj = pull_request_at_ver
558 556 prev_obj = prev_pull_request_at_ver
559 557
560 558 old_commit_ids = prev_obj.revisions
561 559 new_commit_ids = cur_obj.revisions
562 560 commit_changes = PullRequestModel()._calculate_commit_id_changes(
563 561 old_commit_ids, new_commit_ids)
564 562 c.commit_changes_summary = commit_changes
565 563
566 564 # calculate the diff for commits between versions
567 565 c.commit_changes = []
568 566 mark = lambda cs, fw: list(
569 567 h.itertools.izip_longest([], cs, fillvalue=fw))
570 568 for c_type, raw_id in mark(commit_changes.added, 'a') \
571 569 + mark(commit_changes.removed, 'r') \
572 570 + mark(commit_changes.common, 'c'):
573 571
574 572 if raw_id in commit_cache:
575 573 commit = commit_cache[raw_id]
576 574 else:
577 575 try:
578 576 commit = commits_source_repo.get_commit(raw_id)
579 577 except CommitDoesNotExistError:
580 578 # in case we fail extracting still use "dummy" commit
581 579 # for display in commit diff
582 580 commit = h.AttributeDict(
583 581 {'raw_id': raw_id,
584 582 'message': 'EMPTY or MISSING COMMIT'})
585 583 c.commit_changes.append([c_type, commit])
586 584
587 585 # current user review statuses for each version
588 586 c.review_versions = {}
589 587 if self._rhodecode_user.user_id in allowed_reviewers:
590 588 for co in general_comments:
591 589 if co.author.user_id == self._rhodecode_user.user_id:
592 590 # each comment has a status change
593 591 status = co.status_change
594 592 if status:
595 593 _ver_pr = status[0].comment.pull_request_version_id
596 594 c.review_versions[_ver_pr] = status[0]
597 595
598 596 return self._get_template_context(c)
599 597
600 598 def assure_not_empty_repo(self):
601 599 _ = self.request.translate
602 600
603 601 try:
604 602 self.db_repo.scm_instance().get_commit()
605 603 except EmptyRepositoryError:
606 604 h.flash(h.literal(_('There are no commits yet')),
607 605 category='warning')
608 606 raise HTTPFound(
609 607 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
610 608
611 609 @LoginRequired()
612 610 @NotAnonymous()
613 611 @HasRepoPermissionAnyDecorator(
614 612 'repository.read', 'repository.write', 'repository.admin')
615 613 @view_config(
616 614 route_name='pullrequest_new', request_method='GET',
617 615 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
618 616 def pull_request_new(self):
619 617 _ = self.request.translate
620 618 c = self.load_default_context()
621 619
622 620 self.assure_not_empty_repo()
623 621 source_repo = self.db_repo
624 622
625 623 commit_id = self.request.GET.get('commit')
626 624 branch_ref = self.request.GET.get('branch')
627 625 bookmark_ref = self.request.GET.get('bookmark')
628 626
629 627 try:
630 628 source_repo_data = PullRequestModel().generate_repo_data(
631 629 source_repo, commit_id=commit_id,
632 630 branch=branch_ref, bookmark=bookmark_ref)
633 631 except CommitDoesNotExistError as e:
634 632 log.exception(e)
635 633 h.flash(_('Commit does not exist'), 'error')
636 634 raise HTTPFound(
637 635 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
638 636
639 637 default_target_repo = source_repo
640 638
641 639 if source_repo.parent:
642 640 parent_vcs_obj = source_repo.parent.scm_instance()
643 641 if parent_vcs_obj and not parent_vcs_obj.is_empty():
644 642 # change default if we have a parent repo
645 643 default_target_repo = source_repo.parent
646 644
647 645 target_repo_data = PullRequestModel().generate_repo_data(
648 646 default_target_repo)
649 647
650 648 selected_source_ref = source_repo_data['refs']['selected_ref']
651 649
652 650 title_source_ref = selected_source_ref.split(':', 2)[1]
653 651 c.default_title = PullRequestModel().generate_pullrequest_title(
654 652 source=source_repo.repo_name,
655 653 source_ref=title_source_ref,
656 654 target=default_target_repo.repo_name
657 655 )
658 656
659 657 c.default_repo_data = {
660 658 'source_repo_name': source_repo.repo_name,
661 659 'source_refs_json': json.dumps(source_repo_data),
662 660 'target_repo_name': default_target_repo.repo_name,
663 661 'target_refs_json': json.dumps(target_repo_data),
664 662 }
665 663 c.default_source_ref = selected_source_ref
666 664
667 665 return self._get_template_context(c)
668 666
669 667 @LoginRequired()
670 668 @NotAnonymous()
671 669 @HasRepoPermissionAnyDecorator(
672 670 'repository.read', 'repository.write', 'repository.admin')
673 671 @view_config(
674 672 route_name='pullrequest_repo_refs', request_method='GET',
675 673 renderer='json_ext', xhr=True)
676 674 def pull_request_repo_refs(self):
677 675 target_repo_name = self.request.matchdict['target_repo_name']
678 676 repo = Repository.get_by_repo_name(target_repo_name)
679 677 if not repo:
680 678 raise HTTPNotFound()
681 679 return PullRequestModel().generate_repo_data(repo)
682 680
683 681 @LoginRequired()
684 682 @NotAnonymous()
685 683 @HasRepoPermissionAnyDecorator(
686 684 'repository.read', 'repository.write', 'repository.admin')
687 685 @view_config(
688 686 route_name='pullrequest_repo_destinations', request_method='GET',
689 687 renderer='json_ext', xhr=True)
690 688 def pull_request_repo_destinations(self):
691 689 _ = self.request.translate
692 690 filter_query = self.request.GET.get('query')
693 691
694 692 query = Repository.query() \
695 693 .order_by(func.length(Repository.repo_name)) \
696 694 .filter(
697 695 or_(Repository.repo_name == self.db_repo.repo_name,
698 696 Repository.fork_id == self.db_repo.repo_id))
699 697
700 698 if filter_query:
701 699 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
702 700 query = query.filter(
703 701 Repository.repo_name.ilike(ilike_expression))
704 702
705 703 add_parent = False
706 704 if self.db_repo.parent:
707 705 if filter_query in self.db_repo.parent.repo_name:
708 706 parent_vcs_obj = self.db_repo.parent.scm_instance()
709 707 if parent_vcs_obj and not parent_vcs_obj.is_empty():
710 708 add_parent = True
711 709
712 710 limit = 20 - 1 if add_parent else 20
713 711 all_repos = query.limit(limit).all()
714 712 if add_parent:
715 713 all_repos += [self.db_repo.parent]
716 714
717 715 repos = []
718 716 for obj in ScmModel().get_repos(all_repos):
719 717 repos.append({
720 718 'id': obj['name'],
721 719 'text': obj['name'],
722 720 'type': 'repo',
723 721 'obj': obj['dbrepo']
724 722 })
725 723
726 724 data = {
727 725 'more': False,
728 726 'results': [{
729 727 'text': _('Repositories'),
730 728 'children': repos
731 729 }] if repos else []
732 730 }
733 731 return data
734 732
735 733 @LoginRequired()
736 734 @NotAnonymous()
737 735 @HasRepoPermissionAnyDecorator(
738 736 'repository.read', 'repository.write', 'repository.admin')
739 737 @CSRFRequired()
740 738 @view_config(
741 739 route_name='pullrequest_create', request_method='POST',
742 740 renderer=None)
743 741 def pull_request_create(self):
744 742 _ = self.request.translate
745 743 self.assure_not_empty_repo()
746 744
747 745 controls = peppercorn.parse(self.request.POST.items())
748 746
749 747 try:
750 748 _form = PullRequestForm(self.db_repo.repo_id)().to_python(controls)
751 749 except formencode.Invalid as errors:
752 750 if errors.error_dict.get('revisions'):
753 751 msg = 'Revisions: %s' % errors.error_dict['revisions']
754 752 elif errors.error_dict.get('pullrequest_title'):
755 753 msg = _('Pull request requires a title with min. 3 chars')
756 754 else:
757 755 msg = _('Error creating pull request: {}').format(errors)
758 756 log.exception(msg)
759 757 h.flash(msg, 'error')
760 758
761 759 # would rather just go back to form ...
762 760 raise HTTPFound(
763 761 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
764 762
765 763 source_repo = _form['source_repo']
766 764 source_ref = _form['source_ref']
767 765 target_repo = _form['target_repo']
768 766 target_ref = _form['target_ref']
769 767 commit_ids = _form['revisions'][::-1]
770 768
771 769 # find the ancestor for this pr
772 770 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
773 771 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
774 772
775 773 source_scm = source_db_repo.scm_instance()
776 774 target_scm = target_db_repo.scm_instance()
777 775
778 776 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
779 777 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
780 778
781 779 ancestor = source_scm.get_common_ancestor(
782 780 source_commit.raw_id, target_commit.raw_id, target_scm)
783 781
784 782 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
785 783 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
786 784
787 785 pullrequest_title = _form['pullrequest_title']
788 786 title_source_ref = source_ref.split(':', 2)[1]
789 787 if not pullrequest_title:
790 788 pullrequest_title = PullRequestModel().generate_pullrequest_title(
791 789 source=source_repo,
792 790 source_ref=title_source_ref,
793 791 target=target_repo
794 792 )
795 793
796 794 description = _form['pullrequest_desc']
797 795
798 796 get_default_reviewers_data, validate_default_reviewers = \
799 797 PullRequestModel().get_reviewer_functions()
800 798
801 799 # recalculate reviewers logic, to make sure we can validate this
802 800 reviewer_rules = get_default_reviewers_data(
803 801 self._rhodecode_db_user, source_db_repo,
804 802 source_commit, target_db_repo, target_commit)
805 803
806 804 given_reviewers = _form['review_members']
807 805 reviewers = validate_default_reviewers(given_reviewers, reviewer_rules)
808 806
809 807 try:
810 808 pull_request = PullRequestModel().create(
811 809 self._rhodecode_user.user_id, source_repo, source_ref, target_repo,
812 810 target_ref, commit_ids, reviewers, pullrequest_title,
813 811 description, reviewer_rules
814 812 )
815 813 Session().commit()
816 814 h.flash(_('Successfully opened new pull request'),
817 815 category='success')
818 816 except Exception:
819 817 msg = _('Error occurred during creation of this pull request.')
820 818 log.exception(msg)
821 819 h.flash(msg, category='error')
822 820
823 821 # copy the args back to redirect
824 822 org_query = self.request.GET.mixed()
825 823 raise HTTPFound(
826 824 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
827 825 _query=org_query))
828 826
829 827 raise HTTPFound(
830 828 h.route_path('pullrequest_show', repo_name=target_repo,
831 829 pull_request_id=pull_request.pull_request_id))
832 830
833 831 @LoginRequired()
834 832 @NotAnonymous()
835 833 @HasRepoPermissionAnyDecorator(
836 834 'repository.read', 'repository.write', 'repository.admin')
837 835 @CSRFRequired()
838 836 @view_config(
839 837 route_name='pullrequest_update', request_method='POST',
840 838 renderer='json_ext')
841 839 def pull_request_update(self):
842 840 pull_request = PullRequest.get_or_404(
843 841 self.request.matchdict['pull_request_id'])
844 842
845 843 # only owner or admin can update it
846 844 allowed_to_update = PullRequestModel().check_user_update(
847 845 pull_request, self._rhodecode_user)
848 846 if allowed_to_update:
849 847 controls = peppercorn.parse(self.request.POST.items())
850 848
851 849 if 'review_members' in controls:
852 850 self._update_reviewers(
853 851 pull_request, controls['review_members'],
854 852 pull_request.reviewer_data)
855 853 elif str2bool(self.request.POST.get('update_commits', 'false')):
856 854 self._update_commits(pull_request)
857 855 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
858 856 self._edit_pull_request(pull_request)
859 857 else:
860 858 raise HTTPBadRequest()
861 859 return True
862 860 raise HTTPForbidden()
863 861
864 862 def _edit_pull_request(self, pull_request):
865 863 _ = self.request.translate
866 864 try:
867 865 PullRequestModel().edit(
868 866 pull_request, self.request.POST.get('title'),
869 867 self.request.POST.get('description'), self._rhodecode_user)
870 868 except ValueError:
871 869 msg = _(u'Cannot update closed pull requests.')
872 870 h.flash(msg, category='error')
873 871 return
874 872 else:
875 873 Session().commit()
876 874
877 875 msg = _(u'Pull request title & description updated.')
878 876 h.flash(msg, category='success')
879 877 return
880 878
881 879 def _update_commits(self, pull_request):
882 880 _ = self.request.translate
883 881 resp = PullRequestModel().update_commits(pull_request)
884 882
885 883 if resp.executed:
886 884
887 885 if resp.target_changed and resp.source_changed:
888 886 changed = 'target and source repositories'
889 887 elif resp.target_changed and not resp.source_changed:
890 888 changed = 'target repository'
891 889 elif not resp.target_changed and resp.source_changed:
892 890 changed = 'source repository'
893 891 else:
894 892 changed = 'nothing'
895 893
896 894 msg = _(
897 895 u'Pull request updated to "{source_commit_id}" with '
898 896 u'{count_added} added, {count_removed} removed commits. '
899 897 u'Source of changes: {change_source}')
900 898 msg = msg.format(
901 899 source_commit_id=pull_request.source_ref_parts.commit_id,
902 900 count_added=len(resp.changes.added),
903 901 count_removed=len(resp.changes.removed),
904 902 change_source=changed)
905 903 h.flash(msg, category='success')
906 904
907 905 channel = '/repo${}$/pr/{}'.format(
908 906 pull_request.target_repo.repo_name,
909 907 pull_request.pull_request_id)
910 908 message = msg + (
911 909 ' - <a onclick="window.location.reload()">'
912 910 '<strong>{}</strong></a>'.format(_('Reload page')))
913 911 channelstream.post_message(
914 912 channel, message, self._rhodecode_user.username,
915 913 registry=self.request.registry)
916 914 else:
917 915 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
918 916 warning_reasons = [
919 917 UpdateFailureReason.NO_CHANGE,
920 918 UpdateFailureReason.WRONG_REF_TYPE,
921 919 ]
922 920 category = 'warning' if resp.reason in warning_reasons else 'error'
923 921 h.flash(msg, category=category)
924 922
925 923 @LoginRequired()
926 924 @NotAnonymous()
927 925 @HasRepoPermissionAnyDecorator(
928 926 'repository.read', 'repository.write', 'repository.admin')
929 927 @CSRFRequired()
930 928 @view_config(
931 929 route_name='pullrequest_merge', request_method='POST',
932 930 renderer='json_ext')
933 931 def pull_request_merge(self):
934 932 """
935 933 Merge will perform a server-side merge of the specified
936 934 pull request, if the pull request is approved and mergeable.
937 935 After successful merging, the pull request is automatically
938 936 closed, with a relevant comment.
939 937 """
940 938 pull_request = PullRequest.get_or_404(
941 939 self.request.matchdict['pull_request_id'])
942 940
943 941 check = MergeCheck.validate(pull_request, self._rhodecode_db_user)
944 942 merge_possible = not check.failed
945 943
946 944 for err_type, error_msg in check.errors:
947 945 h.flash(error_msg, category=err_type)
948 946
949 947 if merge_possible:
950 948 log.debug("Pre-conditions checked, trying to merge.")
951 949 extras = vcs_operation_context(
952 950 self.request.environ, repo_name=pull_request.target_repo.repo_name,
953 951 username=self._rhodecode_db_user.username, action='push',
954 952 scm=pull_request.target_repo.repo_type)
955 953 self._merge_pull_request(
956 954 pull_request, self._rhodecode_db_user, extras)
957 955 else:
958 956 log.debug("Pre-conditions failed, NOT merging.")
959 957
960 958 raise HTTPFound(
961 959 h.route_path('pullrequest_show',
962 960 repo_name=pull_request.target_repo.repo_name,
963 961 pull_request_id=pull_request.pull_request_id))
964 962
965 963 def _merge_pull_request(self, pull_request, user, extras):
966 964 _ = self.request.translate
967 965 merge_resp = PullRequestModel().merge(pull_request, user, extras=extras)
968 966
969 967 if merge_resp.executed:
970 968 log.debug("The merge was successful, closing the pull request.")
971 969 PullRequestModel().close_pull_request(
972 970 pull_request.pull_request_id, user)
973 971 Session().commit()
974 972 msg = _('Pull request was successfully merged and closed.')
975 973 h.flash(msg, category='success')
976 974 else:
977 975 log.debug(
978 976 "The merge was not successful. Merge response: %s",
979 977 merge_resp)
980 978 msg = PullRequestModel().merge_status_message(
981 979 merge_resp.failure_reason)
982 980 h.flash(msg, category='error')
983 981
984 982 def _update_reviewers(self, pull_request, review_members, reviewer_rules):
985 983 _ = self.request.translate
986 984 get_default_reviewers_data, validate_default_reviewers = \
987 985 PullRequestModel().get_reviewer_functions()
988 986
989 987 try:
990 988 reviewers = validate_default_reviewers(review_members, reviewer_rules)
991 989 except ValueError as e:
992 990 log.error('Reviewers Validation: {}'.format(e))
993 991 h.flash(e, category='error')
994 992 return
995 993
996 994 PullRequestModel().update_reviewers(
997 995 pull_request, reviewers, self._rhodecode_user)
998 996 h.flash(_('Pull request reviewers updated.'), category='success')
999 997 Session().commit()
1000 998
1001 999 @LoginRequired()
1002 1000 @NotAnonymous()
1003 1001 @HasRepoPermissionAnyDecorator(
1004 1002 'repository.read', 'repository.write', 'repository.admin')
1005 1003 @CSRFRequired()
1006 1004 @view_config(
1007 1005 route_name='pullrequest_delete', request_method='POST',
1008 1006 renderer='json_ext')
1009 1007 def pull_request_delete(self):
1010 1008 _ = self.request.translate
1011 1009
1012 1010 pull_request = PullRequest.get_or_404(
1013 1011 self.request.matchdict['pull_request_id'])
1014 1012
1015 1013 pr_closed = pull_request.is_closed()
1016 1014 allowed_to_delete = PullRequestModel().check_user_delete(
1017 1015 pull_request, self._rhodecode_user) and not pr_closed
1018 1016
1019 1017 # only owner can delete it !
1020 1018 if allowed_to_delete:
1021 1019 PullRequestModel().delete(pull_request, self._rhodecode_user)
1022 1020 Session().commit()
1023 1021 h.flash(_('Successfully deleted pull request'),
1024 1022 category='success')
1025 1023 raise HTTPFound(h.route_path('pullrequest_show_all',
1026 1024 repo_name=self.db_repo_name))
1027 1025
1028 1026 log.warning('user %s tried to delete pull request without access',
1029 1027 self._rhodecode_user)
1030 1028 raise HTTPNotFound()
1031 1029
1032 1030 @LoginRequired()
1033 1031 @NotAnonymous()
1034 1032 @HasRepoPermissionAnyDecorator(
1035 1033 'repository.read', 'repository.write', 'repository.admin')
1036 1034 @CSRFRequired()
1037 1035 @view_config(
1038 1036 route_name='pullrequest_comment_create', request_method='POST',
1039 1037 renderer='json_ext')
1040 1038 def pull_request_comment_create(self):
1041 1039 _ = self.request.translate
1042 1040
1043 1041 pull_request = PullRequest.get_or_404(
1044 1042 self.request.matchdict['pull_request_id'])
1045 1043 pull_request_id = pull_request.pull_request_id
1046 1044
1047 1045 if pull_request.is_closed():
1048 1046 log.debug('comment: forbidden because pull request is closed')
1049 1047 raise HTTPForbidden()
1050 1048
1051 1049 c = self.load_default_context()
1052 1050
1053 1051 status = self.request.POST.get('changeset_status', None)
1054 1052 text = self.request.POST.get('text')
1055 1053 comment_type = self.request.POST.get('comment_type')
1056 1054 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
1057 1055 close_pull_request = self.request.POST.get('close_pull_request')
1058 1056
1059 1057 # the logic here should work like following, if we submit close
1060 1058 # pr comment, use `close_pull_request_with_comment` function
1061 1059 # else handle regular comment logic
1062 1060
1063 1061 if close_pull_request:
1064 1062 # only owner or admin or person with write permissions
1065 1063 allowed_to_close = PullRequestModel().check_user_update(
1066 1064 pull_request, self._rhodecode_user)
1067 1065 if not allowed_to_close:
1068 1066 log.debug('comment: forbidden because not allowed to close '
1069 1067 'pull request %s', pull_request_id)
1070 1068 raise HTTPForbidden()
1071 1069 comment, status = PullRequestModel().close_pull_request_with_comment(
1072 1070 pull_request, self._rhodecode_user, self.db_repo, message=text)
1073 1071 Session().flush()
1074 1072 events.trigger(
1075 1073 events.PullRequestCommentEvent(pull_request, comment))
1076 1074
1077 1075 else:
1078 1076 # regular comment case, could be inline, or one with status.
1079 1077 # for that one we check also permissions
1080 1078
1081 1079 allowed_to_change_status = PullRequestModel().check_user_change_status(
1082 1080 pull_request, self._rhodecode_user)
1083 1081
1084 1082 if status and allowed_to_change_status:
1085 1083 message = (_('Status change %(transition_icon)s %(status)s')
1086 1084 % {'transition_icon': '>',
1087 1085 'status': ChangesetStatus.get_status_lbl(status)})
1088 1086 text = text or message
1089 1087
1090 1088 comment = CommentsModel().create(
1091 1089 text=text,
1092 1090 repo=self.db_repo.repo_id,
1093 1091 user=self._rhodecode_user.user_id,
1094 1092 pull_request=pull_request,
1095 1093 f_path=self.request.POST.get('f_path'),
1096 1094 line_no=self.request.POST.get('line'),
1097 1095 status_change=(ChangesetStatus.get_status_lbl(status)
1098 1096 if status and allowed_to_change_status else None),
1099 1097 status_change_type=(status
1100 1098 if status and allowed_to_change_status else None),
1101 1099 comment_type=comment_type,
1102 1100 resolves_comment_id=resolves_comment_id
1103 1101 )
1104 1102
1105 1103 if allowed_to_change_status:
1106 1104 # calculate old status before we change it
1107 1105 old_calculated_status = pull_request.calculated_review_status()
1108 1106
1109 1107 # get status if set !
1110 1108 if status:
1111 1109 ChangesetStatusModel().set_status(
1112 1110 self.db_repo.repo_id,
1113 1111 status,
1114 1112 self._rhodecode_user.user_id,
1115 1113 comment,
1116 1114 pull_request=pull_request
1117 1115 )
1118 1116
1119 1117 Session().flush()
1120 1118 events.trigger(
1121 1119 events.PullRequestCommentEvent(pull_request, comment))
1122 1120
1123 1121 # we now calculate the status of pull request, and based on that
1124 1122 # calculation we set the commits status
1125 1123 calculated_status = pull_request.calculated_review_status()
1126 1124 if old_calculated_status != calculated_status:
1127 1125 PullRequestModel()._trigger_pull_request_hook(
1128 1126 pull_request, self._rhodecode_user, 'review_status_change')
1129 1127
1130 1128 Session().commit()
1131 1129
1132 1130 data = {
1133 1131 'target_id': h.safeid(h.safe_unicode(
1134 1132 self.request.POST.get('f_path'))),
1135 1133 }
1136 1134 if comment:
1137 1135 c.co = comment
1138 1136 rendered_comment = render(
1139 1137 'rhodecode:templates/changeset/changeset_comment_block.mako',
1140 1138 self._get_template_context(c), self.request)
1141 1139
1142 1140 data.update(comment.get_dict())
1143 1141 data.update({'rendered_text': rendered_comment})
1144 1142
1145 1143 return data
1146 1144
1147 1145 @LoginRequired()
1148 1146 @NotAnonymous()
1149 1147 @HasRepoPermissionAnyDecorator(
1150 1148 'repository.read', 'repository.write', 'repository.admin')
1151 1149 @CSRFRequired()
1152 1150 @view_config(
1153 1151 route_name='pullrequest_comment_delete', request_method='POST',
1154 1152 renderer='json_ext')
1155 1153 def pull_request_comment_delete(self):
1156 1154 pull_request = PullRequest.get_or_404(
1157 1155 self.request.matchdict['pull_request_id'])
1158 1156
1159 1157 comment = ChangesetComment.get_or_404(
1160 1158 self.request.matchdict['comment_id'])
1161 1159 comment_id = comment.comment_id
1162 1160
1163 1161 if pull_request.is_closed():
1164 1162 log.debug('comment: forbidden because pull request is closed')
1165 1163 raise HTTPForbidden()
1166 1164
1167 1165 if not comment:
1168 1166 log.debug('Comment with id:%s not found, skipping', comment_id)
1169 1167 # comment already deleted in another call probably
1170 1168 return True
1171 1169
1172 1170 if comment.pull_request.is_closed():
1173 1171 # don't allow deleting comments on closed pull request
1174 1172 raise HTTPForbidden()
1175 1173
1176 1174 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1177 1175 super_admin = h.HasPermissionAny('hg.admin')()
1178 1176 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1179 1177 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1180 1178 comment_repo_admin = is_repo_admin and is_repo_comment
1181 1179
1182 1180 if super_admin or comment_owner or comment_repo_admin:
1183 1181 old_calculated_status = comment.pull_request.calculated_review_status()
1184 1182 CommentsModel().delete(comment=comment, user=self._rhodecode_user)
1185 1183 Session().commit()
1186 1184 calculated_status = comment.pull_request.calculated_review_status()
1187 1185 if old_calculated_status != calculated_status:
1188 1186 PullRequestModel()._trigger_pull_request_hook(
1189 1187 comment.pull_request, self._rhodecode_user, 'review_status_change')
1190 1188 return True
1191 1189 else:
1192 1190 log.warning('No permissions for user %s to delete comment_id: %s',
1193 1191 self._rhodecode_db_user, comment_id)
1194 1192 raise HTTPNotFound()
@@ -1,64 +1,61 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 logging
22 22
23 23 from pyramid.view import view_config
24 24
25 25 from rhodecode.apps._base import RepoAppView
26 26 from rhodecode.apps.repository.utils import get_default_reviewers_data
27 27 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
28 28
29 29 log = logging.getLogger(__name__)
30 30
31 31
32 32 class RepoReviewRulesView(RepoAppView):
33 33 def load_default_context(self):
34 34 c = self._get_local_tmpl_context()
35 35
36 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
37 c.repo_info = self.db_repo
38
39 36 self._register_global_c(c)
40 37 return c
41 38
42 39 @LoginRequired()
43 40 @HasRepoPermissionAnyDecorator('repository.admin')
44 41 @view_config(
45 42 route_name='repo_reviewers', request_method='GET',
46 43 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
47 44 def repo_review_rules(self):
48 45 c = self.load_default_context()
49 46 c.active = 'reviewers'
50 47
51 48 return self._get_template_context(c)
52 49
53 50 @LoginRequired()
54 51 @HasRepoPermissionAnyDecorator(
55 52 'repository.read', 'repository.write', 'repository.admin')
56 53 @view_config(
57 54 route_name='repo_default_reviewers_data', request_method='GET',
58 55 renderer='json_ext')
59 56 def repo_default_reviewers_data(self):
60 57 review_data = get_default_reviewers_data(
61 58 self.db_repo.user, None, None, None, None)
62 59 return review_data
63 60
64 61
@@ -1,254 +1,251 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 import logging
22 22
23 23 import deform
24 24 from pyramid.httpexceptions import HTTPFound
25 25 from pyramid.view import view_config
26 26
27 27 from rhodecode.apps._base import RepoAppView
28 28 from rhodecode.forms import RcForm
29 29 from rhodecode.lib import helpers as h
30 30 from rhodecode.lib import audit_logger
31 31 from rhodecode.lib.auth import (
32 32 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
33 33 from rhodecode.model.db import RepositoryField, RepoGroup, Repository
34 34 from rhodecode.model.meta import Session
35 35 from rhodecode.model.repo import RepoModel
36 36 from rhodecode.model.scm import RepoGroupList, ScmModel
37 37 from rhodecode.model.validation_schema.schemas import repo_schema
38 38
39 39 log = logging.getLogger(__name__)
40 40
41 41
42 42 class RepoSettingsView(RepoAppView):
43 43
44 44 def load_default_context(self):
45 45 c = self._get_local_tmpl_context()
46 46
47 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
48 c.repo_info = self.db_repo
49
50 47 acl_groups = RepoGroupList(
51 48 RepoGroup.query().all(),
52 49 perm_set=['group.write', 'group.admin'])
53 50 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
54 51 c.repo_groups_choices = map(lambda k: k[0], c.repo_groups)
55 52
56 53 # in case someone no longer have a group.write access to a repository
57 54 # pre fill the list with this entry, we don't care if this is the same
58 55 # but it will allow saving repo data properly.
59 56 repo_group = self.db_repo.group
60 57 if repo_group and repo_group.group_id not in c.repo_groups_choices:
61 58 c.repo_groups_choices.append(repo_group.group_id)
62 59 c.repo_groups.append(RepoGroup._generate_choice(repo_group))
63 60
64 61 if c.repository_requirements_missing or self.rhodecode_vcs_repo is None:
65 62 # we might be in missing requirement state, so we load things
66 63 # without touching scm_instance()
67 64 c.landing_revs_choices, c.landing_revs = \
68 65 ScmModel().get_repo_landing_revs()
69 66 else:
70 67 c.landing_revs_choices, c.landing_revs = \
71 68 ScmModel().get_repo_landing_revs(self.db_repo)
72 69
73 70 c.personal_repo_group = c.auth_user.personal_repo_group
74 71 c.repo_fields = RepositoryField.query()\
75 72 .filter(RepositoryField.repository == self.db_repo).all()
76 73
77 74 self._register_global_c(c)
78 75 return c
79 76
80 77 def _get_schema(self, c, old_values=None):
81 78 return repo_schema.RepoSettingsSchema().bind(
82 79 repo_type=self.db_repo.repo_type,
83 80 repo_type_options=[self.db_repo.repo_type],
84 81 repo_ref_options=c.landing_revs_choices,
85 82 repo_ref_items=c.landing_revs,
86 83 repo_repo_group_options=c.repo_groups_choices,
87 84 repo_repo_group_items=c.repo_groups,
88 85 # user caller
89 86 user=self._rhodecode_user,
90 87 old_values=old_values
91 88 )
92 89
93 90 @LoginRequired()
94 91 @HasRepoPermissionAnyDecorator('repository.admin')
95 92 @view_config(
96 93 route_name='edit_repo', request_method='GET',
97 94 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
98 95 def edit_settings(self):
99 96 c = self.load_default_context()
100 97 c.active = 'settings'
101 98
102 99 defaults = RepoModel()._get_defaults(self.db_repo_name)
103 100 defaults['repo_owner'] = defaults['user']
104 101 defaults['repo_landing_commit_ref'] = defaults['repo_landing_rev']
105 102
106 103 schema = self._get_schema(c)
107 104 c.form = RcForm(schema, appstruct=defaults)
108 105 return self._get_template_context(c)
109 106
110 107 @LoginRequired()
111 108 @HasRepoPermissionAnyDecorator('repository.admin')
112 109 @CSRFRequired()
113 110 @view_config(
114 111 route_name='edit_repo', request_method='POST',
115 112 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
116 113 def edit_settings_update(self):
117 114 _ = self.request.translate
118 115 c = self.load_default_context()
119 116 c.active = 'settings'
120 117 old_repo_name = self.db_repo_name
121 118
122 119 old_values = self.db_repo.get_api_data()
123 120 schema = self._get_schema(c, old_values=old_values)
124 121
125 122 c.form = RcForm(schema)
126 123 pstruct = self.request.POST.items()
127 124 pstruct.append(('repo_type', self.db_repo.repo_type))
128 125 try:
129 126 schema_data = c.form.validate(pstruct)
130 127 except deform.ValidationFailure as err_form:
131 128 return self._get_template_context(c)
132 129
133 130 # data is now VALID, proceed with updates
134 131 # save validated data back into the updates dict
135 132 validated_updates = dict(
136 133 repo_name=schema_data['repo_group']['repo_name_without_group'],
137 134 repo_group=schema_data['repo_group']['repo_group_id'],
138 135
139 136 user=schema_data['repo_owner'],
140 137 repo_description=schema_data['repo_description'],
141 138 repo_private=schema_data['repo_private'],
142 139 clone_uri=schema_data['repo_clone_uri'],
143 140 repo_landing_rev=schema_data['repo_landing_commit_ref'],
144 141 repo_enable_statistics=schema_data['repo_enable_statistics'],
145 142 repo_enable_locking=schema_data['repo_enable_locking'],
146 143 repo_enable_downloads=schema_data['repo_enable_downloads'],
147 144 )
148 145 # detect if CLONE URI changed, if we get OLD means we keep old values
149 146 if schema_data['repo_clone_uri_change'] == 'OLD':
150 147 validated_updates['clone_uri'] = self.db_repo.clone_uri
151 148
152 149 # use the new full name for redirect
153 150 new_repo_name = schema_data['repo_group']['repo_name_with_group']
154 151
155 152 # save extra fields into our validated data
156 153 for key, value in pstruct:
157 154 if key.startswith(RepositoryField.PREFIX):
158 155 validated_updates[key] = value
159 156
160 157 try:
161 158 RepoModel().update(self.db_repo, **validated_updates)
162 159 ScmModel().mark_for_invalidation(new_repo_name)
163 160
164 161 audit_logger.store_web(
165 162 'repo.edit', action_data={'old_data': old_values},
166 163 user=self._rhodecode_user, repo=self.db_repo)
167 164
168 165 Session().commit()
169 166
170 167 h.flash(_('Repository {} updated successfully').format(
171 168 old_repo_name), category='success')
172 169 except Exception:
173 170 log.exception("Exception during update of repository")
174 171 h.flash(_('Error occurred during update of repository {}').format(
175 172 old_repo_name), category='error')
176 173
177 174 raise HTTPFound(
178 175 h.route_path('edit_repo', repo_name=new_repo_name))
179 176
180 177 @LoginRequired()
181 178 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
182 179 @view_config(
183 180 route_name='repo_edit_toggle_locking', request_method='GET',
184 181 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
185 182 def toggle_locking(self):
186 183 """
187 184 Toggle locking of repository by simple GET call to url
188 185 """
189 186 _ = self.request.translate
190 187 repo = self.db_repo
191 188
192 189 try:
193 190 if repo.enable_locking:
194 191 if repo.locked[0]:
195 192 Repository.unlock(repo)
196 193 action = _('Unlocked')
197 194 else:
198 195 Repository.lock(
199 196 repo, self._rhodecode_user.user_id,
200 197 lock_reason=Repository.LOCK_WEB)
201 198 action = _('Locked')
202 199
203 200 h.flash(_('Repository has been %s') % action,
204 201 category='success')
205 202 except Exception:
206 203 log.exception("Exception during unlocking")
207 204 h.flash(_('An error occurred during unlocking'),
208 205 category='error')
209 206 raise HTTPFound(
210 207 h.route_path('repo_summary', repo_name=self.db_repo_name))
211 208
212 209 @LoginRequired()
213 210 @HasRepoPermissionAnyDecorator('repository.admin')
214 211 @view_config(
215 212 route_name='edit_repo_statistics', request_method='GET',
216 213 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
217 214 def edit_statistics_form(self):
218 215 c = self.load_default_context()
219 216
220 217 if self.db_repo.stats:
221 218 # this is on what revision we ended up so we add +1 for count
222 219 last_rev = self.db_repo.stats.stat_on_revision + 1
223 220 else:
224 221 last_rev = 0
225 222
226 223 c.active = 'statistics'
227 224 c.stats_revision = last_rev
228 225 c.repo_last_rev = self.rhodecode_vcs_repo.count()
229 226
230 227 if last_rev == 0 or c.repo_last_rev == 0:
231 228 c.stats_percentage = 0
232 229 else:
233 230 c.stats_percentage = '%.2f' % (
234 231 (float((last_rev)) / c.repo_last_rev) * 100)
235 232 return self._get_template_context(c)
236 233
237 234 @LoginRequired()
238 235 @HasRepoPermissionAnyDecorator('repository.admin')
239 236 @CSRFRequired()
240 237 @view_config(
241 238 route_name='edit_repo_statistics_reset', request_method='POST',
242 239 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
243 240 def repo_statistics_reset(self):
244 241 _ = self.request.translate
245 242
246 243 try:
247 244 RepoModel().delete_stats(self.db_repo_name)
248 245 Session().commit()
249 246 except Exception:
250 247 log.exception('Edit statistics failure')
251 248 h.flash(_('An error occurred during deletion of repository stats'),
252 249 category='error')
253 250 raise HTTPFound(
254 251 h.route_path('edit_repo_statistics', repo_name=self.db_repo_name))
@@ -1,226 +1,223 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 import logging
22 22
23 23 from pyramid.view import view_config
24 24 from pyramid.httpexceptions import HTTPFound
25 25
26 26 from rhodecode.apps._base import RepoAppView
27 27 from rhodecode.lib import helpers as h
28 28 from rhodecode.lib import audit_logger
29 29 from rhodecode.lib.auth import (
30 30 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
31 31 from rhodecode.lib.exceptions import AttachedForksError
32 32 from rhodecode.lib.utils2 import safe_int
33 33 from rhodecode.lib.vcs import RepositoryError
34 34 from rhodecode.model.db import Session, UserFollowing, User, Repository
35 35 from rhodecode.model.repo import RepoModel
36 36 from rhodecode.model.scm import ScmModel
37 37
38 38 log = logging.getLogger(__name__)
39 39
40 40
41 41 class RepoSettingsView(RepoAppView):
42 42
43 43 def load_default_context(self):
44 44 c = self._get_local_tmpl_context()
45 45
46 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
47 c.repo_info = self.db_repo
48
49 46 self._register_global_c(c)
50 47 return c
51 48
52 49 @LoginRequired()
53 50 @HasRepoPermissionAnyDecorator('repository.admin')
54 51 @view_config(
55 52 route_name='edit_repo_advanced', request_method='GET',
56 53 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
57 54 def edit_advanced(self):
58 55 c = self.load_default_context()
59 56 c.active = 'advanced'
60 57
61 58 c.default_user_id = User.get_default_user().user_id
62 59 c.in_public_journal = UserFollowing.query() \
63 60 .filter(UserFollowing.user_id == c.default_user_id) \
64 61 .filter(UserFollowing.follows_repository == self.db_repo).scalar()
65 62
66 63 c.has_origin_repo_read_perm = False
67 64 if self.db_repo.fork:
68 65 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
69 66 'repository.write', 'repository.read', 'repository.admin')(
70 67 self.db_repo.fork.repo_name, 'repo set as fork page')
71 68
72 69 return self._get_template_context(c)
73 70
74 71 @LoginRequired()
75 72 @HasRepoPermissionAnyDecorator('repository.admin')
76 73 @CSRFRequired()
77 74 @view_config(
78 75 route_name='edit_repo_advanced_delete', request_method='POST',
79 76 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
80 77 def edit_advanced_delete(self):
81 78 """
82 79 Deletes the repository, or shows warnings if deletion is not possible
83 80 because of attached forks or other errors.
84 81 """
85 82 _ = self.request.translate
86 83 handle_forks = self.request.POST.get('forks', None)
87 84
88 85 try:
89 86 _forks = self.db_repo.forks.count()
90 87 if _forks and handle_forks:
91 88 if handle_forks == 'detach_forks':
92 89 handle_forks = 'detach'
93 90 h.flash(_('Detached %s forks') % _forks, category='success')
94 91 elif handle_forks == 'delete_forks':
95 92 handle_forks = 'delete'
96 93 h.flash(_('Deleted %s forks') % _forks, category='success')
97 94
98 95 old_data = self.db_repo.get_api_data()
99 96 RepoModel().delete(self.db_repo, forks=handle_forks)
100 97
101 98 repo = audit_logger.RepoWrap(repo_id=None,
102 99 repo_name=self.db_repo.repo_name)
103 100 audit_logger.store_web(
104 101 'repo.delete', action_data={'old_data': old_data},
105 102 user=self._rhodecode_user, repo=repo)
106 103
107 104 ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
108 105 h.flash(
109 106 _('Deleted repository `%s`') % self.db_repo_name,
110 107 category='success')
111 108 Session().commit()
112 109 except AttachedForksError:
113 110 repo_advanced_url = h.route_path(
114 111 'edit_repo_advanced', repo_name=self.db_repo_name,
115 112 _anchor='advanced-delete')
116 113 delete_anchor = h.link_to(_('detach or delete'), repo_advanced_url)
117 114 h.flash(_('Cannot delete `{repo}` it still contains attached forks. '
118 115 'Try using {delete_or_detach} option.')
119 116 .format(repo=self.db_repo_name, delete_or_detach=delete_anchor),
120 117 category='warning')
121 118
122 119 # redirect to advanced for forks handle action ?
123 120 raise HTTPFound(repo_advanced_url)
124 121
125 122 except Exception:
126 123 log.exception("Exception during deletion of repository")
127 124 h.flash(_('An error occurred during deletion of `%s`')
128 125 % self.db_repo_name, category='error')
129 126 # redirect to advanced for more deletion options
130 127 raise HTTPFound(
131 128 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name),
132 129 _anchor='advanced-delete')
133 130
134 131 raise HTTPFound(h.route_path('home'))
135 132
136 133 @LoginRequired()
137 134 @HasRepoPermissionAnyDecorator('repository.admin')
138 135 @CSRFRequired()
139 136 @view_config(
140 137 route_name='edit_repo_advanced_journal', request_method='POST',
141 138 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
142 139 def edit_advanced_journal(self):
143 140 """
144 141 Set's this repository to be visible in public journal,
145 142 in other words making default user to follow this repo
146 143 """
147 144 _ = self.request.translate
148 145
149 146 try:
150 147 user_id = User.get_default_user().user_id
151 148 ScmModel().toggle_following_repo(self.db_repo.repo_id, user_id)
152 149 h.flash(_('Updated repository visibility in public journal'),
153 150 category='success')
154 151 Session().commit()
155 152 except Exception:
156 153 h.flash(_('An error occurred during setting this '
157 154 'repository in public journal'),
158 155 category='error')
159 156
160 157 raise HTTPFound(
161 158 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
162 159
163 160 @LoginRequired()
164 161 @HasRepoPermissionAnyDecorator('repository.admin')
165 162 @CSRFRequired()
166 163 @view_config(
167 164 route_name='edit_repo_advanced_fork', request_method='POST',
168 165 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
169 166 def edit_advanced_fork(self):
170 167 """
171 168 Mark given repository as a fork of another
172 169 """
173 170 _ = self.request.translate
174 171
175 172 new_fork_id = self.request.POST.get('id_fork_of')
176 173 try:
177 174
178 175 if new_fork_id and not new_fork_id.isdigit():
179 176 log.error('Given fork id %s is not an INT', new_fork_id)
180 177
181 178 fork_id = safe_int(new_fork_id)
182 179 repo = ScmModel().mark_as_fork(
183 180 self.db_repo_name, fork_id, self._rhodecode_user.user_id)
184 181 fork = repo.fork.repo_name if repo.fork else _('Nothing')
185 182 Session().commit()
186 183 h.flash(_('Marked repo %s as fork of %s') % (self.db_repo_name, fork),
187 184 category='success')
188 185 except RepositoryError as e:
189 186 log.exception("Repository Error occurred")
190 187 h.flash(str(e), category='error')
191 188 except Exception as e:
192 189 log.exception("Exception while editing fork")
193 190 h.flash(_('An error occurred during this operation'),
194 191 category='error')
195 192
196 193 raise HTTPFound(
197 194 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
198 195
199 196 @LoginRequired()
200 197 @HasRepoPermissionAnyDecorator('repository.admin')
201 198 @CSRFRequired()
202 199 @view_config(
203 200 route_name='edit_repo_advanced_locking', request_method='POST',
204 201 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
205 202 def edit_advanced_locking(self):
206 203 """
207 204 Toggle locking of repository
208 205 """
209 206 _ = self.request.translate
210 207 set_lock = self.request.POST.get('set_lock')
211 208 set_unlock = self.request.POST.get('set_unlock')
212 209
213 210 try:
214 211 if set_lock:
215 212 Repository.lock(self.db_repo, self._rhodecode_user.user_id,
216 213 lock_reason=Repository.LOCK_WEB)
217 214 h.flash(_('Locked repository'), category='success')
218 215 elif set_unlock:
219 216 Repository.unlock(self.db_repo)
220 217 h.flash(_('Unlocked repository'), category='success')
221 218 except Exception as e:
222 219 log.exception("Exception during unlocking")
223 220 h.flash(_('An error occurred during unlocking'), category='error')
224 221
225 222 raise HTTPFound(
226 223 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
@@ -1,114 +1,111 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2017-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 logging
22 22
23 23 import formencode
24 24 import formencode.htmlfill
25 25
26 26 from pyramid.httpexceptions import HTTPFound
27 27 from pyramid.view import view_config
28 28
29 29 from rhodecode.apps._base import RepoAppView
30 30 from rhodecode.lib import audit_logger
31 31 from rhodecode.lib import helpers as h
32 32 from rhodecode.lib.auth import (
33 33 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
34 34 from rhodecode.model.db import RepositoryField
35 35 from rhodecode.model.forms import RepoFieldForm
36 36 from rhodecode.model.meta import Session
37 37 from rhodecode.model.repo import RepoModel
38 38
39 39 log = logging.getLogger(__name__)
40 40
41 41
42 42 class RepoSettingsFieldsView(RepoAppView):
43 43 def load_default_context(self):
44 44 c = self._get_local_tmpl_context()
45 45
46 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
47 c.repo_info = self.db_repo
48
49 46 self._register_global_c(c)
50 47 return c
51 48
52 49 @LoginRequired()
53 50 @HasRepoPermissionAnyDecorator('repository.admin')
54 51 @view_config(
55 52 route_name='edit_repo_fields', request_method='GET',
56 53 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
57 54 def repo_field_edit(self):
58 55 c = self.load_default_context()
59 56
60 57 c.active = 'fields'
61 58 c.repo_fields = RepositoryField.query() \
62 59 .filter(RepositoryField.repository == self.db_repo).all()
63 60
64 61 return self._get_template_context(c)
65 62
66 63 @LoginRequired()
67 64 @HasRepoPermissionAnyDecorator('repository.admin')
68 65 @CSRFRequired()
69 66 @view_config(
70 67 route_name='edit_repo_fields_create', request_method='POST',
71 68 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
72 69 def repo_field_create(self):
73 70 _ = self.request.translate
74 71
75 72 try:
76 73 form_result = RepoFieldForm()().to_python(dict(self.request.POST))
77 74 RepoModel().add_repo_field(
78 75 self.db_repo_name,
79 76 form_result['new_field_key'],
80 77 field_type=form_result['new_field_type'],
81 78 field_value=form_result['new_field_value'],
82 79 field_label=form_result['new_field_label'],
83 80 field_desc=form_result['new_field_desc'])
84 81
85 82 Session().commit()
86 83 except Exception as e:
87 84 log.exception("Exception creating field")
88 85 msg = _('An error occurred during creation of field')
89 86 if isinstance(e, formencode.Invalid):
90 87 msg += ". " + e.msg
91 88 h.flash(msg, category='error')
92 89
93 90 raise HTTPFound(
94 91 h.route_path('edit_repo_fields', repo_name=self.db_repo_name))
95 92
96 93 @LoginRequired()
97 94 @HasRepoPermissionAnyDecorator('repository.admin')
98 95 @CSRFRequired()
99 96 @view_config(
100 97 route_name='edit_repo_fields_delete', request_method='POST',
101 98 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
102 99 def repo_field_delete(self):
103 100 _ = self.request.translate
104 101 field = RepositoryField.get_or_404(self.request.matchdict['field_id'])
105 102 try:
106 103 RepoModel().delete_repo_field(self.db_repo_name, field.field_key)
107 104 Session().commit()
108 105 except Exception:
109 106 log.exception('Exception during removal of field')
110 107 msg = _('An error occurred during removal of field')
111 108 h.flash(msg, category='error')
112 109
113 110 raise HTTPFound(
114 111 h.route_path('edit_repo_fields', repo_name=self.db_repo_name))
@@ -1,129 +1,126 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2017-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 logging
22 22
23 23 from pyramid.httpexceptions import HTTPFound
24 24 from pyramid.view import view_config
25 25
26 26 from rhodecode.apps._base import RepoAppView
27 27 from rhodecode.lib import audit_logger
28 28 from rhodecode.lib import helpers as h
29 29 from rhodecode.lib.auth import (
30 30 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
31 31 from rhodecode.model.forms import IssueTrackerPatternsForm
32 32 from rhodecode.model.meta import Session
33 33 from rhodecode.model.settings import IssueTrackerSettingsModel
34 34
35 35 log = logging.getLogger(__name__)
36 36
37 37
38 38 class RepoSettingsIssueTrackersView(RepoAppView):
39 39 def load_default_context(self):
40 40 c = self._get_local_tmpl_context()
41 41
42 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
43 c.repo_info = self.db_repo
44
45 42 self._register_global_c(c)
46 43 return c
47 44
48 45 @LoginRequired()
49 46 @HasRepoPermissionAnyDecorator('repository.admin')
50 47 @view_config(
51 48 route_name='edit_repo_issuetracker', request_method='GET',
52 49 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
53 50 def repo_issuetracker(self):
54 51 c = self.load_default_context()
55 52 c.active = 'issuetracker'
56 53 c.data = 'data'
57 54
58 55 c.settings_model = IssueTrackerSettingsModel(repo=self.db_repo)
59 56 c.global_patterns = c.settings_model.get_global_settings()
60 57 c.repo_patterns = c.settings_model.get_repo_settings()
61 58
62 59 return self._get_template_context(c)
63 60
64 61 @LoginRequired()
65 62 @HasRepoPermissionAnyDecorator('repository.admin')
66 63 @CSRFRequired()
67 64 @view_config(
68 65 route_name='edit_repo_issuetracker_test', request_method='POST',
69 66 xhr=True, renderer='string')
70 67 def repo_issuetracker_test(self):
71 68 return h.urlify_commit_message(
72 69 self.request.POST.get('test_text', ''),
73 70 self.db_repo_name)
74 71
75 72 @LoginRequired()
76 73 @HasRepoPermissionAnyDecorator('repository.admin')
77 74 @CSRFRequired()
78 75 @view_config(
79 76 route_name='edit_repo_issuetracker_delete', request_method='POST',
80 77 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
81 78 def repo_issuetracker_delete(self):
82 79 _ = self.request.translate
83 80 uid = self.request.POST.get('uid')
84 81 repo_settings = IssueTrackerSettingsModel(repo=self.db_repo_name)
85 82 try:
86 83 repo_settings.delete_entries(uid)
87 84 except Exception:
88 85 h.flash(_('Error occurred during deleting issue tracker entry'),
89 86 category='error')
90 87 else:
91 88 h.flash(_('Removed issue tracker entry'), category='success')
92 89 raise HTTPFound(
93 90 h.route_path('edit_repo_issuetracker', repo_name=self.db_repo_name))
94 91
95 92 def _update_patterns(self, form, repo_settings):
96 93 for uid in form['delete_patterns']:
97 94 repo_settings.delete_entries(uid)
98 95
99 96 for pattern_data in form['patterns']:
100 97 for setting_key, pattern, type_ in pattern_data:
101 98 sett = repo_settings.create_or_update_setting(
102 99 setting_key, pattern.strip(), type_)
103 100 Session().add(sett)
104 101
105 102 Session().commit()
106 103
107 104 @LoginRequired()
108 105 @HasRepoPermissionAnyDecorator('repository.admin')
109 106 @CSRFRequired()
110 107 @view_config(
111 108 route_name='edit_repo_issuetracker_update', request_method='POST',
112 109 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
113 110 def repo_issuetracker_update(self):
114 111 _ = self.request.translate
115 112 # Save inheritance
116 113 repo_settings = IssueTrackerSettingsModel(repo=self.db_repo_name)
117 114 inherited = (
118 115 self.request.POST.get('inherit_global_issuetracker') == "inherited")
119 116 repo_settings.inherit_global_settings = inherited
120 117 Session().commit()
121 118
122 119 form = IssueTrackerPatternsForm()().to_python(self.request.POST)
123 120 if form:
124 121 self._update_patterns(form, repo_settings)
125 122
126 123 h.flash(_('Updated issue tracker entries'), category='success')
127 124 raise HTTPFound(
128 125 h.route_path('edit_repo_issuetracker', repo_name=self.db_repo_name))
129 126
@@ -1,75 +1,72 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2017-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 logging
22 22
23 23 from pyramid.httpexceptions import HTTPFound
24 24 from pyramid.view import view_config
25 25
26 26 from rhodecode.apps._base import RepoAppView
27 27 from rhodecode.lib import helpers as h
28 28 from rhodecode.lib.auth import (
29 29 LoginRequired, CSRFRequired, HasRepoPermissionAnyDecorator)
30 30 from rhodecode.model.scm import ScmModel
31 31
32 32 log = logging.getLogger(__name__)
33 33
34 34
35 35 class RepoSettingsRemoteView(RepoAppView):
36 36 def load_default_context(self):
37 37 c = self._get_local_tmpl_context()
38 38
39 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
40 c.repo_info = self.db_repo
41
42 39 self._register_global_c(c)
43 40 return c
44 41
45 42 @LoginRequired()
46 43 @HasRepoPermissionAnyDecorator('repository.admin')
47 44 @view_config(
48 45 route_name='edit_repo_remote', request_method='GET',
49 46 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
50 47 def repo_remote_edit_form(self):
51 48 c = self.load_default_context()
52 49 c.active = 'remote'
53 50
54 51 return self._get_template_context(c)
55 52
56 53 @LoginRequired()
57 54 @HasRepoPermissionAnyDecorator('repository.admin')
58 55 @CSRFRequired()
59 56 @view_config(
60 57 route_name='edit_repo_remote_pull', request_method='POST',
61 58 renderer=None)
62 59 def repo_remote_pull_changes(self):
63 60 _ = self.request.translate
64 61 self.load_default_context()
65 62
66 63 try:
67 64 ScmModel().pull_changes(
68 65 self.db_repo_name, self._rhodecode_user.username)
69 66 h.flash(_('Pulled from remote location'), category='success')
70 67 except Exception:
71 68 log.exception("Exception during pull from remote")
72 69 h.flash(_('An error occurred during pull from remote location'),
73 70 category='error')
74 71 raise HTTPFound(
75 72 h.route_path('edit_repo_remote', repo_name=self.db_repo_name))
@@ -1,173 +1,170 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2017-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 logging
22 22
23 23 import formencode
24 24 import formencode.htmlfill
25 25 from pyramid.httpexceptions import HTTPFound, HTTPBadRequest
26 26 from pyramid.response import Response
27 27 from pyramid.renderers import render
28 28 from pyramid.view import view_config
29 29
30 30 from rhodecode.apps._base import RepoAppView
31 31 from rhodecode.lib import audit_logger
32 32 from rhodecode.lib import helpers as h
33 33 from rhodecode.lib.auth import (
34 34 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
35 35 from rhodecode.model.forms import RepoVcsSettingsForm
36 36 from rhodecode.model.meta import Session
37 37 from rhodecode.model.settings import VcsSettingsModel, SettingNotFound
38 38
39 39 log = logging.getLogger(__name__)
40 40
41 41
42 42 class RepoSettingsVcsView(RepoAppView):
43 43 def load_default_context(self):
44 44 c = self._get_local_tmpl_context()
45 45
46 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
47 c.repo_info = self.db_repo
48
49 46 self._register_global_c(c)
50 47 return c
51 48
52 49 def _vcs_form_defaults(self, repo_name):
53 50 model = VcsSettingsModel(repo=repo_name)
54 51 global_defaults = model.get_global_settings()
55 52
56 53 repo_defaults = {}
57 54 repo_defaults.update(global_defaults)
58 55 repo_defaults.update(model.get_repo_settings())
59 56
60 57 global_defaults = {
61 58 '{}_inherited'.format(k): global_defaults[k]
62 59 for k in global_defaults}
63 60
64 61 defaults = {
65 62 'inherit_global_settings': model.inherit_global_settings
66 63 }
67 64 defaults.update(global_defaults)
68 65 defaults.update(repo_defaults)
69 66 defaults.update({
70 67 'new_svn_branch': '',
71 68 'new_svn_tag': '',
72 69 })
73 70 return defaults
74 71
75 72 @LoginRequired()
76 73 @HasRepoPermissionAnyDecorator('repository.admin')
77 74 @view_config(
78 75 route_name='edit_repo_vcs', request_method='GET',
79 76 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
80 77 def repo_vcs_settings(self):
81 78 c = self.load_default_context()
82 79 model = VcsSettingsModel(repo=self.db_repo_name)
83 80
84 81 c.active = 'vcs'
85 82 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
86 83 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
87 84 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
88 85 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
89 86
90 87 defaults = self._vcs_form_defaults(self.db_repo_name)
91 88 c.inherit_global_settings = defaults['inherit_global_settings']
92 89
93 90 data = render('rhodecode:templates/admin/repos/repo_edit.mako',
94 91 self._get_template_context(c), self.request)
95 92 html = formencode.htmlfill.render(
96 93 data,
97 94 defaults=defaults,
98 95 encoding="UTF-8",
99 96 force_defaults=False
100 97 )
101 98 return Response(html)
102 99
103 100 @LoginRequired()
104 101 @HasRepoPermissionAnyDecorator('repository.admin')
105 102 @CSRFRequired()
106 103 @view_config(
107 104 route_name='edit_repo_vcs_update', request_method='POST',
108 105 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
109 106 def repo_settings_vcs_update(self):
110 107 _ = self.request.translate
111 108 c = self.load_default_context()
112 109 c.active = 'vcs'
113 110
114 111 model = VcsSettingsModel(repo=self.db_repo_name)
115 112 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
116 113 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
117 114 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
118 115 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
119 116
120 117 defaults = self._vcs_form_defaults(self.db_repo_name)
121 118 c.inherit_global_settings = defaults['inherit_global_settings']
122 119
123 120 application_form = RepoVcsSettingsForm(self.db_repo_name)()
124 121 try:
125 122 form_result = application_form.to_python(dict(self.request.POST))
126 123 except formencode.Invalid as errors:
127 124 h.flash(_("Some form inputs contain invalid data."),
128 125 category='error')
129 126
130 127 data = render('rhodecode:templates/admin/repos/repo_edit.mako',
131 128 self._get_template_context(c), self.request)
132 129 html = formencode.htmlfill.render(
133 130 data,
134 131 defaults=errors.value,
135 132 errors=errors.error_dict or {},
136 133 encoding="UTF-8",
137 134 force_defaults=False
138 135 )
139 136 return Response(html)
140 137
141 138 try:
142 139 inherit_global_settings = form_result['inherit_global_settings']
143 140 model.create_or_update_repo_settings(
144 141 form_result, inherit_global_settings=inherit_global_settings)
145 142 Session().commit()
146 143 h.flash(_('Updated VCS settings'), category='success')
147 144 except Exception:
148 145 log.exception("Exception while updating settings")
149 146 h.flash(
150 147 _('Error occurred during updating repository VCS settings'),
151 148 category='error')
152 149
153 150 raise HTTPFound(
154 151 h.route_path('edit_repo_vcs', repo_name=self.db_repo_name))
155 152
156 153 @LoginRequired()
157 154 @HasRepoPermissionAnyDecorator('repository.admin')
158 155 @CSRFRequired()
159 156 @view_config(
160 157 route_name='edit_repo_vcs_svn_pattern_delete', request_method='POST',
161 158 renderer='json_ext', xhr=True)
162 159 def repo_settings_delete_svn_pattern(self):
163 160 self.load_default_context()
164 161 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
165 162 model = VcsSettingsModel(repo=self.db_repo_name)
166 163 try:
167 164 model.delete_repo_svn_pattern(delete_pattern_id)
168 165 except SettingNotFound:
169 166 log.exception('Failed to delete SVN pattern')
170 167 raise HTTPBadRequest()
171 168
172 169 Session().commit()
173 170 return True
@@ -1,116 +1,113 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2017-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 logging
22 22 from pyramid.view import view_config
23 23
24 24 from rhodecode.apps._base import RepoAppView
25 25 from rhodecode.lib import audit_logger
26 26 from rhodecode.lib import helpers as h
27 27 from rhodecode.lib.auth import (
28 28 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
29 29 from rhodecode.lib.ext_json import json
30 30
31 31 log = logging.getLogger(__name__)
32 32
33 33
34 34 class StripView(RepoAppView):
35 35 def load_default_context(self):
36 36 c = self._get_local_tmpl_context()
37 37
38 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
39 c.repo_info = self.db_repo
40
41 38 self._register_global_c(c)
42 39 return c
43 40
44 41 @LoginRequired()
45 42 @HasRepoPermissionAnyDecorator('repository.admin')
46 43 @view_config(
47 44 route_name='edit_repo_strip', request_method='GET',
48 45 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
49 46 def strip(self):
50 47 c = self.load_default_context()
51 48 c.active = 'strip'
52 49 c.strip_limit = 10
53 50
54 51 return self._get_template_context(c)
55 52
56 53 @LoginRequired()
57 54 @HasRepoPermissionAnyDecorator('repository.admin')
58 55 @CSRFRequired()
59 56 @view_config(
60 57 route_name='strip_check', request_method='POST',
61 58 renderer='json', xhr=True)
62 59 def strip_check(self):
63 60 from rhodecode.lib.vcs.backends.base import EmptyCommit
64 61 data = {}
65 62 rp = self.request.POST
66 63 for i in range(1, 11):
67 64 chset = 'changeset_id-%d' % (i,)
68 65 check = rp.get(chset)
69 66
70 67 if check:
71 68 data[i] = self.db_repo.get_changeset(rp[chset])
72 69 if isinstance(data[i], EmptyCommit):
73 70 data[i] = {'rev': None, 'commit': h.escape(rp[chset])}
74 71 else:
75 72 data[i] = {'rev': data[i].raw_id, 'branch': data[i].branch,
76 73 'author': data[i].author,
77 74 'comment': data[i].message}
78 75 else:
79 76 break
80 77 return data
81 78
82 79 @LoginRequired()
83 80 @HasRepoPermissionAnyDecorator('repository.admin')
84 81 @CSRFRequired()
85 82 @view_config(
86 83 route_name='strip_execute', request_method='POST',
87 84 renderer='json', xhr=True)
88 85 def strip_execute(self):
89 86 from rhodecode.model.scm import ScmModel
90 87
91 88 c = self.load_default_context()
92 89 user = self._rhodecode_user
93 90 rp = self.request.POST
94 91 data = {}
95 92 for idx in rp:
96 93 commit = json.loads(rp[idx])
97 94 # If someone put two times the same branch
98 95 if commit['branch'] in data.keys():
99 96 continue
100 97 try:
101 98 ScmModel().strip(
102 99 repo=self.db_repo,
103 100 commit_id=commit['rev'], branch=commit['branch'])
104 101 log.info('Stripped commit %s from repo `%s` by %s' % (
105 102 commit['rev'], self.db_repo_name, user))
106 103 data[commit['rev']] = True
107 104
108 105 audit_logger.store_web(
109 106 'repo.commit.strip', action_data={'commit_id': commit['rev']},
110 107 repo=self.db_repo, user=self._rhodecode_user, commit=True)
111 108
112 109 except Exception as e:
113 110 data[commit['rev']] = False
114 111 log.debug('Stripped commit %s from repo `%s` failed by %s, exeption %s' % (
115 112 commit['rev'], self.db_repo_name, user, e.message))
116 113 return data
@@ -1,372 +1,370 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 import logging
22 22 import string
23 23
24 24 from pyramid.view import view_config
25 25 from beaker.cache import cache_region
26 26
27 27 from rhodecode.controllers import utils
28 28 from rhodecode.apps._base import RepoAppView
29 29 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
30 30 from rhodecode.lib import caches, helpers as h
31 31 from rhodecode.lib.helpers import RepoPage
32 32 from rhodecode.lib.utils2 import safe_str, safe_int
33 33 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
34 34 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
35 35 from rhodecode.lib.ext_json import json
36 36 from rhodecode.lib.vcs.backends.base import EmptyCommit
37 37 from rhodecode.lib.vcs.exceptions import CommitError, EmptyRepositoryError
38 38 from rhodecode.model.db import Statistics, CacheKey, User
39 39 from rhodecode.model.meta import Session
40 40 from rhodecode.model.repo import ReadmeFinder
41 41 from rhodecode.model.scm import ScmModel
42 42
43 43 log = logging.getLogger(__name__)
44 44
45 45
46 46 class RepoSummaryView(RepoAppView):
47 47
48 48 def load_default_context(self):
49 49 c = self._get_local_tmpl_context(include_app_defaults=True)
50 50
51 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
52 c.repo_info = self.db_repo
53 51 c.rhodecode_repo = None
54 52 if not c.repository_requirements_missing:
55 53 c.rhodecode_repo = self.rhodecode_vcs_repo
56 54
57 55 self._register_global_c(c)
58 56 return c
59 57
60 58 def _get_readme_data(self, db_repo, default_renderer):
61 59 repo_name = db_repo.repo_name
62 60 log.debug('Looking for README file')
63 61
64 62 @cache_region('long_term')
65 63 def _generate_readme(cache_key):
66 64 readme_data = None
67 65 readme_node = None
68 66 readme_filename = None
69 67 commit = self._get_landing_commit_or_none(db_repo)
70 68 if commit:
71 69 log.debug("Searching for a README file.")
72 70 readme_node = ReadmeFinder(default_renderer).search(commit)
73 71 if readme_node:
74 72 relative_urls = {
75 73 'raw': h.route_path(
76 74 'repo_file_raw', repo_name=repo_name,
77 75 commit_id=commit.raw_id, f_path=readme_node.path),
78 76 'standard': h.route_path(
79 77 'repo_files', repo_name=repo_name,
80 78 commit_id=commit.raw_id, f_path=readme_node.path),
81 79 }
82 80 readme_data = self._render_readme_or_none(
83 81 commit, readme_node, relative_urls)
84 82 readme_filename = readme_node.path
85 83 return readme_data, readme_filename
86 84
87 85 invalidator_context = CacheKey.repo_context_cache(
88 86 _generate_readme, repo_name, CacheKey.CACHE_TYPE_README)
89 87
90 88 with invalidator_context as context:
91 89 context.invalidate()
92 90 computed = context.compute()
93 91
94 92 return computed
95 93
96 94 def _get_landing_commit_or_none(self, db_repo):
97 95 log.debug("Getting the landing commit.")
98 96 try:
99 97 commit = db_repo.get_landing_commit()
100 98 if not isinstance(commit, EmptyCommit):
101 99 return commit
102 100 else:
103 101 log.debug("Repository is empty, no README to render.")
104 102 except CommitError:
105 103 log.exception(
106 104 "Problem getting commit when trying to render the README.")
107 105
108 106 def _render_readme_or_none(self, commit, readme_node, relative_urls):
109 107 log.debug(
110 108 'Found README file `%s` rendering...', readme_node.path)
111 109 renderer = MarkupRenderer()
112 110 try:
113 111 html_source = renderer.render(
114 112 readme_node.content, filename=readme_node.path)
115 113 if relative_urls:
116 114 return relative_links(html_source, relative_urls)
117 115 return html_source
118 116 except Exception:
119 117 log.exception(
120 118 "Exception while trying to render the README")
121 119
122 120 def _load_commits_context(self, c):
123 121 p = safe_int(self.request.GET.get('page'), 1)
124 122 size = safe_int(self.request.GET.get('size'), 10)
125 123
126 124 def url_generator(**kw):
127 125 query_params = {
128 126 'size': size
129 127 }
130 128 query_params.update(kw)
131 129 return h.route_path(
132 130 'repo_summary_commits',
133 131 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
134 132
135 133 pre_load = ['author', 'branch', 'date', 'message']
136 134 try:
137 135 collection = self.rhodecode_vcs_repo.get_commits(pre_load=pre_load)
138 136 except EmptyRepositoryError:
139 137 collection = self.rhodecode_vcs_repo
140 138
141 139 c.repo_commits = RepoPage(
142 140 collection, page=p, items_per_page=size, url=url_generator)
143 141 page_ids = [x.raw_id for x in c.repo_commits]
144 142 c.comments = self.db_repo.get_comments(page_ids)
145 143 c.statuses = self.db_repo.statuses(page_ids)
146 144
147 145 @LoginRequired()
148 146 @HasRepoPermissionAnyDecorator(
149 147 'repository.read', 'repository.write', 'repository.admin')
150 148 @view_config(
151 149 route_name='repo_summary_commits', request_method='GET',
152 150 renderer='rhodecode:templates/summary/summary_commits.mako')
153 151 def summary_commits(self):
154 152 c = self.load_default_context()
155 153 self._load_commits_context(c)
156 154 return self._get_template_context(c)
157 155
158 156 @LoginRequired()
159 157 @HasRepoPermissionAnyDecorator(
160 158 'repository.read', 'repository.write', 'repository.admin')
161 159 @view_config(
162 160 route_name='repo_summary', request_method='GET',
163 161 renderer='rhodecode:templates/summary/summary.mako')
164 162 @view_config(
165 163 route_name='repo_summary_slash', request_method='GET',
166 164 renderer='rhodecode:templates/summary/summary.mako')
167 165 @view_config(
168 166 route_name='repo_summary_explicit', request_method='GET',
169 167 renderer='rhodecode:templates/summary/summary.mako')
170 168 def summary(self):
171 169 c = self.load_default_context()
172 170
173 171 # Prepare the clone URL
174 172 username = ''
175 173 if self._rhodecode_user.username != User.DEFAULT_USER:
176 174 username = safe_str(self._rhodecode_user.username)
177 175
178 176 _def_clone_uri = _def_clone_uri_by_id = c.clone_uri_tmpl
179 177 if '{repo}' in _def_clone_uri:
180 178 _def_clone_uri_by_id = _def_clone_uri.replace(
181 179 '{repo}', '_{repoid}')
182 180 elif '{repoid}' in _def_clone_uri:
183 181 _def_clone_uri_by_id = _def_clone_uri.replace(
184 182 '_{repoid}', '{repo}')
185 183
186 184 c.clone_repo_url = self.db_repo.clone_url(
187 185 user=username, uri_tmpl=_def_clone_uri)
188 186 c.clone_repo_url_id = self.db_repo.clone_url(
189 187 user=username, uri_tmpl=_def_clone_uri_by_id)
190 188
191 189 # If enabled, get statistics data
192 190
193 191 c.show_stats = bool(self.db_repo.enable_statistics)
194 192
195 193 stats = Session().query(Statistics) \
196 194 .filter(Statistics.repository == self.db_repo) \
197 195 .scalar()
198 196
199 197 c.stats_percentage = 0
200 198
201 199 if stats and stats.languages:
202 200 c.no_data = False is self.db_repo.enable_statistics
203 201 lang_stats_d = json.loads(stats.languages)
204 202
205 203 # Sort first by decreasing count and second by the file extension,
206 204 # so we have a consistent output.
207 205 lang_stats_items = sorted(lang_stats_d.iteritems(),
208 206 key=lambda k: (-k[1], k[0]))[:10]
209 207 lang_stats = [(x, {"count": y,
210 208 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
211 209 for x, y in lang_stats_items]
212 210
213 211 c.trending_languages = json.dumps(lang_stats)
214 212 else:
215 213 c.no_data = True
216 214 c.trending_languages = json.dumps({})
217 215
218 216 scm_model = ScmModel()
219 217 c.enable_downloads = self.db_repo.enable_downloads
220 218 c.repository_followers = scm_model.get_followers(self.db_repo)
221 219 c.repository_forks = scm_model.get_forks(self.db_repo)
222 220 c.repository_is_user_following = scm_model.is_following_repo(
223 221 self.db_repo_name, self._rhodecode_user.user_id)
224 222
225 223 # first interaction with the VCS instance after here...
226 224 if c.repository_requirements_missing:
227 225 self.request.override_renderer = \
228 226 'rhodecode:templates/summary/missing_requirements.mako'
229 227 return self._get_template_context(c)
230 228
231 229 c.readme_data, c.readme_file = \
232 230 self._get_readme_data(self.db_repo, c.visual.default_renderer)
233 231
234 232 # loads the summary commits template context
235 233 self._load_commits_context(c)
236 234
237 235 return self._get_template_context(c)
238 236
239 237 def get_request_commit_id(self):
240 238 return self.request.matchdict['commit_id']
241 239
242 240 @LoginRequired()
243 241 @HasRepoPermissionAnyDecorator(
244 242 'repository.read', 'repository.write', 'repository.admin')
245 243 @view_config(
246 244 route_name='repo_stats', request_method='GET',
247 245 renderer='json_ext')
248 246 def repo_stats(self):
249 247 commit_id = self.get_request_commit_id()
250 248
251 249 _namespace = caches.get_repo_namespace_key(
252 250 caches.SUMMARY_STATS, self.db_repo_name)
253 251 show_stats = bool(self.db_repo.enable_statistics)
254 252 cache_manager = caches.get_cache_manager(
255 253 'repo_cache_long', _namespace)
256 254 _cache_key = caches.compute_key_from_params(
257 255 self.db_repo_name, commit_id, show_stats)
258 256
259 257 def compute_stats():
260 258 code_stats = {}
261 259 size = 0
262 260 try:
263 261 scm_instance = self.db_repo.scm_instance()
264 262 commit = scm_instance.get_commit(commit_id)
265 263
266 264 for node in commit.get_filenodes_generator():
267 265 size += node.size
268 266 if not show_stats:
269 267 continue
270 268 ext = string.lower(node.extension)
271 269 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
272 270 if ext_info:
273 271 if ext in code_stats:
274 272 code_stats[ext]['count'] += 1
275 273 else:
276 274 code_stats[ext] = {"count": 1, "desc": ext_info}
277 275 except EmptyRepositoryError:
278 276 pass
279 277 return {'size': h.format_byte_size_binary(size),
280 278 'code_stats': code_stats}
281 279
282 280 stats = cache_manager.get(_cache_key, createfunc=compute_stats)
283 281 return stats
284 282
285 283 @LoginRequired()
286 284 @HasRepoPermissionAnyDecorator(
287 285 'repository.read', 'repository.write', 'repository.admin')
288 286 @view_config(
289 287 route_name='repo_refs_data', request_method='GET',
290 288 renderer='json_ext')
291 289 def repo_refs_data(self):
292 290 _ = self.request.translate
293 291 self.load_default_context()
294 292
295 293 repo = self.rhodecode_vcs_repo
296 294 refs_to_create = [
297 295 (_("Branch"), repo.branches, 'branch'),
298 296 (_("Tag"), repo.tags, 'tag'),
299 297 (_("Bookmark"), repo.bookmarks, 'book'),
300 298 ]
301 299 res = self._create_reference_data(
302 300 repo, self.db_repo_name, refs_to_create)
303 301 data = {
304 302 'more': False,
305 303 'results': res
306 304 }
307 305 return data
308 306
309 307 @LoginRequired()
310 308 @HasRepoPermissionAnyDecorator(
311 309 'repository.read', 'repository.write', 'repository.admin')
312 310 @view_config(
313 311 route_name='repo_refs_changelog_data', request_method='GET',
314 312 renderer='json_ext')
315 313 def repo_refs_changelog_data(self):
316 314 _ = self.request.translate
317 315 self.load_default_context()
318 316
319 317 repo = self.rhodecode_vcs_repo
320 318
321 319 refs_to_create = [
322 320 (_("Branches"), repo.branches, 'branch'),
323 321 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
324 322 # TODO: enable when vcs can handle bookmarks filters
325 323 # (_("Bookmarks"), repo.bookmarks, "book"),
326 324 ]
327 325 res = self._create_reference_data(
328 326 repo, self.db_repo_name, refs_to_create)
329 327 data = {
330 328 'more': False,
331 329 'results': res
332 330 }
333 331 return data
334 332
335 333 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
336 334 format_ref_id = utils.get_format_ref_id(repo)
337 335
338 336 result = []
339 337 for title, refs, ref_type in refs_to_create:
340 338 if refs:
341 339 result.append({
342 340 'text': title,
343 341 'children': self._create_reference_items(
344 342 repo, full_repo_name, refs, ref_type,
345 343 format_ref_id),
346 344 })
347 345 return result
348 346
349 347 def _create_reference_items(self, repo, full_repo_name, refs, ref_type,
350 348 format_ref_id):
351 349 result = []
352 350 is_svn = h.is_svn(repo)
353 351 for ref_name, raw_id in refs.iteritems():
354 352 files_url = self._create_files_url(
355 353 repo, full_repo_name, ref_name, raw_id, is_svn)
356 354 result.append({
357 355 'text': ref_name,
358 356 'id': format_ref_id(ref_name, raw_id),
359 357 'raw_id': raw_id,
360 358 'type': ref_type,
361 359 'files_url': files_url,
362 360 })
363 361 return result
364 362
365 363 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
366 364 use_commit_id = '/' in ref_name or is_svn
367 365 return h.route_path(
368 366 'repo_files',
369 367 repo_name=full_repo_name,
370 368 f_path=ref_name if is_svn else '',
371 369 commit_id=raw_id if use_commit_id else ref_name,
372 370 _query=dict(at=ref_name))
@@ -1,455 +1,454 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-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 deform
22 22 import logging
23 23 import peppercorn
24 24 import webhelpers.paginate
25 25
26 26 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
27 27
28 28 from rhodecode.apps._base import BaseAppView
29 29 from rhodecode.integrations import integration_type_registry
30 30 from rhodecode.apps.admin.navigation import navigation_list
31 31 from rhodecode.lib.auth import (
32 32 LoginRequired, CSRFRequired, HasPermissionAnyDecorator,
33 33 HasRepoPermissionAnyDecorator, HasRepoGroupPermissionAnyDecorator)
34 34 from rhodecode.lib.utils2 import safe_int
35 35 from rhodecode.lib.helpers import Page
36 36 from rhodecode.model.db import Repository, RepoGroup, Session, Integration
37 37 from rhodecode.model.scm import ScmModel
38 38 from rhodecode.model.integration import IntegrationModel
39 39 from rhodecode.model.validation_schema.schemas.integration_schema import (
40 40 make_integration_schema, IntegrationScopeType)
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44
45 45 class IntegrationSettingsViewBase(BaseAppView):
46 46 """
47 47 Base Integration settings view used by both repo / global settings
48 48 """
49 49
50 50 def __init__(self, context, request):
51 51 super(IntegrationSettingsViewBase, self).__init__(context, request)
52 52 self._load_view_context()
53 53
54 54 def _load_view_context(self):
55 55 """
56 56 This avoids boilerplate for repo/global+list/edit+views/templates
57 57 by doing all possible contexts at the same time however it should
58 58 be split up into separate functions once more "contexts" exist
59 59 """
60 60
61 61 self.IntegrationType = None
62 62 self.repo = None
63 63 self.repo_group = None
64 64 self.integration = None
65 65 self.integrations = {}
66 66
67 67 request = self.request
68 68
69 69 if 'repo_name' in request.matchdict: # in repo settings context
70 70 repo_name = request.matchdict['repo_name']
71 71 self.repo = Repository.get_by_repo_name(repo_name)
72 72
73 73 if 'repo_group_name' in request.matchdict: # in group settings context
74 74 repo_group_name = request.matchdict['repo_group_name']
75 75 self.repo_group = RepoGroup.get_by_group_name(repo_group_name)
76 76
77 77 if 'integration' in request.matchdict: # integration type context
78 78 integration_type = request.matchdict['integration']
79 79 self.IntegrationType = integration_type_registry[integration_type]
80 80
81 81 if 'integration_id' in request.matchdict: # single integration context
82 82 integration_id = request.matchdict['integration_id']
83 83 self.integration = Integration.get(integration_id)
84 84
85 85 # extra perms check just in case
86 86 if not self._has_perms_for_integration(self.integration):
87 87 raise HTTPForbidden()
88 88
89 89 self.settings = self.integration and self.integration.settings or {}
90 90 self.admin_view = not (self.repo or self.repo_group)
91 91
92 92 def _has_perms_for_integration(self, integration):
93 93 perms = self.request.user.permissions
94 94
95 95 if 'hg.admin' in perms['global']:
96 96 return True
97 97
98 98 if integration.repo:
99 99 return perms['repositories'].get(
100 100 integration.repo.repo_name) == 'repository.admin'
101 101
102 102 if integration.repo_group:
103 103 return perms['repositories_groups'].get(
104 104 integration.repo_group.group_name) == 'group.admin'
105 105
106 106 return False
107 107
108 108 def _get_local_tmpl_context(self, include_app_defaults=False):
109 109 _ = self.request.translate
110 110 c = super(IntegrationSettingsViewBase, self)._get_local_tmpl_context(
111 111 include_app_defaults=include_app_defaults)
112 112
113 113 c.active = 'integrations'
114 114
115 115 return c
116 116
117 117 def _form_schema(self):
118 118 schema = make_integration_schema(IntegrationType=self.IntegrationType,
119 119 settings=self.settings)
120 120
121 121 # returns a clone, important if mutating the schema later
122 122 return schema.bind(
123 123 permissions=self.request.user.permissions,
124 124 no_scope=not self.admin_view)
125 125
126 126 def _form_defaults(self):
127 127 _ = self.request.translate
128 128 defaults = {}
129 129
130 130 if self.integration:
131 131 defaults['settings'] = self.integration.settings or {}
132 132 defaults['options'] = {
133 133 'name': self.integration.name,
134 134 'enabled': self.integration.enabled,
135 135 'scope': {
136 136 'repo': self.integration.repo,
137 137 'repo_group': self.integration.repo_group,
138 138 'child_repos_only': self.integration.child_repos_only,
139 139 },
140 140 }
141 141 else:
142 142 if self.repo:
143 143 scope = _('{repo_name} repository').format(
144 144 repo_name=self.repo.repo_name)
145 145 elif self.repo_group:
146 146 scope = _('{repo_group_name} repo group').format(
147 147 repo_group_name=self.repo_group.group_name)
148 148 else:
149 149 scope = _('Global')
150 150
151 151 defaults['options'] = {
152 152 'enabled': True,
153 153 'name': _('{name} integration').format(
154 154 name=self.IntegrationType.display_name),
155 155 }
156 156 defaults['options']['scope'] = {
157 157 'repo': self.repo,
158 158 'repo_group': self.repo_group,
159 159 }
160 160
161 161 return defaults
162 162
163 163 def _delete_integration(self, integration):
164 164 _ = self.request.translate
165 165 Session().delete(integration)
166 166 Session().commit()
167 167 self.request.session.flash(
168 168 _('Integration {integration_name} deleted successfully.').format(
169 169 integration_name=integration.name),
170 170 queue='success')
171 171
172 172 if self.repo:
173 173 redirect_to = self.request.route_path(
174 174 'repo_integrations_home', repo_name=self.repo.repo_name)
175 175 elif self.repo_group:
176 176 redirect_to = self.request.route_path(
177 177 'repo_group_integrations_home',
178 178 repo_group_name=self.repo_group.group_name)
179 179 else:
180 180 redirect_to = self.request.route_path('global_integrations_home')
181 181 raise HTTPFound(redirect_to)
182 182
183 183 def _integration_list(self):
184 184 """ List integrations """
185 185
186 186 c = self.load_default_context()
187 187 if self.repo:
188 188 scope = self.repo
189 189 elif self.repo_group:
190 190 scope = self.repo_group
191 191 else:
192 192 scope = 'all'
193 193
194 194 integrations = []
195 195
196 196 for IntType, integration in IntegrationModel().get_integrations(
197 197 scope=scope, IntegrationType=self.IntegrationType):
198 198
199 199 # extra permissions check *just in case*
200 200 if not self._has_perms_for_integration(integration):
201 201 continue
202 202
203 203 integrations.append((IntType, integration))
204 204
205 205 sort_arg = self.request.GET.get('sort', 'name:asc')
206 206 if ':' in sort_arg:
207 207 sort_field, sort_dir = sort_arg.split(':')
208 208 else:
209 209 sort_field = sort_arg, 'asc'
210 210
211 211 assert sort_field in ('name', 'integration_type', 'enabled', 'scope')
212 212
213 213 integrations.sort(
214 214 key=lambda x: getattr(x[1], sort_field),
215 215 reverse=(sort_dir == 'desc'))
216 216
217 217 page_url = webhelpers.paginate.PageURL(
218 218 self.request.path, self.request.GET)
219 219 page = safe_int(self.request.GET.get('page', 1), 1)
220 220
221 221 integrations = Page(
222 222 integrations, page=page, items_per_page=10, url=page_url)
223 223
224 224 c.rev_sort_dir = sort_dir != 'desc' and 'desc' or 'asc'
225 225
226 226 c.current_IntegrationType = self.IntegrationType
227 227 c.integrations_list = integrations
228 228 c.available_integrations = integration_type_registry
229 229
230 230 return self._get_template_context(c)
231 231
232 232 def _settings_get(self, defaults=None, form=None):
233 233 """
234 234 View that displays the integration settings as a form.
235 235 """
236 236 c = self.load_default_context()
237 237
238 238 defaults = defaults or self._form_defaults()
239 239 schema = self._form_schema()
240 240
241 241 if self.integration:
242 242 buttons = ('submit', 'delete')
243 243 else:
244 244 buttons = ('submit',)
245 245
246 246 form = form or deform.Form(schema, appstruct=defaults, buttons=buttons)
247 247
248 248 c.form = form
249 249 c.current_IntegrationType = self.IntegrationType
250 250 c.integration = self.integration
251 251
252 252 return self._get_template_context(c)
253 253
254 254 def _settings_post(self):
255 255 """
256 256 View that validates and stores the integration settings.
257 257 """
258 258 _ = self.request.translate
259 259
260 260 controls = self.request.POST.items()
261 261 pstruct = peppercorn.parse(controls)
262 262
263 263 if self.integration and pstruct.get('delete'):
264 264 return self._delete_integration(self.integration)
265 265
266 266 schema = self._form_schema()
267 267
268 268 skip_settings_validation = False
269 269 if self.integration and 'enabled' not in pstruct.get('options', {}):
270 270 skip_settings_validation = True
271 271 schema['settings'].validator = None
272 272 for field in schema['settings'].children:
273 273 field.validator = None
274 274 field.missing = ''
275 275
276 276 if self.integration:
277 277 buttons = ('submit', 'delete')
278 278 else:
279 279 buttons = ('submit',)
280 280
281 281 form = deform.Form(schema, buttons=buttons)
282 282
283 283 if not self.admin_view:
284 284 # scope is read only field in these cases, and has to be added
285 285 options = pstruct.setdefault('options', {})
286 286 if 'scope' not in options:
287 287 options['scope'] = IntegrationScopeType().serialize(None, {
288 288 'repo': self.repo,
289 289 'repo_group': self.repo_group,
290 290 })
291 291
292 292 try:
293 293 valid_data = form.validate_pstruct(pstruct)
294 294 except deform.ValidationFailure as e:
295 295 self.request.session.flash(
296 296 _('Errors exist when saving integration settings. '
297 297 'Please check the form inputs.'),
298 298 queue='error')
299 299 return self._settings_get(form=e)
300 300
301 301 if not self.integration:
302 302 self.integration = Integration()
303 303 self.integration.integration_type = self.IntegrationType.key
304 304 Session().add(self.integration)
305 305
306 306 scope = valid_data['options']['scope']
307 307
308 308 IntegrationModel().update_integration(self.integration,
309 309 name=valid_data['options']['name'],
310 310 enabled=valid_data['options']['enabled'],
311 311 settings=valid_data['settings'],
312 312 repo=scope['repo'],
313 313 repo_group=scope['repo_group'],
314 314 child_repos_only=scope['child_repos_only'],
315 315 )
316 316
317 317 self.integration.settings = valid_data['settings']
318 318 Session().commit()
319 319 # Display success message and redirect.
320 320 self.request.session.flash(
321 321 _('Integration {integration_name} updated successfully.').format(
322 322 integration_name=self.IntegrationType.display_name),
323 323 queue='success')
324 324
325 325 # if integration scope changes, we must redirect to the right place
326 326 # keeping in mind if the original view was for /repo/ or /_admin/
327 327 admin_view = not (self.repo or self.repo_group)
328 328
329 329 if self.integration.repo and not admin_view:
330 330 redirect_to = self.request.route_path(
331 331 'repo_integrations_edit',
332 332 repo_name=self.integration.repo.repo_name,
333 333 integration=self.integration.integration_type,
334 334 integration_id=self.integration.integration_id)
335 335 elif self.integration.repo_group and not admin_view:
336 336 redirect_to = self.request.route_path(
337 337 'repo_group_integrations_edit',
338 338 repo_group_name=self.integration.repo_group.group_name,
339 339 integration=self.integration.integration_type,
340 340 integration_id=self.integration.integration_id)
341 341 else:
342 342 redirect_to = self.request.route_path(
343 343 'global_integrations_edit',
344 344 integration=self.integration.integration_type,
345 345 integration_id=self.integration.integration_id)
346 346
347 347 return HTTPFound(redirect_to)
348 348
349 349 def _new_integration(self):
350 350 c = self.load_default_context()
351 351 c.available_integrations = integration_type_registry
352 352 return self._get_template_context(c)
353 353
354 354 def load_default_context(self):
355 355 raise NotImplementedError()
356 356
357 357
358 358 class GlobalIntegrationsView(IntegrationSettingsViewBase):
359 359 def load_default_context(self):
360 360 c = self._get_local_tmpl_context()
361 361 c.repo = self.repo
362 362 c.repo_group = self.repo_group
363 363 c.navlist = navigation_list(self.request)
364 364 self._register_global_c(c)
365 365 return c
366 366
367 367 @LoginRequired()
368 368 @HasPermissionAnyDecorator('hg.admin')
369 369 def integration_list(self):
370 370 return self._integration_list()
371 371
372 372 @LoginRequired()
373 373 @HasPermissionAnyDecorator('hg.admin')
374 374 def settings_get(self):
375 375 return self._settings_get()
376 376
377 377 @LoginRequired()
378 378 @HasPermissionAnyDecorator('hg.admin')
379 379 @CSRFRequired()
380 380 def settings_post(self):
381 381 return self._settings_post()
382 382
383 383 @LoginRequired()
384 384 @HasPermissionAnyDecorator('hg.admin')
385 385 def new_integration(self):
386 386 return self._new_integration()
387 387
388 388
389 389 class RepoIntegrationsView(IntegrationSettingsViewBase):
390 390 def load_default_context(self):
391 391 c = self._get_local_tmpl_context()
392 392
393 393 c.repo = self.repo
394 394 c.repo_group = self.repo_group
395 395
396 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
397 c.repo_info = self.db_repo = self.repo
396 self.db_repo = self.repo
398 397 c.rhodecode_db_repo = self.repo
399 398 c.repo_name = self.db_repo.repo_name
400 399 c.repository_pull_requests = ScmModel().get_pull_requests(self.repo)
401 400
402 401 self._register_global_c(c)
403 402 return c
404 403
405 404 @LoginRequired()
406 405 @HasRepoPermissionAnyDecorator('repository.admin')
407 406 def integration_list(self):
408 407 return self._integration_list()
409 408
410 409 @LoginRequired()
411 410 @HasRepoPermissionAnyDecorator('repository.admin')
412 411 def settings_get(self):
413 412 return self._settings_get()
414 413
415 414 @LoginRequired()
416 415 @HasRepoPermissionAnyDecorator('repository.admin')
417 416 @CSRFRequired()
418 417 def settings_post(self):
419 418 return self._settings_post()
420 419
421 420 @LoginRequired()
422 421 @HasRepoPermissionAnyDecorator('repository.admin')
423 422 def new_integration(self):
424 423 return self._new_integration()
425 424
426 425
427 426 class RepoGroupIntegrationsView(IntegrationSettingsViewBase):
428 427 def load_default_context(self):
429 428 c = self._get_local_tmpl_context()
430 429 c.repo = self.repo
431 430 c.repo_group = self.repo_group
432 431 c.navlist = navigation_list(self.request)
433 432 self._register_global_c(c)
434 433 return c
435 434
436 435 @LoginRequired()
437 436 @HasRepoGroupPermissionAnyDecorator('group.admin')
438 437 def integration_list(self):
439 438 return self._integration_list()
440 439
441 440 @LoginRequired()
442 441 @HasRepoGroupPermissionAnyDecorator('group.admin')
443 442 def settings_get(self):
444 443 return self._settings_get()
445 444
446 445 @LoginRequired()
447 446 @HasRepoGroupPermissionAnyDecorator('group.admin')
448 447 @CSRFRequired()
449 448 def settings_post(self):
450 449 return self._settings_post()
451 450
452 451 @LoginRequired()
453 452 @HasRepoGroupPermissionAnyDecorator('group.admin')
454 453 def new_integration(self):
455 454 return self._new_integration()
@@ -1,99 +1,99 b''
1 1 ## -*- coding: utf-8 -*-
2 2 ##
3 3 ## See also repo_settings.html
4 4 ##
5 5 <%inherit file="/base/base.mako"/>
6 6
7 7 <%def name="title()">
8 ${_('%s repository settings') % c.repo_info.repo_name}
8 ${_('%s repository settings') % c.rhodecode_db_repo.repo_name}
9 9 %if c.rhodecode_name:
10 10 &middot; ${h.branding(c.rhodecode_name)}
11 11 %endif
12 12 </%def>
13 13
14 14 <%def name="breadcrumbs_links()">
15 15 ${_('Settings')}
16 16 </%def>
17 17
18 18 <%def name="menu_bar_nav()">
19 19 ${self.menu_items(active='repositories')}
20 20 </%def>
21 21
22 22 <%def name="menu_bar_subnav()">
23 23 ${self.repo_menu(active='options')}
24 24 </%def>
25 25
26 26 <%def name="main_content()">
27 27 % if hasattr(c, 'repo_edit_template'):
28 28 <%include file="${c.repo_edit_template}"/>
29 29 % else:
30 30 <%include file="/admin/repos/repo_edit_${c.active}.mako"/>
31 31 % endif
32 32 </%def>
33 33
34 34
35 35 <%def name="main()">
36 36 <div class="box">
37 37 <div class="title">
38 38 ${self.repo_page_title(c.rhodecode_db_repo)}
39 39 ${self.breadcrumbs()}
40 40 </div>
41 41
42 42 <div class="sidebar-col-wrapper scw-small">
43 43 <div class="sidebar">
44 44 <ul class="nav nav-pills nav-stacked">
45 45 <li class="${'active' if c.active=='settings' else ''}">
46 46 <a href="${h.route_path('edit_repo', repo_name=c.repo_name)}">${_('Settings')}</a>
47 47 </li>
48 48 <li class="${'active' if c.active=='permissions' else ''}">
49 49 <a href="${h.route_path('edit_repo_perms', repo_name=c.repo_name)}">${_('Permissions')}</a>
50 50 </li>
51 51 <li class="${'active' if c.active=='advanced' else ''}">
52 52 <a href="${h.route_path('edit_repo_advanced', repo_name=c.repo_name)}">${_('Advanced')}</a>
53 53 </li>
54 54 <li class="${'active' if c.active=='vcs' else ''}">
55 55 <a href="${h.route_path('edit_repo_vcs', repo_name=c.repo_name)}">${_('VCS')}</a>
56 56 </li>
57 57 <li class="${'active' if c.active=='fields' else ''}">
58 58 <a href="${h.route_path('edit_repo_fields', repo_name=c.repo_name)}">${_('Extra Fields')}</a>
59 59 </li>
60 60 <li class="${'active' if c.active=='issuetracker' else ''}">
61 61 <a href="${h.route_path('edit_repo_issuetracker', repo_name=c.repo_name)}">${_('Issue Tracker')}</a>
62 62 </li>
63 63 <li class="${'active' if c.active=='caches' else ''}">
64 64 <a href="${h.route_path('edit_repo_caches', repo_name=c.repo_name)}">${_('Caches')}</a>
65 65 </li>
66 %if c.repo_info.repo_type != 'svn':
66 %if c.rhodecode_db_repo.repo_type != 'svn':
67 67 <li class="${'active' if c.active=='remote' else ''}">
68 68 <a href="${h.route_path('edit_repo_remote', repo_name=c.repo_name)}">${_('Remote')}</a>
69 69 </li>
70 70 %endif
71 71 <li class="${'active' if c.active=='statistics' else ''}">
72 72 <a href="${h.route_path('edit_repo_statistics', repo_name=c.repo_name)}">${_('Statistics')}</a>
73 73 </li>
74 74 <li class="${'active' if c.active=='integrations' else ''}">
75 75 <a href="${h.route_path('repo_integrations_home', repo_name=c.repo_name)}">${_('Integrations')}</a>
76 76 </li>
77 %if c.repo_info.repo_type != 'svn':
77 %if c.rhodecode_db_repo.repo_type != 'svn':
78 78 <li class="${'active' if c.active=='reviewers' else ''}">
79 79 <a href="${h.route_path('repo_reviewers', repo_name=c.repo_name)}">${_('Reviewer Rules')}</a>
80 80 </li>
81 81 %endif
82 82 <li class="${'active' if c.active=='maintenance' else ''}">
83 83 <a href="${h.route_path('edit_repo_maintenance', repo_name=c.repo_name)}">${_('Maintenance')}</a>
84 84 </li>
85 85 <li class="${'active' if c.active=='strip' else ''}">
86 86 <a href="${h.route_path('edit_repo_strip', repo_name=c.repo_name)}">${_('Strip')}</a>
87 87 </li>
88 88
89 89 </ul>
90 90 </div>
91 91
92 92 <div class="main-content-full-width">
93 93 ${self.main_content()}
94 94 </div>
95 95
96 96 </div>
97 97 </div>
98 98
99 99 </%def> No newline at end of file
@@ -1,210 +1,210 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <%
4 4 elems = [
5 (_('Owner'), lambda:base.gravatar_with_user(c.repo_info.user.email), '', ''),
6 (_('Created on'), h.format_date(c.repo_info.created_on), '', ''),
7 (_('Updated on'), h.format_date(c.repo_info.updated_on), '', ''),
8 (_('Cached Commit id'), lambda: h.link_to(c.repo_info.changeset_cache.get('short_id'), h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.repo_info.changeset_cache.get('raw_id'))), '', ''),
5 (_('Owner'), lambda:base.gravatar_with_user(c.rhodecode_db_repo.user.email), '', ''),
6 (_('Created on'), h.format_date(c.rhodecode_db_repo.created_on), '', ''),
7 (_('Updated on'), h.format_date(c.rhodecode_db_repo.updated_on), '', ''),
8 (_('Cached Commit id'), lambda: h.link_to(c.rhodecode_db_repo.changeset_cache.get('short_id'), h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.rhodecode_db_repo.changeset_cache.get('raw_id'))), '', ''),
9 9 ]
10 10 %>
11 11
12 12 <div class="panel panel-default">
13 13 <div class="panel-heading" id="advanced-info" >
14 <h3 class="panel-title">${_('Repository: %s') % c.repo_info.repo_name} <a class="permalink" href="#advanced-info"> ¶</a></h3>
14 <h3 class="panel-title">${_('Repository: %s') % c.rhodecode_db_repo.repo_name} <a class="permalink" href="#advanced-info"> ¶</a></h3>
15 15 </div>
16 16 <div class="panel-body">
17 17 ${base.dt_info_panel(elems)}
18 18 </div>
19 19 </div>
20 20
21 21
22 22 <div class="panel panel-default">
23 23 <div class="panel-heading" id="advanced-fork">
24 24 <h3 class="panel-title">${_('Fork Reference')} <a class="permalink" href="#advanced-fork"> ¶</a></h3>
25 25 </div>
26 26 <div class="panel-body">
27 ${h.secure_form(h.route_path('edit_repo_advanced_fork', repo_name=c.repo_info.repo_name), method='POST', request=request)}
27 ${h.secure_form(h.route_path('edit_repo_advanced_fork', repo_name=c.rhodecode_db_repo.repo_name), method='POST', request=request)}
28 28
29 % if c.repo_info.fork:
30 <div class="panel-body-title-text">${h.literal(_('This repository is a fork of %(repo_link)s') % {'repo_link': h.link_to_if(c.has_origin_repo_read_perm,c.repo_info.fork.repo_name, h.route_path('repo_summary', repo_name=c.repo_info.fork.repo_name))})}
29 % if c.rhodecode_db_repo.fork:
30 <div class="panel-body-title-text">${h.literal(_('This repository is a fork of %(repo_link)s') % {'repo_link': h.link_to_if(c.has_origin_repo_read_perm,c.rhodecode_db_repo.fork.repo_name, h.route_path('repo_summary', repo_name=c.rhodecode_db_repo.fork.repo_name))})}
31 31 | <button class="btn btn-link btn-danger" type="submit">Remove fork reference</button></div>
32 32 % endif
33 33
34 34 <div class="field">
35 35 ${h.hidden('id_fork_of')}
36 ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('Set'),class_="btn btn-small",)}
36 ${h.submit('set_as_fork_%s' % c.rhodecode_db_repo.repo_name,_('Set'),class_="btn btn-small",)}
37 37 </div>
38 38 <div class="field">
39 39 <span class="help-block">${_('Manually set this repository as a fork of another from the list')}</span>
40 40 </div>
41 41 ${h.end_form()}
42 42 </div>
43 43 </div>
44 44
45 45
46 46 <div class="panel panel-default">
47 47 <div class="panel-heading" id="advanced-journal">
48 48 <h3 class="panel-title">${_('Public Journal Visibility')} <a class="permalink" href="#advanced-journal"> ¶</a></h3>
49 49 </div>
50 50 <div class="panel-body">
51 ${h.secure_form(h.route_path('edit_repo_advanced_journal', repo_name=c.repo_info.repo_name), method='POST', request=request)}
51 ${h.secure_form(h.route_path('edit_repo_advanced_journal', repo_name=c.rhodecode_db_repo.repo_name), method='POST', request=request)}
52 52 <div class="field">
53 53 %if c.in_public_journal:
54 54 <button class="btn btn-small" type="submit">
55 55 ${_('Remove from Public Journal')}
56 56 </button>
57 57 %else:
58 58 <button class="btn btn-small" type="submit">
59 59 ${_('Add to Public Journal')}
60 60 </button>
61 61 %endif
62 62 </div>
63 63 <div class="field" >
64 64 <span class="help-block">${_('All actions made on this repository will be visible to everyone following the public journal.')}</span>
65 65 </div>
66 66 ${h.end_form()}
67 67 </div>
68 68 </div>
69 69
70 70
71 71 <div class="panel panel-default">
72 72 <div class="panel-heading" id="advanced-locking">
73 73 <h3 class="panel-title">${_('Locking state')} <a class="permalink" href="#advanced-locking"> ¶</a></h3>
74 74 </div>
75 75 <div class="panel-body">
76 ${h.secure_form(h.route_path('edit_repo_advanced_locking', repo_name=c.repo_info.repo_name), method='POST', request=request)}
76 ${h.secure_form(h.route_path('edit_repo_advanced_locking', repo_name=c.rhodecode_db_repo.repo_name), method='POST', request=request)}
77 77
78 %if c.repo_info.locked[0]:
79 <div class="panel-body-title-text">${'Locked by %s on %s. Lock reason: %s' % (h.person_by_id(c.repo_info.locked[0]),
80 h.format_date(h. time_to_datetime(c.repo_info.locked[1])), c.repo_info.locked[2])}</div>
78 %if c.rhodecode_db_repo.locked[0]:
79 <div class="panel-body-title-text">${'Locked by %s on %s. Lock reason: %s' % (h.person_by_id(c.rhodecode_db_repo.locked[0]),
80 h.format_date(h. time_to_datetime(c.rhodecode_db_repo.locked[1])), c.rhodecode_db_repo.locked[2])}</div>
81 81 %else:
82 82 <div class="panel-body-title-text">${_('This Repository is not currently locked.')}</div>
83 83 %endif
84 84
85 85 <div class="field" >
86 %if c.repo_info.locked[0]:
86 %if c.rhodecode_db_repo.locked[0]:
87 87 ${h.hidden('set_unlock', '1')}
88 88 <button class="btn btn-small" type="submit"
89 89 onclick="return confirm('${_('Confirm to unlock repository.')}');">
90 90 <i class="icon-unlock"></i>
91 91 ${_('Unlock repository')}
92 92 </button>
93 93 %else:
94 94 ${h.hidden('set_lock', '1')}
95 95 <button class="btn btn-small" type="submit"
96 96 onclick="return confirm('${_('Confirm to lock repository.')}');">
97 97 <i class="icon-lock"></i>
98 98 ${_('Lock Repository')}
99 99 </button>
100 100 %endif
101 101 </div>
102 102 <div class="field" >
103 103 <span class="help-block">
104 104 ${_('Force repository locking. This only works when anonymous access is disabled. Pulling from the repository locks the repository to that user until the same user pushes to that repository again.')}
105 105 </span>
106 106 </div>
107 107 ${h.end_form()}
108 108 </div>
109 109 </div>
110 110
111 111 <div class="panel panel-danger">
112 112 <div class="panel-heading" id="advanced-delete">
113 113 <h3 class="panel-title">${_('Delete repository')} <a class="permalink" href="#advanced-delete"> ¶</a></h3>
114 114 </div>
115 115 <div class="panel-body">
116 116 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=c.repo_name), method='POST', request=request)}
117 117 <table class="display">
118 118 <tr>
119 119 <td>
120 ${_ungettext('This repository has %s fork.', 'This repository has %s forks.', c.repo_info.forks.count()) % c.repo_info.forks.count()}
120 ${_ungettext('This repository has %s fork.', 'This repository has %s forks.', c.rhodecode_db_repo.forks.count()) % c.rhodecode_db_repo.forks.count()}
121 121 </td>
122 122 <td>
123 %if c.repo_info.forks.count():
123 %if c.rhodecode_db_repo.forks.count():
124 124 <input type="radio" name="forks" value="detach_forks" checked="checked"/> <label for="forks">${_('Detach forks')}</label>
125 125 %endif
126 126 </td>
127 127 <td>
128 %if c.repo_info.forks.count():
128 %if c.rhodecode_db_repo.forks.count():
129 129 <input type="radio" name="forks" value="delete_forks"/> <label for="forks">${_('Delete forks')}</label>
130 130 %endif
131 131 </td>
132 132 </tr>
133 133 </table>
134 134 <div style="margin: 0 0 20px 0" class="fake-space"></div>
135 135
136 136 <div class="field">
137 137 <button class="btn btn-small btn-danger" type="submit"
138 138 onclick="return confirm('${_('Confirm to delete this repository: %s') % c.repo_name}');">
139 139 <i class="icon-remove-sign"></i>
140 140 ${_('Delete This Repository')}
141 141 </button>
142 142 </div>
143 143 <div class="field">
144 144 <span class="help-block">
145 145 ${_('This repository will be renamed in a special way in order to make it inaccessible to RhodeCode Enterprise and its VCS systems. If you need to fully delete it from the file system, please do it manually, or with rhodecode-cleanup-repos command available in rhodecode-tools.')}
146 146 </span>
147 147 </div>
148 148
149 149 ${h.end_form()}
150 150 </div>
151 151 </div>
152 152
153 153
154 154 <script>
155 155
156 var currentRepoId = ${c.repo_info.repo_id};
156 var currentRepoId = ${c.rhodecode_db_repo.repo_id};
157 157
158 158 var repoTypeFilter = function(data) {
159 159 var results = [];
160 160
161 161 if (!data.results[0]) {
162 162 return data
163 163 }
164 164
165 165 $.each(data.results[0].children, function() {
166 166 // filter out the SAME repo, it cannot be used as fork of itself
167 167 if (this.obj.repo_id != currentRepoId) {
168 168 this.id = this.obj.repo_id;
169 169 results.push(this)
170 170 }
171 171 });
172 172 data.results[0].children = results;
173 173 return data;
174 174 };
175 175
176 176 $("#id_fork_of").select2({
177 177 cachedDataSource: {},
178 178 minimumInputLength: 2,
179 placeholder: "${_('Change repository') if c.repo_info.fork else _('Pick repository')}",
179 placeholder: "${_('Change repository') if c.rhodecode_db_repo.fork else _('Pick repository')}",
180 180 dropdownAutoWidth: true,
181 181 containerCssClass: "drop-menu",
182 182 dropdownCssClass: "drop-menu-dropdown",
183 183 formatResult: formatResult,
184 184 query: $.debounce(250, function(query){
185 185 self = this;
186 186 var cacheKey = query.term;
187 187 var cachedData = self.cachedDataSource[cacheKey];
188 188
189 189 if (cachedData) {
190 190 query.callback({results: cachedData.results});
191 191 } else {
192 192 $.ajax({
193 193 url: pyroutes.url('repo_list_data'),
194 data: {'query': query.term, repo_type: '${c.repo_info.repo_type}'},
194 data: {'query': query.term, repo_type: '${c.rhodecode_db_repo.repo_type}'},
195 195 dataType: 'json',
196 196 type: 'GET',
197 197 success: function(data) {
198 198 data = repoTypeFilter(data);
199 199 self.cachedDataSource[cacheKey] = data;
200 200 query.callback({results: data.results});
201 201 },
202 202 error: function(data, textStatus, errorThrown) {
203 203 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
204 204 }
205 205 })
206 206 }
207 207 })
208 208 });
209 209 </script>
210 210
@@ -1,53 +1,53 b''
1 1 <div class="panel panel-default">
2 2 <div class="panel-heading">
3 3 <h3 class="panel-title">${_('Invalidate Cache for Repository')}</h3>
4 4 </div>
5 5 <div class="panel-body">
6 6
7 7 <h4>${_('Manually invalidate the repository cache. On the next access a repository cache will be recreated.')}</h4>
8 8
9 9 <p>
10 10 ${_('Cache purge can be automated by such api call. Can be called periodically in crontab etc.')}
11 11 <br/>
12 12 <code>
13 ${h.api_call_example(method='invalidate_cache', args={"repoid": c.repo_info.repo_name})}
13 ${h.api_call_example(method='invalidate_cache', args={"repoid": c.rhodecode_db_repo.repo_name})}
14 14 </code>
15 15 </p>
16 16
17 17 ${h.secure_form(h.route_path('edit_repo_caches', repo_name=c.repo_name), method='POST', request=request)}
18 18 <div class="form">
19 19 <div class="fields">
20 ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="btn btn-small",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")}
20 ${h.submit('reset_cache_%s' % c.rhodecode_db_repo.repo_name,_('Invalidate repository cache'),class_="btn btn-small",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")}
21 21 </div>
22 22 </div>
23 23 ${h.end_form()}
24 24
25 25 </div>
26 26 </div>
27 27
28 28
29 29 <div class="panel panel-default">
30 30 <div class="panel-heading">
31 31 <h3 class="panel-title">
32 ${(_ungettext('List of repository caches (%(count)s entry)', 'List of repository caches (%(count)s entries)' ,len(c.repo_info.cache_keys)) % {'count': len(c.repo_info.cache_keys)})}
32 ${(_ungettext('List of repository caches (%(count)s entry)', 'List of repository caches (%(count)s entries)' ,len(c.rhodecode_db_repo.cache_keys)) % {'count': len(c.rhodecode_db_repo.cache_keys)})}
33 33 </h3>
34 34 </div>
35 35 <div class="panel-body">
36 36 <div class="field" >
37 37 <table class="rctable edit_cache">
38 38 <tr>
39 39 <th>${_('Prefix')}</th>
40 40 <th>${_('Key')}</th>
41 41 <th>${_('Active')}</th>
42 42 </tr>
43 %for cache in c.repo_info.cache_keys:
43 %for cache in c.rhodecode_db_repo.cache_keys:
44 44 <tr>
45 45 <td class="td-prefix">${cache.get_prefix() or '-'}</td>
46 46 <td class="td-cachekey">${cache.cache_key}</td>
47 47 <td class="td-active">${h.bool2icon(cache.cache_active)}</td>
48 48 </tr>
49 49 %endfor
50 50 </table>
51 51 </div>
52 52 </div>
53 53 </div>
@@ -1,79 +1,79 b''
1 1 <div class="panel panel-default">
2 2 <div class="panel-heading">
3 3 <h3 class="panel-title">${_('Custom extra fields for this repository')}</h3>
4 4 </div>
5 5 <div class="panel-body">
6 6 %if c.visual.repository_fields:
7 7 %if c.repo_fields:
8 8 <div class="emails_wrap">
9 9 <table class="rctable edit_fields">
10 10 <th>${_('Label')}</th>
11 11 <th>${_('Key')}</th>
12 12 <th>${_('Type')}</th>
13 13 <th>${_('Action')}</th>
14 14
15 15 %for field in c.repo_fields:
16 16 <tr>
17 17 <td class="td-tags">${field.field_label}</td>
18 18 <td class="td-hash">${field.field_key}</td>
19 19 <td class="td-type">${field.field_type}</td>
20 20 <td class="td-action">
21 ${h.secure_form(h.route_path('edit_repo_fields_delete', repo_name=c.repo_info.repo_name, field_id=field.repo_field_id), method='POST', request=request)}
21 ${h.secure_form(h.route_path('edit_repo_fields_delete', repo_name=c.rhodecode_db_repo.repo_name, field_id=field.repo_field_id), method='POST', request=request)}
22 22 ${h.hidden('del_repo_field',field.repo_field_id)}
23 23 <button class="btn btn-link btn-danger" type="submit"
24 24 onclick="return confirm('${_('Confirm to delete this field: %s') % field.field_key}');">
25 25 ${_('Delete')}
26 26 </button>
27 27 ${h.end_form()}
28 28 </td>
29 29 </tr>
30 30 %endfor
31 31 </table>
32 32 </div>
33 33 %endif
34 34 ${h.secure_form(h.route_path('edit_repo_fields_create', repo_name=c.repo_name), method='POST', request=request)}
35 35 <div class="form">
36 36 <!-- fields -->
37 37 <div class="fields">
38 38 <div class="field">
39 39 <div class="label">
40 40 <label for="new_field_key">${_('New Field Key')}:</label>
41 41 </div>
42 42 <div class="input">
43 43 ${h.text('new_field_key', class_='medium')}
44 44 </div>
45 45 </div>
46 46 <div class="field">
47 47 <div class="label">
48 48 <label for="new_field_label">${_('New Field Label')}:</label>
49 49 </div>
50 50 <div class="input">
51 51 ${h.text('new_field_label', class_='medium', placeholder=_('Enter short label'))}
52 52 </div>
53 53 </div>
54 54
55 55 <div class="field">
56 56 <div class="label">
57 57 <label for="new_field_desc">${_('New Field Description')}:</label>
58 58 </div>
59 59 <div class="input">
60 60 ${h.text('new_field_desc', class_='medium', placeholder=_('Enter a full description for the field'))}
61 61 </div>
62 62 </div>
63 63
64 64 <div class="buttons">
65 65 ${h.submit('save',_('Add'),class_="btn")}
66 66 ${h.reset('reset',_('Reset'),class_="btn")}
67 67 </div>
68 68 </div>
69 69 </div>
70 70 ${h.end_form()}
71 71 %else:
72 72 <h2>
73 73 ${_('Extra fields are disabled. You can enable them from the Admin/Settings/Visual page.')}
74 74 </h2>
75 75 %endif
76 76 </div>
77 77 </div>
78 78
79 79
@@ -1,109 +1,109 b''
1 1 <%namespace name="its" file="/base/issue_tracker_settings.mako"/>
2 2
3 3 <div id="repo_issue_tracker" class="${'inherited' if c.settings_model.inherit_global_settings else ''}">
4 4 ${h.secure_form(h.route_path('edit_repo_issuetracker_update', repo_name=c.repo_name), id="inherit-form", method='POST', request=request)}
5 5 <div class="panel panel-default panel-body">
6 6 <div class="fields">
7 7 <div class="field">
8 8 <div class="label label-checkbox">
9 9 <label for="inherit_default_permissions">${_('Inherit from global settings')}:</label>
10 10 </div>
11 11 <div class="checkboxes">
12 12 ${h.checkbox('inherit_global_issuetracker', value='inherited', checked=c.settings_model.inherit_global_settings)}
13 13 <span class="help-block">
14 14 ${h.literal(_('Select to inherit global patterns for issue tracker.'))}
15 15 </span>
16 16 </div>
17 17 </div>
18 18 </div>
19 19 </div>
20 20
21 21 <div id="inherit_overlay">
22 22 <div class="panel panel-default">
23 23 <div class="panel-heading">
24 24 <h3 class="panel-title">${_('Inherited Issue Tracker Patterns')}</h3>
25 25 </div>
26 26 <div class="panel-body">
27 27 <table class="rctable issuetracker readonly">
28 28 <tr>
29 29 <th>${_('Description')}</th>
30 30 <th>${_('Pattern')}</th>
31 31 <th>${_('Url')}</th>
32 32 <th>${_('Prefix')}</th>
33 33 <th ></th>
34 34 </tr>
35 35 %for uid, entry in c.global_patterns.items():
36 36 <tr id="${uid}">
37 37 <td class="td-description issuetracker_desc">
38 38 <span class="entry">
39 39 ${entry.desc}
40 40 </span>
41 41 </td>
42 42 <td class="td-regex issuetracker_pat">
43 43 <span class="entry">
44 44 ${entry.pat}
45 45 </span>
46 46 </td>
47 47 <td class="td-url issuetracker_url">
48 48 <span class="entry">
49 49 ${entry.url}
50 50 </span>
51 51 </td>
52 52 <td class="td-prefix issuetracker_pref">
53 53 <span class="entry">
54 54 ${entry.pref}
55 55 </span>
56 56 </td>
57 57 <td class="td-action">
58 58 </td>
59 59 </tr>
60 60 %endfor
61 61
62 62 </table>
63 63 </div>
64 64 </div>
65 65 </div>
66 66
67 67 <div id="custom_overlay">
68 68 <div class="panel panel-default">
69 69 <div class="panel-heading">
70 70 <h3 class="panel-title">${_('Issue Tracker / Wiki Patterns')}</h3>
71 71 </div>
72 72 <div class="panel-body">
73 73 ${its.issue_tracker_settings_table(
74 74 patterns=c.repo_patterns.items(),
75 form_url=h.route_path('edit_repo_issuetracker', repo_name=c.repo_info.repo_name),
76 delete_url=h.route_path('edit_repo_issuetracker_delete', repo_name=c.repo_info.repo_name)
75 form_url=h.route_path('edit_repo_issuetracker', repo_name=c.rhodecode_db_repo.repo_name),
76 delete_url=h.route_path('edit_repo_issuetracker_delete', repo_name=c.rhodecode_db_repo.repo_name)
77 77 )}
78 78 <div class="buttons">
79 79 <button type="submit" class="btn btn-primary save-inheritance" id="save">${_('Save')}</button>
80 80 <button type="reset" class="btn reset-inheritance">${_('Reset')}</button>
81 81 </div>
82 82 </div>
83 83 </div>
84 84 </div>
85 85
86 86
87 87 ${h.end_form()}
88 88
89 89 <div class="panel panel-default">
90 90 <div class="panel-heading">
91 91 <h3 class="panel-title">${_('Test Patterns')}</h3>
92 92 </div>
93 93 <div class="panel-body">
94 94 ${its.issue_tracker_new_row()}
95 ${its.issue_tracker_settings_test(test_url=h.route_path('edit_repo_issuetracker_test', repo_name=c.repo_info.repo_name))}
95 ${its.issue_tracker_settings_test(test_url=h.route_path('edit_repo_issuetracker_test', repo_name=c.rhodecode_db_repo.repo_name))}
96 96 </div>
97 97 </div>
98 98
99 99 </div>
100 100
101 101 <script>
102 102 $('#inherit_global_issuetracker').on('change', function(e){
103 103 $('#repo_issue_tracker').toggleClass('inherited',this.checked);
104 104 });
105 105
106 106 $('.reset-inheritance').on('click', function(e){
107 107 $('#inherit_global_issuetracker').prop('checked', false).change();
108 108 });
109 109 </script>
@@ -1,66 +1,66 b''
1 1 <div class="panel panel-default">
2 2 <div class="panel-heading">
3 3 <h3 class="panel-title">${_('Maintenance')}</h3>
4 4 </div>
5 5 <div class="panel-body">
6 6
7 7 % if c.executable_tasks:
8 8 <h4>${_('Perform maintenance tasks for this repo')}</h4>
9 9
10 10 <span>${_('Following tasks will be performed')}:</span>
11 11 <ol>
12 12 % for task in c.executable_tasks:
13 13 <li>${task}</li>
14 14 % endfor
15 15 </ol>
16 16 <p>
17 17 ${_('Maintenance can be automated by such api call. Can be called periodically in crontab etc.')}
18 18 <br/>
19 19 <code>
20 ${h.api_call_example(method='maintenance', args={"repoid": c.repo_info.repo_name})}
20 ${h.api_call_example(method='maintenance', args={"repoid": c.rhodecode_db_repo.repo_name})}
21 21 </code>
22 22 </p>
23 23
24 24 % else:
25 25 <h4>${_('No maintenance tasks for this repo available')}</h4>
26 26 % endif
27 27
28 28 <div id="results" style="display:none; padding: 10px 0px;"></div>
29 29
30 30 % if c.executable_tasks:
31 31 <div class="form">
32 32 <div class="fields">
33 33 <button class="btn btn-small btn-primary" onclick="executeTask();return false">
34 34 ${_('Run Maintenance')}
35 35 </button>
36 36 </div>
37 37 </div>
38 38 % endif
39 39
40 40 </div>
41 41 </div>
42 42
43 43
44 44 <script>
45 45
46 46 executeTask = function() {
47 47 var btn = $(this);
48 48 $('#results').show();
49 49 $('#results').html('<h4>${_('Performing Maintenance')}...</h4>');
50 50
51 51 btn.attr('disabled', 'disabled');
52 52 btn.addClass('disabled');
53 53
54 var url = "${h.route_path('edit_repo_maintenance_execute', repo_name=c.repo_info.repo_name)}";
54 var url = "${h.route_path('edit_repo_maintenance_execute', repo_name=c.rhodecode_db_repo.repo_name)}";
55 55 var success = function (data) {
56 56 var displayHtml = $('<pre></pre>');
57 57
58 58 $(displayHtml).append(data);
59 59 $('#results').html(displayHtml);
60 60 btn.removeAttr('disabled');
61 61 btn.removeClass('disabled');
62 62 };
63 63 ajaxGET(url, success, null);
64 64
65 65 }
66 66 </script>
@@ -1,123 +1,123 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <div class="panel panel-default">
4 4 <div class="panel-heading">
5 5 <h3 class="panel-title">${_('Repository Permissions')}</h3>
6 6 </div>
7 7 <div class="panel-body">
8 8 ${h.secure_form(h.route_path('edit_repo_perms', repo_name=c.repo_name), method='POST', request=request)}
9 9 <table id="permissions_manage" class="rctable permissions">
10 10 <tr>
11 11 <th class="td-radio">${_('None')}</th>
12 12 <th class="td-radio">${_('Read')}</th>
13 13 <th class="td-radio">${_('Write')}</th>
14 14 <th class="td-radio">${_('Admin')}</th>
15 15 <th class="td-owner">${_('User/User Group')}</th>
16 16 <th></th>
17 17 </tr>
18 18 ## USERS
19 %for _user in c.repo_info.permissions():
19 %for _user in c.rhodecode_db_repo.permissions():
20 20 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
21 21 <tr class="perm_admin_row">
22 22 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
23 23 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
24 24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
25 25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
26 26 <td class="td-user">
27 27 ${base.gravatar(_user.email, 16)}
28 28 ${h.link_to_user(_user.username)}
29 29 %if getattr(_user, 'admin_row', None):
30 30 (${_('super admin')})
31 31 %endif
32 32 %if getattr(_user, 'owner_row', None):
33 33 (${_('owner')})
34 34 %endif
35 35 </td>
36 36 <td></td>
37 37 </tr>
38 %elif _user.username == h.DEFAULT_USER and c.repo_info.private:
38 %elif _user.username == h.DEFAULT_USER and c.rhodecode_db_repo.private:
39 39 <tr>
40 40 <td colspan="4">
41 41 <span class="private_repo_msg">
42 42 <strong title="${h.tooltip(_user.permission)}">${_('private repository')}</strong>
43 43 </span>
44 44 </td>
45 45 <td class="private_repo_msg">
46 46 ${base.gravatar(h.DEFAULT_USER_EMAIL, 16)}
47 47 ${h.DEFAULT_USER} - ${_('only users/user groups explicitly added here will have access')}</td>
48 48 <td></td>
49 49 </tr>
50 50 %else:
51 51 <tr>
52 52 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.none', checked=_user.permission=='repository.none')}</td>
53 53 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.read', checked=_user.permission=='repository.read')}</td>
54 54 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.write', checked=_user.permission=='repository.write')}</td>
55 55 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.admin', checked=_user.permission=='repository.admin')}</td>
56 56 <td class="td-user">
57 57 ${base.gravatar(_user.email, 16)}
58 58 <span class="user">
59 59 % if _user.username == h.DEFAULT_USER:
60 60 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
61 61 % else:
62 62 ${h.link_to_user(_user.username)}
63 63 % endif
64 64 </span>
65 65 </td>
66 66 <td class="td-action">
67 67 %if _user.username != h.DEFAULT_USER:
68 68 <span class="btn btn-link btn-danger revoke_perm"
69 69 member="${_user.user_id}" member_type="user">
70 70 <i class="icon-remove"></i> ${_('Revoke')}
71 71 </span>
72 72 %endif
73 73 </td>
74 74 </tr>
75 75 %endif
76 76 %endfor
77 77
78 78 ## USER GROUPS
79 %for _user_group in c.repo_info.permission_user_groups():
79 %for _user_group in c.rhodecode_db_repo.permission_user_groups():
80 80 <tr>
81 81 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.none', checked=_user_group.permission=='repository.none')}</td>
82 82 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.read', checked=_user_group.permission=='repository.read')}</td>
83 83 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.write', checked=_user_group.permission=='repository.write')}</td>
84 84 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.admin', checked=_user_group.permission=='repository.admin')}</td>
85 85 <td class="td-componentname">
86 86 <i class="icon-group" ></i>
87 87 %if h.HasPermissionAny('hg.admin')():
88 88 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
89 89 ${_user_group.users_group_name}
90 90 </a>
91 91 %else:
92 92 ${_user_group.users_group_name}
93 93 %endif
94 94 </td>
95 95 <td class="td-action">
96 96 <span class="btn btn-link btn-danger revoke_perm"
97 97 member="${_user_group.users_group_id}" member_type="user_group">
98 98 <i class="icon-remove"></i> ${_('Revoke')}
99 99 </span>
100 100 </td>
101 101 </tr>
102 102 %endfor
103 103 <tr class="new_members" id="add_perm_input"></tr>
104 104 </table>
105 105 <div id="add_perm" class="link">
106 106 ${_('Add new')}
107 107 </div>
108 108 <div class="buttons">
109 109 ${h.submit('save',_('Save'),class_="btn btn-primary")}
110 110 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
111 111 </div>
112 112 ${h.end_form()}
113 113 </div>
114 114 </div>
115 115
116 116 <script type="text/javascript">
117 117 $('#add_perm').on('click', function(e){
118 118 addNewPermInput($(this), 'repository');
119 119 });
120 120 $('.revoke_perm').on('click', function(e){
121 121 markRevokePermInput($(this), 'repository');
122 122 });
123 123 </script>
@@ -1,40 +1,40 b''
1 1 <div class="panel panel-default">
2 2 <div class="panel-heading">
3 3 <h3 class="panel-title">${_('Remote url')}</h3>
4 4 </div>
5 5 <div class="panel-body">
6 6
7 7 <h4>${_('Manually pull changes from external repository.')}</h4>
8 8
9 %if c.repo_info.clone_uri:
9 %if c.rhodecode_db_repo.clone_uri:
10 10
11 11 ${_('Remote mirror url')}:
12 <a href="${c.repo_info.clone_uri}">${c.repo_info.clone_uri_hidden}</a>
12 <a href="${c.rhodecode_db_repo.clone_uri}">${c.rhodecode_db_repo.clone_uri_hidden}</a>
13 13
14 14 <p>
15 15 ${_('Pull can be automated by such api call. Can be called periodically in crontab etc.')}
16 16 <br/>
17 17 <code>
18 ${h.api_call_example(method='pull', args={"repoid": c.repo_info.repo_name})}
18 ${h.api_call_example(method='pull', args={"repoid": c.rhodecode_db_repo.repo_name})}
19 19 </code>
20 20 </p>
21 21
22 22 ${h.secure_form(h.route_path('edit_repo_remote_pull', repo_name=c.repo_name), method='POST', request=request)}
23 23 <div class="form">
24 24 <div class="fields">
25 ${h.submit('remote_pull_%s' % c.repo_info.repo_name,_('Pull changes from remote location'),class_="btn btn-small",onclick="return confirm('"+_('Confirm to pull changes from remote side')+"');")}
25 ${h.submit('remote_pull_%s' % c.rhodecode_db_repo.repo_name,_('Pull changes from remote location'),class_="btn btn-small",onclick="return confirm('"+_('Confirm to pull changes from remote side')+"');")}
26 26 </div>
27 27 </div>
28 28 ${h.end_form()}
29 29 %else:
30 30
31 31 ${_('This repository does not have any remote mirror url set.')}
32 <a href="${h.route_path('edit_repo', repo_name=c.repo_info.repo_name)}">${_('Set remote url.')}</a>
32 <a href="${h.route_path('edit_repo', repo_name=c.rhodecode_db_repo.repo_name)}">${_('Set remote url.')}</a>
33 33 <br/>
34 34 <br/>
35 35 <button class="btn disabled" type="submit" disabled="disabled">
36 36 ${_('Pull changes from remote location')}
37 37 </button>
38 38 %endif
39 39 </div>
40 40 </div>
@@ -1,22 +1,22 b''
1 1 <div class="panel panel-default">
2 2 <div class="panel-heading">
3 3 <h3 class="panel-title">${_('Repository statistics')}</h3>
4 4 </div>
5 5 <div class="panel-body">
6 ${h.secure_form(h.route_path('edit_repo_statistics_reset', repo_name=c.repo_info.repo_name), method='POST', request=request)}
6 ${h.secure_form(h.route_path('edit_repo_statistics_reset', repo_name=c.rhodecode_db_repo.repo_name), method='POST', request=request)}
7 7 <div class="form">
8 8 <div class="fields">
9 9 <div class="field" >
10 10 <dl class="dl-horizontal settings">
11 11 <dt>${_('Processed commits')}:</dt><dd>${c.stats_revision}/${c.repo_last_rev}</dd>
12 12 <dt>${_('Processed progress')}:</dt><dd>${c.stats_percentage}%</dd>
13 13 </dl>
14 14 </div>
15 ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset statistics'),class_="btn btn-small",onclick="return confirm('"+_('Confirm to remove current statistics')+"');")}
15 ${h.submit('reset_stats_%s' % c.rhodecode_db_repo.repo_name,_('Reset statistics'),class_="btn btn-small",onclick="return confirm('"+_('Confirm to remove current statistics')+"');")}
16 16 </div>
17 17 </div>
18 18 ${h.end_form()}
19 19 </div>
20 20 </div>
21 21
22 22
@@ -1,197 +1,197 b''
1 1 <div class="panel panel-default">
2 2 <div class="panel-heading">
3 3 <h3 class="panel-title">${_('Strip commits from repository')}</h3>
4 4 </div>
5 5 <div class="panel-body">
6 %if c.repo_info.repo_type != 'svn':
6 %if c.rhodecode_db_repo.repo_type != 'svn':
7 7 <h4>${_('Please provide up to %d commits commits to strip') % c.strip_limit}</h4>
8 8 <p>
9 9 ${_('In the first step commits will be verified for existance in the repository')}. </br>
10 10 ${_('In the second step, correct commits will be available for stripping')}.
11 11 </p>
12 ${h.secure_form(h.route_path('strip_check', repo_name=c.repo_info.repo_name), method='POST', request=request)}
12 ${h.secure_form(h.route_path('strip_check', repo_name=c.rhodecode_db_repo.repo_name), method='POST', request=request)}
13 13 <div id="change_body" class="field">
14 14 <div id="box-1" class="inputx locked_input">
15 15 <input class="text" id="changeset_id-1" name="changeset_id-1" size="59"
16 16 placeholder="${_('Enter full 40 character commit sha')}" type="text" value="">
17 17 <div id="plus_icon-1" class="btn btn-default plus_input_button" onclick="addNew(1);return false">
18 18 <i class="icon-plus">${_('Add another commit')}</i>
19 19 </div>
20 20 </div>
21 21 </div>
22 22
23 23 <div id="results" style="display:none; padding: 10px 0px;"></div>
24 24
25 25 <div class="buttons">
26 26 <button id="strip_action" class="btn btn-small btn-primary" onclick="checkCommits();return false">
27 27 ${_('Check commits')}
28 28 </button>
29 29 </div>
30 30
31 31 ${h.end_form()}
32 32 %else:
33 33 <h4>${_('Sorry this functionality is not available for SVN repository')}</h4>
34 34 %endif
35 35 </div>
36 36 </div>
37 37
38 38
39 39 <script>
40 40 var plus_leaf = 1;
41 41
42 42 addNew = function(number){
43 43 if (number >= ${c.strip_limit}){
44 44 return;
45 45 }
46 46 var minus = '<i class="icon-minus">${_('Remove')}</i>';
47 47 $('#plus_icon-'+number).detach();
48 48 number++;
49 49
50 50 var input = '<div id="box-'+number+'" class="inputx locked_input">'+
51 51 '<input class="text" id="changeset_id-'+number+'" name="changeset_id-'+number+'" size="59" type="text" value=""' +
52 52 'placeholder="${_('Enter full 40 character commit sha')}">'+
53 53 '<div id="plus_icon-'+number+'" class="btn btn-default plus_input_button" onclick="addNew('+number+');return false">'+
54 54 '<i class="icon-plus">${_('Add another commit')}</i>'+
55 55 '</div>'+
56 56 '<div id="minus_icon-'+number+'" class="btn btn-default minus_input_button" onclick="delOld('+(number)+');return false">'+
57 57 minus +
58 58 '</div>' +
59 59 '</div>';
60 60 $('#change_body').append(input);
61 61 plus_leaf++;
62 62 };
63 63
64 64 reIndex = function(number){
65 65 for(var i=number;i<=plus_leaf;i++){
66 66 var check = $('#box-'+i);
67 67 if (check.length == 0){
68 68 var change = $('#box-'+(i+1));
69 69 change.attr('id','box-'+i);
70 70 var plus = $('#plus_icon-'+(i+1));
71 71
72 72 if (plus.length != 0){
73 73 plus.attr('id','plus_icon-'+i);
74 74 plus.attr('onclick','addNew('+i+');return false');
75 75 plus_leaf--;
76 76 }
77 77 var minus = $('#minus_icon-'+(i+1));
78 78
79 79 minus.attr('id','minus_icon-'+i);
80 80
81 81 minus.attr('onclick','delOld('+i+');re' +
82 82 'turn false');
83 83 var input = $('input#changeset_id-'+(i+1));
84 84 input.attr('name','changeset_id-'+i);
85 85 input.attr('id','changeset_id-'+i);
86 86 }
87 87 }
88 88 };
89 89
90 90 delOld = function(number){
91 91 $('#box-'+number).remove();
92 92 number = number - 1;
93 93 var box = $('#box-'+number);
94 94 var plus = '<div id="plus_icon-'+number+'" class="btn btn-default plus_input_button" onclick="addNew('+number +');return false">'+
95 95 '<i id="i_plus_icon-'+number+'" class="icon-plus">${_('Add another commit')}</i></div>';
96 96 var minus = $('#minus_icon-'+number);
97 97 if(number +1 == plus_leaf){
98 98 minus.detach();
99 99 box.append(plus);
100 100 box.append(minus);
101 101 plus_leaf --;
102 102 }
103 103 reIndex(number+1);
104 104
105 105 };
106 106
107 107 var resultData = {
108 108 'csrf_token': CSRF_TOKEN
109 109 };
110 110
111 111 checkCommits = function() {
112 112 var postData = $('form').serialize();
113 113 $('#results').show();
114 114 $('#results').html('<h4>${_('Checking commits')}...</h4>');
115 var url = "${h.route_path('strip_check', repo_name=c.repo_info.repo_name)}";
115 var url = "${h.route_path('strip_check', repo_name=c.rhodecode_db_repo.repo_name)}";
116 116 var btn = $('#strip_action');
117 117 btn.attr('disabled', 'disabled');
118 118 btn.addClass('disabled');
119 119
120 120 var success = function (data) {
121 121 resultData = {
122 122 'csrf_token': CSRF_TOKEN
123 123 };
124 124 var i = 0;
125 125 var result = '<ol>';
126 126 $.each(data, function(index, value){
127 127 i= index;
128 128 var box = $('#box-'+index);
129 129 if (value.rev){
130 130 resultData[index] = JSON.stringify(value);
131 131
132 132 var verifiedHtml = (
133 133 '<li style="line-height:1.2em">' +
134 134 '<code>{0}</code>' +
135 135 '{1}' +
136 136 '<div style="white-space:pre">' +
137 137 'author: {2}\n' +
138 138 'description: {3}' +
139 139 '</div>' +
140 140 '</li>').format(
141 141 value.rev,
142 142 "${_(' commit verified positive')}",
143 143 value.author, value.comment
144 144 );
145 145 result += verifiedHtml;
146 146 }
147 147 else {
148 148 var verifiedHtml = (
149 149 '<li style="line-height:1.2em">' +
150 150 '<code><strike>{0}</strike></code>' +
151 151 '{1}' +
152 152 '</li>').format(
153 153 value.commit,
154 154 "${_(' commit verified negative')}"
155 155 );
156 156 result += verifiedHtml;
157 157 }
158 158 box.remove();
159 159 });
160 160 result += '</ol>';
161 161 var box = $('#box-'+(parseInt(i)+1));
162 162 box.remove();
163 163 $('#results').html(result);
164 164 };
165 165
166 166 btn.html('Strip');
167 167 btn.removeAttr('disabled');
168 168 btn.removeClass('disabled');
169 169 btn.attr('onclick','strip();return false;');
170 170 ajaxPOST(url, postData, success, null);
171 171 };
172 172
173 173 strip = function() {
174 var url = "${h.route_path('strip_execute', repo_name=c.repo_info.repo_name)}";
174 var url = "${h.route_path('strip_execute', repo_name=c.rhodecode_db_repo.repo_name)}";
175 175 var success = function(data) {
176 176 var result = '<h4>Strip executed</h4><ol>';
177 177 $.each(data, function(index, value){
178 178 if(data[index]) {
179 179 result += '<li><code>' +index+ '</code> ${_(' commit striped successfully')}' + '</li>';
180 180 }
181 181 else {
182 182 result += '<li><code>' +index+ '</code> ${_(' commit strip failed')}' + '</li>';
183 183 }
184 184 });
185 185 if ($.isEmptyObject(data)) {
186 186 result += '<li>Nothing done...</li>'
187 187 }
188 188 result += '</ol>';
189 189 $('#results').html(result);
190 190
191 191 };
192 192 ajaxPOST(url, resultData, success, null);
193 193 var btn = $('#strip_action');
194 194 btn.remove();
195 195
196 196 };
197 197 </script>
@@ -1,73 +1,73 b''
1 1 <%namespace name="vcss" file="/base/vcs_settings.mako"/>
2 2
3 3 <div id="repo_vcs_settings" class="${'inherited' if c.inherit_global_settings else ''}">
4 ${h.secure_form(h.route_path('edit_repo_vcs_update', repo_name=c.repo_info.repo_name), method='POST', request=request)}
4 ${h.secure_form(h.route_path('edit_repo_vcs_update', repo_name=c.rhodecode_db_repo.repo_name), method='POST', request=request)}
5 5 <div class="form panel panel-default">
6 6 <div class="fields panel-body">
7 7 <div class="field">
8 8 <div class="label label-checkbox">
9 9 <label for="inherit_global_settings">${_('Inherit from global settings')}:</label>
10 10 </div>
11 11 <div class="checkboxes">
12 12 ${h.checkbox('inherit_global_settings',value=True)}
13 13 <span class="help-block">${h.literal(_('Select to inherit global vcs settings.'))}</span>
14 14 </div>
15 15 </div>
16 16 </div>
17 17 </div>
18 18
19 19 <div id="inherit_overlay_vcs_default">
20 20 <div>
21 21 ${vcss.vcs_settings_fields(
22 22 suffix='_inherited',
23 23 svn_tag_patterns=c.global_svn_tag_patterns,
24 24 svn_branch_patterns=c.global_svn_branch_patterns,
25 repo_type=c.repo_info.repo_type,
25 repo_type=c.rhodecode_db_repo.repo_type,
26 26 disabled='disabled'
27 27 )}
28 28 </div>
29 29 </div>
30 30
31 31 <div id="inherit_overlay_vcs_custom">
32 32 <div>
33 33 ${vcss.vcs_settings_fields(
34 34 suffix='',
35 35 svn_tag_patterns=c.svn_tag_patterns,
36 36 svn_branch_patterns=c.svn_branch_patterns,
37 repo_type=c.repo_info.repo_type
37 repo_type=c.rhodecode_db_repo.repo_type
38 38 )}
39 39 </div>
40 40 </div>
41 41
42 42 <div class="buttons">
43 43 ${h.submit('save',_('Save settings'),class_="btn")}
44 44 ${h.reset('reset',_('Reset'),class_="btn")}
45 45 </div>
46 46
47 47 ${h.end_form()}
48 48 </div>
49 49
50 50 <script type="text/javascript">
51 51
52 52 function ajaxDeletePattern(pattern_id, field_id) {
53 var sUrl = "${h.route_path('edit_repo_vcs_svn_pattern_delete', repo_name=c.repo_info.repo_name)}";
53 var sUrl = "${h.route_path('edit_repo_vcs_svn_pattern_delete', repo_name=c.rhodecode_db_repo.repo_name)}";
54 54 var callback = function (o) {
55 55 var elem = $("#"+field_id);
56 56 elem.remove();
57 57 };
58 58 var postData = {
59 59 'delete_svn_pattern': pattern_id,
60 60 'csrf_token': CSRF_TOKEN
61 61 };
62 62 var request = $.post(sUrl, postData)
63 63 .done(callback)
64 64 .fail(function (data, textStatus, errorThrown) {
65 65 alert("Error while deleting hooks.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(this)[0].url));
66 66 });
67 67 }
68 68
69 69 $('#inherit_global_settings').on('change', function(e){
70 70 $('#repo_vcs_settings').toggleClass('inherited', this.checked);
71 71 });
72 72
73 73 </script>
@@ -1,129 +1,129 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Fork repository %s') % 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 12 ${_('New Fork')}
13 13 </%def>
14 14
15 15 <%def name="menu_bar_nav()">
16 16 ${self.menu_items(active='repositories')}
17 17 </%def>
18 18
19 19 <%def name="menu_bar_subnav()">
20 20 ${self.repo_menu(active='options')}
21 21 </%def>
22 22
23 23 <%def name="main()">
24 24 <div class="box">
25 25 <div class="title">
26 26 ${self.repo_page_title(c.rhodecode_db_repo)}
27 27 ${self.breadcrumbs()}
28 28 </div>
29 29
30 ${h.secure_form(h.route_path('repo_fork_create',repo_name=c.repo_info.repo_name), method='POST', request=request)}
30 ${h.secure_form(h.route_path('repo_fork_create',repo_name=c.rhodecode_db_repo.repo_name), method='POST', request=request)}
31 31 <div class="form">
32 32 <!-- fields -->
33 33 <div class="fields">
34 34
35 35 <div class="field">
36 36 <div class="label">
37 37 <label for="repo_name">${_('Fork name')}:</label>
38 38 </div>
39 39 <div class="input">
40 40 ${h.text('repo_name', class_="medium")}
41 ${h.hidden('repo_type',c.repo_info.repo_type)}
42 ${h.hidden('fork_parent_id',c.repo_info.repo_id)}
41 ${h.hidden('repo_type',c.rhodecode_db_repo.repo_type)}
42 ${h.hidden('fork_parent_id',c.rhodecode_db_repo.repo_id)}
43 43 </div>
44 44 </div>
45 45
46 46 <div class="field">
47 47 <div class="label label-textarea">
48 48 <label for="description">${_('Description')}:</label>
49 49 </div>
50 50 <div class="textarea-repo textarea text-area editor">
51 51 ${h.textarea('description')}
52 52 <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
53 53 </div>
54 54 </div>
55 55
56 56 <div class="field">
57 57 <div class="label">
58 58 <label for="repo_group">${_('Repository group')}:</label>
59 59 </div>
60 60 <div class="select">
61 61 ${h.select('repo_group','',c.repo_groups,class_="medium")}
62 62 % if c.personal_repo_group:
63 63 <a class="btn" href="#" id="select_my_group" data-personal-group-id="${c.personal_repo_group.group_id}">
64 64 ${_('Select my personal group (%(repo_group_name)s)') % {'repo_group_name': c.personal_repo_group.group_name}}
65 65 </a>
66 66 % endif
67 67 <span class="help-block">${_('Optionally select a group to put this repository into.')}</span>
68 68 </div>
69 69 </div>
70 70
71 71 <div class="field">
72 72 <div class="label">
73 73 <label for="landing_rev">${_('Landing commit')}:</label>
74 74 </div>
75 75 <div class="select">
76 76 ${h.select('landing_rev','',c.landing_revs,class_="medium")}
77 77 <span class="help-block">${_('Default commit for files page, downloads, whoosh and readme')}</span>
78 78 </div>
79 79 </div>
80 80
81 81 <div class="field">
82 82 <div class="label label-checkbox">
83 83 <label for="private">${_('Private')}:</label>
84 84 </div>
85 85 <div class="checkboxes">
86 86 ${h.checkbox('private',value="True")}
87 87 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
88 88 </div>
89 89 </div>
90 90
91 91 <div class="field">
92 92 <div class="label label-checkbox">
93 93 <label for="private">${_('Copy permissions')}:</label>
94 94 </div>
95 95 <div class="checkboxes">
96 96 ${h.checkbox('copy_permissions',value="True", checked="checked")}
97 97 <span class="help-block">${_('Copy permissions from forked repository')}</span>
98 98 </div>
99 99 </div>
100 100
101 101 <div class="buttons">
102 102 ${h.submit('',_('Fork this Repository'),class_="btn")}
103 103 </div>
104 104 </div>
105 105 </div>
106 106 ${h.end_form()}
107 107 </div>
108 108 <script>
109 109 $(document).ready(function(){
110 110 $("#repo_group").select2({
111 111 'dropdownAutoWidth': true,
112 112 'containerCssClass': "drop-menu",
113 113 'dropdownCssClass': "drop-menu-dropdown",
114 114 'width': "resolve"
115 115 });
116 116 $("#landing_rev").select2({
117 117 'containerCssClass': "drop-menu",
118 118 'dropdownCssClass': "drop-menu-dropdown",
119 119 'minimumResultsForSearch': -1
120 120 });
121 121 $('#repo_name').focus();
122 122
123 123 $('#select_my_group').on('click', function(e){
124 124 e.preventDefault();
125 125 $("#repo_group").val($(this).data('personalGroupId')).trigger("change");
126 126 })
127 127 })
128 128 </script>
129 129 </%def>
General Comments 0
You need to be logged in to leave comments. Login now