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.r |
|
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.r |
|
8 | ${_('%s repository settings') % c.rhodecode_db_repo.repo_name} | |
9 | %if c.rhodecode_name: |
|
9 | %if c.rhodecode_name: | |
10 | · ${h.branding(c.rhodecode_name)} |
|
10 | · ${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.r |
|
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.r |
|
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.r |
|
5 | (_('Owner'), lambda:base.gravatar_with_user(c.rhodecode_db_repo.user.email), '', ''), | |
6 |
(_('Created on'), h.format_date(c.r |
|
6 | (_('Created on'), h.format_date(c.rhodecode_db_repo.created_on), '', ''), | |
7 |
(_('Updated on'), h.format_date(c.r |
|
7 | (_('Updated on'), h.format_date(c.rhodecode_db_repo.updated_on), '', ''), | |
8 |
(_('Cached Commit id'), lambda: h.link_to(c.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
9 | %if c.rhodecode_db_repo.clone_uri: | |
10 |
|
10 | |||
11 | ${_('Remote mirror url')}: |
|
11 | ${_('Remote mirror url')}: | |
12 |
<a href="${c.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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.r |
|
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 | · ${h.branding(c.rhodecode_name)} |
|
7 | · ${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.r |
|
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.r |
|
41 | ${h.hidden('repo_type',c.rhodecode_db_repo.repo_type)} | |
42 |
${h.hidden('fork_parent_id',c.r |
|
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