##// END OF EJS Templates
cleanup: imports cleanup.
marcink -
r1796:b0e7443a default
parent child Browse files
Show More
@@ -1,404 +1,404 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 Repository groups controller for RhodeCode
24 24 """
25 25
26 26 import logging
27 27 import formencode
28 28
29 29 from formencode import htmlfill
30 30
31 31 from pylons import request, tmpl_context as c, url
32 32 from pylons.controllers.util import abort, redirect
33 33 from pylons.i18n.translation import _, ungettext
34 34
35 35 from rhodecode.lib import auth
36 36 from rhodecode.lib import helpers as h
37 37 from rhodecode.lib.ext_json import json
38 38 from rhodecode.lib.auth import (
39 39 LoginRequired, NotAnonymous, HasPermissionAll,
40 40 HasRepoGroupPermissionAll, HasRepoGroupPermissionAnyDecorator)
41 41 from rhodecode.lib.base import BaseController, render
42 from rhodecode.lib.utils2 import safe_int
42 43 from rhodecode.model.db import RepoGroup, User
43 44 from rhodecode.model.scm import RepoGroupList
44 45 from rhodecode.model.repo_group import RepoGroupModel
45 46 from rhodecode.model.forms import RepoGroupForm, RepoGroupPermsForm
46 47 from rhodecode.model.meta import Session
47 from rhodecode.lib.utils2 import safe_int
48 48
49 49
50 50 log = logging.getLogger(__name__)
51 51
52 52
53 53 class RepoGroupsController(BaseController):
54 54 """REST Controller styled on the Atom Publishing Protocol"""
55 55
56 56 @LoginRequired()
57 57 def __before__(self):
58 58 super(RepoGroupsController, self).__before__()
59 59
60 60 def __load_defaults(self, allow_empty_group=False, repo_group=None):
61 61 if self._can_create_repo_group():
62 62 # we're global admin, we're ok and we can create TOP level groups
63 63 allow_empty_group = True
64 64
65 65 # override the choices for this form, we need to filter choices
66 66 # and display only those we have ADMIN right
67 67 groups_with_admin_rights = RepoGroupList(
68 68 RepoGroup.query().all(),
69 69 perm_set=['group.admin'])
70 70 c.repo_groups = RepoGroup.groups_choices(
71 71 groups=groups_with_admin_rights,
72 72 show_empty_group=allow_empty_group)
73 73
74 74 if repo_group:
75 75 # exclude filtered ids
76 76 exclude_group_ids = [repo_group.group_id]
77 77 c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
78 78 c.repo_groups)
79 79 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
80 80 parent_group = repo_group.parent_group
81 81
82 82 add_parent_group = (parent_group and (
83 83 unicode(parent_group.group_id) not in c.repo_groups_choices))
84 84 if add_parent_group:
85 85 c.repo_groups_choices.append(unicode(parent_group.group_id))
86 86 c.repo_groups.append(RepoGroup._generate_choice(parent_group))
87 87
88 88 def __load_data(self, group_id):
89 89 """
90 90 Load defaults settings for edit, and update
91 91
92 92 :param group_id:
93 93 """
94 94 repo_group = RepoGroup.get_or_404(group_id)
95 95 data = repo_group.get_dict()
96 96 data['group_name'] = repo_group.name
97 97
98 98 # fill owner
99 99 if repo_group.user:
100 100 data.update({'user': repo_group.user.username})
101 101 else:
102 102 replacement_user = User.get_first_super_admin().username
103 103 data.update({'user': replacement_user})
104 104
105 105 # fill repository group users
106 106 for p in repo_group.repo_group_to_perm:
107 107 data.update({
108 108 'u_perm_%s' % p.user.user_id: p.permission.permission_name})
109 109
110 110 # fill repository group user groups
111 111 for p in repo_group.users_group_to_perm:
112 112 data.update({
113 113 'g_perm_%s' % p.users_group.users_group_id:
114 114 p.permission.permission_name})
115 115 # html and form expects -1 as empty parent group
116 116 data['group_parent_id'] = data['group_parent_id'] or -1
117 117 return data
118 118
119 119 def _revoke_perms_on_yourself(self, form_result):
120 120 _updates = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
121 121 form_result['perm_updates'])
122 122 _additions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
123 123 form_result['perm_additions'])
124 124 _deletions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
125 125 form_result['perm_deletions'])
126 126 admin_perm = 'group.admin'
127 127 if _updates and _updates[0][1] != admin_perm or \
128 128 _additions and _additions[0][1] != admin_perm or \
129 129 _deletions and _deletions[0][1] != admin_perm:
130 130 return True
131 131 return False
132 132
133 133 def _can_create_repo_group(self, parent_group_id=None):
134 134 is_admin = HasPermissionAll('hg.admin')('group create controller')
135 135 create_repo_group = HasPermissionAll(
136 136 'hg.repogroup.create.true')('group create controller')
137 137 if is_admin or (create_repo_group and not parent_group_id):
138 138 # we're global admin, or we have global repo group create
139 139 # permission
140 140 # we're ok and we can create TOP level groups
141 141 return True
142 142 elif parent_group_id:
143 143 # we check the permission if we can write to parent group
144 144 group = RepoGroup.get(parent_group_id)
145 145 group_name = group.group_name if group else None
146 146 if HasRepoGroupPermissionAll('group.admin')(
147 147 group_name, 'check if user is an admin of group'):
148 148 # we're an admin of passed in group, we're ok.
149 149 return True
150 150 else:
151 151 return False
152 152 return False
153 153
154 154 @NotAnonymous()
155 155 def index(self):
156 156 """GET /repo_groups: All items in the collection"""
157 157 # url('repo_groups')
158 158
159 159 repo_group_list = RepoGroup.get_all_repo_groups()
160 160 _perms = ['group.admin']
161 161 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
162 162 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
163 163 repo_group_list=repo_group_list_acl, admin=True)
164 164 c.data = json.dumps(repo_group_data)
165 165 return render('admin/repo_groups/repo_groups.mako')
166 166
167 167 # perm checks inside
168 168 @NotAnonymous()
169 169 @auth.CSRFRequired()
170 170 def create(self):
171 171 """POST /repo_groups: Create a new item"""
172 172 # url('repo_groups')
173 173
174 174 parent_group_id = safe_int(request.POST.get('group_parent_id'))
175 175 can_create = self._can_create_repo_group(parent_group_id)
176 176
177 177 self.__load_defaults()
178 178 # permissions for can create group based on parent_id are checked
179 179 # here in the Form
180 180 available_groups = map(lambda k: unicode(k[0]), c.repo_groups)
181 181 repo_group_form = RepoGroupForm(available_groups=available_groups,
182 182 can_create_in_root=can_create)()
183 183 try:
184 184 owner = c.rhodecode_user
185 185 form_result = repo_group_form.to_python(dict(request.POST))
186 186 RepoGroupModel().create(
187 187 group_name=form_result['group_name_full'],
188 188 group_description=form_result['group_description'],
189 189 owner=owner.user_id,
190 190 copy_permissions=form_result['group_copy_permissions']
191 191 )
192 192 Session().commit()
193 193 _new_group_name = form_result['group_name_full']
194 194 repo_group_url = h.link_to(
195 195 _new_group_name,
196 196 h.route_path('repo_group_home', repo_group_name=_new_group_name))
197 197 h.flash(h.literal(_('Created repository group %s')
198 198 % repo_group_url), category='success')
199 199 # TODO: in future action_logger(, '', '', '', self.sa)
200 200 except formencode.Invalid as errors:
201 201 return htmlfill.render(
202 202 render('admin/repo_groups/repo_group_add.mako'),
203 203 defaults=errors.value,
204 204 errors=errors.error_dict or {},
205 205 prefix_error=False,
206 206 encoding="UTF-8",
207 207 force_defaults=False)
208 208 except Exception:
209 209 log.exception("Exception during creation of repository group")
210 210 h.flash(_('Error occurred during creation of repository group %s')
211 211 % request.POST.get('group_name'), category='error')
212 212
213 213 # TODO: maybe we should get back to the main view, not the admin one
214 214 return redirect(url('repo_groups', parent_group=parent_group_id))
215 215
216 216 # perm checks inside
217 217 @NotAnonymous()
218 218 def new(self):
219 219 """GET /repo_groups/new: Form to create a new item"""
220 220 # url('new_repo_group')
221 221 # perm check for admin, create_group perm or admin of parent_group
222 222 parent_group_id = safe_int(request.GET.get('parent_group'))
223 223 if not self._can_create_repo_group(parent_group_id):
224 224 return abort(403)
225 225
226 226 self.__load_defaults()
227 227 return render('admin/repo_groups/repo_group_add.mako')
228 228
229 229 @HasRepoGroupPermissionAnyDecorator('group.admin')
230 230 @auth.CSRFRequired()
231 231 def update(self, group_name):
232 232 """PUT /repo_groups/group_name: Update an existing item"""
233 233 # Forms posted to this method should contain a hidden field:
234 234 # <input type="hidden" name="_method" value="PUT" />
235 235 # Or using helpers:
236 236 # h.form(url('repos_group', group_name=GROUP_NAME), method='put')
237 237
238 238 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
239 239 can_create_in_root = self._can_create_repo_group()
240 240 show_root_location = can_create_in_root
241 241 if not c.repo_group.parent_group:
242 242 # this group don't have a parrent so we should show empty value
243 243 show_root_location = True
244 244 self.__load_defaults(allow_empty_group=show_root_location,
245 245 repo_group=c.repo_group)
246 246
247 247 repo_group_form = RepoGroupForm(
248 248 edit=True, old_data=c.repo_group.get_dict(),
249 249 available_groups=c.repo_groups_choices,
250 250 can_create_in_root=can_create_in_root, allow_disabled=True)()
251 251
252 252 try:
253 253 form_result = repo_group_form.to_python(dict(request.POST))
254 254 gr_name = form_result['group_name']
255 255 new_gr = RepoGroupModel().update(group_name, form_result)
256 256 Session().commit()
257 257 h.flash(_('Updated repository group %s') % (gr_name,),
258 258 category='success')
259 259 # we now have new name !
260 260 group_name = new_gr.group_name
261 261 # TODO: in future action_logger(, '', '', '', self.sa)
262 262 except formencode.Invalid as errors:
263 263 c.active = 'settings'
264 264 return htmlfill.render(
265 265 render('admin/repo_groups/repo_group_edit.mako'),
266 266 defaults=errors.value,
267 267 errors=errors.error_dict or {},
268 268 prefix_error=False,
269 269 encoding="UTF-8",
270 270 force_defaults=False)
271 271 except Exception:
272 272 log.exception("Exception during update or repository group")
273 273 h.flash(_('Error occurred during update of repository group %s')
274 274 % request.POST.get('group_name'), category='error')
275 275
276 276 return redirect(url('edit_repo_group', group_name=group_name))
277 277
278 278 @HasRepoGroupPermissionAnyDecorator('group.admin')
279 279 @auth.CSRFRequired()
280 280 def delete(self, group_name):
281 281 """DELETE /repo_groups/group_name: Delete an existing item"""
282 282 # Forms posted to this method should contain a hidden field:
283 283 # <input type="hidden" name="_method" value="DELETE" />
284 284 # Or using helpers:
285 285 # h.form(url('repos_group', group_name=GROUP_NAME), method='delete')
286 286
287 287 gr = c.repo_group = RepoGroupModel()._get_repo_group(group_name)
288 288 repos = gr.repositories.all()
289 289 if repos:
290 290 msg = ungettext(
291 291 'This group contains %(num)d repository and cannot be deleted',
292 292 'This group contains %(num)d repositories and cannot be'
293 293 ' deleted',
294 294 len(repos)) % {'num': len(repos)}
295 295 h.flash(msg, category='warning')
296 296 return redirect(url('repo_groups'))
297 297
298 298 children = gr.children.all()
299 299 if children:
300 300 msg = ungettext(
301 301 'This group contains %(num)d subgroup and cannot be deleted',
302 302 'This group contains %(num)d subgroups and cannot be deleted',
303 303 len(children)) % {'num': len(children)}
304 304 h.flash(msg, category='warning')
305 305 return redirect(url('repo_groups'))
306 306
307 307 try:
308 308 RepoGroupModel().delete(group_name)
309 309 Session().commit()
310 310 h.flash(_('Removed repository group %s') % group_name,
311 311 category='success')
312 312 # TODO: in future action_logger(, '', '', '', self.sa)
313 313 except Exception:
314 314 log.exception("Exception during deletion of repository group")
315 315 h.flash(_('Error occurred during deletion of repository group %s')
316 316 % group_name, category='error')
317 317
318 318 return redirect(url('repo_groups'))
319 319
320 320 @HasRepoGroupPermissionAnyDecorator('group.admin')
321 321 def edit(self, group_name):
322 322 """GET /repo_groups/group_name/edit: Form to edit an existing item"""
323 323 # url('edit_repo_group', group_name=GROUP_NAME)
324 324 c.active = 'settings'
325 325
326 326 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
327 327 # we can only allow moving empty group if it's already a top-level
328 328 # group, ie has no parents, or we're admin
329 329 can_create_in_root = self._can_create_repo_group()
330 330 show_root_location = can_create_in_root
331 331 if not c.repo_group.parent_group:
332 332 # this group don't have a parrent so we should show empty value
333 333 show_root_location = True
334 334 self.__load_defaults(allow_empty_group=show_root_location,
335 335 repo_group=c.repo_group)
336 336 defaults = self.__load_data(c.repo_group.group_id)
337 337
338 338 return htmlfill.render(
339 339 render('admin/repo_groups/repo_group_edit.mako'),
340 340 defaults=defaults,
341 341 encoding="UTF-8",
342 342 force_defaults=False
343 343 )
344 344
345 345 @HasRepoGroupPermissionAnyDecorator('group.admin')
346 346 def edit_repo_group_advanced(self, group_name):
347 347 """GET /repo_groups/group_name/edit: Form to edit an existing item"""
348 348 # url('edit_repo_group', group_name=GROUP_NAME)
349 349 c.active = 'advanced'
350 350 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
351 351
352 352 return render('admin/repo_groups/repo_group_edit.mako')
353 353
354 354 @HasRepoGroupPermissionAnyDecorator('group.admin')
355 355 def edit_repo_group_perms(self, group_name):
356 356 """GET /repo_groups/group_name/edit: Form to edit an existing item"""
357 357 # url('edit_repo_group', group_name=GROUP_NAME)
358 358 c.active = 'perms'
359 359 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
360 360 self.__load_defaults()
361 361 defaults = self.__load_data(c.repo_group.group_id)
362 362
363 363 return htmlfill.render(
364 364 render('admin/repo_groups/repo_group_edit.mako'),
365 365 defaults=defaults,
366 366 encoding="UTF-8",
367 367 force_defaults=False
368 368 )
369 369
370 370 @HasRepoGroupPermissionAnyDecorator('group.admin')
371 371 @auth.CSRFRequired()
372 372 def update_perms(self, group_name):
373 373 """
374 374 Update permissions for given repository group
375 375
376 376 :param group_name:
377 377 """
378 378
379 379 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
380 380 valid_recursive_choices = ['none', 'repos', 'groups', 'all']
381 381 form = RepoGroupPermsForm(valid_recursive_choices)().to_python(
382 382 request.POST)
383 383
384 384 if not c.rhodecode_user.is_admin:
385 385 if self._revoke_perms_on_yourself(form):
386 386 msg = _('Cannot change permission for yourself as admin')
387 387 h.flash(msg, category='warning')
388 388 return redirect(
389 389 url('edit_repo_group_perms', group_name=group_name))
390 390
391 391 # iterate over all members(if in recursive mode) of this groups and
392 392 # set the permissions !
393 393 # this can be potentially heavy operation
394 394 RepoGroupModel().update_permissions(
395 395 c.repo_group,
396 396 form['perm_additions'], form['perm_updates'],
397 397 form['perm_deletions'], form['recursive'])
398 398
399 399 # TODO: implement this
400 400 # action_logger(c.rhodecode_user, 'admin_changed_repo_permissions',
401 401 # repo_name, self.ip_addr, self.sa)
402 402 Session().commit()
403 403 h.flash(_('Repository Group permissions updated'), category='success')
404 404 return redirect(url('edit_repo_group_perms', group_name=group_name))
@@ -1,610 +1,606 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2013-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 Repositories controller for RhodeCode
24 24 """
25 25
26 26 import logging
27 27 import traceback
28 28
29 29 import formencode
30 30 from formencode import htmlfill
31 31 from pylons import request, tmpl_context as c, url
32 32 from pylons.controllers.util import redirect
33 33 from pylons.i18n.translation import _
34 34 from webob.exc import HTTPForbidden, HTTPNotFound, HTTPBadRequest
35 35
36 36 import rhodecode
37 37 from rhodecode.lib import auth, helpers as h
38 38 from rhodecode.lib.auth import (
39 39 LoginRequired, HasPermissionAllDecorator,
40 40 HasRepoPermissionAllDecorator, NotAnonymous, HasPermissionAny,
41 41 HasRepoGroupPermissionAny, HasRepoPermissionAnyDecorator)
42 42 from rhodecode.lib.base import BaseRepoController, render
43 43 from rhodecode.lib.ext_json import json
44 from rhodecode.lib.exceptions import AttachedForksError
45 from rhodecode.lib.utils import action_logger, repo_name_slug, jsonify
44 from rhodecode.lib.utils import repo_name_slug, jsonify
46 45 from rhodecode.lib.utils2 import safe_int, str2bool
47 from rhodecode.lib.vcs import RepositoryError
48 from rhodecode.model.db import (
49 User, Repository, UserFollowing, RepoGroup, RepositoryField)
46 from rhodecode.model.db import (Repository, RepoGroup, RepositoryField)
50 47 from rhodecode.model.forms import (
51 RepoForm, RepoFieldForm, RepoPermsForm, RepoVcsSettingsForm,
52 IssueTrackerPatternsForm)
48 RepoForm, RepoFieldForm, RepoVcsSettingsForm, IssueTrackerPatternsForm)
53 49 from rhodecode.model.meta import Session
54 50 from rhodecode.model.repo import RepoModel
55 51 from rhodecode.model.scm import ScmModel, RepoGroupList, RepoList
56 52 from rhodecode.model.settings import (
57 53 SettingsModel, IssueTrackerSettingsModel, VcsSettingsModel,
58 54 SettingNotFound)
59 55
60 56 log = logging.getLogger(__name__)
61 57
62 58
63 59 class ReposController(BaseRepoController):
64 60 """
65 61 REST Controller styled on the Atom Publishing Protocol"""
66 62 # To properly map this controller, ensure your config/routing.py
67 63 # file has a resource setup:
68 64 # map.resource('repo', 'repos')
69 65
70 66 @LoginRequired()
71 67 def __before__(self):
72 68 super(ReposController, self).__before__()
73 69
74 70 def _load_repo(self, repo_name):
75 71 repo_obj = Repository.get_by_repo_name(repo_name)
76 72
77 73 if repo_obj is None:
78 74 h.not_mapped_error(repo_name)
79 75 return redirect(url('repos'))
80 76
81 77 return repo_obj
82 78
83 79 def __load_defaults(self, repo=None):
84 80 acl_groups = RepoGroupList(RepoGroup.query().all(),
85 81 perm_set=['group.write', 'group.admin'])
86 82 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
87 83 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
88 84
89 85 # in case someone no longer have a group.write access to a repository
90 86 # pre fill the list with this entry, we don't care if this is the same
91 87 # but it will allow saving repo data properly.
92 88
93 89 repo_group = None
94 90 if repo:
95 91 repo_group = repo.group
96 92 if repo_group and unicode(repo_group.group_id) not in c.repo_groups_choices:
97 93 c.repo_groups_choices.append(unicode(repo_group.group_id))
98 94 c.repo_groups.append(RepoGroup._generate_choice(repo_group))
99 95
100 96 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
101 97 c.landing_revs_choices = choices
102 98
103 99 def __load_data(self, repo_name=None):
104 100 """
105 101 Load defaults settings for edit, and update
106 102
107 103 :param repo_name:
108 104 """
109 105 c.repo_info = self._load_repo(repo_name)
110 106 self.__load_defaults(c.repo_info)
111 107
112 108 # override defaults for exact repo info here git/hg etc
113 109 if not c.repository_requirements_missing:
114 110 choices, c.landing_revs = ScmModel().get_repo_landing_revs(
115 111 c.repo_info)
116 112 c.landing_revs_choices = choices
117 113 defaults = RepoModel()._get_defaults(repo_name)
118 114
119 115 return defaults
120 116
121 117 def _log_creation_exception(self, e, repo_name):
122 118 reason = None
123 119 if len(e.args) == 2:
124 120 reason = e.args[1]
125 121
126 122 if reason == 'INVALID_CERTIFICATE':
127 123 log.exception(
128 124 'Exception creating a repository: invalid certificate')
129 125 msg = (_('Error creating repository %s: invalid certificate')
130 126 % repo_name)
131 127 else:
132 128 log.exception("Exception creating a repository")
133 129 msg = (_('Error creating repository %s')
134 130 % repo_name)
135 131
136 132 return msg
137 133
138 134 @NotAnonymous()
139 135 def index(self, format='html'):
140 136 """GET /repos: All items in the collection"""
141 137 # url('repos')
142 138
143 139 repo_list = Repository.get_all_repos()
144 140 c.repo_list = RepoList(repo_list, perm_set=['repository.admin'])
145 141 repos_data = RepoModel().get_repos_as_dict(
146 142 repo_list=c.repo_list, admin=True, super_user_actions=True)
147 143 # json used to render the grid
148 144 c.data = json.dumps(repos_data)
149 145
150 146 return render('admin/repos/repos.mako')
151 147
152 148 # perms check inside
153 149 @NotAnonymous()
154 150 @auth.CSRFRequired()
155 151 def create(self):
156 152 """
157 153 POST /repos: Create a new item"""
158 154 # url('repos')
159 155
160 156 self.__load_defaults()
161 157 form_result = {}
162 158 task_id = None
163 159 c.personal_repo_group = c.rhodecode_user.personal_repo_group
164 160 try:
165 161 # CanWriteToGroup validators checks permissions of this POST
166 162 form_result = RepoForm(repo_groups=c.repo_groups_choices,
167 163 landing_revs=c.landing_revs_choices)()\
168 164 .to_python(dict(request.POST))
169 165
170 166 # create is done sometimes async on celery, db transaction
171 167 # management is handled there.
172 168 task = RepoModel().create(form_result, c.rhodecode_user.user_id)
173 169 from celery.result import BaseAsyncResult
174 170 if isinstance(task, BaseAsyncResult):
175 171 task_id = task.task_id
176 172 except formencode.Invalid as errors:
177 173 return htmlfill.render(
178 174 render('admin/repos/repo_add.mako'),
179 175 defaults=errors.value,
180 176 errors=errors.error_dict or {},
181 177 prefix_error=False,
182 178 encoding="UTF-8",
183 179 force_defaults=False)
184 180
185 181 except Exception as e:
186 182 msg = self._log_creation_exception(e, form_result.get('repo_name'))
187 183 h.flash(msg, category='error')
188 184 return redirect(h.route_path('home'))
189 185
190 186 return redirect(h.url('repo_creating_home',
191 187 repo_name=form_result['repo_name_full'],
192 188 task_id=task_id))
193 189
194 190 # perms check inside
195 191 @NotAnonymous()
196 192 def create_repository(self):
197 193 """GET /_admin/create_repository: Form to create a new item"""
198 194 new_repo = request.GET.get('repo', '')
199 195 parent_group = safe_int(request.GET.get('parent_group'))
200 196 _gr = RepoGroup.get(parent_group)
201 197
202 198 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
203 199 # you're not super admin nor have global create permissions,
204 200 # but maybe you have at least write permission to a parent group ?
205 201
206 202 gr_name = _gr.group_name if _gr else None
207 203 # create repositories with write permission on group is set to true
208 204 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
209 205 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
210 206 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
211 207 if not (group_admin or (group_write and create_on_write)):
212 208 raise HTTPForbidden
213 209
214 210 acl_groups = RepoGroupList(RepoGroup.query().all(),
215 211 perm_set=['group.write', 'group.admin'])
216 212 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
217 213 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
218 214 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
219 215 c.personal_repo_group = c.rhodecode_user.personal_repo_group
220 216 c.new_repo = repo_name_slug(new_repo)
221 217
222 218 # apply the defaults from defaults page
223 219 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
224 220 # set checkbox to autochecked
225 221 defaults['repo_copy_permissions'] = True
226 222
227 223 parent_group_choice = '-1'
228 224 if not c.rhodecode_user.is_admin and c.rhodecode_user.personal_repo_group:
229 225 parent_group_choice = c.rhodecode_user.personal_repo_group
230 226
231 227 if parent_group and _gr:
232 228 if parent_group in [x[0] for x in c.repo_groups]:
233 229 parent_group_choice = unicode(parent_group)
234 230
235 231 defaults.update({'repo_group': parent_group_choice})
236 232
237 233 return htmlfill.render(
238 234 render('admin/repos/repo_add.mako'),
239 235 defaults=defaults,
240 236 errors={},
241 237 prefix_error=False,
242 238 encoding="UTF-8",
243 239 force_defaults=False
244 240 )
245 241
246 242 @NotAnonymous()
247 243 def repo_creating(self, repo_name):
248 244 c.repo = repo_name
249 245 c.task_id = request.GET.get('task_id')
250 246 if not c.repo:
251 247 raise HTTPNotFound()
252 248 return render('admin/repos/repo_creating.mako')
253 249
254 250 @NotAnonymous()
255 251 @jsonify
256 252 def repo_check(self, repo_name):
257 253 c.repo = repo_name
258 254 task_id = request.GET.get('task_id')
259 255
260 256 if task_id and task_id not in ['None']:
261 257 import rhodecode
262 258 from celery.result import AsyncResult
263 259 if rhodecode.CELERY_ENABLED:
264 260 task = AsyncResult(task_id)
265 261 if task.failed():
266 262 msg = self._log_creation_exception(task.result, c.repo)
267 263 h.flash(msg, category='error')
268 264 return redirect(h.route_path('home'), code=501)
269 265
270 266 repo = Repository.get_by_repo_name(repo_name)
271 267 if repo and repo.repo_state == Repository.STATE_CREATED:
272 268 if repo.clone_uri:
273 269 clone_uri = repo.clone_uri_hidden
274 270 h.flash(_('Created repository %s from %s')
275 271 % (repo.repo_name, clone_uri), category='success')
276 272 else:
277 273 repo_url = h.link_to(
278 274 repo.repo_name,
279 275 h.route_path('repo_summary',repo_name=repo.repo_name))
280 276 fork = repo.fork
281 277 if fork:
282 278 fork_name = fork.repo_name
283 279 h.flash(h.literal(_('Forked repository %s as %s')
284 280 % (fork_name, repo_url)), category='success')
285 281 else:
286 282 h.flash(h.literal(_('Created repository %s') % repo_url),
287 283 category='success')
288 284 return {'result': True}
289 285 return {'result': False}
290 286
291 287 @HasPermissionAllDecorator('hg.admin')
292 288 def show(self, repo_name, format='html'):
293 289 """GET /repos/repo_name: Show a specific item"""
294 290 # url('repo', repo_name=ID)
295 291
296 292 @HasRepoPermissionAllDecorator('repository.admin')
297 293 def edit_fields(self, repo_name):
298 294 """GET /repo_name/settings: Form to edit an existing item"""
299 295 c.repo_info = self._load_repo(repo_name)
300 296 c.repo_fields = RepositoryField.query()\
301 297 .filter(RepositoryField.repository == c.repo_info).all()
302 298 c.active = 'fields'
303 299 if request.POST:
304 300
305 301 return redirect(url('repo_edit_fields'))
306 302 return render('admin/repos/repo_edit.mako')
307 303
308 304 @HasRepoPermissionAllDecorator('repository.admin')
309 305 @auth.CSRFRequired()
310 306 def create_repo_field(self, repo_name):
311 307 try:
312 308 form_result = RepoFieldForm()().to_python(dict(request.POST))
313 309 RepoModel().add_repo_field(
314 310 repo_name, form_result['new_field_key'],
315 311 field_type=form_result['new_field_type'],
316 312 field_value=form_result['new_field_value'],
317 313 field_label=form_result['new_field_label'],
318 314 field_desc=form_result['new_field_desc'])
319 315
320 316 Session().commit()
321 317 except Exception as e:
322 318 log.exception("Exception creating field")
323 319 msg = _('An error occurred during creation of field')
324 320 if isinstance(e, formencode.Invalid):
325 321 msg += ". " + e.msg
326 322 h.flash(msg, category='error')
327 323 return redirect(url('edit_repo_fields', repo_name=repo_name))
328 324
329 325 @HasRepoPermissionAllDecorator('repository.admin')
330 326 @auth.CSRFRequired()
331 327 def delete_repo_field(self, repo_name, field_id):
332 328 field = RepositoryField.get_or_404(field_id)
333 329 try:
334 330 RepoModel().delete_repo_field(repo_name, field.field_key)
335 331 Session().commit()
336 332 except Exception as e:
337 333 log.exception("Exception during removal of field")
338 334 msg = _('An error occurred during removal of field')
339 335 h.flash(msg, category='error')
340 336 return redirect(url('edit_repo_fields', repo_name=repo_name))
341 337
342 338 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
343 339 @auth.CSRFRequired()
344 340 def toggle_locking(self, repo_name):
345 341 """
346 342 Toggle locking of repository by simple GET call to url
347 343
348 344 :param repo_name:
349 345 """
350 346
351 347 try:
352 348 repo = Repository.get_by_repo_name(repo_name)
353 349
354 350 if repo.enable_locking:
355 351 if repo.locked[0]:
356 352 Repository.unlock(repo)
357 353 action = _('Unlocked')
358 354 else:
359 355 Repository.lock(repo, c.rhodecode_user.user_id,
360 356 lock_reason=Repository.LOCK_WEB)
361 357 action = _('Locked')
362 358
363 359 h.flash(_('Repository has been %s') % action,
364 360 category='success')
365 361 except Exception:
366 362 log.exception("Exception during unlocking")
367 363 h.flash(_('An error occurred during unlocking'),
368 364 category='error')
369 365 return redirect(h.route_path('repo_summary', repo_name=repo_name))
370 366
371 367 @HasRepoPermissionAllDecorator('repository.admin')
372 368 @auth.CSRFRequired()
373 369 def edit_remote(self, repo_name):
374 370 """PUT /{repo_name}/settings/remote: edit the repo remote."""
375 371 try:
376 372 ScmModel().pull_changes(repo_name, c.rhodecode_user.username)
377 373 h.flash(_('Pulled from remote location'), category='success')
378 374 except Exception:
379 375 log.exception("Exception during pull from remote")
380 376 h.flash(_('An error occurred during pull from remote location'),
381 377 category='error')
382 378 return redirect(url('edit_repo_remote', repo_name=c.repo_name))
383 379
384 380 @HasRepoPermissionAllDecorator('repository.admin')
385 381 def edit_remote_form(self, repo_name):
386 382 """GET /repo_name/settings: Form to edit an existing item"""
387 383 c.repo_info = self._load_repo(repo_name)
388 384 c.active = 'remote'
389 385
390 386 return render('admin/repos/repo_edit.mako')
391 387
392 388 @HasRepoPermissionAllDecorator('repository.admin')
393 389 @auth.CSRFRequired()
394 390 def edit_statistics(self, repo_name):
395 391 """PUT /{repo_name}/settings/statistics: reset the repo statistics."""
396 392 try:
397 393 RepoModel().delete_stats(repo_name)
398 394 Session().commit()
399 395 except Exception as e:
400 396 log.error(traceback.format_exc())
401 397 h.flash(_('An error occurred during deletion of repository stats'),
402 398 category='error')
403 399 return redirect(url('edit_repo_statistics', repo_name=c.repo_name))
404 400
405 401 @HasRepoPermissionAllDecorator('repository.admin')
406 402 def edit_statistics_form(self, repo_name):
407 403 """GET /repo_name/settings: Form to edit an existing item"""
408 404 c.repo_info = self._load_repo(repo_name)
409 405 repo = c.repo_info.scm_instance()
410 406
411 407 if c.repo_info.stats:
412 408 # this is on what revision we ended up so we add +1 for count
413 409 last_rev = c.repo_info.stats.stat_on_revision + 1
414 410 else:
415 411 last_rev = 0
416 412 c.stats_revision = last_rev
417 413
418 414 c.repo_last_rev = repo.count()
419 415
420 416 if last_rev == 0 or c.repo_last_rev == 0:
421 417 c.stats_percentage = 0
422 418 else:
423 419 c.stats_percentage = '%.2f' % ((float((last_rev)) / c.repo_last_rev) * 100)
424 420
425 421 c.active = 'statistics'
426 422
427 423 return render('admin/repos/repo_edit.mako')
428 424
429 425 @HasRepoPermissionAllDecorator('repository.admin')
430 426 @auth.CSRFRequired()
431 427 def repo_issuetracker_test(self, repo_name):
432 428 if request.is_xhr:
433 429 return h.urlify_commit_message(
434 430 request.POST.get('test_text', ''),
435 431 repo_name)
436 432 else:
437 433 raise HTTPBadRequest()
438 434
439 435 @HasRepoPermissionAllDecorator('repository.admin')
440 436 @auth.CSRFRequired()
441 437 def repo_issuetracker_delete(self, repo_name):
442 438 uid = request.POST.get('uid')
443 439 repo_settings = IssueTrackerSettingsModel(repo=repo_name)
444 440 try:
445 441 repo_settings.delete_entries(uid)
446 442 except Exception:
447 443 h.flash(_('Error occurred during deleting issue tracker entry'),
448 444 category='error')
449 445 else:
450 446 h.flash(_('Removed issue tracker entry'), category='success')
451 447 return redirect(url('repo_settings_issuetracker',
452 448 repo_name=repo_name))
453 449
454 450 def _update_patterns(self, form, repo_settings):
455 451 for uid in form['delete_patterns']:
456 452 repo_settings.delete_entries(uid)
457 453
458 454 for pattern in form['patterns']:
459 455 for setting, value, type_ in pattern:
460 456 sett = repo_settings.create_or_update_setting(
461 457 setting, value, type_)
462 458 Session().add(sett)
463 459
464 460 Session().commit()
465 461
466 462 @HasRepoPermissionAllDecorator('repository.admin')
467 463 @auth.CSRFRequired()
468 464 def repo_issuetracker_save(self, repo_name):
469 465 # Save inheritance
470 466 repo_settings = IssueTrackerSettingsModel(repo=repo_name)
471 467 inherited = (request.POST.get('inherit_global_issuetracker')
472 468 == "inherited")
473 469 repo_settings.inherit_global_settings = inherited
474 470 Session().commit()
475 471
476 472 form = IssueTrackerPatternsForm()().to_python(request.POST)
477 473 if form:
478 474 self._update_patterns(form, repo_settings)
479 475
480 476 h.flash(_('Updated issue tracker entries'), category='success')
481 477 return redirect(url('repo_settings_issuetracker',
482 478 repo_name=repo_name))
483 479
484 480 @HasRepoPermissionAllDecorator('repository.admin')
485 481 def repo_issuetracker(self, repo_name):
486 482 """GET /admin/settings/issue-tracker: All items in the collection"""
487 483 c.active = 'issuetracker'
488 484 c.data = 'data'
489 485 c.repo_info = self._load_repo(repo_name)
490 486
491 487 repo = Repository.get_by_repo_name(repo_name)
492 488 c.settings_model = IssueTrackerSettingsModel(repo=repo)
493 489 c.global_patterns = c.settings_model.get_global_settings()
494 490 c.repo_patterns = c.settings_model.get_repo_settings()
495 491
496 492 return render('admin/repos/repo_edit.mako')
497 493
498 494 @HasRepoPermissionAllDecorator('repository.admin')
499 495 def repo_settings_vcs(self, repo_name):
500 496 """GET /{repo_name}/settings/vcs/: All items in the collection"""
501 497
502 498 model = VcsSettingsModel(repo=repo_name)
503 499
504 500 c.active = 'vcs'
505 501 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
506 502 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
507 503 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
508 504 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
509 505 c.repo_info = self._load_repo(repo_name)
510 506 defaults = self._vcs_form_defaults(repo_name)
511 507 c.inherit_global_settings = defaults['inherit_global_settings']
512 508 c.labs_active = str2bool(
513 509 rhodecode.CONFIG.get('labs_settings_active', 'true'))
514 510
515 511 return htmlfill.render(
516 512 render('admin/repos/repo_edit.mako'),
517 513 defaults=defaults,
518 514 encoding="UTF-8",
519 515 force_defaults=False)
520 516
521 517 @HasRepoPermissionAllDecorator('repository.admin')
522 518 @auth.CSRFRequired()
523 519 def repo_settings_vcs_update(self, repo_name):
524 520 """POST /{repo_name}/settings/vcs/: All items in the collection"""
525 521 c.active = 'vcs'
526 522
527 523 model = VcsSettingsModel(repo=repo_name)
528 524 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
529 525 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
530 526 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
531 527 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
532 528 c.repo_info = self._load_repo(repo_name)
533 529 defaults = self._vcs_form_defaults(repo_name)
534 530 c.inherit_global_settings = defaults['inherit_global_settings']
535 531
536 532 application_form = RepoVcsSettingsForm(repo_name)()
537 533 try:
538 534 form_result = application_form.to_python(dict(request.POST))
539 535 except formencode.Invalid as errors:
540 536 h.flash(
541 537 _("Some form inputs contain invalid data."),
542 538 category='error')
543 539 return htmlfill.render(
544 540 render('admin/repos/repo_edit.mako'),
545 541 defaults=errors.value,
546 542 errors=errors.error_dict or {},
547 543 prefix_error=False,
548 544 encoding="UTF-8",
549 545 force_defaults=False
550 546 )
551 547
552 548 try:
553 549 inherit_global_settings = form_result['inherit_global_settings']
554 550 model.create_or_update_repo_settings(
555 551 form_result, inherit_global_settings=inherit_global_settings)
556 552 except Exception:
557 553 log.exception("Exception while updating settings")
558 554 h.flash(
559 555 _('Error occurred during updating repository VCS settings'),
560 556 category='error')
561 557 else:
562 558 Session().commit()
563 559 h.flash(_('Updated VCS settings'), category='success')
564 560 return redirect(url('repo_vcs_settings', repo_name=repo_name))
565 561
566 562 return htmlfill.render(
567 563 render('admin/repos/repo_edit.mako'),
568 564 defaults=self._vcs_form_defaults(repo_name),
569 565 encoding="UTF-8",
570 566 force_defaults=False)
571 567
572 568 @HasRepoPermissionAllDecorator('repository.admin')
573 569 @auth.CSRFRequired()
574 570 @jsonify
575 571 def repo_delete_svn_pattern(self, repo_name):
576 572 if not request.is_xhr:
577 573 return False
578 574
579 575 delete_pattern_id = request.POST.get('delete_svn_pattern')
580 576 model = VcsSettingsModel(repo=repo_name)
581 577 try:
582 578 model.delete_repo_svn_pattern(delete_pattern_id)
583 579 except SettingNotFound:
584 580 raise HTTPBadRequest()
585 581
586 582 Session().commit()
587 583 return True
588 584
589 585 def _vcs_form_defaults(self, repo_name):
590 586 model = VcsSettingsModel(repo=repo_name)
591 587 global_defaults = model.get_global_settings()
592 588
593 589 repo_defaults = {}
594 590 repo_defaults.update(global_defaults)
595 591 repo_defaults.update(model.get_repo_settings())
596 592
597 593 global_defaults = {
598 594 '{}_inherited'.format(k): global_defaults[k]
599 595 for k in global_defaults}
600 596
601 597 defaults = {
602 598 'inherit_global_settings': model.inherit_global_settings
603 599 }
604 600 defaults.update(global_defaults)
605 601 defaults.update(repo_defaults)
606 602 defaults.update({
607 603 'new_svn_branch': '',
608 604 'new_svn_tag': '',
609 605 })
610 606 return defaults
@@ -1,517 +1,517 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 User Groups crud controller for pylons
23 23 """
24 24
25 25 import logging
26 26 import formencode
27 27
28 28 import peppercorn
29 29 from formencode import htmlfill
30 30 from pylons import request, tmpl_context as c, url, config
31 31 from pylons.controllers.util import redirect
32 32 from pylons.i18n.translation import _
33 33
34 34 from sqlalchemy.orm import joinedload
35 35
36 36 from rhodecode.lib import auth
37 37 from rhodecode.lib import helpers as h
38 from rhodecode.lib.ext_json import json
38 39 from rhodecode.lib.exceptions import UserGroupAssignedException,\
39 40 RepoGroupAssignmentError
40 41 from rhodecode.lib.utils import jsonify, action_logger
41 42 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
42 43 from rhodecode.lib.auth import (
43 44 LoginRequired, NotAnonymous, HasUserGroupPermissionAnyDecorator,
44 45 HasPermissionAnyDecorator, XHRRequired)
45 46 from rhodecode.lib.base import BaseController, render
46 47 from rhodecode.model.permission import PermissionModel
47 48 from rhodecode.model.scm import UserGroupList
48 49 from rhodecode.model.user_group import UserGroupModel
49 50 from rhodecode.model.db import (
50 51 User, UserGroup, UserGroupRepoToPerm, UserGroupRepoGroupToPerm)
51 52 from rhodecode.model.forms import (
52 53 UserGroupForm, UserGroupPermsForm, UserIndividualPermissionsForm,
53 54 UserPermissionsForm)
54 55 from rhodecode.model.meta import Session
55 from rhodecode.lib.utils import action_logger
56 from rhodecode.lib.ext_json import json
56
57 57
58 58 log = logging.getLogger(__name__)
59 59
60 60
61 61 class UserGroupsController(BaseController):
62 62 """REST Controller styled on the Atom Publishing Protocol"""
63 63
64 64 @LoginRequired()
65 65 def __before__(self):
66 66 super(UserGroupsController, self).__before__()
67 67 c.available_permissions = config['available_permissions']
68 68 PermissionModel().set_global_permission_choices(c, gettext_translator=_)
69 69
70 70 def __load_data(self, user_group_id):
71 71 c.group_members_obj = [x.user for x in c.user_group.members]
72 72 c.group_members_obj.sort(key=lambda u: u.username.lower())
73 73 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
74 74
75 75 def __load_defaults(self, user_group_id):
76 76 """
77 77 Load defaults settings for edit, and update
78 78
79 79 :param user_group_id:
80 80 """
81 81 user_group = UserGroup.get_or_404(user_group_id)
82 82 data = user_group.get_dict()
83 83 # fill owner
84 84 if user_group.user:
85 85 data.update({'user': user_group.user.username})
86 86 else:
87 87 replacement_user = User.get_first_super_admin().username
88 88 data.update({'user': replacement_user})
89 89 return data
90 90
91 91 def _revoke_perms_on_yourself(self, form_result):
92 92 _updates = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
93 93 form_result['perm_updates'])
94 94 _additions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
95 95 form_result['perm_additions'])
96 96 _deletions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
97 97 form_result['perm_deletions'])
98 98 admin_perm = 'usergroup.admin'
99 99 if _updates and _updates[0][1] != admin_perm or \
100 100 _additions and _additions[0][1] != admin_perm or \
101 101 _deletions and _deletions[0][1] != admin_perm:
102 102 return True
103 103 return False
104 104
105 105 # permission check inside
106 106 @NotAnonymous()
107 107 def index(self):
108 108 """GET /users_groups: All items in the collection"""
109 109 # url('users_groups')
110 110
111 111 from rhodecode.lib.utils import PartialRenderer
112 112 _render = PartialRenderer('data_table/_dt_elements.mako')
113 113
114 114 def user_group_name(user_group_id, user_group_name):
115 115 return _render("user_group_name", user_group_id, user_group_name)
116 116
117 117 def user_group_actions(user_group_id, user_group_name):
118 118 return _render("user_group_actions", user_group_id, user_group_name)
119 119
120 120 # json generate
121 121 group_iter = UserGroupList(UserGroup.query().all(),
122 122 perm_set=['usergroup.admin'])
123 123
124 124 user_groups_data = []
125 125 for user_gr in group_iter:
126 126 user_groups_data.append({
127 127 "group_name": user_group_name(
128 128 user_gr.users_group_id, h.escape(user_gr.users_group_name)),
129 129 "group_name_raw": user_gr.users_group_name,
130 130 "desc": h.escape(user_gr.user_group_description),
131 131 "members": len(user_gr.members),
132 132 "sync": user_gr.group_data.get('extern_type'),
133 133 "active": h.bool2icon(user_gr.users_group_active),
134 134 "owner": h.escape(h.link_to_user(user_gr.user.username)),
135 135 "action": user_group_actions(
136 136 user_gr.users_group_id, user_gr.users_group_name)
137 137 })
138 138
139 139 c.data = json.dumps(user_groups_data)
140 140 return render('admin/user_groups/user_groups.mako')
141 141
142 142 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
143 143 @auth.CSRFRequired()
144 144 def create(self):
145 145 """POST /users_groups: Create a new item"""
146 146 # url('users_groups')
147 147
148 148 users_group_form = UserGroupForm()()
149 149 try:
150 150 form_result = users_group_form.to_python(dict(request.POST))
151 151 user_group = UserGroupModel().create(
152 152 name=form_result['users_group_name'],
153 153 description=form_result['user_group_description'],
154 154 owner=c.rhodecode_user.user_id,
155 155 active=form_result['users_group_active'])
156 156 Session().flush()
157 157
158 158 user_group_name = form_result['users_group_name']
159 159 action_logger(c.rhodecode_user,
160 160 'admin_created_users_group:%s' % user_group_name,
161 161 None, self.ip_addr, self.sa)
162 162 user_group_link = h.link_to(h.escape(user_group_name),
163 163 url('edit_users_group',
164 164 user_group_id=user_group.users_group_id))
165 165 h.flash(h.literal(_('Created user group %(user_group_link)s')
166 166 % {'user_group_link': user_group_link}),
167 167 category='success')
168 168 Session().commit()
169 169 except formencode.Invalid as errors:
170 170 return htmlfill.render(
171 171 render('admin/user_groups/user_group_add.mako'),
172 172 defaults=errors.value,
173 173 errors=errors.error_dict or {},
174 174 prefix_error=False,
175 175 encoding="UTF-8",
176 176 force_defaults=False)
177 177 except Exception:
178 178 log.exception("Exception creating user group")
179 179 h.flash(_('Error occurred during creation of user group %s') \
180 180 % request.POST.get('users_group_name'), category='error')
181 181
182 182 return redirect(
183 183 url('edit_users_group', user_group_id=user_group.users_group_id))
184 184
185 185 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
186 186 def new(self):
187 187 """GET /user_groups/new: Form to create a new item"""
188 188 # url('new_users_group')
189 189 return render('admin/user_groups/user_group_add.mako')
190 190
191 191 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
192 192 @auth.CSRFRequired()
193 193 def update(self, user_group_id):
194 194 """PUT /user_groups/user_group_id: Update an existing item"""
195 195 # Forms posted to this method should contain a hidden field:
196 196 # <input type="hidden" name="_method" value="PUT" />
197 197 # Or using helpers:
198 198 # h.form(url('users_group', user_group_id=ID),
199 199 # method='put')
200 200 # url('users_group', user_group_id=ID)
201 201
202 202 user_group_id = safe_int(user_group_id)
203 203 c.user_group = UserGroup.get_or_404(user_group_id)
204 204 c.active = 'settings'
205 205 self.__load_data(user_group_id)
206 206
207 207 users_group_form = UserGroupForm(
208 208 edit=True, old_data=c.user_group.get_dict(), allow_disabled=True)()
209 209
210 210 try:
211 211 form_result = users_group_form.to_python(request.POST)
212 212 pstruct = peppercorn.parse(request.POST.items())
213 213 form_result['users_group_members'] = pstruct['user_group_members']
214 214
215 215 UserGroupModel().update(c.user_group, form_result)
216 216 updated_user_group = form_result['users_group_name']
217 217 action_logger(c.rhodecode_user,
218 218 'admin_updated_users_group:%s' % updated_user_group,
219 219 None, self.ip_addr, self.sa)
220 220 h.flash(_('Updated user group %s') % updated_user_group,
221 221 category='success')
222 222 Session().commit()
223 223 except formencode.Invalid as errors:
224 224 defaults = errors.value
225 225 e = errors.error_dict or {}
226 226
227 227 return htmlfill.render(
228 228 render('admin/user_groups/user_group_edit.mako'),
229 229 defaults=defaults,
230 230 errors=e,
231 231 prefix_error=False,
232 232 encoding="UTF-8",
233 233 force_defaults=False)
234 234 except Exception:
235 235 log.exception("Exception during update of user group")
236 236 h.flash(_('Error occurred during update of user group %s')
237 237 % request.POST.get('users_group_name'), category='error')
238 238
239 239 return redirect(url('edit_users_group', user_group_id=user_group_id))
240 240
241 241 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
242 242 @auth.CSRFRequired()
243 243 def delete(self, user_group_id):
244 244 """DELETE /user_groups/user_group_id: Delete an existing item"""
245 245 # Forms posted to this method should contain a hidden field:
246 246 # <input type="hidden" name="_method" value="DELETE" />
247 247 # Or using helpers:
248 248 # h.form(url('users_group', user_group_id=ID),
249 249 # method='delete')
250 250 # url('users_group', user_group_id=ID)
251 251 user_group_id = safe_int(user_group_id)
252 252 c.user_group = UserGroup.get_or_404(user_group_id)
253 253 force = str2bool(request.POST.get('force'))
254 254
255 255 try:
256 256 UserGroupModel().delete(c.user_group, force=force)
257 257 Session().commit()
258 258 h.flash(_('Successfully deleted user group'), category='success')
259 259 except UserGroupAssignedException as e:
260 260 h.flash(str(e), category='error')
261 261 except Exception:
262 262 log.exception("Exception during deletion of user group")
263 263 h.flash(_('An error occurred during deletion of user group'),
264 264 category='error')
265 265 return redirect(url('users_groups'))
266 266
267 267 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
268 268 def edit(self, user_group_id):
269 269 """GET /user_groups/user_group_id/edit: Form to edit an existing item"""
270 270 # url('edit_users_group', user_group_id=ID)
271 271
272 272 user_group_id = safe_int(user_group_id)
273 273 c.user_group = UserGroup.get_or_404(user_group_id)
274 274 c.active = 'settings'
275 275 self.__load_data(user_group_id)
276 276
277 277 defaults = self.__load_defaults(user_group_id)
278 278
279 279 return htmlfill.render(
280 280 render('admin/user_groups/user_group_edit.mako'),
281 281 defaults=defaults,
282 282 encoding="UTF-8",
283 283 force_defaults=False
284 284 )
285 285
286 286 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
287 287 def edit_perms(self, user_group_id):
288 288 user_group_id = safe_int(user_group_id)
289 289 c.user_group = UserGroup.get_or_404(user_group_id)
290 290 c.active = 'perms'
291 291
292 292 defaults = {}
293 293 # fill user group users
294 294 for p in c.user_group.user_user_group_to_perm:
295 295 defaults.update({'u_perm_%s' % p.user.user_id:
296 296 p.permission.permission_name})
297 297
298 298 for p in c.user_group.user_group_user_group_to_perm:
299 299 defaults.update({'g_perm_%s' % p.user_group.users_group_id:
300 300 p.permission.permission_name})
301 301
302 302 return htmlfill.render(
303 303 render('admin/user_groups/user_group_edit.mako'),
304 304 defaults=defaults,
305 305 encoding="UTF-8",
306 306 force_defaults=False
307 307 )
308 308
309 309 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
310 310 @auth.CSRFRequired()
311 311 def update_perms(self, user_group_id):
312 312 """
313 313 grant permission for given usergroup
314 314
315 315 :param user_group_id:
316 316 """
317 317 user_group_id = safe_int(user_group_id)
318 318 c.user_group = UserGroup.get_or_404(user_group_id)
319 319 form = UserGroupPermsForm()().to_python(request.POST)
320 320
321 321 if not c.rhodecode_user.is_admin:
322 322 if self._revoke_perms_on_yourself(form):
323 323 msg = _('Cannot change permission for yourself as admin')
324 324 h.flash(msg, category='warning')
325 325 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
326 326
327 327 try:
328 328 UserGroupModel().update_permissions(user_group_id,
329 329 form['perm_additions'], form['perm_updates'], form['perm_deletions'])
330 330 except RepoGroupAssignmentError:
331 331 h.flash(_('Target group cannot be the same'), category='error')
332 332 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
333 333 #TODO: implement this
334 334 #action_logger(c.rhodecode_user, 'admin_changed_repo_permissions',
335 335 # repo_name, self.ip_addr, self.sa)
336 336 Session().commit()
337 337 h.flash(_('User Group permissions updated'), category='success')
338 338 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
339 339
340 340 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
341 341 def edit_perms_summary(self, user_group_id):
342 342 user_group_id = safe_int(user_group_id)
343 343 c.user_group = UserGroup.get_or_404(user_group_id)
344 344 c.active = 'perms_summary'
345 345 permissions = {
346 346 'repositories': {},
347 347 'repositories_groups': {},
348 348 }
349 349 ugroup_repo_perms = UserGroupRepoToPerm.query()\
350 350 .options(joinedload(UserGroupRepoToPerm.permission))\
351 351 .options(joinedload(UserGroupRepoToPerm.repository))\
352 352 .filter(UserGroupRepoToPerm.users_group_id == user_group_id)\
353 353 .all()
354 354
355 355 for gr in ugroup_repo_perms:
356 356 permissions['repositories'][gr.repository.repo_name] \
357 357 = gr.permission.permission_name
358 358
359 359 ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
360 360 .options(joinedload(UserGroupRepoGroupToPerm.permission))\
361 361 .options(joinedload(UserGroupRepoGroupToPerm.group))\
362 362 .filter(UserGroupRepoGroupToPerm.users_group_id == user_group_id)\
363 363 .all()
364 364
365 365 for gr in ugroup_group_perms:
366 366 permissions['repositories_groups'][gr.group.group_name] \
367 367 = gr.permission.permission_name
368 368 c.permissions = permissions
369 369 return render('admin/user_groups/user_group_edit.mako')
370 370
371 371 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
372 372 def edit_global_perms(self, user_group_id):
373 373 user_group_id = safe_int(user_group_id)
374 374 c.user_group = UserGroup.get_or_404(user_group_id)
375 375 c.active = 'global_perms'
376 376
377 377 c.default_user = User.get_default_user()
378 378 defaults = c.user_group.get_dict()
379 379 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
380 380 defaults.update(c.user_group.get_default_perms())
381 381
382 382 return htmlfill.render(
383 383 render('admin/user_groups/user_group_edit.mako'),
384 384 defaults=defaults,
385 385 encoding="UTF-8",
386 386 force_defaults=False
387 387 )
388 388
389 389 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
390 390 @auth.CSRFRequired()
391 391 def update_global_perms(self, user_group_id):
392 392 """PUT /users_perm/user_group_id: Update an existing item"""
393 393 # url('users_group_perm', user_group_id=ID, method='put')
394 394 user_group_id = safe_int(user_group_id)
395 395 user_group = UserGroup.get_or_404(user_group_id)
396 396 c.active = 'global_perms'
397 397
398 398 try:
399 399 # first stage that verifies the checkbox
400 400 _form = UserIndividualPermissionsForm()
401 401 form_result = _form.to_python(dict(request.POST))
402 402 inherit_perms = form_result['inherit_default_permissions']
403 403 user_group.inherit_default_permissions = inherit_perms
404 404 Session().add(user_group)
405 405
406 406 if not inherit_perms:
407 407 # only update the individual ones if we un check the flag
408 408 _form = UserPermissionsForm(
409 409 [x[0] for x in c.repo_create_choices],
410 410 [x[0] for x in c.repo_create_on_write_choices],
411 411 [x[0] for x in c.repo_group_create_choices],
412 412 [x[0] for x in c.user_group_create_choices],
413 413 [x[0] for x in c.fork_choices],
414 414 [x[0] for x in c.inherit_default_permission_choices])()
415 415
416 416 form_result = _form.to_python(dict(request.POST))
417 417 form_result.update({'perm_user_group_id': user_group.users_group_id})
418 418
419 419 PermissionModel().update_user_group_permissions(form_result)
420 420
421 421 Session().commit()
422 422 h.flash(_('User Group global permissions updated successfully'),
423 423 category='success')
424 424
425 425 except formencode.Invalid as errors:
426 426 defaults = errors.value
427 427 c.user_group = user_group
428 428 return htmlfill.render(
429 429 render('admin/user_groups/user_group_edit.mako'),
430 430 defaults=defaults,
431 431 errors=errors.error_dict or {},
432 432 prefix_error=False,
433 433 encoding="UTF-8",
434 434 force_defaults=False)
435 435 except Exception:
436 436 log.exception("Exception during permissions saving")
437 437 h.flash(_('An error occurred during permissions saving'),
438 438 category='error')
439 439
440 440 return redirect(url('edit_user_group_global_perms', user_group_id=user_group_id))
441 441
442 442 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
443 443 def edit_advanced(self, user_group_id):
444 444 user_group_id = safe_int(user_group_id)
445 445 c.user_group = UserGroup.get_or_404(user_group_id)
446 446 c.active = 'advanced'
447 447 c.group_members_obj = sorted(
448 448 (x.user for x in c.user_group.members),
449 449 key=lambda u: u.username.lower())
450 450
451 451 c.group_to_repos = sorted(
452 452 (x.repository for x in c.user_group.users_group_repo_to_perm),
453 453 key=lambda u: u.repo_name.lower())
454 454
455 455 c.group_to_repo_groups = sorted(
456 456 (x.group for x in c.user_group.users_group_repo_group_to_perm),
457 457 key=lambda u: u.group_name.lower())
458 458
459 459 return render('admin/user_groups/user_group_edit.mako')
460 460
461 461 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
462 462 def edit_advanced_set_synchronization(self, user_group_id):
463 463 user_group_id = safe_int(user_group_id)
464 464 user_group = UserGroup.get_or_404(user_group_id)
465 465
466 466 existing = user_group.group_data.get('extern_type')
467 467
468 468 if existing:
469 469 new_state = user_group.group_data
470 470 new_state['extern_type'] = None
471 471 else:
472 472 new_state = user_group.group_data
473 473 new_state['extern_type'] = 'manual'
474 474 new_state['extern_type_set_by'] = c.rhodecode_user.username
475 475
476 476 try:
477 477 user_group.group_data = new_state
478 478 Session().add(user_group)
479 479 Session().commit()
480 480
481 481 h.flash(_('User Group synchronization updated successfully'),
482 482 category='success')
483 483 except Exception:
484 484 log.exception("Exception during sync settings saving")
485 485 h.flash(_('An error occurred during synchronization update'),
486 486 category='error')
487 487
488 488 return redirect(
489 489 url('edit_user_group_advanced', user_group_id=user_group_id))
490 490
491 491 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
492 492 @XHRRequired()
493 493 @jsonify
494 494 def user_group_members(self, user_group_id):
495 495 user_group_id = safe_int(user_group_id)
496 496 user_group = UserGroup.get_or_404(user_group_id)
497 497 group_members_obj = sorted((x.user for x in user_group.members),
498 498 key=lambda u: u.username.lower())
499 499
500 500 group_members = [
501 501 {
502 502 'id': user.user_id,
503 503 'first_name': user.name,
504 504 'last_name': user.lastname,
505 505 'username': user.username,
506 506 'icon_link': h.gravatar_url(user.email, 30),
507 507 'value_display': h.person(user.email),
508 508 'value': user.username,
509 509 'value_type': 'user',
510 510 'active': user.active,
511 511 }
512 512 for user in group_members_obj
513 513 ]
514 514
515 515 return {
516 516 'members': group_members
517 517 }
@@ -1,485 +1,484 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 commit controller for RhodeCode showing changes between commits
23 23 """
24 24
25 25 import logging
26 26
27 27 from collections import defaultdict
28 28 from webob.exc import HTTPForbidden, HTTPBadRequest, HTTPNotFound
29 29
30 30 from pylons import tmpl_context as c, request, response
31 31 from pylons.i18n.translation import _
32 32 from pylons.controllers.util import redirect
33 33
34 34 from rhodecode.lib import auth
35 35 from rhodecode.lib import diffs, codeblocks
36 36 from rhodecode.lib.auth import (
37 37 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous)
38 38 from rhodecode.lib.base import BaseRepoController, render
39 39 from rhodecode.lib.compat import OrderedDict
40 40 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
41 41 import rhodecode.lib.helpers as h
42 from rhodecode.lib.utils import action_logger, jsonify
42 from rhodecode.lib.utils import jsonify
43 43 from rhodecode.lib.utils2 import safe_unicode
44 44 from rhodecode.lib.vcs.backends.base import EmptyCommit
45 45 from rhodecode.lib.vcs.exceptions import (
46 46 RepositoryError, CommitDoesNotExistError, NodeDoesNotExistError)
47 47 from rhodecode.model.db import ChangesetComment, ChangesetStatus
48 48 from rhodecode.model.changeset_status import ChangesetStatusModel
49 49 from rhodecode.model.comment import CommentsModel
50 50 from rhodecode.model.meta import Session
51 from rhodecode.model.repo import RepoModel
52 51
53 52
54 53 log = logging.getLogger(__name__)
55 54
56 55
57 56 def _update_with_GET(params, GET):
58 57 for k in ['diff1', 'diff2', 'diff']:
59 58 params[k] += GET.getall(k)
60 59
61 60
62 61 def get_ignore_ws(fid, GET):
63 62 ig_ws_global = GET.get('ignorews')
64 63 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
65 64 if ig_ws:
66 65 try:
67 66 return int(ig_ws[0].split(':')[-1])
68 67 except Exception:
69 68 pass
70 69 return ig_ws_global
71 70
72 71
73 72 def _ignorews_url(GET, fileid=None):
74 73 fileid = str(fileid) if fileid else None
75 74 params = defaultdict(list)
76 75 _update_with_GET(params, GET)
77 76 label = _('Show whitespace')
78 77 tooltiplbl = _('Show whitespace for all diffs')
79 78 ig_ws = get_ignore_ws(fileid, GET)
80 79 ln_ctx = get_line_ctx(fileid, GET)
81 80
82 81 if ig_ws is None:
83 82 params['ignorews'] += [1]
84 83 label = _('Ignore whitespace')
85 84 tooltiplbl = _('Ignore whitespace for all diffs')
86 85 ctx_key = 'context'
87 86 ctx_val = ln_ctx
88 87
89 88 # if we have passed in ln_ctx pass it along to our params
90 89 if ln_ctx:
91 90 params[ctx_key] += [ctx_val]
92 91
93 92 if fileid:
94 93 params['anchor'] = 'a_' + fileid
95 94 return h.link_to(label, h.url.current(**params), title=tooltiplbl, class_='tooltip')
96 95
97 96
98 97 def get_line_ctx(fid, GET):
99 98 ln_ctx_global = GET.get('context')
100 99 if fid:
101 100 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
102 101 else:
103 102 _ln_ctx = filter(lambda k: k.startswith('C'), GET)
104 103 ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global
105 104 if ln_ctx:
106 105 ln_ctx = [ln_ctx]
107 106
108 107 if ln_ctx:
109 108 retval = ln_ctx[0].split(':')[-1]
110 109 else:
111 110 retval = ln_ctx_global
112 111
113 112 try:
114 113 return int(retval)
115 114 except Exception:
116 115 return 3
117 116
118 117
119 118 def _context_url(GET, fileid=None):
120 119 """
121 120 Generates a url for context lines.
122 121
123 122 :param fileid:
124 123 """
125 124
126 125 fileid = str(fileid) if fileid else None
127 126 ig_ws = get_ignore_ws(fileid, GET)
128 127 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
129 128
130 129 params = defaultdict(list)
131 130 _update_with_GET(params, GET)
132 131
133 132 if ln_ctx > 0:
134 133 params['context'] += [ln_ctx]
135 134
136 135 if ig_ws:
137 136 ig_ws_key = 'ignorews'
138 137 ig_ws_val = 1
139 138 params[ig_ws_key] += [ig_ws_val]
140 139
141 140 lbl = _('Increase context')
142 141 tooltiplbl = _('Increase context for all diffs')
143 142
144 143 if fileid:
145 144 params['anchor'] = 'a_' + fileid
146 145 return h.link_to(lbl, h.url.current(**params), title=tooltiplbl, class_='tooltip')
147 146
148 147
149 148 class ChangesetController(BaseRepoController):
150 149
151 150 def __before__(self):
152 151 super(ChangesetController, self).__before__()
153 152 c.affected_files_cut_off = 60
154 153
155 154 def _index(self, commit_id_range, method):
156 155 c.ignorews_url = _ignorews_url
157 156 c.context_url = _context_url
158 157 c.fulldiff = fulldiff = request.GET.get('fulldiff')
159 158
160 159 # fetch global flags of ignore ws or context lines
161 160 context_lcl = get_line_ctx('', request.GET)
162 161 ign_whitespace_lcl = get_ignore_ws('', request.GET)
163 162
164 163 # diff_limit will cut off the whole diff if the limit is applied
165 164 # otherwise it will just hide the big files from the front-end
166 165 diff_limit = self.cut_off_limit_diff
167 166 file_limit = self.cut_off_limit_file
168 167
169 168 # get ranges of commit ids if preset
170 169 commit_range = commit_id_range.split('...')[:2]
171 170
172 171 try:
173 172 pre_load = ['affected_files', 'author', 'branch', 'date',
174 173 'message', 'parents']
175 174
176 175 if len(commit_range) == 2:
177 176 commits = c.rhodecode_repo.get_commits(
178 177 start_id=commit_range[0], end_id=commit_range[1],
179 178 pre_load=pre_load)
180 179 commits = list(commits)
181 180 else:
182 181 commits = [c.rhodecode_repo.get_commit(
183 182 commit_id=commit_id_range, pre_load=pre_load)]
184 183
185 184 c.commit_ranges = commits
186 185 if not c.commit_ranges:
187 186 raise RepositoryError(
188 187 'The commit range returned an empty result')
189 188 except CommitDoesNotExistError:
190 189 msg = _('No such commit exists for this repository')
191 190 h.flash(msg, category='error')
192 191 raise HTTPNotFound()
193 192 except Exception:
194 193 log.exception("General failure")
195 194 raise HTTPNotFound()
196 195
197 196 c.changes = OrderedDict()
198 197 c.lines_added = 0
199 198 c.lines_deleted = 0
200 199
201 200 # auto collapse if we have more than limit
202 201 collapse_limit = diffs.DiffProcessor._collapse_commits_over
203 202 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
204 203
205 204 c.commit_statuses = ChangesetStatus.STATUSES
206 205 c.inline_comments = []
207 206 c.files = []
208 207
209 208 c.statuses = []
210 209 c.comments = []
211 210 c.unresolved_comments = []
212 211 if len(c.commit_ranges) == 1:
213 212 commit = c.commit_ranges[0]
214 213 c.comments = CommentsModel().get_comments(
215 214 c.rhodecode_db_repo.repo_id,
216 215 revision=commit.raw_id)
217 216 c.statuses.append(ChangesetStatusModel().get_status(
218 217 c.rhodecode_db_repo.repo_id, commit.raw_id))
219 218 # comments from PR
220 219 statuses = ChangesetStatusModel().get_statuses(
221 220 c.rhodecode_db_repo.repo_id, commit.raw_id,
222 221 with_revisions=True)
223 222 prs = set(st.pull_request for st in statuses
224 223 if st.pull_request is not None)
225 224 # from associated statuses, check the pull requests, and
226 225 # show comments from them
227 226 for pr in prs:
228 227 c.comments.extend(pr.comments)
229 228
230 229 c.unresolved_comments = CommentsModel()\
231 230 .get_commit_unresolved_todos(commit.raw_id)
232 231
233 232 # Iterate over ranges (default commit view is always one commit)
234 233 for commit in c.commit_ranges:
235 234 c.changes[commit.raw_id] = []
236 235
237 236 commit2 = commit
238 237 commit1 = commit.parents[0] if commit.parents else EmptyCommit()
239 238
240 239 _diff = c.rhodecode_repo.get_diff(
241 240 commit1, commit2,
242 241 ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
243 242 diff_processor = diffs.DiffProcessor(
244 243 _diff, format='newdiff', diff_limit=diff_limit,
245 244 file_limit=file_limit, show_full_diff=fulldiff)
246 245
247 246 commit_changes = OrderedDict()
248 247 if method == 'show':
249 248 _parsed = diff_processor.prepare()
250 249 c.limited_diff = isinstance(_parsed, diffs.LimitedDiffContainer)
251 250
252 251 _parsed = diff_processor.prepare()
253 252
254 253 def _node_getter(commit):
255 254 def get_node(fname):
256 255 try:
257 256 return commit.get_node(fname)
258 257 except NodeDoesNotExistError:
259 258 return None
260 259 return get_node
261 260
262 261 inline_comments = CommentsModel().get_inline_comments(
263 262 c.rhodecode_db_repo.repo_id, revision=commit.raw_id)
264 263 c.inline_cnt = CommentsModel().get_inline_comments_count(
265 264 inline_comments)
266 265
267 266 diffset = codeblocks.DiffSet(
268 267 repo_name=c.repo_name,
269 268 source_node_getter=_node_getter(commit1),
270 269 target_node_getter=_node_getter(commit2),
271 270 comments=inline_comments
272 271 ).render_patchset(_parsed, commit1.raw_id, commit2.raw_id)
273 272 c.changes[commit.raw_id] = diffset
274 273 else:
275 274 # downloads/raw we only need RAW diff nothing else
276 275 diff = diff_processor.as_raw()
277 276 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
278 277
279 278 # sort comments by how they were generated
280 279 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
281 280
282 281 if len(c.commit_ranges) == 1:
283 282 c.commit = c.commit_ranges[0]
284 283 c.parent_tmpl = ''.join(
285 284 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
286 285 if method == 'download':
287 286 response.content_type = 'text/plain'
288 287 response.content_disposition = (
289 288 'attachment; filename=%s.diff' % commit_id_range[:12])
290 289 return diff
291 290 elif method == 'patch':
292 291 response.content_type = 'text/plain'
293 292 c.diff = safe_unicode(diff)
294 293 return render('changeset/patch_changeset.mako')
295 294 elif method == 'raw':
296 295 response.content_type = 'text/plain'
297 296 return diff
298 297 elif method == 'show':
299 298 if len(c.commit_ranges) == 1:
300 299 return render('changeset/changeset.mako')
301 300 else:
302 301 c.ancestor = None
303 302 c.target_repo = c.rhodecode_db_repo
304 303 return render('changeset/changeset_range.mako')
305 304
306 305 @LoginRequired()
307 306 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
308 307 'repository.admin')
309 308 def index(self, revision, method='show'):
310 309 return self._index(revision, method=method)
311 310
312 311 @LoginRequired()
313 312 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
314 313 'repository.admin')
315 314 def changeset_raw(self, revision):
316 315 return self._index(revision, method='raw')
317 316
318 317 @LoginRequired()
319 318 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
320 319 'repository.admin')
321 320 def changeset_patch(self, revision):
322 321 return self._index(revision, method='patch')
323 322
324 323 @LoginRequired()
325 324 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
326 325 'repository.admin')
327 326 def changeset_download(self, revision):
328 327 return self._index(revision, method='download')
329 328
330 329 @LoginRequired()
331 330 @NotAnonymous()
332 331 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
333 332 'repository.admin')
334 333 @auth.CSRFRequired()
335 334 @jsonify
336 335 def comment(self, repo_name, revision):
337 336 commit_id = revision
338 337 status = request.POST.get('changeset_status', None)
339 338 text = request.POST.get('text')
340 339 comment_type = request.POST.get('comment_type')
341 340 resolves_comment_id = request.POST.get('resolves_comment_id', None)
342 341
343 342 if status:
344 343 text = text or (_('Status change %(transition_icon)s %(status)s')
345 344 % {'transition_icon': '>',
346 345 'status': ChangesetStatus.get_status_lbl(status)})
347 346
348 347 multi_commit_ids = []
349 348 for _commit_id in request.POST.get('commit_ids', '').split(','):
350 349 if _commit_id not in ['', None, EmptyCommit.raw_id]:
351 350 if _commit_id not in multi_commit_ids:
352 351 multi_commit_ids.append(_commit_id)
353 352
354 353 commit_ids = multi_commit_ids or [commit_id]
355 354
356 355 comment = None
357 356 for current_id in filter(None, commit_ids):
358 357 c.co = comment = CommentsModel().create(
359 358 text=text,
360 359 repo=c.rhodecode_db_repo.repo_id,
361 360 user=c.rhodecode_user.user_id,
362 361 commit_id=current_id,
363 362 f_path=request.POST.get('f_path'),
364 363 line_no=request.POST.get('line'),
365 364 status_change=(ChangesetStatus.get_status_lbl(status)
366 365 if status else None),
367 366 status_change_type=status,
368 367 comment_type=comment_type,
369 368 resolves_comment_id=resolves_comment_id
370 369 )
371 370
372 371 # get status if set !
373 372 if status:
374 373 # if latest status was from pull request and it's closed
375 374 # disallow changing status !
376 375 # dont_allow_on_closed_pull_request = True !
377 376
378 377 try:
379 378 ChangesetStatusModel().set_status(
380 379 c.rhodecode_db_repo.repo_id,
381 380 status,
382 381 c.rhodecode_user.user_id,
383 382 comment,
384 383 revision=current_id,
385 384 dont_allow_on_closed_pull_request=True
386 385 )
387 386 except StatusChangeOnClosedPullRequestError:
388 387 msg = _('Changing the status of a commit associated with '
389 388 'a closed pull request is not allowed')
390 389 log.exception(msg)
391 390 h.flash(msg, category='warning')
392 391 return redirect(h.url(
393 392 'changeset_home', repo_name=repo_name,
394 393 revision=current_id))
395 394
396 395 # finalize, commit and redirect
397 396 Session().commit()
398 397
399 398 data = {
400 399 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
401 400 }
402 401 if comment:
403 402 data.update(comment.get_dict())
404 403 data.update({'rendered_text':
405 404 render('changeset/changeset_comment_block.mako')})
406 405
407 406 return data
408 407
409 408 @LoginRequired()
410 409 @NotAnonymous()
411 410 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
412 411 'repository.admin')
413 412 @auth.CSRFRequired()
414 413 def preview_comment(self):
415 414 # Technically a CSRF token is not needed as no state changes with this
416 415 # call. However, as this is a POST is better to have it, so automated
417 416 # tools don't flag it as potential CSRF.
418 417 # Post is required because the payload could be bigger than the maximum
419 418 # allowed by GET.
420 419 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
421 420 raise HTTPBadRequest()
422 421 text = request.POST.get('text')
423 422 renderer = request.POST.get('renderer') or 'rst'
424 423 if text:
425 424 return h.render(text, renderer=renderer, mentions=True)
426 425 return ''
427 426
428 427 @LoginRequired()
429 428 @NotAnonymous()
430 429 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
431 430 'repository.admin')
432 431 @auth.CSRFRequired()
433 432 @jsonify
434 433 def delete_comment(self, repo_name, comment_id):
435 434 comment = ChangesetComment.get(comment_id)
436 435 if not comment:
437 436 log.debug('Comment with id:%s not found, skipping', comment_id)
438 437 # comment already deleted in another call probably
439 438 return True
440 439
441 440 owner = (comment.author.user_id == c.rhodecode_user.user_id)
442 441 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
443 442 if h.HasPermissionAny('hg.admin')() or is_repo_admin or owner:
444 443 CommentsModel().delete(comment=comment)
445 444 Session().commit()
446 445 return True
447 446 else:
448 447 raise HTTPForbidden()
449 448
450 449 @LoginRequired()
451 450 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
452 451 'repository.admin')
453 452 @jsonify
454 453 def changeset_info(self, repo_name, revision):
455 454 if request.is_xhr:
456 455 try:
457 456 return c.rhodecode_repo.get_commit(commit_id=revision)
458 457 except CommitDoesNotExistError as e:
459 458 return EmptyCommit(message=str(e))
460 459 else:
461 460 raise HTTPBadRequest()
462 461
463 462 @LoginRequired()
464 463 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
465 464 'repository.admin')
466 465 @jsonify
467 466 def changeset_children(self, repo_name, revision):
468 467 if request.is_xhr:
469 468 commit = c.rhodecode_repo.get_commit(commit_id=revision)
470 469 result = {"results": commit.children}
471 470 return result
472 471 else:
473 472 raise HTTPBadRequest()
474 473
475 474 @LoginRequired()
476 475 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
477 476 'repository.admin')
478 477 @jsonify
479 478 def changeset_parents(self, repo_name, revision):
480 479 if request.is_xhr:
481 480 commit = c.rhodecode_repo.get_commit(commit_id=revision)
482 481 result = {"results": commit.parents}
483 482 return result
484 483 else:
485 484 raise HTTPBadRequest()
General Comments 0
You need to be logged in to leave comments. Login now