##// 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 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import time
21 import time
22 import logging
22 import logging
23 import operator
23 import operator
24
24
25 from pyramid.httpexceptions import HTTPFound
25 from pyramid.httpexceptions import HTTPFound
26
26
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
28 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
29 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
29 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
30 from rhodecode.model import repo
30 from rhodecode.model import repo
31 from rhodecode.model import repo_group
31 from rhodecode.model import repo_group
32 from rhodecode.model import user_group
32 from rhodecode.model import user_group
33 from rhodecode.model.db import User
33 from rhodecode.model.db import User
34 from rhodecode.model.scm import ScmModel
34 from rhodecode.model.scm import ScmModel
35
35
36 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
37
37
38
38
39 ADMIN_PREFIX = '/_admin'
39 ADMIN_PREFIX = '/_admin'
40 STATIC_FILE_PREFIX = '/_static'
40 STATIC_FILE_PREFIX = '/_static'
41
41
42 URL_NAME_REQUIREMENTS = {
42 URL_NAME_REQUIREMENTS = {
43 # group name can have a slash in them, but they must not end with a slash
43 # group name can have a slash in them, but they must not end with a slash
44 'group_name': r'.*?[^/]',
44 'group_name': r'.*?[^/]',
45 'repo_group_name': r'.*?[^/]',
45 'repo_group_name': r'.*?[^/]',
46 # repo names can have a slash in them, but they must not end with a slash
46 # repo names can have a slash in them, but they must not end with a slash
47 'repo_name': r'.*?[^/]',
47 'repo_name': r'.*?[^/]',
48 # file path eats up everything at the end
48 # file path eats up everything at the end
49 'f_path': r'.*',
49 'f_path': r'.*',
50 # reference types
50 # reference types
51 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
51 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
52 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
52 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
53 }
53 }
54
54
55
55
56 def add_route_with_slash(config,name, pattern, **kw):
56 def add_route_with_slash(config,name, pattern, **kw):
57 config.add_route(name, pattern, **kw)
57 config.add_route(name, pattern, **kw)
58 if not pattern.endswith('/'):
58 if not pattern.endswith('/'):
59 config.add_route(name + '_slash', pattern + '/', **kw)
59 config.add_route(name + '_slash', pattern + '/', **kw)
60
60
61
61
62 def add_route_requirements(route_path, requirements=URL_NAME_REQUIREMENTS):
62 def add_route_requirements(route_path, requirements=URL_NAME_REQUIREMENTS):
63 """
63 """
64 Adds regex requirements to pyramid routes using a mapping dict
64 Adds regex requirements to pyramid routes using a mapping dict
65 e.g::
65 e.g::
66 add_route_requirements('{repo_name}/settings')
66 add_route_requirements('{repo_name}/settings')
67 """
67 """
68 for key, regex in requirements.items():
68 for key, regex in requirements.items():
69 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
69 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
70 return route_path
70 return route_path
71
71
72
72
73 def get_format_ref_id(repo):
73 def get_format_ref_id(repo):
74 """Returns a `repo` specific reference formatter function"""
74 """Returns a `repo` specific reference formatter function"""
75 if h.is_svn(repo):
75 if h.is_svn(repo):
76 return _format_ref_id_svn
76 return _format_ref_id_svn
77 else:
77 else:
78 return _format_ref_id
78 return _format_ref_id
79
79
80
80
81 def _format_ref_id(name, raw_id):
81 def _format_ref_id(name, raw_id):
82 """Default formatting of a given reference `name`"""
82 """Default formatting of a given reference `name`"""
83 return name
83 return name
84
84
85
85
86 def _format_ref_id_svn(name, raw_id):
86 def _format_ref_id_svn(name, raw_id):
87 """Special way of formatting a reference for Subversion including path"""
87 """Special way of formatting a reference for Subversion including path"""
88 return '%s@%s' % (name, raw_id)
88 return '%s@%s' % (name, raw_id)
89
89
90
90
91 class TemplateArgs(StrictAttributeDict):
91 class TemplateArgs(StrictAttributeDict):
92 pass
92 pass
93
93
94
94
95 class BaseAppView(object):
95 class BaseAppView(object):
96
96
97 def __init__(self, context, request):
97 def __init__(self, context, request):
98 self.request = request
98 self.request = request
99 self.context = context
99 self.context = context
100 self.session = request.session
100 self.session = request.session
101 self._rhodecode_user = request.user # auth user
101 self._rhodecode_user = request.user # auth user
102 self._rhodecode_db_user = self._rhodecode_user.get_instance()
102 self._rhodecode_db_user = self._rhodecode_user.get_instance()
103 self._maybe_needs_password_change(
103 self._maybe_needs_password_change(
104 request.matched_route.name, self._rhodecode_db_user)
104 request.matched_route.name, self._rhodecode_db_user)
105
105
106 def _maybe_needs_password_change(self, view_name, user_obj):
106 def _maybe_needs_password_change(self, view_name, user_obj):
107 log.debug('Checking if user %s needs password change on view %s',
107 log.debug('Checking if user %s needs password change on view %s',
108 user_obj, view_name)
108 user_obj, view_name)
109 skip_user_views = [
109 skip_user_views = [
110 'logout', 'login',
110 'logout', 'login',
111 'my_account_password', 'my_account_password_update'
111 'my_account_password', 'my_account_password_update'
112 ]
112 ]
113
113
114 if not user_obj:
114 if not user_obj:
115 return
115 return
116
116
117 if user_obj.username == User.DEFAULT_USER:
117 if user_obj.username == User.DEFAULT_USER:
118 return
118 return
119
119
120 now = time.time()
120 now = time.time()
121 should_change = user_obj.user_data.get('force_password_change')
121 should_change = user_obj.user_data.get('force_password_change')
122 change_after = safe_int(should_change) or 0
122 change_after = safe_int(should_change) or 0
123 if should_change and now > change_after:
123 if should_change and now > change_after:
124 log.debug('User %s requires password change', user_obj)
124 log.debug('User %s requires password change', user_obj)
125 h.flash('You are required to change your password', 'warning',
125 h.flash('You are required to change your password', 'warning',
126 ignore_duplicate=True)
126 ignore_duplicate=True)
127
127
128 if view_name not in skip_user_views:
128 if view_name not in skip_user_views:
129 raise HTTPFound(
129 raise HTTPFound(
130 self.request.route_path('my_account_password'))
130 self.request.route_path('my_account_password'))
131
131
132 def _log_creation_exception(self, e, repo_name):
132 def _log_creation_exception(self, e, repo_name):
133 _ = self.request.translate
133 _ = self.request.translate
134 reason = None
134 reason = None
135 if len(e.args) == 2:
135 if len(e.args) == 2:
136 reason = e.args[1]
136 reason = e.args[1]
137
137
138 if reason == 'INVALID_CERTIFICATE':
138 if reason == 'INVALID_CERTIFICATE':
139 log.exception(
139 log.exception(
140 'Exception creating a repository: invalid certificate')
140 'Exception creating a repository: invalid certificate')
141 msg = (_('Error creating repository %s: invalid certificate')
141 msg = (_('Error creating repository %s: invalid certificate')
142 % repo_name)
142 % repo_name)
143 else:
143 else:
144 log.exception("Exception creating a repository")
144 log.exception("Exception creating a repository")
145 msg = (_('Error creating repository %s')
145 msg = (_('Error creating repository %s')
146 % repo_name)
146 % repo_name)
147 return msg
147 return msg
148
148
149 def _get_local_tmpl_context(self, include_app_defaults=False):
149 def _get_local_tmpl_context(self, include_app_defaults=False):
150 c = TemplateArgs()
150 c = TemplateArgs()
151 c.auth_user = self.request.user
151 c.auth_user = self.request.user
152 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
152 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
153 c.rhodecode_user = self.request.user
153 c.rhodecode_user = self.request.user
154
154
155 if include_app_defaults:
155 if include_app_defaults:
156 # NOTE(marcink): after full pyramid migration include_app_defaults
156 # NOTE(marcink): after full pyramid migration include_app_defaults
157 # should be turned on by default
157 # should be turned on by default
158 from rhodecode.lib.base import attach_context_attributes
158 from rhodecode.lib.base import attach_context_attributes
159 attach_context_attributes(c, self.request, self.request.user.user_id)
159 attach_context_attributes(c, self.request, self.request.user.user_id)
160
160
161 return c
161 return c
162
162
163 def _register_global_c(self, tmpl_args):
163 def _register_global_c(self, tmpl_args):
164 """
164 """
165 Registers attributes to pylons global `c`
165 Registers attributes to pylons global `c`
166 """
166 """
167
167
168 # TODO(marcink): remove once pyramid migration is finished
168 # TODO(marcink): remove once pyramid migration is finished
169 from pylons import tmpl_context as c
169 from pylons import tmpl_context as c
170 try:
170 try:
171 for k, v in tmpl_args.items():
171 for k, v in tmpl_args.items():
172 setattr(c, k, v)
172 setattr(c, k, v)
173 except TypeError:
173 except TypeError:
174 log.exception('Failed to register pylons C')
174 log.exception('Failed to register pylons C')
175 pass
175 pass
176
176
177 def _get_template_context(self, tmpl_args):
177 def _get_template_context(self, tmpl_args):
178 self._register_global_c(tmpl_args)
178 self._register_global_c(tmpl_args)
179
179
180 local_tmpl_args = {
180 local_tmpl_args = {
181 'defaults': {},
181 'defaults': {},
182 'errors': {},
182 'errors': {},
183 # register a fake 'c' to be used in templates instead of global
183 # register a fake 'c' to be used in templates instead of global
184 # pylons c, after migration to pyramid we should rename it to 'c'
184 # pylons c, after migration to pyramid we should rename it to 'c'
185 # make sure we replace usage of _c in templates too
185 # make sure we replace usage of _c in templates too
186 '_c': tmpl_args
186 '_c': tmpl_args
187 }
187 }
188 local_tmpl_args.update(tmpl_args)
188 local_tmpl_args.update(tmpl_args)
189 return local_tmpl_args
189 return local_tmpl_args
190
190
191 def load_default_context(self):
191 def load_default_context(self):
192 """
192 """
193 example:
193 example:
194
194
195 def load_default_context(self):
195 def load_default_context(self):
196 c = self._get_local_tmpl_context()
196 c = self._get_local_tmpl_context()
197 c.custom_var = 'foobar'
197 c.custom_var = 'foobar'
198 self._register_global_c(c)
198 self._register_global_c(c)
199 return c
199 return c
200 """
200 """
201 raise NotImplementedError('Needs implementation in view class')
201 raise NotImplementedError('Needs implementation in view class')
202
202
203
203
204 class RepoAppView(BaseAppView):
204 class RepoAppView(BaseAppView):
205
205
206 def __init__(self, context, request):
206 def __init__(self, context, request):
207 super(RepoAppView, self).__init__(context, request)
207 super(RepoAppView, self).__init__(context, request)
208 self.db_repo = request.db_repo
208 self.db_repo = request.db_repo
209 self.db_repo_name = self.db_repo.repo_name
209 self.db_repo_name = self.db_repo.repo_name
210 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
210 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
211
211
212 def _handle_missing_requirements(self, error):
212 def _handle_missing_requirements(self, error):
213 log.error(
213 log.error(
214 'Requirements are missing for repository %s: %s',
214 'Requirements are missing for repository %s: %s',
215 self.db_repo_name, error.message)
215 self.db_repo_name, error.message)
216
216
217 def _get_local_tmpl_context(self, include_app_defaults=False):
217 def _get_local_tmpl_context(self, include_app_defaults=False):
218 _ = self.request.translate
218 _ = self.request.translate
219 c = super(RepoAppView, self)._get_local_tmpl_context(
219 c = super(RepoAppView, self)._get_local_tmpl_context(
220 include_app_defaults=include_app_defaults)
220 include_app_defaults=include_app_defaults)
221
221
222 # register common vars for this type of view
222 # register common vars for this type of view
223 c.rhodecode_db_repo = self.db_repo
223 c.rhodecode_db_repo = self.db_repo
224 c.repo_name = self.db_repo_name
224 c.repo_name = self.db_repo_name
225 c.repository_pull_requests = self.db_repo_pull_requests
225 c.repository_pull_requests = self.db_repo_pull_requests
226
226
227 c.repository_requirements_missing = False
227 c.repository_requirements_missing = False
228 try:
228 try:
229 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
229 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
230 except RepositoryRequirementError as e:
230 except RepositoryRequirementError as e:
231 c.repository_requirements_missing = True
231 c.repository_requirements_missing = True
232 self._handle_missing_requirements(e)
232 self._handle_missing_requirements(e)
233 self.rhodecode_vcs_repo = None
233 self.rhodecode_vcs_repo = None
234
234
235 if (not c.repository_requirements_missing
235 if (not c.repository_requirements_missing
236 and self.rhodecode_vcs_repo is None):
236 and self.rhodecode_vcs_repo is None):
237 # unable to fetch this repo as vcs instance, report back to user
237 # unable to fetch this repo as vcs instance, report back to user
238 h.flash(_(
238 h.flash(_(
239 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
239 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
240 "Please check if it exist, or is not damaged.") %
240 "Please check if it exist, or is not damaged.") %
241 {'repo_name': c.repo_name},
241 {'repo_name': c.repo_name},
242 category='error', ignore_duplicate=True)
242 category='error', ignore_duplicate=True)
243 raise HTTPFound(h.route_path('home'))
243 raise HTTPFound(h.route_path('home'))
244
244
245 return c
245 return c
246
246
247 def _get_f_path(self, matchdict, default=None):
247 def _get_f_path(self, matchdict, default=None):
248 f_path = matchdict.get('f_path')
248 f_path = matchdict.get('f_path')
249 if f_path:
249 if f_path:
250 # fix for multiple initial slashes that causes errors for GIT
250 # fix for multiple initial slashes that causes errors for GIT
251 return f_path.lstrip('/')
251 return f_path.lstrip('/')
252
252
253 return default
253 return default
254
254
255
255
256 class RepoGroupAppView(BaseAppView):
256 class RepoGroupAppView(BaseAppView):
257 def __init__(self, context, request):
257 def __init__(self, context, request):
258 super(RepoGroupAppView, self).__init__(context, request)
258 super(RepoGroupAppView, self).__init__(context, request)
259 self.db_repo_group = request.db_repo_group
259 self.db_repo_group = request.db_repo_group
260 self.db_repo_group_name = self.db_repo_group.group_name
260 self.db_repo_group_name = self.db_repo_group.group_name
261
261
262
262
263 class UserGroupAppView(BaseAppView):
263 class UserGroupAppView(BaseAppView):
264 def __init__(self, context, request):
264 def __init__(self, context, request):
265 super(UserGroupAppView, self).__init__(context, request)
265 super(UserGroupAppView, self).__init__(context, request)
266 self.db_user_group = request.db_user_group
266 self.db_user_group = request.db_user_group
267 self.db_user_group_name = self.db_user_group.users_group_name
267 self.db_user_group_name = self.db_user_group.users_group_name
268
268
269
269
270 class DataGridAppView(object):
270 class DataGridAppView(object):
271 """
271 """
272 Common class to have re-usable grid rendering components
272 Common class to have re-usable grid rendering components
273 """
273 """
274
274
275 def _extract_ordering(self, request, column_map=None):
275 def _extract_ordering(self, request, column_map=None):
276 column_map = column_map or {}
276 column_map = column_map or {}
277 column_index = safe_int(request.GET.get('order[0][column]'))
277 column_index = safe_int(request.GET.get('order[0][column]'))
278 order_dir = request.GET.get(
278 order_dir = request.GET.get(
279 'order[0][dir]', 'desc')
279 'order[0][dir]', 'desc')
280 order_by = request.GET.get(
280 order_by = request.GET.get(
281 'columns[%s][data][sort]' % column_index, 'name_raw')
281 'columns[%s][data][sort]' % column_index, 'name_raw')
282
282
283 # translate datatable to DB columns
283 # translate datatable to DB columns
284 order_by = column_map.get(order_by) or order_by
284 order_by = column_map.get(order_by) or order_by
285
285
286 search_q = request.GET.get('search[value]')
286 search_q = request.GET.get('search[value]')
287 return search_q, order_by, order_dir
287 return search_q, order_by, order_dir
288
288
289 def _extract_chunk(self, request):
289 def _extract_chunk(self, request):
290 start = safe_int(request.GET.get('start'), 0)
290 start = safe_int(request.GET.get('start'), 0)
291 length = safe_int(request.GET.get('length'), 25)
291 length = safe_int(request.GET.get('length'), 25)
292 draw = safe_int(request.GET.get('draw'))
292 draw = safe_int(request.GET.get('draw'))
293 return draw, start, length
293 return draw, start, length
294
294
295 def _get_order_col(self, order_by, model):
295 def _get_order_col(self, order_by, model):
296 if isinstance(order_by, basestring):
296 if isinstance(order_by, basestring):
297 try:
297 try:
298 return operator.attrgetter(order_by)(model)
298 return operator.attrgetter(order_by)(model)
299 except AttributeError:
299 except AttributeError:
300 return None
300 return None
301 else:
301 else:
302 return order_by
302 return order_by
303
303
304
304
305 class BaseReferencesView(RepoAppView):
305 class BaseReferencesView(RepoAppView):
306 """
306 """
307 Base for reference view for branches, tags and bookmarks.
307 Base for reference view for branches, tags and bookmarks.
308 """
308 """
309 def load_default_context(self):
309 def load_default_context(self):
310 c = self._get_local_tmpl_context()
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 self._register_global_c(c)
312 self._register_global_c(c)
316 return c
313 return c
317
314
318 def load_refs_context(self, ref_items, partials_template):
315 def load_refs_context(self, ref_items, partials_template):
319 _render = self.request.get_partial_renderer(partials_template)
316 _render = self.request.get_partial_renderer(partials_template)
320 pre_load = ["author", "date", "message"]
317 pre_load = ["author", "date", "message"]
321
318
322 is_svn = h.is_svn(self.rhodecode_vcs_repo)
319 is_svn = h.is_svn(self.rhodecode_vcs_repo)
323 is_hg = h.is_hg(self.rhodecode_vcs_repo)
320 is_hg = h.is_hg(self.rhodecode_vcs_repo)
324
321
325 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
322 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
326
323
327 closed_refs = {}
324 closed_refs = {}
328 if is_hg:
325 if is_hg:
329 closed_refs = self.rhodecode_vcs_repo.branches_closed
326 closed_refs = self.rhodecode_vcs_repo.branches_closed
330
327
331 data = []
328 data = []
332 for ref_name, commit_id in ref_items:
329 for ref_name, commit_id in ref_items:
333 commit = self.rhodecode_vcs_repo.get_commit(
330 commit = self.rhodecode_vcs_repo.get_commit(
334 commit_id=commit_id, pre_load=pre_load)
331 commit_id=commit_id, pre_load=pre_load)
335 closed = ref_name in closed_refs
332 closed = ref_name in closed_refs
336
333
337 # TODO: johbo: Unify generation of reference links
334 # TODO: johbo: Unify generation of reference links
338 use_commit_id = '/' in ref_name or is_svn
335 use_commit_id = '/' in ref_name or is_svn
339
336
340 if use_commit_id:
337 if use_commit_id:
341 files_url = h.route_path(
338 files_url = h.route_path(
342 'repo_files',
339 'repo_files',
343 repo_name=self.db_repo_name,
340 repo_name=self.db_repo_name,
344 f_path=ref_name if is_svn else '',
341 f_path=ref_name if is_svn else '',
345 commit_id=commit_id)
342 commit_id=commit_id)
346
343
347 else:
344 else:
348 files_url = h.route_path(
345 files_url = h.route_path(
349 'repo_files',
346 'repo_files',
350 repo_name=self.db_repo_name,
347 repo_name=self.db_repo_name,
351 f_path=ref_name if is_svn else '',
348 f_path=ref_name if is_svn else '',
352 commit_id=ref_name,
349 commit_id=ref_name,
353 _query=dict(at=ref_name))
350 _query=dict(at=ref_name))
354
351
355 data.append({
352 data.append({
356 "name": _render('name', ref_name, files_url, closed),
353 "name": _render('name', ref_name, files_url, closed),
357 "name_raw": ref_name,
354 "name_raw": ref_name,
358 "date": _render('date', commit.date),
355 "date": _render('date', commit.date),
359 "date_raw": datetime_to_time(commit.date),
356 "date_raw": datetime_to_time(commit.date),
360 "author": _render('author', commit.author),
357 "author": _render('author', commit.author),
361 "commit": _render(
358 "commit": _render(
362 'commit', commit.message, commit.raw_id, commit.idx),
359 'commit', commit.message, commit.raw_id, commit.idx),
363 "commit_raw": commit.idx,
360 "commit_raw": commit.idx,
364 "compare": _render(
361 "compare": _render(
365 'compare', format_ref_id(ref_name, commit.raw_id)),
362 'compare', format_ref_id(ref_name, commit.raw_id)),
366 })
363 })
367
364
368 return data
365 return data
369
366
370
367
371 class RepoRoutePredicate(object):
368 class RepoRoutePredicate(object):
372 def __init__(self, val, config):
369 def __init__(self, val, config):
373 self.val = val
370 self.val = val
374
371
375 def text(self):
372 def text(self):
376 return 'repo_route = %s' % self.val
373 return 'repo_route = %s' % self.val
377
374
378 phash = text
375 phash = text
379
376
380 def __call__(self, info, request):
377 def __call__(self, info, request):
381
378
382 if hasattr(request, 'vcs_call'):
379 if hasattr(request, 'vcs_call'):
383 # skip vcs calls
380 # skip vcs calls
384 return
381 return
385
382
386 repo_name = info['match']['repo_name']
383 repo_name = info['match']['repo_name']
387 repo_model = repo.RepoModel()
384 repo_model = repo.RepoModel()
388 by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
385 by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
389
386
390 def redirect_if_creating(db_repo):
387 def redirect_if_creating(db_repo):
391 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
388 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
392 raise HTTPFound(
389 raise HTTPFound(
393 request.route_path('repo_creating',
390 request.route_path('repo_creating',
394 repo_name=db_repo.repo_name))
391 repo_name=db_repo.repo_name))
395
392
396 if by_name_match:
393 if by_name_match:
397 # register this as request object we can re-use later
394 # register this as request object we can re-use later
398 request.db_repo = by_name_match
395 request.db_repo = by_name_match
399 redirect_if_creating(by_name_match)
396 redirect_if_creating(by_name_match)
400 return True
397 return True
401
398
402 by_id_match = repo_model.get_repo_by_id(repo_name)
399 by_id_match = repo_model.get_repo_by_id(repo_name)
403 if by_id_match:
400 if by_id_match:
404 request.db_repo = by_id_match
401 request.db_repo = by_id_match
405 redirect_if_creating(by_id_match)
402 redirect_if_creating(by_id_match)
406 return True
403 return True
407
404
408 return False
405 return False
409
406
410
407
411 class RepoTypeRoutePredicate(object):
408 class RepoTypeRoutePredicate(object):
412 def __init__(self, val, config):
409 def __init__(self, val, config):
413 self.val = val or ['hg', 'git', 'svn']
410 self.val = val or ['hg', 'git', 'svn']
414
411
415 def text(self):
412 def text(self):
416 return 'repo_accepted_type = %s' % self.val
413 return 'repo_accepted_type = %s' % self.val
417
414
418 phash = text
415 phash = text
419
416
420 def __call__(self, info, request):
417 def __call__(self, info, request):
421 if hasattr(request, 'vcs_call'):
418 if hasattr(request, 'vcs_call'):
422 # skip vcs calls
419 # skip vcs calls
423 return
420 return
424
421
425 rhodecode_db_repo = request.db_repo
422 rhodecode_db_repo = request.db_repo
426
423
427 log.debug(
424 log.debug(
428 '%s checking repo type for %s in %s',
425 '%s checking repo type for %s in %s',
429 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
426 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
430
427
431 if rhodecode_db_repo.repo_type in self.val:
428 if rhodecode_db_repo.repo_type in self.val:
432 return True
429 return True
433 else:
430 else:
434 log.warning('Current view is not supported for repo type:%s',
431 log.warning('Current view is not supported for repo type:%s',
435 rhodecode_db_repo.repo_type)
432 rhodecode_db_repo.repo_type)
436 #
433 #
437 # h.flash(h.literal(
434 # h.flash(h.literal(
438 # _('Action not supported for %s.' % rhodecode_repo.alias)),
435 # _('Action not supported for %s.' % rhodecode_repo.alias)),
439 # category='warning')
436 # category='warning')
440 # return redirect(
437 # return redirect(
441 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
438 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
442
439
443 return False
440 return False
444
441
445
442
446 class RepoGroupRoutePredicate(object):
443 class RepoGroupRoutePredicate(object):
447 def __init__(self, val, config):
444 def __init__(self, val, config):
448 self.val = val
445 self.val = val
449
446
450 def text(self):
447 def text(self):
451 return 'repo_group_route = %s' % self.val
448 return 'repo_group_route = %s' % self.val
452
449
453 phash = text
450 phash = text
454
451
455 def __call__(self, info, request):
452 def __call__(self, info, request):
456 if hasattr(request, 'vcs_call'):
453 if hasattr(request, 'vcs_call'):
457 # skip vcs calls
454 # skip vcs calls
458 return
455 return
459
456
460 repo_group_name = info['match']['repo_group_name']
457 repo_group_name = info['match']['repo_group_name']
461 repo_group_model = repo_group.RepoGroupModel()
458 repo_group_model = repo_group.RepoGroupModel()
462 by_name_match = repo_group_model.get_by_group_name(
459 by_name_match = repo_group_model.get_by_group_name(
463 repo_group_name, cache=True)
460 repo_group_name, cache=True)
464
461
465 if by_name_match:
462 if by_name_match:
466 # register this as request object we can re-use later
463 # register this as request object we can re-use later
467 request.db_repo_group = by_name_match
464 request.db_repo_group = by_name_match
468 return True
465 return True
469
466
470 return False
467 return False
471
468
472
469
473 class UserGroupRoutePredicate(object):
470 class UserGroupRoutePredicate(object):
474 def __init__(self, val, config):
471 def __init__(self, val, config):
475 self.val = val
472 self.val = val
476
473
477 def text(self):
474 def text(self):
478 return 'user_group_route = %s' % self.val
475 return 'user_group_route = %s' % self.val
479
476
480 phash = text
477 phash = text
481
478
482 def __call__(self, info, request):
479 def __call__(self, info, request):
483 if hasattr(request, 'vcs_call'):
480 if hasattr(request, 'vcs_call'):
484 # skip vcs calls
481 # skip vcs calls
485 return
482 return
486
483
487 user_group_id = info['match']['user_group_id']
484 user_group_id = info['match']['user_group_id']
488 user_group_model = user_group.UserGroup()
485 user_group_model = user_group.UserGroup()
489 by_name_match = user_group_model.get(
486 by_name_match = user_group_model.get(
490 user_group_id, cache=True)
487 user_group_id, cache=True)
491
488
492 if by_name_match:
489 if by_name_match:
493 # register this as request object we can re-use later
490 # register this as request object we can re-use later
494 request.db_user_group = by_name_match
491 request.db_user_group = by_name_match
495 return True
492 return True
496
493
497 return False
494 return False
498
495
499
496
500 def includeme(config):
497 def includeme(config):
501 config.add_route_predicate(
498 config.add_route_predicate(
502 'repo_route', RepoRoutePredicate)
499 'repo_route', RepoRoutePredicate)
503 config.add_route_predicate(
500 config.add_route_predicate(
504 'repo_accepted_types', RepoTypeRoutePredicate)
501 'repo_accepted_types', RepoTypeRoutePredicate)
505 config.add_route_predicate(
502 config.add_route_predicate(
506 'repo_group_route', RepoGroupRoutePredicate)
503 'repo_group_route', RepoGroupRoutePredicate)
507 config.add_route_predicate(
504 config.add_route_predicate(
508 'user_group_route', UserGroupRoutePredicate)
505 'user_group_route', UserGroupRoutePredicate)
@@ -1,78 +1,75 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from pyramid.httpexceptions import HTTPFound
23 from pyramid.httpexceptions import HTTPFound
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25
25
26 from rhodecode.apps._base import RepoAppView
26 from rhodecode.apps._base import RepoAppView
27 from rhodecode.lib.auth import (
27 from rhodecode.lib.auth import (
28 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
28 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
29 from rhodecode.lib import helpers as h
29 from rhodecode.lib import helpers as h
30 from rhodecode.model.meta import Session
30 from rhodecode.model.meta import Session
31 from rhodecode.model.scm import ScmModel
31 from rhodecode.model.scm import ScmModel
32
32
33 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
34
34
35
35
36 class RepoCachesView(RepoAppView):
36 class RepoCachesView(RepoAppView):
37 def load_default_context(self):
37 def load_default_context(self):
38 c = self._get_local_tmpl_context()
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 self._register_global_c(c)
40 self._register_global_c(c)
44 return c
41 return c
45
42
46 @LoginRequired()
43 @LoginRequired()
47 @HasRepoPermissionAnyDecorator('repository.admin')
44 @HasRepoPermissionAnyDecorator('repository.admin')
48 @view_config(
45 @view_config(
49 route_name='edit_repo_caches', request_method='GET',
46 route_name='edit_repo_caches', request_method='GET',
50 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
47 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
51 def repo_caches(self):
48 def repo_caches(self):
52 c = self.load_default_context()
49 c = self.load_default_context()
53 c.active = 'caches'
50 c.active = 'caches'
54
51
55 return self._get_template_context(c)
52 return self._get_template_context(c)
56
53
57 @LoginRequired()
54 @LoginRequired()
58 @HasRepoPermissionAnyDecorator('repository.admin')
55 @HasRepoPermissionAnyDecorator('repository.admin')
59 @CSRFRequired()
56 @CSRFRequired()
60 @view_config(
57 @view_config(
61 route_name='edit_repo_caches', request_method='POST')
58 route_name='edit_repo_caches', request_method='POST')
62 def repo_caches_purge(self):
59 def repo_caches_purge(self):
63 _ = self.request.translate
60 _ = self.request.translate
64 c = self.load_default_context()
61 c = self.load_default_context()
65 c.active = 'caches'
62 c.active = 'caches'
66
63
67 try:
64 try:
68 ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
65 ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
69 Session().commit()
66 Session().commit()
70 h.flash(_('Cache invalidation successful'),
67 h.flash(_('Cache invalidation successful'),
71 category='success')
68 category='success')
72 except Exception:
69 except Exception:
73 log.exception("Exception during cache invalidation")
70 log.exception("Exception during cache invalidation")
74 h.flash(_('An error occurred during cache invalidation'),
71 h.flash(_('An error occurred during cache invalidation'),
75 category='error')
72 category='error')
76
73
77 raise HTTPFound(h.route_path(
74 raise HTTPFound(h.route_path(
78 'edit_repo_caches', repo_name=self.db_repo_name)) No newline at end of file
75 'edit_repo_caches', repo_name=self.db_repo_name))
@@ -1,302 +1,299 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23
23
24 from pyramid.httpexceptions import HTTPNotFound, HTTPFound
24 from pyramid.httpexceptions import HTTPNotFound, HTTPFound
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26 from pyramid.renderers import render
26 from pyramid.renderers import render
27 from pyramid.response import Response
27 from pyramid.response import Response
28
28
29 from rhodecode.apps._base import RepoAppView
29 from rhodecode.apps._base import RepoAppView
30 import rhodecode.lib.helpers as h
30 import rhodecode.lib.helpers as h
31 from rhodecode.lib.auth import (
31 from rhodecode.lib.auth import (
32 LoginRequired, HasRepoPermissionAnyDecorator)
32 LoginRequired, HasRepoPermissionAnyDecorator)
33
33
34 from rhodecode.lib.ext_json import json
34 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.graphmod import _colored, _dagwalker
35 from rhodecode.lib.graphmod import _colored, _dagwalker
36 from rhodecode.lib.helpers import RepoPage
36 from rhodecode.lib.helpers import RepoPage
37 from rhodecode.lib.utils2 import safe_int, safe_str
37 from rhodecode.lib.utils2 import safe_int, safe_str
38 from rhodecode.lib.vcs.exceptions import (
38 from rhodecode.lib.vcs.exceptions import (
39 RepositoryError, CommitDoesNotExistError,
39 RepositoryError, CommitDoesNotExistError,
40 CommitError, NodeDoesNotExistError, EmptyRepositoryError)
40 CommitError, NodeDoesNotExistError, EmptyRepositoryError)
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44 DEFAULT_CHANGELOG_SIZE = 20
44 DEFAULT_CHANGELOG_SIZE = 20
45
45
46
46
47 class RepoChangelogView(RepoAppView):
47 class RepoChangelogView(RepoAppView):
48
48
49 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
49 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
50 """
50 """
51 This is a safe way to get commit. If an error occurs it redirects to
51 This is a safe way to get commit. If an error occurs it redirects to
52 tip with proper message
52 tip with proper message
53
53
54 :param commit_id: id of commit to fetch
54 :param commit_id: id of commit to fetch
55 :param redirect_after: toggle redirection
55 :param redirect_after: toggle redirection
56 """
56 """
57 _ = self.request.translate
57 _ = self.request.translate
58
58
59 try:
59 try:
60 return self.rhodecode_vcs_repo.get_commit(commit_id)
60 return self.rhodecode_vcs_repo.get_commit(commit_id)
61 except EmptyRepositoryError:
61 except EmptyRepositoryError:
62 if not redirect_after:
62 if not redirect_after:
63 return None
63 return None
64
64
65 h.flash(h.literal(
65 h.flash(h.literal(
66 _('There are no commits yet')), category='warning')
66 _('There are no commits yet')), category='warning')
67 raise HTTPFound(
67 raise HTTPFound(
68 h.route_path('repo_summary', repo_name=self.db_repo_name))
68 h.route_path('repo_summary', repo_name=self.db_repo_name))
69
69
70 except (CommitDoesNotExistError, LookupError):
70 except (CommitDoesNotExistError, LookupError):
71 msg = _('No such commit exists for this repository')
71 msg = _('No such commit exists for this repository')
72 h.flash(msg, category='error')
72 h.flash(msg, category='error')
73 raise HTTPNotFound()
73 raise HTTPNotFound()
74 except RepositoryError as e:
74 except RepositoryError as e:
75 h.flash(safe_str(h.escape(e)), category='error')
75 h.flash(safe_str(h.escape(e)), category='error')
76 raise HTTPNotFound()
76 raise HTTPNotFound()
77
77
78 def _graph(self, repo, commits, prev_data=None, next_data=None):
78 def _graph(self, repo, commits, prev_data=None, next_data=None):
79 """
79 """
80 Generates a DAG graph for repo
80 Generates a DAG graph for repo
81
81
82 :param repo: repo instance
82 :param repo: repo instance
83 :param commits: list of commits
83 :param commits: list of commits
84 """
84 """
85 if not commits:
85 if not commits:
86 return json.dumps([])
86 return json.dumps([])
87
87
88 def serialize(commit, parents=True):
88 def serialize(commit, parents=True):
89 data = dict(
89 data = dict(
90 raw_id=commit.raw_id,
90 raw_id=commit.raw_id,
91 idx=commit.idx,
91 idx=commit.idx,
92 branch=commit.branch,
92 branch=commit.branch,
93 )
93 )
94 if parents:
94 if parents:
95 data['parents'] = [
95 data['parents'] = [
96 serialize(x, parents=False) for x in commit.parents]
96 serialize(x, parents=False) for x in commit.parents]
97 return data
97 return data
98
98
99 prev_data = prev_data or []
99 prev_data = prev_data or []
100 next_data = next_data or []
100 next_data = next_data or []
101
101
102 current = [serialize(x) for x in commits]
102 current = [serialize(x) for x in commits]
103 commits = prev_data + current + next_data
103 commits = prev_data + current + next_data
104
104
105 dag = _dagwalker(repo, commits)
105 dag = _dagwalker(repo, commits)
106
106
107 data = [[commit_id, vtx, edges, branch]
107 data = [[commit_id, vtx, edges, branch]
108 for commit_id, vtx, edges, branch in _colored(dag)]
108 for commit_id, vtx, edges, branch in _colored(dag)]
109 return json.dumps(data), json.dumps(current)
109 return json.dumps(data), json.dumps(current)
110
110
111 def _check_if_valid_branch(self, branch_name, repo_name, f_path):
111 def _check_if_valid_branch(self, branch_name, repo_name, f_path):
112 if branch_name not in self.rhodecode_vcs_repo.branches_all:
112 if branch_name not in self.rhodecode_vcs_repo.branches_all:
113 h.flash('Branch {} is not found.'.format(h.escape(branch_name)),
113 h.flash('Branch {} is not found.'.format(h.escape(branch_name)),
114 category='warning')
114 category='warning')
115 redirect_url = h.route_path(
115 redirect_url = h.route_path(
116 'repo_changelog_file', repo_name=repo_name,
116 'repo_changelog_file', repo_name=repo_name,
117 commit_id=branch_name, f_path=f_path or '')
117 commit_id=branch_name, f_path=f_path or '')
118 raise HTTPFound(redirect_url)
118 raise HTTPFound(redirect_url)
119
119
120 def _load_changelog_data(
120 def _load_changelog_data(
121 self, c, collection, page, chunk_size, branch_name=None,
121 self, c, collection, page, chunk_size, branch_name=None,
122 dynamic=False):
122 dynamic=False):
123
123
124 def url_generator(**kw):
124 def url_generator(**kw):
125 query_params = {}
125 query_params = {}
126 query_params.update(kw)
126 query_params.update(kw)
127 return h.route_path(
127 return h.route_path(
128 'repo_changelog',
128 'repo_changelog',
129 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
129 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
130
130
131 c.total_cs = len(collection)
131 c.total_cs = len(collection)
132 c.showing_commits = min(chunk_size, c.total_cs)
132 c.showing_commits = min(chunk_size, c.total_cs)
133 c.pagination = RepoPage(collection, page=page, item_count=c.total_cs,
133 c.pagination = RepoPage(collection, page=page, item_count=c.total_cs,
134 items_per_page=chunk_size, branch=branch_name,
134 items_per_page=chunk_size, branch=branch_name,
135 url=url_generator)
135 url=url_generator)
136
136
137 c.next_page = c.pagination.next_page
137 c.next_page = c.pagination.next_page
138 c.prev_page = c.pagination.previous_page
138 c.prev_page = c.pagination.previous_page
139
139
140 if dynamic:
140 if dynamic:
141 if self.request.GET.get('chunk') != 'next':
141 if self.request.GET.get('chunk') != 'next':
142 c.next_page = None
142 c.next_page = None
143 if self.request.GET.get('chunk') != 'prev':
143 if self.request.GET.get('chunk') != 'prev':
144 c.prev_page = None
144 c.prev_page = None
145
145
146 page_commit_ids = [x.raw_id for x in c.pagination]
146 page_commit_ids = [x.raw_id for x in c.pagination]
147 c.comments = c.rhodecode_db_repo.get_comments(page_commit_ids)
147 c.comments = c.rhodecode_db_repo.get_comments(page_commit_ids)
148 c.statuses = c.rhodecode_db_repo.statuses(page_commit_ids)
148 c.statuses = c.rhodecode_db_repo.statuses(page_commit_ids)
149
149
150 def load_default_context(self):
150 def load_default_context(self):
151 c = self._get_local_tmpl_context(include_app_defaults=True)
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 c.rhodecode_repo = self.rhodecode_vcs_repo
153 c.rhodecode_repo = self.rhodecode_vcs_repo
156
157 self._register_global_c(c)
154 self._register_global_c(c)
158 return c
155 return c
159
156
160 @LoginRequired()
157 @LoginRequired()
161 @HasRepoPermissionAnyDecorator(
158 @HasRepoPermissionAnyDecorator(
162 'repository.read', 'repository.write', 'repository.admin')
159 'repository.read', 'repository.write', 'repository.admin')
163 @view_config(
160 @view_config(
164 route_name='repo_changelog', request_method='GET',
161 route_name='repo_changelog', request_method='GET',
165 renderer='rhodecode:templates/changelog/changelog.mako')
162 renderer='rhodecode:templates/changelog/changelog.mako')
166 @view_config(
163 @view_config(
167 route_name='repo_changelog_file', request_method='GET',
164 route_name='repo_changelog_file', request_method='GET',
168 renderer='rhodecode:templates/changelog/changelog.mako')
165 renderer='rhodecode:templates/changelog/changelog.mako')
169 def repo_changelog(self):
166 def repo_changelog(self):
170 c = self.load_default_context()
167 c = self.load_default_context()
171
168
172 commit_id = self.request.matchdict.get('commit_id')
169 commit_id = self.request.matchdict.get('commit_id')
173 f_path = self._get_f_path(self.request.matchdict)
170 f_path = self._get_f_path(self.request.matchdict)
174
171
175 chunk_size = 20
172 chunk_size = 20
176
173
177 c.branch_name = branch_name = self.request.GET.get('branch') or ''
174 c.branch_name = branch_name = self.request.GET.get('branch') or ''
178 c.book_name = book_name = self.request.GET.get('bookmark') or ''
175 c.book_name = book_name = self.request.GET.get('bookmark') or ''
179 hist_limit = safe_int(self.request.GET.get('limit')) or None
176 hist_limit = safe_int(self.request.GET.get('limit')) or None
180
177
181 p = safe_int(self.request.GET.get('page', 1), 1)
178 p = safe_int(self.request.GET.get('page', 1), 1)
182
179
183 c.selected_name = branch_name or book_name
180 c.selected_name = branch_name or book_name
184 if not commit_id and branch_name:
181 if not commit_id and branch_name:
185 self._check_if_valid_branch(branch_name, self.db_repo_name, f_path)
182 self._check_if_valid_branch(branch_name, self.db_repo_name, f_path)
186
183
187 c.changelog_for_path = f_path
184 c.changelog_for_path = f_path
188 pre_load = ['author', 'branch', 'date', 'message', 'parents']
185 pre_load = ['author', 'branch', 'date', 'message', 'parents']
189 commit_ids = []
186 commit_ids = []
190
187
191 partial_xhr = self.request.environ.get('HTTP_X_PARTIAL_XHR')
188 partial_xhr = self.request.environ.get('HTTP_X_PARTIAL_XHR')
192
189
193 try:
190 try:
194 if f_path:
191 if f_path:
195 log.debug('generating changelog for path %s', f_path)
192 log.debug('generating changelog for path %s', f_path)
196 # get the history for the file !
193 # get the history for the file !
197 base_commit = self.rhodecode_vcs_repo.get_commit(commit_id)
194 base_commit = self.rhodecode_vcs_repo.get_commit(commit_id)
198 try:
195 try:
199 collection = base_commit.get_file_history(
196 collection = base_commit.get_file_history(
200 f_path, limit=hist_limit, pre_load=pre_load)
197 f_path, limit=hist_limit, pre_load=pre_load)
201 if collection and partial_xhr:
198 if collection and partial_xhr:
202 # for ajax call we remove first one since we're looking
199 # for ajax call we remove first one since we're looking
203 # at it right now in the context of a file commit
200 # at it right now in the context of a file commit
204 collection.pop(0)
201 collection.pop(0)
205 except (NodeDoesNotExistError, CommitError):
202 except (NodeDoesNotExistError, CommitError):
206 # this node is not present at tip!
203 # this node is not present at tip!
207 try:
204 try:
208 commit = self._get_commit_or_redirect(commit_id)
205 commit = self._get_commit_or_redirect(commit_id)
209 collection = commit.get_file_history(f_path)
206 collection = commit.get_file_history(f_path)
210 except RepositoryError as e:
207 except RepositoryError as e:
211 h.flash(safe_str(e), category='warning')
208 h.flash(safe_str(e), category='warning')
212 redirect_url = h.route_path(
209 redirect_url = h.route_path(
213 'repo_changelog', repo_name=self.db_repo_name)
210 'repo_changelog', repo_name=self.db_repo_name)
214 raise HTTPFound(redirect_url)
211 raise HTTPFound(redirect_url)
215 collection = list(reversed(collection))
212 collection = list(reversed(collection))
216 else:
213 else:
217 collection = self.rhodecode_vcs_repo.get_commits(
214 collection = self.rhodecode_vcs_repo.get_commits(
218 branch_name=branch_name, pre_load=pre_load)
215 branch_name=branch_name, pre_load=pre_load)
219
216
220 self._load_changelog_data(
217 self._load_changelog_data(
221 c, collection, p, chunk_size, c.branch_name, dynamic=f_path)
218 c, collection, p, chunk_size, c.branch_name, dynamic=f_path)
222
219
223 except EmptyRepositoryError as e:
220 except EmptyRepositoryError as e:
224 h.flash(safe_str(h.escape(e)), category='warning')
221 h.flash(safe_str(h.escape(e)), category='warning')
225 raise HTTPFound(
222 raise HTTPFound(
226 h.route_path('repo_summary', repo_name=self.db_repo_name))
223 h.route_path('repo_summary', repo_name=self.db_repo_name))
227 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
224 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
228 log.exception(safe_str(e))
225 log.exception(safe_str(e))
229 h.flash(safe_str(h.escape(e)), category='error')
226 h.flash(safe_str(h.escape(e)), category='error')
230 raise HTTPFound(
227 raise HTTPFound(
231 h.route_path('repo_changelog', repo_name=self.db_repo_name))
228 h.route_path('repo_changelog', repo_name=self.db_repo_name))
232
229
233 if partial_xhr or self.request.environ.get('HTTP_X_PJAX'):
230 if partial_xhr or self.request.environ.get('HTTP_X_PJAX'):
234 # loading from ajax, we don't want the first result, it's popped
231 # loading from ajax, we don't want the first result, it's popped
235 # in the code above
232 # in the code above
236 html = render(
233 html = render(
237 'rhodecode:templates/changelog/changelog_file_history.mako',
234 'rhodecode:templates/changelog/changelog_file_history.mako',
238 self._get_template_context(c), self.request)
235 self._get_template_context(c), self.request)
239 return Response(html)
236 return Response(html)
240
237
241 if not f_path:
238 if not f_path:
242 commit_ids = c.pagination
239 commit_ids = c.pagination
243
240
244 c.graph_data, c.graph_commits = self._graph(
241 c.graph_data, c.graph_commits = self._graph(
245 self.rhodecode_vcs_repo, commit_ids)
242 self.rhodecode_vcs_repo, commit_ids)
246
243
247 return self._get_template_context(c)
244 return self._get_template_context(c)
248
245
249 @LoginRequired()
246 @LoginRequired()
250 @HasRepoPermissionAnyDecorator(
247 @HasRepoPermissionAnyDecorator(
251 'repository.read', 'repository.write', 'repository.admin')
248 'repository.read', 'repository.write', 'repository.admin')
252 @view_config(
249 @view_config(
253 route_name='repo_changelog_elements', request_method=('GET', 'POST'),
250 route_name='repo_changelog_elements', request_method=('GET', 'POST'),
254 renderer='rhodecode:templates/changelog/changelog_elements.mako',
251 renderer='rhodecode:templates/changelog/changelog_elements.mako',
255 xhr=True)
252 xhr=True)
256 def repo_changelog_elements(self):
253 def repo_changelog_elements(self):
257 c = self.load_default_context()
254 c = self.load_default_context()
258 chunk_size = 20
255 chunk_size = 20
259
256
260 def wrap_for_error(err):
257 def wrap_for_error(err):
261 html = '<tr>' \
258 html = '<tr>' \
262 '<td colspan="9" class="alert alert-error">ERROR: {}</td>' \
259 '<td colspan="9" class="alert alert-error">ERROR: {}</td>' \
263 '</tr>'.format(err)
260 '</tr>'.format(err)
264 return Response(html)
261 return Response(html)
265
262
266 c.branch_name = branch_name = self.request.GET.get('branch') or ''
263 c.branch_name = branch_name = self.request.GET.get('branch') or ''
267 c.book_name = book_name = self.request.GET.get('bookmark') or ''
264 c.book_name = book_name = self.request.GET.get('bookmark') or ''
268
265
269 c.selected_name = branch_name or book_name
266 c.selected_name = branch_name or book_name
270 if branch_name and branch_name not in self.rhodecode_vcs_repo.branches_all:
267 if branch_name and branch_name not in self.rhodecode_vcs_repo.branches_all:
271 return wrap_for_error(
268 return wrap_for_error(
272 safe_str('Branch: {} is not valid'.format(branch_name)))
269 safe_str('Branch: {} is not valid'.format(branch_name)))
273
270
274 pre_load = ['author', 'branch', 'date', 'message', 'parents']
271 pre_load = ['author', 'branch', 'date', 'message', 'parents']
275 collection = self.rhodecode_vcs_repo.get_commits(
272 collection = self.rhodecode_vcs_repo.get_commits(
276 branch_name=branch_name, pre_load=pre_load)
273 branch_name=branch_name, pre_load=pre_load)
277
274
278 p = safe_int(self.request.GET.get('page', 1), 1)
275 p = safe_int(self.request.GET.get('page', 1), 1)
279 try:
276 try:
280 self._load_changelog_data(
277 self._load_changelog_data(
281 c, collection, p, chunk_size, dynamic=True)
278 c, collection, p, chunk_size, dynamic=True)
282 except EmptyRepositoryError as e:
279 except EmptyRepositoryError as e:
283 return wrap_for_error(safe_str(e))
280 return wrap_for_error(safe_str(e))
284 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
281 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
285 log.exception('Failed to fetch commits')
282 log.exception('Failed to fetch commits')
286 return wrap_for_error(safe_str(e))
283 return wrap_for_error(safe_str(e))
287
284
288 prev_data = None
285 prev_data = None
289 next_data = None
286 next_data = None
290
287
291 prev_graph = json.loads(self.request.POST.get('graph', ''))
288 prev_graph = json.loads(self.request.POST.get('graph', ''))
292
289
293 if self.request.GET.get('chunk') == 'prev':
290 if self.request.GET.get('chunk') == 'prev':
294 next_data = prev_graph
291 next_data = prev_graph
295 elif self.request.GET.get('chunk') == 'next':
292 elif self.request.GET.get('chunk') == 'next':
296 prev_data = prev_graph
293 prev_data = prev_graph
297
294
298 c.graph_data, c.graph_commits = self._graph(
295 c.graph_data, c.graph_commits = self._graph(
299 self.rhodecode_vcs_repo, c.pagination,
296 self.rhodecode_vcs_repo, c.pagination,
300 prev_data=prev_data, next_data=next_data)
297 prev_data=prev_data, next_data=next_data)
301
298
302 return self._get_template_context(c)
299 return self._get_template_context(c)
@@ -1,557 +1,554 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23 import collections
23 import collections
24
24
25 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
25 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
26 from pyramid.view import view_config
26 from pyramid.view import view_config
27 from pyramid.renderers import render
27 from pyramid.renderers import render
28 from pyramid.response import Response
28 from pyramid.response import Response
29
29
30 from rhodecode.apps._base import RepoAppView
30 from rhodecode.apps._base import RepoAppView
31
31
32 from rhodecode.lib import diffs, codeblocks
32 from rhodecode.lib import diffs, codeblocks
33 from rhodecode.lib.auth import (
33 from rhodecode.lib.auth import (
34 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
34 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
35
35
36 from rhodecode.lib.compat import OrderedDict
36 from rhodecode.lib.compat import OrderedDict
37 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
37 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
38 import rhodecode.lib.helpers as h
38 import rhodecode.lib.helpers as h
39 from rhodecode.lib.utils2 import safe_unicode
39 from rhodecode.lib.utils2 import safe_unicode
40 from rhodecode.lib.vcs.backends.base import EmptyCommit
40 from rhodecode.lib.vcs.backends.base import EmptyCommit
41 from rhodecode.lib.vcs.exceptions import (
41 from rhodecode.lib.vcs.exceptions import (
42 RepositoryError, CommitDoesNotExistError, NodeDoesNotExistError)
42 RepositoryError, CommitDoesNotExistError, NodeDoesNotExistError)
43 from rhodecode.model.db import ChangesetComment, ChangesetStatus
43 from rhodecode.model.db import ChangesetComment, ChangesetStatus
44 from rhodecode.model.changeset_status import ChangesetStatusModel
44 from rhodecode.model.changeset_status import ChangesetStatusModel
45 from rhodecode.model.comment import CommentsModel
45 from rhodecode.model.comment import CommentsModel
46 from rhodecode.model.meta import Session
46 from rhodecode.model.meta import Session
47
47
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51
51
52 def _update_with_GET(params, request):
52 def _update_with_GET(params, request):
53 for k in ['diff1', 'diff2', 'diff']:
53 for k in ['diff1', 'diff2', 'diff']:
54 params[k] += request.GET.getall(k)
54 params[k] += request.GET.getall(k)
55
55
56
56
57 def get_ignore_ws(fid, request):
57 def get_ignore_ws(fid, request):
58 ig_ws_global = request.GET.get('ignorews')
58 ig_ws_global = request.GET.get('ignorews')
59 ig_ws = filter(lambda k: k.startswith('WS'), request.GET.getall(fid))
59 ig_ws = filter(lambda k: k.startswith('WS'), request.GET.getall(fid))
60 if ig_ws:
60 if ig_ws:
61 try:
61 try:
62 return int(ig_ws[0].split(':')[-1])
62 return int(ig_ws[0].split(':')[-1])
63 except Exception:
63 except Exception:
64 pass
64 pass
65 return ig_ws_global
65 return ig_ws_global
66
66
67
67
68 def _ignorews_url(request, fileid=None):
68 def _ignorews_url(request, fileid=None):
69 _ = request.translate
69 _ = request.translate
70 fileid = str(fileid) if fileid else None
70 fileid = str(fileid) if fileid else None
71 params = collections.defaultdict(list)
71 params = collections.defaultdict(list)
72 _update_with_GET(params, request)
72 _update_with_GET(params, request)
73 label = _('Show whitespace')
73 label = _('Show whitespace')
74 tooltiplbl = _('Show whitespace for all diffs')
74 tooltiplbl = _('Show whitespace for all diffs')
75 ig_ws = get_ignore_ws(fileid, request)
75 ig_ws = get_ignore_ws(fileid, request)
76 ln_ctx = get_line_ctx(fileid, request)
76 ln_ctx = get_line_ctx(fileid, request)
77
77
78 if ig_ws is None:
78 if ig_ws is None:
79 params['ignorews'] += [1]
79 params['ignorews'] += [1]
80 label = _('Ignore whitespace')
80 label = _('Ignore whitespace')
81 tooltiplbl = _('Ignore whitespace for all diffs')
81 tooltiplbl = _('Ignore whitespace for all diffs')
82 ctx_key = 'context'
82 ctx_key = 'context'
83 ctx_val = ln_ctx
83 ctx_val = ln_ctx
84
84
85 # if we have passed in ln_ctx pass it along to our params
85 # if we have passed in ln_ctx pass it along to our params
86 if ln_ctx:
86 if ln_ctx:
87 params[ctx_key] += [ctx_val]
87 params[ctx_key] += [ctx_val]
88
88
89 if fileid:
89 if fileid:
90 params['anchor'] = 'a_' + fileid
90 params['anchor'] = 'a_' + fileid
91 return h.link_to(label, request.current_route_path(_query=params),
91 return h.link_to(label, request.current_route_path(_query=params),
92 title=tooltiplbl, class_='tooltip')
92 title=tooltiplbl, class_='tooltip')
93
93
94
94
95 def get_line_ctx(fid, request):
95 def get_line_ctx(fid, request):
96 ln_ctx_global = request.GET.get('context')
96 ln_ctx_global = request.GET.get('context')
97 if fid:
97 if fid:
98 ln_ctx = filter(lambda k: k.startswith('C'), request.GET.getall(fid))
98 ln_ctx = filter(lambda k: k.startswith('C'), request.GET.getall(fid))
99 else:
99 else:
100 _ln_ctx = filter(lambda k: k.startswith('C'), request.GET)
100 _ln_ctx = filter(lambda k: k.startswith('C'), request.GET)
101 ln_ctx = request.GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global
101 ln_ctx = request.GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global
102 if ln_ctx:
102 if ln_ctx:
103 ln_ctx = [ln_ctx]
103 ln_ctx = [ln_ctx]
104
104
105 if ln_ctx:
105 if ln_ctx:
106 retval = ln_ctx[0].split(':')[-1]
106 retval = ln_ctx[0].split(':')[-1]
107 else:
107 else:
108 retval = ln_ctx_global
108 retval = ln_ctx_global
109
109
110 try:
110 try:
111 return int(retval)
111 return int(retval)
112 except Exception:
112 except Exception:
113 return 3
113 return 3
114
114
115
115
116 def _context_url(request, fileid=None):
116 def _context_url(request, fileid=None):
117 """
117 """
118 Generates a url for context lines.
118 Generates a url for context lines.
119
119
120 :param fileid:
120 :param fileid:
121 """
121 """
122
122
123 _ = request.translate
123 _ = request.translate
124 fileid = str(fileid) if fileid else None
124 fileid = str(fileid) if fileid else None
125 ig_ws = get_ignore_ws(fileid, request)
125 ig_ws = get_ignore_ws(fileid, request)
126 ln_ctx = (get_line_ctx(fileid, request) or 3) * 2
126 ln_ctx = (get_line_ctx(fileid, request) or 3) * 2
127
127
128 params = collections.defaultdict(list)
128 params = collections.defaultdict(list)
129 _update_with_GET(params, request)
129 _update_with_GET(params, request)
130
130
131 if ln_ctx > 0:
131 if ln_ctx > 0:
132 params['context'] += [ln_ctx]
132 params['context'] += [ln_ctx]
133
133
134 if ig_ws:
134 if ig_ws:
135 ig_ws_key = 'ignorews'
135 ig_ws_key = 'ignorews'
136 ig_ws_val = 1
136 ig_ws_val = 1
137 params[ig_ws_key] += [ig_ws_val]
137 params[ig_ws_key] += [ig_ws_val]
138
138
139 lbl = _('Increase context')
139 lbl = _('Increase context')
140 tooltiplbl = _('Increase context for all diffs')
140 tooltiplbl = _('Increase context for all diffs')
141
141
142 if fileid:
142 if fileid:
143 params['anchor'] = 'a_' + fileid
143 params['anchor'] = 'a_' + fileid
144 return h.link_to(lbl, request.current_route_path(_query=params),
144 return h.link_to(lbl, request.current_route_path(_query=params),
145 title=tooltiplbl, class_='tooltip')
145 title=tooltiplbl, class_='tooltip')
146
146
147
147
148 class RepoCommitsView(RepoAppView):
148 class RepoCommitsView(RepoAppView):
149 def load_default_context(self):
149 def load_default_context(self):
150 c = self._get_local_tmpl_context(include_app_defaults=True)
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 c.rhodecode_repo = self.rhodecode_vcs_repo
151 c.rhodecode_repo = self.rhodecode_vcs_repo
155
152
156 self._register_global_c(c)
153 self._register_global_c(c)
157 return c
154 return c
158
155
159 def _commit(self, commit_id_range, method):
156 def _commit(self, commit_id_range, method):
160 _ = self.request.translate
157 _ = self.request.translate
161 c = self.load_default_context()
158 c = self.load_default_context()
162 c.ignorews_url = _ignorews_url
159 c.ignorews_url = _ignorews_url
163 c.context_url = _context_url
160 c.context_url = _context_url
164 c.fulldiff = self.request.GET.get('fulldiff')
161 c.fulldiff = self.request.GET.get('fulldiff')
165
162
166 # fetch global flags of ignore ws or context lines
163 # fetch global flags of ignore ws or context lines
167 context_lcl = get_line_ctx('', self.request)
164 context_lcl = get_line_ctx('', self.request)
168 ign_whitespace_lcl = get_ignore_ws('', self.request)
165 ign_whitespace_lcl = get_ignore_ws('', self.request)
169
166
170 # diff_limit will cut off the whole diff if the limit is applied
167 # diff_limit will cut off the whole diff if the limit is applied
171 # otherwise it will just hide the big files from the front-end
168 # otherwise it will just hide the big files from the front-end
172 diff_limit = c.visual.cut_off_limit_diff
169 diff_limit = c.visual.cut_off_limit_diff
173 file_limit = c.visual.cut_off_limit_file
170 file_limit = c.visual.cut_off_limit_file
174
171
175 # get ranges of commit ids if preset
172 # get ranges of commit ids if preset
176 commit_range = commit_id_range.split('...')[:2]
173 commit_range = commit_id_range.split('...')[:2]
177
174
178 try:
175 try:
179 pre_load = ['affected_files', 'author', 'branch', 'date',
176 pre_load = ['affected_files', 'author', 'branch', 'date',
180 'message', 'parents']
177 'message', 'parents']
181
178
182 if len(commit_range) == 2:
179 if len(commit_range) == 2:
183 commits = self.rhodecode_vcs_repo.get_commits(
180 commits = self.rhodecode_vcs_repo.get_commits(
184 start_id=commit_range[0], end_id=commit_range[1],
181 start_id=commit_range[0], end_id=commit_range[1],
185 pre_load=pre_load)
182 pre_load=pre_load)
186 commits = list(commits)
183 commits = list(commits)
187 else:
184 else:
188 commits = [self.rhodecode_vcs_repo.get_commit(
185 commits = [self.rhodecode_vcs_repo.get_commit(
189 commit_id=commit_id_range, pre_load=pre_load)]
186 commit_id=commit_id_range, pre_load=pre_load)]
190
187
191 c.commit_ranges = commits
188 c.commit_ranges = commits
192 if not c.commit_ranges:
189 if not c.commit_ranges:
193 raise RepositoryError(
190 raise RepositoryError(
194 'The commit range returned an empty result')
191 'The commit range returned an empty result')
195 except CommitDoesNotExistError:
192 except CommitDoesNotExistError:
196 msg = _('No such commit exists for this repository')
193 msg = _('No such commit exists for this repository')
197 h.flash(msg, category='error')
194 h.flash(msg, category='error')
198 raise HTTPNotFound()
195 raise HTTPNotFound()
199 except Exception:
196 except Exception:
200 log.exception("General failure")
197 log.exception("General failure")
201 raise HTTPNotFound()
198 raise HTTPNotFound()
202
199
203 c.changes = OrderedDict()
200 c.changes = OrderedDict()
204 c.lines_added = 0
201 c.lines_added = 0
205 c.lines_deleted = 0
202 c.lines_deleted = 0
206
203
207 # auto collapse if we have more than limit
204 # auto collapse if we have more than limit
208 collapse_limit = diffs.DiffProcessor._collapse_commits_over
205 collapse_limit = diffs.DiffProcessor._collapse_commits_over
209 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
206 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
210
207
211 c.commit_statuses = ChangesetStatus.STATUSES
208 c.commit_statuses = ChangesetStatus.STATUSES
212 c.inline_comments = []
209 c.inline_comments = []
213 c.files = []
210 c.files = []
214
211
215 c.statuses = []
212 c.statuses = []
216 c.comments = []
213 c.comments = []
217 c.unresolved_comments = []
214 c.unresolved_comments = []
218 if len(c.commit_ranges) == 1:
215 if len(c.commit_ranges) == 1:
219 commit = c.commit_ranges[0]
216 commit = c.commit_ranges[0]
220 c.comments = CommentsModel().get_comments(
217 c.comments = CommentsModel().get_comments(
221 self.db_repo.repo_id,
218 self.db_repo.repo_id,
222 revision=commit.raw_id)
219 revision=commit.raw_id)
223 c.statuses.append(ChangesetStatusModel().get_status(
220 c.statuses.append(ChangesetStatusModel().get_status(
224 self.db_repo.repo_id, commit.raw_id))
221 self.db_repo.repo_id, commit.raw_id))
225 # comments from PR
222 # comments from PR
226 statuses = ChangesetStatusModel().get_statuses(
223 statuses = ChangesetStatusModel().get_statuses(
227 self.db_repo.repo_id, commit.raw_id,
224 self.db_repo.repo_id, commit.raw_id,
228 with_revisions=True)
225 with_revisions=True)
229 prs = set(st.pull_request for st in statuses
226 prs = set(st.pull_request for st in statuses
230 if st.pull_request is not None)
227 if st.pull_request is not None)
231 # from associated statuses, check the pull requests, and
228 # from associated statuses, check the pull requests, and
232 # show comments from them
229 # show comments from them
233 for pr in prs:
230 for pr in prs:
234 c.comments.extend(pr.comments)
231 c.comments.extend(pr.comments)
235
232
236 c.unresolved_comments = CommentsModel()\
233 c.unresolved_comments = CommentsModel()\
237 .get_commit_unresolved_todos(commit.raw_id)
234 .get_commit_unresolved_todos(commit.raw_id)
238
235
239 diff = None
236 diff = None
240 # Iterate over ranges (default commit view is always one commit)
237 # Iterate over ranges (default commit view is always one commit)
241 for commit in c.commit_ranges:
238 for commit in c.commit_ranges:
242 c.changes[commit.raw_id] = []
239 c.changes[commit.raw_id] = []
243
240
244 commit2 = commit
241 commit2 = commit
245 commit1 = commit.parents[0] if commit.parents else EmptyCommit()
242 commit1 = commit.parents[0] if commit.parents else EmptyCommit()
246
243
247 _diff = self.rhodecode_vcs_repo.get_diff(
244 _diff = self.rhodecode_vcs_repo.get_diff(
248 commit1, commit2,
245 commit1, commit2,
249 ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
246 ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
250 diff_processor = diffs.DiffProcessor(
247 diff_processor = diffs.DiffProcessor(
251 _diff, format='newdiff', diff_limit=diff_limit,
248 _diff, format='newdiff', diff_limit=diff_limit,
252 file_limit=file_limit, show_full_diff=c.fulldiff)
249 file_limit=file_limit, show_full_diff=c.fulldiff)
253
250
254 commit_changes = OrderedDict()
251 commit_changes = OrderedDict()
255 if method == 'show':
252 if method == 'show':
256 _parsed = diff_processor.prepare()
253 _parsed = diff_processor.prepare()
257 c.limited_diff = isinstance(_parsed, diffs.LimitedDiffContainer)
254 c.limited_diff = isinstance(_parsed, diffs.LimitedDiffContainer)
258
255
259 _parsed = diff_processor.prepare()
256 _parsed = diff_processor.prepare()
260
257
261 def _node_getter(commit):
258 def _node_getter(commit):
262 def get_node(fname):
259 def get_node(fname):
263 try:
260 try:
264 return commit.get_node(fname)
261 return commit.get_node(fname)
265 except NodeDoesNotExistError:
262 except NodeDoesNotExistError:
266 return None
263 return None
267 return get_node
264 return get_node
268
265
269 inline_comments = CommentsModel().get_inline_comments(
266 inline_comments = CommentsModel().get_inline_comments(
270 self.db_repo.repo_id, revision=commit.raw_id)
267 self.db_repo.repo_id, revision=commit.raw_id)
271 c.inline_cnt = CommentsModel().get_inline_comments_count(
268 c.inline_cnt = CommentsModel().get_inline_comments_count(
272 inline_comments)
269 inline_comments)
273
270
274 diffset = codeblocks.DiffSet(
271 diffset = codeblocks.DiffSet(
275 repo_name=self.db_repo_name,
272 repo_name=self.db_repo_name,
276 source_node_getter=_node_getter(commit1),
273 source_node_getter=_node_getter(commit1),
277 target_node_getter=_node_getter(commit2),
274 target_node_getter=_node_getter(commit2),
278 comments=inline_comments)
275 comments=inline_comments)
279 diffset = diffset.render_patchset(
276 diffset = diffset.render_patchset(
280 _parsed, commit1.raw_id, commit2.raw_id)
277 _parsed, commit1.raw_id, commit2.raw_id)
281
278
282 c.changes[commit.raw_id] = diffset
279 c.changes[commit.raw_id] = diffset
283 else:
280 else:
284 # downloads/raw we only need RAW diff nothing else
281 # downloads/raw we only need RAW diff nothing else
285 diff = diff_processor.as_raw()
282 diff = diff_processor.as_raw()
286 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
283 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
287
284
288 # sort comments by how they were generated
285 # sort comments by how they were generated
289 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
286 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
290
287
291 if len(c.commit_ranges) == 1:
288 if len(c.commit_ranges) == 1:
292 c.commit = c.commit_ranges[0]
289 c.commit = c.commit_ranges[0]
293 c.parent_tmpl = ''.join(
290 c.parent_tmpl = ''.join(
294 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
291 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
295
292
296 if method == 'download':
293 if method == 'download':
297 response = Response(diff)
294 response = Response(diff)
298 response.content_type = 'text/plain'
295 response.content_type = 'text/plain'
299 response.content_disposition = (
296 response.content_disposition = (
300 'attachment; filename=%s.diff' % commit_id_range[:12])
297 'attachment; filename=%s.diff' % commit_id_range[:12])
301 return response
298 return response
302 elif method == 'patch':
299 elif method == 'patch':
303 c.diff = safe_unicode(diff)
300 c.diff = safe_unicode(diff)
304 patch = render(
301 patch = render(
305 'rhodecode:templates/changeset/patch_changeset.mako',
302 'rhodecode:templates/changeset/patch_changeset.mako',
306 self._get_template_context(c), self.request)
303 self._get_template_context(c), self.request)
307 response = Response(patch)
304 response = Response(patch)
308 response.content_type = 'text/plain'
305 response.content_type = 'text/plain'
309 return response
306 return response
310 elif method == 'raw':
307 elif method == 'raw':
311 response = Response(diff)
308 response = Response(diff)
312 response.content_type = 'text/plain'
309 response.content_type = 'text/plain'
313 return response
310 return response
314 elif method == 'show':
311 elif method == 'show':
315 if len(c.commit_ranges) == 1:
312 if len(c.commit_ranges) == 1:
316 html = render(
313 html = render(
317 'rhodecode:templates/changeset/changeset.mako',
314 'rhodecode:templates/changeset/changeset.mako',
318 self._get_template_context(c), self.request)
315 self._get_template_context(c), self.request)
319 return Response(html)
316 return Response(html)
320 else:
317 else:
321 c.ancestor = None
318 c.ancestor = None
322 c.target_repo = self.db_repo
319 c.target_repo = self.db_repo
323 html = render(
320 html = render(
324 'rhodecode:templates/changeset/changeset_range.mako',
321 'rhodecode:templates/changeset/changeset_range.mako',
325 self._get_template_context(c), self.request)
322 self._get_template_context(c), self.request)
326 return Response(html)
323 return Response(html)
327
324
328 raise HTTPBadRequest()
325 raise HTTPBadRequest()
329
326
330 @LoginRequired()
327 @LoginRequired()
331 @HasRepoPermissionAnyDecorator(
328 @HasRepoPermissionAnyDecorator(
332 'repository.read', 'repository.write', 'repository.admin')
329 'repository.read', 'repository.write', 'repository.admin')
333 @view_config(
330 @view_config(
334 route_name='repo_commit', request_method='GET',
331 route_name='repo_commit', request_method='GET',
335 renderer=None)
332 renderer=None)
336 def repo_commit_show(self):
333 def repo_commit_show(self):
337 commit_id = self.request.matchdict['commit_id']
334 commit_id = self.request.matchdict['commit_id']
338 return self._commit(commit_id, method='show')
335 return self._commit(commit_id, method='show')
339
336
340 @LoginRequired()
337 @LoginRequired()
341 @HasRepoPermissionAnyDecorator(
338 @HasRepoPermissionAnyDecorator(
342 'repository.read', 'repository.write', 'repository.admin')
339 'repository.read', 'repository.write', 'repository.admin')
343 @view_config(
340 @view_config(
344 route_name='repo_commit_raw', request_method='GET',
341 route_name='repo_commit_raw', request_method='GET',
345 renderer=None)
342 renderer=None)
346 @view_config(
343 @view_config(
347 route_name='repo_commit_raw_deprecated', request_method='GET',
344 route_name='repo_commit_raw_deprecated', request_method='GET',
348 renderer=None)
345 renderer=None)
349 def repo_commit_raw(self):
346 def repo_commit_raw(self):
350 commit_id = self.request.matchdict['commit_id']
347 commit_id = self.request.matchdict['commit_id']
351 return self._commit(commit_id, method='raw')
348 return self._commit(commit_id, method='raw')
352
349
353 @LoginRequired()
350 @LoginRequired()
354 @HasRepoPermissionAnyDecorator(
351 @HasRepoPermissionAnyDecorator(
355 'repository.read', 'repository.write', 'repository.admin')
352 'repository.read', 'repository.write', 'repository.admin')
356 @view_config(
353 @view_config(
357 route_name='repo_commit_patch', request_method='GET',
354 route_name='repo_commit_patch', request_method='GET',
358 renderer=None)
355 renderer=None)
359 def repo_commit_patch(self):
356 def repo_commit_patch(self):
360 commit_id = self.request.matchdict['commit_id']
357 commit_id = self.request.matchdict['commit_id']
361 return self._commit(commit_id, method='patch')
358 return self._commit(commit_id, method='patch')
362
359
363 @LoginRequired()
360 @LoginRequired()
364 @HasRepoPermissionAnyDecorator(
361 @HasRepoPermissionAnyDecorator(
365 'repository.read', 'repository.write', 'repository.admin')
362 'repository.read', 'repository.write', 'repository.admin')
366 @view_config(
363 @view_config(
367 route_name='repo_commit_download', request_method='GET',
364 route_name='repo_commit_download', request_method='GET',
368 renderer=None)
365 renderer=None)
369 def repo_commit_download(self):
366 def repo_commit_download(self):
370 commit_id = self.request.matchdict['commit_id']
367 commit_id = self.request.matchdict['commit_id']
371 return self._commit(commit_id, method='download')
368 return self._commit(commit_id, method='download')
372
369
373 @LoginRequired()
370 @LoginRequired()
374 @NotAnonymous()
371 @NotAnonymous()
375 @HasRepoPermissionAnyDecorator(
372 @HasRepoPermissionAnyDecorator(
376 'repository.read', 'repository.write', 'repository.admin')
373 'repository.read', 'repository.write', 'repository.admin')
377 @CSRFRequired()
374 @CSRFRequired()
378 @view_config(
375 @view_config(
379 route_name='repo_commit_comment_create', request_method='POST',
376 route_name='repo_commit_comment_create', request_method='POST',
380 renderer='json_ext')
377 renderer='json_ext')
381 def repo_commit_comment_create(self):
378 def repo_commit_comment_create(self):
382 _ = self.request.translate
379 _ = self.request.translate
383 commit_id = self.request.matchdict['commit_id']
380 commit_id = self.request.matchdict['commit_id']
384
381
385 c = self.load_default_context()
382 c = self.load_default_context()
386 status = self.request.POST.get('changeset_status', None)
383 status = self.request.POST.get('changeset_status', None)
387 text = self.request.POST.get('text')
384 text = self.request.POST.get('text')
388 comment_type = self.request.POST.get('comment_type')
385 comment_type = self.request.POST.get('comment_type')
389 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
386 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
390
387
391 if status:
388 if status:
392 text = text or (_('Status change %(transition_icon)s %(status)s')
389 text = text or (_('Status change %(transition_icon)s %(status)s')
393 % {'transition_icon': '>',
390 % {'transition_icon': '>',
394 'status': ChangesetStatus.get_status_lbl(status)})
391 'status': ChangesetStatus.get_status_lbl(status)})
395
392
396 multi_commit_ids = []
393 multi_commit_ids = []
397 for _commit_id in self.request.POST.get('commit_ids', '').split(','):
394 for _commit_id in self.request.POST.get('commit_ids', '').split(','):
398 if _commit_id not in ['', None, EmptyCommit.raw_id]:
395 if _commit_id not in ['', None, EmptyCommit.raw_id]:
399 if _commit_id not in multi_commit_ids:
396 if _commit_id not in multi_commit_ids:
400 multi_commit_ids.append(_commit_id)
397 multi_commit_ids.append(_commit_id)
401
398
402 commit_ids = multi_commit_ids or [commit_id]
399 commit_ids = multi_commit_ids or [commit_id]
403
400
404 comment = None
401 comment = None
405 for current_id in filter(None, commit_ids):
402 for current_id in filter(None, commit_ids):
406 comment = CommentsModel().create(
403 comment = CommentsModel().create(
407 text=text,
404 text=text,
408 repo=self.db_repo.repo_id,
405 repo=self.db_repo.repo_id,
409 user=self._rhodecode_db_user.user_id,
406 user=self._rhodecode_db_user.user_id,
410 commit_id=current_id,
407 commit_id=current_id,
411 f_path=self.request.POST.get('f_path'),
408 f_path=self.request.POST.get('f_path'),
412 line_no=self.request.POST.get('line'),
409 line_no=self.request.POST.get('line'),
413 status_change=(ChangesetStatus.get_status_lbl(status)
410 status_change=(ChangesetStatus.get_status_lbl(status)
414 if status else None),
411 if status else None),
415 status_change_type=status,
412 status_change_type=status,
416 comment_type=comment_type,
413 comment_type=comment_type,
417 resolves_comment_id=resolves_comment_id
414 resolves_comment_id=resolves_comment_id
418 )
415 )
419
416
420 # get status if set !
417 # get status if set !
421 if status:
418 if status:
422 # if latest status was from pull request and it's closed
419 # if latest status was from pull request and it's closed
423 # disallow changing status !
420 # disallow changing status !
424 # dont_allow_on_closed_pull_request = True !
421 # dont_allow_on_closed_pull_request = True !
425
422
426 try:
423 try:
427 ChangesetStatusModel().set_status(
424 ChangesetStatusModel().set_status(
428 self.db_repo.repo_id,
425 self.db_repo.repo_id,
429 status,
426 status,
430 self._rhodecode_db_user.user_id,
427 self._rhodecode_db_user.user_id,
431 comment,
428 comment,
432 revision=current_id,
429 revision=current_id,
433 dont_allow_on_closed_pull_request=True
430 dont_allow_on_closed_pull_request=True
434 )
431 )
435 except StatusChangeOnClosedPullRequestError:
432 except StatusChangeOnClosedPullRequestError:
436 msg = _('Changing the status of a commit associated with '
433 msg = _('Changing the status of a commit associated with '
437 'a closed pull request is not allowed')
434 'a closed pull request is not allowed')
438 log.exception(msg)
435 log.exception(msg)
439 h.flash(msg, category='warning')
436 h.flash(msg, category='warning')
440 raise HTTPFound(h.route_path(
437 raise HTTPFound(h.route_path(
441 'repo_commit', repo_name=self.db_repo_name,
438 'repo_commit', repo_name=self.db_repo_name,
442 commit_id=current_id))
439 commit_id=current_id))
443
440
444 # finalize, commit and redirect
441 # finalize, commit and redirect
445 Session().commit()
442 Session().commit()
446
443
447 data = {
444 data = {
448 'target_id': h.safeid(h.safe_unicode(
445 'target_id': h.safeid(h.safe_unicode(
449 self.request.POST.get('f_path'))),
446 self.request.POST.get('f_path'))),
450 }
447 }
451 if comment:
448 if comment:
452 c.co = comment
449 c.co = comment
453 rendered_comment = render(
450 rendered_comment = render(
454 'rhodecode:templates/changeset/changeset_comment_block.mako',
451 'rhodecode:templates/changeset/changeset_comment_block.mako',
455 self._get_template_context(c), self.request)
452 self._get_template_context(c), self.request)
456
453
457 data.update(comment.get_dict())
454 data.update(comment.get_dict())
458 data.update({'rendered_text': rendered_comment})
455 data.update({'rendered_text': rendered_comment})
459
456
460 return data
457 return data
461
458
462 @LoginRequired()
459 @LoginRequired()
463 @NotAnonymous()
460 @NotAnonymous()
464 @HasRepoPermissionAnyDecorator(
461 @HasRepoPermissionAnyDecorator(
465 'repository.read', 'repository.write', 'repository.admin')
462 'repository.read', 'repository.write', 'repository.admin')
466 @CSRFRequired()
463 @CSRFRequired()
467 @view_config(
464 @view_config(
468 route_name='repo_commit_comment_preview', request_method='POST',
465 route_name='repo_commit_comment_preview', request_method='POST',
469 renderer='string', xhr=True)
466 renderer='string', xhr=True)
470 def repo_commit_comment_preview(self):
467 def repo_commit_comment_preview(self):
471 # Technically a CSRF token is not needed as no state changes with this
468 # Technically a CSRF token is not needed as no state changes with this
472 # call. However, as this is a POST is better to have it, so automated
469 # call. However, as this is a POST is better to have it, so automated
473 # tools don't flag it as potential CSRF.
470 # tools don't flag it as potential CSRF.
474 # Post is required because the payload could be bigger than the maximum
471 # Post is required because the payload could be bigger than the maximum
475 # allowed by GET.
472 # allowed by GET.
476
473
477 text = self.request.POST.get('text')
474 text = self.request.POST.get('text')
478 renderer = self.request.POST.get('renderer') or 'rst'
475 renderer = self.request.POST.get('renderer') or 'rst'
479 if text:
476 if text:
480 return h.render(text, renderer=renderer, mentions=True)
477 return h.render(text, renderer=renderer, mentions=True)
481 return ''
478 return ''
482
479
483 @LoginRequired()
480 @LoginRequired()
484 @NotAnonymous()
481 @NotAnonymous()
485 @HasRepoPermissionAnyDecorator(
482 @HasRepoPermissionAnyDecorator(
486 'repository.read', 'repository.write', 'repository.admin')
483 'repository.read', 'repository.write', 'repository.admin')
487 @CSRFRequired()
484 @CSRFRequired()
488 @view_config(
485 @view_config(
489 route_name='repo_commit_comment_delete', request_method='POST',
486 route_name='repo_commit_comment_delete', request_method='POST',
490 renderer='json_ext')
487 renderer='json_ext')
491 def repo_commit_comment_delete(self):
488 def repo_commit_comment_delete(self):
492 commit_id = self.request.matchdict['commit_id']
489 commit_id = self.request.matchdict['commit_id']
493 comment_id = self.request.matchdict['comment_id']
490 comment_id = self.request.matchdict['comment_id']
494
491
495 comment = ChangesetComment.get_or_404(comment_id)
492 comment = ChangesetComment.get_or_404(comment_id)
496 if not comment:
493 if not comment:
497 log.debug('Comment with id:%s not found, skipping', comment_id)
494 log.debug('Comment with id:%s not found, skipping', comment_id)
498 # comment already deleted in another call probably
495 # comment already deleted in another call probably
499 return True
496 return True
500
497
501 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
498 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
502 super_admin = h.HasPermissionAny('hg.admin')()
499 super_admin = h.HasPermissionAny('hg.admin')()
503 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
500 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
504 is_repo_comment = comment.repo.repo_name == self.db_repo_name
501 is_repo_comment = comment.repo.repo_name == self.db_repo_name
505 comment_repo_admin = is_repo_admin and is_repo_comment
502 comment_repo_admin = is_repo_admin and is_repo_comment
506
503
507 if super_admin or comment_owner or comment_repo_admin:
504 if super_admin or comment_owner or comment_repo_admin:
508 CommentsModel().delete(comment=comment, user=self._rhodecode_db_user)
505 CommentsModel().delete(comment=comment, user=self._rhodecode_db_user)
509 Session().commit()
506 Session().commit()
510 return True
507 return True
511 else:
508 else:
512 log.warning('No permissions for user %s to delete comment_id: %s',
509 log.warning('No permissions for user %s to delete comment_id: %s',
513 self._rhodecode_db_user, comment_id)
510 self._rhodecode_db_user, comment_id)
514 raise HTTPNotFound()
511 raise HTTPNotFound()
515
512
516 @LoginRequired()
513 @LoginRequired()
517 @HasRepoPermissionAnyDecorator(
514 @HasRepoPermissionAnyDecorator(
518 'repository.read', 'repository.write', 'repository.admin')
515 'repository.read', 'repository.write', 'repository.admin')
519 @view_config(
516 @view_config(
520 route_name='repo_commit_data', request_method='GET',
517 route_name='repo_commit_data', request_method='GET',
521 renderer='json_ext', xhr=True)
518 renderer='json_ext', xhr=True)
522 def repo_commit_data(self):
519 def repo_commit_data(self):
523 commit_id = self.request.matchdict['commit_id']
520 commit_id = self.request.matchdict['commit_id']
524 self.load_default_context()
521 self.load_default_context()
525
522
526 try:
523 try:
527 return self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
524 return self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
528 except CommitDoesNotExistError as e:
525 except CommitDoesNotExistError as e:
529 return EmptyCommit(message=str(e))
526 return EmptyCommit(message=str(e))
530
527
531 @LoginRequired()
528 @LoginRequired()
532 @HasRepoPermissionAnyDecorator(
529 @HasRepoPermissionAnyDecorator(
533 'repository.read', 'repository.write', 'repository.admin')
530 'repository.read', 'repository.write', 'repository.admin')
534 @view_config(
531 @view_config(
535 route_name='repo_commit_children', request_method='GET',
532 route_name='repo_commit_children', request_method='GET',
536 renderer='json_ext', xhr=True)
533 renderer='json_ext', xhr=True)
537 def repo_commit_children(self):
534 def repo_commit_children(self):
538 commit_id = self.request.matchdict['commit_id']
535 commit_id = self.request.matchdict['commit_id']
539 self.load_default_context()
536 self.load_default_context()
540
537
541 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
538 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
542 result = {"results": commit.children}
539 result = {"results": commit.children}
543 return result
540 return result
544
541
545 @LoginRequired()
542 @LoginRequired()
546 @HasRepoPermissionAnyDecorator(
543 @HasRepoPermissionAnyDecorator(
547 'repository.read', 'repository.write', 'repository.admin')
544 'repository.read', 'repository.write', 'repository.admin')
548 @view_config(
545 @view_config(
549 route_name='repo_commit_parents', request_method='GET',
546 route_name='repo_commit_parents', request_method='GET',
550 renderer='json_ext')
547 renderer='json_ext')
551 def repo_commit_parents(self):
548 def repo_commit_parents(self):
552 commit_id = self.request.matchdict['commit_id']
549 commit_id = self.request.matchdict['commit_id']
553 self.load_default_context()
550 self.load_default_context()
554
551
555 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
552 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
556 result = {"results": commit.parents}
553 result = {"results": commit.parents}
557 return result
554 return result
@@ -1,324 +1,322 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2017 RhodeCode GmbH
3 # Copyright (C) 2012-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23
23
24 from pyramid.httpexceptions import HTTPBadRequest, HTTPNotFound, HTTPFound
24 from pyramid.httpexceptions import HTTPBadRequest, HTTPNotFound, HTTPFound
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26 from pyramid.renderers import render
26 from pyramid.renderers import render
27 from pyramid.response import Response
27 from pyramid.response import Response
28
28
29 from rhodecode.apps._base import RepoAppView
29 from rhodecode.apps._base import RepoAppView
30 from rhodecode.controllers.utils import parse_path_ref, get_commit_from_ref_name
30 from rhodecode.controllers.utils import parse_path_ref, get_commit_from_ref_name
31 from rhodecode.lib import helpers as h
31 from rhodecode.lib import helpers as h
32 from rhodecode.lib import diffs, codeblocks
32 from rhodecode.lib import diffs, codeblocks
33 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
33 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
34 from rhodecode.lib.utils import safe_str
34 from rhodecode.lib.utils import safe_str
35 from rhodecode.lib.utils2 import safe_unicode, str2bool
35 from rhodecode.lib.utils2 import safe_unicode, str2bool
36 from rhodecode.lib.vcs.exceptions import (
36 from rhodecode.lib.vcs.exceptions import (
37 EmptyRepositoryError, RepositoryError, RepositoryRequirementError,
37 EmptyRepositoryError, RepositoryError, RepositoryRequirementError,
38 NodeDoesNotExistError)
38 NodeDoesNotExistError)
39 from rhodecode.model.db import Repository, ChangesetStatus
39 from rhodecode.model.db import Repository, ChangesetStatus
40
40
41 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
42
42
43
43
44 class RepoCompareView(RepoAppView):
44 class RepoCompareView(RepoAppView):
45 def load_default_context(self):
45 def load_default_context(self):
46 c = self._get_local_tmpl_context(include_app_defaults=True)
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 c.rhodecode_repo = self.rhodecode_vcs_repo
48 c.rhodecode_repo = self.rhodecode_vcs_repo
51
49
52 self._register_global_c(c)
50 self._register_global_c(c)
53 return c
51 return c
54
52
55 def _get_commit_or_redirect(
53 def _get_commit_or_redirect(
56 self, ref, ref_type, repo, redirect_after=True, partial=False):
54 self, ref, ref_type, repo, redirect_after=True, partial=False):
57 """
55 """
58 This is a safe way to get a commit. If an error occurs it
56 This is a safe way to get a commit. If an error occurs it
59 redirects to a commit with a proper message. If partial is set
57 redirects to a commit with a proper message. If partial is set
60 then it does not do redirect raise and throws an exception instead.
58 then it does not do redirect raise and throws an exception instead.
61 """
59 """
62 _ = self.request.translate
60 _ = self.request.translate
63 try:
61 try:
64 return get_commit_from_ref_name(repo, safe_str(ref), ref_type)
62 return get_commit_from_ref_name(repo, safe_str(ref), ref_type)
65 except EmptyRepositoryError:
63 except EmptyRepositoryError:
66 if not redirect_after:
64 if not redirect_after:
67 return repo.scm_instance().EMPTY_COMMIT
65 return repo.scm_instance().EMPTY_COMMIT
68 h.flash(h.literal(_('There are no commits yet')),
66 h.flash(h.literal(_('There are no commits yet')),
69 category='warning')
67 category='warning')
70 if not partial:
68 if not partial:
71 raise HTTPFound(
69 raise HTTPFound(
72 h.route_path('repo_summary', repo_name=repo.repo_name))
70 h.route_path('repo_summary', repo_name=repo.repo_name))
73 raise HTTPBadRequest()
71 raise HTTPBadRequest()
74
72
75 except RepositoryError as e:
73 except RepositoryError as e:
76 log.exception(safe_str(e))
74 log.exception(safe_str(e))
77 h.flash(safe_str(h.escape(e)), category='warning')
75 h.flash(safe_str(h.escape(e)), category='warning')
78 if not partial:
76 if not partial:
79 raise HTTPFound(
77 raise HTTPFound(
80 h.route_path('repo_summary', repo_name=repo.repo_name))
78 h.route_path('repo_summary', repo_name=repo.repo_name))
81 raise HTTPBadRequest()
79 raise HTTPBadRequest()
82
80
83 @LoginRequired()
81 @LoginRequired()
84 @HasRepoPermissionAnyDecorator(
82 @HasRepoPermissionAnyDecorator(
85 'repository.read', 'repository.write', 'repository.admin')
83 'repository.read', 'repository.write', 'repository.admin')
86 @view_config(
84 @view_config(
87 route_name='repo_compare_select', request_method='GET',
85 route_name='repo_compare_select', request_method='GET',
88 renderer='rhodecode:templates/compare/compare_diff.mako')
86 renderer='rhodecode:templates/compare/compare_diff.mako')
89 def compare_select(self):
87 def compare_select(self):
90 _ = self.request.translate
88 _ = self.request.translate
91 c = self.load_default_context()
89 c = self.load_default_context()
92
90
93 source_repo = self.db_repo_name
91 source_repo = self.db_repo_name
94 target_repo = self.request.GET.get('target_repo', source_repo)
92 target_repo = self.request.GET.get('target_repo', source_repo)
95 c.source_repo = Repository.get_by_repo_name(source_repo)
93 c.source_repo = Repository.get_by_repo_name(source_repo)
96 c.target_repo = Repository.get_by_repo_name(target_repo)
94 c.target_repo = Repository.get_by_repo_name(target_repo)
97
95
98 if c.source_repo is None or c.target_repo is None:
96 if c.source_repo is None or c.target_repo is None:
99 raise HTTPNotFound()
97 raise HTTPNotFound()
100
98
101 c.compare_home = True
99 c.compare_home = True
102 c.commit_ranges = []
100 c.commit_ranges = []
103 c.collapse_all_commits = False
101 c.collapse_all_commits = False
104 c.diffset = None
102 c.diffset = None
105 c.limited_diff = False
103 c.limited_diff = False
106 c.source_ref = c.target_ref = _('Select commit')
104 c.source_ref = c.target_ref = _('Select commit')
107 c.source_ref_type = ""
105 c.source_ref_type = ""
108 c.target_ref_type = ""
106 c.target_ref_type = ""
109 c.commit_statuses = ChangesetStatus.STATUSES
107 c.commit_statuses = ChangesetStatus.STATUSES
110 c.preview_mode = False
108 c.preview_mode = False
111 c.file_path = None
109 c.file_path = None
112
110
113 return self._get_template_context(c)
111 return self._get_template_context(c)
114
112
115 @LoginRequired()
113 @LoginRequired()
116 @HasRepoPermissionAnyDecorator(
114 @HasRepoPermissionAnyDecorator(
117 'repository.read', 'repository.write', 'repository.admin')
115 'repository.read', 'repository.write', 'repository.admin')
118 @view_config(
116 @view_config(
119 route_name='repo_compare', request_method='GET',
117 route_name='repo_compare', request_method='GET',
120 renderer=None)
118 renderer=None)
121 def compare(self):
119 def compare(self):
122 _ = self.request.translate
120 _ = self.request.translate
123 c = self.load_default_context()
121 c = self.load_default_context()
124
122
125 source_ref_type = self.request.matchdict['source_ref_type']
123 source_ref_type = self.request.matchdict['source_ref_type']
126 source_ref = self.request.matchdict['source_ref']
124 source_ref = self.request.matchdict['source_ref']
127 target_ref_type = self.request.matchdict['target_ref_type']
125 target_ref_type = self.request.matchdict['target_ref_type']
128 target_ref = self.request.matchdict['target_ref']
126 target_ref = self.request.matchdict['target_ref']
129
127
130 # source_ref will be evaluated in source_repo
128 # source_ref will be evaluated in source_repo
131 source_repo_name = self.db_repo_name
129 source_repo_name = self.db_repo_name
132 source_path, source_id = parse_path_ref(source_ref)
130 source_path, source_id = parse_path_ref(source_ref)
133
131
134 # target_ref will be evaluated in target_repo
132 # target_ref will be evaluated in target_repo
135 target_repo_name = self.request.GET.get('target_repo', source_repo_name)
133 target_repo_name = self.request.GET.get('target_repo', source_repo_name)
136 target_path, target_id = parse_path_ref(
134 target_path, target_id = parse_path_ref(
137 target_ref, default_path=self.request.GET.get('f_path', ''))
135 target_ref, default_path=self.request.GET.get('f_path', ''))
138
136
139 # if merge is True
137 # if merge is True
140 # Show what changes since the shared ancestor commit of target/source
138 # Show what changes since the shared ancestor commit of target/source
141 # the source would get if it was merged with target. Only commits
139 # the source would get if it was merged with target. Only commits
142 # which are in target but not in source will be shown.
140 # which are in target but not in source will be shown.
143 merge = str2bool(self.request.GET.get('merge'))
141 merge = str2bool(self.request.GET.get('merge'))
144 # if merge is False
142 # if merge is False
145 # Show a raw diff of source/target refs even if no ancestor exists
143 # Show a raw diff of source/target refs even if no ancestor exists
146
144
147 # c.fulldiff disables cut_off_limit
145 # c.fulldiff disables cut_off_limit
148 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
146 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
149
147
150 c.file_path = target_path
148 c.file_path = target_path
151 c.commit_statuses = ChangesetStatus.STATUSES
149 c.commit_statuses = ChangesetStatus.STATUSES
152
150
153 # if partial, returns just compare_commits.html (commits log)
151 # if partial, returns just compare_commits.html (commits log)
154 partial = self.request.is_xhr
152 partial = self.request.is_xhr
155
153
156 # swap url for compare_diff page
154 # swap url for compare_diff page
157 c.swap_url = h.route_path(
155 c.swap_url = h.route_path(
158 'repo_compare',
156 'repo_compare',
159 repo_name=target_repo_name,
157 repo_name=target_repo_name,
160 source_ref_type=target_ref_type,
158 source_ref_type=target_ref_type,
161 source_ref=target_ref,
159 source_ref=target_ref,
162 target_repo=source_repo_name,
160 target_repo=source_repo_name,
163 target_ref_type=source_ref_type,
161 target_ref_type=source_ref_type,
164 target_ref=source_ref,
162 target_ref=source_ref,
165 _query=dict(merge=merge and '1' or '', f_path=target_path))
163 _query=dict(merge=merge and '1' or '', f_path=target_path))
166
164
167 source_repo = Repository.get_by_repo_name(source_repo_name)
165 source_repo = Repository.get_by_repo_name(source_repo_name)
168 target_repo = Repository.get_by_repo_name(target_repo_name)
166 target_repo = Repository.get_by_repo_name(target_repo_name)
169
167
170 if source_repo is None:
168 if source_repo is None:
171 log.error('Could not find the source repo: {}'
169 log.error('Could not find the source repo: {}'
172 .format(source_repo_name))
170 .format(source_repo_name))
173 h.flash(_('Could not find the source repo: `{}`')
171 h.flash(_('Could not find the source repo: `{}`')
174 .format(h.escape(source_repo_name)), category='error')
172 .format(h.escape(source_repo_name)), category='error')
175 raise HTTPFound(
173 raise HTTPFound(
176 h.route_path('repo_compare_select', repo_name=self.db_repo_name))
174 h.route_path('repo_compare_select', repo_name=self.db_repo_name))
177
175
178 if target_repo is None:
176 if target_repo is None:
179 log.error('Could not find the target repo: {}'
177 log.error('Could not find the target repo: {}'
180 .format(source_repo_name))
178 .format(source_repo_name))
181 h.flash(_('Could not find the target repo: `{}`')
179 h.flash(_('Could not find the target repo: `{}`')
182 .format(h.escape(target_repo_name)), category='error')
180 .format(h.escape(target_repo_name)), category='error')
183 raise HTTPFound(
181 raise HTTPFound(
184 h.route_path('repo_compare_select', repo_name=self.db_repo_name))
182 h.route_path('repo_compare_select', repo_name=self.db_repo_name))
185
183
186 source_scm = source_repo.scm_instance()
184 source_scm = source_repo.scm_instance()
187 target_scm = target_repo.scm_instance()
185 target_scm = target_repo.scm_instance()
188
186
189 source_alias = source_scm.alias
187 source_alias = source_scm.alias
190 target_alias = target_scm.alias
188 target_alias = target_scm.alias
191 if source_alias != target_alias:
189 if source_alias != target_alias:
192 msg = _('The comparison of two different kinds of remote repos '
190 msg = _('The comparison of two different kinds of remote repos '
193 'is not available')
191 'is not available')
194 log.error(msg)
192 log.error(msg)
195 h.flash(msg, category='error')
193 h.flash(msg, category='error')
196 raise HTTPFound(
194 raise HTTPFound(
197 h.route_path('repo_compare_select', repo_name=self.db_repo_name))
195 h.route_path('repo_compare_select', repo_name=self.db_repo_name))
198
196
199 source_commit = self._get_commit_or_redirect(
197 source_commit = self._get_commit_or_redirect(
200 ref=source_id, ref_type=source_ref_type, repo=source_repo,
198 ref=source_id, ref_type=source_ref_type, repo=source_repo,
201 partial=partial)
199 partial=partial)
202 target_commit = self._get_commit_or_redirect(
200 target_commit = self._get_commit_or_redirect(
203 ref=target_id, ref_type=target_ref_type, repo=target_repo,
201 ref=target_id, ref_type=target_ref_type, repo=target_repo,
204 partial=partial)
202 partial=partial)
205
203
206 c.compare_home = False
204 c.compare_home = False
207 c.source_repo = source_repo
205 c.source_repo = source_repo
208 c.target_repo = target_repo
206 c.target_repo = target_repo
209 c.source_ref = source_ref
207 c.source_ref = source_ref
210 c.target_ref = target_ref
208 c.target_ref = target_ref
211 c.source_ref_type = source_ref_type
209 c.source_ref_type = source_ref_type
212 c.target_ref_type = target_ref_type
210 c.target_ref_type = target_ref_type
213
211
214 pre_load = ["author", "branch", "date", "message"]
212 pre_load = ["author", "branch", "date", "message"]
215 c.ancestor = None
213 c.ancestor = None
216
214
217 if c.file_path:
215 if c.file_path:
218 if source_commit == target_commit:
216 if source_commit == target_commit:
219 c.commit_ranges = []
217 c.commit_ranges = []
220 else:
218 else:
221 c.commit_ranges = [target_commit]
219 c.commit_ranges = [target_commit]
222 else:
220 else:
223 try:
221 try:
224 c.commit_ranges = source_scm.compare(
222 c.commit_ranges = source_scm.compare(
225 source_commit.raw_id, target_commit.raw_id,
223 source_commit.raw_id, target_commit.raw_id,
226 target_scm, merge, pre_load=pre_load)
224 target_scm, merge, pre_load=pre_load)
227 if merge:
225 if merge:
228 c.ancestor = source_scm.get_common_ancestor(
226 c.ancestor = source_scm.get_common_ancestor(
229 source_commit.raw_id, target_commit.raw_id, target_scm)
227 source_commit.raw_id, target_commit.raw_id, target_scm)
230 except RepositoryRequirementError:
228 except RepositoryRequirementError:
231 msg = _('Could not compare repos with different '
229 msg = _('Could not compare repos with different '
232 'large file settings')
230 'large file settings')
233 log.error(msg)
231 log.error(msg)
234 if partial:
232 if partial:
235 return Response(msg)
233 return Response(msg)
236 h.flash(msg, category='error')
234 h.flash(msg, category='error')
237 raise HTTPFound(
235 raise HTTPFound(
238 h.route_path('repo_compare_select',
236 h.route_path('repo_compare_select',
239 repo_name=self.db_repo_name))
237 repo_name=self.db_repo_name))
240
238
241 c.statuses = self.db_repo.statuses(
239 c.statuses = self.db_repo.statuses(
242 [x.raw_id for x in c.commit_ranges])
240 [x.raw_id for x in c.commit_ranges])
243
241
244 # auto collapse if we have more than limit
242 # auto collapse if we have more than limit
245 collapse_limit = diffs.DiffProcessor._collapse_commits_over
243 collapse_limit = diffs.DiffProcessor._collapse_commits_over
246 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
244 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
247
245
248 if partial: # for PR ajax commits loader
246 if partial: # for PR ajax commits loader
249 if not c.ancestor:
247 if not c.ancestor:
250 return Response('') # cannot merge if there is no ancestor
248 return Response('') # cannot merge if there is no ancestor
251
249
252 html = render(
250 html = render(
253 'rhodecode:templates/compare/compare_commits.mako',
251 'rhodecode:templates/compare/compare_commits.mako',
254 self._get_template_context(c), self.request)
252 self._get_template_context(c), self.request)
255 return Response(html)
253 return Response(html)
256
254
257 if c.ancestor:
255 if c.ancestor:
258 # case we want a simple diff without incoming commits,
256 # case we want a simple diff without incoming commits,
259 # previewing what will be merged.
257 # previewing what will be merged.
260 # Make the diff on target repo (which is known to have target_ref)
258 # Make the diff on target repo (which is known to have target_ref)
261 log.debug('Using ancestor %s as source_ref instead of %s'
259 log.debug('Using ancestor %s as source_ref instead of %s'
262 % (c.ancestor, source_ref))
260 % (c.ancestor, source_ref))
263 source_repo = target_repo
261 source_repo = target_repo
264 source_commit = target_repo.get_commit(commit_id=c.ancestor)
262 source_commit = target_repo.get_commit(commit_id=c.ancestor)
265
263
266 # diff_limit will cut off the whole diff if the limit is applied
264 # diff_limit will cut off the whole diff if the limit is applied
267 # otherwise it will just hide the big files from the front-end
265 # otherwise it will just hide the big files from the front-end
268 diff_limit = c.visual.cut_off_limit_diff
266 diff_limit = c.visual.cut_off_limit_diff
269 file_limit = c.visual.cut_off_limit_file
267 file_limit = c.visual.cut_off_limit_file
270
268
271 log.debug('calculating diff between '
269 log.debug('calculating diff between '
272 'source_ref:%s and target_ref:%s for repo `%s`',
270 'source_ref:%s and target_ref:%s for repo `%s`',
273 source_commit, target_commit,
271 source_commit, target_commit,
274 safe_unicode(source_repo.scm_instance().path))
272 safe_unicode(source_repo.scm_instance().path))
275
273
276 if source_commit.repository != target_commit.repository:
274 if source_commit.repository != target_commit.repository:
277 msg = _(
275 msg = _(
278 "Repositories unrelated. "
276 "Repositories unrelated. "
279 "Cannot compare commit %(commit1)s from repository %(repo1)s "
277 "Cannot compare commit %(commit1)s from repository %(repo1)s "
280 "with commit %(commit2)s from repository %(repo2)s.") % {
278 "with commit %(commit2)s from repository %(repo2)s.") % {
281 'commit1': h.show_id(source_commit),
279 'commit1': h.show_id(source_commit),
282 'repo1': source_repo.repo_name,
280 'repo1': source_repo.repo_name,
283 'commit2': h.show_id(target_commit),
281 'commit2': h.show_id(target_commit),
284 'repo2': target_repo.repo_name,
282 'repo2': target_repo.repo_name,
285 }
283 }
286 h.flash(msg, category='error')
284 h.flash(msg, category='error')
287 raise HTTPFound(
285 raise HTTPFound(
288 h.route_path('repo_compare_select',
286 h.route_path('repo_compare_select',
289 repo_name=self.db_repo_name))
287 repo_name=self.db_repo_name))
290
288
291 txt_diff = source_repo.scm_instance().get_diff(
289 txt_diff = source_repo.scm_instance().get_diff(
292 commit1=source_commit, commit2=target_commit,
290 commit1=source_commit, commit2=target_commit,
293 path=target_path, path1=source_path)
291 path=target_path, path1=source_path)
294
292
295 diff_processor = diffs.DiffProcessor(
293 diff_processor = diffs.DiffProcessor(
296 txt_diff, format='newdiff', diff_limit=diff_limit,
294 txt_diff, format='newdiff', diff_limit=diff_limit,
297 file_limit=file_limit, show_full_diff=c.fulldiff)
295 file_limit=file_limit, show_full_diff=c.fulldiff)
298 _parsed = diff_processor.prepare()
296 _parsed = diff_processor.prepare()
299
297
300 def _node_getter(commit):
298 def _node_getter(commit):
301 """ Returns a function that returns a node for a commit or None """
299 """ Returns a function that returns a node for a commit or None """
302 def get_node(fname):
300 def get_node(fname):
303 try:
301 try:
304 return commit.get_node(fname)
302 return commit.get_node(fname)
305 except NodeDoesNotExistError:
303 except NodeDoesNotExistError:
306 return None
304 return None
307 return get_node
305 return get_node
308
306
309 diffset = codeblocks.DiffSet(
307 diffset = codeblocks.DiffSet(
310 repo_name=source_repo.repo_name,
308 repo_name=source_repo.repo_name,
311 source_node_getter=_node_getter(source_commit),
309 source_node_getter=_node_getter(source_commit),
312 target_node_getter=_node_getter(target_commit),
310 target_node_getter=_node_getter(target_commit),
313 )
311 )
314 c.diffset = diffset.render_patchset(
312 c.diffset = diffset.render_patchset(
315 _parsed, source_ref, target_ref)
313 _parsed, source_ref, target_ref)
316
314
317 c.preview_mode = merge
315 c.preview_mode = merge
318 c.source_commit = source_commit
316 c.source_commit = source_commit
319 c.target_commit = target_commit
317 c.target_commit = target_commit
320
318
321 html = render(
319 html = render(
322 'rhodecode:templates/compare/compare_diff.mako',
320 'rhodecode:templates/compare/compare_diff.mako',
323 self._get_template_context(c), self.request)
321 self._get_template_context(c), self.request)
324 return Response(html) No newline at end of file
322 return Response(html)
@@ -1,207 +1,204 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2017-2017 RhodeCode GmbH
3 # Copyright (C) 2017-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytz
21 import pytz
22 import logging
22 import logging
23
23
24 from beaker.cache import cache_region
24 from beaker.cache import cache_region
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26 from pyramid.response import Response
26 from pyramid.response import Response
27 from webhelpers.feedgenerator import Rss201rev2Feed, Atom1Feed
27 from webhelpers.feedgenerator import Rss201rev2Feed, Atom1Feed
28
28
29 from rhodecode.apps._base import RepoAppView
29 from rhodecode.apps._base import RepoAppView
30 from rhodecode.lib import audit_logger
30 from rhodecode.lib import audit_logger
31 from rhodecode.lib import helpers as h
31 from rhodecode.lib import helpers as h
32 from rhodecode.lib.auth import (
32 from rhodecode.lib.auth import (
33 LoginRequired, HasRepoPermissionAnyDecorator)
33 LoginRequired, HasRepoPermissionAnyDecorator)
34 from rhodecode.lib.diffs import DiffProcessor, LimitedDiffContainer
34 from rhodecode.lib.diffs import DiffProcessor, LimitedDiffContainer
35 from rhodecode.lib.utils2 import str2bool, safe_int, md5_safe
35 from rhodecode.lib.utils2 import str2bool, safe_int, md5_safe
36 from rhodecode.model.db import UserApiKeys, CacheKey
36 from rhodecode.model.db import UserApiKeys, CacheKey
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40
40
41 class RepoFeedView(RepoAppView):
41 class RepoFeedView(RepoAppView):
42 def load_default_context(self):
42 def load_default_context(self):
43 c = self._get_local_tmpl_context()
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 self._register_global_c(c)
45 self._register_global_c(c)
49 self._load_defaults()
46 self._load_defaults()
50 return c
47 return c
51
48
52 def _get_config(self):
49 def _get_config(self):
53 import rhodecode
50 import rhodecode
54 config = rhodecode.CONFIG
51 config = rhodecode.CONFIG
55
52
56 return {
53 return {
57 'language': 'en-us',
54 'language': 'en-us',
58 'feed_ttl': '5', # TTL of feed,
55 'feed_ttl': '5', # TTL of feed,
59 'feed_include_diff':
56 'feed_include_diff':
60 str2bool(config.get('rss_include_diff', False)),
57 str2bool(config.get('rss_include_diff', False)),
61 'feed_items_per_page':
58 'feed_items_per_page':
62 safe_int(config.get('rss_items_per_page', 20)),
59 safe_int(config.get('rss_items_per_page', 20)),
63 'feed_diff_limit':
60 'feed_diff_limit':
64 # we need to protect from parsing huge diffs here other way
61 # we need to protect from parsing huge diffs here other way
65 # we can kill the server
62 # we can kill the server
66 safe_int(config.get('rss_cut_off_limit', 32 * 1024)),
63 safe_int(config.get('rss_cut_off_limit', 32 * 1024)),
67 }
64 }
68
65
69 def _load_defaults(self):
66 def _load_defaults(self):
70 _ = self.request.translate
67 _ = self.request.translate
71 config = self._get_config()
68 config = self._get_config()
72 # common values for feeds
69 # common values for feeds
73 self.description = _('Changes on %s repository')
70 self.description = _('Changes on %s repository')
74 self.title = self.title = _('%s %s feed') % (self.db_repo_name, '%s')
71 self.title = self.title = _('%s %s feed') % (self.db_repo_name, '%s')
75 self.language = config["language"]
72 self.language = config["language"]
76 self.ttl = config["feed_ttl"]
73 self.ttl = config["feed_ttl"]
77 self.feed_include_diff = config['feed_include_diff']
74 self.feed_include_diff = config['feed_include_diff']
78 self.feed_diff_limit = config['feed_diff_limit']
75 self.feed_diff_limit = config['feed_diff_limit']
79 self.feed_items_per_page = config['feed_items_per_page']
76 self.feed_items_per_page = config['feed_items_per_page']
80
77
81 def _changes(self, commit):
78 def _changes(self, commit):
82 diff_processor = DiffProcessor(
79 diff_processor = DiffProcessor(
83 commit.diff(), diff_limit=self.feed_diff_limit)
80 commit.diff(), diff_limit=self.feed_diff_limit)
84 _parsed = diff_processor.prepare(inline_diff=False)
81 _parsed = diff_processor.prepare(inline_diff=False)
85 limited_diff = isinstance(_parsed, LimitedDiffContainer)
82 limited_diff = isinstance(_parsed, LimitedDiffContainer)
86
83
87 return _parsed, limited_diff
84 return _parsed, limited_diff
88
85
89 def _get_title(self, commit):
86 def _get_title(self, commit):
90 return h.shorter(commit.message, 160)
87 return h.shorter(commit.message, 160)
91
88
92 def _get_description(self, commit):
89 def _get_description(self, commit):
93 _renderer = self.request.get_partial_renderer(
90 _renderer = self.request.get_partial_renderer(
94 'feed/atom_feed_entry.mako')
91 'feed/atom_feed_entry.mako')
95 parsed_diff, limited_diff = self._changes(commit)
92 parsed_diff, limited_diff = self._changes(commit)
96 return _renderer(
93 return _renderer(
97 'body',
94 'body',
98 commit=commit,
95 commit=commit,
99 parsed_diff=parsed_diff,
96 parsed_diff=parsed_diff,
100 limited_diff=limited_diff,
97 limited_diff=limited_diff,
101 feed_include_diff=self.feed_include_diff,
98 feed_include_diff=self.feed_include_diff,
102 )
99 )
103
100
104 def _set_timezone(self, date, tzinfo=pytz.utc):
101 def _set_timezone(self, date, tzinfo=pytz.utc):
105 if not getattr(date, "tzinfo", None):
102 if not getattr(date, "tzinfo", None):
106 date.replace(tzinfo=tzinfo)
103 date.replace(tzinfo=tzinfo)
107 return date
104 return date
108
105
109 def _get_commits(self):
106 def _get_commits(self):
110 return list(self.rhodecode_vcs_repo[-self.feed_items_per_page:])
107 return list(self.rhodecode_vcs_repo[-self.feed_items_per_page:])
111
108
112 def uid(self, repo_id, commit_id):
109 def uid(self, repo_id, commit_id):
113 return '{}:{}'.format(md5_safe(repo_id), md5_safe(commit_id))
110 return '{}:{}'.format(md5_safe(repo_id), md5_safe(commit_id))
114
111
115 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
112 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
116 @HasRepoPermissionAnyDecorator(
113 @HasRepoPermissionAnyDecorator(
117 'repository.read', 'repository.write', 'repository.admin')
114 'repository.read', 'repository.write', 'repository.admin')
118 @view_config(
115 @view_config(
119 route_name='atom_feed_home', request_method='GET',
116 route_name='atom_feed_home', request_method='GET',
120 renderer=None)
117 renderer=None)
121 def atom(self):
118 def atom(self):
122 """
119 """
123 Produce an atom-1.0 feed via feedgenerator module
120 Produce an atom-1.0 feed via feedgenerator module
124 """
121 """
125 self.load_default_context()
122 self.load_default_context()
126
123
127 @cache_region('long_term')
124 @cache_region('long_term')
128 def _generate_feed(cache_key):
125 def _generate_feed(cache_key):
129 feed = Atom1Feed(
126 feed = Atom1Feed(
130 title=self.title % self.db_repo_name,
127 title=self.title % self.db_repo_name,
131 link=h.route_url('repo_summary', repo_name=self.db_repo_name),
128 link=h.route_url('repo_summary', repo_name=self.db_repo_name),
132 description=self.description % self.db_repo_name,
129 description=self.description % self.db_repo_name,
133 language=self.language,
130 language=self.language,
134 ttl=self.ttl
131 ttl=self.ttl
135 )
132 )
136
133
137 for commit in reversed(self._get_commits()):
134 for commit in reversed(self._get_commits()):
138 date = self._set_timezone(commit.date)
135 date = self._set_timezone(commit.date)
139 feed.add_item(
136 feed.add_item(
140 unique_id=self.uid(self.db_repo.repo_id, commit.raw_id),
137 unique_id=self.uid(self.db_repo.repo_id, commit.raw_id),
141 title=self._get_title(commit),
138 title=self._get_title(commit),
142 author_name=commit.author,
139 author_name=commit.author,
143 description=self._get_description(commit),
140 description=self._get_description(commit),
144 link=h.route_url(
141 link=h.route_url(
145 'repo_commit', repo_name=self.db_repo_name,
142 'repo_commit', repo_name=self.db_repo_name,
146 commit_id=commit.raw_id),
143 commit_id=commit.raw_id),
147 pubdate=date,)
144 pubdate=date,)
148
145
149 return feed.mime_type, feed.writeString('utf-8')
146 return feed.mime_type, feed.writeString('utf-8')
150
147
151 invalidator_context = CacheKey.repo_context_cache(
148 invalidator_context = CacheKey.repo_context_cache(
152 _generate_feed, self.db_repo_name, CacheKey.CACHE_TYPE_ATOM)
149 _generate_feed, self.db_repo_name, CacheKey.CACHE_TYPE_ATOM)
153
150
154 with invalidator_context as context:
151 with invalidator_context as context:
155 context.invalidate()
152 context.invalidate()
156 mime_type, feed = context.compute()
153 mime_type, feed = context.compute()
157
154
158 response = Response(feed)
155 response = Response(feed)
159 response.content_type = mime_type
156 response.content_type = mime_type
160 return response
157 return response
161
158
162 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
159 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
163 @HasRepoPermissionAnyDecorator(
160 @HasRepoPermissionAnyDecorator(
164 'repository.read', 'repository.write', 'repository.admin')
161 'repository.read', 'repository.write', 'repository.admin')
165 @view_config(
162 @view_config(
166 route_name='rss_feed_home', request_method='GET',
163 route_name='rss_feed_home', request_method='GET',
167 renderer=None)
164 renderer=None)
168 def rss(self):
165 def rss(self):
169 """
166 """
170 Produce an rss2 feed via feedgenerator module
167 Produce an rss2 feed via feedgenerator module
171 """
168 """
172 self.load_default_context()
169 self.load_default_context()
173
170
174 @cache_region('long_term')
171 @cache_region('long_term')
175 def _generate_feed(cache_key):
172 def _generate_feed(cache_key):
176 feed = Rss201rev2Feed(
173 feed = Rss201rev2Feed(
177 title=self.title % self.db_repo_name,
174 title=self.title % self.db_repo_name,
178 link=h.route_url('repo_summary', repo_name=self.db_repo_name),
175 link=h.route_url('repo_summary', repo_name=self.db_repo_name),
179 description=self.description % self.db_repo_name,
176 description=self.description % self.db_repo_name,
180 language=self.language,
177 language=self.language,
181 ttl=self.ttl
178 ttl=self.ttl
182 )
179 )
183
180
184 for commit in reversed(self._get_commits()):
181 for commit in reversed(self._get_commits()):
185 date = self._set_timezone(commit.date)
182 date = self._set_timezone(commit.date)
186 feed.add_item(
183 feed.add_item(
187 unique_id=self.uid(self.db_repo.repo_id, commit.raw_id),
184 unique_id=self.uid(self.db_repo.repo_id, commit.raw_id),
188 title=self._get_title(commit),
185 title=self._get_title(commit),
189 author_name=commit.author,
186 author_name=commit.author,
190 description=self._get_description(commit),
187 description=self._get_description(commit),
191 link=h.route_url(
188 link=h.route_url(
192 'repo_commit', repo_name=self.db_repo_name,
189 'repo_commit', repo_name=self.db_repo_name,
193 commit_id=commit.raw_id),
190 commit_id=commit.raw_id),
194 pubdate=date,)
191 pubdate=date,)
195
192
196 return feed.mime_type, feed.writeString('utf-8')
193 return feed.mime_type, feed.writeString('utf-8')
197
194
198 invalidator_context = CacheKey.repo_context_cache(
195 invalidator_context = CacheKey.repo_context_cache(
199 _generate_feed, self.db_repo_name, CacheKey.CACHE_TYPE_RSS)
196 _generate_feed, self.db_repo_name, CacheKey.CACHE_TYPE_RSS)
200
197
201 with invalidator_context as context:
198 with invalidator_context as context:
202 context.invalidate()
199 context.invalidate()
203 mime_type, feed = context.compute()
200 mime_type, feed = context.compute()
204
201
205 response = Response(feed)
202 response = Response(feed)
206 response.content_type = mime_type
203 response.content_type = mime_type
207 return response
204 return response
@@ -1,1284 +1,1282 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import itertools
21 import itertools
22 import logging
22 import logging
23 import os
23 import os
24 import shutil
24 import shutil
25 import tempfile
25 import tempfile
26 import collections
26 import collections
27
27
28 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
28 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
29 from pyramid.view import view_config
29 from pyramid.view import view_config
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31 from pyramid.response import Response
31 from pyramid.response import Response
32
32
33 from rhodecode.apps._base import RepoAppView
33 from rhodecode.apps._base import RepoAppView
34
34
35 from rhodecode.controllers.utils import parse_path_ref
35 from rhodecode.controllers.utils import parse_path_ref
36 from rhodecode.lib import diffs, helpers as h, caches
36 from rhodecode.lib import diffs, helpers as h, caches
37 from rhodecode.lib import audit_logger
37 from rhodecode.lib import audit_logger
38 from rhodecode.lib.exceptions import NonRelativePathError
38 from rhodecode.lib.exceptions import NonRelativePathError
39 from rhodecode.lib.codeblocks import (
39 from rhodecode.lib.codeblocks import (
40 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
40 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
41 from rhodecode.lib.utils2 import (
41 from rhodecode.lib.utils2 import (
42 convert_line_endings, detect_mode, safe_str, str2bool)
42 convert_line_endings, detect_mode, safe_str, str2bool)
43 from rhodecode.lib.auth import (
43 from rhodecode.lib.auth import (
44 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
44 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
45 from rhodecode.lib.vcs import path as vcspath
45 from rhodecode.lib.vcs import path as vcspath
46 from rhodecode.lib.vcs.backends.base import EmptyCommit
46 from rhodecode.lib.vcs.backends.base import EmptyCommit
47 from rhodecode.lib.vcs.conf import settings
47 from rhodecode.lib.vcs.conf import settings
48 from rhodecode.lib.vcs.nodes import FileNode
48 from rhodecode.lib.vcs.nodes import FileNode
49 from rhodecode.lib.vcs.exceptions import (
49 from rhodecode.lib.vcs.exceptions import (
50 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
50 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
51 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
51 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
52 NodeDoesNotExistError, CommitError, NodeError)
52 NodeDoesNotExistError, CommitError, NodeError)
53
53
54 from rhodecode.model.scm import ScmModel
54 from rhodecode.model.scm import ScmModel
55 from rhodecode.model.db import Repository
55 from rhodecode.model.db import Repository
56
56
57 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
58
58
59
59
60 class RepoFilesView(RepoAppView):
60 class RepoFilesView(RepoAppView):
61
61
62 @staticmethod
62 @staticmethod
63 def adjust_file_path_for_svn(f_path, repo):
63 def adjust_file_path_for_svn(f_path, repo):
64 """
64 """
65 Computes the relative path of `f_path`.
65 Computes the relative path of `f_path`.
66
66
67 This is mainly based on prefix matching of the recognized tags and
67 This is mainly based on prefix matching of the recognized tags and
68 branches in the underlying repository.
68 branches in the underlying repository.
69 """
69 """
70 tags_and_branches = itertools.chain(
70 tags_and_branches = itertools.chain(
71 repo.branches.iterkeys(),
71 repo.branches.iterkeys(),
72 repo.tags.iterkeys())
72 repo.tags.iterkeys())
73 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
73 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
74
74
75 for name in tags_and_branches:
75 for name in tags_and_branches:
76 if f_path.startswith('{}/'.format(name)):
76 if f_path.startswith('{}/'.format(name)):
77 f_path = vcspath.relpath(f_path, name)
77 f_path = vcspath.relpath(f_path, name)
78 break
78 break
79 return f_path
79 return f_path
80
80
81 def load_default_context(self):
81 def load_default_context(self):
82 c = self._get_local_tmpl_context(include_app_defaults=True)
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 c.rhodecode_repo = self.rhodecode_vcs_repo
84 c.rhodecode_repo = self.rhodecode_vcs_repo
87
85
88 self._register_global_c(c)
86 self._register_global_c(c)
89 return c
87 return c
90
88
91 def _ensure_not_locked(self):
89 def _ensure_not_locked(self):
92 _ = self.request.translate
90 _ = self.request.translate
93
91
94 repo = self.db_repo
92 repo = self.db_repo
95 if repo.enable_locking and repo.locked[0]:
93 if repo.enable_locking and repo.locked[0]:
96 h.flash(_('This repository has been locked by %s on %s')
94 h.flash(_('This repository has been locked by %s on %s')
97 % (h.person_by_id(repo.locked[0]),
95 % (h.person_by_id(repo.locked[0]),
98 h.format_date(h.time_to_datetime(repo.locked[1]))),
96 h.format_date(h.time_to_datetime(repo.locked[1]))),
99 'warning')
97 'warning')
100 files_url = h.route_path(
98 files_url = h.route_path(
101 'repo_files:default_path',
99 'repo_files:default_path',
102 repo_name=self.db_repo_name, commit_id='tip')
100 repo_name=self.db_repo_name, commit_id='tip')
103 raise HTTPFound(files_url)
101 raise HTTPFound(files_url)
104
102
105 def _get_commit_and_path(self):
103 def _get_commit_and_path(self):
106 default_commit_id = self.db_repo.landing_rev[1]
104 default_commit_id = self.db_repo.landing_rev[1]
107 default_f_path = '/'
105 default_f_path = '/'
108
106
109 commit_id = self.request.matchdict.get(
107 commit_id = self.request.matchdict.get(
110 'commit_id', default_commit_id)
108 'commit_id', default_commit_id)
111 f_path = self._get_f_path(self.request.matchdict, default_f_path)
109 f_path = self._get_f_path(self.request.matchdict, default_f_path)
112 return commit_id, f_path
110 return commit_id, f_path
113
111
114 def _get_default_encoding(self, c):
112 def _get_default_encoding(self, c):
115 enc_list = getattr(c, 'default_encodings', [])
113 enc_list = getattr(c, 'default_encodings', [])
116 return enc_list[0] if enc_list else 'UTF-8'
114 return enc_list[0] if enc_list else 'UTF-8'
117
115
118 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
116 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
119 """
117 """
120 This is a safe way to get commit. If an error occurs it redirects to
118 This is a safe way to get commit. If an error occurs it redirects to
121 tip with proper message
119 tip with proper message
122
120
123 :param commit_id: id of commit to fetch
121 :param commit_id: id of commit to fetch
124 :param redirect_after: toggle redirection
122 :param redirect_after: toggle redirection
125 """
123 """
126 _ = self.request.translate
124 _ = self.request.translate
127
125
128 try:
126 try:
129 return self.rhodecode_vcs_repo.get_commit(commit_id)
127 return self.rhodecode_vcs_repo.get_commit(commit_id)
130 except EmptyRepositoryError:
128 except EmptyRepositoryError:
131 if not redirect_after:
129 if not redirect_after:
132 return None
130 return None
133
131
134 _url = h.route_path(
132 _url = h.route_path(
135 'repo_files_add_file',
133 'repo_files_add_file',
136 repo_name=self.db_repo_name, commit_id=0, f_path='',
134 repo_name=self.db_repo_name, commit_id=0, f_path='',
137 _anchor='edit')
135 _anchor='edit')
138
136
139 if h.HasRepoPermissionAny(
137 if h.HasRepoPermissionAny(
140 'repository.write', 'repository.admin')(self.db_repo_name):
138 'repository.write', 'repository.admin')(self.db_repo_name):
141 add_new = h.link_to(
139 add_new = h.link_to(
142 _('Click here to add a new file.'), _url, class_="alert-link")
140 _('Click here to add a new file.'), _url, class_="alert-link")
143 else:
141 else:
144 add_new = ""
142 add_new = ""
145
143
146 h.flash(h.literal(
144 h.flash(h.literal(
147 _('There are no files yet. %s') % add_new), category='warning')
145 _('There are no files yet. %s') % add_new), category='warning')
148 raise HTTPFound(
146 raise HTTPFound(
149 h.route_path('repo_summary', repo_name=self.db_repo_name))
147 h.route_path('repo_summary', repo_name=self.db_repo_name))
150
148
151 except (CommitDoesNotExistError, LookupError):
149 except (CommitDoesNotExistError, LookupError):
152 msg = _('No such commit exists for this repository')
150 msg = _('No such commit exists for this repository')
153 h.flash(msg, category='error')
151 h.flash(msg, category='error')
154 raise HTTPNotFound()
152 raise HTTPNotFound()
155 except RepositoryError as e:
153 except RepositoryError as e:
156 h.flash(safe_str(h.escape(e)), category='error')
154 h.flash(safe_str(h.escape(e)), category='error')
157 raise HTTPNotFound()
155 raise HTTPNotFound()
158
156
159 def _get_filenode_or_redirect(self, commit_obj, path):
157 def _get_filenode_or_redirect(self, commit_obj, path):
160 """
158 """
161 Returns file_node, if error occurs or given path is directory,
159 Returns file_node, if error occurs or given path is directory,
162 it'll redirect to top level path
160 it'll redirect to top level path
163 """
161 """
164 _ = self.request.translate
162 _ = self.request.translate
165
163
166 try:
164 try:
167 file_node = commit_obj.get_node(path)
165 file_node = commit_obj.get_node(path)
168 if file_node.is_dir():
166 if file_node.is_dir():
169 raise RepositoryError('The given path is a directory')
167 raise RepositoryError('The given path is a directory')
170 except CommitDoesNotExistError:
168 except CommitDoesNotExistError:
171 log.exception('No such commit exists for this repository')
169 log.exception('No such commit exists for this repository')
172 h.flash(_('No such commit exists for this repository'), category='error')
170 h.flash(_('No such commit exists for this repository'), category='error')
173 raise HTTPNotFound()
171 raise HTTPNotFound()
174 except RepositoryError as e:
172 except RepositoryError as e:
175 log.warning('Repository error while fetching '
173 log.warning('Repository error while fetching '
176 'filenode `%s`. Err:%s', path, e)
174 'filenode `%s`. Err:%s', path, e)
177 h.flash(safe_str(h.escape(e)), category='error')
175 h.flash(safe_str(h.escape(e)), category='error')
178 raise HTTPNotFound()
176 raise HTTPNotFound()
179
177
180 return file_node
178 return file_node
181
179
182 def _is_valid_head(self, commit_id, repo):
180 def _is_valid_head(self, commit_id, repo):
183 # check if commit is a branch identifier- basically we cannot
181 # check if commit is a branch identifier- basically we cannot
184 # create multiple heads via file editing
182 # create multiple heads via file editing
185 valid_heads = repo.branches.keys() + repo.branches.values()
183 valid_heads = repo.branches.keys() + repo.branches.values()
186
184
187 if h.is_svn(repo) and not repo.is_empty():
185 if h.is_svn(repo) and not repo.is_empty():
188 # Note: Subversion only has one head, we add it here in case there
186 # Note: Subversion only has one head, we add it here in case there
189 # is no branch matched.
187 # is no branch matched.
190 valid_heads.append(repo.get_commit(commit_idx=-1).raw_id)
188 valid_heads.append(repo.get_commit(commit_idx=-1).raw_id)
191
189
192 # check if commit is a branch name or branch hash
190 # check if commit is a branch name or branch hash
193 return commit_id in valid_heads
191 return commit_id in valid_heads
194
192
195 def _get_tree_cache_manager(self, namespace_type):
193 def _get_tree_cache_manager(self, namespace_type):
196 _namespace = caches.get_repo_namespace_key(
194 _namespace = caches.get_repo_namespace_key(
197 namespace_type, self.db_repo_name)
195 namespace_type, self.db_repo_name)
198 return caches.get_cache_manager('repo_cache_long', _namespace)
196 return caches.get_cache_manager('repo_cache_long', _namespace)
199
197
200 def _get_tree_at_commit(
198 def _get_tree_at_commit(
201 self, c, commit_id, f_path, full_load=False, force=False):
199 self, c, commit_id, f_path, full_load=False, force=False):
202 def _cached_tree():
200 def _cached_tree():
203 log.debug('Generating cached file tree for %s, %s, %s',
201 log.debug('Generating cached file tree for %s, %s, %s',
204 self.db_repo_name, commit_id, f_path)
202 self.db_repo_name, commit_id, f_path)
205
203
206 c.full_load = full_load
204 c.full_load = full_load
207 return render(
205 return render(
208 'rhodecode:templates/files/files_browser_tree.mako',
206 'rhodecode:templates/files/files_browser_tree.mako',
209 self._get_template_context(c), self.request)
207 self._get_template_context(c), self.request)
210
208
211 cache_manager = self._get_tree_cache_manager(caches.FILE_TREE)
209 cache_manager = self._get_tree_cache_manager(caches.FILE_TREE)
212
210
213 cache_key = caches.compute_key_from_params(
211 cache_key = caches.compute_key_from_params(
214 self.db_repo_name, commit_id, f_path)
212 self.db_repo_name, commit_id, f_path)
215
213
216 if force:
214 if force:
217 # we want to force recompute of caches
215 # we want to force recompute of caches
218 cache_manager.remove_value(cache_key)
216 cache_manager.remove_value(cache_key)
219
217
220 return cache_manager.get(cache_key, createfunc=_cached_tree)
218 return cache_manager.get(cache_key, createfunc=_cached_tree)
221
219
222 def _get_archive_spec(self, fname):
220 def _get_archive_spec(self, fname):
223 log.debug('Detecting archive spec for: `%s`', fname)
221 log.debug('Detecting archive spec for: `%s`', fname)
224
222
225 fileformat = None
223 fileformat = None
226 ext = None
224 ext = None
227 content_type = None
225 content_type = None
228 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
226 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
229 content_type, extension = ext_data
227 content_type, extension = ext_data
230
228
231 if fname.endswith(extension):
229 if fname.endswith(extension):
232 fileformat = a_type
230 fileformat = a_type
233 log.debug('archive is of type: %s', fileformat)
231 log.debug('archive is of type: %s', fileformat)
234 ext = extension
232 ext = extension
235 break
233 break
236
234
237 if not fileformat:
235 if not fileformat:
238 raise ValueError()
236 raise ValueError()
239
237
240 # left over part of whole fname is the commit
238 # left over part of whole fname is the commit
241 commit_id = fname[:-len(ext)]
239 commit_id = fname[:-len(ext)]
242
240
243 return commit_id, ext, fileformat, content_type
241 return commit_id, ext, fileformat, content_type
244
242
245 @LoginRequired()
243 @LoginRequired()
246 @HasRepoPermissionAnyDecorator(
244 @HasRepoPermissionAnyDecorator(
247 'repository.read', 'repository.write', 'repository.admin')
245 'repository.read', 'repository.write', 'repository.admin')
248 @view_config(
246 @view_config(
249 route_name='repo_archivefile', request_method='GET',
247 route_name='repo_archivefile', request_method='GET',
250 renderer=None)
248 renderer=None)
251 def repo_archivefile(self):
249 def repo_archivefile(self):
252 # archive cache config
250 # archive cache config
253 from rhodecode import CONFIG
251 from rhodecode import CONFIG
254 _ = self.request.translate
252 _ = self.request.translate
255 self.load_default_context()
253 self.load_default_context()
256
254
257 fname = self.request.matchdict['fname']
255 fname = self.request.matchdict['fname']
258 subrepos = self.request.GET.get('subrepos') == 'true'
256 subrepos = self.request.GET.get('subrepos') == 'true'
259
257
260 if not self.db_repo.enable_downloads:
258 if not self.db_repo.enable_downloads:
261 return Response(_('Downloads disabled'))
259 return Response(_('Downloads disabled'))
262
260
263 try:
261 try:
264 commit_id, ext, fileformat, content_type = \
262 commit_id, ext, fileformat, content_type = \
265 self._get_archive_spec(fname)
263 self._get_archive_spec(fname)
266 except ValueError:
264 except ValueError:
267 return Response(_('Unknown archive type for: `{}`').format(fname))
265 return Response(_('Unknown archive type for: `{}`').format(fname))
268
266
269 try:
267 try:
270 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
268 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
271 except CommitDoesNotExistError:
269 except CommitDoesNotExistError:
272 return Response(_('Unknown commit_id %s') % commit_id)
270 return Response(_('Unknown commit_id %s') % commit_id)
273 except EmptyRepositoryError:
271 except EmptyRepositoryError:
274 return Response(_('Empty repository'))
272 return Response(_('Empty repository'))
275
273
276 archive_name = '%s-%s%s%s' % (
274 archive_name = '%s-%s%s%s' % (
277 safe_str(self.db_repo_name.replace('/', '_')),
275 safe_str(self.db_repo_name.replace('/', '_')),
278 '-sub' if subrepos else '',
276 '-sub' if subrepos else '',
279 safe_str(commit.short_id), ext)
277 safe_str(commit.short_id), ext)
280
278
281 use_cached_archive = False
279 use_cached_archive = False
282 archive_cache_enabled = CONFIG.get(
280 archive_cache_enabled = CONFIG.get(
283 'archive_cache_dir') and not self.request.GET.get('no_cache')
281 'archive_cache_dir') and not self.request.GET.get('no_cache')
284
282
285 if archive_cache_enabled:
283 if archive_cache_enabled:
286 # check if we it's ok to write
284 # check if we it's ok to write
287 if not os.path.isdir(CONFIG['archive_cache_dir']):
285 if not os.path.isdir(CONFIG['archive_cache_dir']):
288 os.makedirs(CONFIG['archive_cache_dir'])
286 os.makedirs(CONFIG['archive_cache_dir'])
289 cached_archive_path = os.path.join(
287 cached_archive_path = os.path.join(
290 CONFIG['archive_cache_dir'], archive_name)
288 CONFIG['archive_cache_dir'], archive_name)
291 if os.path.isfile(cached_archive_path):
289 if os.path.isfile(cached_archive_path):
292 log.debug('Found cached archive in %s', cached_archive_path)
290 log.debug('Found cached archive in %s', cached_archive_path)
293 fd, archive = None, cached_archive_path
291 fd, archive = None, cached_archive_path
294 use_cached_archive = True
292 use_cached_archive = True
295 else:
293 else:
296 log.debug('Archive %s is not yet cached', archive_name)
294 log.debug('Archive %s is not yet cached', archive_name)
297
295
298 if not use_cached_archive:
296 if not use_cached_archive:
299 # generate new archive
297 # generate new archive
300 fd, archive = tempfile.mkstemp()
298 fd, archive = tempfile.mkstemp()
301 log.debug('Creating new temp archive in %s', archive)
299 log.debug('Creating new temp archive in %s', archive)
302 try:
300 try:
303 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
301 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
304 except ImproperArchiveTypeError:
302 except ImproperArchiveTypeError:
305 return _('Unknown archive type')
303 return _('Unknown archive type')
306 if archive_cache_enabled:
304 if archive_cache_enabled:
307 # if we generated the archive and we have cache enabled
305 # if we generated the archive and we have cache enabled
308 # let's use this for future
306 # let's use this for future
309 log.debug('Storing new archive in %s', cached_archive_path)
307 log.debug('Storing new archive in %s', cached_archive_path)
310 shutil.move(archive, cached_archive_path)
308 shutil.move(archive, cached_archive_path)
311 archive = cached_archive_path
309 archive = cached_archive_path
312
310
313 # store download action
311 # store download action
314 audit_logger.store_web(
312 audit_logger.store_web(
315 'repo.archive.download', action_data={
313 'repo.archive.download', action_data={
316 'user_agent': self.request.user_agent,
314 'user_agent': self.request.user_agent,
317 'archive_name': archive_name,
315 'archive_name': archive_name,
318 'archive_spec': fname,
316 'archive_spec': fname,
319 'archive_cached': use_cached_archive},
317 'archive_cached': use_cached_archive},
320 user=self._rhodecode_user,
318 user=self._rhodecode_user,
321 repo=self.db_repo,
319 repo=self.db_repo,
322 commit=True
320 commit=True
323 )
321 )
324
322
325 def get_chunked_archive(archive):
323 def get_chunked_archive(archive):
326 with open(archive, 'rb') as stream:
324 with open(archive, 'rb') as stream:
327 while True:
325 while True:
328 data = stream.read(16 * 1024)
326 data = stream.read(16 * 1024)
329 if not data:
327 if not data:
330 if fd: # fd means we used temporary file
328 if fd: # fd means we used temporary file
331 os.close(fd)
329 os.close(fd)
332 if not archive_cache_enabled:
330 if not archive_cache_enabled:
333 log.debug('Destroying temp archive %s', archive)
331 log.debug('Destroying temp archive %s', archive)
334 os.remove(archive)
332 os.remove(archive)
335 break
333 break
336 yield data
334 yield data
337
335
338 response = Response(app_iter=get_chunked_archive(archive))
336 response = Response(app_iter=get_chunked_archive(archive))
339 response.content_disposition = str(
337 response.content_disposition = str(
340 'attachment; filename=%s' % archive_name)
338 'attachment; filename=%s' % archive_name)
341 response.content_type = str(content_type)
339 response.content_type = str(content_type)
342
340
343 return response
341 return response
344
342
345 def _get_file_node(self, commit_id, f_path):
343 def _get_file_node(self, commit_id, f_path):
346 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
344 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
347 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
345 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
348 try:
346 try:
349 node = commit.get_node(f_path)
347 node = commit.get_node(f_path)
350 if node.is_dir():
348 if node.is_dir():
351 raise NodeError('%s path is a %s not a file'
349 raise NodeError('%s path is a %s not a file'
352 % (node, type(node)))
350 % (node, type(node)))
353 except NodeDoesNotExistError:
351 except NodeDoesNotExistError:
354 commit = EmptyCommit(
352 commit = EmptyCommit(
355 commit_id=commit_id,
353 commit_id=commit_id,
356 idx=commit.idx,
354 idx=commit.idx,
357 repo=commit.repository,
355 repo=commit.repository,
358 alias=commit.repository.alias,
356 alias=commit.repository.alias,
359 message=commit.message,
357 message=commit.message,
360 author=commit.author,
358 author=commit.author,
361 date=commit.date)
359 date=commit.date)
362 node = FileNode(f_path, '', commit=commit)
360 node = FileNode(f_path, '', commit=commit)
363 else:
361 else:
364 commit = EmptyCommit(
362 commit = EmptyCommit(
365 repo=self.rhodecode_vcs_repo,
363 repo=self.rhodecode_vcs_repo,
366 alias=self.rhodecode_vcs_repo.alias)
364 alias=self.rhodecode_vcs_repo.alias)
367 node = FileNode(f_path, '', commit=commit)
365 node = FileNode(f_path, '', commit=commit)
368 return node
366 return node
369
367
370 @LoginRequired()
368 @LoginRequired()
371 @HasRepoPermissionAnyDecorator(
369 @HasRepoPermissionAnyDecorator(
372 'repository.read', 'repository.write', 'repository.admin')
370 'repository.read', 'repository.write', 'repository.admin')
373 @view_config(
371 @view_config(
374 route_name='repo_files_diff', request_method='GET',
372 route_name='repo_files_diff', request_method='GET',
375 renderer=None)
373 renderer=None)
376 def repo_files_diff(self):
374 def repo_files_diff(self):
377 c = self.load_default_context()
375 c = self.load_default_context()
378 f_path = self._get_f_path(self.request.matchdict)
376 f_path = self._get_f_path(self.request.matchdict)
379 diff1 = self.request.GET.get('diff1', '')
377 diff1 = self.request.GET.get('diff1', '')
380 diff2 = self.request.GET.get('diff2', '')
378 diff2 = self.request.GET.get('diff2', '')
381
379
382 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
380 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
383
381
384 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
382 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
385 line_context = self.request.GET.get('context', 3)
383 line_context = self.request.GET.get('context', 3)
386
384
387 if not any((diff1, diff2)):
385 if not any((diff1, diff2)):
388 h.flash(
386 h.flash(
389 'Need query parameter "diff1" or "diff2" to generate a diff.',
387 'Need query parameter "diff1" or "diff2" to generate a diff.',
390 category='error')
388 category='error')
391 raise HTTPBadRequest()
389 raise HTTPBadRequest()
392
390
393 c.action = self.request.GET.get('diff')
391 c.action = self.request.GET.get('diff')
394 if c.action not in ['download', 'raw']:
392 if c.action not in ['download', 'raw']:
395 compare_url = h.route_path(
393 compare_url = h.route_path(
396 'repo_compare',
394 'repo_compare',
397 repo_name=self.db_repo_name,
395 repo_name=self.db_repo_name,
398 source_ref_type='rev',
396 source_ref_type='rev',
399 source_ref=diff1,
397 source_ref=diff1,
400 target_repo=self.db_repo_name,
398 target_repo=self.db_repo_name,
401 target_ref_type='rev',
399 target_ref_type='rev',
402 target_ref=diff2,
400 target_ref=diff2,
403 _query=dict(f_path=f_path))
401 _query=dict(f_path=f_path))
404 # redirect to new view if we render diff
402 # redirect to new view if we render diff
405 raise HTTPFound(compare_url)
403 raise HTTPFound(compare_url)
406
404
407 try:
405 try:
408 node1 = self._get_file_node(diff1, path1)
406 node1 = self._get_file_node(diff1, path1)
409 node2 = self._get_file_node(diff2, f_path)
407 node2 = self._get_file_node(diff2, f_path)
410 except (RepositoryError, NodeError):
408 except (RepositoryError, NodeError):
411 log.exception("Exception while trying to get node from repository")
409 log.exception("Exception while trying to get node from repository")
412 raise HTTPFound(
410 raise HTTPFound(
413 h.route_path('repo_files', repo_name=self.db_repo_name,
411 h.route_path('repo_files', repo_name=self.db_repo_name,
414 commit_id='tip', f_path=f_path))
412 commit_id='tip', f_path=f_path))
415
413
416 if all(isinstance(node.commit, EmptyCommit)
414 if all(isinstance(node.commit, EmptyCommit)
417 for node in (node1, node2)):
415 for node in (node1, node2)):
418 raise HTTPNotFound()
416 raise HTTPNotFound()
419
417
420 c.commit_1 = node1.commit
418 c.commit_1 = node1.commit
421 c.commit_2 = node2.commit
419 c.commit_2 = node2.commit
422
420
423 if c.action == 'download':
421 if c.action == 'download':
424 _diff = diffs.get_gitdiff(node1, node2,
422 _diff = diffs.get_gitdiff(node1, node2,
425 ignore_whitespace=ignore_whitespace,
423 ignore_whitespace=ignore_whitespace,
426 context=line_context)
424 context=line_context)
427 diff = diffs.DiffProcessor(_diff, format='gitdiff')
425 diff = diffs.DiffProcessor(_diff, format='gitdiff')
428
426
429 response = Response(diff.as_raw())
427 response = Response(diff.as_raw())
430 response.content_type = 'text/plain'
428 response.content_type = 'text/plain'
431 response.content_disposition = (
429 response.content_disposition = (
432 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
430 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
433 )
431 )
434 charset = self._get_default_encoding(c)
432 charset = self._get_default_encoding(c)
435 if charset:
433 if charset:
436 response.charset = charset
434 response.charset = charset
437 return response
435 return response
438
436
439 elif c.action == 'raw':
437 elif c.action == 'raw':
440 _diff = diffs.get_gitdiff(node1, node2,
438 _diff = diffs.get_gitdiff(node1, node2,
441 ignore_whitespace=ignore_whitespace,
439 ignore_whitespace=ignore_whitespace,
442 context=line_context)
440 context=line_context)
443 diff = diffs.DiffProcessor(_diff, format='gitdiff')
441 diff = diffs.DiffProcessor(_diff, format='gitdiff')
444
442
445 response = Response(diff.as_raw())
443 response = Response(diff.as_raw())
446 response.content_type = 'text/plain'
444 response.content_type = 'text/plain'
447 charset = self._get_default_encoding(c)
445 charset = self._get_default_encoding(c)
448 if charset:
446 if charset:
449 response.charset = charset
447 response.charset = charset
450 return response
448 return response
451
449
452 # in case we ever end up here
450 # in case we ever end up here
453 raise HTTPNotFound()
451 raise HTTPNotFound()
454
452
455 @LoginRequired()
453 @LoginRequired()
456 @HasRepoPermissionAnyDecorator(
454 @HasRepoPermissionAnyDecorator(
457 'repository.read', 'repository.write', 'repository.admin')
455 'repository.read', 'repository.write', 'repository.admin')
458 @view_config(
456 @view_config(
459 route_name='repo_files_diff_2way_redirect', request_method='GET',
457 route_name='repo_files_diff_2way_redirect', request_method='GET',
460 renderer=None)
458 renderer=None)
461 def repo_files_diff_2way_redirect(self):
459 def repo_files_diff_2way_redirect(self):
462 """
460 """
463 Kept only to make OLD links work
461 Kept only to make OLD links work
464 """
462 """
465 f_path = self._get_f_path(self.request.matchdict)
463 f_path = self._get_f_path(self.request.matchdict)
466 diff1 = self.request.GET.get('diff1', '')
464 diff1 = self.request.GET.get('diff1', '')
467 diff2 = self.request.GET.get('diff2', '')
465 diff2 = self.request.GET.get('diff2', '')
468
466
469 if not any((diff1, diff2)):
467 if not any((diff1, diff2)):
470 h.flash(
468 h.flash(
471 'Need query parameter "diff1" or "diff2" to generate a diff.',
469 'Need query parameter "diff1" or "diff2" to generate a diff.',
472 category='error')
470 category='error')
473 raise HTTPBadRequest()
471 raise HTTPBadRequest()
474
472
475 compare_url = h.route_path(
473 compare_url = h.route_path(
476 'repo_compare',
474 'repo_compare',
477 repo_name=self.db_repo_name,
475 repo_name=self.db_repo_name,
478 source_ref_type='rev',
476 source_ref_type='rev',
479 source_ref=diff1,
477 source_ref=diff1,
480 target_ref_type='rev',
478 target_ref_type='rev',
481 target_ref=diff2,
479 target_ref=diff2,
482 _query=dict(f_path=f_path, diffmode='sideside',
480 _query=dict(f_path=f_path, diffmode='sideside',
483 target_repo=self.db_repo_name,))
481 target_repo=self.db_repo_name,))
484 raise HTTPFound(compare_url)
482 raise HTTPFound(compare_url)
485
483
486 @LoginRequired()
484 @LoginRequired()
487 @HasRepoPermissionAnyDecorator(
485 @HasRepoPermissionAnyDecorator(
488 'repository.read', 'repository.write', 'repository.admin')
486 'repository.read', 'repository.write', 'repository.admin')
489 @view_config(
487 @view_config(
490 route_name='repo_files', request_method='GET',
488 route_name='repo_files', request_method='GET',
491 renderer=None)
489 renderer=None)
492 @view_config(
490 @view_config(
493 route_name='repo_files:default_path', request_method='GET',
491 route_name='repo_files:default_path', request_method='GET',
494 renderer=None)
492 renderer=None)
495 @view_config(
493 @view_config(
496 route_name='repo_files:default_commit', request_method='GET',
494 route_name='repo_files:default_commit', request_method='GET',
497 renderer=None)
495 renderer=None)
498 @view_config(
496 @view_config(
499 route_name='repo_files:rendered', request_method='GET',
497 route_name='repo_files:rendered', request_method='GET',
500 renderer=None)
498 renderer=None)
501 @view_config(
499 @view_config(
502 route_name='repo_files:annotated', request_method='GET',
500 route_name='repo_files:annotated', request_method='GET',
503 renderer=None)
501 renderer=None)
504 def repo_files(self):
502 def repo_files(self):
505 c = self.load_default_context()
503 c = self.load_default_context()
506
504
507 view_name = getattr(self.request.matched_route, 'name', None)
505 view_name = getattr(self.request.matched_route, 'name', None)
508
506
509 c.annotate = view_name == 'repo_files:annotated'
507 c.annotate = view_name == 'repo_files:annotated'
510 # default is false, but .rst/.md files later are auto rendered, we can
508 # default is false, but .rst/.md files later are auto rendered, we can
511 # overwrite auto rendering by setting this GET flag
509 # overwrite auto rendering by setting this GET flag
512 c.renderer = view_name == 'repo_files:rendered' or \
510 c.renderer = view_name == 'repo_files:rendered' or \
513 not self.request.GET.get('no-render', False)
511 not self.request.GET.get('no-render', False)
514
512
515 # redirect to given commit_id from form if given
513 # redirect to given commit_id from form if given
516 get_commit_id = self.request.GET.get('at_rev', None)
514 get_commit_id = self.request.GET.get('at_rev', None)
517 if get_commit_id:
515 if get_commit_id:
518 self._get_commit_or_redirect(get_commit_id)
516 self._get_commit_or_redirect(get_commit_id)
519
517
520 commit_id, f_path = self._get_commit_and_path()
518 commit_id, f_path = self._get_commit_and_path()
521 c.commit = self._get_commit_or_redirect(commit_id)
519 c.commit = self._get_commit_or_redirect(commit_id)
522 c.branch = self.request.GET.get('branch', None)
520 c.branch = self.request.GET.get('branch', None)
523 c.f_path = f_path
521 c.f_path = f_path
524
522
525 # prev link
523 # prev link
526 try:
524 try:
527 prev_commit = c.commit.prev(c.branch)
525 prev_commit = c.commit.prev(c.branch)
528 c.prev_commit = prev_commit
526 c.prev_commit = prev_commit
529 c.url_prev = h.route_path(
527 c.url_prev = h.route_path(
530 'repo_files', repo_name=self.db_repo_name,
528 'repo_files', repo_name=self.db_repo_name,
531 commit_id=prev_commit.raw_id, f_path=f_path)
529 commit_id=prev_commit.raw_id, f_path=f_path)
532 if c.branch:
530 if c.branch:
533 c.url_prev += '?branch=%s' % c.branch
531 c.url_prev += '?branch=%s' % c.branch
534 except (CommitDoesNotExistError, VCSError):
532 except (CommitDoesNotExistError, VCSError):
535 c.url_prev = '#'
533 c.url_prev = '#'
536 c.prev_commit = EmptyCommit()
534 c.prev_commit = EmptyCommit()
537
535
538 # next link
536 # next link
539 try:
537 try:
540 next_commit = c.commit.next(c.branch)
538 next_commit = c.commit.next(c.branch)
541 c.next_commit = next_commit
539 c.next_commit = next_commit
542 c.url_next = h.route_path(
540 c.url_next = h.route_path(
543 'repo_files', repo_name=self.db_repo_name,
541 'repo_files', repo_name=self.db_repo_name,
544 commit_id=next_commit.raw_id, f_path=f_path)
542 commit_id=next_commit.raw_id, f_path=f_path)
545 if c.branch:
543 if c.branch:
546 c.url_next += '?branch=%s' % c.branch
544 c.url_next += '?branch=%s' % c.branch
547 except (CommitDoesNotExistError, VCSError):
545 except (CommitDoesNotExistError, VCSError):
548 c.url_next = '#'
546 c.url_next = '#'
549 c.next_commit = EmptyCommit()
547 c.next_commit = EmptyCommit()
550
548
551 # files or dirs
549 # files or dirs
552 try:
550 try:
553 c.file = c.commit.get_node(f_path)
551 c.file = c.commit.get_node(f_path)
554 c.file_author = True
552 c.file_author = True
555 c.file_tree = ''
553 c.file_tree = ''
556
554
557 # load file content
555 # load file content
558 if c.file.is_file():
556 if c.file.is_file():
559 c.lf_node = c.file.get_largefile_node()
557 c.lf_node = c.file.get_largefile_node()
560
558
561 c.file_source_page = 'true'
559 c.file_source_page = 'true'
562 c.file_last_commit = c.file.last_commit
560 c.file_last_commit = c.file.last_commit
563 if c.file.size < c.visual.cut_off_limit_diff:
561 if c.file.size < c.visual.cut_off_limit_diff:
564 if c.annotate: # annotation has precedence over renderer
562 if c.annotate: # annotation has precedence over renderer
565 c.annotated_lines = filenode_as_annotated_lines_tokens(
563 c.annotated_lines = filenode_as_annotated_lines_tokens(
566 c.file
564 c.file
567 )
565 )
568 else:
566 else:
569 c.renderer = (
567 c.renderer = (
570 c.renderer and h.renderer_from_filename(c.file.path)
568 c.renderer and h.renderer_from_filename(c.file.path)
571 )
569 )
572 if not c.renderer:
570 if not c.renderer:
573 c.lines = filenode_as_lines_tokens(c.file)
571 c.lines = filenode_as_lines_tokens(c.file)
574
572
575 c.on_branch_head = self._is_valid_head(
573 c.on_branch_head = self._is_valid_head(
576 commit_id, self.rhodecode_vcs_repo)
574 commit_id, self.rhodecode_vcs_repo)
577
575
578 branch = c.commit.branch if (
576 branch = c.commit.branch if (
579 c.commit.branch and '/' not in c.commit.branch) else None
577 c.commit.branch and '/' not in c.commit.branch) else None
580 c.branch_or_raw_id = branch or c.commit.raw_id
578 c.branch_or_raw_id = branch or c.commit.raw_id
581 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
579 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
582
580
583 author = c.file_last_commit.author
581 author = c.file_last_commit.author
584 c.authors = [[
582 c.authors = [[
585 h.email(author),
583 h.email(author),
586 h.person(author, 'username_or_name_or_email'),
584 h.person(author, 'username_or_name_or_email'),
587 1
585 1
588 ]]
586 ]]
589
587
590 else: # load tree content at path
588 else: # load tree content at path
591 c.file_source_page = 'false'
589 c.file_source_page = 'false'
592 c.authors = []
590 c.authors = []
593 # this loads a simple tree without metadata to speed things up
591 # this loads a simple tree without metadata to speed things up
594 # later via ajax we call repo_nodetree_full and fetch whole
592 # later via ajax we call repo_nodetree_full and fetch whole
595 c.file_tree = self._get_tree_at_commit(
593 c.file_tree = self._get_tree_at_commit(
596 c, c.commit.raw_id, f_path)
594 c, c.commit.raw_id, f_path)
597
595
598 except RepositoryError as e:
596 except RepositoryError as e:
599 h.flash(safe_str(h.escape(e)), category='error')
597 h.flash(safe_str(h.escape(e)), category='error')
600 raise HTTPNotFound()
598 raise HTTPNotFound()
601
599
602 if self.request.environ.get('HTTP_X_PJAX'):
600 if self.request.environ.get('HTTP_X_PJAX'):
603 html = render('rhodecode:templates/files/files_pjax.mako',
601 html = render('rhodecode:templates/files/files_pjax.mako',
604 self._get_template_context(c), self.request)
602 self._get_template_context(c), self.request)
605 else:
603 else:
606 html = render('rhodecode:templates/files/files.mako',
604 html = render('rhodecode:templates/files/files.mako',
607 self._get_template_context(c), self.request)
605 self._get_template_context(c), self.request)
608 return Response(html)
606 return Response(html)
609
607
610 @HasRepoPermissionAnyDecorator(
608 @HasRepoPermissionAnyDecorator(
611 'repository.read', 'repository.write', 'repository.admin')
609 'repository.read', 'repository.write', 'repository.admin')
612 @view_config(
610 @view_config(
613 route_name='repo_files:annotated_previous', request_method='GET',
611 route_name='repo_files:annotated_previous', request_method='GET',
614 renderer=None)
612 renderer=None)
615 def repo_files_annotated_previous(self):
613 def repo_files_annotated_previous(self):
616 self.load_default_context()
614 self.load_default_context()
617
615
618 commit_id, f_path = self._get_commit_and_path()
616 commit_id, f_path = self._get_commit_and_path()
619 commit = self._get_commit_or_redirect(commit_id)
617 commit = self._get_commit_or_redirect(commit_id)
620 prev_commit_id = commit.raw_id
618 prev_commit_id = commit.raw_id
621 line_anchor = self.request.GET.get('line_anchor')
619 line_anchor = self.request.GET.get('line_anchor')
622 is_file = False
620 is_file = False
623 try:
621 try:
624 _file = commit.get_node(f_path)
622 _file = commit.get_node(f_path)
625 is_file = _file.is_file()
623 is_file = _file.is_file()
626 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
624 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
627 pass
625 pass
628
626
629 if is_file:
627 if is_file:
630 history = commit.get_file_history(f_path)
628 history = commit.get_file_history(f_path)
631 prev_commit_id = history[1].raw_id \
629 prev_commit_id = history[1].raw_id \
632 if len(history) > 1 else prev_commit_id
630 if len(history) > 1 else prev_commit_id
633 prev_url = h.route_path(
631 prev_url = h.route_path(
634 'repo_files:annotated', repo_name=self.db_repo_name,
632 'repo_files:annotated', repo_name=self.db_repo_name,
635 commit_id=prev_commit_id, f_path=f_path,
633 commit_id=prev_commit_id, f_path=f_path,
636 _anchor='L{}'.format(line_anchor))
634 _anchor='L{}'.format(line_anchor))
637
635
638 raise HTTPFound(prev_url)
636 raise HTTPFound(prev_url)
639
637
640 @LoginRequired()
638 @LoginRequired()
641 @HasRepoPermissionAnyDecorator(
639 @HasRepoPermissionAnyDecorator(
642 'repository.read', 'repository.write', 'repository.admin')
640 'repository.read', 'repository.write', 'repository.admin')
643 @view_config(
641 @view_config(
644 route_name='repo_nodetree_full', request_method='GET',
642 route_name='repo_nodetree_full', request_method='GET',
645 renderer=None, xhr=True)
643 renderer=None, xhr=True)
646 @view_config(
644 @view_config(
647 route_name='repo_nodetree_full:default_path', request_method='GET',
645 route_name='repo_nodetree_full:default_path', request_method='GET',
648 renderer=None, xhr=True)
646 renderer=None, xhr=True)
649 def repo_nodetree_full(self):
647 def repo_nodetree_full(self):
650 """
648 """
651 Returns rendered html of file tree that contains commit date,
649 Returns rendered html of file tree that contains commit date,
652 author, commit_id for the specified combination of
650 author, commit_id for the specified combination of
653 repo, commit_id and file path
651 repo, commit_id and file path
654 """
652 """
655 c = self.load_default_context()
653 c = self.load_default_context()
656
654
657 commit_id, f_path = self._get_commit_and_path()
655 commit_id, f_path = self._get_commit_and_path()
658 commit = self._get_commit_or_redirect(commit_id)
656 commit = self._get_commit_or_redirect(commit_id)
659 try:
657 try:
660 dir_node = commit.get_node(f_path)
658 dir_node = commit.get_node(f_path)
661 except RepositoryError as e:
659 except RepositoryError as e:
662 return Response('error: {}'.format(safe_str(e)))
660 return Response('error: {}'.format(safe_str(e)))
663
661
664 if dir_node.is_file():
662 if dir_node.is_file():
665 return Response('')
663 return Response('')
666
664
667 c.file = dir_node
665 c.file = dir_node
668 c.commit = commit
666 c.commit = commit
669
667
670 # using force=True here, make a little trick. We flush the cache and
668 # using force=True here, make a little trick. We flush the cache and
671 # compute it using the same key as without previous full_load, so now
669 # compute it using the same key as without previous full_load, so now
672 # the fully loaded tree is now returned instead of partial,
670 # the fully loaded tree is now returned instead of partial,
673 # and we store this in caches
671 # and we store this in caches
674 html = self._get_tree_at_commit(
672 html = self._get_tree_at_commit(
675 c, commit.raw_id, dir_node.path, full_load=True, force=True)
673 c, commit.raw_id, dir_node.path, full_load=True, force=True)
676
674
677 return Response(html)
675 return Response(html)
678
676
679 def _get_attachement_disposition(self, f_path):
677 def _get_attachement_disposition(self, f_path):
680 return 'attachment; filename=%s' % \
678 return 'attachment; filename=%s' % \
681 safe_str(f_path.split(Repository.NAME_SEP)[-1])
679 safe_str(f_path.split(Repository.NAME_SEP)[-1])
682
680
683 @LoginRequired()
681 @LoginRequired()
684 @HasRepoPermissionAnyDecorator(
682 @HasRepoPermissionAnyDecorator(
685 'repository.read', 'repository.write', 'repository.admin')
683 'repository.read', 'repository.write', 'repository.admin')
686 @view_config(
684 @view_config(
687 route_name='repo_file_raw', request_method='GET',
685 route_name='repo_file_raw', request_method='GET',
688 renderer=None)
686 renderer=None)
689 def repo_file_raw(self):
687 def repo_file_raw(self):
690 """
688 """
691 Action for show as raw, some mimetypes are "rendered",
689 Action for show as raw, some mimetypes are "rendered",
692 those include images, icons.
690 those include images, icons.
693 """
691 """
694 c = self.load_default_context()
692 c = self.load_default_context()
695
693
696 commit_id, f_path = self._get_commit_and_path()
694 commit_id, f_path = self._get_commit_and_path()
697 commit = self._get_commit_or_redirect(commit_id)
695 commit = self._get_commit_or_redirect(commit_id)
698 file_node = self._get_filenode_or_redirect(commit, f_path)
696 file_node = self._get_filenode_or_redirect(commit, f_path)
699
697
700 raw_mimetype_mapping = {
698 raw_mimetype_mapping = {
701 # map original mimetype to a mimetype used for "show as raw"
699 # map original mimetype to a mimetype used for "show as raw"
702 # you can also provide a content-disposition to override the
700 # you can also provide a content-disposition to override the
703 # default "attachment" disposition.
701 # default "attachment" disposition.
704 # orig_type: (new_type, new_dispo)
702 # orig_type: (new_type, new_dispo)
705
703
706 # show images inline:
704 # show images inline:
707 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
705 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
708 # for example render an SVG with javascript inside or even render
706 # for example render an SVG with javascript inside or even render
709 # HTML.
707 # HTML.
710 'image/x-icon': ('image/x-icon', 'inline'),
708 'image/x-icon': ('image/x-icon', 'inline'),
711 'image/png': ('image/png', 'inline'),
709 'image/png': ('image/png', 'inline'),
712 'image/gif': ('image/gif', 'inline'),
710 'image/gif': ('image/gif', 'inline'),
713 'image/jpeg': ('image/jpeg', 'inline'),
711 'image/jpeg': ('image/jpeg', 'inline'),
714 'application/pdf': ('application/pdf', 'inline'),
712 'application/pdf': ('application/pdf', 'inline'),
715 }
713 }
716
714
717 mimetype = file_node.mimetype
715 mimetype = file_node.mimetype
718 try:
716 try:
719 mimetype, disposition = raw_mimetype_mapping[mimetype]
717 mimetype, disposition = raw_mimetype_mapping[mimetype]
720 except KeyError:
718 except KeyError:
721 # we don't know anything special about this, handle it safely
719 # we don't know anything special about this, handle it safely
722 if file_node.is_binary:
720 if file_node.is_binary:
723 # do same as download raw for binary files
721 # do same as download raw for binary files
724 mimetype, disposition = 'application/octet-stream', 'attachment'
722 mimetype, disposition = 'application/octet-stream', 'attachment'
725 else:
723 else:
726 # do not just use the original mimetype, but force text/plain,
724 # do not just use the original mimetype, but force text/plain,
727 # otherwise it would serve text/html and that might be unsafe.
725 # otherwise it would serve text/html and that might be unsafe.
728 # Note: underlying vcs library fakes text/plain mimetype if the
726 # Note: underlying vcs library fakes text/plain mimetype if the
729 # mimetype can not be determined and it thinks it is not
727 # mimetype can not be determined and it thinks it is not
730 # binary.This might lead to erroneous text display in some
728 # binary.This might lead to erroneous text display in some
731 # cases, but helps in other cases, like with text files
729 # cases, but helps in other cases, like with text files
732 # without extension.
730 # without extension.
733 mimetype, disposition = 'text/plain', 'inline'
731 mimetype, disposition = 'text/plain', 'inline'
734
732
735 if disposition == 'attachment':
733 if disposition == 'attachment':
736 disposition = self._get_attachement_disposition(f_path)
734 disposition = self._get_attachement_disposition(f_path)
737
735
738 def stream_node():
736 def stream_node():
739 yield file_node.raw_bytes
737 yield file_node.raw_bytes
740
738
741 response = Response(app_iter=stream_node())
739 response = Response(app_iter=stream_node())
742 response.content_disposition = disposition
740 response.content_disposition = disposition
743 response.content_type = mimetype
741 response.content_type = mimetype
744
742
745 charset = self._get_default_encoding(c)
743 charset = self._get_default_encoding(c)
746 if charset:
744 if charset:
747 response.charset = charset
745 response.charset = charset
748
746
749 return response
747 return response
750
748
751 @LoginRequired()
749 @LoginRequired()
752 @HasRepoPermissionAnyDecorator(
750 @HasRepoPermissionAnyDecorator(
753 'repository.read', 'repository.write', 'repository.admin')
751 'repository.read', 'repository.write', 'repository.admin')
754 @view_config(
752 @view_config(
755 route_name='repo_file_download', request_method='GET',
753 route_name='repo_file_download', request_method='GET',
756 renderer=None)
754 renderer=None)
757 @view_config(
755 @view_config(
758 route_name='repo_file_download:legacy', request_method='GET',
756 route_name='repo_file_download:legacy', request_method='GET',
759 renderer=None)
757 renderer=None)
760 def repo_file_download(self):
758 def repo_file_download(self):
761 c = self.load_default_context()
759 c = self.load_default_context()
762
760
763 commit_id, f_path = self._get_commit_and_path()
761 commit_id, f_path = self._get_commit_and_path()
764 commit = self._get_commit_or_redirect(commit_id)
762 commit = self._get_commit_or_redirect(commit_id)
765 file_node = self._get_filenode_or_redirect(commit, f_path)
763 file_node = self._get_filenode_or_redirect(commit, f_path)
766
764
767 if self.request.GET.get('lf'):
765 if self.request.GET.get('lf'):
768 # only if lf get flag is passed, we download this file
766 # only if lf get flag is passed, we download this file
769 # as LFS/Largefile
767 # as LFS/Largefile
770 lf_node = file_node.get_largefile_node()
768 lf_node = file_node.get_largefile_node()
771 if lf_node:
769 if lf_node:
772 # overwrite our pointer with the REAL large-file
770 # overwrite our pointer with the REAL large-file
773 file_node = lf_node
771 file_node = lf_node
774
772
775 disposition = self._get_attachement_disposition(f_path)
773 disposition = self._get_attachement_disposition(f_path)
776
774
777 def stream_node():
775 def stream_node():
778 yield file_node.raw_bytes
776 yield file_node.raw_bytes
779
777
780 response = Response(app_iter=stream_node())
778 response = Response(app_iter=stream_node())
781 response.content_disposition = disposition
779 response.content_disposition = disposition
782 response.content_type = file_node.mimetype
780 response.content_type = file_node.mimetype
783
781
784 charset = self._get_default_encoding(c)
782 charset = self._get_default_encoding(c)
785 if charset:
783 if charset:
786 response.charset = charset
784 response.charset = charset
787
785
788 return response
786 return response
789
787
790 def _get_nodelist_at_commit(self, repo_name, commit_id, f_path):
788 def _get_nodelist_at_commit(self, repo_name, commit_id, f_path):
791 def _cached_nodes():
789 def _cached_nodes():
792 log.debug('Generating cached nodelist for %s, %s, %s',
790 log.debug('Generating cached nodelist for %s, %s, %s',
793 repo_name, commit_id, f_path)
791 repo_name, commit_id, f_path)
794 _d, _f = ScmModel().get_nodes(
792 _d, _f = ScmModel().get_nodes(
795 repo_name, commit_id, f_path, flat=False)
793 repo_name, commit_id, f_path, flat=False)
796 return _d + _f
794 return _d + _f
797
795
798 cache_manager = self._get_tree_cache_manager(caches.FILE_SEARCH_TREE_META)
796 cache_manager = self._get_tree_cache_manager(caches.FILE_SEARCH_TREE_META)
799
797
800 cache_key = caches.compute_key_from_params(
798 cache_key = caches.compute_key_from_params(
801 repo_name, commit_id, f_path)
799 repo_name, commit_id, f_path)
802 return cache_manager.get(cache_key, createfunc=_cached_nodes)
800 return cache_manager.get(cache_key, createfunc=_cached_nodes)
803
801
804 @LoginRequired()
802 @LoginRequired()
805 @HasRepoPermissionAnyDecorator(
803 @HasRepoPermissionAnyDecorator(
806 'repository.read', 'repository.write', 'repository.admin')
804 'repository.read', 'repository.write', 'repository.admin')
807 @view_config(
805 @view_config(
808 route_name='repo_files_nodelist', request_method='GET',
806 route_name='repo_files_nodelist', request_method='GET',
809 renderer='json_ext', xhr=True)
807 renderer='json_ext', xhr=True)
810 def repo_nodelist(self):
808 def repo_nodelist(self):
811 self.load_default_context()
809 self.load_default_context()
812
810
813 commit_id, f_path = self._get_commit_and_path()
811 commit_id, f_path = self._get_commit_and_path()
814 commit = self._get_commit_or_redirect(commit_id)
812 commit = self._get_commit_or_redirect(commit_id)
815
813
816 metadata = self._get_nodelist_at_commit(
814 metadata = self._get_nodelist_at_commit(
817 self.db_repo_name, commit.raw_id, f_path)
815 self.db_repo_name, commit.raw_id, f_path)
818 return {'nodes': metadata}
816 return {'nodes': metadata}
819
817
820 def _create_references(
818 def _create_references(
821 self, branches_or_tags, symbolic_reference, f_path):
819 self, branches_or_tags, symbolic_reference, f_path):
822 items = []
820 items = []
823 for name, commit_id in branches_or_tags.items():
821 for name, commit_id in branches_or_tags.items():
824 sym_ref = symbolic_reference(commit_id, name, f_path)
822 sym_ref = symbolic_reference(commit_id, name, f_path)
825 items.append((sym_ref, name))
823 items.append((sym_ref, name))
826 return items
824 return items
827
825
828 def _symbolic_reference(self, commit_id, name, f_path):
826 def _symbolic_reference(self, commit_id, name, f_path):
829 return commit_id
827 return commit_id
830
828
831 def _symbolic_reference_svn(self, commit_id, name, f_path):
829 def _symbolic_reference_svn(self, commit_id, name, f_path):
832 new_f_path = vcspath.join(name, f_path)
830 new_f_path = vcspath.join(name, f_path)
833 return u'%s@%s' % (new_f_path, commit_id)
831 return u'%s@%s' % (new_f_path, commit_id)
834
832
835 def _get_node_history(self, commit_obj, f_path, commits=None):
833 def _get_node_history(self, commit_obj, f_path, commits=None):
836 """
834 """
837 get commit history for given node
835 get commit history for given node
838
836
839 :param commit_obj: commit to calculate history
837 :param commit_obj: commit to calculate history
840 :param f_path: path for node to calculate history for
838 :param f_path: path for node to calculate history for
841 :param commits: if passed don't calculate history and take
839 :param commits: if passed don't calculate history and take
842 commits defined in this list
840 commits defined in this list
843 """
841 """
844 _ = self.request.translate
842 _ = self.request.translate
845
843
846 # calculate history based on tip
844 # calculate history based on tip
847 tip = self.rhodecode_vcs_repo.get_commit()
845 tip = self.rhodecode_vcs_repo.get_commit()
848 if commits is None:
846 if commits is None:
849 pre_load = ["author", "branch"]
847 pre_load = ["author", "branch"]
850 try:
848 try:
851 commits = tip.get_file_history(f_path, pre_load=pre_load)
849 commits = tip.get_file_history(f_path, pre_load=pre_load)
852 except (NodeDoesNotExistError, CommitError):
850 except (NodeDoesNotExistError, CommitError):
853 # this node is not present at tip!
851 # this node is not present at tip!
854 commits = commit_obj.get_file_history(f_path, pre_load=pre_load)
852 commits = commit_obj.get_file_history(f_path, pre_load=pre_load)
855
853
856 history = []
854 history = []
857 commits_group = ([], _("Changesets"))
855 commits_group = ([], _("Changesets"))
858 for commit in commits:
856 for commit in commits:
859 branch = ' (%s)' % commit.branch if commit.branch else ''
857 branch = ' (%s)' % commit.branch if commit.branch else ''
860 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
858 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
861 commits_group[0].append((commit.raw_id, n_desc,))
859 commits_group[0].append((commit.raw_id, n_desc,))
862 history.append(commits_group)
860 history.append(commits_group)
863
861
864 symbolic_reference = self._symbolic_reference
862 symbolic_reference = self._symbolic_reference
865
863
866 if self.rhodecode_vcs_repo.alias == 'svn':
864 if self.rhodecode_vcs_repo.alias == 'svn':
867 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
865 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
868 f_path, self.rhodecode_vcs_repo)
866 f_path, self.rhodecode_vcs_repo)
869 if adjusted_f_path != f_path:
867 if adjusted_f_path != f_path:
870 log.debug(
868 log.debug(
871 'Recognized svn tag or branch in file "%s", using svn '
869 'Recognized svn tag or branch in file "%s", using svn '
872 'specific symbolic references', f_path)
870 'specific symbolic references', f_path)
873 f_path = adjusted_f_path
871 f_path = adjusted_f_path
874 symbolic_reference = self._symbolic_reference_svn
872 symbolic_reference = self._symbolic_reference_svn
875
873
876 branches = self._create_references(
874 branches = self._create_references(
877 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path)
875 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path)
878 branches_group = (branches, _("Branches"))
876 branches_group = (branches, _("Branches"))
879
877
880 tags = self._create_references(
878 tags = self._create_references(
881 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path)
879 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path)
882 tags_group = (tags, _("Tags"))
880 tags_group = (tags, _("Tags"))
883
881
884 history.append(branches_group)
882 history.append(branches_group)
885 history.append(tags_group)
883 history.append(tags_group)
886
884
887 return history, commits
885 return history, commits
888
886
889 @LoginRequired()
887 @LoginRequired()
890 @HasRepoPermissionAnyDecorator(
888 @HasRepoPermissionAnyDecorator(
891 'repository.read', 'repository.write', 'repository.admin')
889 'repository.read', 'repository.write', 'repository.admin')
892 @view_config(
890 @view_config(
893 route_name='repo_file_history', request_method='GET',
891 route_name='repo_file_history', request_method='GET',
894 renderer='json_ext')
892 renderer='json_ext')
895 def repo_file_history(self):
893 def repo_file_history(self):
896 self.load_default_context()
894 self.load_default_context()
897
895
898 commit_id, f_path = self._get_commit_and_path()
896 commit_id, f_path = self._get_commit_and_path()
899 commit = self._get_commit_or_redirect(commit_id)
897 commit = self._get_commit_or_redirect(commit_id)
900 file_node = self._get_filenode_or_redirect(commit, f_path)
898 file_node = self._get_filenode_or_redirect(commit, f_path)
901
899
902 if file_node.is_file():
900 if file_node.is_file():
903 file_history, _hist = self._get_node_history(commit, f_path)
901 file_history, _hist = self._get_node_history(commit, f_path)
904
902
905 res = []
903 res = []
906 for obj in file_history:
904 for obj in file_history:
907 res.append({
905 res.append({
908 'text': obj[1],
906 'text': obj[1],
909 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
907 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
910 })
908 })
911
909
912 data = {
910 data = {
913 'more': False,
911 'more': False,
914 'results': res
912 'results': res
915 }
913 }
916 return data
914 return data
917
915
918 log.warning('Cannot fetch history for directory')
916 log.warning('Cannot fetch history for directory')
919 raise HTTPBadRequest()
917 raise HTTPBadRequest()
920
918
921 @LoginRequired()
919 @LoginRequired()
922 @HasRepoPermissionAnyDecorator(
920 @HasRepoPermissionAnyDecorator(
923 'repository.read', 'repository.write', 'repository.admin')
921 'repository.read', 'repository.write', 'repository.admin')
924 @view_config(
922 @view_config(
925 route_name='repo_file_authors', request_method='GET',
923 route_name='repo_file_authors', request_method='GET',
926 renderer='rhodecode:templates/files/file_authors_box.mako')
924 renderer='rhodecode:templates/files/file_authors_box.mako')
927 def repo_file_authors(self):
925 def repo_file_authors(self):
928 c = self.load_default_context()
926 c = self.load_default_context()
929
927
930 commit_id, f_path = self._get_commit_and_path()
928 commit_id, f_path = self._get_commit_and_path()
931 commit = self._get_commit_or_redirect(commit_id)
929 commit = self._get_commit_or_redirect(commit_id)
932 file_node = self._get_filenode_or_redirect(commit, f_path)
930 file_node = self._get_filenode_or_redirect(commit, f_path)
933
931
934 if not file_node.is_file():
932 if not file_node.is_file():
935 raise HTTPBadRequest()
933 raise HTTPBadRequest()
936
934
937 c.file_last_commit = file_node.last_commit
935 c.file_last_commit = file_node.last_commit
938 if self.request.GET.get('annotate') == '1':
936 if self.request.GET.get('annotate') == '1':
939 # use _hist from annotation if annotation mode is on
937 # use _hist from annotation if annotation mode is on
940 commit_ids = set(x[1] for x in file_node.annotate)
938 commit_ids = set(x[1] for x in file_node.annotate)
941 _hist = (
939 _hist = (
942 self.rhodecode_vcs_repo.get_commit(commit_id)
940 self.rhodecode_vcs_repo.get_commit(commit_id)
943 for commit_id in commit_ids)
941 for commit_id in commit_ids)
944 else:
942 else:
945 _f_history, _hist = self._get_node_history(commit, f_path)
943 _f_history, _hist = self._get_node_history(commit, f_path)
946 c.file_author = False
944 c.file_author = False
947
945
948 unique = collections.OrderedDict()
946 unique = collections.OrderedDict()
949 for commit in _hist:
947 for commit in _hist:
950 author = commit.author
948 author = commit.author
951 if author not in unique:
949 if author not in unique:
952 unique[commit.author] = [
950 unique[commit.author] = [
953 h.email(author),
951 h.email(author),
954 h.person(author, 'username_or_name_or_email'),
952 h.person(author, 'username_or_name_or_email'),
955 1 # counter
953 1 # counter
956 ]
954 ]
957
955
958 else:
956 else:
959 # increase counter
957 # increase counter
960 unique[commit.author][2] += 1
958 unique[commit.author][2] += 1
961
959
962 c.authors = [val for val in unique.values()]
960 c.authors = [val for val in unique.values()]
963
961
964 return self._get_template_context(c)
962 return self._get_template_context(c)
965
963
966 @LoginRequired()
964 @LoginRequired()
967 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
965 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
968 @view_config(
966 @view_config(
969 route_name='repo_files_remove_file', request_method='GET',
967 route_name='repo_files_remove_file', request_method='GET',
970 renderer='rhodecode:templates/files/files_delete.mako')
968 renderer='rhodecode:templates/files/files_delete.mako')
971 def repo_files_remove_file(self):
969 def repo_files_remove_file(self):
972 _ = self.request.translate
970 _ = self.request.translate
973 c = self.load_default_context()
971 c = self.load_default_context()
974 commit_id, f_path = self._get_commit_and_path()
972 commit_id, f_path = self._get_commit_and_path()
975
973
976 self._ensure_not_locked()
974 self._ensure_not_locked()
977
975
978 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
976 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
979 h.flash(_('You can only delete files with commit '
977 h.flash(_('You can only delete files with commit '
980 'being a valid branch '), category='warning')
978 'being a valid branch '), category='warning')
981 raise HTTPFound(
979 raise HTTPFound(
982 h.route_path('repo_files',
980 h.route_path('repo_files',
983 repo_name=self.db_repo_name, commit_id='tip',
981 repo_name=self.db_repo_name, commit_id='tip',
984 f_path=f_path))
982 f_path=f_path))
985
983
986 c.commit = self._get_commit_or_redirect(commit_id)
984 c.commit = self._get_commit_or_redirect(commit_id)
987 c.file = self._get_filenode_or_redirect(c.commit, f_path)
985 c.file = self._get_filenode_or_redirect(c.commit, f_path)
988
986
989 c.default_message = _(
987 c.default_message = _(
990 'Deleted file {} via RhodeCode Enterprise').format(f_path)
988 'Deleted file {} via RhodeCode Enterprise').format(f_path)
991 c.f_path = f_path
989 c.f_path = f_path
992
990
993 return self._get_template_context(c)
991 return self._get_template_context(c)
994
992
995 @LoginRequired()
993 @LoginRequired()
996 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
994 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
997 @CSRFRequired()
995 @CSRFRequired()
998 @view_config(
996 @view_config(
999 route_name='repo_files_delete_file', request_method='POST',
997 route_name='repo_files_delete_file', request_method='POST',
1000 renderer=None)
998 renderer=None)
1001 def repo_files_delete_file(self):
999 def repo_files_delete_file(self):
1002 _ = self.request.translate
1000 _ = self.request.translate
1003
1001
1004 c = self.load_default_context()
1002 c = self.load_default_context()
1005 commit_id, f_path = self._get_commit_and_path()
1003 commit_id, f_path = self._get_commit_and_path()
1006
1004
1007 self._ensure_not_locked()
1005 self._ensure_not_locked()
1008
1006
1009 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1007 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1010 h.flash(_('You can only delete files with commit '
1008 h.flash(_('You can only delete files with commit '
1011 'being a valid branch '), category='warning')
1009 'being a valid branch '), category='warning')
1012 raise HTTPFound(
1010 raise HTTPFound(
1013 h.route_path('repo_files',
1011 h.route_path('repo_files',
1014 repo_name=self.db_repo_name, commit_id='tip',
1012 repo_name=self.db_repo_name, commit_id='tip',
1015 f_path=f_path))
1013 f_path=f_path))
1016
1014
1017 c.commit = self._get_commit_or_redirect(commit_id)
1015 c.commit = self._get_commit_or_redirect(commit_id)
1018 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1016 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1019
1017
1020 c.default_message = _(
1018 c.default_message = _(
1021 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1019 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1022 c.f_path = f_path
1020 c.f_path = f_path
1023 node_path = f_path
1021 node_path = f_path
1024 author = self._rhodecode_db_user.full_contact
1022 author = self._rhodecode_db_user.full_contact
1025 message = self.request.POST.get('message') or c.default_message
1023 message = self.request.POST.get('message') or c.default_message
1026 try:
1024 try:
1027 nodes = {
1025 nodes = {
1028 node_path: {
1026 node_path: {
1029 'content': ''
1027 'content': ''
1030 }
1028 }
1031 }
1029 }
1032 ScmModel().delete_nodes(
1030 ScmModel().delete_nodes(
1033 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1031 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1034 message=message,
1032 message=message,
1035 nodes=nodes,
1033 nodes=nodes,
1036 parent_commit=c.commit,
1034 parent_commit=c.commit,
1037 author=author,
1035 author=author,
1038 )
1036 )
1039
1037
1040 h.flash(
1038 h.flash(
1041 _('Successfully deleted file `{}`').format(
1039 _('Successfully deleted file `{}`').format(
1042 h.escape(f_path)), category='success')
1040 h.escape(f_path)), category='success')
1043 except Exception:
1041 except Exception:
1044 log.exception('Error during commit operation')
1042 log.exception('Error during commit operation')
1045 h.flash(_('Error occurred during commit'), category='error')
1043 h.flash(_('Error occurred during commit'), category='error')
1046 raise HTTPFound(
1044 raise HTTPFound(
1047 h.route_path('repo_commit', repo_name=self.db_repo_name,
1045 h.route_path('repo_commit', repo_name=self.db_repo_name,
1048 commit_id='tip'))
1046 commit_id='tip'))
1049
1047
1050 @LoginRequired()
1048 @LoginRequired()
1051 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1049 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1052 @view_config(
1050 @view_config(
1053 route_name='repo_files_edit_file', request_method='GET',
1051 route_name='repo_files_edit_file', request_method='GET',
1054 renderer='rhodecode:templates/files/files_edit.mako')
1052 renderer='rhodecode:templates/files/files_edit.mako')
1055 def repo_files_edit_file(self):
1053 def repo_files_edit_file(self):
1056 _ = self.request.translate
1054 _ = self.request.translate
1057 c = self.load_default_context()
1055 c = self.load_default_context()
1058 commit_id, f_path = self._get_commit_and_path()
1056 commit_id, f_path = self._get_commit_and_path()
1059
1057
1060 self._ensure_not_locked()
1058 self._ensure_not_locked()
1061
1059
1062 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1060 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1063 h.flash(_('You can only edit files with commit '
1061 h.flash(_('You can only edit files with commit '
1064 'being a valid branch '), category='warning')
1062 'being a valid branch '), category='warning')
1065 raise HTTPFound(
1063 raise HTTPFound(
1066 h.route_path('repo_files',
1064 h.route_path('repo_files',
1067 repo_name=self.db_repo_name, commit_id='tip',
1065 repo_name=self.db_repo_name, commit_id='tip',
1068 f_path=f_path))
1066 f_path=f_path))
1069
1067
1070 c.commit = self._get_commit_or_redirect(commit_id)
1068 c.commit = self._get_commit_or_redirect(commit_id)
1071 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1069 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1072
1070
1073 if c.file.is_binary:
1071 if c.file.is_binary:
1074 files_url = h.route_path(
1072 files_url = h.route_path(
1075 'repo_files',
1073 'repo_files',
1076 repo_name=self.db_repo_name,
1074 repo_name=self.db_repo_name,
1077 commit_id=c.commit.raw_id, f_path=f_path)
1075 commit_id=c.commit.raw_id, f_path=f_path)
1078 raise HTTPFound(files_url)
1076 raise HTTPFound(files_url)
1079
1077
1080 c.default_message = _(
1078 c.default_message = _(
1081 'Edited file {} via RhodeCode Enterprise').format(f_path)
1079 'Edited file {} via RhodeCode Enterprise').format(f_path)
1082 c.f_path = f_path
1080 c.f_path = f_path
1083
1081
1084 return self._get_template_context(c)
1082 return self._get_template_context(c)
1085
1083
1086 @LoginRequired()
1084 @LoginRequired()
1087 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1085 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1088 @CSRFRequired()
1086 @CSRFRequired()
1089 @view_config(
1087 @view_config(
1090 route_name='repo_files_update_file', request_method='POST',
1088 route_name='repo_files_update_file', request_method='POST',
1091 renderer=None)
1089 renderer=None)
1092 def repo_files_update_file(self):
1090 def repo_files_update_file(self):
1093 _ = self.request.translate
1091 _ = self.request.translate
1094 c = self.load_default_context()
1092 c = self.load_default_context()
1095 commit_id, f_path = self._get_commit_and_path()
1093 commit_id, f_path = self._get_commit_and_path()
1096
1094
1097 self._ensure_not_locked()
1095 self._ensure_not_locked()
1098
1096
1099 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1097 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1100 h.flash(_('You can only edit files with commit '
1098 h.flash(_('You can only edit files with commit '
1101 'being a valid branch '), category='warning')
1099 'being a valid branch '), category='warning')
1102 raise HTTPFound(
1100 raise HTTPFound(
1103 h.route_path('repo_files',
1101 h.route_path('repo_files',
1104 repo_name=self.db_repo_name, commit_id='tip',
1102 repo_name=self.db_repo_name, commit_id='tip',
1105 f_path=f_path))
1103 f_path=f_path))
1106
1104
1107 c.commit = self._get_commit_or_redirect(commit_id)
1105 c.commit = self._get_commit_or_redirect(commit_id)
1108 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1106 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1109
1107
1110 if c.file.is_binary:
1108 if c.file.is_binary:
1111 raise HTTPFound(
1109 raise HTTPFound(
1112 h.route_path('repo_files',
1110 h.route_path('repo_files',
1113 repo_name=self.db_repo_name,
1111 repo_name=self.db_repo_name,
1114 commit_id=c.commit.raw_id,
1112 commit_id=c.commit.raw_id,
1115 f_path=f_path))
1113 f_path=f_path))
1116
1114
1117 c.default_message = _(
1115 c.default_message = _(
1118 'Edited file {} via RhodeCode Enterprise').format(f_path)
1116 'Edited file {} via RhodeCode Enterprise').format(f_path)
1119 c.f_path = f_path
1117 c.f_path = f_path
1120 old_content = c.file.content
1118 old_content = c.file.content
1121 sl = old_content.splitlines(1)
1119 sl = old_content.splitlines(1)
1122 first_line = sl[0] if sl else ''
1120 first_line = sl[0] if sl else ''
1123
1121
1124 r_post = self.request.POST
1122 r_post = self.request.POST
1125 # modes: 0 - Unix, 1 - Mac, 2 - DOS
1123 # modes: 0 - Unix, 1 - Mac, 2 - DOS
1126 mode = detect_mode(first_line, 0)
1124 mode = detect_mode(first_line, 0)
1127 content = convert_line_endings(r_post.get('content', ''), mode)
1125 content = convert_line_endings(r_post.get('content', ''), mode)
1128
1126
1129 message = r_post.get('message') or c.default_message
1127 message = r_post.get('message') or c.default_message
1130 org_f_path = c.file.unicode_path
1128 org_f_path = c.file.unicode_path
1131 filename = r_post['filename']
1129 filename = r_post['filename']
1132 org_filename = c.file.name
1130 org_filename = c.file.name
1133
1131
1134 if content == old_content and filename == org_filename:
1132 if content == old_content and filename == org_filename:
1135 h.flash(_('No changes'), category='warning')
1133 h.flash(_('No changes'), category='warning')
1136 raise HTTPFound(
1134 raise HTTPFound(
1137 h.route_path('repo_commit', repo_name=self.db_repo_name,
1135 h.route_path('repo_commit', repo_name=self.db_repo_name,
1138 commit_id='tip'))
1136 commit_id='tip'))
1139 try:
1137 try:
1140 mapping = {
1138 mapping = {
1141 org_f_path: {
1139 org_f_path: {
1142 'org_filename': org_f_path,
1140 'org_filename': org_f_path,
1143 'filename': os.path.join(c.file.dir_path, filename),
1141 'filename': os.path.join(c.file.dir_path, filename),
1144 'content': content,
1142 'content': content,
1145 'lexer': '',
1143 'lexer': '',
1146 'op': 'mod',
1144 'op': 'mod',
1147 }
1145 }
1148 }
1146 }
1149
1147
1150 ScmModel().update_nodes(
1148 ScmModel().update_nodes(
1151 user=self._rhodecode_db_user.user_id,
1149 user=self._rhodecode_db_user.user_id,
1152 repo=self.db_repo,
1150 repo=self.db_repo,
1153 message=message,
1151 message=message,
1154 nodes=mapping,
1152 nodes=mapping,
1155 parent_commit=c.commit,
1153 parent_commit=c.commit,
1156 )
1154 )
1157
1155
1158 h.flash(
1156 h.flash(
1159 _('Successfully committed changes to file `{}`').format(
1157 _('Successfully committed changes to file `{}`').format(
1160 h.escape(f_path)), category='success')
1158 h.escape(f_path)), category='success')
1161 except Exception:
1159 except Exception:
1162 log.exception('Error occurred during commit')
1160 log.exception('Error occurred during commit')
1163 h.flash(_('Error occurred during commit'), category='error')
1161 h.flash(_('Error occurred during commit'), category='error')
1164 raise HTTPFound(
1162 raise HTTPFound(
1165 h.route_path('repo_commit', repo_name=self.db_repo_name,
1163 h.route_path('repo_commit', repo_name=self.db_repo_name,
1166 commit_id='tip'))
1164 commit_id='tip'))
1167
1165
1168 @LoginRequired()
1166 @LoginRequired()
1169 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1167 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1170 @view_config(
1168 @view_config(
1171 route_name='repo_files_add_file', request_method='GET',
1169 route_name='repo_files_add_file', request_method='GET',
1172 renderer='rhodecode:templates/files/files_add.mako')
1170 renderer='rhodecode:templates/files/files_add.mako')
1173 def repo_files_add_file(self):
1171 def repo_files_add_file(self):
1174 _ = self.request.translate
1172 _ = self.request.translate
1175 c = self.load_default_context()
1173 c = self.load_default_context()
1176 commit_id, f_path = self._get_commit_and_path()
1174 commit_id, f_path = self._get_commit_and_path()
1177
1175
1178 self._ensure_not_locked()
1176 self._ensure_not_locked()
1179
1177
1180 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1178 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1181 if c.commit is None:
1179 if c.commit is None:
1182 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1180 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1183 c.default_message = (_('Added file via RhodeCode Enterprise'))
1181 c.default_message = (_('Added file via RhodeCode Enterprise'))
1184 c.f_path = f_path.lstrip('/') # ensure not relative path
1182 c.f_path = f_path.lstrip('/') # ensure not relative path
1185
1183
1186 return self._get_template_context(c)
1184 return self._get_template_context(c)
1187
1185
1188 @LoginRequired()
1186 @LoginRequired()
1189 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1187 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1190 @CSRFRequired()
1188 @CSRFRequired()
1191 @view_config(
1189 @view_config(
1192 route_name='repo_files_create_file', request_method='POST',
1190 route_name='repo_files_create_file', request_method='POST',
1193 renderer=None)
1191 renderer=None)
1194 def repo_files_create_file(self):
1192 def repo_files_create_file(self):
1195 _ = self.request.translate
1193 _ = self.request.translate
1196 c = self.load_default_context()
1194 c = self.load_default_context()
1197 commit_id, f_path = self._get_commit_and_path()
1195 commit_id, f_path = self._get_commit_and_path()
1198
1196
1199 self._ensure_not_locked()
1197 self._ensure_not_locked()
1200
1198
1201 r_post = self.request.POST
1199 r_post = self.request.POST
1202
1200
1203 c.commit = self._get_commit_or_redirect(
1201 c.commit = self._get_commit_or_redirect(
1204 commit_id, redirect_after=False)
1202 commit_id, redirect_after=False)
1205 if c.commit is None:
1203 if c.commit is None:
1206 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1204 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1207 c.default_message = (_('Added file via RhodeCode Enterprise'))
1205 c.default_message = (_('Added file via RhodeCode Enterprise'))
1208 c.f_path = f_path
1206 c.f_path = f_path
1209 unix_mode = 0
1207 unix_mode = 0
1210 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1208 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1211
1209
1212 message = r_post.get('message') or c.default_message
1210 message = r_post.get('message') or c.default_message
1213 filename = r_post.get('filename')
1211 filename = r_post.get('filename')
1214 location = r_post.get('location', '') # dir location
1212 location = r_post.get('location', '') # dir location
1215 file_obj = r_post.get('upload_file', None)
1213 file_obj = r_post.get('upload_file', None)
1216
1214
1217 if file_obj is not None and hasattr(file_obj, 'filename'):
1215 if file_obj is not None and hasattr(file_obj, 'filename'):
1218 filename = r_post.get('filename_upload')
1216 filename = r_post.get('filename_upload')
1219 content = file_obj.file
1217 content = file_obj.file
1220
1218
1221 if hasattr(content, 'file'):
1219 if hasattr(content, 'file'):
1222 # non posix systems store real file under file attr
1220 # non posix systems store real file under file attr
1223 content = content.file
1221 content = content.file
1224
1222
1225 if self.rhodecode_vcs_repo.is_empty:
1223 if self.rhodecode_vcs_repo.is_empty:
1226 default_redirect_url = h.route_path(
1224 default_redirect_url = h.route_path(
1227 'repo_summary', repo_name=self.db_repo_name)
1225 'repo_summary', repo_name=self.db_repo_name)
1228 else:
1226 else:
1229 default_redirect_url = h.route_path(
1227 default_redirect_url = h.route_path(
1230 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1228 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1231
1229
1232 # If there's no commit, redirect to repo summary
1230 # If there's no commit, redirect to repo summary
1233 if type(c.commit) is EmptyCommit:
1231 if type(c.commit) is EmptyCommit:
1234 redirect_url = h.route_path(
1232 redirect_url = h.route_path(
1235 'repo_summary', repo_name=self.db_repo_name)
1233 'repo_summary', repo_name=self.db_repo_name)
1236 else:
1234 else:
1237 redirect_url = default_redirect_url
1235 redirect_url = default_redirect_url
1238
1236
1239 if not filename:
1237 if not filename:
1240 h.flash(_('No filename'), category='warning')
1238 h.flash(_('No filename'), category='warning')
1241 raise HTTPFound(redirect_url)
1239 raise HTTPFound(redirect_url)
1242
1240
1243 # extract the location from filename,
1241 # extract the location from filename,
1244 # allows using foo/bar.txt syntax to create subdirectories
1242 # allows using foo/bar.txt syntax to create subdirectories
1245 subdir_loc = filename.rsplit('/', 1)
1243 subdir_loc = filename.rsplit('/', 1)
1246 if len(subdir_loc) == 2:
1244 if len(subdir_loc) == 2:
1247 location = os.path.join(location, subdir_loc[0])
1245 location = os.path.join(location, subdir_loc[0])
1248
1246
1249 # strip all crap out of file, just leave the basename
1247 # strip all crap out of file, just leave the basename
1250 filename = os.path.basename(filename)
1248 filename = os.path.basename(filename)
1251 node_path = os.path.join(location, filename)
1249 node_path = os.path.join(location, filename)
1252 author = self._rhodecode_db_user.full_contact
1250 author = self._rhodecode_db_user.full_contact
1253
1251
1254 try:
1252 try:
1255 nodes = {
1253 nodes = {
1256 node_path: {
1254 node_path: {
1257 'content': content
1255 'content': content
1258 }
1256 }
1259 }
1257 }
1260 ScmModel().create_nodes(
1258 ScmModel().create_nodes(
1261 user=self._rhodecode_db_user.user_id,
1259 user=self._rhodecode_db_user.user_id,
1262 repo=self.db_repo,
1260 repo=self.db_repo,
1263 message=message,
1261 message=message,
1264 nodes=nodes,
1262 nodes=nodes,
1265 parent_commit=c.commit,
1263 parent_commit=c.commit,
1266 author=author,
1264 author=author,
1267 )
1265 )
1268
1266
1269 h.flash(
1267 h.flash(
1270 _('Successfully committed new file `{}`').format(
1268 _('Successfully committed new file `{}`').format(
1271 h.escape(node_path)), category='success')
1269 h.escape(node_path)), category='success')
1272 except NonRelativePathError:
1270 except NonRelativePathError:
1273 log.exception('Non Relative path found')
1271 log.exception('Non Relative path found')
1274 h.flash(_(
1272 h.flash(_(
1275 'The location specified must be a relative path and must not '
1273 'The location specified must be a relative path and must not '
1276 'contain .. in the path'), category='warning')
1274 'contain .. in the path'), category='warning')
1277 raise HTTPFound(default_redirect_url)
1275 raise HTTPFound(default_redirect_url)
1278 except (NodeError, NodeAlreadyExistsError) as e:
1276 except (NodeError, NodeAlreadyExistsError) as e:
1279 h.flash(_(h.escape(e)), category='error')
1277 h.flash(_(h.escape(e)), category='error')
1280 except Exception:
1278 except Exception:
1281 log.exception('Error occurred during commit')
1279 log.exception('Error occurred during commit')
1282 h.flash(_('Error occurred during commit'), category='error')
1280 h.flash(_('Error occurred during commit'), category='error')
1283
1281
1284 raise HTTPFound(default_redirect_url)
1282 raise HTTPFound(default_redirect_url)
@@ -1,256 +1,253 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import datetime
22 import datetime
23 import formencode
23 import formencode
24 import formencode.htmlfill
24 import formencode.htmlfill
25
25
26 from pyramid.httpexceptions import HTTPFound
26 from pyramid.httpexceptions import HTTPFound
27 from pyramid.view import view_config
27 from pyramid.view import view_config
28 from pyramid.renderers import render
28 from pyramid.renderers import render
29 from pyramid.response import Response
29 from pyramid.response import Response
30
30
31 from rhodecode.apps._base import RepoAppView, DataGridAppView
31 from rhodecode.apps._base import RepoAppView, DataGridAppView
32 from rhodecode.lib.auth import (
32 from rhodecode.lib.auth import (
33 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous,
33 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous,
34 HasRepoPermissionAny, HasPermissionAnyDecorator, CSRFRequired)
34 HasRepoPermissionAny, HasPermissionAnyDecorator, CSRFRequired)
35 import rhodecode.lib.helpers as h
35 import rhodecode.lib.helpers as h
36 from rhodecode.model.db import coalesce, or_, Repository, RepoGroup
36 from rhodecode.model.db import coalesce, or_, Repository, RepoGroup
37 from rhodecode.model.repo import RepoModel
37 from rhodecode.model.repo import RepoModel
38 from rhodecode.model.forms import RepoForkForm
38 from rhodecode.model.forms import RepoForkForm
39 from rhodecode.model.scm import ScmModel, RepoGroupList
39 from rhodecode.model.scm import ScmModel, RepoGroupList
40 from rhodecode.lib.utils2 import safe_int, safe_unicode
40 from rhodecode.lib.utils2 import safe_int, safe_unicode
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44
44
45 class RepoForksView(RepoAppView, DataGridAppView):
45 class RepoForksView(RepoAppView, DataGridAppView):
46
46
47 def load_default_context(self):
47 def load_default_context(self):
48 c = self._get_local_tmpl_context(include_app_defaults=True)
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 c.rhodecode_repo = self.rhodecode_vcs_repo
49 c.rhodecode_repo = self.rhodecode_vcs_repo
53
50
54 acl_groups = RepoGroupList(
51 acl_groups = RepoGroupList(
55 RepoGroup.query().all(),
52 RepoGroup.query().all(),
56 perm_set=['group.write', 'group.admin'])
53 perm_set=['group.write', 'group.admin'])
57 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
54 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
58 c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
55 c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
59 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
56 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
60 c.landing_revs_choices = choices
57 c.landing_revs_choices = choices
61 c.personal_repo_group = c.rhodecode_user.personal_repo_group
58 c.personal_repo_group = c.rhodecode_user.personal_repo_group
62
59
63 self._register_global_c(c)
60 self._register_global_c(c)
64 return c
61 return c
65
62
66 @LoginRequired()
63 @LoginRequired()
67 @HasRepoPermissionAnyDecorator(
64 @HasRepoPermissionAnyDecorator(
68 'repository.read', 'repository.write', 'repository.admin')
65 'repository.read', 'repository.write', 'repository.admin')
69 @view_config(
66 @view_config(
70 route_name='repo_forks_show_all', request_method='GET',
67 route_name='repo_forks_show_all', request_method='GET',
71 renderer='rhodecode:templates/forks/forks.mako')
68 renderer='rhodecode:templates/forks/forks.mako')
72 def repo_forks_show_all(self):
69 def repo_forks_show_all(self):
73 c = self.load_default_context()
70 c = self.load_default_context()
74 return self._get_template_context(c)
71 return self._get_template_context(c)
75
72
76 @LoginRequired()
73 @LoginRequired()
77 @HasRepoPermissionAnyDecorator(
74 @HasRepoPermissionAnyDecorator(
78 'repository.read', 'repository.write', 'repository.admin')
75 'repository.read', 'repository.write', 'repository.admin')
79 @view_config(
76 @view_config(
80 route_name='repo_forks_data', request_method='GET',
77 route_name='repo_forks_data', request_method='GET',
81 renderer='json_ext', xhr=True)
78 renderer='json_ext', xhr=True)
82 def repo_forks_data(self):
79 def repo_forks_data(self):
83 _ = self.request.translate
80 _ = self.request.translate
84 column_map = {
81 column_map = {
85 'fork_name': 'repo_name',
82 'fork_name': 'repo_name',
86 'fork_date': 'created_on',
83 'fork_date': 'created_on',
87 'last_activity': 'updated_on'
84 'last_activity': 'updated_on'
88 }
85 }
89 draw, start, limit = self._extract_chunk(self.request)
86 draw, start, limit = self._extract_chunk(self.request)
90 search_q, order_by, order_dir = self._extract_ordering(
87 search_q, order_by, order_dir = self._extract_ordering(
91 self.request, column_map=column_map)
88 self.request, column_map=column_map)
92
89
93 acl_check = HasRepoPermissionAny(
90 acl_check = HasRepoPermissionAny(
94 'repository.read', 'repository.write', 'repository.admin')
91 'repository.read', 'repository.write', 'repository.admin')
95 repo_id = self.db_repo.repo_id
92 repo_id = self.db_repo.repo_id
96 allowed_ids = []
93 allowed_ids = []
97 for f in Repository.query().filter(Repository.fork_id == repo_id):
94 for f in Repository.query().filter(Repository.fork_id == repo_id):
98 if acl_check(f.repo_name, 'get forks check'):
95 if acl_check(f.repo_name, 'get forks check'):
99 allowed_ids.append(f.repo_id)
96 allowed_ids.append(f.repo_id)
100
97
101 forks_data_total_count = Repository.query()\
98 forks_data_total_count = Repository.query()\
102 .filter(Repository.fork_id == repo_id)\
99 .filter(Repository.fork_id == repo_id)\
103 .filter(Repository.repo_id.in_(allowed_ids))\
100 .filter(Repository.repo_id.in_(allowed_ids))\
104 .count()
101 .count()
105
102
106 # json generate
103 # json generate
107 base_q = Repository.query()\
104 base_q = Repository.query()\
108 .filter(Repository.fork_id == repo_id)\
105 .filter(Repository.fork_id == repo_id)\
109 .filter(Repository.repo_id.in_(allowed_ids))\
106 .filter(Repository.repo_id.in_(allowed_ids))\
110
107
111 if search_q:
108 if search_q:
112 like_expression = u'%{}%'.format(safe_unicode(search_q))
109 like_expression = u'%{}%'.format(safe_unicode(search_q))
113 base_q = base_q.filter(or_(
110 base_q = base_q.filter(or_(
114 Repository.repo_name.ilike(like_expression),
111 Repository.repo_name.ilike(like_expression),
115 Repository.description.ilike(like_expression),
112 Repository.description.ilike(like_expression),
116 ))
113 ))
117
114
118 forks_data_total_filtered_count = base_q.count()
115 forks_data_total_filtered_count = base_q.count()
119
116
120 sort_col = getattr(Repository, order_by, None)
117 sort_col = getattr(Repository, order_by, None)
121 if sort_col:
118 if sort_col:
122 if order_dir == 'asc':
119 if order_dir == 'asc':
123 # handle null values properly to order by NULL last
120 # handle null values properly to order by NULL last
124 if order_by in ['last_activity']:
121 if order_by in ['last_activity']:
125 sort_col = coalesce(sort_col, datetime.date.max)
122 sort_col = coalesce(sort_col, datetime.date.max)
126 sort_col = sort_col.asc()
123 sort_col = sort_col.asc()
127 else:
124 else:
128 # handle null values properly to order by NULL last
125 # handle null values properly to order by NULL last
129 if order_by in ['last_activity']:
126 if order_by in ['last_activity']:
130 sort_col = coalesce(sort_col, datetime.date.min)
127 sort_col = coalesce(sort_col, datetime.date.min)
131 sort_col = sort_col.desc()
128 sort_col = sort_col.desc()
132
129
133 base_q = base_q.order_by(sort_col)
130 base_q = base_q.order_by(sort_col)
134 base_q = base_q.offset(start).limit(limit)
131 base_q = base_q.offset(start).limit(limit)
135
132
136 fork_list = base_q.all()
133 fork_list = base_q.all()
137
134
138 def fork_actions(fork):
135 def fork_actions(fork):
139 url_link = h.route_path(
136 url_link = h.route_path(
140 'repo_compare',
137 'repo_compare',
141 repo_name=fork.repo_name,
138 repo_name=fork.repo_name,
142 source_ref_type=self.db_repo.landing_rev[0],
139 source_ref_type=self.db_repo.landing_rev[0],
143 source_ref=self.db_repo.landing_rev[1],
140 source_ref=self.db_repo.landing_rev[1],
144 target_ref_type=self.db_repo.landing_rev[0],
141 target_ref_type=self.db_repo.landing_rev[0],
145 target_ref=self.db_repo.landing_rev[1],
142 target_ref=self.db_repo.landing_rev[1],
146 _query=dict(merge=1, target_repo=f.repo_name))
143 _query=dict(merge=1, target_repo=f.repo_name))
147 return h.link_to(_('Compare fork'), url_link, class_='btn-link')
144 return h.link_to(_('Compare fork'), url_link, class_='btn-link')
148
145
149 def fork_name(fork):
146 def fork_name(fork):
150 return h.link_to(fork.repo_name,
147 return h.link_to(fork.repo_name,
151 h.route_path('repo_summary', repo_name=fork.repo_name))
148 h.route_path('repo_summary', repo_name=fork.repo_name))
152
149
153 forks_data = []
150 forks_data = []
154 for fork in fork_list:
151 for fork in fork_list:
155 forks_data.append({
152 forks_data.append({
156 "username": h.gravatar_with_user(self.request, fork.user.username),
153 "username": h.gravatar_with_user(self.request, fork.user.username),
157 "fork_name": fork_name(fork),
154 "fork_name": fork_name(fork),
158 "description": fork.description,
155 "description": fork.description,
159 "fork_date": h.age_component(fork.created_on, time_is_local=True),
156 "fork_date": h.age_component(fork.created_on, time_is_local=True),
160 "last_activity": h.format_date(fork.updated_on),
157 "last_activity": h.format_date(fork.updated_on),
161 "action": fork_actions(fork),
158 "action": fork_actions(fork),
162 })
159 })
163
160
164 data = ({
161 data = ({
165 'draw': draw,
162 'draw': draw,
166 'data': forks_data,
163 'data': forks_data,
167 'recordsTotal': forks_data_total_count,
164 'recordsTotal': forks_data_total_count,
168 'recordsFiltered': forks_data_total_filtered_count,
165 'recordsFiltered': forks_data_total_filtered_count,
169 })
166 })
170
167
171 return data
168 return data
172
169
173 @LoginRequired()
170 @LoginRequired()
174 @NotAnonymous()
171 @NotAnonymous()
175 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
172 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
176 @HasRepoPermissionAnyDecorator(
173 @HasRepoPermissionAnyDecorator(
177 'repository.read', 'repository.write', 'repository.admin')
174 'repository.read', 'repository.write', 'repository.admin')
178 @view_config(
175 @view_config(
179 route_name='repo_fork_new', request_method='GET',
176 route_name='repo_fork_new', request_method='GET',
180 renderer='rhodecode:templates/forks/forks.mako')
177 renderer='rhodecode:templates/forks/forks.mako')
181 def repo_fork_new(self):
178 def repo_fork_new(self):
182 c = self.load_default_context()
179 c = self.load_default_context()
183
180
184 defaults = RepoModel()._get_defaults(self.db_repo_name)
181 defaults = RepoModel()._get_defaults(self.db_repo_name)
185 # alter the description to indicate a fork
182 # alter the description to indicate a fork
186 defaults['description'] = (
183 defaults['description'] = (
187 'fork of repository: %s \n%s' % (
184 'fork of repository: %s \n%s' % (
188 defaults['repo_name'], defaults['description']))
185 defaults['repo_name'], defaults['description']))
189 # add suffix to fork
186 # add suffix to fork
190 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
187 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
191
188
192 data = render('rhodecode:templates/forks/fork.mako',
189 data = render('rhodecode:templates/forks/fork.mako',
193 self._get_template_context(c), self.request)
190 self._get_template_context(c), self.request)
194 html = formencode.htmlfill.render(
191 html = formencode.htmlfill.render(
195 data,
192 data,
196 defaults=defaults,
193 defaults=defaults,
197 encoding="UTF-8",
194 encoding="UTF-8",
198 force_defaults=False
195 force_defaults=False
199 )
196 )
200 return Response(html)
197 return Response(html)
201
198
202 @LoginRequired()
199 @LoginRequired()
203 @NotAnonymous()
200 @NotAnonymous()
204 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
201 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
205 @HasRepoPermissionAnyDecorator(
202 @HasRepoPermissionAnyDecorator(
206 'repository.read', 'repository.write', 'repository.admin')
203 'repository.read', 'repository.write', 'repository.admin')
207 @CSRFRequired()
204 @CSRFRequired()
208 @view_config(
205 @view_config(
209 route_name='repo_fork_create', request_method='POST',
206 route_name='repo_fork_create', request_method='POST',
210 renderer='rhodecode:templates/forks/fork.mako')
207 renderer='rhodecode:templates/forks/fork.mako')
211 def repo_fork_create(self):
208 def repo_fork_create(self):
212 _ = self.request.translate
209 _ = self.request.translate
213 c = self.load_default_context()
210 c = self.load_default_context()
214
211
215 _form = RepoForkForm(old_data={'repo_type': self.db_repo.repo_type},
212 _form = RepoForkForm(old_data={'repo_type': self.db_repo.repo_type},
216 repo_groups=c.repo_groups_choices,
213 repo_groups=c.repo_groups_choices,
217 landing_revs=c.landing_revs_choices)()
214 landing_revs=c.landing_revs_choices)()
218 form_result = {}
215 form_result = {}
219 task_id = None
216 task_id = None
220 try:
217 try:
221 form_result = _form.to_python(dict(self.request.POST))
218 form_result = _form.to_python(dict(self.request.POST))
222 # create fork is done sometimes async on celery, db transaction
219 # create fork is done sometimes async on celery, db transaction
223 # management is handled there.
220 # management is handled there.
224 task = RepoModel().create_fork(
221 task = RepoModel().create_fork(
225 form_result, c.rhodecode_user.user_id)
222 form_result, c.rhodecode_user.user_id)
226 from celery.result import BaseAsyncResult
223 from celery.result import BaseAsyncResult
227 if isinstance(task, BaseAsyncResult):
224 if isinstance(task, BaseAsyncResult):
228 task_id = task.task_id
225 task_id = task.task_id
229 except formencode.Invalid as errors:
226 except formencode.Invalid as errors:
230 c.repo_info = self.db_repo
227 c.rhodecode_db_repo = self.db_repo
231
228
232 data = render('rhodecode:templates/forks/fork.mako',
229 data = render('rhodecode:templates/forks/fork.mako',
233 self._get_template_context(c), self.request)
230 self._get_template_context(c), self.request)
234 html = formencode.htmlfill.render(
231 html = formencode.htmlfill.render(
235 data,
232 data,
236 defaults=errors.value,
233 defaults=errors.value,
237 errors=errors.error_dict or {},
234 errors=errors.error_dict or {},
238 prefix_error=False,
235 prefix_error=False,
239 encoding="UTF-8",
236 encoding="UTF-8",
240 force_defaults=False
237 force_defaults=False
241 )
238 )
242 return Response(html)
239 return Response(html)
243 except Exception:
240 except Exception:
244 log.exception(
241 log.exception(
245 u'Exception while trying to fork the repository %s',
242 u'Exception while trying to fork the repository %s',
246 self.db_repo_name)
243 self.db_repo_name)
247 msg = (
244 msg = (
248 _('An error occurred during repository forking %s') % (
245 _('An error occurred during repository forking %s') % (
249 self.db_repo_name, ))
246 self.db_repo_name, ))
250 h.flash(msg, category='error')
247 h.flash(msg, category='error')
251
248
252 repo_name = form_result.get('repo_name_full', self.db_repo_name)
249 repo_name = form_result.get('repo_name_full', self.db_repo_name)
253 raise HTTPFound(
250 raise HTTPFound(
254 h.route_path('repo_creating',
251 h.route_path('repo_creating',
255 repo_name=repo_name,
252 repo_name=repo_name,
256 _query=dict(task_id=task_id)))
253 _query=dict(task_id=task_id)))
@@ -1,67 +1,64 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from pyramid.view import view_config
23 from pyramid.view import view_config
24
24
25 from rhodecode.apps._base import RepoAppView
25 from rhodecode.apps._base import RepoAppView
26 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
26 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
27 from rhodecode.lib import repo_maintenance
27 from rhodecode.lib import repo_maintenance
28
28
29 log = logging.getLogger(__name__)
29 log = logging.getLogger(__name__)
30
30
31
31
32 class RepoMaintenanceView(RepoAppView):
32 class RepoMaintenanceView(RepoAppView):
33 def load_default_context(self):
33 def load_default_context(self):
34 c = self._get_local_tmpl_context()
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 self._register_global_c(c)
36 self._register_global_c(c)
40 return c
37 return c
41
38
42 @LoginRequired()
39 @LoginRequired()
43 @HasRepoPermissionAnyDecorator('repository.admin')
40 @HasRepoPermissionAnyDecorator('repository.admin')
44 @view_config(
41 @view_config(
45 route_name='edit_repo_maintenance', request_method='GET',
42 route_name='edit_repo_maintenance', request_method='GET',
46 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
43 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
47 def repo_maintenance(self):
44 def repo_maintenance(self):
48 c = self.load_default_context()
45 c = self.load_default_context()
49 c.active = 'maintenance'
46 c.active = 'maintenance'
50 maintenance = repo_maintenance.RepoMaintenance()
47 maintenance = repo_maintenance.RepoMaintenance()
51 c.executable_tasks = maintenance.get_tasks_for_repo(self.db_repo)
48 c.executable_tasks = maintenance.get_tasks_for_repo(self.db_repo)
52 return self._get_template_context(c)
49 return self._get_template_context(c)
53
50
54 @LoginRequired()
51 @LoginRequired()
55 @HasRepoPermissionAnyDecorator('repository.admin')
52 @HasRepoPermissionAnyDecorator('repository.admin')
56 @view_config(
53 @view_config(
57 route_name='edit_repo_maintenance_execute', request_method='GET',
54 route_name='edit_repo_maintenance_execute', request_method='GET',
58 renderer='json', xhr=True)
55 renderer='json', xhr=True)
59 def repo_maintenance_execute(self):
56 def repo_maintenance_execute(self):
60 c = self.load_default_context()
57 c = self.load_default_context()
61 c.active = 'maintenance'
58 c.active = 'maintenance'
62 _ = self.request.translate
59 _ = self.request.translate
63
60
64 maintenance = repo_maintenance.RepoMaintenance()
61 maintenance = repo_maintenance.RepoMaintenance()
65 executed_types = maintenance.execute(self.db_repo)
62 executed_types = maintenance.execute(self.db_repo)
66
63
67 return executed_types
64 return executed_types
@@ -1,92 +1,89 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from pyramid.httpexceptions import HTTPFound
23 from pyramid.httpexceptions import HTTPFound
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25
25
26 from rhodecode.apps._base import RepoAppView
26 from rhodecode.apps._base import RepoAppView
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib import audit_logger
28 from rhodecode.lib import audit_logger
29 from rhodecode.lib.auth import (
29 from rhodecode.lib.auth import (
30 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
30 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
31 from rhodecode.model.forms import RepoPermsForm
31 from rhodecode.model.forms import RepoPermsForm
32 from rhodecode.model.meta import Session
32 from rhodecode.model.meta import Session
33 from rhodecode.model.repo import RepoModel
33 from rhodecode.model.repo import RepoModel
34
34
35 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
36
36
37
37
38 class RepoSettingsPermissionsView(RepoAppView):
38 class RepoSettingsPermissionsView(RepoAppView):
39
39
40 def load_default_context(self):
40 def load_default_context(self):
41 c = self._get_local_tmpl_context()
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 self._register_global_c(c)
43 self._register_global_c(c)
47 return c
44 return c
48
45
49 @LoginRequired()
46 @LoginRequired()
50 @HasRepoPermissionAnyDecorator('repository.admin')
47 @HasRepoPermissionAnyDecorator('repository.admin')
51 @view_config(
48 @view_config(
52 route_name='edit_repo_perms', request_method='GET',
49 route_name='edit_repo_perms', request_method='GET',
53 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
50 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
54 def edit_permissions(self):
51 def edit_permissions(self):
55 c = self.load_default_context()
52 c = self.load_default_context()
56 c.active = 'permissions'
53 c.active = 'permissions'
57 return self._get_template_context(c)
54 return self._get_template_context(c)
58
55
59 @LoginRequired()
56 @LoginRequired()
60 @HasRepoPermissionAnyDecorator('repository.admin')
57 @HasRepoPermissionAnyDecorator('repository.admin')
61 @CSRFRequired()
58 @CSRFRequired()
62 @view_config(
59 @view_config(
63 route_name='edit_repo_perms', request_method='POST',
60 route_name='edit_repo_perms', request_method='POST',
64 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
61 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
65 def edit_permissions_update(self):
62 def edit_permissions_update(self):
66 _ = self.request.translate
63 _ = self.request.translate
67 c = self.load_default_context()
64 c = self.load_default_context()
68 c.active = 'permissions'
65 c.active = 'permissions'
69 data = self.request.POST
66 data = self.request.POST
70 # store private flag outside of HTML to verify if we can modify
67 # store private flag outside of HTML to verify if we can modify
71 # default user permissions, prevents submission of FAKE post data
68 # default user permissions, prevents submission of FAKE post data
72 # into the form for private repos
69 # into the form for private repos
73 data['repo_private'] = self.db_repo.private
70 data['repo_private'] = self.db_repo.private
74 form = RepoPermsForm()().to_python(data)
71 form = RepoPermsForm()().to_python(data)
75 changes = RepoModel().update_permissions(
72 changes = RepoModel().update_permissions(
76 self.db_repo_name, form['perm_additions'], form['perm_updates'],
73 self.db_repo_name, form['perm_additions'], form['perm_updates'],
77 form['perm_deletions'])
74 form['perm_deletions'])
78
75
79 action_data = {
76 action_data = {
80 'added': changes['added'],
77 'added': changes['added'],
81 'updated': changes['updated'],
78 'updated': changes['updated'],
82 'deleted': changes['deleted'],
79 'deleted': changes['deleted'],
83 }
80 }
84 audit_logger.store_web(
81 audit_logger.store_web(
85 'repo.edit.permissions', action_data=action_data,
82 'repo.edit.permissions', action_data=action_data,
86 user=self._rhodecode_user, repo=self.db_repo)
83 user=self._rhodecode_user, repo=self.db_repo)
87
84
88 Session().commit()
85 Session().commit()
89 h.flash(_('Repository permissions updated'), category='success')
86 h.flash(_('Repository permissions updated'), category='success')
90
87
91 raise HTTPFound(
88 raise HTTPFound(
92 h.route_path('edit_repo_perms', repo_name=self.db_repo_name))
89 h.route_path('edit_repo_perms', repo_name=self.db_repo_name))
@@ -1,1194 +1,1192 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import collections
22 import collections
23
23
24 import formencode
24 import formencode
25 import formencode.htmlfill
25 import formencode.htmlfill
26 import peppercorn
26 import peppercorn
27 from pyramid.httpexceptions import (
27 from pyramid.httpexceptions import (
28 HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest)
28 HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest)
29 from pyramid.view import view_config
29 from pyramid.view import view_config
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31
31
32 from rhodecode import events
32 from rhodecode import events
33 from rhodecode.apps._base import RepoAppView, DataGridAppView
33 from rhodecode.apps._base import RepoAppView, DataGridAppView
34
34
35 from rhodecode.lib import helpers as h, diffs, codeblocks, channelstream
35 from rhodecode.lib import helpers as h, diffs, codeblocks, channelstream
36 from rhodecode.lib.base import vcs_operation_context
36 from rhodecode.lib.base import vcs_operation_context
37 from rhodecode.lib.ext_json import json
37 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.auth import (
38 from rhodecode.lib.auth import (
39 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
39 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
40 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode
40 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode
41 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
41 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
42 from rhodecode.lib.vcs.exceptions import (CommitDoesNotExistError,
42 from rhodecode.lib.vcs.exceptions import (CommitDoesNotExistError,
43 RepositoryRequirementError, NodeDoesNotExistError, EmptyRepositoryError)
43 RepositoryRequirementError, NodeDoesNotExistError, EmptyRepositoryError)
44 from rhodecode.model.changeset_status import ChangesetStatusModel
44 from rhodecode.model.changeset_status import ChangesetStatusModel
45 from rhodecode.model.comment import CommentsModel
45 from rhodecode.model.comment import CommentsModel
46 from rhodecode.model.db import (func, or_, PullRequest, PullRequestVersion,
46 from rhodecode.model.db import (func, or_, PullRequest, PullRequestVersion,
47 ChangesetComment, ChangesetStatus, Repository)
47 ChangesetComment, ChangesetStatus, Repository)
48 from rhodecode.model.forms import PullRequestForm
48 from rhodecode.model.forms import PullRequestForm
49 from rhodecode.model.meta import Session
49 from rhodecode.model.meta import Session
50 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
50 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
51 from rhodecode.model.scm import ScmModel
51 from rhodecode.model.scm import ScmModel
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55
55
56 class RepoPullRequestsView(RepoAppView, DataGridAppView):
56 class RepoPullRequestsView(RepoAppView, DataGridAppView):
57
57
58 def load_default_context(self):
58 def load_default_context(self):
59 c = self._get_local_tmpl_context(include_app_defaults=True)
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 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
60 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
63 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
61 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
64 self._register_global_c(c)
62 self._register_global_c(c)
65 return c
63 return c
66
64
67 def _get_pull_requests_list(
65 def _get_pull_requests_list(
68 self, repo_name, source, filter_type, opened_by, statuses):
66 self, repo_name, source, filter_type, opened_by, statuses):
69
67
70 draw, start, limit = self._extract_chunk(self.request)
68 draw, start, limit = self._extract_chunk(self.request)
71 search_q, order_by, order_dir = self._extract_ordering(self.request)
69 search_q, order_by, order_dir = self._extract_ordering(self.request)
72 _render = self.request.get_partial_renderer(
70 _render = self.request.get_partial_renderer(
73 'data_table/_dt_elements.mako')
71 'data_table/_dt_elements.mako')
74
72
75 # pagination
73 # pagination
76
74
77 if filter_type == 'awaiting_review':
75 if filter_type == 'awaiting_review':
78 pull_requests = PullRequestModel().get_awaiting_review(
76 pull_requests = PullRequestModel().get_awaiting_review(
79 repo_name, source=source, opened_by=opened_by,
77 repo_name, source=source, opened_by=opened_by,
80 statuses=statuses, offset=start, length=limit,
78 statuses=statuses, offset=start, length=limit,
81 order_by=order_by, order_dir=order_dir)
79 order_by=order_by, order_dir=order_dir)
82 pull_requests_total_count = PullRequestModel().count_awaiting_review(
80 pull_requests_total_count = PullRequestModel().count_awaiting_review(
83 repo_name, source=source, statuses=statuses,
81 repo_name, source=source, statuses=statuses,
84 opened_by=opened_by)
82 opened_by=opened_by)
85 elif filter_type == 'awaiting_my_review':
83 elif filter_type == 'awaiting_my_review':
86 pull_requests = PullRequestModel().get_awaiting_my_review(
84 pull_requests = PullRequestModel().get_awaiting_my_review(
87 repo_name, source=source, opened_by=opened_by,
85 repo_name, source=source, opened_by=opened_by,
88 user_id=self._rhodecode_user.user_id, statuses=statuses,
86 user_id=self._rhodecode_user.user_id, statuses=statuses,
89 offset=start, length=limit, order_by=order_by,
87 offset=start, length=limit, order_by=order_by,
90 order_dir=order_dir)
88 order_dir=order_dir)
91 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
89 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
92 repo_name, source=source, user_id=self._rhodecode_user.user_id,
90 repo_name, source=source, user_id=self._rhodecode_user.user_id,
93 statuses=statuses, opened_by=opened_by)
91 statuses=statuses, opened_by=opened_by)
94 else:
92 else:
95 pull_requests = PullRequestModel().get_all(
93 pull_requests = PullRequestModel().get_all(
96 repo_name, source=source, opened_by=opened_by,
94 repo_name, source=source, opened_by=opened_by,
97 statuses=statuses, offset=start, length=limit,
95 statuses=statuses, offset=start, length=limit,
98 order_by=order_by, order_dir=order_dir)
96 order_by=order_by, order_dir=order_dir)
99 pull_requests_total_count = PullRequestModel().count_all(
97 pull_requests_total_count = PullRequestModel().count_all(
100 repo_name, source=source, statuses=statuses,
98 repo_name, source=source, statuses=statuses,
101 opened_by=opened_by)
99 opened_by=opened_by)
102
100
103 data = []
101 data = []
104 comments_model = CommentsModel()
102 comments_model = CommentsModel()
105 for pr in pull_requests:
103 for pr in pull_requests:
106 comments = comments_model.get_all_comments(
104 comments = comments_model.get_all_comments(
107 self.db_repo.repo_id, pull_request=pr)
105 self.db_repo.repo_id, pull_request=pr)
108
106
109 data.append({
107 data.append({
110 'name': _render('pullrequest_name',
108 'name': _render('pullrequest_name',
111 pr.pull_request_id, pr.target_repo.repo_name),
109 pr.pull_request_id, pr.target_repo.repo_name),
112 'name_raw': pr.pull_request_id,
110 'name_raw': pr.pull_request_id,
113 'status': _render('pullrequest_status',
111 'status': _render('pullrequest_status',
114 pr.calculated_review_status()),
112 pr.calculated_review_status()),
115 'title': _render(
113 'title': _render(
116 'pullrequest_title', pr.title, pr.description),
114 'pullrequest_title', pr.title, pr.description),
117 'description': h.escape(pr.description),
115 'description': h.escape(pr.description),
118 'updated_on': _render('pullrequest_updated_on',
116 'updated_on': _render('pullrequest_updated_on',
119 h.datetime_to_time(pr.updated_on)),
117 h.datetime_to_time(pr.updated_on)),
120 'updated_on_raw': h.datetime_to_time(pr.updated_on),
118 'updated_on_raw': h.datetime_to_time(pr.updated_on),
121 'created_on': _render('pullrequest_updated_on',
119 'created_on': _render('pullrequest_updated_on',
122 h.datetime_to_time(pr.created_on)),
120 h.datetime_to_time(pr.created_on)),
123 'created_on_raw': h.datetime_to_time(pr.created_on),
121 'created_on_raw': h.datetime_to_time(pr.created_on),
124 'author': _render('pullrequest_author',
122 'author': _render('pullrequest_author',
125 pr.author.full_contact, ),
123 pr.author.full_contact, ),
126 'author_raw': pr.author.full_name,
124 'author_raw': pr.author.full_name,
127 'comments': _render('pullrequest_comments', len(comments)),
125 'comments': _render('pullrequest_comments', len(comments)),
128 'comments_raw': len(comments),
126 'comments_raw': len(comments),
129 'closed': pr.is_closed(),
127 'closed': pr.is_closed(),
130 })
128 })
131
129
132 data = ({
130 data = ({
133 'draw': draw,
131 'draw': draw,
134 'data': data,
132 'data': data,
135 'recordsTotal': pull_requests_total_count,
133 'recordsTotal': pull_requests_total_count,
136 'recordsFiltered': pull_requests_total_count,
134 'recordsFiltered': pull_requests_total_count,
137 })
135 })
138 return data
136 return data
139
137
140 @LoginRequired()
138 @LoginRequired()
141 @HasRepoPermissionAnyDecorator(
139 @HasRepoPermissionAnyDecorator(
142 'repository.read', 'repository.write', 'repository.admin')
140 'repository.read', 'repository.write', 'repository.admin')
143 @view_config(
141 @view_config(
144 route_name='pullrequest_show_all', request_method='GET',
142 route_name='pullrequest_show_all', request_method='GET',
145 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
143 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
146 def pull_request_list(self):
144 def pull_request_list(self):
147 c = self.load_default_context()
145 c = self.load_default_context()
148
146
149 req_get = self.request.GET
147 req_get = self.request.GET
150 c.source = str2bool(req_get.get('source'))
148 c.source = str2bool(req_get.get('source'))
151 c.closed = str2bool(req_get.get('closed'))
149 c.closed = str2bool(req_get.get('closed'))
152 c.my = str2bool(req_get.get('my'))
150 c.my = str2bool(req_get.get('my'))
153 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
151 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
154 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
152 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
155
153
156 c.active = 'open'
154 c.active = 'open'
157 if c.my:
155 if c.my:
158 c.active = 'my'
156 c.active = 'my'
159 if c.closed:
157 if c.closed:
160 c.active = 'closed'
158 c.active = 'closed'
161 if c.awaiting_review and not c.source:
159 if c.awaiting_review and not c.source:
162 c.active = 'awaiting'
160 c.active = 'awaiting'
163 if c.source and not c.awaiting_review:
161 if c.source and not c.awaiting_review:
164 c.active = 'source'
162 c.active = 'source'
165 if c.awaiting_my_review:
163 if c.awaiting_my_review:
166 c.active = 'awaiting_my'
164 c.active = 'awaiting_my'
167
165
168 return self._get_template_context(c)
166 return self._get_template_context(c)
169
167
170 @LoginRequired()
168 @LoginRequired()
171 @HasRepoPermissionAnyDecorator(
169 @HasRepoPermissionAnyDecorator(
172 'repository.read', 'repository.write', 'repository.admin')
170 'repository.read', 'repository.write', 'repository.admin')
173 @view_config(
171 @view_config(
174 route_name='pullrequest_show_all_data', request_method='GET',
172 route_name='pullrequest_show_all_data', request_method='GET',
175 renderer='json_ext', xhr=True)
173 renderer='json_ext', xhr=True)
176 def pull_request_list_data(self):
174 def pull_request_list_data(self):
177
175
178 # additional filters
176 # additional filters
179 req_get = self.request.GET
177 req_get = self.request.GET
180 source = str2bool(req_get.get('source'))
178 source = str2bool(req_get.get('source'))
181 closed = str2bool(req_get.get('closed'))
179 closed = str2bool(req_get.get('closed'))
182 my = str2bool(req_get.get('my'))
180 my = str2bool(req_get.get('my'))
183 awaiting_review = str2bool(req_get.get('awaiting_review'))
181 awaiting_review = str2bool(req_get.get('awaiting_review'))
184 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
182 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
185
183
186 filter_type = 'awaiting_review' if awaiting_review \
184 filter_type = 'awaiting_review' if awaiting_review \
187 else 'awaiting_my_review' if awaiting_my_review \
185 else 'awaiting_my_review' if awaiting_my_review \
188 else None
186 else None
189
187
190 opened_by = None
188 opened_by = None
191 if my:
189 if my:
192 opened_by = [self._rhodecode_user.user_id]
190 opened_by = [self._rhodecode_user.user_id]
193
191
194 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
192 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
195 if closed:
193 if closed:
196 statuses = [PullRequest.STATUS_CLOSED]
194 statuses = [PullRequest.STATUS_CLOSED]
197
195
198 data = self._get_pull_requests_list(
196 data = self._get_pull_requests_list(
199 repo_name=self.db_repo_name, source=source,
197 repo_name=self.db_repo_name, source=source,
200 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
198 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
201
199
202 return data
200 return data
203
201
204 def _get_pr_version(self, pull_request_id, version=None):
202 def _get_pr_version(self, pull_request_id, version=None):
205 at_version = None
203 at_version = None
206
204
207 if version and version == 'latest':
205 if version and version == 'latest':
208 pull_request_ver = PullRequest.get(pull_request_id)
206 pull_request_ver = PullRequest.get(pull_request_id)
209 pull_request_obj = pull_request_ver
207 pull_request_obj = pull_request_ver
210 _org_pull_request_obj = pull_request_obj
208 _org_pull_request_obj = pull_request_obj
211 at_version = 'latest'
209 at_version = 'latest'
212 elif version:
210 elif version:
213 pull_request_ver = PullRequestVersion.get_or_404(version)
211 pull_request_ver = PullRequestVersion.get_or_404(version)
214 pull_request_obj = pull_request_ver
212 pull_request_obj = pull_request_ver
215 _org_pull_request_obj = pull_request_ver.pull_request
213 _org_pull_request_obj = pull_request_ver.pull_request
216 at_version = pull_request_ver.pull_request_version_id
214 at_version = pull_request_ver.pull_request_version_id
217 else:
215 else:
218 _org_pull_request_obj = pull_request_obj = PullRequest.get_or_404(
216 _org_pull_request_obj = pull_request_obj = PullRequest.get_or_404(
219 pull_request_id)
217 pull_request_id)
220
218
221 pull_request_display_obj = PullRequest.get_pr_display_object(
219 pull_request_display_obj = PullRequest.get_pr_display_object(
222 pull_request_obj, _org_pull_request_obj)
220 pull_request_obj, _org_pull_request_obj)
223
221
224 return _org_pull_request_obj, pull_request_obj, \
222 return _org_pull_request_obj, pull_request_obj, \
225 pull_request_display_obj, at_version
223 pull_request_display_obj, at_version
226
224
227 def _get_diffset(self, source_repo_name, source_repo,
225 def _get_diffset(self, source_repo_name, source_repo,
228 source_ref_id, target_ref_id,
226 source_ref_id, target_ref_id,
229 target_commit, source_commit, diff_limit, fulldiff,
227 target_commit, source_commit, diff_limit, fulldiff,
230 file_limit, display_inline_comments):
228 file_limit, display_inline_comments):
231
229
232 vcs_diff = PullRequestModel().get_diff(
230 vcs_diff = PullRequestModel().get_diff(
233 source_repo, source_ref_id, target_ref_id)
231 source_repo, source_ref_id, target_ref_id)
234
232
235 diff_processor = diffs.DiffProcessor(
233 diff_processor = diffs.DiffProcessor(
236 vcs_diff, format='newdiff', diff_limit=diff_limit,
234 vcs_diff, format='newdiff', diff_limit=diff_limit,
237 file_limit=file_limit, show_full_diff=fulldiff)
235 file_limit=file_limit, show_full_diff=fulldiff)
238
236
239 _parsed = diff_processor.prepare()
237 _parsed = diff_processor.prepare()
240
238
241 def _node_getter(commit):
239 def _node_getter(commit):
242 def get_node(fname):
240 def get_node(fname):
243 try:
241 try:
244 return commit.get_node(fname)
242 return commit.get_node(fname)
245 except NodeDoesNotExistError:
243 except NodeDoesNotExistError:
246 return None
244 return None
247
245
248 return get_node
246 return get_node
249
247
250 diffset = codeblocks.DiffSet(
248 diffset = codeblocks.DiffSet(
251 repo_name=self.db_repo_name,
249 repo_name=self.db_repo_name,
252 source_repo_name=source_repo_name,
250 source_repo_name=source_repo_name,
253 source_node_getter=_node_getter(target_commit),
251 source_node_getter=_node_getter(target_commit),
254 target_node_getter=_node_getter(source_commit),
252 target_node_getter=_node_getter(source_commit),
255 comments=display_inline_comments
253 comments=display_inline_comments
256 )
254 )
257 diffset = diffset.render_patchset(
255 diffset = diffset.render_patchset(
258 _parsed, target_commit.raw_id, source_commit.raw_id)
256 _parsed, target_commit.raw_id, source_commit.raw_id)
259
257
260 return diffset
258 return diffset
261
259
262 @LoginRequired()
260 @LoginRequired()
263 @HasRepoPermissionAnyDecorator(
261 @HasRepoPermissionAnyDecorator(
264 'repository.read', 'repository.write', 'repository.admin')
262 'repository.read', 'repository.write', 'repository.admin')
265 @view_config(
263 @view_config(
266 route_name='pullrequest_show', request_method='GET',
264 route_name='pullrequest_show', request_method='GET',
267 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
265 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
268 def pull_request_show(self):
266 def pull_request_show(self):
269 pull_request_id = self.request.matchdict['pull_request_id']
267 pull_request_id = self.request.matchdict['pull_request_id']
270
268
271 c = self.load_default_context()
269 c = self.load_default_context()
272
270
273 version = self.request.GET.get('version')
271 version = self.request.GET.get('version')
274 from_version = self.request.GET.get('from_version') or version
272 from_version = self.request.GET.get('from_version') or version
275 merge_checks = self.request.GET.get('merge_checks')
273 merge_checks = self.request.GET.get('merge_checks')
276 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
274 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
277
275
278 (pull_request_latest,
276 (pull_request_latest,
279 pull_request_at_ver,
277 pull_request_at_ver,
280 pull_request_display_obj,
278 pull_request_display_obj,
281 at_version) = self._get_pr_version(
279 at_version) = self._get_pr_version(
282 pull_request_id, version=version)
280 pull_request_id, version=version)
283 pr_closed = pull_request_latest.is_closed()
281 pr_closed = pull_request_latest.is_closed()
284
282
285 if pr_closed and (version or from_version):
283 if pr_closed and (version or from_version):
286 # not allow to browse versions
284 # not allow to browse versions
287 raise HTTPFound(h.route_path(
285 raise HTTPFound(h.route_path(
288 'pullrequest_show', repo_name=self.db_repo_name,
286 'pullrequest_show', repo_name=self.db_repo_name,
289 pull_request_id=pull_request_id))
287 pull_request_id=pull_request_id))
290
288
291 versions = pull_request_display_obj.versions()
289 versions = pull_request_display_obj.versions()
292
290
293 c.at_version = at_version
291 c.at_version = at_version
294 c.at_version_num = (at_version
292 c.at_version_num = (at_version
295 if at_version and at_version != 'latest'
293 if at_version and at_version != 'latest'
296 else None)
294 else None)
297 c.at_version_pos = ChangesetComment.get_index_from_version(
295 c.at_version_pos = ChangesetComment.get_index_from_version(
298 c.at_version_num, versions)
296 c.at_version_num, versions)
299
297
300 (prev_pull_request_latest,
298 (prev_pull_request_latest,
301 prev_pull_request_at_ver,
299 prev_pull_request_at_ver,
302 prev_pull_request_display_obj,
300 prev_pull_request_display_obj,
303 prev_at_version) = self._get_pr_version(
301 prev_at_version) = self._get_pr_version(
304 pull_request_id, version=from_version)
302 pull_request_id, version=from_version)
305
303
306 c.from_version = prev_at_version
304 c.from_version = prev_at_version
307 c.from_version_num = (prev_at_version
305 c.from_version_num = (prev_at_version
308 if prev_at_version and prev_at_version != 'latest'
306 if prev_at_version and prev_at_version != 'latest'
309 else None)
307 else None)
310 c.from_version_pos = ChangesetComment.get_index_from_version(
308 c.from_version_pos = ChangesetComment.get_index_from_version(
311 c.from_version_num, versions)
309 c.from_version_num, versions)
312
310
313 # define if we're in COMPARE mode or VIEW at version mode
311 # define if we're in COMPARE mode or VIEW at version mode
314 compare = at_version != prev_at_version
312 compare = at_version != prev_at_version
315
313
316 # pull_requests repo_name we opened it against
314 # pull_requests repo_name we opened it against
317 # ie. target_repo must match
315 # ie. target_repo must match
318 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
316 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
319 raise HTTPNotFound()
317 raise HTTPNotFound()
320
318
321 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
319 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
322 pull_request_at_ver)
320 pull_request_at_ver)
323
321
324 c.pull_request = pull_request_display_obj
322 c.pull_request = pull_request_display_obj
325 c.pull_request_latest = pull_request_latest
323 c.pull_request_latest = pull_request_latest
326
324
327 if compare or (at_version and not at_version == 'latest'):
325 if compare or (at_version and not at_version == 'latest'):
328 c.allowed_to_change_status = False
326 c.allowed_to_change_status = False
329 c.allowed_to_update = False
327 c.allowed_to_update = False
330 c.allowed_to_merge = False
328 c.allowed_to_merge = False
331 c.allowed_to_delete = False
329 c.allowed_to_delete = False
332 c.allowed_to_comment = False
330 c.allowed_to_comment = False
333 c.allowed_to_close = False
331 c.allowed_to_close = False
334 else:
332 else:
335 can_change_status = PullRequestModel().check_user_change_status(
333 can_change_status = PullRequestModel().check_user_change_status(
336 pull_request_at_ver, self._rhodecode_user)
334 pull_request_at_ver, self._rhodecode_user)
337 c.allowed_to_change_status = can_change_status and not pr_closed
335 c.allowed_to_change_status = can_change_status and not pr_closed
338
336
339 c.allowed_to_update = PullRequestModel().check_user_update(
337 c.allowed_to_update = PullRequestModel().check_user_update(
340 pull_request_latest, self._rhodecode_user) and not pr_closed
338 pull_request_latest, self._rhodecode_user) and not pr_closed
341 c.allowed_to_merge = PullRequestModel().check_user_merge(
339 c.allowed_to_merge = PullRequestModel().check_user_merge(
342 pull_request_latest, self._rhodecode_user) and not pr_closed
340 pull_request_latest, self._rhodecode_user) and not pr_closed
343 c.allowed_to_delete = PullRequestModel().check_user_delete(
341 c.allowed_to_delete = PullRequestModel().check_user_delete(
344 pull_request_latest, self._rhodecode_user) and not pr_closed
342 pull_request_latest, self._rhodecode_user) and not pr_closed
345 c.allowed_to_comment = not pr_closed
343 c.allowed_to_comment = not pr_closed
346 c.allowed_to_close = c.allowed_to_merge and not pr_closed
344 c.allowed_to_close = c.allowed_to_merge and not pr_closed
347
345
348 c.forbid_adding_reviewers = False
346 c.forbid_adding_reviewers = False
349 c.forbid_author_to_review = False
347 c.forbid_author_to_review = False
350 c.forbid_commit_author_to_review = False
348 c.forbid_commit_author_to_review = False
351
349
352 if pull_request_latest.reviewer_data and \
350 if pull_request_latest.reviewer_data and \
353 'rules' in pull_request_latest.reviewer_data:
351 'rules' in pull_request_latest.reviewer_data:
354 rules = pull_request_latest.reviewer_data['rules'] or {}
352 rules = pull_request_latest.reviewer_data['rules'] or {}
355 try:
353 try:
356 c.forbid_adding_reviewers = rules.get(
354 c.forbid_adding_reviewers = rules.get(
357 'forbid_adding_reviewers')
355 'forbid_adding_reviewers')
358 c.forbid_author_to_review = rules.get(
356 c.forbid_author_to_review = rules.get(
359 'forbid_author_to_review')
357 'forbid_author_to_review')
360 c.forbid_commit_author_to_review = rules.get(
358 c.forbid_commit_author_to_review = rules.get(
361 'forbid_commit_author_to_review')
359 'forbid_commit_author_to_review')
362 except Exception:
360 except Exception:
363 pass
361 pass
364
362
365 # check merge capabilities
363 # check merge capabilities
366 _merge_check = MergeCheck.validate(
364 _merge_check = MergeCheck.validate(
367 pull_request_latest, user=self._rhodecode_user)
365 pull_request_latest, user=self._rhodecode_user)
368 c.pr_merge_errors = _merge_check.error_details
366 c.pr_merge_errors = _merge_check.error_details
369 c.pr_merge_possible = not _merge_check.failed
367 c.pr_merge_possible = not _merge_check.failed
370 c.pr_merge_message = _merge_check.merge_msg
368 c.pr_merge_message = _merge_check.merge_msg
371
369
372 c.pr_merge_info = MergeCheck.get_merge_conditions(pull_request_latest)
370 c.pr_merge_info = MergeCheck.get_merge_conditions(pull_request_latest)
373
371
374 c.pull_request_review_status = _merge_check.review_status
372 c.pull_request_review_status = _merge_check.review_status
375 if merge_checks:
373 if merge_checks:
376 self.request.override_renderer = \
374 self.request.override_renderer = \
377 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
375 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
378 return self._get_template_context(c)
376 return self._get_template_context(c)
379
377
380 comments_model = CommentsModel()
378 comments_model = CommentsModel()
381
379
382 # reviewers and statuses
380 # reviewers and statuses
383 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
381 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
384 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
382 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
385
383
386 # GENERAL COMMENTS with versions #
384 # GENERAL COMMENTS with versions #
387 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
385 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
388 q = q.order_by(ChangesetComment.comment_id.asc())
386 q = q.order_by(ChangesetComment.comment_id.asc())
389 general_comments = q
387 general_comments = q
390
388
391 # pick comments we want to render at current version
389 # pick comments we want to render at current version
392 c.comment_versions = comments_model.aggregate_comments(
390 c.comment_versions = comments_model.aggregate_comments(
393 general_comments, versions, c.at_version_num)
391 general_comments, versions, c.at_version_num)
394 c.comments = c.comment_versions[c.at_version_num]['until']
392 c.comments = c.comment_versions[c.at_version_num]['until']
395
393
396 # INLINE COMMENTS with versions #
394 # INLINE COMMENTS with versions #
397 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
395 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
398 q = q.order_by(ChangesetComment.comment_id.asc())
396 q = q.order_by(ChangesetComment.comment_id.asc())
399 inline_comments = q
397 inline_comments = q
400
398
401 c.inline_versions = comments_model.aggregate_comments(
399 c.inline_versions = comments_model.aggregate_comments(
402 inline_comments, versions, c.at_version_num, inline=True)
400 inline_comments, versions, c.at_version_num, inline=True)
403
401
404 # inject latest version
402 # inject latest version
405 latest_ver = PullRequest.get_pr_display_object(
403 latest_ver = PullRequest.get_pr_display_object(
406 pull_request_latest, pull_request_latest)
404 pull_request_latest, pull_request_latest)
407
405
408 c.versions = versions + [latest_ver]
406 c.versions = versions + [latest_ver]
409
407
410 # if we use version, then do not show later comments
408 # if we use version, then do not show later comments
411 # than current version
409 # than current version
412 display_inline_comments = collections.defaultdict(
410 display_inline_comments = collections.defaultdict(
413 lambda: collections.defaultdict(list))
411 lambda: collections.defaultdict(list))
414 for co in inline_comments:
412 for co in inline_comments:
415 if c.at_version_num:
413 if c.at_version_num:
416 # pick comments that are at least UPTO given version, so we
414 # pick comments that are at least UPTO given version, so we
417 # don't render comments for higher version
415 # don't render comments for higher version
418 should_render = co.pull_request_version_id and \
416 should_render = co.pull_request_version_id and \
419 co.pull_request_version_id <= c.at_version_num
417 co.pull_request_version_id <= c.at_version_num
420 else:
418 else:
421 # showing all, for 'latest'
419 # showing all, for 'latest'
422 should_render = True
420 should_render = True
423
421
424 if should_render:
422 if should_render:
425 display_inline_comments[co.f_path][co.line_no].append(co)
423 display_inline_comments[co.f_path][co.line_no].append(co)
426
424
427 # load diff data into template context, if we use compare mode then
425 # load diff data into template context, if we use compare mode then
428 # diff is calculated based on changes between versions of PR
426 # diff is calculated based on changes between versions of PR
429
427
430 source_repo = pull_request_at_ver.source_repo
428 source_repo = pull_request_at_ver.source_repo
431 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
429 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
432
430
433 target_repo = pull_request_at_ver.target_repo
431 target_repo = pull_request_at_ver.target_repo
434 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
432 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
435
433
436 if compare:
434 if compare:
437 # in compare switch the diff base to latest commit from prev version
435 # in compare switch the diff base to latest commit from prev version
438 target_ref_id = prev_pull_request_display_obj.revisions[0]
436 target_ref_id = prev_pull_request_display_obj.revisions[0]
439
437
440 # despite opening commits for bookmarks/branches/tags, we always
438 # despite opening commits for bookmarks/branches/tags, we always
441 # convert this to rev to prevent changes after bookmark or branch change
439 # convert this to rev to prevent changes after bookmark or branch change
442 c.source_ref_type = 'rev'
440 c.source_ref_type = 'rev'
443 c.source_ref = source_ref_id
441 c.source_ref = source_ref_id
444
442
445 c.target_ref_type = 'rev'
443 c.target_ref_type = 'rev'
446 c.target_ref = target_ref_id
444 c.target_ref = target_ref_id
447
445
448 c.source_repo = source_repo
446 c.source_repo = source_repo
449 c.target_repo = target_repo
447 c.target_repo = target_repo
450
448
451 c.commit_ranges = []
449 c.commit_ranges = []
452 source_commit = EmptyCommit()
450 source_commit = EmptyCommit()
453 target_commit = EmptyCommit()
451 target_commit = EmptyCommit()
454 c.missing_requirements = False
452 c.missing_requirements = False
455
453
456 source_scm = source_repo.scm_instance()
454 source_scm = source_repo.scm_instance()
457 target_scm = target_repo.scm_instance()
455 target_scm = target_repo.scm_instance()
458
456
459 # try first shadow repo, fallback to regular repo
457 # try first shadow repo, fallback to regular repo
460 try:
458 try:
461 commits_source_repo = pull_request_latest.get_shadow_repo()
459 commits_source_repo = pull_request_latest.get_shadow_repo()
462 except Exception:
460 except Exception:
463 log.debug('Failed to get shadow repo', exc_info=True)
461 log.debug('Failed to get shadow repo', exc_info=True)
464 commits_source_repo = source_scm
462 commits_source_repo = source_scm
465
463
466 c.commits_source_repo = commits_source_repo
464 c.commits_source_repo = commits_source_repo
467 commit_cache = {}
465 commit_cache = {}
468 try:
466 try:
469 pre_load = ["author", "branch", "date", "message"]
467 pre_load = ["author", "branch", "date", "message"]
470 show_revs = pull_request_at_ver.revisions
468 show_revs = pull_request_at_ver.revisions
471 for rev in show_revs:
469 for rev in show_revs:
472 comm = commits_source_repo.get_commit(
470 comm = commits_source_repo.get_commit(
473 commit_id=rev, pre_load=pre_load)
471 commit_id=rev, pre_load=pre_load)
474 c.commit_ranges.append(comm)
472 c.commit_ranges.append(comm)
475 commit_cache[comm.raw_id] = comm
473 commit_cache[comm.raw_id] = comm
476
474
477 # Order here matters, we first need to get target, and then
475 # Order here matters, we first need to get target, and then
478 # the source
476 # the source
479 target_commit = commits_source_repo.get_commit(
477 target_commit = commits_source_repo.get_commit(
480 commit_id=safe_str(target_ref_id))
478 commit_id=safe_str(target_ref_id))
481
479
482 source_commit = commits_source_repo.get_commit(
480 source_commit = commits_source_repo.get_commit(
483 commit_id=safe_str(source_ref_id))
481 commit_id=safe_str(source_ref_id))
484
482
485 except CommitDoesNotExistError:
483 except CommitDoesNotExistError:
486 log.warning(
484 log.warning(
487 'Failed to get commit from `{}` repo'.format(
485 'Failed to get commit from `{}` repo'.format(
488 commits_source_repo), exc_info=True)
486 commits_source_repo), exc_info=True)
489 except RepositoryRequirementError:
487 except RepositoryRequirementError:
490 log.warning(
488 log.warning(
491 'Failed to get all required data from repo', exc_info=True)
489 'Failed to get all required data from repo', exc_info=True)
492 c.missing_requirements = True
490 c.missing_requirements = True
493
491
494 c.ancestor = None # set it to None, to hide it from PR view
492 c.ancestor = None # set it to None, to hide it from PR view
495
493
496 try:
494 try:
497 ancestor_id = source_scm.get_common_ancestor(
495 ancestor_id = source_scm.get_common_ancestor(
498 source_commit.raw_id, target_commit.raw_id, target_scm)
496 source_commit.raw_id, target_commit.raw_id, target_scm)
499 c.ancestor_commit = source_scm.get_commit(ancestor_id)
497 c.ancestor_commit = source_scm.get_commit(ancestor_id)
500 except Exception:
498 except Exception:
501 c.ancestor_commit = None
499 c.ancestor_commit = None
502
500
503 c.statuses = source_repo.statuses(
501 c.statuses = source_repo.statuses(
504 [x.raw_id for x in c.commit_ranges])
502 [x.raw_id for x in c.commit_ranges])
505
503
506 # auto collapse if we have more than limit
504 # auto collapse if we have more than limit
507 collapse_limit = diffs.DiffProcessor._collapse_commits_over
505 collapse_limit = diffs.DiffProcessor._collapse_commits_over
508 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
506 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
509 c.compare_mode = compare
507 c.compare_mode = compare
510
508
511 # diff_limit is the old behavior, will cut off the whole diff
509 # diff_limit is the old behavior, will cut off the whole diff
512 # if the limit is applied otherwise will just hide the
510 # if the limit is applied otherwise will just hide the
513 # big files from the front-end
511 # big files from the front-end
514 diff_limit = c.visual.cut_off_limit_diff
512 diff_limit = c.visual.cut_off_limit_diff
515 file_limit = c.visual.cut_off_limit_file
513 file_limit = c.visual.cut_off_limit_file
516
514
517 c.missing_commits = False
515 c.missing_commits = False
518 if (c.missing_requirements
516 if (c.missing_requirements
519 or isinstance(source_commit, EmptyCommit)
517 or isinstance(source_commit, EmptyCommit)
520 or source_commit == target_commit):
518 or source_commit == target_commit):
521
519
522 c.missing_commits = True
520 c.missing_commits = True
523 else:
521 else:
524
522
525 c.diffset = self._get_diffset(
523 c.diffset = self._get_diffset(
526 c.source_repo.repo_name, commits_source_repo,
524 c.source_repo.repo_name, commits_source_repo,
527 source_ref_id, target_ref_id,
525 source_ref_id, target_ref_id,
528 target_commit, source_commit,
526 target_commit, source_commit,
529 diff_limit, c.fulldiff, file_limit, display_inline_comments)
527 diff_limit, c.fulldiff, file_limit, display_inline_comments)
530
528
531 c.limited_diff = c.diffset.limited_diff
529 c.limited_diff = c.diffset.limited_diff
532
530
533 # calculate removed files that are bound to comments
531 # calculate removed files that are bound to comments
534 comment_deleted_files = [
532 comment_deleted_files = [
535 fname for fname in display_inline_comments
533 fname for fname in display_inline_comments
536 if fname not in c.diffset.file_stats]
534 if fname not in c.diffset.file_stats]
537
535
538 c.deleted_files_comments = collections.defaultdict(dict)
536 c.deleted_files_comments = collections.defaultdict(dict)
539 for fname, per_line_comments in display_inline_comments.items():
537 for fname, per_line_comments in display_inline_comments.items():
540 if fname in comment_deleted_files:
538 if fname in comment_deleted_files:
541 c.deleted_files_comments[fname]['stats'] = 0
539 c.deleted_files_comments[fname]['stats'] = 0
542 c.deleted_files_comments[fname]['comments'] = list()
540 c.deleted_files_comments[fname]['comments'] = list()
543 for lno, comments in per_line_comments.items():
541 for lno, comments in per_line_comments.items():
544 c.deleted_files_comments[fname]['comments'].extend(
542 c.deleted_files_comments[fname]['comments'].extend(
545 comments)
543 comments)
546
544
547 # this is a hack to properly display links, when creating PR, the
545 # this is a hack to properly display links, when creating PR, the
548 # compare view and others uses different notation, and
546 # compare view and others uses different notation, and
549 # compare_commits.mako renders links based on the target_repo.
547 # compare_commits.mako renders links based on the target_repo.
550 # We need to swap that here to generate it properly on the html side
548 # We need to swap that here to generate it properly on the html side
551 c.target_repo = c.source_repo
549 c.target_repo = c.source_repo
552
550
553 c.commit_statuses = ChangesetStatus.STATUSES
551 c.commit_statuses = ChangesetStatus.STATUSES
554
552
555 c.show_version_changes = not pr_closed
553 c.show_version_changes = not pr_closed
556 if c.show_version_changes:
554 if c.show_version_changes:
557 cur_obj = pull_request_at_ver
555 cur_obj = pull_request_at_ver
558 prev_obj = prev_pull_request_at_ver
556 prev_obj = prev_pull_request_at_ver
559
557
560 old_commit_ids = prev_obj.revisions
558 old_commit_ids = prev_obj.revisions
561 new_commit_ids = cur_obj.revisions
559 new_commit_ids = cur_obj.revisions
562 commit_changes = PullRequestModel()._calculate_commit_id_changes(
560 commit_changes = PullRequestModel()._calculate_commit_id_changes(
563 old_commit_ids, new_commit_ids)
561 old_commit_ids, new_commit_ids)
564 c.commit_changes_summary = commit_changes
562 c.commit_changes_summary = commit_changes
565
563
566 # calculate the diff for commits between versions
564 # calculate the diff for commits between versions
567 c.commit_changes = []
565 c.commit_changes = []
568 mark = lambda cs, fw: list(
566 mark = lambda cs, fw: list(
569 h.itertools.izip_longest([], cs, fillvalue=fw))
567 h.itertools.izip_longest([], cs, fillvalue=fw))
570 for c_type, raw_id in mark(commit_changes.added, 'a') \
568 for c_type, raw_id in mark(commit_changes.added, 'a') \
571 + mark(commit_changes.removed, 'r') \
569 + mark(commit_changes.removed, 'r') \
572 + mark(commit_changes.common, 'c'):
570 + mark(commit_changes.common, 'c'):
573
571
574 if raw_id in commit_cache:
572 if raw_id in commit_cache:
575 commit = commit_cache[raw_id]
573 commit = commit_cache[raw_id]
576 else:
574 else:
577 try:
575 try:
578 commit = commits_source_repo.get_commit(raw_id)
576 commit = commits_source_repo.get_commit(raw_id)
579 except CommitDoesNotExistError:
577 except CommitDoesNotExistError:
580 # in case we fail extracting still use "dummy" commit
578 # in case we fail extracting still use "dummy" commit
581 # for display in commit diff
579 # for display in commit diff
582 commit = h.AttributeDict(
580 commit = h.AttributeDict(
583 {'raw_id': raw_id,
581 {'raw_id': raw_id,
584 'message': 'EMPTY or MISSING COMMIT'})
582 'message': 'EMPTY or MISSING COMMIT'})
585 c.commit_changes.append([c_type, commit])
583 c.commit_changes.append([c_type, commit])
586
584
587 # current user review statuses for each version
585 # current user review statuses for each version
588 c.review_versions = {}
586 c.review_versions = {}
589 if self._rhodecode_user.user_id in allowed_reviewers:
587 if self._rhodecode_user.user_id in allowed_reviewers:
590 for co in general_comments:
588 for co in general_comments:
591 if co.author.user_id == self._rhodecode_user.user_id:
589 if co.author.user_id == self._rhodecode_user.user_id:
592 # each comment has a status change
590 # each comment has a status change
593 status = co.status_change
591 status = co.status_change
594 if status:
592 if status:
595 _ver_pr = status[0].comment.pull_request_version_id
593 _ver_pr = status[0].comment.pull_request_version_id
596 c.review_versions[_ver_pr] = status[0]
594 c.review_versions[_ver_pr] = status[0]
597
595
598 return self._get_template_context(c)
596 return self._get_template_context(c)
599
597
600 def assure_not_empty_repo(self):
598 def assure_not_empty_repo(self):
601 _ = self.request.translate
599 _ = self.request.translate
602
600
603 try:
601 try:
604 self.db_repo.scm_instance().get_commit()
602 self.db_repo.scm_instance().get_commit()
605 except EmptyRepositoryError:
603 except EmptyRepositoryError:
606 h.flash(h.literal(_('There are no commits yet')),
604 h.flash(h.literal(_('There are no commits yet')),
607 category='warning')
605 category='warning')
608 raise HTTPFound(
606 raise HTTPFound(
609 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
607 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
610
608
611 @LoginRequired()
609 @LoginRequired()
612 @NotAnonymous()
610 @NotAnonymous()
613 @HasRepoPermissionAnyDecorator(
611 @HasRepoPermissionAnyDecorator(
614 'repository.read', 'repository.write', 'repository.admin')
612 'repository.read', 'repository.write', 'repository.admin')
615 @view_config(
613 @view_config(
616 route_name='pullrequest_new', request_method='GET',
614 route_name='pullrequest_new', request_method='GET',
617 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
615 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
618 def pull_request_new(self):
616 def pull_request_new(self):
619 _ = self.request.translate
617 _ = self.request.translate
620 c = self.load_default_context()
618 c = self.load_default_context()
621
619
622 self.assure_not_empty_repo()
620 self.assure_not_empty_repo()
623 source_repo = self.db_repo
621 source_repo = self.db_repo
624
622
625 commit_id = self.request.GET.get('commit')
623 commit_id = self.request.GET.get('commit')
626 branch_ref = self.request.GET.get('branch')
624 branch_ref = self.request.GET.get('branch')
627 bookmark_ref = self.request.GET.get('bookmark')
625 bookmark_ref = self.request.GET.get('bookmark')
628
626
629 try:
627 try:
630 source_repo_data = PullRequestModel().generate_repo_data(
628 source_repo_data = PullRequestModel().generate_repo_data(
631 source_repo, commit_id=commit_id,
629 source_repo, commit_id=commit_id,
632 branch=branch_ref, bookmark=bookmark_ref)
630 branch=branch_ref, bookmark=bookmark_ref)
633 except CommitDoesNotExistError as e:
631 except CommitDoesNotExistError as e:
634 log.exception(e)
632 log.exception(e)
635 h.flash(_('Commit does not exist'), 'error')
633 h.flash(_('Commit does not exist'), 'error')
636 raise HTTPFound(
634 raise HTTPFound(
637 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
635 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
638
636
639 default_target_repo = source_repo
637 default_target_repo = source_repo
640
638
641 if source_repo.parent:
639 if source_repo.parent:
642 parent_vcs_obj = source_repo.parent.scm_instance()
640 parent_vcs_obj = source_repo.parent.scm_instance()
643 if parent_vcs_obj and not parent_vcs_obj.is_empty():
641 if parent_vcs_obj and not parent_vcs_obj.is_empty():
644 # change default if we have a parent repo
642 # change default if we have a parent repo
645 default_target_repo = source_repo.parent
643 default_target_repo = source_repo.parent
646
644
647 target_repo_data = PullRequestModel().generate_repo_data(
645 target_repo_data = PullRequestModel().generate_repo_data(
648 default_target_repo)
646 default_target_repo)
649
647
650 selected_source_ref = source_repo_data['refs']['selected_ref']
648 selected_source_ref = source_repo_data['refs']['selected_ref']
651
649
652 title_source_ref = selected_source_ref.split(':', 2)[1]
650 title_source_ref = selected_source_ref.split(':', 2)[1]
653 c.default_title = PullRequestModel().generate_pullrequest_title(
651 c.default_title = PullRequestModel().generate_pullrequest_title(
654 source=source_repo.repo_name,
652 source=source_repo.repo_name,
655 source_ref=title_source_ref,
653 source_ref=title_source_ref,
656 target=default_target_repo.repo_name
654 target=default_target_repo.repo_name
657 )
655 )
658
656
659 c.default_repo_data = {
657 c.default_repo_data = {
660 'source_repo_name': source_repo.repo_name,
658 'source_repo_name': source_repo.repo_name,
661 'source_refs_json': json.dumps(source_repo_data),
659 'source_refs_json': json.dumps(source_repo_data),
662 'target_repo_name': default_target_repo.repo_name,
660 'target_repo_name': default_target_repo.repo_name,
663 'target_refs_json': json.dumps(target_repo_data),
661 'target_refs_json': json.dumps(target_repo_data),
664 }
662 }
665 c.default_source_ref = selected_source_ref
663 c.default_source_ref = selected_source_ref
666
664
667 return self._get_template_context(c)
665 return self._get_template_context(c)
668
666
669 @LoginRequired()
667 @LoginRequired()
670 @NotAnonymous()
668 @NotAnonymous()
671 @HasRepoPermissionAnyDecorator(
669 @HasRepoPermissionAnyDecorator(
672 'repository.read', 'repository.write', 'repository.admin')
670 'repository.read', 'repository.write', 'repository.admin')
673 @view_config(
671 @view_config(
674 route_name='pullrequest_repo_refs', request_method='GET',
672 route_name='pullrequest_repo_refs', request_method='GET',
675 renderer='json_ext', xhr=True)
673 renderer='json_ext', xhr=True)
676 def pull_request_repo_refs(self):
674 def pull_request_repo_refs(self):
677 target_repo_name = self.request.matchdict['target_repo_name']
675 target_repo_name = self.request.matchdict['target_repo_name']
678 repo = Repository.get_by_repo_name(target_repo_name)
676 repo = Repository.get_by_repo_name(target_repo_name)
679 if not repo:
677 if not repo:
680 raise HTTPNotFound()
678 raise HTTPNotFound()
681 return PullRequestModel().generate_repo_data(repo)
679 return PullRequestModel().generate_repo_data(repo)
682
680
683 @LoginRequired()
681 @LoginRequired()
684 @NotAnonymous()
682 @NotAnonymous()
685 @HasRepoPermissionAnyDecorator(
683 @HasRepoPermissionAnyDecorator(
686 'repository.read', 'repository.write', 'repository.admin')
684 'repository.read', 'repository.write', 'repository.admin')
687 @view_config(
685 @view_config(
688 route_name='pullrequest_repo_destinations', request_method='GET',
686 route_name='pullrequest_repo_destinations', request_method='GET',
689 renderer='json_ext', xhr=True)
687 renderer='json_ext', xhr=True)
690 def pull_request_repo_destinations(self):
688 def pull_request_repo_destinations(self):
691 _ = self.request.translate
689 _ = self.request.translate
692 filter_query = self.request.GET.get('query')
690 filter_query = self.request.GET.get('query')
693
691
694 query = Repository.query() \
692 query = Repository.query() \
695 .order_by(func.length(Repository.repo_name)) \
693 .order_by(func.length(Repository.repo_name)) \
696 .filter(
694 .filter(
697 or_(Repository.repo_name == self.db_repo.repo_name,
695 or_(Repository.repo_name == self.db_repo.repo_name,
698 Repository.fork_id == self.db_repo.repo_id))
696 Repository.fork_id == self.db_repo.repo_id))
699
697
700 if filter_query:
698 if filter_query:
701 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
699 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
702 query = query.filter(
700 query = query.filter(
703 Repository.repo_name.ilike(ilike_expression))
701 Repository.repo_name.ilike(ilike_expression))
704
702
705 add_parent = False
703 add_parent = False
706 if self.db_repo.parent:
704 if self.db_repo.parent:
707 if filter_query in self.db_repo.parent.repo_name:
705 if filter_query in self.db_repo.parent.repo_name:
708 parent_vcs_obj = self.db_repo.parent.scm_instance()
706 parent_vcs_obj = self.db_repo.parent.scm_instance()
709 if parent_vcs_obj and not parent_vcs_obj.is_empty():
707 if parent_vcs_obj and not parent_vcs_obj.is_empty():
710 add_parent = True
708 add_parent = True
711
709
712 limit = 20 - 1 if add_parent else 20
710 limit = 20 - 1 if add_parent else 20
713 all_repos = query.limit(limit).all()
711 all_repos = query.limit(limit).all()
714 if add_parent:
712 if add_parent:
715 all_repos += [self.db_repo.parent]
713 all_repos += [self.db_repo.parent]
716
714
717 repos = []
715 repos = []
718 for obj in ScmModel().get_repos(all_repos):
716 for obj in ScmModel().get_repos(all_repos):
719 repos.append({
717 repos.append({
720 'id': obj['name'],
718 'id': obj['name'],
721 'text': obj['name'],
719 'text': obj['name'],
722 'type': 'repo',
720 'type': 'repo',
723 'obj': obj['dbrepo']
721 'obj': obj['dbrepo']
724 })
722 })
725
723
726 data = {
724 data = {
727 'more': False,
725 'more': False,
728 'results': [{
726 'results': [{
729 'text': _('Repositories'),
727 'text': _('Repositories'),
730 'children': repos
728 'children': repos
731 }] if repos else []
729 }] if repos else []
732 }
730 }
733 return data
731 return data
734
732
735 @LoginRequired()
733 @LoginRequired()
736 @NotAnonymous()
734 @NotAnonymous()
737 @HasRepoPermissionAnyDecorator(
735 @HasRepoPermissionAnyDecorator(
738 'repository.read', 'repository.write', 'repository.admin')
736 'repository.read', 'repository.write', 'repository.admin')
739 @CSRFRequired()
737 @CSRFRequired()
740 @view_config(
738 @view_config(
741 route_name='pullrequest_create', request_method='POST',
739 route_name='pullrequest_create', request_method='POST',
742 renderer=None)
740 renderer=None)
743 def pull_request_create(self):
741 def pull_request_create(self):
744 _ = self.request.translate
742 _ = self.request.translate
745 self.assure_not_empty_repo()
743 self.assure_not_empty_repo()
746
744
747 controls = peppercorn.parse(self.request.POST.items())
745 controls = peppercorn.parse(self.request.POST.items())
748
746
749 try:
747 try:
750 _form = PullRequestForm(self.db_repo.repo_id)().to_python(controls)
748 _form = PullRequestForm(self.db_repo.repo_id)().to_python(controls)
751 except formencode.Invalid as errors:
749 except formencode.Invalid as errors:
752 if errors.error_dict.get('revisions'):
750 if errors.error_dict.get('revisions'):
753 msg = 'Revisions: %s' % errors.error_dict['revisions']
751 msg = 'Revisions: %s' % errors.error_dict['revisions']
754 elif errors.error_dict.get('pullrequest_title'):
752 elif errors.error_dict.get('pullrequest_title'):
755 msg = _('Pull request requires a title with min. 3 chars')
753 msg = _('Pull request requires a title with min. 3 chars')
756 else:
754 else:
757 msg = _('Error creating pull request: {}').format(errors)
755 msg = _('Error creating pull request: {}').format(errors)
758 log.exception(msg)
756 log.exception(msg)
759 h.flash(msg, 'error')
757 h.flash(msg, 'error')
760
758
761 # would rather just go back to form ...
759 # would rather just go back to form ...
762 raise HTTPFound(
760 raise HTTPFound(
763 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
761 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
764
762
765 source_repo = _form['source_repo']
763 source_repo = _form['source_repo']
766 source_ref = _form['source_ref']
764 source_ref = _form['source_ref']
767 target_repo = _form['target_repo']
765 target_repo = _form['target_repo']
768 target_ref = _form['target_ref']
766 target_ref = _form['target_ref']
769 commit_ids = _form['revisions'][::-1]
767 commit_ids = _form['revisions'][::-1]
770
768
771 # find the ancestor for this pr
769 # find the ancestor for this pr
772 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
770 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
773 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
771 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
774
772
775 source_scm = source_db_repo.scm_instance()
773 source_scm = source_db_repo.scm_instance()
776 target_scm = target_db_repo.scm_instance()
774 target_scm = target_db_repo.scm_instance()
777
775
778 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
776 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
779 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
777 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
780
778
781 ancestor = source_scm.get_common_ancestor(
779 ancestor = source_scm.get_common_ancestor(
782 source_commit.raw_id, target_commit.raw_id, target_scm)
780 source_commit.raw_id, target_commit.raw_id, target_scm)
783
781
784 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
782 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
785 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
783 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
786
784
787 pullrequest_title = _form['pullrequest_title']
785 pullrequest_title = _form['pullrequest_title']
788 title_source_ref = source_ref.split(':', 2)[1]
786 title_source_ref = source_ref.split(':', 2)[1]
789 if not pullrequest_title:
787 if not pullrequest_title:
790 pullrequest_title = PullRequestModel().generate_pullrequest_title(
788 pullrequest_title = PullRequestModel().generate_pullrequest_title(
791 source=source_repo,
789 source=source_repo,
792 source_ref=title_source_ref,
790 source_ref=title_source_ref,
793 target=target_repo
791 target=target_repo
794 )
792 )
795
793
796 description = _form['pullrequest_desc']
794 description = _form['pullrequest_desc']
797
795
798 get_default_reviewers_data, validate_default_reviewers = \
796 get_default_reviewers_data, validate_default_reviewers = \
799 PullRequestModel().get_reviewer_functions()
797 PullRequestModel().get_reviewer_functions()
800
798
801 # recalculate reviewers logic, to make sure we can validate this
799 # recalculate reviewers logic, to make sure we can validate this
802 reviewer_rules = get_default_reviewers_data(
800 reviewer_rules = get_default_reviewers_data(
803 self._rhodecode_db_user, source_db_repo,
801 self._rhodecode_db_user, source_db_repo,
804 source_commit, target_db_repo, target_commit)
802 source_commit, target_db_repo, target_commit)
805
803
806 given_reviewers = _form['review_members']
804 given_reviewers = _form['review_members']
807 reviewers = validate_default_reviewers(given_reviewers, reviewer_rules)
805 reviewers = validate_default_reviewers(given_reviewers, reviewer_rules)
808
806
809 try:
807 try:
810 pull_request = PullRequestModel().create(
808 pull_request = PullRequestModel().create(
811 self._rhodecode_user.user_id, source_repo, source_ref, target_repo,
809 self._rhodecode_user.user_id, source_repo, source_ref, target_repo,
812 target_ref, commit_ids, reviewers, pullrequest_title,
810 target_ref, commit_ids, reviewers, pullrequest_title,
813 description, reviewer_rules
811 description, reviewer_rules
814 )
812 )
815 Session().commit()
813 Session().commit()
816 h.flash(_('Successfully opened new pull request'),
814 h.flash(_('Successfully opened new pull request'),
817 category='success')
815 category='success')
818 except Exception:
816 except Exception:
819 msg = _('Error occurred during creation of this pull request.')
817 msg = _('Error occurred during creation of this pull request.')
820 log.exception(msg)
818 log.exception(msg)
821 h.flash(msg, category='error')
819 h.flash(msg, category='error')
822
820
823 # copy the args back to redirect
821 # copy the args back to redirect
824 org_query = self.request.GET.mixed()
822 org_query = self.request.GET.mixed()
825 raise HTTPFound(
823 raise HTTPFound(
826 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
824 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
827 _query=org_query))
825 _query=org_query))
828
826
829 raise HTTPFound(
827 raise HTTPFound(
830 h.route_path('pullrequest_show', repo_name=target_repo,
828 h.route_path('pullrequest_show', repo_name=target_repo,
831 pull_request_id=pull_request.pull_request_id))
829 pull_request_id=pull_request.pull_request_id))
832
830
833 @LoginRequired()
831 @LoginRequired()
834 @NotAnonymous()
832 @NotAnonymous()
835 @HasRepoPermissionAnyDecorator(
833 @HasRepoPermissionAnyDecorator(
836 'repository.read', 'repository.write', 'repository.admin')
834 'repository.read', 'repository.write', 'repository.admin')
837 @CSRFRequired()
835 @CSRFRequired()
838 @view_config(
836 @view_config(
839 route_name='pullrequest_update', request_method='POST',
837 route_name='pullrequest_update', request_method='POST',
840 renderer='json_ext')
838 renderer='json_ext')
841 def pull_request_update(self):
839 def pull_request_update(self):
842 pull_request = PullRequest.get_or_404(
840 pull_request = PullRequest.get_or_404(
843 self.request.matchdict['pull_request_id'])
841 self.request.matchdict['pull_request_id'])
844
842
845 # only owner or admin can update it
843 # only owner or admin can update it
846 allowed_to_update = PullRequestModel().check_user_update(
844 allowed_to_update = PullRequestModel().check_user_update(
847 pull_request, self._rhodecode_user)
845 pull_request, self._rhodecode_user)
848 if allowed_to_update:
846 if allowed_to_update:
849 controls = peppercorn.parse(self.request.POST.items())
847 controls = peppercorn.parse(self.request.POST.items())
850
848
851 if 'review_members' in controls:
849 if 'review_members' in controls:
852 self._update_reviewers(
850 self._update_reviewers(
853 pull_request, controls['review_members'],
851 pull_request, controls['review_members'],
854 pull_request.reviewer_data)
852 pull_request.reviewer_data)
855 elif str2bool(self.request.POST.get('update_commits', 'false')):
853 elif str2bool(self.request.POST.get('update_commits', 'false')):
856 self._update_commits(pull_request)
854 self._update_commits(pull_request)
857 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
855 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
858 self._edit_pull_request(pull_request)
856 self._edit_pull_request(pull_request)
859 else:
857 else:
860 raise HTTPBadRequest()
858 raise HTTPBadRequest()
861 return True
859 return True
862 raise HTTPForbidden()
860 raise HTTPForbidden()
863
861
864 def _edit_pull_request(self, pull_request):
862 def _edit_pull_request(self, pull_request):
865 _ = self.request.translate
863 _ = self.request.translate
866 try:
864 try:
867 PullRequestModel().edit(
865 PullRequestModel().edit(
868 pull_request, self.request.POST.get('title'),
866 pull_request, self.request.POST.get('title'),
869 self.request.POST.get('description'), self._rhodecode_user)
867 self.request.POST.get('description'), self._rhodecode_user)
870 except ValueError:
868 except ValueError:
871 msg = _(u'Cannot update closed pull requests.')
869 msg = _(u'Cannot update closed pull requests.')
872 h.flash(msg, category='error')
870 h.flash(msg, category='error')
873 return
871 return
874 else:
872 else:
875 Session().commit()
873 Session().commit()
876
874
877 msg = _(u'Pull request title & description updated.')
875 msg = _(u'Pull request title & description updated.')
878 h.flash(msg, category='success')
876 h.flash(msg, category='success')
879 return
877 return
880
878
881 def _update_commits(self, pull_request):
879 def _update_commits(self, pull_request):
882 _ = self.request.translate
880 _ = self.request.translate
883 resp = PullRequestModel().update_commits(pull_request)
881 resp = PullRequestModel().update_commits(pull_request)
884
882
885 if resp.executed:
883 if resp.executed:
886
884
887 if resp.target_changed and resp.source_changed:
885 if resp.target_changed and resp.source_changed:
888 changed = 'target and source repositories'
886 changed = 'target and source repositories'
889 elif resp.target_changed and not resp.source_changed:
887 elif resp.target_changed and not resp.source_changed:
890 changed = 'target repository'
888 changed = 'target repository'
891 elif not resp.target_changed and resp.source_changed:
889 elif not resp.target_changed and resp.source_changed:
892 changed = 'source repository'
890 changed = 'source repository'
893 else:
891 else:
894 changed = 'nothing'
892 changed = 'nothing'
895
893
896 msg = _(
894 msg = _(
897 u'Pull request updated to "{source_commit_id}" with '
895 u'Pull request updated to "{source_commit_id}" with '
898 u'{count_added} added, {count_removed} removed commits. '
896 u'{count_added} added, {count_removed} removed commits. '
899 u'Source of changes: {change_source}')
897 u'Source of changes: {change_source}')
900 msg = msg.format(
898 msg = msg.format(
901 source_commit_id=pull_request.source_ref_parts.commit_id,
899 source_commit_id=pull_request.source_ref_parts.commit_id,
902 count_added=len(resp.changes.added),
900 count_added=len(resp.changes.added),
903 count_removed=len(resp.changes.removed),
901 count_removed=len(resp.changes.removed),
904 change_source=changed)
902 change_source=changed)
905 h.flash(msg, category='success')
903 h.flash(msg, category='success')
906
904
907 channel = '/repo${}$/pr/{}'.format(
905 channel = '/repo${}$/pr/{}'.format(
908 pull_request.target_repo.repo_name,
906 pull_request.target_repo.repo_name,
909 pull_request.pull_request_id)
907 pull_request.pull_request_id)
910 message = msg + (
908 message = msg + (
911 ' - <a onclick="window.location.reload()">'
909 ' - <a onclick="window.location.reload()">'
912 '<strong>{}</strong></a>'.format(_('Reload page')))
910 '<strong>{}</strong></a>'.format(_('Reload page')))
913 channelstream.post_message(
911 channelstream.post_message(
914 channel, message, self._rhodecode_user.username,
912 channel, message, self._rhodecode_user.username,
915 registry=self.request.registry)
913 registry=self.request.registry)
916 else:
914 else:
917 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
915 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
918 warning_reasons = [
916 warning_reasons = [
919 UpdateFailureReason.NO_CHANGE,
917 UpdateFailureReason.NO_CHANGE,
920 UpdateFailureReason.WRONG_REF_TYPE,
918 UpdateFailureReason.WRONG_REF_TYPE,
921 ]
919 ]
922 category = 'warning' if resp.reason in warning_reasons else 'error'
920 category = 'warning' if resp.reason in warning_reasons else 'error'
923 h.flash(msg, category=category)
921 h.flash(msg, category=category)
924
922
925 @LoginRequired()
923 @LoginRequired()
926 @NotAnonymous()
924 @NotAnonymous()
927 @HasRepoPermissionAnyDecorator(
925 @HasRepoPermissionAnyDecorator(
928 'repository.read', 'repository.write', 'repository.admin')
926 'repository.read', 'repository.write', 'repository.admin')
929 @CSRFRequired()
927 @CSRFRequired()
930 @view_config(
928 @view_config(
931 route_name='pullrequest_merge', request_method='POST',
929 route_name='pullrequest_merge', request_method='POST',
932 renderer='json_ext')
930 renderer='json_ext')
933 def pull_request_merge(self):
931 def pull_request_merge(self):
934 """
932 """
935 Merge will perform a server-side merge of the specified
933 Merge will perform a server-side merge of the specified
936 pull request, if the pull request is approved and mergeable.
934 pull request, if the pull request is approved and mergeable.
937 After successful merging, the pull request is automatically
935 After successful merging, the pull request is automatically
938 closed, with a relevant comment.
936 closed, with a relevant comment.
939 """
937 """
940 pull_request = PullRequest.get_or_404(
938 pull_request = PullRequest.get_or_404(
941 self.request.matchdict['pull_request_id'])
939 self.request.matchdict['pull_request_id'])
942
940
943 check = MergeCheck.validate(pull_request, self._rhodecode_db_user)
941 check = MergeCheck.validate(pull_request, self._rhodecode_db_user)
944 merge_possible = not check.failed
942 merge_possible = not check.failed
945
943
946 for err_type, error_msg in check.errors:
944 for err_type, error_msg in check.errors:
947 h.flash(error_msg, category=err_type)
945 h.flash(error_msg, category=err_type)
948
946
949 if merge_possible:
947 if merge_possible:
950 log.debug("Pre-conditions checked, trying to merge.")
948 log.debug("Pre-conditions checked, trying to merge.")
951 extras = vcs_operation_context(
949 extras = vcs_operation_context(
952 self.request.environ, repo_name=pull_request.target_repo.repo_name,
950 self.request.environ, repo_name=pull_request.target_repo.repo_name,
953 username=self._rhodecode_db_user.username, action='push',
951 username=self._rhodecode_db_user.username, action='push',
954 scm=pull_request.target_repo.repo_type)
952 scm=pull_request.target_repo.repo_type)
955 self._merge_pull_request(
953 self._merge_pull_request(
956 pull_request, self._rhodecode_db_user, extras)
954 pull_request, self._rhodecode_db_user, extras)
957 else:
955 else:
958 log.debug("Pre-conditions failed, NOT merging.")
956 log.debug("Pre-conditions failed, NOT merging.")
959
957
960 raise HTTPFound(
958 raise HTTPFound(
961 h.route_path('pullrequest_show',
959 h.route_path('pullrequest_show',
962 repo_name=pull_request.target_repo.repo_name,
960 repo_name=pull_request.target_repo.repo_name,
963 pull_request_id=pull_request.pull_request_id))
961 pull_request_id=pull_request.pull_request_id))
964
962
965 def _merge_pull_request(self, pull_request, user, extras):
963 def _merge_pull_request(self, pull_request, user, extras):
966 _ = self.request.translate
964 _ = self.request.translate
967 merge_resp = PullRequestModel().merge(pull_request, user, extras=extras)
965 merge_resp = PullRequestModel().merge(pull_request, user, extras=extras)
968
966
969 if merge_resp.executed:
967 if merge_resp.executed:
970 log.debug("The merge was successful, closing the pull request.")
968 log.debug("The merge was successful, closing the pull request.")
971 PullRequestModel().close_pull_request(
969 PullRequestModel().close_pull_request(
972 pull_request.pull_request_id, user)
970 pull_request.pull_request_id, user)
973 Session().commit()
971 Session().commit()
974 msg = _('Pull request was successfully merged and closed.')
972 msg = _('Pull request was successfully merged and closed.')
975 h.flash(msg, category='success')
973 h.flash(msg, category='success')
976 else:
974 else:
977 log.debug(
975 log.debug(
978 "The merge was not successful. Merge response: %s",
976 "The merge was not successful. Merge response: %s",
979 merge_resp)
977 merge_resp)
980 msg = PullRequestModel().merge_status_message(
978 msg = PullRequestModel().merge_status_message(
981 merge_resp.failure_reason)
979 merge_resp.failure_reason)
982 h.flash(msg, category='error')
980 h.flash(msg, category='error')
983
981
984 def _update_reviewers(self, pull_request, review_members, reviewer_rules):
982 def _update_reviewers(self, pull_request, review_members, reviewer_rules):
985 _ = self.request.translate
983 _ = self.request.translate
986 get_default_reviewers_data, validate_default_reviewers = \
984 get_default_reviewers_data, validate_default_reviewers = \
987 PullRequestModel().get_reviewer_functions()
985 PullRequestModel().get_reviewer_functions()
988
986
989 try:
987 try:
990 reviewers = validate_default_reviewers(review_members, reviewer_rules)
988 reviewers = validate_default_reviewers(review_members, reviewer_rules)
991 except ValueError as e:
989 except ValueError as e:
992 log.error('Reviewers Validation: {}'.format(e))
990 log.error('Reviewers Validation: {}'.format(e))
993 h.flash(e, category='error')
991 h.flash(e, category='error')
994 return
992 return
995
993
996 PullRequestModel().update_reviewers(
994 PullRequestModel().update_reviewers(
997 pull_request, reviewers, self._rhodecode_user)
995 pull_request, reviewers, self._rhodecode_user)
998 h.flash(_('Pull request reviewers updated.'), category='success')
996 h.flash(_('Pull request reviewers updated.'), category='success')
999 Session().commit()
997 Session().commit()
1000
998
1001 @LoginRequired()
999 @LoginRequired()
1002 @NotAnonymous()
1000 @NotAnonymous()
1003 @HasRepoPermissionAnyDecorator(
1001 @HasRepoPermissionAnyDecorator(
1004 'repository.read', 'repository.write', 'repository.admin')
1002 'repository.read', 'repository.write', 'repository.admin')
1005 @CSRFRequired()
1003 @CSRFRequired()
1006 @view_config(
1004 @view_config(
1007 route_name='pullrequest_delete', request_method='POST',
1005 route_name='pullrequest_delete', request_method='POST',
1008 renderer='json_ext')
1006 renderer='json_ext')
1009 def pull_request_delete(self):
1007 def pull_request_delete(self):
1010 _ = self.request.translate
1008 _ = self.request.translate
1011
1009
1012 pull_request = PullRequest.get_or_404(
1010 pull_request = PullRequest.get_or_404(
1013 self.request.matchdict['pull_request_id'])
1011 self.request.matchdict['pull_request_id'])
1014
1012
1015 pr_closed = pull_request.is_closed()
1013 pr_closed = pull_request.is_closed()
1016 allowed_to_delete = PullRequestModel().check_user_delete(
1014 allowed_to_delete = PullRequestModel().check_user_delete(
1017 pull_request, self._rhodecode_user) and not pr_closed
1015 pull_request, self._rhodecode_user) and not pr_closed
1018
1016
1019 # only owner can delete it !
1017 # only owner can delete it !
1020 if allowed_to_delete:
1018 if allowed_to_delete:
1021 PullRequestModel().delete(pull_request, self._rhodecode_user)
1019 PullRequestModel().delete(pull_request, self._rhodecode_user)
1022 Session().commit()
1020 Session().commit()
1023 h.flash(_('Successfully deleted pull request'),
1021 h.flash(_('Successfully deleted pull request'),
1024 category='success')
1022 category='success')
1025 raise HTTPFound(h.route_path('pullrequest_show_all',
1023 raise HTTPFound(h.route_path('pullrequest_show_all',
1026 repo_name=self.db_repo_name))
1024 repo_name=self.db_repo_name))
1027
1025
1028 log.warning('user %s tried to delete pull request without access',
1026 log.warning('user %s tried to delete pull request without access',
1029 self._rhodecode_user)
1027 self._rhodecode_user)
1030 raise HTTPNotFound()
1028 raise HTTPNotFound()
1031
1029
1032 @LoginRequired()
1030 @LoginRequired()
1033 @NotAnonymous()
1031 @NotAnonymous()
1034 @HasRepoPermissionAnyDecorator(
1032 @HasRepoPermissionAnyDecorator(
1035 'repository.read', 'repository.write', 'repository.admin')
1033 'repository.read', 'repository.write', 'repository.admin')
1036 @CSRFRequired()
1034 @CSRFRequired()
1037 @view_config(
1035 @view_config(
1038 route_name='pullrequest_comment_create', request_method='POST',
1036 route_name='pullrequest_comment_create', request_method='POST',
1039 renderer='json_ext')
1037 renderer='json_ext')
1040 def pull_request_comment_create(self):
1038 def pull_request_comment_create(self):
1041 _ = self.request.translate
1039 _ = self.request.translate
1042
1040
1043 pull_request = PullRequest.get_or_404(
1041 pull_request = PullRequest.get_or_404(
1044 self.request.matchdict['pull_request_id'])
1042 self.request.matchdict['pull_request_id'])
1045 pull_request_id = pull_request.pull_request_id
1043 pull_request_id = pull_request.pull_request_id
1046
1044
1047 if pull_request.is_closed():
1045 if pull_request.is_closed():
1048 log.debug('comment: forbidden because pull request is closed')
1046 log.debug('comment: forbidden because pull request is closed')
1049 raise HTTPForbidden()
1047 raise HTTPForbidden()
1050
1048
1051 c = self.load_default_context()
1049 c = self.load_default_context()
1052
1050
1053 status = self.request.POST.get('changeset_status', None)
1051 status = self.request.POST.get('changeset_status', None)
1054 text = self.request.POST.get('text')
1052 text = self.request.POST.get('text')
1055 comment_type = self.request.POST.get('comment_type')
1053 comment_type = self.request.POST.get('comment_type')
1056 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
1054 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
1057 close_pull_request = self.request.POST.get('close_pull_request')
1055 close_pull_request = self.request.POST.get('close_pull_request')
1058
1056
1059 # the logic here should work like following, if we submit close
1057 # the logic here should work like following, if we submit close
1060 # pr comment, use `close_pull_request_with_comment` function
1058 # pr comment, use `close_pull_request_with_comment` function
1061 # else handle regular comment logic
1059 # else handle regular comment logic
1062
1060
1063 if close_pull_request:
1061 if close_pull_request:
1064 # only owner or admin or person with write permissions
1062 # only owner or admin or person with write permissions
1065 allowed_to_close = PullRequestModel().check_user_update(
1063 allowed_to_close = PullRequestModel().check_user_update(
1066 pull_request, self._rhodecode_user)
1064 pull_request, self._rhodecode_user)
1067 if not allowed_to_close:
1065 if not allowed_to_close:
1068 log.debug('comment: forbidden because not allowed to close '
1066 log.debug('comment: forbidden because not allowed to close '
1069 'pull request %s', pull_request_id)
1067 'pull request %s', pull_request_id)
1070 raise HTTPForbidden()
1068 raise HTTPForbidden()
1071 comment, status = PullRequestModel().close_pull_request_with_comment(
1069 comment, status = PullRequestModel().close_pull_request_with_comment(
1072 pull_request, self._rhodecode_user, self.db_repo, message=text)
1070 pull_request, self._rhodecode_user, self.db_repo, message=text)
1073 Session().flush()
1071 Session().flush()
1074 events.trigger(
1072 events.trigger(
1075 events.PullRequestCommentEvent(pull_request, comment))
1073 events.PullRequestCommentEvent(pull_request, comment))
1076
1074
1077 else:
1075 else:
1078 # regular comment case, could be inline, or one with status.
1076 # regular comment case, could be inline, or one with status.
1079 # for that one we check also permissions
1077 # for that one we check also permissions
1080
1078
1081 allowed_to_change_status = PullRequestModel().check_user_change_status(
1079 allowed_to_change_status = PullRequestModel().check_user_change_status(
1082 pull_request, self._rhodecode_user)
1080 pull_request, self._rhodecode_user)
1083
1081
1084 if status and allowed_to_change_status:
1082 if status and allowed_to_change_status:
1085 message = (_('Status change %(transition_icon)s %(status)s')
1083 message = (_('Status change %(transition_icon)s %(status)s')
1086 % {'transition_icon': '>',
1084 % {'transition_icon': '>',
1087 'status': ChangesetStatus.get_status_lbl(status)})
1085 'status': ChangesetStatus.get_status_lbl(status)})
1088 text = text or message
1086 text = text or message
1089
1087
1090 comment = CommentsModel().create(
1088 comment = CommentsModel().create(
1091 text=text,
1089 text=text,
1092 repo=self.db_repo.repo_id,
1090 repo=self.db_repo.repo_id,
1093 user=self._rhodecode_user.user_id,
1091 user=self._rhodecode_user.user_id,
1094 pull_request=pull_request,
1092 pull_request=pull_request,
1095 f_path=self.request.POST.get('f_path'),
1093 f_path=self.request.POST.get('f_path'),
1096 line_no=self.request.POST.get('line'),
1094 line_no=self.request.POST.get('line'),
1097 status_change=(ChangesetStatus.get_status_lbl(status)
1095 status_change=(ChangesetStatus.get_status_lbl(status)
1098 if status and allowed_to_change_status else None),
1096 if status and allowed_to_change_status else None),
1099 status_change_type=(status
1097 status_change_type=(status
1100 if status and allowed_to_change_status else None),
1098 if status and allowed_to_change_status else None),
1101 comment_type=comment_type,
1099 comment_type=comment_type,
1102 resolves_comment_id=resolves_comment_id
1100 resolves_comment_id=resolves_comment_id
1103 )
1101 )
1104
1102
1105 if allowed_to_change_status:
1103 if allowed_to_change_status:
1106 # calculate old status before we change it
1104 # calculate old status before we change it
1107 old_calculated_status = pull_request.calculated_review_status()
1105 old_calculated_status = pull_request.calculated_review_status()
1108
1106
1109 # get status if set !
1107 # get status if set !
1110 if status:
1108 if status:
1111 ChangesetStatusModel().set_status(
1109 ChangesetStatusModel().set_status(
1112 self.db_repo.repo_id,
1110 self.db_repo.repo_id,
1113 status,
1111 status,
1114 self._rhodecode_user.user_id,
1112 self._rhodecode_user.user_id,
1115 comment,
1113 comment,
1116 pull_request=pull_request
1114 pull_request=pull_request
1117 )
1115 )
1118
1116
1119 Session().flush()
1117 Session().flush()
1120 events.trigger(
1118 events.trigger(
1121 events.PullRequestCommentEvent(pull_request, comment))
1119 events.PullRequestCommentEvent(pull_request, comment))
1122
1120
1123 # we now calculate the status of pull request, and based on that
1121 # we now calculate the status of pull request, and based on that
1124 # calculation we set the commits status
1122 # calculation we set the commits status
1125 calculated_status = pull_request.calculated_review_status()
1123 calculated_status = pull_request.calculated_review_status()
1126 if old_calculated_status != calculated_status:
1124 if old_calculated_status != calculated_status:
1127 PullRequestModel()._trigger_pull_request_hook(
1125 PullRequestModel()._trigger_pull_request_hook(
1128 pull_request, self._rhodecode_user, 'review_status_change')
1126 pull_request, self._rhodecode_user, 'review_status_change')
1129
1127
1130 Session().commit()
1128 Session().commit()
1131
1129
1132 data = {
1130 data = {
1133 'target_id': h.safeid(h.safe_unicode(
1131 'target_id': h.safeid(h.safe_unicode(
1134 self.request.POST.get('f_path'))),
1132 self.request.POST.get('f_path'))),
1135 }
1133 }
1136 if comment:
1134 if comment:
1137 c.co = comment
1135 c.co = comment
1138 rendered_comment = render(
1136 rendered_comment = render(
1139 'rhodecode:templates/changeset/changeset_comment_block.mako',
1137 'rhodecode:templates/changeset/changeset_comment_block.mako',
1140 self._get_template_context(c), self.request)
1138 self._get_template_context(c), self.request)
1141
1139
1142 data.update(comment.get_dict())
1140 data.update(comment.get_dict())
1143 data.update({'rendered_text': rendered_comment})
1141 data.update({'rendered_text': rendered_comment})
1144
1142
1145 return data
1143 return data
1146
1144
1147 @LoginRequired()
1145 @LoginRequired()
1148 @NotAnonymous()
1146 @NotAnonymous()
1149 @HasRepoPermissionAnyDecorator(
1147 @HasRepoPermissionAnyDecorator(
1150 'repository.read', 'repository.write', 'repository.admin')
1148 'repository.read', 'repository.write', 'repository.admin')
1151 @CSRFRequired()
1149 @CSRFRequired()
1152 @view_config(
1150 @view_config(
1153 route_name='pullrequest_comment_delete', request_method='POST',
1151 route_name='pullrequest_comment_delete', request_method='POST',
1154 renderer='json_ext')
1152 renderer='json_ext')
1155 def pull_request_comment_delete(self):
1153 def pull_request_comment_delete(self):
1156 pull_request = PullRequest.get_or_404(
1154 pull_request = PullRequest.get_or_404(
1157 self.request.matchdict['pull_request_id'])
1155 self.request.matchdict['pull_request_id'])
1158
1156
1159 comment = ChangesetComment.get_or_404(
1157 comment = ChangesetComment.get_or_404(
1160 self.request.matchdict['comment_id'])
1158 self.request.matchdict['comment_id'])
1161 comment_id = comment.comment_id
1159 comment_id = comment.comment_id
1162
1160
1163 if pull_request.is_closed():
1161 if pull_request.is_closed():
1164 log.debug('comment: forbidden because pull request is closed')
1162 log.debug('comment: forbidden because pull request is closed')
1165 raise HTTPForbidden()
1163 raise HTTPForbidden()
1166
1164
1167 if not comment:
1165 if not comment:
1168 log.debug('Comment with id:%s not found, skipping', comment_id)
1166 log.debug('Comment with id:%s not found, skipping', comment_id)
1169 # comment already deleted in another call probably
1167 # comment already deleted in another call probably
1170 return True
1168 return True
1171
1169
1172 if comment.pull_request.is_closed():
1170 if comment.pull_request.is_closed():
1173 # don't allow deleting comments on closed pull request
1171 # don't allow deleting comments on closed pull request
1174 raise HTTPForbidden()
1172 raise HTTPForbidden()
1175
1173
1176 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1174 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1177 super_admin = h.HasPermissionAny('hg.admin')()
1175 super_admin = h.HasPermissionAny('hg.admin')()
1178 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1176 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1179 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1177 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1180 comment_repo_admin = is_repo_admin and is_repo_comment
1178 comment_repo_admin = is_repo_admin and is_repo_comment
1181
1179
1182 if super_admin or comment_owner or comment_repo_admin:
1180 if super_admin or comment_owner or comment_repo_admin:
1183 old_calculated_status = comment.pull_request.calculated_review_status()
1181 old_calculated_status = comment.pull_request.calculated_review_status()
1184 CommentsModel().delete(comment=comment, user=self._rhodecode_user)
1182 CommentsModel().delete(comment=comment, user=self._rhodecode_user)
1185 Session().commit()
1183 Session().commit()
1186 calculated_status = comment.pull_request.calculated_review_status()
1184 calculated_status = comment.pull_request.calculated_review_status()
1187 if old_calculated_status != calculated_status:
1185 if old_calculated_status != calculated_status:
1188 PullRequestModel()._trigger_pull_request_hook(
1186 PullRequestModel()._trigger_pull_request_hook(
1189 comment.pull_request, self._rhodecode_user, 'review_status_change')
1187 comment.pull_request, self._rhodecode_user, 'review_status_change')
1190 return True
1188 return True
1191 else:
1189 else:
1192 log.warning('No permissions for user %s to delete comment_id: %s',
1190 log.warning('No permissions for user %s to delete comment_id: %s',
1193 self._rhodecode_db_user, comment_id)
1191 self._rhodecode_db_user, comment_id)
1194 raise HTTPNotFound()
1192 raise HTTPNotFound()
@@ -1,64 +1,61 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from pyramid.view import view_config
23 from pyramid.view import view_config
24
24
25 from rhodecode.apps._base import RepoAppView
25 from rhodecode.apps._base import RepoAppView
26 from rhodecode.apps.repository.utils import get_default_reviewers_data
26 from rhodecode.apps.repository.utils import get_default_reviewers_data
27 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
27 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
28
28
29 log = logging.getLogger(__name__)
29 log = logging.getLogger(__name__)
30
30
31
31
32 class RepoReviewRulesView(RepoAppView):
32 class RepoReviewRulesView(RepoAppView):
33 def load_default_context(self):
33 def load_default_context(self):
34 c = self._get_local_tmpl_context()
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 self._register_global_c(c)
36 self._register_global_c(c)
40 return c
37 return c
41
38
42 @LoginRequired()
39 @LoginRequired()
43 @HasRepoPermissionAnyDecorator('repository.admin')
40 @HasRepoPermissionAnyDecorator('repository.admin')
44 @view_config(
41 @view_config(
45 route_name='repo_reviewers', request_method='GET',
42 route_name='repo_reviewers', request_method='GET',
46 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
43 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
47 def repo_review_rules(self):
44 def repo_review_rules(self):
48 c = self.load_default_context()
45 c = self.load_default_context()
49 c.active = 'reviewers'
46 c.active = 'reviewers'
50
47
51 return self._get_template_context(c)
48 return self._get_template_context(c)
52
49
53 @LoginRequired()
50 @LoginRequired()
54 @HasRepoPermissionAnyDecorator(
51 @HasRepoPermissionAnyDecorator(
55 'repository.read', 'repository.write', 'repository.admin')
52 'repository.read', 'repository.write', 'repository.admin')
56 @view_config(
53 @view_config(
57 route_name='repo_default_reviewers_data', request_method='GET',
54 route_name='repo_default_reviewers_data', request_method='GET',
58 renderer='json_ext')
55 renderer='json_ext')
59 def repo_default_reviewers_data(self):
56 def repo_default_reviewers_data(self):
60 review_data = get_default_reviewers_data(
57 review_data = get_default_reviewers_data(
61 self.db_repo.user, None, None, None, None)
58 self.db_repo.user, None, None, None, None)
62 return review_data
59 return review_data
63
60
64
61
@@ -1,254 +1,251 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 import deform
23 import deform
24 from pyramid.httpexceptions import HTTPFound
24 from pyramid.httpexceptions import HTTPFound
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26
26
27 from rhodecode.apps._base import RepoAppView
27 from rhodecode.apps._base import RepoAppView
28 from rhodecode.forms import RcForm
28 from rhodecode.forms import RcForm
29 from rhodecode.lib import helpers as h
29 from rhodecode.lib import helpers as h
30 from rhodecode.lib import audit_logger
30 from rhodecode.lib import audit_logger
31 from rhodecode.lib.auth import (
31 from rhodecode.lib.auth import (
32 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
32 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
33 from rhodecode.model.db import RepositoryField, RepoGroup, Repository
33 from rhodecode.model.db import RepositoryField, RepoGroup, Repository
34 from rhodecode.model.meta import Session
34 from rhodecode.model.meta import Session
35 from rhodecode.model.repo import RepoModel
35 from rhodecode.model.repo import RepoModel
36 from rhodecode.model.scm import RepoGroupList, ScmModel
36 from rhodecode.model.scm import RepoGroupList, ScmModel
37 from rhodecode.model.validation_schema.schemas import repo_schema
37 from rhodecode.model.validation_schema.schemas import repo_schema
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41
41
42 class RepoSettingsView(RepoAppView):
42 class RepoSettingsView(RepoAppView):
43
43
44 def load_default_context(self):
44 def load_default_context(self):
45 c = self._get_local_tmpl_context()
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 acl_groups = RepoGroupList(
47 acl_groups = RepoGroupList(
51 RepoGroup.query().all(),
48 RepoGroup.query().all(),
52 perm_set=['group.write', 'group.admin'])
49 perm_set=['group.write', 'group.admin'])
53 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
50 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
54 c.repo_groups_choices = map(lambda k: k[0], c.repo_groups)
51 c.repo_groups_choices = map(lambda k: k[0], c.repo_groups)
55
52
56 # in case someone no longer have a group.write access to a repository
53 # in case someone no longer have a group.write access to a repository
57 # pre fill the list with this entry, we don't care if this is the same
54 # pre fill the list with this entry, we don't care if this is the same
58 # but it will allow saving repo data properly.
55 # but it will allow saving repo data properly.
59 repo_group = self.db_repo.group
56 repo_group = self.db_repo.group
60 if repo_group and repo_group.group_id not in c.repo_groups_choices:
57 if repo_group and repo_group.group_id not in c.repo_groups_choices:
61 c.repo_groups_choices.append(repo_group.group_id)
58 c.repo_groups_choices.append(repo_group.group_id)
62 c.repo_groups.append(RepoGroup._generate_choice(repo_group))
59 c.repo_groups.append(RepoGroup._generate_choice(repo_group))
63
60
64 if c.repository_requirements_missing or self.rhodecode_vcs_repo is None:
61 if c.repository_requirements_missing or self.rhodecode_vcs_repo is None:
65 # we might be in missing requirement state, so we load things
62 # we might be in missing requirement state, so we load things
66 # without touching scm_instance()
63 # without touching scm_instance()
67 c.landing_revs_choices, c.landing_revs = \
64 c.landing_revs_choices, c.landing_revs = \
68 ScmModel().get_repo_landing_revs()
65 ScmModel().get_repo_landing_revs()
69 else:
66 else:
70 c.landing_revs_choices, c.landing_revs = \
67 c.landing_revs_choices, c.landing_revs = \
71 ScmModel().get_repo_landing_revs(self.db_repo)
68 ScmModel().get_repo_landing_revs(self.db_repo)
72
69
73 c.personal_repo_group = c.auth_user.personal_repo_group
70 c.personal_repo_group = c.auth_user.personal_repo_group
74 c.repo_fields = RepositoryField.query()\
71 c.repo_fields = RepositoryField.query()\
75 .filter(RepositoryField.repository == self.db_repo).all()
72 .filter(RepositoryField.repository == self.db_repo).all()
76
73
77 self._register_global_c(c)
74 self._register_global_c(c)
78 return c
75 return c
79
76
80 def _get_schema(self, c, old_values=None):
77 def _get_schema(self, c, old_values=None):
81 return repo_schema.RepoSettingsSchema().bind(
78 return repo_schema.RepoSettingsSchema().bind(
82 repo_type=self.db_repo.repo_type,
79 repo_type=self.db_repo.repo_type,
83 repo_type_options=[self.db_repo.repo_type],
80 repo_type_options=[self.db_repo.repo_type],
84 repo_ref_options=c.landing_revs_choices,
81 repo_ref_options=c.landing_revs_choices,
85 repo_ref_items=c.landing_revs,
82 repo_ref_items=c.landing_revs,
86 repo_repo_group_options=c.repo_groups_choices,
83 repo_repo_group_options=c.repo_groups_choices,
87 repo_repo_group_items=c.repo_groups,
84 repo_repo_group_items=c.repo_groups,
88 # user caller
85 # user caller
89 user=self._rhodecode_user,
86 user=self._rhodecode_user,
90 old_values=old_values
87 old_values=old_values
91 )
88 )
92
89
93 @LoginRequired()
90 @LoginRequired()
94 @HasRepoPermissionAnyDecorator('repository.admin')
91 @HasRepoPermissionAnyDecorator('repository.admin')
95 @view_config(
92 @view_config(
96 route_name='edit_repo', request_method='GET',
93 route_name='edit_repo', request_method='GET',
97 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
94 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
98 def edit_settings(self):
95 def edit_settings(self):
99 c = self.load_default_context()
96 c = self.load_default_context()
100 c.active = 'settings'
97 c.active = 'settings'
101
98
102 defaults = RepoModel()._get_defaults(self.db_repo_name)
99 defaults = RepoModel()._get_defaults(self.db_repo_name)
103 defaults['repo_owner'] = defaults['user']
100 defaults['repo_owner'] = defaults['user']
104 defaults['repo_landing_commit_ref'] = defaults['repo_landing_rev']
101 defaults['repo_landing_commit_ref'] = defaults['repo_landing_rev']
105
102
106 schema = self._get_schema(c)
103 schema = self._get_schema(c)
107 c.form = RcForm(schema, appstruct=defaults)
104 c.form = RcForm(schema, appstruct=defaults)
108 return self._get_template_context(c)
105 return self._get_template_context(c)
109
106
110 @LoginRequired()
107 @LoginRequired()
111 @HasRepoPermissionAnyDecorator('repository.admin')
108 @HasRepoPermissionAnyDecorator('repository.admin')
112 @CSRFRequired()
109 @CSRFRequired()
113 @view_config(
110 @view_config(
114 route_name='edit_repo', request_method='POST',
111 route_name='edit_repo', request_method='POST',
115 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
112 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
116 def edit_settings_update(self):
113 def edit_settings_update(self):
117 _ = self.request.translate
114 _ = self.request.translate
118 c = self.load_default_context()
115 c = self.load_default_context()
119 c.active = 'settings'
116 c.active = 'settings'
120 old_repo_name = self.db_repo_name
117 old_repo_name = self.db_repo_name
121
118
122 old_values = self.db_repo.get_api_data()
119 old_values = self.db_repo.get_api_data()
123 schema = self._get_schema(c, old_values=old_values)
120 schema = self._get_schema(c, old_values=old_values)
124
121
125 c.form = RcForm(schema)
122 c.form = RcForm(schema)
126 pstruct = self.request.POST.items()
123 pstruct = self.request.POST.items()
127 pstruct.append(('repo_type', self.db_repo.repo_type))
124 pstruct.append(('repo_type', self.db_repo.repo_type))
128 try:
125 try:
129 schema_data = c.form.validate(pstruct)
126 schema_data = c.form.validate(pstruct)
130 except deform.ValidationFailure as err_form:
127 except deform.ValidationFailure as err_form:
131 return self._get_template_context(c)
128 return self._get_template_context(c)
132
129
133 # data is now VALID, proceed with updates
130 # data is now VALID, proceed with updates
134 # save validated data back into the updates dict
131 # save validated data back into the updates dict
135 validated_updates = dict(
132 validated_updates = dict(
136 repo_name=schema_data['repo_group']['repo_name_without_group'],
133 repo_name=schema_data['repo_group']['repo_name_without_group'],
137 repo_group=schema_data['repo_group']['repo_group_id'],
134 repo_group=schema_data['repo_group']['repo_group_id'],
138
135
139 user=schema_data['repo_owner'],
136 user=schema_data['repo_owner'],
140 repo_description=schema_data['repo_description'],
137 repo_description=schema_data['repo_description'],
141 repo_private=schema_data['repo_private'],
138 repo_private=schema_data['repo_private'],
142 clone_uri=schema_data['repo_clone_uri'],
139 clone_uri=schema_data['repo_clone_uri'],
143 repo_landing_rev=schema_data['repo_landing_commit_ref'],
140 repo_landing_rev=schema_data['repo_landing_commit_ref'],
144 repo_enable_statistics=schema_data['repo_enable_statistics'],
141 repo_enable_statistics=schema_data['repo_enable_statistics'],
145 repo_enable_locking=schema_data['repo_enable_locking'],
142 repo_enable_locking=schema_data['repo_enable_locking'],
146 repo_enable_downloads=schema_data['repo_enable_downloads'],
143 repo_enable_downloads=schema_data['repo_enable_downloads'],
147 )
144 )
148 # detect if CLONE URI changed, if we get OLD means we keep old values
145 # detect if CLONE URI changed, if we get OLD means we keep old values
149 if schema_data['repo_clone_uri_change'] == 'OLD':
146 if schema_data['repo_clone_uri_change'] == 'OLD':
150 validated_updates['clone_uri'] = self.db_repo.clone_uri
147 validated_updates['clone_uri'] = self.db_repo.clone_uri
151
148
152 # use the new full name for redirect
149 # use the new full name for redirect
153 new_repo_name = schema_data['repo_group']['repo_name_with_group']
150 new_repo_name = schema_data['repo_group']['repo_name_with_group']
154
151
155 # save extra fields into our validated data
152 # save extra fields into our validated data
156 for key, value in pstruct:
153 for key, value in pstruct:
157 if key.startswith(RepositoryField.PREFIX):
154 if key.startswith(RepositoryField.PREFIX):
158 validated_updates[key] = value
155 validated_updates[key] = value
159
156
160 try:
157 try:
161 RepoModel().update(self.db_repo, **validated_updates)
158 RepoModel().update(self.db_repo, **validated_updates)
162 ScmModel().mark_for_invalidation(new_repo_name)
159 ScmModel().mark_for_invalidation(new_repo_name)
163
160
164 audit_logger.store_web(
161 audit_logger.store_web(
165 'repo.edit', action_data={'old_data': old_values},
162 'repo.edit', action_data={'old_data': old_values},
166 user=self._rhodecode_user, repo=self.db_repo)
163 user=self._rhodecode_user, repo=self.db_repo)
167
164
168 Session().commit()
165 Session().commit()
169
166
170 h.flash(_('Repository {} updated successfully').format(
167 h.flash(_('Repository {} updated successfully').format(
171 old_repo_name), category='success')
168 old_repo_name), category='success')
172 except Exception:
169 except Exception:
173 log.exception("Exception during update of repository")
170 log.exception("Exception during update of repository")
174 h.flash(_('Error occurred during update of repository {}').format(
171 h.flash(_('Error occurred during update of repository {}').format(
175 old_repo_name), category='error')
172 old_repo_name), category='error')
176
173
177 raise HTTPFound(
174 raise HTTPFound(
178 h.route_path('edit_repo', repo_name=new_repo_name))
175 h.route_path('edit_repo', repo_name=new_repo_name))
179
176
180 @LoginRequired()
177 @LoginRequired()
181 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
178 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
182 @view_config(
179 @view_config(
183 route_name='repo_edit_toggle_locking', request_method='GET',
180 route_name='repo_edit_toggle_locking', request_method='GET',
184 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
181 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
185 def toggle_locking(self):
182 def toggle_locking(self):
186 """
183 """
187 Toggle locking of repository by simple GET call to url
184 Toggle locking of repository by simple GET call to url
188 """
185 """
189 _ = self.request.translate
186 _ = self.request.translate
190 repo = self.db_repo
187 repo = self.db_repo
191
188
192 try:
189 try:
193 if repo.enable_locking:
190 if repo.enable_locking:
194 if repo.locked[0]:
191 if repo.locked[0]:
195 Repository.unlock(repo)
192 Repository.unlock(repo)
196 action = _('Unlocked')
193 action = _('Unlocked')
197 else:
194 else:
198 Repository.lock(
195 Repository.lock(
199 repo, self._rhodecode_user.user_id,
196 repo, self._rhodecode_user.user_id,
200 lock_reason=Repository.LOCK_WEB)
197 lock_reason=Repository.LOCK_WEB)
201 action = _('Locked')
198 action = _('Locked')
202
199
203 h.flash(_('Repository has been %s') % action,
200 h.flash(_('Repository has been %s') % action,
204 category='success')
201 category='success')
205 except Exception:
202 except Exception:
206 log.exception("Exception during unlocking")
203 log.exception("Exception during unlocking")
207 h.flash(_('An error occurred during unlocking'),
204 h.flash(_('An error occurred during unlocking'),
208 category='error')
205 category='error')
209 raise HTTPFound(
206 raise HTTPFound(
210 h.route_path('repo_summary', repo_name=self.db_repo_name))
207 h.route_path('repo_summary', repo_name=self.db_repo_name))
211
208
212 @LoginRequired()
209 @LoginRequired()
213 @HasRepoPermissionAnyDecorator('repository.admin')
210 @HasRepoPermissionAnyDecorator('repository.admin')
214 @view_config(
211 @view_config(
215 route_name='edit_repo_statistics', request_method='GET',
212 route_name='edit_repo_statistics', request_method='GET',
216 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
213 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
217 def edit_statistics_form(self):
214 def edit_statistics_form(self):
218 c = self.load_default_context()
215 c = self.load_default_context()
219
216
220 if self.db_repo.stats:
217 if self.db_repo.stats:
221 # this is on what revision we ended up so we add +1 for count
218 # this is on what revision we ended up so we add +1 for count
222 last_rev = self.db_repo.stats.stat_on_revision + 1
219 last_rev = self.db_repo.stats.stat_on_revision + 1
223 else:
220 else:
224 last_rev = 0
221 last_rev = 0
225
222
226 c.active = 'statistics'
223 c.active = 'statistics'
227 c.stats_revision = last_rev
224 c.stats_revision = last_rev
228 c.repo_last_rev = self.rhodecode_vcs_repo.count()
225 c.repo_last_rev = self.rhodecode_vcs_repo.count()
229
226
230 if last_rev == 0 or c.repo_last_rev == 0:
227 if last_rev == 0 or c.repo_last_rev == 0:
231 c.stats_percentage = 0
228 c.stats_percentage = 0
232 else:
229 else:
233 c.stats_percentage = '%.2f' % (
230 c.stats_percentage = '%.2f' % (
234 (float((last_rev)) / c.repo_last_rev) * 100)
231 (float((last_rev)) / c.repo_last_rev) * 100)
235 return self._get_template_context(c)
232 return self._get_template_context(c)
236
233
237 @LoginRequired()
234 @LoginRequired()
238 @HasRepoPermissionAnyDecorator('repository.admin')
235 @HasRepoPermissionAnyDecorator('repository.admin')
239 @CSRFRequired()
236 @CSRFRequired()
240 @view_config(
237 @view_config(
241 route_name='edit_repo_statistics_reset', request_method='POST',
238 route_name='edit_repo_statistics_reset', request_method='POST',
242 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
239 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
243 def repo_statistics_reset(self):
240 def repo_statistics_reset(self):
244 _ = self.request.translate
241 _ = self.request.translate
245
242
246 try:
243 try:
247 RepoModel().delete_stats(self.db_repo_name)
244 RepoModel().delete_stats(self.db_repo_name)
248 Session().commit()
245 Session().commit()
249 except Exception:
246 except Exception:
250 log.exception('Edit statistics failure')
247 log.exception('Edit statistics failure')
251 h.flash(_('An error occurred during deletion of repository stats'),
248 h.flash(_('An error occurred during deletion of repository stats'),
252 category='error')
249 category='error')
253 raise HTTPFound(
250 raise HTTPFound(
254 h.route_path('edit_repo_statistics', repo_name=self.db_repo_name))
251 h.route_path('edit_repo_statistics', repo_name=self.db_repo_name))
@@ -1,226 +1,223 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from pyramid.view import view_config
23 from pyramid.view import view_config
24 from pyramid.httpexceptions import HTTPFound
24 from pyramid.httpexceptions import HTTPFound
25
25
26 from rhodecode.apps._base import RepoAppView
26 from rhodecode.apps._base import RepoAppView
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib import audit_logger
28 from rhodecode.lib import audit_logger
29 from rhodecode.lib.auth import (
29 from rhodecode.lib.auth import (
30 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
30 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
31 from rhodecode.lib.exceptions import AttachedForksError
31 from rhodecode.lib.exceptions import AttachedForksError
32 from rhodecode.lib.utils2 import safe_int
32 from rhodecode.lib.utils2 import safe_int
33 from rhodecode.lib.vcs import RepositoryError
33 from rhodecode.lib.vcs import RepositoryError
34 from rhodecode.model.db import Session, UserFollowing, User, Repository
34 from rhodecode.model.db import Session, UserFollowing, User, Repository
35 from rhodecode.model.repo import RepoModel
35 from rhodecode.model.repo import RepoModel
36 from rhodecode.model.scm import ScmModel
36 from rhodecode.model.scm import ScmModel
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40
40
41 class RepoSettingsView(RepoAppView):
41 class RepoSettingsView(RepoAppView):
42
42
43 def load_default_context(self):
43 def load_default_context(self):
44 c = self._get_local_tmpl_context()
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 self._register_global_c(c)
46 self._register_global_c(c)
50 return c
47 return c
51
48
52 @LoginRequired()
49 @LoginRequired()
53 @HasRepoPermissionAnyDecorator('repository.admin')
50 @HasRepoPermissionAnyDecorator('repository.admin')
54 @view_config(
51 @view_config(
55 route_name='edit_repo_advanced', request_method='GET',
52 route_name='edit_repo_advanced', request_method='GET',
56 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
53 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
57 def edit_advanced(self):
54 def edit_advanced(self):
58 c = self.load_default_context()
55 c = self.load_default_context()
59 c.active = 'advanced'
56 c.active = 'advanced'
60
57
61 c.default_user_id = User.get_default_user().user_id
58 c.default_user_id = User.get_default_user().user_id
62 c.in_public_journal = UserFollowing.query() \
59 c.in_public_journal = UserFollowing.query() \
63 .filter(UserFollowing.user_id == c.default_user_id) \
60 .filter(UserFollowing.user_id == c.default_user_id) \
64 .filter(UserFollowing.follows_repository == self.db_repo).scalar()
61 .filter(UserFollowing.follows_repository == self.db_repo).scalar()
65
62
66 c.has_origin_repo_read_perm = False
63 c.has_origin_repo_read_perm = False
67 if self.db_repo.fork:
64 if self.db_repo.fork:
68 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
65 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
69 'repository.write', 'repository.read', 'repository.admin')(
66 'repository.write', 'repository.read', 'repository.admin')(
70 self.db_repo.fork.repo_name, 'repo set as fork page')
67 self.db_repo.fork.repo_name, 'repo set as fork page')
71
68
72 return self._get_template_context(c)
69 return self._get_template_context(c)
73
70
74 @LoginRequired()
71 @LoginRequired()
75 @HasRepoPermissionAnyDecorator('repository.admin')
72 @HasRepoPermissionAnyDecorator('repository.admin')
76 @CSRFRequired()
73 @CSRFRequired()
77 @view_config(
74 @view_config(
78 route_name='edit_repo_advanced_delete', request_method='POST',
75 route_name='edit_repo_advanced_delete', request_method='POST',
79 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
76 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
80 def edit_advanced_delete(self):
77 def edit_advanced_delete(self):
81 """
78 """
82 Deletes the repository, or shows warnings if deletion is not possible
79 Deletes the repository, or shows warnings if deletion is not possible
83 because of attached forks or other errors.
80 because of attached forks or other errors.
84 """
81 """
85 _ = self.request.translate
82 _ = self.request.translate
86 handle_forks = self.request.POST.get('forks', None)
83 handle_forks = self.request.POST.get('forks', None)
87
84
88 try:
85 try:
89 _forks = self.db_repo.forks.count()
86 _forks = self.db_repo.forks.count()
90 if _forks and handle_forks:
87 if _forks and handle_forks:
91 if handle_forks == 'detach_forks':
88 if handle_forks == 'detach_forks':
92 handle_forks = 'detach'
89 handle_forks = 'detach'
93 h.flash(_('Detached %s forks') % _forks, category='success')
90 h.flash(_('Detached %s forks') % _forks, category='success')
94 elif handle_forks == 'delete_forks':
91 elif handle_forks == 'delete_forks':
95 handle_forks = 'delete'
92 handle_forks = 'delete'
96 h.flash(_('Deleted %s forks') % _forks, category='success')
93 h.flash(_('Deleted %s forks') % _forks, category='success')
97
94
98 old_data = self.db_repo.get_api_data()
95 old_data = self.db_repo.get_api_data()
99 RepoModel().delete(self.db_repo, forks=handle_forks)
96 RepoModel().delete(self.db_repo, forks=handle_forks)
100
97
101 repo = audit_logger.RepoWrap(repo_id=None,
98 repo = audit_logger.RepoWrap(repo_id=None,
102 repo_name=self.db_repo.repo_name)
99 repo_name=self.db_repo.repo_name)
103 audit_logger.store_web(
100 audit_logger.store_web(
104 'repo.delete', action_data={'old_data': old_data},
101 'repo.delete', action_data={'old_data': old_data},
105 user=self._rhodecode_user, repo=repo)
102 user=self._rhodecode_user, repo=repo)
106
103
107 ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
104 ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
108 h.flash(
105 h.flash(
109 _('Deleted repository `%s`') % self.db_repo_name,
106 _('Deleted repository `%s`') % self.db_repo_name,
110 category='success')
107 category='success')
111 Session().commit()
108 Session().commit()
112 except AttachedForksError:
109 except AttachedForksError:
113 repo_advanced_url = h.route_path(
110 repo_advanced_url = h.route_path(
114 'edit_repo_advanced', repo_name=self.db_repo_name,
111 'edit_repo_advanced', repo_name=self.db_repo_name,
115 _anchor='advanced-delete')
112 _anchor='advanced-delete')
116 delete_anchor = h.link_to(_('detach or delete'), repo_advanced_url)
113 delete_anchor = h.link_to(_('detach or delete'), repo_advanced_url)
117 h.flash(_('Cannot delete `{repo}` it still contains attached forks. '
114 h.flash(_('Cannot delete `{repo}` it still contains attached forks. '
118 'Try using {delete_or_detach} option.')
115 'Try using {delete_or_detach} option.')
119 .format(repo=self.db_repo_name, delete_or_detach=delete_anchor),
116 .format(repo=self.db_repo_name, delete_or_detach=delete_anchor),
120 category='warning')
117 category='warning')
121
118
122 # redirect to advanced for forks handle action ?
119 # redirect to advanced for forks handle action ?
123 raise HTTPFound(repo_advanced_url)
120 raise HTTPFound(repo_advanced_url)
124
121
125 except Exception:
122 except Exception:
126 log.exception("Exception during deletion of repository")
123 log.exception("Exception during deletion of repository")
127 h.flash(_('An error occurred during deletion of `%s`')
124 h.flash(_('An error occurred during deletion of `%s`')
128 % self.db_repo_name, category='error')
125 % self.db_repo_name, category='error')
129 # redirect to advanced for more deletion options
126 # redirect to advanced for more deletion options
130 raise HTTPFound(
127 raise HTTPFound(
131 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name),
128 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name),
132 _anchor='advanced-delete')
129 _anchor='advanced-delete')
133
130
134 raise HTTPFound(h.route_path('home'))
131 raise HTTPFound(h.route_path('home'))
135
132
136 @LoginRequired()
133 @LoginRequired()
137 @HasRepoPermissionAnyDecorator('repository.admin')
134 @HasRepoPermissionAnyDecorator('repository.admin')
138 @CSRFRequired()
135 @CSRFRequired()
139 @view_config(
136 @view_config(
140 route_name='edit_repo_advanced_journal', request_method='POST',
137 route_name='edit_repo_advanced_journal', request_method='POST',
141 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
138 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
142 def edit_advanced_journal(self):
139 def edit_advanced_journal(self):
143 """
140 """
144 Set's this repository to be visible in public journal,
141 Set's this repository to be visible in public journal,
145 in other words making default user to follow this repo
142 in other words making default user to follow this repo
146 """
143 """
147 _ = self.request.translate
144 _ = self.request.translate
148
145
149 try:
146 try:
150 user_id = User.get_default_user().user_id
147 user_id = User.get_default_user().user_id
151 ScmModel().toggle_following_repo(self.db_repo.repo_id, user_id)
148 ScmModel().toggle_following_repo(self.db_repo.repo_id, user_id)
152 h.flash(_('Updated repository visibility in public journal'),
149 h.flash(_('Updated repository visibility in public journal'),
153 category='success')
150 category='success')
154 Session().commit()
151 Session().commit()
155 except Exception:
152 except Exception:
156 h.flash(_('An error occurred during setting this '
153 h.flash(_('An error occurred during setting this '
157 'repository in public journal'),
154 'repository in public journal'),
158 category='error')
155 category='error')
159
156
160 raise HTTPFound(
157 raise HTTPFound(
161 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
158 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
162
159
163 @LoginRequired()
160 @LoginRequired()
164 @HasRepoPermissionAnyDecorator('repository.admin')
161 @HasRepoPermissionAnyDecorator('repository.admin')
165 @CSRFRequired()
162 @CSRFRequired()
166 @view_config(
163 @view_config(
167 route_name='edit_repo_advanced_fork', request_method='POST',
164 route_name='edit_repo_advanced_fork', request_method='POST',
168 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
165 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
169 def edit_advanced_fork(self):
166 def edit_advanced_fork(self):
170 """
167 """
171 Mark given repository as a fork of another
168 Mark given repository as a fork of another
172 """
169 """
173 _ = self.request.translate
170 _ = self.request.translate
174
171
175 new_fork_id = self.request.POST.get('id_fork_of')
172 new_fork_id = self.request.POST.get('id_fork_of')
176 try:
173 try:
177
174
178 if new_fork_id and not new_fork_id.isdigit():
175 if new_fork_id and not new_fork_id.isdigit():
179 log.error('Given fork id %s is not an INT', new_fork_id)
176 log.error('Given fork id %s is not an INT', new_fork_id)
180
177
181 fork_id = safe_int(new_fork_id)
178 fork_id = safe_int(new_fork_id)
182 repo = ScmModel().mark_as_fork(
179 repo = ScmModel().mark_as_fork(
183 self.db_repo_name, fork_id, self._rhodecode_user.user_id)
180 self.db_repo_name, fork_id, self._rhodecode_user.user_id)
184 fork = repo.fork.repo_name if repo.fork else _('Nothing')
181 fork = repo.fork.repo_name if repo.fork else _('Nothing')
185 Session().commit()
182 Session().commit()
186 h.flash(_('Marked repo %s as fork of %s') % (self.db_repo_name, fork),
183 h.flash(_('Marked repo %s as fork of %s') % (self.db_repo_name, fork),
187 category='success')
184 category='success')
188 except RepositoryError as e:
185 except RepositoryError as e:
189 log.exception("Repository Error occurred")
186 log.exception("Repository Error occurred")
190 h.flash(str(e), category='error')
187 h.flash(str(e), category='error')
191 except Exception as e:
188 except Exception as e:
192 log.exception("Exception while editing fork")
189 log.exception("Exception while editing fork")
193 h.flash(_('An error occurred during this operation'),
190 h.flash(_('An error occurred during this operation'),
194 category='error')
191 category='error')
195
192
196 raise HTTPFound(
193 raise HTTPFound(
197 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
194 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
198
195
199 @LoginRequired()
196 @LoginRequired()
200 @HasRepoPermissionAnyDecorator('repository.admin')
197 @HasRepoPermissionAnyDecorator('repository.admin')
201 @CSRFRequired()
198 @CSRFRequired()
202 @view_config(
199 @view_config(
203 route_name='edit_repo_advanced_locking', request_method='POST',
200 route_name='edit_repo_advanced_locking', request_method='POST',
204 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
201 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
205 def edit_advanced_locking(self):
202 def edit_advanced_locking(self):
206 """
203 """
207 Toggle locking of repository
204 Toggle locking of repository
208 """
205 """
209 _ = self.request.translate
206 _ = self.request.translate
210 set_lock = self.request.POST.get('set_lock')
207 set_lock = self.request.POST.get('set_lock')
211 set_unlock = self.request.POST.get('set_unlock')
208 set_unlock = self.request.POST.get('set_unlock')
212
209
213 try:
210 try:
214 if set_lock:
211 if set_lock:
215 Repository.lock(self.db_repo, self._rhodecode_user.user_id,
212 Repository.lock(self.db_repo, self._rhodecode_user.user_id,
216 lock_reason=Repository.LOCK_WEB)
213 lock_reason=Repository.LOCK_WEB)
217 h.flash(_('Locked repository'), category='success')
214 h.flash(_('Locked repository'), category='success')
218 elif set_unlock:
215 elif set_unlock:
219 Repository.unlock(self.db_repo)
216 Repository.unlock(self.db_repo)
220 h.flash(_('Unlocked repository'), category='success')
217 h.flash(_('Unlocked repository'), category='success')
221 except Exception as e:
218 except Exception as e:
222 log.exception("Exception during unlocking")
219 log.exception("Exception during unlocking")
223 h.flash(_('An error occurred during unlocking'), category='error')
220 h.flash(_('An error occurred during unlocking'), category='error')
224
221
225 raise HTTPFound(
222 raise HTTPFound(
226 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
223 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
@@ -1,114 +1,111 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2017-2017 RhodeCode GmbH
3 # Copyright (C) 2017-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 import formencode
23 import formencode
24 import formencode.htmlfill
24 import formencode.htmlfill
25
25
26 from pyramid.httpexceptions import HTTPFound
26 from pyramid.httpexceptions import HTTPFound
27 from pyramid.view import view_config
27 from pyramid.view import view_config
28
28
29 from rhodecode.apps._base import RepoAppView
29 from rhodecode.apps._base import RepoAppView
30 from rhodecode.lib import audit_logger
30 from rhodecode.lib import audit_logger
31 from rhodecode.lib import helpers as h
31 from rhodecode.lib import helpers as h
32 from rhodecode.lib.auth import (
32 from rhodecode.lib.auth import (
33 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
33 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
34 from rhodecode.model.db import RepositoryField
34 from rhodecode.model.db import RepositoryField
35 from rhodecode.model.forms import RepoFieldForm
35 from rhodecode.model.forms import RepoFieldForm
36 from rhodecode.model.meta import Session
36 from rhodecode.model.meta import Session
37 from rhodecode.model.repo import RepoModel
37 from rhodecode.model.repo import RepoModel
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41
41
42 class RepoSettingsFieldsView(RepoAppView):
42 class RepoSettingsFieldsView(RepoAppView):
43 def load_default_context(self):
43 def load_default_context(self):
44 c = self._get_local_tmpl_context()
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 self._register_global_c(c)
46 self._register_global_c(c)
50 return c
47 return c
51
48
52 @LoginRequired()
49 @LoginRequired()
53 @HasRepoPermissionAnyDecorator('repository.admin')
50 @HasRepoPermissionAnyDecorator('repository.admin')
54 @view_config(
51 @view_config(
55 route_name='edit_repo_fields', request_method='GET',
52 route_name='edit_repo_fields', request_method='GET',
56 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
53 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
57 def repo_field_edit(self):
54 def repo_field_edit(self):
58 c = self.load_default_context()
55 c = self.load_default_context()
59
56
60 c.active = 'fields'
57 c.active = 'fields'
61 c.repo_fields = RepositoryField.query() \
58 c.repo_fields = RepositoryField.query() \
62 .filter(RepositoryField.repository == self.db_repo).all()
59 .filter(RepositoryField.repository == self.db_repo).all()
63
60
64 return self._get_template_context(c)
61 return self._get_template_context(c)
65
62
66 @LoginRequired()
63 @LoginRequired()
67 @HasRepoPermissionAnyDecorator('repository.admin')
64 @HasRepoPermissionAnyDecorator('repository.admin')
68 @CSRFRequired()
65 @CSRFRequired()
69 @view_config(
66 @view_config(
70 route_name='edit_repo_fields_create', request_method='POST',
67 route_name='edit_repo_fields_create', request_method='POST',
71 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
68 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
72 def repo_field_create(self):
69 def repo_field_create(self):
73 _ = self.request.translate
70 _ = self.request.translate
74
71
75 try:
72 try:
76 form_result = RepoFieldForm()().to_python(dict(self.request.POST))
73 form_result = RepoFieldForm()().to_python(dict(self.request.POST))
77 RepoModel().add_repo_field(
74 RepoModel().add_repo_field(
78 self.db_repo_name,
75 self.db_repo_name,
79 form_result['new_field_key'],
76 form_result['new_field_key'],
80 field_type=form_result['new_field_type'],
77 field_type=form_result['new_field_type'],
81 field_value=form_result['new_field_value'],
78 field_value=form_result['new_field_value'],
82 field_label=form_result['new_field_label'],
79 field_label=form_result['new_field_label'],
83 field_desc=form_result['new_field_desc'])
80 field_desc=form_result['new_field_desc'])
84
81
85 Session().commit()
82 Session().commit()
86 except Exception as e:
83 except Exception as e:
87 log.exception("Exception creating field")
84 log.exception("Exception creating field")
88 msg = _('An error occurred during creation of field')
85 msg = _('An error occurred during creation of field')
89 if isinstance(e, formencode.Invalid):
86 if isinstance(e, formencode.Invalid):
90 msg += ". " + e.msg
87 msg += ". " + e.msg
91 h.flash(msg, category='error')
88 h.flash(msg, category='error')
92
89
93 raise HTTPFound(
90 raise HTTPFound(
94 h.route_path('edit_repo_fields', repo_name=self.db_repo_name))
91 h.route_path('edit_repo_fields', repo_name=self.db_repo_name))
95
92
96 @LoginRequired()
93 @LoginRequired()
97 @HasRepoPermissionAnyDecorator('repository.admin')
94 @HasRepoPermissionAnyDecorator('repository.admin')
98 @CSRFRequired()
95 @CSRFRequired()
99 @view_config(
96 @view_config(
100 route_name='edit_repo_fields_delete', request_method='POST',
97 route_name='edit_repo_fields_delete', request_method='POST',
101 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
98 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
102 def repo_field_delete(self):
99 def repo_field_delete(self):
103 _ = self.request.translate
100 _ = self.request.translate
104 field = RepositoryField.get_or_404(self.request.matchdict['field_id'])
101 field = RepositoryField.get_or_404(self.request.matchdict['field_id'])
105 try:
102 try:
106 RepoModel().delete_repo_field(self.db_repo_name, field.field_key)
103 RepoModel().delete_repo_field(self.db_repo_name, field.field_key)
107 Session().commit()
104 Session().commit()
108 except Exception:
105 except Exception:
109 log.exception('Exception during removal of field')
106 log.exception('Exception during removal of field')
110 msg = _('An error occurred during removal of field')
107 msg = _('An error occurred during removal of field')
111 h.flash(msg, category='error')
108 h.flash(msg, category='error')
112
109
113 raise HTTPFound(
110 raise HTTPFound(
114 h.route_path('edit_repo_fields', repo_name=self.db_repo_name))
111 h.route_path('edit_repo_fields', repo_name=self.db_repo_name))
@@ -1,129 +1,126 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2017-2017 RhodeCode GmbH
3 # Copyright (C) 2017-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from pyramid.httpexceptions import HTTPFound
23 from pyramid.httpexceptions import HTTPFound
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25
25
26 from rhodecode.apps._base import RepoAppView
26 from rhodecode.apps._base import RepoAppView
27 from rhodecode.lib import audit_logger
27 from rhodecode.lib import audit_logger
28 from rhodecode.lib import helpers as h
28 from rhodecode.lib import helpers as h
29 from rhodecode.lib.auth import (
29 from rhodecode.lib.auth import (
30 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
30 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
31 from rhodecode.model.forms import IssueTrackerPatternsForm
31 from rhodecode.model.forms import IssueTrackerPatternsForm
32 from rhodecode.model.meta import Session
32 from rhodecode.model.meta import Session
33 from rhodecode.model.settings import IssueTrackerSettingsModel
33 from rhodecode.model.settings import IssueTrackerSettingsModel
34
34
35 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
36
36
37
37
38 class RepoSettingsIssueTrackersView(RepoAppView):
38 class RepoSettingsIssueTrackersView(RepoAppView):
39 def load_default_context(self):
39 def load_default_context(self):
40 c = self._get_local_tmpl_context()
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 self._register_global_c(c)
42 self._register_global_c(c)
46 return c
43 return c
47
44
48 @LoginRequired()
45 @LoginRequired()
49 @HasRepoPermissionAnyDecorator('repository.admin')
46 @HasRepoPermissionAnyDecorator('repository.admin')
50 @view_config(
47 @view_config(
51 route_name='edit_repo_issuetracker', request_method='GET',
48 route_name='edit_repo_issuetracker', request_method='GET',
52 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
49 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
53 def repo_issuetracker(self):
50 def repo_issuetracker(self):
54 c = self.load_default_context()
51 c = self.load_default_context()
55 c.active = 'issuetracker'
52 c.active = 'issuetracker'
56 c.data = 'data'
53 c.data = 'data'
57
54
58 c.settings_model = IssueTrackerSettingsModel(repo=self.db_repo)
55 c.settings_model = IssueTrackerSettingsModel(repo=self.db_repo)
59 c.global_patterns = c.settings_model.get_global_settings()
56 c.global_patterns = c.settings_model.get_global_settings()
60 c.repo_patterns = c.settings_model.get_repo_settings()
57 c.repo_patterns = c.settings_model.get_repo_settings()
61
58
62 return self._get_template_context(c)
59 return self._get_template_context(c)
63
60
64 @LoginRequired()
61 @LoginRequired()
65 @HasRepoPermissionAnyDecorator('repository.admin')
62 @HasRepoPermissionAnyDecorator('repository.admin')
66 @CSRFRequired()
63 @CSRFRequired()
67 @view_config(
64 @view_config(
68 route_name='edit_repo_issuetracker_test', request_method='POST',
65 route_name='edit_repo_issuetracker_test', request_method='POST',
69 xhr=True, renderer='string')
66 xhr=True, renderer='string')
70 def repo_issuetracker_test(self):
67 def repo_issuetracker_test(self):
71 return h.urlify_commit_message(
68 return h.urlify_commit_message(
72 self.request.POST.get('test_text', ''),
69 self.request.POST.get('test_text', ''),
73 self.db_repo_name)
70 self.db_repo_name)
74
71
75 @LoginRequired()
72 @LoginRequired()
76 @HasRepoPermissionAnyDecorator('repository.admin')
73 @HasRepoPermissionAnyDecorator('repository.admin')
77 @CSRFRequired()
74 @CSRFRequired()
78 @view_config(
75 @view_config(
79 route_name='edit_repo_issuetracker_delete', request_method='POST',
76 route_name='edit_repo_issuetracker_delete', request_method='POST',
80 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
77 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
81 def repo_issuetracker_delete(self):
78 def repo_issuetracker_delete(self):
82 _ = self.request.translate
79 _ = self.request.translate
83 uid = self.request.POST.get('uid')
80 uid = self.request.POST.get('uid')
84 repo_settings = IssueTrackerSettingsModel(repo=self.db_repo_name)
81 repo_settings = IssueTrackerSettingsModel(repo=self.db_repo_name)
85 try:
82 try:
86 repo_settings.delete_entries(uid)
83 repo_settings.delete_entries(uid)
87 except Exception:
84 except Exception:
88 h.flash(_('Error occurred during deleting issue tracker entry'),
85 h.flash(_('Error occurred during deleting issue tracker entry'),
89 category='error')
86 category='error')
90 else:
87 else:
91 h.flash(_('Removed issue tracker entry'), category='success')
88 h.flash(_('Removed issue tracker entry'), category='success')
92 raise HTTPFound(
89 raise HTTPFound(
93 h.route_path('edit_repo_issuetracker', repo_name=self.db_repo_name))
90 h.route_path('edit_repo_issuetracker', repo_name=self.db_repo_name))
94
91
95 def _update_patterns(self, form, repo_settings):
92 def _update_patterns(self, form, repo_settings):
96 for uid in form['delete_patterns']:
93 for uid in form['delete_patterns']:
97 repo_settings.delete_entries(uid)
94 repo_settings.delete_entries(uid)
98
95
99 for pattern_data in form['patterns']:
96 for pattern_data in form['patterns']:
100 for setting_key, pattern, type_ in pattern_data:
97 for setting_key, pattern, type_ in pattern_data:
101 sett = repo_settings.create_or_update_setting(
98 sett = repo_settings.create_or_update_setting(
102 setting_key, pattern.strip(), type_)
99 setting_key, pattern.strip(), type_)
103 Session().add(sett)
100 Session().add(sett)
104
101
105 Session().commit()
102 Session().commit()
106
103
107 @LoginRequired()
104 @LoginRequired()
108 @HasRepoPermissionAnyDecorator('repository.admin')
105 @HasRepoPermissionAnyDecorator('repository.admin')
109 @CSRFRequired()
106 @CSRFRequired()
110 @view_config(
107 @view_config(
111 route_name='edit_repo_issuetracker_update', request_method='POST',
108 route_name='edit_repo_issuetracker_update', request_method='POST',
112 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
109 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
113 def repo_issuetracker_update(self):
110 def repo_issuetracker_update(self):
114 _ = self.request.translate
111 _ = self.request.translate
115 # Save inheritance
112 # Save inheritance
116 repo_settings = IssueTrackerSettingsModel(repo=self.db_repo_name)
113 repo_settings = IssueTrackerSettingsModel(repo=self.db_repo_name)
117 inherited = (
114 inherited = (
118 self.request.POST.get('inherit_global_issuetracker') == "inherited")
115 self.request.POST.get('inherit_global_issuetracker') == "inherited")
119 repo_settings.inherit_global_settings = inherited
116 repo_settings.inherit_global_settings = inherited
120 Session().commit()
117 Session().commit()
121
118
122 form = IssueTrackerPatternsForm()().to_python(self.request.POST)
119 form = IssueTrackerPatternsForm()().to_python(self.request.POST)
123 if form:
120 if form:
124 self._update_patterns(form, repo_settings)
121 self._update_patterns(form, repo_settings)
125
122
126 h.flash(_('Updated issue tracker entries'), category='success')
123 h.flash(_('Updated issue tracker entries'), category='success')
127 raise HTTPFound(
124 raise HTTPFound(
128 h.route_path('edit_repo_issuetracker', repo_name=self.db_repo_name))
125 h.route_path('edit_repo_issuetracker', repo_name=self.db_repo_name))
129
126
@@ -1,75 +1,72 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2017-2017 RhodeCode GmbH
3 # Copyright (C) 2017-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from pyramid.httpexceptions import HTTPFound
23 from pyramid.httpexceptions import HTTPFound
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25
25
26 from rhodecode.apps._base import RepoAppView
26 from rhodecode.apps._base import RepoAppView
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib.auth import (
28 from rhodecode.lib.auth import (
29 LoginRequired, CSRFRequired, HasRepoPermissionAnyDecorator)
29 LoginRequired, CSRFRequired, HasRepoPermissionAnyDecorator)
30 from rhodecode.model.scm import ScmModel
30 from rhodecode.model.scm import ScmModel
31
31
32 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
33
33
34
34
35 class RepoSettingsRemoteView(RepoAppView):
35 class RepoSettingsRemoteView(RepoAppView):
36 def load_default_context(self):
36 def load_default_context(self):
37 c = self._get_local_tmpl_context()
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 self._register_global_c(c)
39 self._register_global_c(c)
43 return c
40 return c
44
41
45 @LoginRequired()
42 @LoginRequired()
46 @HasRepoPermissionAnyDecorator('repository.admin')
43 @HasRepoPermissionAnyDecorator('repository.admin')
47 @view_config(
44 @view_config(
48 route_name='edit_repo_remote', request_method='GET',
45 route_name='edit_repo_remote', request_method='GET',
49 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
46 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
50 def repo_remote_edit_form(self):
47 def repo_remote_edit_form(self):
51 c = self.load_default_context()
48 c = self.load_default_context()
52 c.active = 'remote'
49 c.active = 'remote'
53
50
54 return self._get_template_context(c)
51 return self._get_template_context(c)
55
52
56 @LoginRequired()
53 @LoginRequired()
57 @HasRepoPermissionAnyDecorator('repository.admin')
54 @HasRepoPermissionAnyDecorator('repository.admin')
58 @CSRFRequired()
55 @CSRFRequired()
59 @view_config(
56 @view_config(
60 route_name='edit_repo_remote_pull', request_method='POST',
57 route_name='edit_repo_remote_pull', request_method='POST',
61 renderer=None)
58 renderer=None)
62 def repo_remote_pull_changes(self):
59 def repo_remote_pull_changes(self):
63 _ = self.request.translate
60 _ = self.request.translate
64 self.load_default_context()
61 self.load_default_context()
65
62
66 try:
63 try:
67 ScmModel().pull_changes(
64 ScmModel().pull_changes(
68 self.db_repo_name, self._rhodecode_user.username)
65 self.db_repo_name, self._rhodecode_user.username)
69 h.flash(_('Pulled from remote location'), category='success')
66 h.flash(_('Pulled from remote location'), category='success')
70 except Exception:
67 except Exception:
71 log.exception("Exception during pull from remote")
68 log.exception("Exception during pull from remote")
72 h.flash(_('An error occurred during pull from remote location'),
69 h.flash(_('An error occurred during pull from remote location'),
73 category='error')
70 category='error')
74 raise HTTPFound(
71 raise HTTPFound(
75 h.route_path('edit_repo_remote', repo_name=self.db_repo_name))
72 h.route_path('edit_repo_remote', repo_name=self.db_repo_name))
@@ -1,173 +1,170 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2017-2017 RhodeCode GmbH
3 # Copyright (C) 2017-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 import formencode
23 import formencode
24 import formencode.htmlfill
24 import formencode.htmlfill
25 from pyramid.httpexceptions import HTTPFound, HTTPBadRequest
25 from pyramid.httpexceptions import HTTPFound, HTTPBadRequest
26 from pyramid.response import Response
26 from pyramid.response import Response
27 from pyramid.renderers import render
27 from pyramid.renderers import render
28 from pyramid.view import view_config
28 from pyramid.view import view_config
29
29
30 from rhodecode.apps._base import RepoAppView
30 from rhodecode.apps._base import RepoAppView
31 from rhodecode.lib import audit_logger
31 from rhodecode.lib import audit_logger
32 from rhodecode.lib import helpers as h
32 from rhodecode.lib import helpers as h
33 from rhodecode.lib.auth import (
33 from rhodecode.lib.auth import (
34 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
34 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
35 from rhodecode.model.forms import RepoVcsSettingsForm
35 from rhodecode.model.forms import RepoVcsSettingsForm
36 from rhodecode.model.meta import Session
36 from rhodecode.model.meta import Session
37 from rhodecode.model.settings import VcsSettingsModel, SettingNotFound
37 from rhodecode.model.settings import VcsSettingsModel, SettingNotFound
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41
41
42 class RepoSettingsVcsView(RepoAppView):
42 class RepoSettingsVcsView(RepoAppView):
43 def load_default_context(self):
43 def load_default_context(self):
44 c = self._get_local_tmpl_context()
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 self._register_global_c(c)
46 self._register_global_c(c)
50 return c
47 return c
51
48
52 def _vcs_form_defaults(self, repo_name):
49 def _vcs_form_defaults(self, repo_name):
53 model = VcsSettingsModel(repo=repo_name)
50 model = VcsSettingsModel(repo=repo_name)
54 global_defaults = model.get_global_settings()
51 global_defaults = model.get_global_settings()
55
52
56 repo_defaults = {}
53 repo_defaults = {}
57 repo_defaults.update(global_defaults)
54 repo_defaults.update(global_defaults)
58 repo_defaults.update(model.get_repo_settings())
55 repo_defaults.update(model.get_repo_settings())
59
56
60 global_defaults = {
57 global_defaults = {
61 '{}_inherited'.format(k): global_defaults[k]
58 '{}_inherited'.format(k): global_defaults[k]
62 for k in global_defaults}
59 for k in global_defaults}
63
60
64 defaults = {
61 defaults = {
65 'inherit_global_settings': model.inherit_global_settings
62 'inherit_global_settings': model.inherit_global_settings
66 }
63 }
67 defaults.update(global_defaults)
64 defaults.update(global_defaults)
68 defaults.update(repo_defaults)
65 defaults.update(repo_defaults)
69 defaults.update({
66 defaults.update({
70 'new_svn_branch': '',
67 'new_svn_branch': '',
71 'new_svn_tag': '',
68 'new_svn_tag': '',
72 })
69 })
73 return defaults
70 return defaults
74
71
75 @LoginRequired()
72 @LoginRequired()
76 @HasRepoPermissionAnyDecorator('repository.admin')
73 @HasRepoPermissionAnyDecorator('repository.admin')
77 @view_config(
74 @view_config(
78 route_name='edit_repo_vcs', request_method='GET',
75 route_name='edit_repo_vcs', request_method='GET',
79 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
76 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
80 def repo_vcs_settings(self):
77 def repo_vcs_settings(self):
81 c = self.load_default_context()
78 c = self.load_default_context()
82 model = VcsSettingsModel(repo=self.db_repo_name)
79 model = VcsSettingsModel(repo=self.db_repo_name)
83
80
84 c.active = 'vcs'
81 c.active = 'vcs'
85 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
82 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
86 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
83 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
87 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
84 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
88 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
85 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
89
86
90 defaults = self._vcs_form_defaults(self.db_repo_name)
87 defaults = self._vcs_form_defaults(self.db_repo_name)
91 c.inherit_global_settings = defaults['inherit_global_settings']
88 c.inherit_global_settings = defaults['inherit_global_settings']
92
89
93 data = render('rhodecode:templates/admin/repos/repo_edit.mako',
90 data = render('rhodecode:templates/admin/repos/repo_edit.mako',
94 self._get_template_context(c), self.request)
91 self._get_template_context(c), self.request)
95 html = formencode.htmlfill.render(
92 html = formencode.htmlfill.render(
96 data,
93 data,
97 defaults=defaults,
94 defaults=defaults,
98 encoding="UTF-8",
95 encoding="UTF-8",
99 force_defaults=False
96 force_defaults=False
100 )
97 )
101 return Response(html)
98 return Response(html)
102
99
103 @LoginRequired()
100 @LoginRequired()
104 @HasRepoPermissionAnyDecorator('repository.admin')
101 @HasRepoPermissionAnyDecorator('repository.admin')
105 @CSRFRequired()
102 @CSRFRequired()
106 @view_config(
103 @view_config(
107 route_name='edit_repo_vcs_update', request_method='POST',
104 route_name='edit_repo_vcs_update', request_method='POST',
108 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
105 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
109 def repo_settings_vcs_update(self):
106 def repo_settings_vcs_update(self):
110 _ = self.request.translate
107 _ = self.request.translate
111 c = self.load_default_context()
108 c = self.load_default_context()
112 c.active = 'vcs'
109 c.active = 'vcs'
113
110
114 model = VcsSettingsModel(repo=self.db_repo_name)
111 model = VcsSettingsModel(repo=self.db_repo_name)
115 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
112 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
116 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
113 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
117 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
114 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
118 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
115 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
119
116
120 defaults = self._vcs_form_defaults(self.db_repo_name)
117 defaults = self._vcs_form_defaults(self.db_repo_name)
121 c.inherit_global_settings = defaults['inherit_global_settings']
118 c.inherit_global_settings = defaults['inherit_global_settings']
122
119
123 application_form = RepoVcsSettingsForm(self.db_repo_name)()
120 application_form = RepoVcsSettingsForm(self.db_repo_name)()
124 try:
121 try:
125 form_result = application_form.to_python(dict(self.request.POST))
122 form_result = application_form.to_python(dict(self.request.POST))
126 except formencode.Invalid as errors:
123 except formencode.Invalid as errors:
127 h.flash(_("Some form inputs contain invalid data."),
124 h.flash(_("Some form inputs contain invalid data."),
128 category='error')
125 category='error')
129
126
130 data = render('rhodecode:templates/admin/repos/repo_edit.mako',
127 data = render('rhodecode:templates/admin/repos/repo_edit.mako',
131 self._get_template_context(c), self.request)
128 self._get_template_context(c), self.request)
132 html = formencode.htmlfill.render(
129 html = formencode.htmlfill.render(
133 data,
130 data,
134 defaults=errors.value,
131 defaults=errors.value,
135 errors=errors.error_dict or {},
132 errors=errors.error_dict or {},
136 encoding="UTF-8",
133 encoding="UTF-8",
137 force_defaults=False
134 force_defaults=False
138 )
135 )
139 return Response(html)
136 return Response(html)
140
137
141 try:
138 try:
142 inherit_global_settings = form_result['inherit_global_settings']
139 inherit_global_settings = form_result['inherit_global_settings']
143 model.create_or_update_repo_settings(
140 model.create_or_update_repo_settings(
144 form_result, inherit_global_settings=inherit_global_settings)
141 form_result, inherit_global_settings=inherit_global_settings)
145 Session().commit()
142 Session().commit()
146 h.flash(_('Updated VCS settings'), category='success')
143 h.flash(_('Updated VCS settings'), category='success')
147 except Exception:
144 except Exception:
148 log.exception("Exception while updating settings")
145 log.exception("Exception while updating settings")
149 h.flash(
146 h.flash(
150 _('Error occurred during updating repository VCS settings'),
147 _('Error occurred during updating repository VCS settings'),
151 category='error')
148 category='error')
152
149
153 raise HTTPFound(
150 raise HTTPFound(
154 h.route_path('edit_repo_vcs', repo_name=self.db_repo_name))
151 h.route_path('edit_repo_vcs', repo_name=self.db_repo_name))
155
152
156 @LoginRequired()
153 @LoginRequired()
157 @HasRepoPermissionAnyDecorator('repository.admin')
154 @HasRepoPermissionAnyDecorator('repository.admin')
158 @CSRFRequired()
155 @CSRFRequired()
159 @view_config(
156 @view_config(
160 route_name='edit_repo_vcs_svn_pattern_delete', request_method='POST',
157 route_name='edit_repo_vcs_svn_pattern_delete', request_method='POST',
161 renderer='json_ext', xhr=True)
158 renderer='json_ext', xhr=True)
162 def repo_settings_delete_svn_pattern(self):
159 def repo_settings_delete_svn_pattern(self):
163 self.load_default_context()
160 self.load_default_context()
164 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
161 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
165 model = VcsSettingsModel(repo=self.db_repo_name)
162 model = VcsSettingsModel(repo=self.db_repo_name)
166 try:
163 try:
167 model.delete_repo_svn_pattern(delete_pattern_id)
164 model.delete_repo_svn_pattern(delete_pattern_id)
168 except SettingNotFound:
165 except SettingNotFound:
169 log.exception('Failed to delete SVN pattern')
166 log.exception('Failed to delete SVN pattern')
170 raise HTTPBadRequest()
167 raise HTTPBadRequest()
171
168
172 Session().commit()
169 Session().commit()
173 return True
170 return True
@@ -1,116 +1,113 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2017-2017 RhodeCode GmbH
3 # Copyright (C) 2017-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 from pyramid.view import view_config
22 from pyramid.view import view_config
23
23
24 from rhodecode.apps._base import RepoAppView
24 from rhodecode.apps._base import RepoAppView
25 from rhodecode.lib import audit_logger
25 from rhodecode.lib import audit_logger
26 from rhodecode.lib import helpers as h
26 from rhodecode.lib import helpers as h
27 from rhodecode.lib.auth import (
27 from rhodecode.lib.auth import (
28 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
28 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
29 from rhodecode.lib.ext_json import json
29 from rhodecode.lib.ext_json import json
30
30
31 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
32
32
33
33
34 class StripView(RepoAppView):
34 class StripView(RepoAppView):
35 def load_default_context(self):
35 def load_default_context(self):
36 c = self._get_local_tmpl_context()
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 self._register_global_c(c)
38 self._register_global_c(c)
42 return c
39 return c
43
40
44 @LoginRequired()
41 @LoginRequired()
45 @HasRepoPermissionAnyDecorator('repository.admin')
42 @HasRepoPermissionAnyDecorator('repository.admin')
46 @view_config(
43 @view_config(
47 route_name='edit_repo_strip', request_method='GET',
44 route_name='edit_repo_strip', request_method='GET',
48 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
45 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
49 def strip(self):
46 def strip(self):
50 c = self.load_default_context()
47 c = self.load_default_context()
51 c.active = 'strip'
48 c.active = 'strip'
52 c.strip_limit = 10
49 c.strip_limit = 10
53
50
54 return self._get_template_context(c)
51 return self._get_template_context(c)
55
52
56 @LoginRequired()
53 @LoginRequired()
57 @HasRepoPermissionAnyDecorator('repository.admin')
54 @HasRepoPermissionAnyDecorator('repository.admin')
58 @CSRFRequired()
55 @CSRFRequired()
59 @view_config(
56 @view_config(
60 route_name='strip_check', request_method='POST',
57 route_name='strip_check', request_method='POST',
61 renderer='json', xhr=True)
58 renderer='json', xhr=True)
62 def strip_check(self):
59 def strip_check(self):
63 from rhodecode.lib.vcs.backends.base import EmptyCommit
60 from rhodecode.lib.vcs.backends.base import EmptyCommit
64 data = {}
61 data = {}
65 rp = self.request.POST
62 rp = self.request.POST
66 for i in range(1, 11):
63 for i in range(1, 11):
67 chset = 'changeset_id-%d' % (i,)
64 chset = 'changeset_id-%d' % (i,)
68 check = rp.get(chset)
65 check = rp.get(chset)
69
66
70 if check:
67 if check:
71 data[i] = self.db_repo.get_changeset(rp[chset])
68 data[i] = self.db_repo.get_changeset(rp[chset])
72 if isinstance(data[i], EmptyCommit):
69 if isinstance(data[i], EmptyCommit):
73 data[i] = {'rev': None, 'commit': h.escape(rp[chset])}
70 data[i] = {'rev': None, 'commit': h.escape(rp[chset])}
74 else:
71 else:
75 data[i] = {'rev': data[i].raw_id, 'branch': data[i].branch,
72 data[i] = {'rev': data[i].raw_id, 'branch': data[i].branch,
76 'author': data[i].author,
73 'author': data[i].author,
77 'comment': data[i].message}
74 'comment': data[i].message}
78 else:
75 else:
79 break
76 break
80 return data
77 return data
81
78
82 @LoginRequired()
79 @LoginRequired()
83 @HasRepoPermissionAnyDecorator('repository.admin')
80 @HasRepoPermissionAnyDecorator('repository.admin')
84 @CSRFRequired()
81 @CSRFRequired()
85 @view_config(
82 @view_config(
86 route_name='strip_execute', request_method='POST',
83 route_name='strip_execute', request_method='POST',
87 renderer='json', xhr=True)
84 renderer='json', xhr=True)
88 def strip_execute(self):
85 def strip_execute(self):
89 from rhodecode.model.scm import ScmModel
86 from rhodecode.model.scm import ScmModel
90
87
91 c = self.load_default_context()
88 c = self.load_default_context()
92 user = self._rhodecode_user
89 user = self._rhodecode_user
93 rp = self.request.POST
90 rp = self.request.POST
94 data = {}
91 data = {}
95 for idx in rp:
92 for idx in rp:
96 commit = json.loads(rp[idx])
93 commit = json.loads(rp[idx])
97 # If someone put two times the same branch
94 # If someone put two times the same branch
98 if commit['branch'] in data.keys():
95 if commit['branch'] in data.keys():
99 continue
96 continue
100 try:
97 try:
101 ScmModel().strip(
98 ScmModel().strip(
102 repo=self.db_repo,
99 repo=self.db_repo,
103 commit_id=commit['rev'], branch=commit['branch'])
100 commit_id=commit['rev'], branch=commit['branch'])
104 log.info('Stripped commit %s from repo `%s` by %s' % (
101 log.info('Stripped commit %s from repo `%s` by %s' % (
105 commit['rev'], self.db_repo_name, user))
102 commit['rev'], self.db_repo_name, user))
106 data[commit['rev']] = True
103 data[commit['rev']] = True
107
104
108 audit_logger.store_web(
105 audit_logger.store_web(
109 'repo.commit.strip', action_data={'commit_id': commit['rev']},
106 'repo.commit.strip', action_data={'commit_id': commit['rev']},
110 repo=self.db_repo, user=self._rhodecode_user, commit=True)
107 repo=self.db_repo, user=self._rhodecode_user, commit=True)
111
108
112 except Exception as e:
109 except Exception as e:
113 data[commit['rev']] = False
110 data[commit['rev']] = False
114 log.debug('Stripped commit %s from repo `%s` failed by %s, exeption %s' % (
111 log.debug('Stripped commit %s from repo `%s` failed by %s, exeption %s' % (
115 commit['rev'], self.db_repo_name, user, e.message))
112 commit['rev'], self.db_repo_name, user, e.message))
116 return data
113 return data
@@ -1,372 +1,370 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import string
22 import string
23
23
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25 from beaker.cache import cache_region
25 from beaker.cache import cache_region
26
26
27 from rhodecode.controllers import utils
27 from rhodecode.controllers import utils
28 from rhodecode.apps._base import RepoAppView
28 from rhodecode.apps._base import RepoAppView
29 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
29 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
30 from rhodecode.lib import caches, helpers as h
30 from rhodecode.lib import caches, helpers as h
31 from rhodecode.lib.helpers import RepoPage
31 from rhodecode.lib.helpers import RepoPage
32 from rhodecode.lib.utils2 import safe_str, safe_int
32 from rhodecode.lib.utils2 import safe_str, safe_int
33 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
33 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
34 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
34 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
35 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.ext_json import json
36 from rhodecode.lib.vcs.backends.base import EmptyCommit
36 from rhodecode.lib.vcs.backends.base import EmptyCommit
37 from rhodecode.lib.vcs.exceptions import CommitError, EmptyRepositoryError
37 from rhodecode.lib.vcs.exceptions import CommitError, EmptyRepositoryError
38 from rhodecode.model.db import Statistics, CacheKey, User
38 from rhodecode.model.db import Statistics, CacheKey, User
39 from rhodecode.model.meta import Session
39 from rhodecode.model.meta import Session
40 from rhodecode.model.repo import ReadmeFinder
40 from rhodecode.model.repo import ReadmeFinder
41 from rhodecode.model.scm import ScmModel
41 from rhodecode.model.scm import ScmModel
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 class RepoSummaryView(RepoAppView):
46 class RepoSummaryView(RepoAppView):
47
47
48 def load_default_context(self):
48 def load_default_context(self):
49 c = self._get_local_tmpl_context(include_app_defaults=True)
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 c.rhodecode_repo = None
51 c.rhodecode_repo = None
54 if not c.repository_requirements_missing:
52 if not c.repository_requirements_missing:
55 c.rhodecode_repo = self.rhodecode_vcs_repo
53 c.rhodecode_repo = self.rhodecode_vcs_repo
56
54
57 self._register_global_c(c)
55 self._register_global_c(c)
58 return c
56 return c
59
57
60 def _get_readme_data(self, db_repo, default_renderer):
58 def _get_readme_data(self, db_repo, default_renderer):
61 repo_name = db_repo.repo_name
59 repo_name = db_repo.repo_name
62 log.debug('Looking for README file')
60 log.debug('Looking for README file')
63
61
64 @cache_region('long_term')
62 @cache_region('long_term')
65 def _generate_readme(cache_key):
63 def _generate_readme(cache_key):
66 readme_data = None
64 readme_data = None
67 readme_node = None
65 readme_node = None
68 readme_filename = None
66 readme_filename = None
69 commit = self._get_landing_commit_or_none(db_repo)
67 commit = self._get_landing_commit_or_none(db_repo)
70 if commit:
68 if commit:
71 log.debug("Searching for a README file.")
69 log.debug("Searching for a README file.")
72 readme_node = ReadmeFinder(default_renderer).search(commit)
70 readme_node = ReadmeFinder(default_renderer).search(commit)
73 if readme_node:
71 if readme_node:
74 relative_urls = {
72 relative_urls = {
75 'raw': h.route_path(
73 'raw': h.route_path(
76 'repo_file_raw', repo_name=repo_name,
74 'repo_file_raw', repo_name=repo_name,
77 commit_id=commit.raw_id, f_path=readme_node.path),
75 commit_id=commit.raw_id, f_path=readme_node.path),
78 'standard': h.route_path(
76 'standard': h.route_path(
79 'repo_files', repo_name=repo_name,
77 'repo_files', repo_name=repo_name,
80 commit_id=commit.raw_id, f_path=readme_node.path),
78 commit_id=commit.raw_id, f_path=readme_node.path),
81 }
79 }
82 readme_data = self._render_readme_or_none(
80 readme_data = self._render_readme_or_none(
83 commit, readme_node, relative_urls)
81 commit, readme_node, relative_urls)
84 readme_filename = readme_node.path
82 readme_filename = readme_node.path
85 return readme_data, readme_filename
83 return readme_data, readme_filename
86
84
87 invalidator_context = CacheKey.repo_context_cache(
85 invalidator_context = CacheKey.repo_context_cache(
88 _generate_readme, repo_name, CacheKey.CACHE_TYPE_README)
86 _generate_readme, repo_name, CacheKey.CACHE_TYPE_README)
89
87
90 with invalidator_context as context:
88 with invalidator_context as context:
91 context.invalidate()
89 context.invalidate()
92 computed = context.compute()
90 computed = context.compute()
93
91
94 return computed
92 return computed
95
93
96 def _get_landing_commit_or_none(self, db_repo):
94 def _get_landing_commit_or_none(self, db_repo):
97 log.debug("Getting the landing commit.")
95 log.debug("Getting the landing commit.")
98 try:
96 try:
99 commit = db_repo.get_landing_commit()
97 commit = db_repo.get_landing_commit()
100 if not isinstance(commit, EmptyCommit):
98 if not isinstance(commit, EmptyCommit):
101 return commit
99 return commit
102 else:
100 else:
103 log.debug("Repository is empty, no README to render.")
101 log.debug("Repository is empty, no README to render.")
104 except CommitError:
102 except CommitError:
105 log.exception(
103 log.exception(
106 "Problem getting commit when trying to render the README.")
104 "Problem getting commit when trying to render the README.")
107
105
108 def _render_readme_or_none(self, commit, readme_node, relative_urls):
106 def _render_readme_or_none(self, commit, readme_node, relative_urls):
109 log.debug(
107 log.debug(
110 'Found README file `%s` rendering...', readme_node.path)
108 'Found README file `%s` rendering...', readme_node.path)
111 renderer = MarkupRenderer()
109 renderer = MarkupRenderer()
112 try:
110 try:
113 html_source = renderer.render(
111 html_source = renderer.render(
114 readme_node.content, filename=readme_node.path)
112 readme_node.content, filename=readme_node.path)
115 if relative_urls:
113 if relative_urls:
116 return relative_links(html_source, relative_urls)
114 return relative_links(html_source, relative_urls)
117 return html_source
115 return html_source
118 except Exception:
116 except Exception:
119 log.exception(
117 log.exception(
120 "Exception while trying to render the README")
118 "Exception while trying to render the README")
121
119
122 def _load_commits_context(self, c):
120 def _load_commits_context(self, c):
123 p = safe_int(self.request.GET.get('page'), 1)
121 p = safe_int(self.request.GET.get('page'), 1)
124 size = safe_int(self.request.GET.get('size'), 10)
122 size = safe_int(self.request.GET.get('size'), 10)
125
123
126 def url_generator(**kw):
124 def url_generator(**kw):
127 query_params = {
125 query_params = {
128 'size': size
126 'size': size
129 }
127 }
130 query_params.update(kw)
128 query_params.update(kw)
131 return h.route_path(
129 return h.route_path(
132 'repo_summary_commits',
130 'repo_summary_commits',
133 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
131 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
134
132
135 pre_load = ['author', 'branch', 'date', 'message']
133 pre_load = ['author', 'branch', 'date', 'message']
136 try:
134 try:
137 collection = self.rhodecode_vcs_repo.get_commits(pre_load=pre_load)
135 collection = self.rhodecode_vcs_repo.get_commits(pre_load=pre_load)
138 except EmptyRepositoryError:
136 except EmptyRepositoryError:
139 collection = self.rhodecode_vcs_repo
137 collection = self.rhodecode_vcs_repo
140
138
141 c.repo_commits = RepoPage(
139 c.repo_commits = RepoPage(
142 collection, page=p, items_per_page=size, url=url_generator)
140 collection, page=p, items_per_page=size, url=url_generator)
143 page_ids = [x.raw_id for x in c.repo_commits]
141 page_ids = [x.raw_id for x in c.repo_commits]
144 c.comments = self.db_repo.get_comments(page_ids)
142 c.comments = self.db_repo.get_comments(page_ids)
145 c.statuses = self.db_repo.statuses(page_ids)
143 c.statuses = self.db_repo.statuses(page_ids)
146
144
147 @LoginRequired()
145 @LoginRequired()
148 @HasRepoPermissionAnyDecorator(
146 @HasRepoPermissionAnyDecorator(
149 'repository.read', 'repository.write', 'repository.admin')
147 'repository.read', 'repository.write', 'repository.admin')
150 @view_config(
148 @view_config(
151 route_name='repo_summary_commits', request_method='GET',
149 route_name='repo_summary_commits', request_method='GET',
152 renderer='rhodecode:templates/summary/summary_commits.mako')
150 renderer='rhodecode:templates/summary/summary_commits.mako')
153 def summary_commits(self):
151 def summary_commits(self):
154 c = self.load_default_context()
152 c = self.load_default_context()
155 self._load_commits_context(c)
153 self._load_commits_context(c)
156 return self._get_template_context(c)
154 return self._get_template_context(c)
157
155
158 @LoginRequired()
156 @LoginRequired()
159 @HasRepoPermissionAnyDecorator(
157 @HasRepoPermissionAnyDecorator(
160 'repository.read', 'repository.write', 'repository.admin')
158 'repository.read', 'repository.write', 'repository.admin')
161 @view_config(
159 @view_config(
162 route_name='repo_summary', request_method='GET',
160 route_name='repo_summary', request_method='GET',
163 renderer='rhodecode:templates/summary/summary.mako')
161 renderer='rhodecode:templates/summary/summary.mako')
164 @view_config(
162 @view_config(
165 route_name='repo_summary_slash', request_method='GET',
163 route_name='repo_summary_slash', request_method='GET',
166 renderer='rhodecode:templates/summary/summary.mako')
164 renderer='rhodecode:templates/summary/summary.mako')
167 @view_config(
165 @view_config(
168 route_name='repo_summary_explicit', request_method='GET',
166 route_name='repo_summary_explicit', request_method='GET',
169 renderer='rhodecode:templates/summary/summary.mako')
167 renderer='rhodecode:templates/summary/summary.mako')
170 def summary(self):
168 def summary(self):
171 c = self.load_default_context()
169 c = self.load_default_context()
172
170
173 # Prepare the clone URL
171 # Prepare the clone URL
174 username = ''
172 username = ''
175 if self._rhodecode_user.username != User.DEFAULT_USER:
173 if self._rhodecode_user.username != User.DEFAULT_USER:
176 username = safe_str(self._rhodecode_user.username)
174 username = safe_str(self._rhodecode_user.username)
177
175
178 _def_clone_uri = _def_clone_uri_by_id = c.clone_uri_tmpl
176 _def_clone_uri = _def_clone_uri_by_id = c.clone_uri_tmpl
179 if '{repo}' in _def_clone_uri:
177 if '{repo}' in _def_clone_uri:
180 _def_clone_uri_by_id = _def_clone_uri.replace(
178 _def_clone_uri_by_id = _def_clone_uri.replace(
181 '{repo}', '_{repoid}')
179 '{repo}', '_{repoid}')
182 elif '{repoid}' in _def_clone_uri:
180 elif '{repoid}' in _def_clone_uri:
183 _def_clone_uri_by_id = _def_clone_uri.replace(
181 _def_clone_uri_by_id = _def_clone_uri.replace(
184 '_{repoid}', '{repo}')
182 '_{repoid}', '{repo}')
185
183
186 c.clone_repo_url = self.db_repo.clone_url(
184 c.clone_repo_url = self.db_repo.clone_url(
187 user=username, uri_tmpl=_def_clone_uri)
185 user=username, uri_tmpl=_def_clone_uri)
188 c.clone_repo_url_id = self.db_repo.clone_url(
186 c.clone_repo_url_id = self.db_repo.clone_url(
189 user=username, uri_tmpl=_def_clone_uri_by_id)
187 user=username, uri_tmpl=_def_clone_uri_by_id)
190
188
191 # If enabled, get statistics data
189 # If enabled, get statistics data
192
190
193 c.show_stats = bool(self.db_repo.enable_statistics)
191 c.show_stats = bool(self.db_repo.enable_statistics)
194
192
195 stats = Session().query(Statistics) \
193 stats = Session().query(Statistics) \
196 .filter(Statistics.repository == self.db_repo) \
194 .filter(Statistics.repository == self.db_repo) \
197 .scalar()
195 .scalar()
198
196
199 c.stats_percentage = 0
197 c.stats_percentage = 0
200
198
201 if stats and stats.languages:
199 if stats and stats.languages:
202 c.no_data = False is self.db_repo.enable_statistics
200 c.no_data = False is self.db_repo.enable_statistics
203 lang_stats_d = json.loads(stats.languages)
201 lang_stats_d = json.loads(stats.languages)
204
202
205 # Sort first by decreasing count and second by the file extension,
203 # Sort first by decreasing count and second by the file extension,
206 # so we have a consistent output.
204 # so we have a consistent output.
207 lang_stats_items = sorted(lang_stats_d.iteritems(),
205 lang_stats_items = sorted(lang_stats_d.iteritems(),
208 key=lambda k: (-k[1], k[0]))[:10]
206 key=lambda k: (-k[1], k[0]))[:10]
209 lang_stats = [(x, {"count": y,
207 lang_stats = [(x, {"count": y,
210 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
208 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
211 for x, y in lang_stats_items]
209 for x, y in lang_stats_items]
212
210
213 c.trending_languages = json.dumps(lang_stats)
211 c.trending_languages = json.dumps(lang_stats)
214 else:
212 else:
215 c.no_data = True
213 c.no_data = True
216 c.trending_languages = json.dumps({})
214 c.trending_languages = json.dumps({})
217
215
218 scm_model = ScmModel()
216 scm_model = ScmModel()
219 c.enable_downloads = self.db_repo.enable_downloads
217 c.enable_downloads = self.db_repo.enable_downloads
220 c.repository_followers = scm_model.get_followers(self.db_repo)
218 c.repository_followers = scm_model.get_followers(self.db_repo)
221 c.repository_forks = scm_model.get_forks(self.db_repo)
219 c.repository_forks = scm_model.get_forks(self.db_repo)
222 c.repository_is_user_following = scm_model.is_following_repo(
220 c.repository_is_user_following = scm_model.is_following_repo(
223 self.db_repo_name, self._rhodecode_user.user_id)
221 self.db_repo_name, self._rhodecode_user.user_id)
224
222
225 # first interaction with the VCS instance after here...
223 # first interaction with the VCS instance after here...
226 if c.repository_requirements_missing:
224 if c.repository_requirements_missing:
227 self.request.override_renderer = \
225 self.request.override_renderer = \
228 'rhodecode:templates/summary/missing_requirements.mako'
226 'rhodecode:templates/summary/missing_requirements.mako'
229 return self._get_template_context(c)
227 return self._get_template_context(c)
230
228
231 c.readme_data, c.readme_file = \
229 c.readme_data, c.readme_file = \
232 self._get_readme_data(self.db_repo, c.visual.default_renderer)
230 self._get_readme_data(self.db_repo, c.visual.default_renderer)
233
231
234 # loads the summary commits template context
232 # loads the summary commits template context
235 self._load_commits_context(c)
233 self._load_commits_context(c)
236
234
237 return self._get_template_context(c)
235 return self._get_template_context(c)
238
236
239 def get_request_commit_id(self):
237 def get_request_commit_id(self):
240 return self.request.matchdict['commit_id']
238 return self.request.matchdict['commit_id']
241
239
242 @LoginRequired()
240 @LoginRequired()
243 @HasRepoPermissionAnyDecorator(
241 @HasRepoPermissionAnyDecorator(
244 'repository.read', 'repository.write', 'repository.admin')
242 'repository.read', 'repository.write', 'repository.admin')
245 @view_config(
243 @view_config(
246 route_name='repo_stats', request_method='GET',
244 route_name='repo_stats', request_method='GET',
247 renderer='json_ext')
245 renderer='json_ext')
248 def repo_stats(self):
246 def repo_stats(self):
249 commit_id = self.get_request_commit_id()
247 commit_id = self.get_request_commit_id()
250
248
251 _namespace = caches.get_repo_namespace_key(
249 _namespace = caches.get_repo_namespace_key(
252 caches.SUMMARY_STATS, self.db_repo_name)
250 caches.SUMMARY_STATS, self.db_repo_name)
253 show_stats = bool(self.db_repo.enable_statistics)
251 show_stats = bool(self.db_repo.enable_statistics)
254 cache_manager = caches.get_cache_manager(
252 cache_manager = caches.get_cache_manager(
255 'repo_cache_long', _namespace)
253 'repo_cache_long', _namespace)
256 _cache_key = caches.compute_key_from_params(
254 _cache_key = caches.compute_key_from_params(
257 self.db_repo_name, commit_id, show_stats)
255 self.db_repo_name, commit_id, show_stats)
258
256
259 def compute_stats():
257 def compute_stats():
260 code_stats = {}
258 code_stats = {}
261 size = 0
259 size = 0
262 try:
260 try:
263 scm_instance = self.db_repo.scm_instance()
261 scm_instance = self.db_repo.scm_instance()
264 commit = scm_instance.get_commit(commit_id)
262 commit = scm_instance.get_commit(commit_id)
265
263
266 for node in commit.get_filenodes_generator():
264 for node in commit.get_filenodes_generator():
267 size += node.size
265 size += node.size
268 if not show_stats:
266 if not show_stats:
269 continue
267 continue
270 ext = string.lower(node.extension)
268 ext = string.lower(node.extension)
271 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
269 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
272 if ext_info:
270 if ext_info:
273 if ext in code_stats:
271 if ext in code_stats:
274 code_stats[ext]['count'] += 1
272 code_stats[ext]['count'] += 1
275 else:
273 else:
276 code_stats[ext] = {"count": 1, "desc": ext_info}
274 code_stats[ext] = {"count": 1, "desc": ext_info}
277 except EmptyRepositoryError:
275 except EmptyRepositoryError:
278 pass
276 pass
279 return {'size': h.format_byte_size_binary(size),
277 return {'size': h.format_byte_size_binary(size),
280 'code_stats': code_stats}
278 'code_stats': code_stats}
281
279
282 stats = cache_manager.get(_cache_key, createfunc=compute_stats)
280 stats = cache_manager.get(_cache_key, createfunc=compute_stats)
283 return stats
281 return stats
284
282
285 @LoginRequired()
283 @LoginRequired()
286 @HasRepoPermissionAnyDecorator(
284 @HasRepoPermissionAnyDecorator(
287 'repository.read', 'repository.write', 'repository.admin')
285 'repository.read', 'repository.write', 'repository.admin')
288 @view_config(
286 @view_config(
289 route_name='repo_refs_data', request_method='GET',
287 route_name='repo_refs_data', request_method='GET',
290 renderer='json_ext')
288 renderer='json_ext')
291 def repo_refs_data(self):
289 def repo_refs_data(self):
292 _ = self.request.translate
290 _ = self.request.translate
293 self.load_default_context()
291 self.load_default_context()
294
292
295 repo = self.rhodecode_vcs_repo
293 repo = self.rhodecode_vcs_repo
296 refs_to_create = [
294 refs_to_create = [
297 (_("Branch"), repo.branches, 'branch'),
295 (_("Branch"), repo.branches, 'branch'),
298 (_("Tag"), repo.tags, 'tag'),
296 (_("Tag"), repo.tags, 'tag'),
299 (_("Bookmark"), repo.bookmarks, 'book'),
297 (_("Bookmark"), repo.bookmarks, 'book'),
300 ]
298 ]
301 res = self._create_reference_data(
299 res = self._create_reference_data(
302 repo, self.db_repo_name, refs_to_create)
300 repo, self.db_repo_name, refs_to_create)
303 data = {
301 data = {
304 'more': False,
302 'more': False,
305 'results': res
303 'results': res
306 }
304 }
307 return data
305 return data
308
306
309 @LoginRequired()
307 @LoginRequired()
310 @HasRepoPermissionAnyDecorator(
308 @HasRepoPermissionAnyDecorator(
311 'repository.read', 'repository.write', 'repository.admin')
309 'repository.read', 'repository.write', 'repository.admin')
312 @view_config(
310 @view_config(
313 route_name='repo_refs_changelog_data', request_method='GET',
311 route_name='repo_refs_changelog_data', request_method='GET',
314 renderer='json_ext')
312 renderer='json_ext')
315 def repo_refs_changelog_data(self):
313 def repo_refs_changelog_data(self):
316 _ = self.request.translate
314 _ = self.request.translate
317 self.load_default_context()
315 self.load_default_context()
318
316
319 repo = self.rhodecode_vcs_repo
317 repo = self.rhodecode_vcs_repo
320
318
321 refs_to_create = [
319 refs_to_create = [
322 (_("Branches"), repo.branches, 'branch'),
320 (_("Branches"), repo.branches, 'branch'),
323 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
321 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
324 # TODO: enable when vcs can handle bookmarks filters
322 # TODO: enable when vcs can handle bookmarks filters
325 # (_("Bookmarks"), repo.bookmarks, "book"),
323 # (_("Bookmarks"), repo.bookmarks, "book"),
326 ]
324 ]
327 res = self._create_reference_data(
325 res = self._create_reference_data(
328 repo, self.db_repo_name, refs_to_create)
326 repo, self.db_repo_name, refs_to_create)
329 data = {
327 data = {
330 'more': False,
328 'more': False,
331 'results': res
329 'results': res
332 }
330 }
333 return data
331 return data
334
332
335 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
333 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
336 format_ref_id = utils.get_format_ref_id(repo)
334 format_ref_id = utils.get_format_ref_id(repo)
337
335
338 result = []
336 result = []
339 for title, refs, ref_type in refs_to_create:
337 for title, refs, ref_type in refs_to_create:
340 if refs:
338 if refs:
341 result.append({
339 result.append({
342 'text': title,
340 'text': title,
343 'children': self._create_reference_items(
341 'children': self._create_reference_items(
344 repo, full_repo_name, refs, ref_type,
342 repo, full_repo_name, refs, ref_type,
345 format_ref_id),
343 format_ref_id),
346 })
344 })
347 return result
345 return result
348
346
349 def _create_reference_items(self, repo, full_repo_name, refs, ref_type,
347 def _create_reference_items(self, repo, full_repo_name, refs, ref_type,
350 format_ref_id):
348 format_ref_id):
351 result = []
349 result = []
352 is_svn = h.is_svn(repo)
350 is_svn = h.is_svn(repo)
353 for ref_name, raw_id in refs.iteritems():
351 for ref_name, raw_id in refs.iteritems():
354 files_url = self._create_files_url(
352 files_url = self._create_files_url(
355 repo, full_repo_name, ref_name, raw_id, is_svn)
353 repo, full_repo_name, ref_name, raw_id, is_svn)
356 result.append({
354 result.append({
357 'text': ref_name,
355 'text': ref_name,
358 'id': format_ref_id(ref_name, raw_id),
356 'id': format_ref_id(ref_name, raw_id),
359 'raw_id': raw_id,
357 'raw_id': raw_id,
360 'type': ref_type,
358 'type': ref_type,
361 'files_url': files_url,
359 'files_url': files_url,
362 })
360 })
363 return result
361 return result
364
362
365 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
363 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
366 use_commit_id = '/' in ref_name or is_svn
364 use_commit_id = '/' in ref_name or is_svn
367 return h.route_path(
365 return h.route_path(
368 'repo_files',
366 'repo_files',
369 repo_name=full_repo_name,
367 repo_name=full_repo_name,
370 f_path=ref_name if is_svn else '',
368 f_path=ref_name if is_svn else '',
371 commit_id=raw_id if use_commit_id else ref_name,
369 commit_id=raw_id if use_commit_id else ref_name,
372 _query=dict(at=ref_name))
370 _query=dict(at=ref_name))
@@ -1,455 +1,454 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2017 RhodeCode GmbH
3 # Copyright (C) 2012-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import deform
21 import deform
22 import logging
22 import logging
23 import peppercorn
23 import peppercorn
24 import webhelpers.paginate
24 import webhelpers.paginate
25
25
26 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
26 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
27
27
28 from rhodecode.apps._base import BaseAppView
28 from rhodecode.apps._base import BaseAppView
29 from rhodecode.integrations import integration_type_registry
29 from rhodecode.integrations import integration_type_registry
30 from rhodecode.apps.admin.navigation import navigation_list
30 from rhodecode.apps.admin.navigation import navigation_list
31 from rhodecode.lib.auth import (
31 from rhodecode.lib.auth import (
32 LoginRequired, CSRFRequired, HasPermissionAnyDecorator,
32 LoginRequired, CSRFRequired, HasPermissionAnyDecorator,
33 HasRepoPermissionAnyDecorator, HasRepoGroupPermissionAnyDecorator)
33 HasRepoPermissionAnyDecorator, HasRepoGroupPermissionAnyDecorator)
34 from rhodecode.lib.utils2 import safe_int
34 from rhodecode.lib.utils2 import safe_int
35 from rhodecode.lib.helpers import Page
35 from rhodecode.lib.helpers import Page
36 from rhodecode.model.db import Repository, RepoGroup, Session, Integration
36 from rhodecode.model.db import Repository, RepoGroup, Session, Integration
37 from rhodecode.model.scm import ScmModel
37 from rhodecode.model.scm import ScmModel
38 from rhodecode.model.integration import IntegrationModel
38 from rhodecode.model.integration import IntegrationModel
39 from rhodecode.model.validation_schema.schemas.integration_schema import (
39 from rhodecode.model.validation_schema.schemas.integration_schema import (
40 make_integration_schema, IntegrationScopeType)
40 make_integration_schema, IntegrationScopeType)
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44
44
45 class IntegrationSettingsViewBase(BaseAppView):
45 class IntegrationSettingsViewBase(BaseAppView):
46 """
46 """
47 Base Integration settings view used by both repo / global settings
47 Base Integration settings view used by both repo / global settings
48 """
48 """
49
49
50 def __init__(self, context, request):
50 def __init__(self, context, request):
51 super(IntegrationSettingsViewBase, self).__init__(context, request)
51 super(IntegrationSettingsViewBase, self).__init__(context, request)
52 self._load_view_context()
52 self._load_view_context()
53
53
54 def _load_view_context(self):
54 def _load_view_context(self):
55 """
55 """
56 This avoids boilerplate for repo/global+list/edit+views/templates
56 This avoids boilerplate for repo/global+list/edit+views/templates
57 by doing all possible contexts at the same time however it should
57 by doing all possible contexts at the same time however it should
58 be split up into separate functions once more "contexts" exist
58 be split up into separate functions once more "contexts" exist
59 """
59 """
60
60
61 self.IntegrationType = None
61 self.IntegrationType = None
62 self.repo = None
62 self.repo = None
63 self.repo_group = None
63 self.repo_group = None
64 self.integration = None
64 self.integration = None
65 self.integrations = {}
65 self.integrations = {}
66
66
67 request = self.request
67 request = self.request
68
68
69 if 'repo_name' in request.matchdict: # in repo settings context
69 if 'repo_name' in request.matchdict: # in repo settings context
70 repo_name = request.matchdict['repo_name']
70 repo_name = request.matchdict['repo_name']
71 self.repo = Repository.get_by_repo_name(repo_name)
71 self.repo = Repository.get_by_repo_name(repo_name)
72
72
73 if 'repo_group_name' in request.matchdict: # in group settings context
73 if 'repo_group_name' in request.matchdict: # in group settings context
74 repo_group_name = request.matchdict['repo_group_name']
74 repo_group_name = request.matchdict['repo_group_name']
75 self.repo_group = RepoGroup.get_by_group_name(repo_group_name)
75 self.repo_group = RepoGroup.get_by_group_name(repo_group_name)
76
76
77 if 'integration' in request.matchdict: # integration type context
77 if 'integration' in request.matchdict: # integration type context
78 integration_type = request.matchdict['integration']
78 integration_type = request.matchdict['integration']
79 self.IntegrationType = integration_type_registry[integration_type]
79 self.IntegrationType = integration_type_registry[integration_type]
80
80
81 if 'integration_id' in request.matchdict: # single integration context
81 if 'integration_id' in request.matchdict: # single integration context
82 integration_id = request.matchdict['integration_id']
82 integration_id = request.matchdict['integration_id']
83 self.integration = Integration.get(integration_id)
83 self.integration = Integration.get(integration_id)
84
84
85 # extra perms check just in case
85 # extra perms check just in case
86 if not self._has_perms_for_integration(self.integration):
86 if not self._has_perms_for_integration(self.integration):
87 raise HTTPForbidden()
87 raise HTTPForbidden()
88
88
89 self.settings = self.integration and self.integration.settings or {}
89 self.settings = self.integration and self.integration.settings or {}
90 self.admin_view = not (self.repo or self.repo_group)
90 self.admin_view = not (self.repo or self.repo_group)
91
91
92 def _has_perms_for_integration(self, integration):
92 def _has_perms_for_integration(self, integration):
93 perms = self.request.user.permissions
93 perms = self.request.user.permissions
94
94
95 if 'hg.admin' in perms['global']:
95 if 'hg.admin' in perms['global']:
96 return True
96 return True
97
97
98 if integration.repo:
98 if integration.repo:
99 return perms['repositories'].get(
99 return perms['repositories'].get(
100 integration.repo.repo_name) == 'repository.admin'
100 integration.repo.repo_name) == 'repository.admin'
101
101
102 if integration.repo_group:
102 if integration.repo_group:
103 return perms['repositories_groups'].get(
103 return perms['repositories_groups'].get(
104 integration.repo_group.group_name) == 'group.admin'
104 integration.repo_group.group_name) == 'group.admin'
105
105
106 return False
106 return False
107
107
108 def _get_local_tmpl_context(self, include_app_defaults=False):
108 def _get_local_tmpl_context(self, include_app_defaults=False):
109 _ = self.request.translate
109 _ = self.request.translate
110 c = super(IntegrationSettingsViewBase, self)._get_local_tmpl_context(
110 c = super(IntegrationSettingsViewBase, self)._get_local_tmpl_context(
111 include_app_defaults=include_app_defaults)
111 include_app_defaults=include_app_defaults)
112
112
113 c.active = 'integrations'
113 c.active = 'integrations'
114
114
115 return c
115 return c
116
116
117 def _form_schema(self):
117 def _form_schema(self):
118 schema = make_integration_schema(IntegrationType=self.IntegrationType,
118 schema = make_integration_schema(IntegrationType=self.IntegrationType,
119 settings=self.settings)
119 settings=self.settings)
120
120
121 # returns a clone, important if mutating the schema later
121 # returns a clone, important if mutating the schema later
122 return schema.bind(
122 return schema.bind(
123 permissions=self.request.user.permissions,
123 permissions=self.request.user.permissions,
124 no_scope=not self.admin_view)
124 no_scope=not self.admin_view)
125
125
126 def _form_defaults(self):
126 def _form_defaults(self):
127 _ = self.request.translate
127 _ = self.request.translate
128 defaults = {}
128 defaults = {}
129
129
130 if self.integration:
130 if self.integration:
131 defaults['settings'] = self.integration.settings or {}
131 defaults['settings'] = self.integration.settings or {}
132 defaults['options'] = {
132 defaults['options'] = {
133 'name': self.integration.name,
133 'name': self.integration.name,
134 'enabled': self.integration.enabled,
134 'enabled': self.integration.enabled,
135 'scope': {
135 'scope': {
136 'repo': self.integration.repo,
136 'repo': self.integration.repo,
137 'repo_group': self.integration.repo_group,
137 'repo_group': self.integration.repo_group,
138 'child_repos_only': self.integration.child_repos_only,
138 'child_repos_only': self.integration.child_repos_only,
139 },
139 },
140 }
140 }
141 else:
141 else:
142 if self.repo:
142 if self.repo:
143 scope = _('{repo_name} repository').format(
143 scope = _('{repo_name} repository').format(
144 repo_name=self.repo.repo_name)
144 repo_name=self.repo.repo_name)
145 elif self.repo_group:
145 elif self.repo_group:
146 scope = _('{repo_group_name} repo group').format(
146 scope = _('{repo_group_name} repo group').format(
147 repo_group_name=self.repo_group.group_name)
147 repo_group_name=self.repo_group.group_name)
148 else:
148 else:
149 scope = _('Global')
149 scope = _('Global')
150
150
151 defaults['options'] = {
151 defaults['options'] = {
152 'enabled': True,
152 'enabled': True,
153 'name': _('{name} integration').format(
153 'name': _('{name} integration').format(
154 name=self.IntegrationType.display_name),
154 name=self.IntegrationType.display_name),
155 }
155 }
156 defaults['options']['scope'] = {
156 defaults['options']['scope'] = {
157 'repo': self.repo,
157 'repo': self.repo,
158 'repo_group': self.repo_group,
158 'repo_group': self.repo_group,
159 }
159 }
160
160
161 return defaults
161 return defaults
162
162
163 def _delete_integration(self, integration):
163 def _delete_integration(self, integration):
164 _ = self.request.translate
164 _ = self.request.translate
165 Session().delete(integration)
165 Session().delete(integration)
166 Session().commit()
166 Session().commit()
167 self.request.session.flash(
167 self.request.session.flash(
168 _('Integration {integration_name} deleted successfully.').format(
168 _('Integration {integration_name} deleted successfully.').format(
169 integration_name=integration.name),
169 integration_name=integration.name),
170 queue='success')
170 queue='success')
171
171
172 if self.repo:
172 if self.repo:
173 redirect_to = self.request.route_path(
173 redirect_to = self.request.route_path(
174 'repo_integrations_home', repo_name=self.repo.repo_name)
174 'repo_integrations_home', repo_name=self.repo.repo_name)
175 elif self.repo_group:
175 elif self.repo_group:
176 redirect_to = self.request.route_path(
176 redirect_to = self.request.route_path(
177 'repo_group_integrations_home',
177 'repo_group_integrations_home',
178 repo_group_name=self.repo_group.group_name)
178 repo_group_name=self.repo_group.group_name)
179 else:
179 else:
180 redirect_to = self.request.route_path('global_integrations_home')
180 redirect_to = self.request.route_path('global_integrations_home')
181 raise HTTPFound(redirect_to)
181 raise HTTPFound(redirect_to)
182
182
183 def _integration_list(self):
183 def _integration_list(self):
184 """ List integrations """
184 """ List integrations """
185
185
186 c = self.load_default_context()
186 c = self.load_default_context()
187 if self.repo:
187 if self.repo:
188 scope = self.repo
188 scope = self.repo
189 elif self.repo_group:
189 elif self.repo_group:
190 scope = self.repo_group
190 scope = self.repo_group
191 else:
191 else:
192 scope = 'all'
192 scope = 'all'
193
193
194 integrations = []
194 integrations = []
195
195
196 for IntType, integration in IntegrationModel().get_integrations(
196 for IntType, integration in IntegrationModel().get_integrations(
197 scope=scope, IntegrationType=self.IntegrationType):
197 scope=scope, IntegrationType=self.IntegrationType):
198
198
199 # extra permissions check *just in case*
199 # extra permissions check *just in case*
200 if not self._has_perms_for_integration(integration):
200 if not self._has_perms_for_integration(integration):
201 continue
201 continue
202
202
203 integrations.append((IntType, integration))
203 integrations.append((IntType, integration))
204
204
205 sort_arg = self.request.GET.get('sort', 'name:asc')
205 sort_arg = self.request.GET.get('sort', 'name:asc')
206 if ':' in sort_arg:
206 if ':' in sort_arg:
207 sort_field, sort_dir = sort_arg.split(':')
207 sort_field, sort_dir = sort_arg.split(':')
208 else:
208 else:
209 sort_field = sort_arg, 'asc'
209 sort_field = sort_arg, 'asc'
210
210
211 assert sort_field in ('name', 'integration_type', 'enabled', 'scope')
211 assert sort_field in ('name', 'integration_type', 'enabled', 'scope')
212
212
213 integrations.sort(
213 integrations.sort(
214 key=lambda x: getattr(x[1], sort_field),
214 key=lambda x: getattr(x[1], sort_field),
215 reverse=(sort_dir == 'desc'))
215 reverse=(sort_dir == 'desc'))
216
216
217 page_url = webhelpers.paginate.PageURL(
217 page_url = webhelpers.paginate.PageURL(
218 self.request.path, self.request.GET)
218 self.request.path, self.request.GET)
219 page = safe_int(self.request.GET.get('page', 1), 1)
219 page = safe_int(self.request.GET.get('page', 1), 1)
220
220
221 integrations = Page(
221 integrations = Page(
222 integrations, page=page, items_per_page=10, url=page_url)
222 integrations, page=page, items_per_page=10, url=page_url)
223
223
224 c.rev_sort_dir = sort_dir != 'desc' and 'desc' or 'asc'
224 c.rev_sort_dir = sort_dir != 'desc' and 'desc' or 'asc'
225
225
226 c.current_IntegrationType = self.IntegrationType
226 c.current_IntegrationType = self.IntegrationType
227 c.integrations_list = integrations
227 c.integrations_list = integrations
228 c.available_integrations = integration_type_registry
228 c.available_integrations = integration_type_registry
229
229
230 return self._get_template_context(c)
230 return self._get_template_context(c)
231
231
232 def _settings_get(self, defaults=None, form=None):
232 def _settings_get(self, defaults=None, form=None):
233 """
233 """
234 View that displays the integration settings as a form.
234 View that displays the integration settings as a form.
235 """
235 """
236 c = self.load_default_context()
236 c = self.load_default_context()
237
237
238 defaults = defaults or self._form_defaults()
238 defaults = defaults or self._form_defaults()
239 schema = self._form_schema()
239 schema = self._form_schema()
240
240
241 if self.integration:
241 if self.integration:
242 buttons = ('submit', 'delete')
242 buttons = ('submit', 'delete')
243 else:
243 else:
244 buttons = ('submit',)
244 buttons = ('submit',)
245
245
246 form = form or deform.Form(schema, appstruct=defaults, buttons=buttons)
246 form = form or deform.Form(schema, appstruct=defaults, buttons=buttons)
247
247
248 c.form = form
248 c.form = form
249 c.current_IntegrationType = self.IntegrationType
249 c.current_IntegrationType = self.IntegrationType
250 c.integration = self.integration
250 c.integration = self.integration
251
251
252 return self._get_template_context(c)
252 return self._get_template_context(c)
253
253
254 def _settings_post(self):
254 def _settings_post(self):
255 """
255 """
256 View that validates and stores the integration settings.
256 View that validates and stores the integration settings.
257 """
257 """
258 _ = self.request.translate
258 _ = self.request.translate
259
259
260 controls = self.request.POST.items()
260 controls = self.request.POST.items()
261 pstruct = peppercorn.parse(controls)
261 pstruct = peppercorn.parse(controls)
262
262
263 if self.integration and pstruct.get('delete'):
263 if self.integration and pstruct.get('delete'):
264 return self._delete_integration(self.integration)
264 return self._delete_integration(self.integration)
265
265
266 schema = self._form_schema()
266 schema = self._form_schema()
267
267
268 skip_settings_validation = False
268 skip_settings_validation = False
269 if self.integration and 'enabled' not in pstruct.get('options', {}):
269 if self.integration and 'enabled' not in pstruct.get('options', {}):
270 skip_settings_validation = True
270 skip_settings_validation = True
271 schema['settings'].validator = None
271 schema['settings'].validator = None
272 for field in schema['settings'].children:
272 for field in schema['settings'].children:
273 field.validator = None
273 field.validator = None
274 field.missing = ''
274 field.missing = ''
275
275
276 if self.integration:
276 if self.integration:
277 buttons = ('submit', 'delete')
277 buttons = ('submit', 'delete')
278 else:
278 else:
279 buttons = ('submit',)
279 buttons = ('submit',)
280
280
281 form = deform.Form(schema, buttons=buttons)
281 form = deform.Form(schema, buttons=buttons)
282
282
283 if not self.admin_view:
283 if not self.admin_view:
284 # scope is read only field in these cases, and has to be added
284 # scope is read only field in these cases, and has to be added
285 options = pstruct.setdefault('options', {})
285 options = pstruct.setdefault('options', {})
286 if 'scope' not in options:
286 if 'scope' not in options:
287 options['scope'] = IntegrationScopeType().serialize(None, {
287 options['scope'] = IntegrationScopeType().serialize(None, {
288 'repo': self.repo,
288 'repo': self.repo,
289 'repo_group': self.repo_group,
289 'repo_group': self.repo_group,
290 })
290 })
291
291
292 try:
292 try:
293 valid_data = form.validate_pstruct(pstruct)
293 valid_data = form.validate_pstruct(pstruct)
294 except deform.ValidationFailure as e:
294 except deform.ValidationFailure as e:
295 self.request.session.flash(
295 self.request.session.flash(
296 _('Errors exist when saving integration settings. '
296 _('Errors exist when saving integration settings. '
297 'Please check the form inputs.'),
297 'Please check the form inputs.'),
298 queue='error')
298 queue='error')
299 return self._settings_get(form=e)
299 return self._settings_get(form=e)
300
300
301 if not self.integration:
301 if not self.integration:
302 self.integration = Integration()
302 self.integration = Integration()
303 self.integration.integration_type = self.IntegrationType.key
303 self.integration.integration_type = self.IntegrationType.key
304 Session().add(self.integration)
304 Session().add(self.integration)
305
305
306 scope = valid_data['options']['scope']
306 scope = valid_data['options']['scope']
307
307
308 IntegrationModel().update_integration(self.integration,
308 IntegrationModel().update_integration(self.integration,
309 name=valid_data['options']['name'],
309 name=valid_data['options']['name'],
310 enabled=valid_data['options']['enabled'],
310 enabled=valid_data['options']['enabled'],
311 settings=valid_data['settings'],
311 settings=valid_data['settings'],
312 repo=scope['repo'],
312 repo=scope['repo'],
313 repo_group=scope['repo_group'],
313 repo_group=scope['repo_group'],
314 child_repos_only=scope['child_repos_only'],
314 child_repos_only=scope['child_repos_only'],
315 )
315 )
316
316
317 self.integration.settings = valid_data['settings']
317 self.integration.settings = valid_data['settings']
318 Session().commit()
318 Session().commit()
319 # Display success message and redirect.
319 # Display success message and redirect.
320 self.request.session.flash(
320 self.request.session.flash(
321 _('Integration {integration_name} updated successfully.').format(
321 _('Integration {integration_name} updated successfully.').format(
322 integration_name=self.IntegrationType.display_name),
322 integration_name=self.IntegrationType.display_name),
323 queue='success')
323 queue='success')
324
324
325 # if integration scope changes, we must redirect to the right place
325 # if integration scope changes, we must redirect to the right place
326 # keeping in mind if the original view was for /repo/ or /_admin/
326 # keeping in mind if the original view was for /repo/ or /_admin/
327 admin_view = not (self.repo or self.repo_group)
327 admin_view = not (self.repo or self.repo_group)
328
328
329 if self.integration.repo and not admin_view:
329 if self.integration.repo and not admin_view:
330 redirect_to = self.request.route_path(
330 redirect_to = self.request.route_path(
331 'repo_integrations_edit',
331 'repo_integrations_edit',
332 repo_name=self.integration.repo.repo_name,
332 repo_name=self.integration.repo.repo_name,
333 integration=self.integration.integration_type,
333 integration=self.integration.integration_type,
334 integration_id=self.integration.integration_id)
334 integration_id=self.integration.integration_id)
335 elif self.integration.repo_group and not admin_view:
335 elif self.integration.repo_group and not admin_view:
336 redirect_to = self.request.route_path(
336 redirect_to = self.request.route_path(
337 'repo_group_integrations_edit',
337 'repo_group_integrations_edit',
338 repo_group_name=self.integration.repo_group.group_name,
338 repo_group_name=self.integration.repo_group.group_name,
339 integration=self.integration.integration_type,
339 integration=self.integration.integration_type,
340 integration_id=self.integration.integration_id)
340 integration_id=self.integration.integration_id)
341 else:
341 else:
342 redirect_to = self.request.route_path(
342 redirect_to = self.request.route_path(
343 'global_integrations_edit',
343 'global_integrations_edit',
344 integration=self.integration.integration_type,
344 integration=self.integration.integration_type,
345 integration_id=self.integration.integration_id)
345 integration_id=self.integration.integration_id)
346
346
347 return HTTPFound(redirect_to)
347 return HTTPFound(redirect_to)
348
348
349 def _new_integration(self):
349 def _new_integration(self):
350 c = self.load_default_context()
350 c = self.load_default_context()
351 c.available_integrations = integration_type_registry
351 c.available_integrations = integration_type_registry
352 return self._get_template_context(c)
352 return self._get_template_context(c)
353
353
354 def load_default_context(self):
354 def load_default_context(self):
355 raise NotImplementedError()
355 raise NotImplementedError()
356
356
357
357
358 class GlobalIntegrationsView(IntegrationSettingsViewBase):
358 class GlobalIntegrationsView(IntegrationSettingsViewBase):
359 def load_default_context(self):
359 def load_default_context(self):
360 c = self._get_local_tmpl_context()
360 c = self._get_local_tmpl_context()
361 c.repo = self.repo
361 c.repo = self.repo
362 c.repo_group = self.repo_group
362 c.repo_group = self.repo_group
363 c.navlist = navigation_list(self.request)
363 c.navlist = navigation_list(self.request)
364 self._register_global_c(c)
364 self._register_global_c(c)
365 return c
365 return c
366
366
367 @LoginRequired()
367 @LoginRequired()
368 @HasPermissionAnyDecorator('hg.admin')
368 @HasPermissionAnyDecorator('hg.admin')
369 def integration_list(self):
369 def integration_list(self):
370 return self._integration_list()
370 return self._integration_list()
371
371
372 @LoginRequired()
372 @LoginRequired()
373 @HasPermissionAnyDecorator('hg.admin')
373 @HasPermissionAnyDecorator('hg.admin')
374 def settings_get(self):
374 def settings_get(self):
375 return self._settings_get()
375 return self._settings_get()
376
376
377 @LoginRequired()
377 @LoginRequired()
378 @HasPermissionAnyDecorator('hg.admin')
378 @HasPermissionAnyDecorator('hg.admin')
379 @CSRFRequired()
379 @CSRFRequired()
380 def settings_post(self):
380 def settings_post(self):
381 return self._settings_post()
381 return self._settings_post()
382
382
383 @LoginRequired()
383 @LoginRequired()
384 @HasPermissionAnyDecorator('hg.admin')
384 @HasPermissionAnyDecorator('hg.admin')
385 def new_integration(self):
385 def new_integration(self):
386 return self._new_integration()
386 return self._new_integration()
387
387
388
388
389 class RepoIntegrationsView(IntegrationSettingsViewBase):
389 class RepoIntegrationsView(IntegrationSettingsViewBase):
390 def load_default_context(self):
390 def load_default_context(self):
391 c = self._get_local_tmpl_context()
391 c = self._get_local_tmpl_context()
392
392
393 c.repo = self.repo
393 c.repo = self.repo
394 c.repo_group = self.repo_group
394 c.repo_group = self.repo_group
395
395
396 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
396 self.db_repo = self.repo
397 c.repo_info = self.db_repo = self.repo
398 c.rhodecode_db_repo = self.repo
397 c.rhodecode_db_repo = self.repo
399 c.repo_name = self.db_repo.repo_name
398 c.repo_name = self.db_repo.repo_name
400 c.repository_pull_requests = ScmModel().get_pull_requests(self.repo)
399 c.repository_pull_requests = ScmModel().get_pull_requests(self.repo)
401
400
402 self._register_global_c(c)
401 self._register_global_c(c)
403 return c
402 return c
404
403
405 @LoginRequired()
404 @LoginRequired()
406 @HasRepoPermissionAnyDecorator('repository.admin')
405 @HasRepoPermissionAnyDecorator('repository.admin')
407 def integration_list(self):
406 def integration_list(self):
408 return self._integration_list()
407 return self._integration_list()
409
408
410 @LoginRequired()
409 @LoginRequired()
411 @HasRepoPermissionAnyDecorator('repository.admin')
410 @HasRepoPermissionAnyDecorator('repository.admin')
412 def settings_get(self):
411 def settings_get(self):
413 return self._settings_get()
412 return self._settings_get()
414
413
415 @LoginRequired()
414 @LoginRequired()
416 @HasRepoPermissionAnyDecorator('repository.admin')
415 @HasRepoPermissionAnyDecorator('repository.admin')
417 @CSRFRequired()
416 @CSRFRequired()
418 def settings_post(self):
417 def settings_post(self):
419 return self._settings_post()
418 return self._settings_post()
420
419
421 @LoginRequired()
420 @LoginRequired()
422 @HasRepoPermissionAnyDecorator('repository.admin')
421 @HasRepoPermissionAnyDecorator('repository.admin')
423 def new_integration(self):
422 def new_integration(self):
424 return self._new_integration()
423 return self._new_integration()
425
424
426
425
427 class RepoGroupIntegrationsView(IntegrationSettingsViewBase):
426 class RepoGroupIntegrationsView(IntegrationSettingsViewBase):
428 def load_default_context(self):
427 def load_default_context(self):
429 c = self._get_local_tmpl_context()
428 c = self._get_local_tmpl_context()
430 c.repo = self.repo
429 c.repo = self.repo
431 c.repo_group = self.repo_group
430 c.repo_group = self.repo_group
432 c.navlist = navigation_list(self.request)
431 c.navlist = navigation_list(self.request)
433 self._register_global_c(c)
432 self._register_global_c(c)
434 return c
433 return c
435
434
436 @LoginRequired()
435 @LoginRequired()
437 @HasRepoGroupPermissionAnyDecorator('group.admin')
436 @HasRepoGroupPermissionAnyDecorator('group.admin')
438 def integration_list(self):
437 def integration_list(self):
439 return self._integration_list()
438 return self._integration_list()
440
439
441 @LoginRequired()
440 @LoginRequired()
442 @HasRepoGroupPermissionAnyDecorator('group.admin')
441 @HasRepoGroupPermissionAnyDecorator('group.admin')
443 def settings_get(self):
442 def settings_get(self):
444 return self._settings_get()
443 return self._settings_get()
445
444
446 @LoginRequired()
445 @LoginRequired()
447 @HasRepoGroupPermissionAnyDecorator('group.admin')
446 @HasRepoGroupPermissionAnyDecorator('group.admin')
448 @CSRFRequired()
447 @CSRFRequired()
449 def settings_post(self):
448 def settings_post(self):
450 return self._settings_post()
449 return self._settings_post()
451
450
452 @LoginRequired()
451 @LoginRequired()
453 @HasRepoGroupPermissionAnyDecorator('group.admin')
452 @HasRepoGroupPermissionAnyDecorator('group.admin')
454 def new_integration(self):
453 def new_integration(self):
455 return self._new_integration()
454 return self._new_integration()
@@ -1,99 +1,99 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 ##
2 ##
3 ## See also repo_settings.html
3 ## See also repo_settings.html
4 ##
4 ##
5 <%inherit file="/base/base.mako"/>
5 <%inherit file="/base/base.mako"/>
6
6
7 <%def name="title()">
7 <%def name="title()">
8 ${_('%s repository settings') % c.repo_info.repo_name}
8 ${_('%s repository settings') % c.rhodecode_db_repo.repo_name}
9 %if c.rhodecode_name:
9 %if c.rhodecode_name:
10 &middot; ${h.branding(c.rhodecode_name)}
10 &middot; ${h.branding(c.rhodecode_name)}
11 %endif
11 %endif
12 </%def>
12 </%def>
13
13
14 <%def name="breadcrumbs_links()">
14 <%def name="breadcrumbs_links()">
15 ${_('Settings')}
15 ${_('Settings')}
16 </%def>
16 </%def>
17
17
18 <%def name="menu_bar_nav()">
18 <%def name="menu_bar_nav()">
19 ${self.menu_items(active='repositories')}
19 ${self.menu_items(active='repositories')}
20 </%def>
20 </%def>
21
21
22 <%def name="menu_bar_subnav()">
22 <%def name="menu_bar_subnav()">
23 ${self.repo_menu(active='options')}
23 ${self.repo_menu(active='options')}
24 </%def>
24 </%def>
25
25
26 <%def name="main_content()">
26 <%def name="main_content()">
27 % if hasattr(c, 'repo_edit_template'):
27 % if hasattr(c, 'repo_edit_template'):
28 <%include file="${c.repo_edit_template}"/>
28 <%include file="${c.repo_edit_template}"/>
29 % else:
29 % else:
30 <%include file="/admin/repos/repo_edit_${c.active}.mako"/>
30 <%include file="/admin/repos/repo_edit_${c.active}.mako"/>
31 % endif
31 % endif
32 </%def>
32 </%def>
33
33
34
34
35 <%def name="main()">
35 <%def name="main()">
36 <div class="box">
36 <div class="box">
37 <div class="title">
37 <div class="title">
38 ${self.repo_page_title(c.rhodecode_db_repo)}
38 ${self.repo_page_title(c.rhodecode_db_repo)}
39 ${self.breadcrumbs()}
39 ${self.breadcrumbs()}
40 </div>
40 </div>
41
41
42 <div class="sidebar-col-wrapper scw-small">
42 <div class="sidebar-col-wrapper scw-small">
43 <div class="sidebar">
43 <div class="sidebar">
44 <ul class="nav nav-pills nav-stacked">
44 <ul class="nav nav-pills nav-stacked">
45 <li class="${'active' if c.active=='settings' else ''}">
45 <li class="${'active' if c.active=='settings' else ''}">
46 <a href="${h.route_path('edit_repo', repo_name=c.repo_name)}">${_('Settings')}</a>
46 <a href="${h.route_path('edit_repo', repo_name=c.repo_name)}">${_('Settings')}</a>
47 </li>
47 </li>
48 <li class="${'active' if c.active=='permissions' else ''}">
48 <li class="${'active' if c.active=='permissions' else ''}">
49 <a href="${h.route_path('edit_repo_perms', repo_name=c.repo_name)}">${_('Permissions')}</a>
49 <a href="${h.route_path('edit_repo_perms', repo_name=c.repo_name)}">${_('Permissions')}</a>
50 </li>
50 </li>
51 <li class="${'active' if c.active=='advanced' else ''}">
51 <li class="${'active' if c.active=='advanced' else ''}">
52 <a href="${h.route_path('edit_repo_advanced', repo_name=c.repo_name)}">${_('Advanced')}</a>
52 <a href="${h.route_path('edit_repo_advanced', repo_name=c.repo_name)}">${_('Advanced')}</a>
53 </li>
53 </li>
54 <li class="${'active' if c.active=='vcs' else ''}">
54 <li class="${'active' if c.active=='vcs' else ''}">
55 <a href="${h.route_path('edit_repo_vcs', repo_name=c.repo_name)}">${_('VCS')}</a>
55 <a href="${h.route_path('edit_repo_vcs', repo_name=c.repo_name)}">${_('VCS')}</a>
56 </li>
56 </li>
57 <li class="${'active' if c.active=='fields' else ''}">
57 <li class="${'active' if c.active=='fields' else ''}">
58 <a href="${h.route_path('edit_repo_fields', repo_name=c.repo_name)}">${_('Extra Fields')}</a>
58 <a href="${h.route_path('edit_repo_fields', repo_name=c.repo_name)}">${_('Extra Fields')}</a>
59 </li>
59 </li>
60 <li class="${'active' if c.active=='issuetracker' else ''}">
60 <li class="${'active' if c.active=='issuetracker' else ''}">
61 <a href="${h.route_path('edit_repo_issuetracker', repo_name=c.repo_name)}">${_('Issue Tracker')}</a>
61 <a href="${h.route_path('edit_repo_issuetracker', repo_name=c.repo_name)}">${_('Issue Tracker')}</a>
62 </li>
62 </li>
63 <li class="${'active' if c.active=='caches' else ''}">
63 <li class="${'active' if c.active=='caches' else ''}">
64 <a href="${h.route_path('edit_repo_caches', repo_name=c.repo_name)}">${_('Caches')}</a>
64 <a href="${h.route_path('edit_repo_caches', repo_name=c.repo_name)}">${_('Caches')}</a>
65 </li>
65 </li>
66 %if c.repo_info.repo_type != 'svn':
66 %if c.rhodecode_db_repo.repo_type != 'svn':
67 <li class="${'active' if c.active=='remote' else ''}">
67 <li class="${'active' if c.active=='remote' else ''}">
68 <a href="${h.route_path('edit_repo_remote', repo_name=c.repo_name)}">${_('Remote')}</a>
68 <a href="${h.route_path('edit_repo_remote', repo_name=c.repo_name)}">${_('Remote')}</a>
69 </li>
69 </li>
70 %endif
70 %endif
71 <li class="${'active' if c.active=='statistics' else ''}">
71 <li class="${'active' if c.active=='statistics' else ''}">
72 <a href="${h.route_path('edit_repo_statistics', repo_name=c.repo_name)}">${_('Statistics')}</a>
72 <a href="${h.route_path('edit_repo_statistics', repo_name=c.repo_name)}">${_('Statistics')}</a>
73 </li>
73 </li>
74 <li class="${'active' if c.active=='integrations' else ''}">
74 <li class="${'active' if c.active=='integrations' else ''}">
75 <a href="${h.route_path('repo_integrations_home', repo_name=c.repo_name)}">${_('Integrations')}</a>
75 <a href="${h.route_path('repo_integrations_home', repo_name=c.repo_name)}">${_('Integrations')}</a>
76 </li>
76 </li>
77 %if c.repo_info.repo_type != 'svn':
77 %if c.rhodecode_db_repo.repo_type != 'svn':
78 <li class="${'active' if c.active=='reviewers' else ''}">
78 <li class="${'active' if c.active=='reviewers' else ''}">
79 <a href="${h.route_path('repo_reviewers', repo_name=c.repo_name)}">${_('Reviewer Rules')}</a>
79 <a href="${h.route_path('repo_reviewers', repo_name=c.repo_name)}">${_('Reviewer Rules')}</a>
80 </li>
80 </li>
81 %endif
81 %endif
82 <li class="${'active' if c.active=='maintenance' else ''}">
82 <li class="${'active' if c.active=='maintenance' else ''}">
83 <a href="${h.route_path('edit_repo_maintenance', repo_name=c.repo_name)}">${_('Maintenance')}</a>
83 <a href="${h.route_path('edit_repo_maintenance', repo_name=c.repo_name)}">${_('Maintenance')}</a>
84 </li>
84 </li>
85 <li class="${'active' if c.active=='strip' else ''}">
85 <li class="${'active' if c.active=='strip' else ''}">
86 <a href="${h.route_path('edit_repo_strip', repo_name=c.repo_name)}">${_('Strip')}</a>
86 <a href="${h.route_path('edit_repo_strip', repo_name=c.repo_name)}">${_('Strip')}</a>
87 </li>
87 </li>
88
88
89 </ul>
89 </ul>
90 </div>
90 </div>
91
91
92 <div class="main-content-full-width">
92 <div class="main-content-full-width">
93 ${self.main_content()}
93 ${self.main_content()}
94 </div>
94 </div>
95
95
96 </div>
96 </div>
97 </div>
97 </div>
98
98
99 </%def> No newline at end of file
99 </%def>
@@ -1,210 +1,210 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <%
3 <%
4 elems = [
4 elems = [
5 (_('Owner'), lambda:base.gravatar_with_user(c.repo_info.user.email), '', ''),
5 (_('Owner'), lambda:base.gravatar_with_user(c.rhodecode_db_repo.user.email), '', ''),
6 (_('Created on'), h.format_date(c.repo_info.created_on), '', ''),
6 (_('Created on'), h.format_date(c.rhodecode_db_repo.created_on), '', ''),
7 (_('Updated on'), h.format_date(c.repo_info.updated_on), '', ''),
7 (_('Updated on'), h.format_date(c.rhodecode_db_repo.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'))), '', ''),
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 <div class="panel panel-default">
12 <div class="panel panel-default">
13 <div class="panel-heading" id="advanced-info" >
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 </div>
15 </div>
16 <div class="panel-body">
16 <div class="panel-body">
17 ${base.dt_info_panel(elems)}
17 ${base.dt_info_panel(elems)}
18 </div>
18 </div>
19 </div>
19 </div>
20
20
21
21
22 <div class="panel panel-default">
22 <div class="panel panel-default">
23 <div class="panel-heading" id="advanced-fork">
23 <div class="panel-heading" id="advanced-fork">
24 <h3 class="panel-title">${_('Fork Reference')} <a class="permalink" href="#advanced-fork"> ¶</a></h3>
24 <h3 class="panel-title">${_('Fork Reference')} <a class="permalink" href="#advanced-fork"> ¶</a></h3>
25 </div>
25 </div>
26 <div class="panel-body">
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:
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.repo_info.fork.repo_name, h.route_path('repo_summary', repo_name=c.repo_info.fork.repo_name))})}
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 | <button class="btn btn-link btn-danger" type="submit">Remove fork reference</button></div>
31 | <button class="btn btn-link btn-danger" type="submit">Remove fork reference</button></div>
32 % endif
32 % endif
33
33
34 <div class="field">
34 <div class="field">
35 ${h.hidden('id_fork_of')}
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 </div>
37 </div>
38 <div class="field">
38 <div class="field">
39 <span class="help-block">${_('Manually set this repository as a fork of another from the list')}</span>
39 <span class="help-block">${_('Manually set this repository as a fork of another from the list')}</span>
40 </div>
40 </div>
41 ${h.end_form()}
41 ${h.end_form()}
42 </div>
42 </div>
43 </div>
43 </div>
44
44
45
45
46 <div class="panel panel-default">
46 <div class="panel panel-default">
47 <div class="panel-heading" id="advanced-journal">
47 <div class="panel-heading" id="advanced-journal">
48 <h3 class="panel-title">${_('Public Journal Visibility')} <a class="permalink" href="#advanced-journal"> ¶</a></h3>
48 <h3 class="panel-title">${_('Public Journal Visibility')} <a class="permalink" href="#advanced-journal"> ¶</a></h3>
49 </div>
49 </div>
50 <div class="panel-body">
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 <div class="field">
52 <div class="field">
53 %if c.in_public_journal:
53 %if c.in_public_journal:
54 <button class="btn btn-small" type="submit">
54 <button class="btn btn-small" type="submit">
55 ${_('Remove from Public Journal')}
55 ${_('Remove from Public Journal')}
56 </button>
56 </button>
57 %else:
57 %else:
58 <button class="btn btn-small" type="submit">
58 <button class="btn btn-small" type="submit">
59 ${_('Add to Public Journal')}
59 ${_('Add to Public Journal')}
60 </button>
60 </button>
61 %endif
61 %endif
62 </div>
62 </div>
63 <div class="field" >
63 <div class="field" >
64 <span class="help-block">${_('All actions made on this repository will be visible to everyone following the public journal.')}</span>
64 <span class="help-block">${_('All actions made on this repository will be visible to everyone following the public journal.')}</span>
65 </div>
65 </div>
66 ${h.end_form()}
66 ${h.end_form()}
67 </div>
67 </div>
68 </div>
68 </div>
69
69
70
70
71 <div class="panel panel-default">
71 <div class="panel panel-default">
72 <div class="panel-heading" id="advanced-locking">
72 <div class="panel-heading" id="advanced-locking">
73 <h3 class="panel-title">${_('Locking state')} <a class="permalink" href="#advanced-locking"> ¶</a></h3>
73 <h3 class="panel-title">${_('Locking state')} <a class="permalink" href="#advanced-locking"> ¶</a></h3>
74 </div>
74 </div>
75 <div class="panel-body">
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]:
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.repo_info.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.repo_info.locked[1])), c.repo_info.locked[2])}</div>
80 h.format_date(h. time_to_datetime(c.rhodecode_db_repo.locked[1])), c.rhodecode_db_repo.locked[2])}</div>
81 %else:
81 %else:
82 <div class="panel-body-title-text">${_('This Repository is not currently locked.')}</div>
82 <div class="panel-body-title-text">${_('This Repository is not currently locked.')}</div>
83 %endif
83 %endif
84
84
85 <div class="field" >
85 <div class="field" >
86 %if c.repo_info.locked[0]:
86 %if c.rhodecode_db_repo.locked[0]:
87 ${h.hidden('set_unlock', '1')}
87 ${h.hidden('set_unlock', '1')}
88 <button class="btn btn-small" type="submit"
88 <button class="btn btn-small" type="submit"
89 onclick="return confirm('${_('Confirm to unlock repository.')}');">
89 onclick="return confirm('${_('Confirm to unlock repository.')}');">
90 <i class="icon-unlock"></i>
90 <i class="icon-unlock"></i>
91 ${_('Unlock repository')}
91 ${_('Unlock repository')}
92 </button>
92 </button>
93 %else:
93 %else:
94 ${h.hidden('set_lock', '1')}
94 ${h.hidden('set_lock', '1')}
95 <button class="btn btn-small" type="submit"
95 <button class="btn btn-small" type="submit"
96 onclick="return confirm('${_('Confirm to lock repository.')}');">
96 onclick="return confirm('${_('Confirm to lock repository.')}');">
97 <i class="icon-lock"></i>
97 <i class="icon-lock"></i>
98 ${_('Lock Repository')}
98 ${_('Lock Repository')}
99 </button>
99 </button>
100 %endif
100 %endif
101 </div>
101 </div>
102 <div class="field" >
102 <div class="field" >
103 <span class="help-block">
103 <span class="help-block">
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.')}
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 </span>
105 </span>
106 </div>
106 </div>
107 ${h.end_form()}
107 ${h.end_form()}
108 </div>
108 </div>
109 </div>
109 </div>
110
110
111 <div class="panel panel-danger">
111 <div class="panel panel-danger">
112 <div class="panel-heading" id="advanced-delete">
112 <div class="panel-heading" id="advanced-delete">
113 <h3 class="panel-title">${_('Delete repository')} <a class="permalink" href="#advanced-delete"> ¶</a></h3>
113 <h3 class="panel-title">${_('Delete repository')} <a class="permalink" href="#advanced-delete"> ¶</a></h3>
114 </div>
114 </div>
115 <div class="panel-body">
115 <div class="panel-body">
116 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=c.repo_name), method='POST', request=request)}
116 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=c.repo_name), method='POST', request=request)}
117 <table class="display">
117 <table class="display">
118 <tr>
118 <tr>
119 <td>
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 </td>
121 </td>
122 <td>
122 <td>
123 %if c.repo_info.forks.count():
123 %if c.rhodecode_db_repo.forks.count():
124 <input type="radio" name="forks" value="detach_forks" checked="checked"/> <label for="forks">${_('Detach forks')}</label>
124 <input type="radio" name="forks" value="detach_forks" checked="checked"/> <label for="forks">${_('Detach forks')}</label>
125 %endif
125 %endif
126 </td>
126 </td>
127 <td>
127 <td>
128 %if c.repo_info.forks.count():
128 %if c.rhodecode_db_repo.forks.count():
129 <input type="radio" name="forks" value="delete_forks"/> <label for="forks">${_('Delete forks')}</label>
129 <input type="radio" name="forks" value="delete_forks"/> <label for="forks">${_('Delete forks')}</label>
130 %endif
130 %endif
131 </td>
131 </td>
132 </tr>
132 </tr>
133 </table>
133 </table>
134 <div style="margin: 0 0 20px 0" class="fake-space"></div>
134 <div style="margin: 0 0 20px 0" class="fake-space"></div>
135
135
136 <div class="field">
136 <div class="field">
137 <button class="btn btn-small btn-danger" type="submit"
137 <button class="btn btn-small btn-danger" type="submit"
138 onclick="return confirm('${_('Confirm to delete this repository: %s') % c.repo_name}');">
138 onclick="return confirm('${_('Confirm to delete this repository: %s') % c.repo_name}');">
139 <i class="icon-remove-sign"></i>
139 <i class="icon-remove-sign"></i>
140 ${_('Delete This Repository')}
140 ${_('Delete This Repository')}
141 </button>
141 </button>
142 </div>
142 </div>
143 <div class="field">
143 <div class="field">
144 <span class="help-block">
144 <span class="help-block">
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.')}
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 </span>
146 </span>
147 </div>
147 </div>
148
148
149 ${h.end_form()}
149 ${h.end_form()}
150 </div>
150 </div>
151 </div>
151 </div>
152
152
153
153
154 <script>
154 <script>
155
155
156 var currentRepoId = ${c.repo_info.repo_id};
156 var currentRepoId = ${c.rhodecode_db_repo.repo_id};
157
157
158 var repoTypeFilter = function(data) {
158 var repoTypeFilter = function(data) {
159 var results = [];
159 var results = [];
160
160
161 if (!data.results[0]) {
161 if (!data.results[0]) {
162 return data
162 return data
163 }
163 }
164
164
165 $.each(data.results[0].children, function() {
165 $.each(data.results[0].children, function() {
166 // filter out the SAME repo, it cannot be used as fork of itself
166 // filter out the SAME repo, it cannot be used as fork of itself
167 if (this.obj.repo_id != currentRepoId) {
167 if (this.obj.repo_id != currentRepoId) {
168 this.id = this.obj.repo_id;
168 this.id = this.obj.repo_id;
169 results.push(this)
169 results.push(this)
170 }
170 }
171 });
171 });
172 data.results[0].children = results;
172 data.results[0].children = results;
173 return data;
173 return data;
174 };
174 };
175
175
176 $("#id_fork_of").select2({
176 $("#id_fork_of").select2({
177 cachedDataSource: {},
177 cachedDataSource: {},
178 minimumInputLength: 2,
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 dropdownAutoWidth: true,
180 dropdownAutoWidth: true,
181 containerCssClass: "drop-menu",
181 containerCssClass: "drop-menu",
182 dropdownCssClass: "drop-menu-dropdown",
182 dropdownCssClass: "drop-menu-dropdown",
183 formatResult: formatResult,
183 formatResult: formatResult,
184 query: $.debounce(250, function(query){
184 query: $.debounce(250, function(query){
185 self = this;
185 self = this;
186 var cacheKey = query.term;
186 var cacheKey = query.term;
187 var cachedData = self.cachedDataSource[cacheKey];
187 var cachedData = self.cachedDataSource[cacheKey];
188
188
189 if (cachedData) {
189 if (cachedData) {
190 query.callback({results: cachedData.results});
190 query.callback({results: cachedData.results});
191 } else {
191 } else {
192 $.ajax({
192 $.ajax({
193 url: pyroutes.url('repo_list_data'),
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 dataType: 'json',
195 dataType: 'json',
196 type: 'GET',
196 type: 'GET',
197 success: function(data) {
197 success: function(data) {
198 data = repoTypeFilter(data);
198 data = repoTypeFilter(data);
199 self.cachedDataSource[cacheKey] = data;
199 self.cachedDataSource[cacheKey] = data;
200 query.callback({results: data.results});
200 query.callback({results: data.results});
201 },
201 },
202 error: function(data, textStatus, errorThrown) {
202 error: function(data, textStatus, errorThrown) {
203 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
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 </script>
209 </script>
210
210
@@ -1,53 +1,53 b''
1 <div class="panel panel-default">
1 <div class="panel panel-default">
2 <div class="panel-heading">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('Invalidate Cache for Repository')}</h3>
3 <h3 class="panel-title">${_('Invalidate Cache for Repository')}</h3>
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
6
6
7 <h4>${_('Manually invalidate the repository cache. On the next access a repository cache will be recreated.')}</h4>
7 <h4>${_('Manually invalidate the repository cache. On the next access a repository cache will be recreated.')}</h4>
8
8
9 <p>
9 <p>
10 ${_('Cache purge can be automated by such api call. Can be called periodically in crontab etc.')}
10 ${_('Cache purge can be automated by such api call. Can be called periodically in crontab etc.')}
11 <br/>
11 <br/>
12 <code>
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 </code>
14 </code>
15 </p>
15 </p>
16
16
17 ${h.secure_form(h.route_path('edit_repo_caches', repo_name=c.repo_name), method='POST', request=request)}
17 ${h.secure_form(h.route_path('edit_repo_caches', repo_name=c.repo_name), method='POST', request=request)}
18 <div class="form">
18 <div class="form">
19 <div class="fields">
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 </div>
21 </div>
22 </div>
22 </div>
23 ${h.end_form()}
23 ${h.end_form()}
24
24
25 </div>
25 </div>
26 </div>
26 </div>
27
27
28
28
29 <div class="panel panel-default">
29 <div class="panel panel-default">
30 <div class="panel-heading">
30 <div class="panel-heading">
31 <h3 class="panel-title">
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 </h3>
33 </h3>
34 </div>
34 </div>
35 <div class="panel-body">
35 <div class="panel-body">
36 <div class="field" >
36 <div class="field" >
37 <table class="rctable edit_cache">
37 <table class="rctable edit_cache">
38 <tr>
38 <tr>
39 <th>${_('Prefix')}</th>
39 <th>${_('Prefix')}</th>
40 <th>${_('Key')}</th>
40 <th>${_('Key')}</th>
41 <th>${_('Active')}</th>
41 <th>${_('Active')}</th>
42 </tr>
42 </tr>
43 %for cache in c.repo_info.cache_keys:
43 %for cache in c.rhodecode_db_repo.cache_keys:
44 <tr>
44 <tr>
45 <td class="td-prefix">${cache.get_prefix() or '-'}</td>
45 <td class="td-prefix">${cache.get_prefix() or '-'}</td>
46 <td class="td-cachekey">${cache.cache_key}</td>
46 <td class="td-cachekey">${cache.cache_key}</td>
47 <td class="td-active">${h.bool2icon(cache.cache_active)}</td>
47 <td class="td-active">${h.bool2icon(cache.cache_active)}</td>
48 </tr>
48 </tr>
49 %endfor
49 %endfor
50 </table>
50 </table>
51 </div>
51 </div>
52 </div>
52 </div>
53 </div>
53 </div>
@@ -1,79 +1,79 b''
1 <div class="panel panel-default">
1 <div class="panel panel-default">
2 <div class="panel-heading">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('Custom extra fields for this repository')}</h3>
3 <h3 class="panel-title">${_('Custom extra fields for this repository')}</h3>
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
6 %if c.visual.repository_fields:
6 %if c.visual.repository_fields:
7 %if c.repo_fields:
7 %if c.repo_fields:
8 <div class="emails_wrap">
8 <div class="emails_wrap">
9 <table class="rctable edit_fields">
9 <table class="rctable edit_fields">
10 <th>${_('Label')}</th>
10 <th>${_('Label')}</th>
11 <th>${_('Key')}</th>
11 <th>${_('Key')}</th>
12 <th>${_('Type')}</th>
12 <th>${_('Type')}</th>
13 <th>${_('Action')}</th>
13 <th>${_('Action')}</th>
14
14
15 %for field in c.repo_fields:
15 %for field in c.repo_fields:
16 <tr>
16 <tr>
17 <td class="td-tags">${field.field_label}</td>
17 <td class="td-tags">${field.field_label}</td>
18 <td class="td-hash">${field.field_key}</td>
18 <td class="td-hash">${field.field_key}</td>
19 <td class="td-type">${field.field_type}</td>
19 <td class="td-type">${field.field_type}</td>
20 <td class="td-action">
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 ${h.hidden('del_repo_field',field.repo_field_id)}
22 ${h.hidden('del_repo_field',field.repo_field_id)}
23 <button class="btn btn-link btn-danger" type="submit"
23 <button class="btn btn-link btn-danger" type="submit"
24 onclick="return confirm('${_('Confirm to delete this field: %s') % field.field_key}');">
24 onclick="return confirm('${_('Confirm to delete this field: %s') % field.field_key}');">
25 ${_('Delete')}
25 ${_('Delete')}
26 </button>
26 </button>
27 ${h.end_form()}
27 ${h.end_form()}
28 </td>
28 </td>
29 </tr>
29 </tr>
30 %endfor
30 %endfor
31 </table>
31 </table>
32 </div>
32 </div>
33 %endif
33 %endif
34 ${h.secure_form(h.route_path('edit_repo_fields_create', repo_name=c.repo_name), method='POST', request=request)}
34 ${h.secure_form(h.route_path('edit_repo_fields_create', repo_name=c.repo_name), method='POST', request=request)}
35 <div class="form">
35 <div class="form">
36 <!-- fields -->
36 <!-- fields -->
37 <div class="fields">
37 <div class="fields">
38 <div class="field">
38 <div class="field">
39 <div class="label">
39 <div class="label">
40 <label for="new_field_key">${_('New Field Key')}:</label>
40 <label for="new_field_key">${_('New Field Key')}:</label>
41 </div>
41 </div>
42 <div class="input">
42 <div class="input">
43 ${h.text('new_field_key', class_='medium')}
43 ${h.text('new_field_key', class_='medium')}
44 </div>
44 </div>
45 </div>
45 </div>
46 <div class="field">
46 <div class="field">
47 <div class="label">
47 <div class="label">
48 <label for="new_field_label">${_('New Field Label')}:</label>
48 <label for="new_field_label">${_('New Field Label')}:</label>
49 </div>
49 </div>
50 <div class="input">
50 <div class="input">
51 ${h.text('new_field_label', class_='medium', placeholder=_('Enter short label'))}
51 ${h.text('new_field_label', class_='medium', placeholder=_('Enter short label'))}
52 </div>
52 </div>
53 </div>
53 </div>
54
54
55 <div class="field">
55 <div class="field">
56 <div class="label">
56 <div class="label">
57 <label for="new_field_desc">${_('New Field Description')}:</label>
57 <label for="new_field_desc">${_('New Field Description')}:</label>
58 </div>
58 </div>
59 <div class="input">
59 <div class="input">
60 ${h.text('new_field_desc', class_='medium', placeholder=_('Enter a full description for the field'))}
60 ${h.text('new_field_desc', class_='medium', placeholder=_('Enter a full description for the field'))}
61 </div>
61 </div>
62 </div>
62 </div>
63
63
64 <div class="buttons">
64 <div class="buttons">
65 ${h.submit('save',_('Add'),class_="btn")}
65 ${h.submit('save',_('Add'),class_="btn")}
66 ${h.reset('reset',_('Reset'),class_="btn")}
66 ${h.reset('reset',_('Reset'),class_="btn")}
67 </div>
67 </div>
68 </div>
68 </div>
69 </div>
69 </div>
70 ${h.end_form()}
70 ${h.end_form()}
71 %else:
71 %else:
72 <h2>
72 <h2>
73 ${_('Extra fields are disabled. You can enable them from the Admin/Settings/Visual page.')}
73 ${_('Extra fields are disabled. You can enable them from the Admin/Settings/Visual page.')}
74 </h2>
74 </h2>
75 %endif
75 %endif
76 </div>
76 </div>
77 </div>
77 </div>
78
78
79
79
@@ -1,109 +1,109 b''
1 <%namespace name="its" file="/base/issue_tracker_settings.mako"/>
1 <%namespace name="its" file="/base/issue_tracker_settings.mako"/>
2
2
3 <div id="repo_issue_tracker" class="${'inherited' if c.settings_model.inherit_global_settings else ''}">
3 <div id="repo_issue_tracker" class="${'inherited' if c.settings_model.inherit_global_settings else ''}">
4 ${h.secure_form(h.route_path('edit_repo_issuetracker_update', repo_name=c.repo_name), id="inherit-form", method='POST', request=request)}
4 ${h.secure_form(h.route_path('edit_repo_issuetracker_update', repo_name=c.repo_name), id="inherit-form", method='POST', request=request)}
5 <div class="panel panel-default panel-body">
5 <div class="panel panel-default panel-body">
6 <div class="fields">
6 <div class="fields">
7 <div class="field">
7 <div class="field">
8 <div class="label label-checkbox">
8 <div class="label label-checkbox">
9 <label for="inherit_default_permissions">${_('Inherit from global settings')}:</label>
9 <label for="inherit_default_permissions">${_('Inherit from global settings')}:</label>
10 </div>
10 </div>
11 <div class="checkboxes">
11 <div class="checkboxes">
12 ${h.checkbox('inherit_global_issuetracker', value='inherited', checked=c.settings_model.inherit_global_settings)}
12 ${h.checkbox('inherit_global_issuetracker', value='inherited', checked=c.settings_model.inherit_global_settings)}
13 <span class="help-block">
13 <span class="help-block">
14 ${h.literal(_('Select to inherit global patterns for issue tracker.'))}
14 ${h.literal(_('Select to inherit global patterns for issue tracker.'))}
15 </span>
15 </span>
16 </div>
16 </div>
17 </div>
17 </div>
18 </div>
18 </div>
19 </div>
19 </div>
20
20
21 <div id="inherit_overlay">
21 <div id="inherit_overlay">
22 <div class="panel panel-default">
22 <div class="panel panel-default">
23 <div class="panel-heading">
23 <div class="panel-heading">
24 <h3 class="panel-title">${_('Inherited Issue Tracker Patterns')}</h3>
24 <h3 class="panel-title">${_('Inherited Issue Tracker Patterns')}</h3>
25 </div>
25 </div>
26 <div class="panel-body">
26 <div class="panel-body">
27 <table class="rctable issuetracker readonly">
27 <table class="rctable issuetracker readonly">
28 <tr>
28 <tr>
29 <th>${_('Description')}</th>
29 <th>${_('Description')}</th>
30 <th>${_('Pattern')}</th>
30 <th>${_('Pattern')}</th>
31 <th>${_('Url')}</th>
31 <th>${_('Url')}</th>
32 <th>${_('Prefix')}</th>
32 <th>${_('Prefix')}</th>
33 <th ></th>
33 <th ></th>
34 </tr>
34 </tr>
35 %for uid, entry in c.global_patterns.items():
35 %for uid, entry in c.global_patterns.items():
36 <tr id="${uid}">
36 <tr id="${uid}">
37 <td class="td-description issuetracker_desc">
37 <td class="td-description issuetracker_desc">
38 <span class="entry">
38 <span class="entry">
39 ${entry.desc}
39 ${entry.desc}
40 </span>
40 </span>
41 </td>
41 </td>
42 <td class="td-regex issuetracker_pat">
42 <td class="td-regex issuetracker_pat">
43 <span class="entry">
43 <span class="entry">
44 ${entry.pat}
44 ${entry.pat}
45 </span>
45 </span>
46 </td>
46 </td>
47 <td class="td-url issuetracker_url">
47 <td class="td-url issuetracker_url">
48 <span class="entry">
48 <span class="entry">
49 ${entry.url}
49 ${entry.url}
50 </span>
50 </span>
51 </td>
51 </td>
52 <td class="td-prefix issuetracker_pref">
52 <td class="td-prefix issuetracker_pref">
53 <span class="entry">
53 <span class="entry">
54 ${entry.pref}
54 ${entry.pref}
55 </span>
55 </span>
56 </td>
56 </td>
57 <td class="td-action">
57 <td class="td-action">
58 </td>
58 </td>
59 </tr>
59 </tr>
60 %endfor
60 %endfor
61
61
62 </table>
62 </table>
63 </div>
63 </div>
64 </div>
64 </div>
65 </div>
65 </div>
66
66
67 <div id="custom_overlay">
67 <div id="custom_overlay">
68 <div class="panel panel-default">
68 <div class="panel panel-default">
69 <div class="panel-heading">
69 <div class="panel-heading">
70 <h3 class="panel-title">${_('Issue Tracker / Wiki Patterns')}</h3>
70 <h3 class="panel-title">${_('Issue Tracker / Wiki Patterns')}</h3>
71 </div>
71 </div>
72 <div class="panel-body">
72 <div class="panel-body">
73 ${its.issue_tracker_settings_table(
73 ${its.issue_tracker_settings_table(
74 patterns=c.repo_patterns.items(),
74 patterns=c.repo_patterns.items(),
75 form_url=h.route_path('edit_repo_issuetracker', 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.repo_info.repo_name)
76 delete_url=h.route_path('edit_repo_issuetracker_delete', repo_name=c.rhodecode_db_repo.repo_name)
77 )}
77 )}
78 <div class="buttons">
78 <div class="buttons">
79 <button type="submit" class="btn btn-primary save-inheritance" id="save">${_('Save')}</button>
79 <button type="submit" class="btn btn-primary save-inheritance" id="save">${_('Save')}</button>
80 <button type="reset" class="btn reset-inheritance">${_('Reset')}</button>
80 <button type="reset" class="btn reset-inheritance">${_('Reset')}</button>
81 </div>
81 </div>
82 </div>
82 </div>
83 </div>
83 </div>
84 </div>
84 </div>
85
85
86
86
87 ${h.end_form()}
87 ${h.end_form()}
88
88
89 <div class="panel panel-default">
89 <div class="panel panel-default">
90 <div class="panel-heading">
90 <div class="panel-heading">
91 <h3 class="panel-title">${_('Test Patterns')}</h3>
91 <h3 class="panel-title">${_('Test Patterns')}</h3>
92 </div>
92 </div>
93 <div class="panel-body">
93 <div class="panel-body">
94 ${its.issue_tracker_new_row()}
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 </div>
96 </div>
97 </div>
97 </div>
98
98
99 </div>
99 </div>
100
100
101 <script>
101 <script>
102 $('#inherit_global_issuetracker').on('change', function(e){
102 $('#inherit_global_issuetracker').on('change', function(e){
103 $('#repo_issue_tracker').toggleClass('inherited',this.checked);
103 $('#repo_issue_tracker').toggleClass('inherited',this.checked);
104 });
104 });
105
105
106 $('.reset-inheritance').on('click', function(e){
106 $('.reset-inheritance').on('click', function(e){
107 $('#inherit_global_issuetracker').prop('checked', false).change();
107 $('#inherit_global_issuetracker').prop('checked', false).change();
108 });
108 });
109 </script>
109 </script>
@@ -1,66 +1,66 b''
1 <div class="panel panel-default">
1 <div class="panel panel-default">
2 <div class="panel-heading">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('Maintenance')}</h3>
3 <h3 class="panel-title">${_('Maintenance')}</h3>
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
6
6
7 % if c.executable_tasks:
7 % if c.executable_tasks:
8 <h4>${_('Perform maintenance tasks for this repo')}</h4>
8 <h4>${_('Perform maintenance tasks for this repo')}</h4>
9
9
10 <span>${_('Following tasks will be performed')}:</span>
10 <span>${_('Following tasks will be performed')}:</span>
11 <ol>
11 <ol>
12 % for task in c.executable_tasks:
12 % for task in c.executable_tasks:
13 <li>${task}</li>
13 <li>${task}</li>
14 % endfor
14 % endfor
15 </ol>
15 </ol>
16 <p>
16 <p>
17 ${_('Maintenance can be automated by such api call. Can be called periodically in crontab etc.')}
17 ${_('Maintenance can be automated by such api call. Can be called periodically in crontab etc.')}
18 <br/>
18 <br/>
19 <code>
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 </code>
21 </code>
22 </p>
22 </p>
23
23
24 % else:
24 % else:
25 <h4>${_('No maintenance tasks for this repo available')}</h4>
25 <h4>${_('No maintenance tasks for this repo available')}</h4>
26 % endif
26 % endif
27
27
28 <div id="results" style="display:none; padding: 10px 0px;"></div>
28 <div id="results" style="display:none; padding: 10px 0px;"></div>
29
29
30 % if c.executable_tasks:
30 % if c.executable_tasks:
31 <div class="form">
31 <div class="form">
32 <div class="fields">
32 <div class="fields">
33 <button class="btn btn-small btn-primary" onclick="executeTask();return false">
33 <button class="btn btn-small btn-primary" onclick="executeTask();return false">
34 ${_('Run Maintenance')}
34 ${_('Run Maintenance')}
35 </button>
35 </button>
36 </div>
36 </div>
37 </div>
37 </div>
38 % endif
38 % endif
39
39
40 </div>
40 </div>
41 </div>
41 </div>
42
42
43
43
44 <script>
44 <script>
45
45
46 executeTask = function() {
46 executeTask = function() {
47 var btn = $(this);
47 var btn = $(this);
48 $('#results').show();
48 $('#results').show();
49 $('#results').html('<h4>${_('Performing Maintenance')}...</h4>');
49 $('#results').html('<h4>${_('Performing Maintenance')}...</h4>');
50
50
51 btn.attr('disabled', 'disabled');
51 btn.attr('disabled', 'disabled');
52 btn.addClass('disabled');
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 var success = function (data) {
55 var success = function (data) {
56 var displayHtml = $('<pre></pre>');
56 var displayHtml = $('<pre></pre>');
57
57
58 $(displayHtml).append(data);
58 $(displayHtml).append(data);
59 $('#results').html(displayHtml);
59 $('#results').html(displayHtml);
60 btn.removeAttr('disabled');
60 btn.removeAttr('disabled');
61 btn.removeClass('disabled');
61 btn.removeClass('disabled');
62 };
62 };
63 ajaxGET(url, success, null);
63 ajaxGET(url, success, null);
64
64
65 }
65 }
66 </script>
66 </script>
@@ -1,123 +1,123 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <div class="panel panel-default">
3 <div class="panel panel-default">
4 <div class="panel-heading">
4 <div class="panel-heading">
5 <h3 class="panel-title">${_('Repository Permissions')}</h3>
5 <h3 class="panel-title">${_('Repository Permissions')}</h3>
6 </div>
6 </div>
7 <div class="panel-body">
7 <div class="panel-body">
8 ${h.secure_form(h.route_path('edit_repo_perms', repo_name=c.repo_name), method='POST', request=request)}
8 ${h.secure_form(h.route_path('edit_repo_perms', repo_name=c.repo_name), method='POST', request=request)}
9 <table id="permissions_manage" class="rctable permissions">
9 <table id="permissions_manage" class="rctable permissions">
10 <tr>
10 <tr>
11 <th class="td-radio">${_('None')}</th>
11 <th class="td-radio">${_('None')}</th>
12 <th class="td-radio">${_('Read')}</th>
12 <th class="td-radio">${_('Read')}</th>
13 <th class="td-radio">${_('Write')}</th>
13 <th class="td-radio">${_('Write')}</th>
14 <th class="td-radio">${_('Admin')}</th>
14 <th class="td-radio">${_('Admin')}</th>
15 <th class="td-owner">${_('User/User Group')}</th>
15 <th class="td-owner">${_('User/User Group')}</th>
16 <th></th>
16 <th></th>
17 </tr>
17 </tr>
18 ## USERS
18 ## USERS
19 %for _user in c.repo_info.permissions():
19 %for _user in c.rhodecode_db_repo.permissions():
20 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
20 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
21 <tr class="perm_admin_row">
21 <tr class="perm_admin_row">
22 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
22 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
23 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
23 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
26 <td class="td-user">
26 <td class="td-user">
27 ${base.gravatar(_user.email, 16)}
27 ${base.gravatar(_user.email, 16)}
28 ${h.link_to_user(_user.username)}
28 ${h.link_to_user(_user.username)}
29 %if getattr(_user, 'admin_row', None):
29 %if getattr(_user, 'admin_row', None):
30 (${_('super admin')})
30 (${_('super admin')})
31 %endif
31 %endif
32 %if getattr(_user, 'owner_row', None):
32 %if getattr(_user, 'owner_row', None):
33 (${_('owner')})
33 (${_('owner')})
34 %endif
34 %endif
35 </td>
35 </td>
36 <td></td>
36 <td></td>
37 </tr>
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 <tr>
39 <tr>
40 <td colspan="4">
40 <td colspan="4">
41 <span class="private_repo_msg">
41 <span class="private_repo_msg">
42 <strong title="${h.tooltip(_user.permission)}">${_('private repository')}</strong>
42 <strong title="${h.tooltip(_user.permission)}">${_('private repository')}</strong>
43 </span>
43 </span>
44 </td>
44 </td>
45 <td class="private_repo_msg">
45 <td class="private_repo_msg">
46 ${base.gravatar(h.DEFAULT_USER_EMAIL, 16)}
46 ${base.gravatar(h.DEFAULT_USER_EMAIL, 16)}
47 ${h.DEFAULT_USER} - ${_('only users/user groups explicitly added here will have access')}</td>
47 ${h.DEFAULT_USER} - ${_('only users/user groups explicitly added here will have access')}</td>
48 <td></td>
48 <td></td>
49 </tr>
49 </tr>
50 %else:
50 %else:
51 <tr>
51 <tr>
52 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.none', checked=_user.permission=='repository.none')}</td>
52 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.none', checked=_user.permission=='repository.none')}</td>
53 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.read', checked=_user.permission=='repository.read')}</td>
53 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.read', checked=_user.permission=='repository.read')}</td>
54 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.write', checked=_user.permission=='repository.write')}</td>
54 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.write', checked=_user.permission=='repository.write')}</td>
55 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.admin', checked=_user.permission=='repository.admin')}</td>
55 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.admin', checked=_user.permission=='repository.admin')}</td>
56 <td class="td-user">
56 <td class="td-user">
57 ${base.gravatar(_user.email, 16)}
57 ${base.gravatar(_user.email, 16)}
58 <span class="user">
58 <span class="user">
59 % if _user.username == h.DEFAULT_USER:
59 % if _user.username == h.DEFAULT_USER:
60 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
60 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
61 % else:
61 % else:
62 ${h.link_to_user(_user.username)}
62 ${h.link_to_user(_user.username)}
63 % endif
63 % endif
64 </span>
64 </span>
65 </td>
65 </td>
66 <td class="td-action">
66 <td class="td-action">
67 %if _user.username != h.DEFAULT_USER:
67 %if _user.username != h.DEFAULT_USER:
68 <span class="btn btn-link btn-danger revoke_perm"
68 <span class="btn btn-link btn-danger revoke_perm"
69 member="${_user.user_id}" member_type="user">
69 member="${_user.user_id}" member_type="user">
70 <i class="icon-remove"></i> ${_('Revoke')}
70 <i class="icon-remove"></i> ${_('Revoke')}
71 </span>
71 </span>
72 %endif
72 %endif
73 </td>
73 </td>
74 </tr>
74 </tr>
75 %endif
75 %endif
76 %endfor
76 %endfor
77
77
78 ## USER GROUPS
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 <tr>
80 <tr>
81 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.none', checked=_user_group.permission=='repository.none')}</td>
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 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.read', checked=_user_group.permission=='repository.read')}</td>
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 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.write', checked=_user_group.permission=='repository.write')}</td>
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 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.admin', checked=_user_group.permission=='repository.admin')}</td>
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 <td class="td-componentname">
85 <td class="td-componentname">
86 <i class="icon-group" ></i>
86 <i class="icon-group" ></i>
87 %if h.HasPermissionAny('hg.admin')():
87 %if h.HasPermissionAny('hg.admin')():
88 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
88 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
89 ${_user_group.users_group_name}
89 ${_user_group.users_group_name}
90 </a>
90 </a>
91 %else:
91 %else:
92 ${_user_group.users_group_name}
92 ${_user_group.users_group_name}
93 %endif
93 %endif
94 </td>
94 </td>
95 <td class="td-action">
95 <td class="td-action">
96 <span class="btn btn-link btn-danger revoke_perm"
96 <span class="btn btn-link btn-danger revoke_perm"
97 member="${_user_group.users_group_id}" member_type="user_group">
97 member="${_user_group.users_group_id}" member_type="user_group">
98 <i class="icon-remove"></i> ${_('Revoke')}
98 <i class="icon-remove"></i> ${_('Revoke')}
99 </span>
99 </span>
100 </td>
100 </td>
101 </tr>
101 </tr>
102 %endfor
102 %endfor
103 <tr class="new_members" id="add_perm_input"></tr>
103 <tr class="new_members" id="add_perm_input"></tr>
104 </table>
104 </table>
105 <div id="add_perm" class="link">
105 <div id="add_perm" class="link">
106 ${_('Add new')}
106 ${_('Add new')}
107 </div>
107 </div>
108 <div class="buttons">
108 <div class="buttons">
109 ${h.submit('save',_('Save'),class_="btn btn-primary")}
109 ${h.submit('save',_('Save'),class_="btn btn-primary")}
110 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
110 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
111 </div>
111 </div>
112 ${h.end_form()}
112 ${h.end_form()}
113 </div>
113 </div>
114 </div>
114 </div>
115
115
116 <script type="text/javascript">
116 <script type="text/javascript">
117 $('#add_perm').on('click', function(e){
117 $('#add_perm').on('click', function(e){
118 addNewPermInput($(this), 'repository');
118 addNewPermInput($(this), 'repository');
119 });
119 });
120 $('.revoke_perm').on('click', function(e){
120 $('.revoke_perm').on('click', function(e){
121 markRevokePermInput($(this), 'repository');
121 markRevokePermInput($(this), 'repository');
122 });
122 });
123 </script>
123 </script>
@@ -1,40 +1,40 b''
1 <div class="panel panel-default">
1 <div class="panel panel-default">
2 <div class="panel-heading">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('Remote url')}</h3>
3 <h3 class="panel-title">${_('Remote url')}</h3>
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
6
6
7 <h4>${_('Manually pull changes from external repository.')}</h4>
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 ${_('Remote mirror url')}:
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 <p>
14 <p>
15 ${_('Pull can be automated by such api call. Can be called periodically in crontab etc.')}
15 ${_('Pull can be automated by such api call. Can be called periodically in crontab etc.')}
16 <br/>
16 <br/>
17 <code>
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 </code>
19 </code>
20 </p>
20 </p>
21
21
22 ${h.secure_form(h.route_path('edit_repo_remote_pull', repo_name=c.repo_name), method='POST', request=request)}
22 ${h.secure_form(h.route_path('edit_repo_remote_pull', repo_name=c.repo_name), method='POST', request=request)}
23 <div class="form">
23 <div class="form">
24 <div class="fields">
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 </div>
26 </div>
27 </div>
27 </div>
28 ${h.end_form()}
28 ${h.end_form()}
29 %else:
29 %else:
30
30
31 ${_('This repository does not have any remote mirror url set.')}
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 <br/>
33 <br/>
34 <br/>
34 <br/>
35 <button class="btn disabled" type="submit" disabled="disabled">
35 <button class="btn disabled" type="submit" disabled="disabled">
36 ${_('Pull changes from remote location')}
36 ${_('Pull changes from remote location')}
37 </button>
37 </button>
38 %endif
38 %endif
39 </div>
39 </div>
40 </div>
40 </div>
@@ -1,22 +1,22 b''
1 <div class="panel panel-default">
1 <div class="panel panel-default">
2 <div class="panel-heading">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('Repository statistics')}</h3>
3 <h3 class="panel-title">${_('Repository statistics')}</h3>
4 </div>
4 </div>
5 <div class="panel-body">
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 <div class="form">
7 <div class="form">
8 <div class="fields">
8 <div class="fields">
9 <div class="field" >
9 <div class="field" >
10 <dl class="dl-horizontal settings">
10 <dl class="dl-horizontal settings">
11 <dt>${_('Processed commits')}:</dt><dd>${c.stats_revision}/${c.repo_last_rev}</dd>
11 <dt>${_('Processed commits')}:</dt><dd>${c.stats_revision}/${c.repo_last_rev}</dd>
12 <dt>${_('Processed progress')}:</dt><dd>${c.stats_percentage}%</dd>
12 <dt>${_('Processed progress')}:</dt><dd>${c.stats_percentage}%</dd>
13 </dl>
13 </dl>
14 </div>
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 </div>
16 </div>
17 </div>
17 </div>
18 ${h.end_form()}
18 ${h.end_form()}
19 </div>
19 </div>
20 </div>
20 </div>
21
21
22
22
@@ -1,197 +1,197 b''
1 <div class="panel panel-default">
1 <div class="panel panel-default">
2 <div class="panel-heading">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('Strip commits from repository')}</h3>
3 <h3 class="panel-title">${_('Strip commits from repository')}</h3>
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
6 %if c.repo_info.repo_type != 'svn':
6 %if c.rhodecode_db_repo.repo_type != 'svn':
7 <h4>${_('Please provide up to %d commits commits to strip') % c.strip_limit}</h4>
7 <h4>${_('Please provide up to %d commits commits to strip') % c.strip_limit}</h4>
8 <p>
8 <p>
9 ${_('In the first step commits will be verified for existance in the repository')}. </br>
9 ${_('In the first step commits will be verified for existance in the repository')}. </br>
10 ${_('In the second step, correct commits will be available for stripping')}.
10 ${_('In the second step, correct commits will be available for stripping')}.
11 </p>
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 <div id="change_body" class="field">
13 <div id="change_body" class="field">
14 <div id="box-1" class="inputx locked_input">
14 <div id="box-1" class="inputx locked_input">
15 <input class="text" id="changeset_id-1" name="changeset_id-1" size="59"
15 <input class="text" id="changeset_id-1" name="changeset_id-1" size="59"
16 placeholder="${_('Enter full 40 character commit sha')}" type="text" value="">
16 placeholder="${_('Enter full 40 character commit sha')}" type="text" value="">
17 <div id="plus_icon-1" class="btn btn-default plus_input_button" onclick="addNew(1);return false">
17 <div id="plus_icon-1" class="btn btn-default plus_input_button" onclick="addNew(1);return false">
18 <i class="icon-plus">${_('Add another commit')}</i>
18 <i class="icon-plus">${_('Add another commit')}</i>
19 </div>
19 </div>
20 </div>
20 </div>
21 </div>
21 </div>
22
22
23 <div id="results" style="display:none; padding: 10px 0px;"></div>
23 <div id="results" style="display:none; padding: 10px 0px;"></div>
24
24
25 <div class="buttons">
25 <div class="buttons">
26 <button id="strip_action" class="btn btn-small btn-primary" onclick="checkCommits();return false">
26 <button id="strip_action" class="btn btn-small btn-primary" onclick="checkCommits();return false">
27 ${_('Check commits')}
27 ${_('Check commits')}
28 </button>
28 </button>
29 </div>
29 </div>
30
30
31 ${h.end_form()}
31 ${h.end_form()}
32 %else:
32 %else:
33 <h4>${_('Sorry this functionality is not available for SVN repository')}</h4>
33 <h4>${_('Sorry this functionality is not available for SVN repository')}</h4>
34 %endif
34 %endif
35 </div>
35 </div>
36 </div>
36 </div>
37
37
38
38
39 <script>
39 <script>
40 var plus_leaf = 1;
40 var plus_leaf = 1;
41
41
42 addNew = function(number){
42 addNew = function(number){
43 if (number >= ${c.strip_limit}){
43 if (number >= ${c.strip_limit}){
44 return;
44 return;
45 }
45 }
46 var minus = '<i class="icon-minus">${_('Remove')}</i>';
46 var minus = '<i class="icon-minus">${_('Remove')}</i>';
47 $('#plus_icon-'+number).detach();
47 $('#plus_icon-'+number).detach();
48 number++;
48 number++;
49
49
50 var input = '<div id="box-'+number+'" class="inputx locked_input">'+
50 var input = '<div id="box-'+number+'" class="inputx locked_input">'+
51 '<input class="text" id="changeset_id-'+number+'" name="changeset_id-'+number+'" size="59" type="text" value=""' +
51 '<input class="text" id="changeset_id-'+number+'" name="changeset_id-'+number+'" size="59" type="text" value=""' +
52 'placeholder="${_('Enter full 40 character commit sha')}">'+
52 'placeholder="${_('Enter full 40 character commit sha')}">'+
53 '<div id="plus_icon-'+number+'" class="btn btn-default plus_input_button" onclick="addNew('+number+');return false">'+
53 '<div id="plus_icon-'+number+'" class="btn btn-default plus_input_button" onclick="addNew('+number+');return false">'+
54 '<i class="icon-plus">${_('Add another commit')}</i>'+
54 '<i class="icon-plus">${_('Add another commit')}</i>'+
55 '</div>'+
55 '</div>'+
56 '<div id="minus_icon-'+number+'" class="btn btn-default minus_input_button" onclick="delOld('+(number)+');return false">'+
56 '<div id="minus_icon-'+number+'" class="btn btn-default minus_input_button" onclick="delOld('+(number)+');return false">'+
57 minus +
57 minus +
58 '</div>' +
58 '</div>' +
59 '</div>';
59 '</div>';
60 $('#change_body').append(input);
60 $('#change_body').append(input);
61 plus_leaf++;
61 plus_leaf++;
62 };
62 };
63
63
64 reIndex = function(number){
64 reIndex = function(number){
65 for(var i=number;i<=plus_leaf;i++){
65 for(var i=number;i<=plus_leaf;i++){
66 var check = $('#box-'+i);
66 var check = $('#box-'+i);
67 if (check.length == 0){
67 if (check.length == 0){
68 var change = $('#box-'+(i+1));
68 var change = $('#box-'+(i+1));
69 change.attr('id','box-'+i);
69 change.attr('id','box-'+i);
70 var plus = $('#plus_icon-'+(i+1));
70 var plus = $('#plus_icon-'+(i+1));
71
71
72 if (plus.length != 0){
72 if (plus.length != 0){
73 plus.attr('id','plus_icon-'+i);
73 plus.attr('id','plus_icon-'+i);
74 plus.attr('onclick','addNew('+i+');return false');
74 plus.attr('onclick','addNew('+i+');return false');
75 plus_leaf--;
75 plus_leaf--;
76 }
76 }
77 var minus = $('#minus_icon-'+(i+1));
77 var minus = $('#minus_icon-'+(i+1));
78
78
79 minus.attr('id','minus_icon-'+i);
79 minus.attr('id','minus_icon-'+i);
80
80
81 minus.attr('onclick','delOld('+i+');re' +
81 minus.attr('onclick','delOld('+i+');re' +
82 'turn false');
82 'turn false');
83 var input = $('input#changeset_id-'+(i+1));
83 var input = $('input#changeset_id-'+(i+1));
84 input.attr('name','changeset_id-'+i);
84 input.attr('name','changeset_id-'+i);
85 input.attr('id','changeset_id-'+i);
85 input.attr('id','changeset_id-'+i);
86 }
86 }
87 }
87 }
88 };
88 };
89
89
90 delOld = function(number){
90 delOld = function(number){
91 $('#box-'+number).remove();
91 $('#box-'+number).remove();
92 number = number - 1;
92 number = number - 1;
93 var box = $('#box-'+number);
93 var box = $('#box-'+number);
94 var plus = '<div id="plus_icon-'+number+'" class="btn btn-default plus_input_button" onclick="addNew('+number +');return false">'+
94 var plus = '<div id="plus_icon-'+number+'" class="btn btn-default plus_input_button" onclick="addNew('+number +');return false">'+
95 '<i id="i_plus_icon-'+number+'" class="icon-plus">${_('Add another commit')}</i></div>';
95 '<i id="i_plus_icon-'+number+'" class="icon-plus">${_('Add another commit')}</i></div>';
96 var minus = $('#minus_icon-'+number);
96 var minus = $('#minus_icon-'+number);
97 if(number +1 == plus_leaf){
97 if(number +1 == plus_leaf){
98 minus.detach();
98 minus.detach();
99 box.append(plus);
99 box.append(plus);
100 box.append(minus);
100 box.append(minus);
101 plus_leaf --;
101 plus_leaf --;
102 }
102 }
103 reIndex(number+1);
103 reIndex(number+1);
104
104
105 };
105 };
106
106
107 var resultData = {
107 var resultData = {
108 'csrf_token': CSRF_TOKEN
108 'csrf_token': CSRF_TOKEN
109 };
109 };
110
110
111 checkCommits = function() {
111 checkCommits = function() {
112 var postData = $('form').serialize();
112 var postData = $('form').serialize();
113 $('#results').show();
113 $('#results').show();
114 $('#results').html('<h4>${_('Checking commits')}...</h4>');
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 var btn = $('#strip_action');
116 var btn = $('#strip_action');
117 btn.attr('disabled', 'disabled');
117 btn.attr('disabled', 'disabled');
118 btn.addClass('disabled');
118 btn.addClass('disabled');
119
119
120 var success = function (data) {
120 var success = function (data) {
121 resultData = {
121 resultData = {
122 'csrf_token': CSRF_TOKEN
122 'csrf_token': CSRF_TOKEN
123 };
123 };
124 var i = 0;
124 var i = 0;
125 var result = '<ol>';
125 var result = '<ol>';
126 $.each(data, function(index, value){
126 $.each(data, function(index, value){
127 i= index;
127 i= index;
128 var box = $('#box-'+index);
128 var box = $('#box-'+index);
129 if (value.rev){
129 if (value.rev){
130 resultData[index] = JSON.stringify(value);
130 resultData[index] = JSON.stringify(value);
131
131
132 var verifiedHtml = (
132 var verifiedHtml = (
133 '<li style="line-height:1.2em">' +
133 '<li style="line-height:1.2em">' +
134 '<code>{0}</code>' +
134 '<code>{0}</code>' +
135 '{1}' +
135 '{1}' +
136 '<div style="white-space:pre">' +
136 '<div style="white-space:pre">' +
137 'author: {2}\n' +
137 'author: {2}\n' +
138 'description: {3}' +
138 'description: {3}' +
139 '</div>' +
139 '</div>' +
140 '</li>').format(
140 '</li>').format(
141 value.rev,
141 value.rev,
142 "${_(' commit verified positive')}",
142 "${_(' commit verified positive')}",
143 value.author, value.comment
143 value.author, value.comment
144 );
144 );
145 result += verifiedHtml;
145 result += verifiedHtml;
146 }
146 }
147 else {
147 else {
148 var verifiedHtml = (
148 var verifiedHtml = (
149 '<li style="line-height:1.2em">' +
149 '<li style="line-height:1.2em">' +
150 '<code><strike>{0}</strike></code>' +
150 '<code><strike>{0}</strike></code>' +
151 '{1}' +
151 '{1}' +
152 '</li>').format(
152 '</li>').format(
153 value.commit,
153 value.commit,
154 "${_(' commit verified negative')}"
154 "${_(' commit verified negative')}"
155 );
155 );
156 result += verifiedHtml;
156 result += verifiedHtml;
157 }
157 }
158 box.remove();
158 box.remove();
159 });
159 });
160 result += '</ol>';
160 result += '</ol>';
161 var box = $('#box-'+(parseInt(i)+1));
161 var box = $('#box-'+(parseInt(i)+1));
162 box.remove();
162 box.remove();
163 $('#results').html(result);
163 $('#results').html(result);
164 };
164 };
165
165
166 btn.html('Strip');
166 btn.html('Strip');
167 btn.removeAttr('disabled');
167 btn.removeAttr('disabled');
168 btn.removeClass('disabled');
168 btn.removeClass('disabled');
169 btn.attr('onclick','strip();return false;');
169 btn.attr('onclick','strip();return false;');
170 ajaxPOST(url, postData, success, null);
170 ajaxPOST(url, postData, success, null);
171 };
171 };
172
172
173 strip = function() {
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 var success = function(data) {
175 var success = function(data) {
176 var result = '<h4>Strip executed</h4><ol>';
176 var result = '<h4>Strip executed</h4><ol>';
177 $.each(data, function(index, value){
177 $.each(data, function(index, value){
178 if(data[index]) {
178 if(data[index]) {
179 result += '<li><code>' +index+ '</code> ${_(' commit striped successfully')}' + '</li>';
179 result += '<li><code>' +index+ '</code> ${_(' commit striped successfully')}' + '</li>';
180 }
180 }
181 else {
181 else {
182 result += '<li><code>' +index+ '</code> ${_(' commit strip failed')}' + '</li>';
182 result += '<li><code>' +index+ '</code> ${_(' commit strip failed')}' + '</li>';
183 }
183 }
184 });
184 });
185 if ($.isEmptyObject(data)) {
185 if ($.isEmptyObject(data)) {
186 result += '<li>Nothing done...</li>'
186 result += '<li>Nothing done...</li>'
187 }
187 }
188 result += '</ol>';
188 result += '</ol>';
189 $('#results').html(result);
189 $('#results').html(result);
190
190
191 };
191 };
192 ajaxPOST(url, resultData, success, null);
192 ajaxPOST(url, resultData, success, null);
193 var btn = $('#strip_action');
193 var btn = $('#strip_action');
194 btn.remove();
194 btn.remove();
195
195
196 };
196 };
197 </script>
197 </script>
@@ -1,73 +1,73 b''
1 <%namespace name="vcss" file="/base/vcs_settings.mako"/>
1 <%namespace name="vcss" file="/base/vcs_settings.mako"/>
2
2
3 <div id="repo_vcs_settings" class="${'inherited' if c.inherit_global_settings else ''}">
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 <div class="form panel panel-default">
5 <div class="form panel panel-default">
6 <div class="fields panel-body">
6 <div class="fields panel-body">
7 <div class="field">
7 <div class="field">
8 <div class="label label-checkbox">
8 <div class="label label-checkbox">
9 <label for="inherit_global_settings">${_('Inherit from global settings')}:</label>
9 <label for="inherit_global_settings">${_('Inherit from global settings')}:</label>
10 </div>
10 </div>
11 <div class="checkboxes">
11 <div class="checkboxes">
12 ${h.checkbox('inherit_global_settings',value=True)}
12 ${h.checkbox('inherit_global_settings',value=True)}
13 <span class="help-block">${h.literal(_('Select to inherit global vcs settings.'))}</span>
13 <span class="help-block">${h.literal(_('Select to inherit global vcs settings.'))}</span>
14 </div>
14 </div>
15 </div>
15 </div>
16 </div>
16 </div>
17 </div>
17 </div>
18
18
19 <div id="inherit_overlay_vcs_default">
19 <div id="inherit_overlay_vcs_default">
20 <div>
20 <div>
21 ${vcss.vcs_settings_fields(
21 ${vcss.vcs_settings_fields(
22 suffix='_inherited',
22 suffix='_inherited',
23 svn_tag_patterns=c.global_svn_tag_patterns,
23 svn_tag_patterns=c.global_svn_tag_patterns,
24 svn_branch_patterns=c.global_svn_branch_patterns,
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 disabled='disabled'
26 disabled='disabled'
27 )}
27 )}
28 </div>
28 </div>
29 </div>
29 </div>
30
30
31 <div id="inherit_overlay_vcs_custom">
31 <div id="inherit_overlay_vcs_custom">
32 <div>
32 <div>
33 ${vcss.vcs_settings_fields(
33 ${vcss.vcs_settings_fields(
34 suffix='',
34 suffix='',
35 svn_tag_patterns=c.svn_tag_patterns,
35 svn_tag_patterns=c.svn_tag_patterns,
36 svn_branch_patterns=c.svn_branch_patterns,
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 </div>
39 </div>
40 </div>
40 </div>
41
41
42 <div class="buttons">
42 <div class="buttons">
43 ${h.submit('save',_('Save settings'),class_="btn")}
43 ${h.submit('save',_('Save settings'),class_="btn")}
44 ${h.reset('reset',_('Reset'),class_="btn")}
44 ${h.reset('reset',_('Reset'),class_="btn")}
45 </div>
45 </div>
46
46
47 ${h.end_form()}
47 ${h.end_form()}
48 </div>
48 </div>
49
49
50 <script type="text/javascript">
50 <script type="text/javascript">
51
51
52 function ajaxDeletePattern(pattern_id, field_id) {
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 var callback = function (o) {
54 var callback = function (o) {
55 var elem = $("#"+field_id);
55 var elem = $("#"+field_id);
56 elem.remove();
56 elem.remove();
57 };
57 };
58 var postData = {
58 var postData = {
59 'delete_svn_pattern': pattern_id,
59 'delete_svn_pattern': pattern_id,
60 'csrf_token': CSRF_TOKEN
60 'csrf_token': CSRF_TOKEN
61 };
61 };
62 var request = $.post(sUrl, postData)
62 var request = $.post(sUrl, postData)
63 .done(callback)
63 .done(callback)
64 .fail(function (data, textStatus, errorThrown) {
64 .fail(function (data, textStatus, errorThrown) {
65 alert("Error while deleting hooks.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(this)[0].url));
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 $('#inherit_global_settings').on('change', function(e){
69 $('#inherit_global_settings').on('change', function(e){
70 $('#repo_vcs_settings').toggleClass('inherited', this.checked);
70 $('#repo_vcs_settings').toggleClass('inherited', this.checked);
71 });
71 });
72
72
73 </script>
73 </script>
@@ -1,129 +1,129 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Fork repository %s') % c.repo_name}
5 ${_('Fork repository %s') % c.repo_name}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 ${_('New Fork')}
12 ${_('New Fork')}
13 </%def>
13 </%def>
14
14
15 <%def name="menu_bar_nav()">
15 <%def name="menu_bar_nav()">
16 ${self.menu_items(active='repositories')}
16 ${self.menu_items(active='repositories')}
17 </%def>
17 </%def>
18
18
19 <%def name="menu_bar_subnav()">
19 <%def name="menu_bar_subnav()">
20 ${self.repo_menu(active='options')}
20 ${self.repo_menu(active='options')}
21 </%def>
21 </%def>
22
22
23 <%def name="main()">
23 <%def name="main()">
24 <div class="box">
24 <div class="box">
25 <div class="title">
25 <div class="title">
26 ${self.repo_page_title(c.rhodecode_db_repo)}
26 ${self.repo_page_title(c.rhodecode_db_repo)}
27 ${self.breadcrumbs()}
27 ${self.breadcrumbs()}
28 </div>
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 <div class="form">
31 <div class="form">
32 <!-- fields -->
32 <!-- fields -->
33 <div class="fields">
33 <div class="fields">
34
34
35 <div class="field">
35 <div class="field">
36 <div class="label">
36 <div class="label">
37 <label for="repo_name">${_('Fork name')}:</label>
37 <label for="repo_name">${_('Fork name')}:</label>
38 </div>
38 </div>
39 <div class="input">
39 <div class="input">
40 ${h.text('repo_name', class_="medium")}
40 ${h.text('repo_name', class_="medium")}
41 ${h.hidden('repo_type',c.repo_info.repo_type)}
41 ${h.hidden('repo_type',c.rhodecode_db_repo.repo_type)}
42 ${h.hidden('fork_parent_id',c.repo_info.repo_id)}
42 ${h.hidden('fork_parent_id',c.rhodecode_db_repo.repo_id)}
43 </div>
43 </div>
44 </div>
44 </div>
45
45
46 <div class="field">
46 <div class="field">
47 <div class="label label-textarea">
47 <div class="label label-textarea">
48 <label for="description">${_('Description')}:</label>
48 <label for="description">${_('Description')}:</label>
49 </div>
49 </div>
50 <div class="textarea-repo textarea text-area editor">
50 <div class="textarea-repo textarea text-area editor">
51 ${h.textarea('description')}
51 ${h.textarea('description')}
52 <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
52 <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
53 </div>
53 </div>
54 </div>
54 </div>
55
55
56 <div class="field">
56 <div class="field">
57 <div class="label">
57 <div class="label">
58 <label for="repo_group">${_('Repository group')}:</label>
58 <label for="repo_group">${_('Repository group')}:</label>
59 </div>
59 </div>
60 <div class="select">
60 <div class="select">
61 ${h.select('repo_group','',c.repo_groups,class_="medium")}
61 ${h.select('repo_group','',c.repo_groups,class_="medium")}
62 % if c.personal_repo_group:
62 % if c.personal_repo_group:
63 <a class="btn" href="#" id="select_my_group" data-personal-group-id="${c.personal_repo_group.group_id}">
63 <a class="btn" href="#" id="select_my_group" data-personal-group-id="${c.personal_repo_group.group_id}">
64 ${_('Select my personal group (%(repo_group_name)s)') % {'repo_group_name': c.personal_repo_group.group_name}}
64 ${_('Select my personal group (%(repo_group_name)s)') % {'repo_group_name': c.personal_repo_group.group_name}}
65 </a>
65 </a>
66 % endif
66 % endif
67 <span class="help-block">${_('Optionally select a group to put this repository into.')}</span>
67 <span class="help-block">${_('Optionally select a group to put this repository into.')}</span>
68 </div>
68 </div>
69 </div>
69 </div>
70
70
71 <div class="field">
71 <div class="field">
72 <div class="label">
72 <div class="label">
73 <label for="landing_rev">${_('Landing commit')}:</label>
73 <label for="landing_rev">${_('Landing commit')}:</label>
74 </div>
74 </div>
75 <div class="select">
75 <div class="select">
76 ${h.select('landing_rev','',c.landing_revs,class_="medium")}
76 ${h.select('landing_rev','',c.landing_revs,class_="medium")}
77 <span class="help-block">${_('Default commit for files page, downloads, whoosh and readme')}</span>
77 <span class="help-block">${_('Default commit for files page, downloads, whoosh and readme')}</span>
78 </div>
78 </div>
79 </div>
79 </div>
80
80
81 <div class="field">
81 <div class="field">
82 <div class="label label-checkbox">
82 <div class="label label-checkbox">
83 <label for="private">${_('Private')}:</label>
83 <label for="private">${_('Private')}:</label>
84 </div>
84 </div>
85 <div class="checkboxes">
85 <div class="checkboxes">
86 ${h.checkbox('private',value="True")}
86 ${h.checkbox('private',value="True")}
87 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
87 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
88 </div>
88 </div>
89 </div>
89 </div>
90
90
91 <div class="field">
91 <div class="field">
92 <div class="label label-checkbox">
92 <div class="label label-checkbox">
93 <label for="private">${_('Copy permissions')}:</label>
93 <label for="private">${_('Copy permissions')}:</label>
94 </div>
94 </div>
95 <div class="checkboxes">
95 <div class="checkboxes">
96 ${h.checkbox('copy_permissions',value="True", checked="checked")}
96 ${h.checkbox('copy_permissions',value="True", checked="checked")}
97 <span class="help-block">${_('Copy permissions from forked repository')}</span>
97 <span class="help-block">${_('Copy permissions from forked repository')}</span>
98 </div>
98 </div>
99 </div>
99 </div>
100
100
101 <div class="buttons">
101 <div class="buttons">
102 ${h.submit('',_('Fork this Repository'),class_="btn")}
102 ${h.submit('',_('Fork this Repository'),class_="btn")}
103 </div>
103 </div>
104 </div>
104 </div>
105 </div>
105 </div>
106 ${h.end_form()}
106 ${h.end_form()}
107 </div>
107 </div>
108 <script>
108 <script>
109 $(document).ready(function(){
109 $(document).ready(function(){
110 $("#repo_group").select2({
110 $("#repo_group").select2({
111 'dropdownAutoWidth': true,
111 'dropdownAutoWidth': true,
112 'containerCssClass': "drop-menu",
112 'containerCssClass': "drop-menu",
113 'dropdownCssClass': "drop-menu-dropdown",
113 'dropdownCssClass': "drop-menu-dropdown",
114 'width': "resolve"
114 'width': "resolve"
115 });
115 });
116 $("#landing_rev").select2({
116 $("#landing_rev").select2({
117 'containerCssClass': "drop-menu",
117 'containerCssClass': "drop-menu",
118 'dropdownCssClass': "drop-menu-dropdown",
118 'dropdownCssClass': "drop-menu-dropdown",
119 'minimumResultsForSearch': -1
119 'minimumResultsForSearch': -1
120 });
120 });
121 $('#repo_name').focus();
121 $('#repo_name').focus();
122
122
123 $('#select_my_group').on('click', function(e){
123 $('#select_my_group').on('click', function(e){
124 e.preventDefault();
124 e.preventDefault();
125 $("#repo_group").val($(this).data('personalGroupId')).trigger("change");
125 $("#repo_group").val($(this).data('personalGroupId')).trigger("change");
126 })
126 })
127 })
127 })
128 </script>
128 </script>
129 </%def>
129 </%def>
General Comments 0
You need to be logged in to leave comments. Login now