##// END OF EJS Templates
app: dropped deprecated controllers to view_utils which are actually proper name
marcink -
r3346:fc6df472 default
parent child Browse files
Show More
@@ -1,441 +1,441 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2014-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 JSON RPC utils
23 23 """
24 24
25 25 import collections
26 26 import logging
27 27
28 28 from rhodecode.api.exc import JSONRPCError
29 29 from rhodecode.lib.auth import (
30 30 HasPermissionAnyApi, HasRepoPermissionAnyApi, HasRepoGroupPermissionAnyApi)
31 31 from rhodecode.lib.utils import safe_unicode
32 32 from rhodecode.lib.vcs.exceptions import RepositoryError
33 from rhodecode.controllers.utils import get_commit_from_ref_name
33 from rhodecode.lib.view_utils import get_commit_from_ref_name
34 34 from rhodecode.lib.utils2 import str2bool
35 35
36 36 log = logging.getLogger(__name__)
37 37
38 38
39 39 class OAttr(object):
40 40 """
41 41 Special Option that defines other attribute, and can default to them
42 42
43 43 Example::
44 44
45 45 def test(apiuser, userid=Optional(OAttr('apiuser')):
46 46 user = Optional.extract(userid, evaluate_locals=local())
47 47 #if we pass in userid, we get it, else it will default to apiuser
48 48 #attribute
49 49 """
50 50
51 51 def __init__(self, attr_name):
52 52 self.attr_name = attr_name
53 53
54 54 def __repr__(self):
55 55 return '<OptionalAttr:%s>' % self.attr_name
56 56
57 57 def __call__(self):
58 58 return self
59 59
60 60
61 61 class Optional(object):
62 62 """
63 63 Defines an optional parameter::
64 64
65 65 param = param.getval() if isinstance(param, Optional) else param
66 66 param = param() if isinstance(param, Optional) else param
67 67
68 68 is equivalent of::
69 69
70 70 param = Optional.extract(param)
71 71
72 72 """
73 73
74 74 def __init__(self, type_):
75 75 self.type_ = type_
76 76
77 77 def __repr__(self):
78 78 return '<Optional:%s>' % self.type_.__repr__()
79 79
80 80 def __call__(self):
81 81 return self.getval()
82 82
83 83 def getval(self, evaluate_locals=None):
84 84 """
85 85 returns value from this Optional instance
86 86 """
87 87 if isinstance(self.type_, OAttr):
88 88 param_name = self.type_.attr_name
89 89 if evaluate_locals:
90 90 return evaluate_locals[param_name]
91 91 # use params name
92 92 return param_name
93 93 return self.type_
94 94
95 95 @classmethod
96 96 def extract(cls, val, evaluate_locals=None, binary=None):
97 97 """
98 98 Extracts value from Optional() instance
99 99
100 100 :param val:
101 101 :return: original value if it's not Optional instance else
102 102 value of instance
103 103 """
104 104 if isinstance(val, cls):
105 105 val = val.getval(evaluate_locals)
106 106
107 107 if binary:
108 108 val = str2bool(val)
109 109
110 110 return val
111 111
112 112
113 113 def parse_args(cli_args, key_prefix=''):
114 114 from rhodecode.lib.utils2 import (escape_split)
115 115 kwargs = collections.defaultdict(dict)
116 116 for el in escape_split(cli_args, ','):
117 117 kv = escape_split(el, '=', 1)
118 118 if len(kv) == 2:
119 119 k, v = kv
120 120 kwargs[key_prefix + k] = v
121 121 return kwargs
122 122
123 123
124 124 def get_origin(obj):
125 125 """
126 126 Get origin of permission from object.
127 127
128 128 :param obj:
129 129 """
130 130 origin = 'permission'
131 131
132 132 if getattr(obj, 'owner_row', '') and getattr(obj, 'admin_row', ''):
133 133 # admin and owner case, maybe we should use dual string ?
134 134 origin = 'owner'
135 135 elif getattr(obj, 'owner_row', ''):
136 136 origin = 'owner'
137 137 elif getattr(obj, 'admin_row', ''):
138 138 origin = 'super-admin'
139 139 return origin
140 140
141 141
142 142 def store_update(updates, attr, name):
143 143 """
144 144 Stores param in updates dict if it's not instance of Optional
145 145 allows easy updates of passed in params
146 146 """
147 147 if not isinstance(attr, Optional):
148 148 updates[name] = attr
149 149
150 150
151 151 def has_superadmin_permission(apiuser):
152 152 """
153 153 Return True if apiuser is admin or return False
154 154
155 155 :param apiuser:
156 156 """
157 157 if HasPermissionAnyApi('hg.admin')(user=apiuser):
158 158 return True
159 159 return False
160 160
161 161
162 162 def validate_repo_permissions(apiuser, repoid, repo, perms):
163 163 """
164 164 Raise JsonRPCError if apiuser is not authorized or return True
165 165
166 166 :param apiuser:
167 167 :param repoid:
168 168 :param repo:
169 169 :param perms:
170 170 """
171 171 if not HasRepoPermissionAnyApi(*perms)(
172 172 user=apiuser, repo_name=repo.repo_name):
173 173 raise JSONRPCError(
174 174 'repository `%s` does not exist' % repoid)
175 175
176 176 return True
177 177
178 178
179 179 def validate_repo_group_permissions(apiuser, repogroupid, repo_group, perms):
180 180 """
181 181 Raise JsonRPCError if apiuser is not authorized or return True
182 182
183 183 :param apiuser:
184 184 :param repogroupid: just the id of repository group
185 185 :param repo_group: instance of repo_group
186 186 :param perms:
187 187 """
188 188 if not HasRepoGroupPermissionAnyApi(*perms)(
189 189 user=apiuser, group_name=repo_group.group_name):
190 190 raise JSONRPCError(
191 191 'repository group `%s` does not exist' % repogroupid)
192 192
193 193 return True
194 194
195 195
196 196 def validate_set_owner_permissions(apiuser, owner):
197 197 if isinstance(owner, Optional):
198 198 owner = get_user_or_error(apiuser.user_id)
199 199 else:
200 200 if has_superadmin_permission(apiuser):
201 201 owner = get_user_or_error(owner)
202 202 else:
203 203 # forbid setting owner for non-admins
204 204 raise JSONRPCError(
205 205 'Only RhodeCode super-admin can specify `owner` param')
206 206 return owner
207 207
208 208
209 209 def get_user_or_error(userid):
210 210 """
211 211 Get user by id or name or return JsonRPCError if not found
212 212
213 213 :param userid:
214 214 """
215 215 from rhodecode.model.user import UserModel
216 216 user_model = UserModel()
217 217
218 218 if isinstance(userid, (int, long)):
219 219 try:
220 220 user = user_model.get_user(userid)
221 221 except ValueError:
222 222 user = None
223 223 else:
224 224 user = user_model.get_by_username(userid)
225 225
226 226 if user is None:
227 227 raise JSONRPCError(
228 228 'user `%s` does not exist' % (userid,))
229 229 return user
230 230
231 231
232 232 def get_repo_or_error(repoid):
233 233 """
234 234 Get repo by id or name or return JsonRPCError if not found
235 235
236 236 :param repoid:
237 237 """
238 238 from rhodecode.model.repo import RepoModel
239 239 repo_model = RepoModel()
240 240
241 241 if isinstance(repoid, (int, long)):
242 242 try:
243 243 repo = repo_model.get_repo(repoid)
244 244 except ValueError:
245 245 repo = None
246 246 else:
247 247 repo = repo_model.get_by_repo_name(repoid)
248 248
249 249 if repo is None:
250 250 raise JSONRPCError(
251 251 'repository `%s` does not exist' % (repoid,))
252 252 return repo
253 253
254 254
255 255 def get_repo_group_or_error(repogroupid):
256 256 """
257 257 Get repo group by id or name or return JsonRPCError if not found
258 258
259 259 :param repogroupid:
260 260 """
261 261 from rhodecode.model.repo_group import RepoGroupModel
262 262 repo_group_model = RepoGroupModel()
263 263
264 264 if isinstance(repogroupid, (int, long)):
265 265 try:
266 266 repo_group = repo_group_model._get_repo_group(repogroupid)
267 267 except ValueError:
268 268 repo_group = None
269 269 else:
270 270 repo_group = repo_group_model.get_by_group_name(repogroupid)
271 271
272 272 if repo_group is None:
273 273 raise JSONRPCError(
274 274 'repository group `%s` does not exist' % (repogroupid,))
275 275 return repo_group
276 276
277 277
278 278 def get_user_group_or_error(usergroupid):
279 279 """
280 280 Get user group by id or name or return JsonRPCError if not found
281 281
282 282 :param usergroupid:
283 283 """
284 284 from rhodecode.model.user_group import UserGroupModel
285 285 user_group_model = UserGroupModel()
286 286
287 287 if isinstance(usergroupid, (int, long)):
288 288 try:
289 289 user_group = user_group_model.get_group(usergroupid)
290 290 except ValueError:
291 291 user_group = None
292 292 else:
293 293 user_group = user_group_model.get_by_name(usergroupid)
294 294
295 295 if user_group is None:
296 296 raise JSONRPCError(
297 297 'user group `%s` does not exist' % (usergroupid,))
298 298 return user_group
299 299
300 300
301 301 def get_perm_or_error(permid, prefix=None):
302 302 """
303 303 Get permission by id or name or return JsonRPCError if not found
304 304
305 305 :param permid:
306 306 """
307 307 from rhodecode.model.permission import PermissionModel
308 308
309 309 perm = PermissionModel.cls.get_by_key(permid)
310 310 if perm is None:
311 311 raise JSONRPCError('permission `%s` does not exist' % (permid,))
312 312 if prefix:
313 313 if not perm.permission_name.startswith(prefix):
314 314 raise JSONRPCError('permission `%s` is invalid, '
315 315 'should start with %s' % (permid, prefix))
316 316 return perm
317 317
318 318
319 319 def get_gist_or_error(gistid):
320 320 """
321 321 Get gist by id or gist_access_id or return JsonRPCError if not found
322 322
323 323 :param gistid:
324 324 """
325 325 from rhodecode.model.gist import GistModel
326 326
327 327 gist = GistModel.cls.get_by_access_id(gistid)
328 328 if gist is None:
329 329 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
330 330 return gist
331 331
332 332
333 333 def get_pull_request_or_error(pullrequestid):
334 334 """
335 335 Get pull request by id or return JsonRPCError if not found
336 336
337 337 :param pullrequestid:
338 338 """
339 339 from rhodecode.model.pull_request import PullRequestModel
340 340
341 341 try:
342 342 pull_request = PullRequestModel().get(int(pullrequestid))
343 343 except ValueError:
344 344 raise JSONRPCError('pullrequestid must be an integer')
345 345 if not pull_request:
346 346 raise JSONRPCError('pull request `%s` does not exist' % (
347 347 pullrequestid,))
348 348 return pull_request
349 349
350 350
351 351 def build_commit_data(commit, detail_level):
352 352 parsed_diff = []
353 353 if detail_level == 'extended':
354 354 for f in commit.added:
355 355 parsed_diff.append(_get_commit_dict(filename=f.path, op='A'))
356 356 for f in commit.changed:
357 357 parsed_diff.append(_get_commit_dict(filename=f.path, op='M'))
358 358 for f in commit.removed:
359 359 parsed_diff.append(_get_commit_dict(filename=f.path, op='D'))
360 360
361 361 elif detail_level == 'full':
362 362 from rhodecode.lib.diffs import DiffProcessor
363 363 diff_processor = DiffProcessor(commit.diff())
364 364 for dp in diff_processor.prepare():
365 365 del dp['stats']['ops']
366 366 _stats = dp['stats']
367 367 parsed_diff.append(_get_commit_dict(
368 368 filename=dp['filename'], op=dp['operation'],
369 369 new_revision=dp['new_revision'],
370 370 old_revision=dp['old_revision'],
371 371 raw_diff=dp['raw_diff'], stats=_stats))
372 372
373 373 return parsed_diff
374 374
375 375
376 376 def get_commit_or_error(ref, repo):
377 377 try:
378 378 ref_type, _, ref_hash = ref.split(':')
379 379 except ValueError:
380 380 raise JSONRPCError(
381 381 'Ref `{ref}` given in a wrong format. Please check the API'
382 382 ' documentation for more details'.format(ref=ref))
383 383 try:
384 384 # TODO: dan: refactor this to use repo.scm_instance().get_commit()
385 385 # once get_commit supports ref_types
386 386 return get_commit_from_ref_name(repo, ref_hash)
387 387 except RepositoryError:
388 388 raise JSONRPCError('Ref `{ref}` does not exist'.format(ref=ref))
389 389
390 390
391 391 def resolve_ref_or_error(ref, repo):
392 392 def _parse_ref(type_, name, hash_=None):
393 393 return type_, name, hash_
394 394
395 395 try:
396 396 ref_type, ref_name, ref_hash = _parse_ref(*ref.split(':'))
397 397 except TypeError:
398 398 raise JSONRPCError(
399 399 'Ref `{ref}` given in a wrong format. Please check the API'
400 400 ' documentation for more details'.format(ref=ref))
401 401
402 402 try:
403 403 ref_hash = ref_hash or _get_ref_hash(repo, ref_type, ref_name)
404 404 except (KeyError, ValueError):
405 405 raise JSONRPCError(
406 406 'The specified value:{type}:`{name}` does not exist, or is not allowed.'.format(
407 407 type=ref_type, name=ref_name))
408 408
409 409 return ':'.join([ref_type, ref_name, ref_hash])
410 410
411 411
412 412 def _get_commit_dict(
413 413 filename, op, new_revision=None, old_revision=None,
414 414 raw_diff=None, stats=None):
415 415 if stats is None:
416 416 stats = {
417 417 "added": None,
418 418 "binary": None,
419 419 "deleted": None
420 420 }
421 421 return {
422 422 "filename": safe_unicode(filename),
423 423 "op": op,
424 424
425 425 # extra details
426 426 "new_revision": new_revision,
427 427 "old_revision": old_revision,
428 428
429 429 "raw_diff": raw_diff,
430 430 "stats": stats
431 431 }
432 432
433 433
434 434 def _get_ref_hash(repo, type_, name):
435 435 vcs_repo = repo.scm_instance()
436 436 if type_ == 'branch' and vcs_repo.alias in ('hg', 'git'):
437 437 return vcs_repo.branches[name]
438 438 elif type_ == 'bookmark' and vcs_repo.alias == 'hg':
439 439 return vcs_repo.bookmarks[name]
440 440 else:
441 441 raise ValueError()
@@ -1,316 +1,317 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import logging
23 23
24 24 from pyramid.httpexceptions import HTTPBadRequest, HTTPNotFound, HTTPFound
25 25 from pyramid.view import view_config
26 26 from pyramid.renderers import render
27 27 from pyramid.response import Response
28 28
29 29 from rhodecode.apps._base import RepoAppView
30 from rhodecode.controllers.utils import parse_path_ref, get_commit_from_ref_name
30
31 31 from rhodecode.lib import helpers as h
32 32 from rhodecode.lib import diffs, codeblocks
33 33 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
34 34 from rhodecode.lib.utils import safe_str
35 35 from rhodecode.lib.utils2 import safe_unicode, str2bool
36 from rhodecode.lib.view_utils import parse_path_ref, get_commit_from_ref_name
36 37 from rhodecode.lib.vcs.exceptions import (
37 38 EmptyRepositoryError, RepositoryError, RepositoryRequirementError,
38 39 NodeDoesNotExistError)
39 40 from rhodecode.model.db import Repository, ChangesetStatus
40 41
41 42 log = logging.getLogger(__name__)
42 43
43 44
44 45 class RepoCompareView(RepoAppView):
45 46 def load_default_context(self):
46 47 c = self._get_local_tmpl_context(include_app_defaults=True)
47 48 c.rhodecode_repo = self.rhodecode_vcs_repo
48 49 return c
49 50
50 51 def _get_commit_or_redirect(
51 52 self, ref, ref_type, repo, redirect_after=True, partial=False):
52 53 """
53 54 This is a safe way to get a commit. If an error occurs it
54 55 redirects to a commit with a proper message. If partial is set
55 56 then it does not do redirect raise and throws an exception instead.
56 57 """
57 58 _ = self.request.translate
58 59 try:
59 60 return get_commit_from_ref_name(repo, safe_str(ref), ref_type)
60 61 except EmptyRepositoryError:
61 62 if not redirect_after:
62 63 return repo.scm_instance().EMPTY_COMMIT
63 64 h.flash(h.literal(_('There are no commits yet')),
64 65 category='warning')
65 66 if not partial:
66 67 raise HTTPFound(
67 68 h.route_path('repo_summary', repo_name=repo.repo_name))
68 69 raise HTTPBadRequest()
69 70
70 71 except RepositoryError as e:
71 72 log.exception(safe_str(e))
72 73 h.flash(safe_str(h.escape(e)), category='warning')
73 74 if not partial:
74 75 raise HTTPFound(
75 76 h.route_path('repo_summary', repo_name=repo.repo_name))
76 77 raise HTTPBadRequest()
77 78
78 79 @LoginRequired()
79 80 @HasRepoPermissionAnyDecorator(
80 81 'repository.read', 'repository.write', 'repository.admin')
81 82 @view_config(
82 83 route_name='repo_compare_select', request_method='GET',
83 84 renderer='rhodecode:templates/compare/compare_diff.mako')
84 85 def compare_select(self):
85 86 _ = self.request.translate
86 87 c = self.load_default_context()
87 88
88 89 source_repo = self.db_repo_name
89 90 target_repo = self.request.GET.get('target_repo', source_repo)
90 91 c.source_repo = Repository.get_by_repo_name(source_repo)
91 92 c.target_repo = Repository.get_by_repo_name(target_repo)
92 93
93 94 if c.source_repo is None or c.target_repo is None:
94 95 raise HTTPNotFound()
95 96
96 97 c.compare_home = True
97 98 c.commit_ranges = []
98 99 c.collapse_all_commits = False
99 100 c.diffset = None
100 101 c.limited_diff = False
101 102 c.source_ref = c.target_ref = _('Select commit')
102 103 c.source_ref_type = ""
103 104 c.target_ref_type = ""
104 105 c.commit_statuses = ChangesetStatus.STATUSES
105 106 c.preview_mode = False
106 107 c.file_path = None
107 108
108 109 return self._get_template_context(c)
109 110
110 111 @LoginRequired()
111 112 @HasRepoPermissionAnyDecorator(
112 113 'repository.read', 'repository.write', 'repository.admin')
113 114 @view_config(
114 115 route_name='repo_compare', request_method='GET',
115 116 renderer=None)
116 117 def compare(self):
117 118 _ = self.request.translate
118 119 c = self.load_default_context()
119 120
120 121 source_ref_type = self.request.matchdict['source_ref_type']
121 122 source_ref = self.request.matchdict['source_ref']
122 123 target_ref_type = self.request.matchdict['target_ref_type']
123 124 target_ref = self.request.matchdict['target_ref']
124 125
125 126 # source_ref will be evaluated in source_repo
126 127 source_repo_name = self.db_repo_name
127 128 source_path, source_id = parse_path_ref(source_ref)
128 129
129 130 # target_ref will be evaluated in target_repo
130 131 target_repo_name = self.request.GET.get('target_repo', source_repo_name)
131 132 target_path, target_id = parse_path_ref(
132 133 target_ref, default_path=self.request.GET.get('f_path', ''))
133 134
134 135 # if merge is True
135 136 # Show what changes since the shared ancestor commit of target/source
136 137 # the source would get if it was merged with target. Only commits
137 138 # which are in target but not in source will be shown.
138 139 merge = str2bool(self.request.GET.get('merge'))
139 140 # if merge is False
140 141 # Show a raw diff of source/target refs even if no ancestor exists
141 142
142 143 # c.fulldiff disables cut_off_limit
143 144 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
144 145
145 146 # fetch global flags of ignore ws or context lines
146 147 diff_context = diffs.get_diff_context(self.request)
147 148 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
148 149
149 150 c.file_path = target_path
150 151 c.commit_statuses = ChangesetStatus.STATUSES
151 152
152 153 # if partial, returns just compare_commits.html (commits log)
153 154 partial = self.request.is_xhr
154 155
155 156 # swap url for compare_diff page
156 157 c.swap_url = h.route_path(
157 158 'repo_compare',
158 159 repo_name=target_repo_name,
159 160 source_ref_type=target_ref_type,
160 161 source_ref=target_ref,
161 162 target_repo=source_repo_name,
162 163 target_ref_type=source_ref_type,
163 164 target_ref=source_ref,
164 165 _query=dict(merge=merge and '1' or '', f_path=target_path))
165 166
166 167 source_repo = Repository.get_by_repo_name(source_repo_name)
167 168 target_repo = Repository.get_by_repo_name(target_repo_name)
168 169
169 170 if source_repo is None:
170 171 log.error('Could not find the source repo: {}'
171 172 .format(source_repo_name))
172 173 h.flash(_('Could not find the source repo: `{}`')
173 174 .format(h.escape(source_repo_name)), category='error')
174 175 raise HTTPFound(
175 176 h.route_path('repo_compare_select', repo_name=self.db_repo_name))
176 177
177 178 if target_repo is None:
178 179 log.error('Could not find the target repo: {}'
179 180 .format(source_repo_name))
180 181 h.flash(_('Could not find the target repo: `{}`')
181 182 .format(h.escape(target_repo_name)), category='error')
182 183 raise HTTPFound(
183 184 h.route_path('repo_compare_select', repo_name=self.db_repo_name))
184 185
185 186 source_scm = source_repo.scm_instance()
186 187 target_scm = target_repo.scm_instance()
187 188
188 189 source_alias = source_scm.alias
189 190 target_alias = target_scm.alias
190 191 if source_alias != target_alias:
191 192 msg = _('The comparison of two different kinds of remote repos '
192 193 'is not available')
193 194 log.error(msg)
194 195 h.flash(msg, category='error')
195 196 raise HTTPFound(
196 197 h.route_path('repo_compare_select', repo_name=self.db_repo_name))
197 198
198 199 source_commit = self._get_commit_or_redirect(
199 200 ref=source_id, ref_type=source_ref_type, repo=source_repo,
200 201 partial=partial)
201 202 target_commit = self._get_commit_or_redirect(
202 203 ref=target_id, ref_type=target_ref_type, repo=target_repo,
203 204 partial=partial)
204 205
205 206 c.compare_home = False
206 207 c.source_repo = source_repo
207 208 c.target_repo = target_repo
208 209 c.source_ref = source_ref
209 210 c.target_ref = target_ref
210 211 c.source_ref_type = source_ref_type
211 212 c.target_ref_type = target_ref_type
212 213
213 214 pre_load = ["author", "branch", "date", "message"]
214 215 c.ancestor = None
215 216
216 217 if c.file_path:
217 218 if source_commit == target_commit:
218 219 c.commit_ranges = []
219 220 else:
220 221 c.commit_ranges = [target_commit]
221 222 else:
222 223 try:
223 224 c.commit_ranges = source_scm.compare(
224 225 source_commit.raw_id, target_commit.raw_id,
225 226 target_scm, merge, pre_load=pre_load)
226 227 if merge:
227 228 c.ancestor = source_scm.get_common_ancestor(
228 229 source_commit.raw_id, target_commit.raw_id, target_scm)
229 230 except RepositoryRequirementError:
230 231 msg = _('Could not compare repos with different '
231 232 'large file settings')
232 233 log.error(msg)
233 234 if partial:
234 235 return Response(msg)
235 236 h.flash(msg, category='error')
236 237 raise HTTPFound(
237 238 h.route_path('repo_compare_select',
238 239 repo_name=self.db_repo_name))
239 240
240 241 c.statuses = self.db_repo.statuses(
241 242 [x.raw_id for x in c.commit_ranges])
242 243
243 244 # auto collapse if we have more than limit
244 245 collapse_limit = diffs.DiffProcessor._collapse_commits_over
245 246 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
246 247
247 248 if partial: # for PR ajax commits loader
248 249 if not c.ancestor:
249 250 return Response('') # cannot merge if there is no ancestor
250 251
251 252 html = render(
252 253 'rhodecode:templates/compare/compare_commits.mako',
253 254 self._get_template_context(c), self.request)
254 255 return Response(html)
255 256
256 257 if c.ancestor:
257 258 # case we want a simple diff without incoming commits,
258 259 # previewing what will be merged.
259 260 # Make the diff on target repo (which is known to have target_ref)
260 261 log.debug('Using ancestor %s as source_ref instead of %s',
261 262 c.ancestor, source_ref)
262 263 source_repo = target_repo
263 264 source_commit = target_repo.get_commit(commit_id=c.ancestor)
264 265
265 266 # diff_limit will cut off the whole diff if the limit is applied
266 267 # otherwise it will just hide the big files from the front-end
267 268 diff_limit = c.visual.cut_off_limit_diff
268 269 file_limit = c.visual.cut_off_limit_file
269 270
270 271 log.debug('calculating diff between '
271 272 'source_ref:%s and target_ref:%s for repo `%s`',
272 273 source_commit, target_commit,
273 274 safe_unicode(source_repo.scm_instance().path))
274 275
275 276 if source_commit.repository != target_commit.repository:
276 277 msg = _(
277 278 "Repositories unrelated. "
278 279 "Cannot compare commit %(commit1)s from repository %(repo1)s "
279 280 "with commit %(commit2)s from repository %(repo2)s.") % {
280 281 'commit1': h.show_id(source_commit),
281 282 'repo1': source_repo.repo_name,
282 283 'commit2': h.show_id(target_commit),
283 284 'repo2': target_repo.repo_name,
284 285 }
285 286 h.flash(msg, category='error')
286 287 raise HTTPFound(
287 288 h.route_path('repo_compare_select',
288 289 repo_name=self.db_repo_name))
289 290
290 291 txt_diff = source_repo.scm_instance().get_diff(
291 292 commit1=source_commit, commit2=target_commit,
292 293 path=target_path, path1=source_path,
293 294 ignore_whitespace=hide_whitespace_changes, context=diff_context)
294 295
295 296 diff_processor = diffs.DiffProcessor(
296 297 txt_diff, format='newdiff', diff_limit=diff_limit,
297 298 file_limit=file_limit, show_full_diff=c.fulldiff)
298 299 _parsed = diff_processor.prepare()
299 300
300 301 diffset = codeblocks.DiffSet(
301 302 repo_name=source_repo.repo_name,
302 303 source_node_getter=codeblocks.diffset_node_getter(source_commit),
303 304 target_repo_name=self.db_repo_name,
304 305 target_node_getter=codeblocks.diffset_node_getter(target_commit),
305 306 )
306 307 c.diffset = self.path_filter.render_patchset_filtered(
307 308 diffset, _parsed, source_ref, target_ref)
308 309
309 310 c.preview_mode = merge
310 311 c.source_commit = source_commit
311 312 c.target_commit = target_commit
312 313
313 314 html = render(
314 315 'rhodecode:templates/compare/compare_diff.mako',
315 316 self._get_template_context(c), self.request)
316 317 return Response(html) No newline at end of file
@@ -1,1385 +1,1386 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import itertools
22 22 import logging
23 23 import os
24 24 import shutil
25 25 import tempfile
26 26 import collections
27 27
28 28 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
29 29 from pyramid.view import view_config
30 30 from pyramid.renderers import render
31 31 from pyramid.response import Response
32 32
33 33 import rhodecode
34 34 from rhodecode.apps._base import RepoAppView
35 35
36 from rhodecode.controllers.utils import parse_path_ref
36
37 37 from rhodecode.lib import diffs, helpers as h, rc_cache
38 38 from rhodecode.lib import audit_logger
39 from rhodecode.lib.view_utils import parse_path_ref
39 40 from rhodecode.lib.exceptions import NonRelativePathError
40 41 from rhodecode.lib.codeblocks import (
41 42 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
42 43 from rhodecode.lib.utils2 import (
43 44 convert_line_endings, detect_mode, safe_str, str2bool, safe_int)
44 45 from rhodecode.lib.auth import (
45 46 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
46 47 from rhodecode.lib.vcs import path as vcspath
47 48 from rhodecode.lib.vcs.backends.base import EmptyCommit
48 49 from rhodecode.lib.vcs.conf import settings
49 50 from rhodecode.lib.vcs.nodes import FileNode
50 51 from rhodecode.lib.vcs.exceptions import (
51 52 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
52 53 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
53 54 NodeDoesNotExistError, CommitError, NodeError)
54 55
55 56 from rhodecode.model.scm import ScmModel
56 57 from rhodecode.model.db import Repository
57 58
58 59 log = logging.getLogger(__name__)
59 60
60 61
61 62 class RepoFilesView(RepoAppView):
62 63
63 64 @staticmethod
64 65 def adjust_file_path_for_svn(f_path, repo):
65 66 """
66 67 Computes the relative path of `f_path`.
67 68
68 69 This is mainly based on prefix matching of the recognized tags and
69 70 branches in the underlying repository.
70 71 """
71 72 tags_and_branches = itertools.chain(
72 73 repo.branches.iterkeys(),
73 74 repo.tags.iterkeys())
74 75 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
75 76
76 77 for name in tags_and_branches:
77 78 if f_path.startswith('{}/'.format(name)):
78 79 f_path = vcspath.relpath(f_path, name)
79 80 break
80 81 return f_path
81 82
82 83 def load_default_context(self):
83 84 c = self._get_local_tmpl_context(include_app_defaults=True)
84 85 c.rhodecode_repo = self.rhodecode_vcs_repo
85 86 return c
86 87
87 88 def _ensure_not_locked(self):
88 89 _ = self.request.translate
89 90
90 91 repo = self.db_repo
91 92 if repo.enable_locking and repo.locked[0]:
92 93 h.flash(_('This repository has been locked by %s on %s')
93 94 % (h.person_by_id(repo.locked[0]),
94 95 h.format_date(h.time_to_datetime(repo.locked[1]))),
95 96 'warning')
96 97 files_url = h.route_path(
97 98 'repo_files:default_path',
98 99 repo_name=self.db_repo_name, commit_id='tip')
99 100 raise HTTPFound(files_url)
100 101
101 102 def check_branch_permission(self, branch_name):
102 103 _ = self.request.translate
103 104
104 105 rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
105 106 self.db_repo_name, branch_name)
106 107 if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
107 108 h.flash(
108 109 _('Branch `{}` changes forbidden by rule {}.').format(branch_name, rule),
109 110 'warning')
110 111 files_url = h.route_path(
111 112 'repo_files:default_path',
112 113 repo_name=self.db_repo_name, commit_id='tip')
113 114 raise HTTPFound(files_url)
114 115
115 116 def _get_commit_and_path(self):
116 117 default_commit_id = self.db_repo.landing_rev[1]
117 118 default_f_path = '/'
118 119
119 120 commit_id = self.request.matchdict.get(
120 121 'commit_id', default_commit_id)
121 122 f_path = self._get_f_path(self.request.matchdict, default_f_path)
122 123 return commit_id, f_path
123 124
124 125 def _get_default_encoding(self, c):
125 126 enc_list = getattr(c, 'default_encodings', [])
126 127 return enc_list[0] if enc_list else 'UTF-8'
127 128
128 129 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
129 130 """
130 131 This is a safe way to get commit. If an error occurs it redirects to
131 132 tip with proper message
132 133
133 134 :param commit_id: id of commit to fetch
134 135 :param redirect_after: toggle redirection
135 136 """
136 137 _ = self.request.translate
137 138
138 139 try:
139 140 return self.rhodecode_vcs_repo.get_commit(commit_id)
140 141 except EmptyRepositoryError:
141 142 if not redirect_after:
142 143 return None
143 144
144 145 _url = h.route_path(
145 146 'repo_files_add_file',
146 147 repo_name=self.db_repo_name, commit_id=0, f_path='',
147 148 _anchor='edit')
148 149
149 150 if h.HasRepoPermissionAny(
150 151 'repository.write', 'repository.admin')(self.db_repo_name):
151 152 add_new = h.link_to(
152 153 _('Click here to add a new file.'), _url, class_="alert-link")
153 154 else:
154 155 add_new = ""
155 156
156 157 h.flash(h.literal(
157 158 _('There are no files yet. %s') % add_new), category='warning')
158 159 raise HTTPFound(
159 160 h.route_path('repo_summary', repo_name=self.db_repo_name))
160 161
161 162 except (CommitDoesNotExistError, LookupError):
162 163 msg = _('No such commit exists for this repository')
163 164 h.flash(msg, category='error')
164 165 raise HTTPNotFound()
165 166 except RepositoryError as e:
166 167 h.flash(safe_str(h.escape(e)), category='error')
167 168 raise HTTPNotFound()
168 169
169 170 def _get_filenode_or_redirect(self, commit_obj, path):
170 171 """
171 172 Returns file_node, if error occurs or given path is directory,
172 173 it'll redirect to top level path
173 174 """
174 175 _ = self.request.translate
175 176
176 177 try:
177 178 file_node = commit_obj.get_node(path)
178 179 if file_node.is_dir():
179 180 raise RepositoryError('The given path is a directory')
180 181 except CommitDoesNotExistError:
181 182 log.exception('No such commit exists for this repository')
182 183 h.flash(_('No such commit exists for this repository'), category='error')
183 184 raise HTTPNotFound()
184 185 except RepositoryError as e:
185 186 log.warning('Repository error while fetching '
186 187 'filenode `%s`. Err:%s', path, e)
187 188 h.flash(safe_str(h.escape(e)), category='error')
188 189 raise HTTPNotFound()
189 190
190 191 return file_node
191 192
192 193 def _is_valid_head(self, commit_id, repo):
193 194 branch_name = sha_commit_id = ''
194 195 is_head = False
195 196
196 197 if h.is_svn(repo) and not repo.is_empty():
197 198 # Note: Subversion only has one head.
198 199 if commit_id == repo.get_commit(commit_idx=-1).raw_id:
199 200 is_head = True
200 201 return branch_name, sha_commit_id, is_head
201 202
202 203 for _branch_name, branch_commit_id in repo.branches.items():
203 204 # simple case we pass in branch name, it's a HEAD
204 205 if commit_id == _branch_name:
205 206 is_head = True
206 207 branch_name = _branch_name
207 208 sha_commit_id = branch_commit_id
208 209 break
209 210 # case when we pass in full sha commit_id, which is a head
210 211 elif commit_id == branch_commit_id:
211 212 is_head = True
212 213 branch_name = _branch_name
213 214 sha_commit_id = branch_commit_id
214 215 break
215 216
216 217 # checked branches, means we only need to try to get the branch/commit_sha
217 218 if not repo.is_empty:
218 219 commit = repo.get_commit(commit_id=commit_id)
219 220 if commit:
220 221 branch_name = commit.branch
221 222 sha_commit_id = commit.raw_id
222 223
223 224 return branch_name, sha_commit_id, is_head
224 225
225 226 def _get_tree_at_commit(
226 227 self, c, commit_id, f_path, full_load=False):
227 228
228 229 repo_id = self.db_repo.repo_id
229 230
230 231 cache_seconds = safe_int(
231 232 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
232 233 cache_on = cache_seconds > 0
233 234 log.debug(
234 235 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
235 236 'with caching: %s[TTL: %ss]' % (
236 237 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
237 238
238 239 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
239 240 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
240 241
241 242 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
242 243 condition=cache_on)
243 244 def compute_file_tree(repo_id, commit_id, f_path, full_load):
244 245 log.debug('Generating cached file tree for repo_id: %s, %s, %s',
245 246 repo_id, commit_id, f_path)
246 247
247 248 c.full_load = full_load
248 249 return render(
249 250 'rhodecode:templates/files/files_browser_tree.mako',
250 251 self._get_template_context(c), self.request)
251 252
252 253 return compute_file_tree(self.db_repo.repo_id, commit_id, f_path, full_load)
253 254
254 255 def _get_archive_spec(self, fname):
255 256 log.debug('Detecting archive spec for: `%s`', fname)
256 257
257 258 fileformat = None
258 259 ext = None
259 260 content_type = None
260 261 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
261 262 content_type, extension = ext_data
262 263
263 264 if fname.endswith(extension):
264 265 fileformat = a_type
265 266 log.debug('archive is of type: %s', fileformat)
266 267 ext = extension
267 268 break
268 269
269 270 if not fileformat:
270 271 raise ValueError()
271 272
272 273 # left over part of whole fname is the commit
273 274 commit_id = fname[:-len(ext)]
274 275
275 276 return commit_id, ext, fileformat, content_type
276 277
277 278 @LoginRequired()
278 279 @HasRepoPermissionAnyDecorator(
279 280 'repository.read', 'repository.write', 'repository.admin')
280 281 @view_config(
281 282 route_name='repo_archivefile', request_method='GET',
282 283 renderer=None)
283 284 def repo_archivefile(self):
284 285 # archive cache config
285 286 from rhodecode import CONFIG
286 287 _ = self.request.translate
287 288 self.load_default_context()
288 289
289 290 fname = self.request.matchdict['fname']
290 291 subrepos = self.request.GET.get('subrepos') == 'true'
291 292
292 293 if not self.db_repo.enable_downloads:
293 294 return Response(_('Downloads disabled'))
294 295
295 296 try:
296 297 commit_id, ext, fileformat, content_type = \
297 298 self._get_archive_spec(fname)
298 299 except ValueError:
299 300 return Response(_('Unknown archive type for: `{}`').format(
300 301 h.escape(fname)))
301 302
302 303 try:
303 304 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
304 305 except CommitDoesNotExistError:
305 306 return Response(_('Unknown commit_id {}').format(
306 307 h.escape(commit_id)))
307 308 except EmptyRepositoryError:
308 309 return Response(_('Empty repository'))
309 310
310 311 archive_name = '%s-%s%s%s' % (
311 312 safe_str(self.db_repo_name.replace('/', '_')),
312 313 '-sub' if subrepos else '',
313 314 safe_str(commit.short_id), ext)
314 315
315 316 use_cached_archive = False
316 317 archive_cache_enabled = CONFIG.get(
317 318 'archive_cache_dir') and not self.request.GET.get('no_cache')
318 319 cached_archive_path = None
319 320
320 321 if archive_cache_enabled:
321 322 # check if we it's ok to write
322 323 if not os.path.isdir(CONFIG['archive_cache_dir']):
323 324 os.makedirs(CONFIG['archive_cache_dir'])
324 325 cached_archive_path = os.path.join(
325 326 CONFIG['archive_cache_dir'], archive_name)
326 327 if os.path.isfile(cached_archive_path):
327 328 log.debug('Found cached archive in %s', cached_archive_path)
328 329 fd, archive = None, cached_archive_path
329 330 use_cached_archive = True
330 331 else:
331 332 log.debug('Archive %s is not yet cached', archive_name)
332 333
333 334 if not use_cached_archive:
334 335 # generate new archive
335 336 fd, archive = tempfile.mkstemp()
336 337 log.debug('Creating new temp archive in %s', archive)
337 338 try:
338 339 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
339 340 except ImproperArchiveTypeError:
340 341 return _('Unknown archive type')
341 342 if archive_cache_enabled:
342 343 # if we generated the archive and we have cache enabled
343 344 # let's use this for future
344 345 log.debug('Storing new archive in %s', cached_archive_path)
345 346 shutil.move(archive, cached_archive_path)
346 347 archive = cached_archive_path
347 348
348 349 # store download action
349 350 audit_logger.store_web(
350 351 'repo.archive.download', action_data={
351 352 'user_agent': self.request.user_agent,
352 353 'archive_name': archive_name,
353 354 'archive_spec': fname,
354 355 'archive_cached': use_cached_archive},
355 356 user=self._rhodecode_user,
356 357 repo=self.db_repo,
357 358 commit=True
358 359 )
359 360
360 361 def get_chunked_archive(archive_path):
361 362 with open(archive_path, 'rb') as stream:
362 363 while True:
363 364 data = stream.read(16 * 1024)
364 365 if not data:
365 366 if fd: # fd means we used temporary file
366 367 os.close(fd)
367 368 if not archive_cache_enabled:
368 369 log.debug('Destroying temp archive %s', archive_path)
369 370 os.remove(archive_path)
370 371 break
371 372 yield data
372 373
373 374 response = Response(app_iter=get_chunked_archive(archive))
374 375 response.content_disposition = str(
375 376 'attachment; filename=%s' % archive_name)
376 377 response.content_type = str(content_type)
377 378
378 379 return response
379 380
380 381 def _get_file_node(self, commit_id, f_path):
381 382 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
382 383 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
383 384 try:
384 385 node = commit.get_node(f_path)
385 386 if node.is_dir():
386 387 raise NodeError('%s path is a %s not a file'
387 388 % (node, type(node)))
388 389 except NodeDoesNotExistError:
389 390 commit = EmptyCommit(
390 391 commit_id=commit_id,
391 392 idx=commit.idx,
392 393 repo=commit.repository,
393 394 alias=commit.repository.alias,
394 395 message=commit.message,
395 396 author=commit.author,
396 397 date=commit.date)
397 398 node = FileNode(f_path, '', commit=commit)
398 399 else:
399 400 commit = EmptyCommit(
400 401 repo=self.rhodecode_vcs_repo,
401 402 alias=self.rhodecode_vcs_repo.alias)
402 403 node = FileNode(f_path, '', commit=commit)
403 404 return node
404 405
405 406 @LoginRequired()
406 407 @HasRepoPermissionAnyDecorator(
407 408 'repository.read', 'repository.write', 'repository.admin')
408 409 @view_config(
409 410 route_name='repo_files_diff', request_method='GET',
410 411 renderer=None)
411 412 def repo_files_diff(self):
412 413 c = self.load_default_context()
413 414 f_path = self._get_f_path(self.request.matchdict)
414 415 diff1 = self.request.GET.get('diff1', '')
415 416 diff2 = self.request.GET.get('diff2', '')
416 417
417 418 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
418 419
419 420 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
420 421 line_context = self.request.GET.get('context', 3)
421 422
422 423 if not any((diff1, diff2)):
423 424 h.flash(
424 425 'Need query parameter "diff1" or "diff2" to generate a diff.',
425 426 category='error')
426 427 raise HTTPBadRequest()
427 428
428 429 c.action = self.request.GET.get('diff')
429 430 if c.action not in ['download', 'raw']:
430 431 compare_url = h.route_path(
431 432 'repo_compare',
432 433 repo_name=self.db_repo_name,
433 434 source_ref_type='rev',
434 435 source_ref=diff1,
435 436 target_repo=self.db_repo_name,
436 437 target_ref_type='rev',
437 438 target_ref=diff2,
438 439 _query=dict(f_path=f_path))
439 440 # redirect to new view if we render diff
440 441 raise HTTPFound(compare_url)
441 442
442 443 try:
443 444 node1 = self._get_file_node(diff1, path1)
444 445 node2 = self._get_file_node(diff2, f_path)
445 446 except (RepositoryError, NodeError):
446 447 log.exception("Exception while trying to get node from repository")
447 448 raise HTTPFound(
448 449 h.route_path('repo_files', repo_name=self.db_repo_name,
449 450 commit_id='tip', f_path=f_path))
450 451
451 452 if all(isinstance(node.commit, EmptyCommit)
452 453 for node in (node1, node2)):
453 454 raise HTTPNotFound()
454 455
455 456 c.commit_1 = node1.commit
456 457 c.commit_2 = node2.commit
457 458
458 459 if c.action == 'download':
459 460 _diff = diffs.get_gitdiff(node1, node2,
460 461 ignore_whitespace=ignore_whitespace,
461 462 context=line_context)
462 463 diff = diffs.DiffProcessor(_diff, format='gitdiff')
463 464
464 465 response = Response(self.path_filter.get_raw_patch(diff))
465 466 response.content_type = 'text/plain'
466 467 response.content_disposition = (
467 468 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
468 469 )
469 470 charset = self._get_default_encoding(c)
470 471 if charset:
471 472 response.charset = charset
472 473 return response
473 474
474 475 elif c.action == 'raw':
475 476 _diff = diffs.get_gitdiff(node1, node2,
476 477 ignore_whitespace=ignore_whitespace,
477 478 context=line_context)
478 479 diff = diffs.DiffProcessor(_diff, format='gitdiff')
479 480
480 481 response = Response(self.path_filter.get_raw_patch(diff))
481 482 response.content_type = 'text/plain'
482 483 charset = self._get_default_encoding(c)
483 484 if charset:
484 485 response.charset = charset
485 486 return response
486 487
487 488 # in case we ever end up here
488 489 raise HTTPNotFound()
489 490
490 491 @LoginRequired()
491 492 @HasRepoPermissionAnyDecorator(
492 493 'repository.read', 'repository.write', 'repository.admin')
493 494 @view_config(
494 495 route_name='repo_files_diff_2way_redirect', request_method='GET',
495 496 renderer=None)
496 497 def repo_files_diff_2way_redirect(self):
497 498 """
498 499 Kept only to make OLD links work
499 500 """
500 501 f_path = self._get_f_path_unchecked(self.request.matchdict)
501 502 diff1 = self.request.GET.get('diff1', '')
502 503 diff2 = self.request.GET.get('diff2', '')
503 504
504 505 if not any((diff1, diff2)):
505 506 h.flash(
506 507 'Need query parameter "diff1" or "diff2" to generate a diff.',
507 508 category='error')
508 509 raise HTTPBadRequest()
509 510
510 511 compare_url = h.route_path(
511 512 'repo_compare',
512 513 repo_name=self.db_repo_name,
513 514 source_ref_type='rev',
514 515 source_ref=diff1,
515 516 target_ref_type='rev',
516 517 target_ref=diff2,
517 518 _query=dict(f_path=f_path, diffmode='sideside',
518 519 target_repo=self.db_repo_name,))
519 520 raise HTTPFound(compare_url)
520 521
521 522 @LoginRequired()
522 523 @HasRepoPermissionAnyDecorator(
523 524 'repository.read', 'repository.write', 'repository.admin')
524 525 @view_config(
525 526 route_name='repo_files', request_method='GET',
526 527 renderer=None)
527 528 @view_config(
528 529 route_name='repo_files:default_path', request_method='GET',
529 530 renderer=None)
530 531 @view_config(
531 532 route_name='repo_files:default_commit', request_method='GET',
532 533 renderer=None)
533 534 @view_config(
534 535 route_name='repo_files:rendered', request_method='GET',
535 536 renderer=None)
536 537 @view_config(
537 538 route_name='repo_files:annotated', request_method='GET',
538 539 renderer=None)
539 540 def repo_files(self):
540 541 c = self.load_default_context()
541 542
542 543 view_name = getattr(self.request.matched_route, 'name', None)
543 544
544 545 c.annotate = view_name == 'repo_files:annotated'
545 546 # default is false, but .rst/.md files later are auto rendered, we can
546 547 # overwrite auto rendering by setting this GET flag
547 548 c.renderer = view_name == 'repo_files:rendered' or \
548 549 not self.request.GET.get('no-render', False)
549 550
550 551 # redirect to given commit_id from form if given
551 552 get_commit_id = self.request.GET.get('at_rev', None)
552 553 if get_commit_id:
553 554 self._get_commit_or_redirect(get_commit_id)
554 555
555 556 commit_id, f_path = self._get_commit_and_path()
556 557 c.commit = self._get_commit_or_redirect(commit_id)
557 558 c.branch = self.request.GET.get('branch', None)
558 559 c.f_path = f_path
559 560
560 561 # prev link
561 562 try:
562 563 prev_commit = c.commit.prev(c.branch)
563 564 c.prev_commit = prev_commit
564 565 c.url_prev = h.route_path(
565 566 'repo_files', repo_name=self.db_repo_name,
566 567 commit_id=prev_commit.raw_id, f_path=f_path)
567 568 if c.branch:
568 569 c.url_prev += '?branch=%s' % c.branch
569 570 except (CommitDoesNotExistError, VCSError):
570 571 c.url_prev = '#'
571 572 c.prev_commit = EmptyCommit()
572 573
573 574 # next link
574 575 try:
575 576 next_commit = c.commit.next(c.branch)
576 577 c.next_commit = next_commit
577 578 c.url_next = h.route_path(
578 579 'repo_files', repo_name=self.db_repo_name,
579 580 commit_id=next_commit.raw_id, f_path=f_path)
580 581 if c.branch:
581 582 c.url_next += '?branch=%s' % c.branch
582 583 except (CommitDoesNotExistError, VCSError):
583 584 c.url_next = '#'
584 585 c.next_commit = EmptyCommit()
585 586
586 587 # files or dirs
587 588 try:
588 589 c.file = c.commit.get_node(f_path)
589 590 c.file_author = True
590 591 c.file_tree = ''
591 592
592 593 # load file content
593 594 if c.file.is_file():
594 595 c.lf_node = c.file.get_largefile_node()
595 596
596 597 c.file_source_page = 'true'
597 598 c.file_last_commit = c.file.last_commit
598 599 if c.file.size < c.visual.cut_off_limit_diff:
599 600 if c.annotate: # annotation has precedence over renderer
600 601 c.annotated_lines = filenode_as_annotated_lines_tokens(
601 602 c.file
602 603 )
603 604 else:
604 605 c.renderer = (
605 606 c.renderer and h.renderer_from_filename(c.file.path)
606 607 )
607 608 if not c.renderer:
608 609 c.lines = filenode_as_lines_tokens(c.file)
609 610
610 611 _branch_name, _sha_commit_id, is_head = self._is_valid_head(
611 612 commit_id, self.rhodecode_vcs_repo)
612 613 c.on_branch_head = is_head
613 614
614 615 branch = c.commit.branch if (
615 616 c.commit.branch and '/' not in c.commit.branch) else None
616 617 c.branch_or_raw_id = branch or c.commit.raw_id
617 618 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
618 619
619 620 author = c.file_last_commit.author
620 621 c.authors = [[
621 622 h.email(author),
622 623 h.person(author, 'username_or_name_or_email'),
623 624 1
624 625 ]]
625 626
626 627 else: # load tree content at path
627 628 c.file_source_page = 'false'
628 629 c.authors = []
629 630 # this loads a simple tree without metadata to speed things up
630 631 # later via ajax we call repo_nodetree_full and fetch whole
631 632 c.file_tree = self._get_tree_at_commit(
632 633 c, c.commit.raw_id, f_path)
633 634
634 635 except RepositoryError as e:
635 636 h.flash(safe_str(h.escape(e)), category='error')
636 637 raise HTTPNotFound()
637 638
638 639 if self.request.environ.get('HTTP_X_PJAX'):
639 640 html = render('rhodecode:templates/files/files_pjax.mako',
640 641 self._get_template_context(c), self.request)
641 642 else:
642 643 html = render('rhodecode:templates/files/files.mako',
643 644 self._get_template_context(c), self.request)
644 645 return Response(html)
645 646
646 647 @HasRepoPermissionAnyDecorator(
647 648 'repository.read', 'repository.write', 'repository.admin')
648 649 @view_config(
649 650 route_name='repo_files:annotated_previous', request_method='GET',
650 651 renderer=None)
651 652 def repo_files_annotated_previous(self):
652 653 self.load_default_context()
653 654
654 655 commit_id, f_path = self._get_commit_and_path()
655 656 commit = self._get_commit_or_redirect(commit_id)
656 657 prev_commit_id = commit.raw_id
657 658 line_anchor = self.request.GET.get('line_anchor')
658 659 is_file = False
659 660 try:
660 661 _file = commit.get_node(f_path)
661 662 is_file = _file.is_file()
662 663 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
663 664 pass
664 665
665 666 if is_file:
666 667 history = commit.get_path_history(f_path)
667 668 prev_commit_id = history[1].raw_id \
668 669 if len(history) > 1 else prev_commit_id
669 670 prev_url = h.route_path(
670 671 'repo_files:annotated', repo_name=self.db_repo_name,
671 672 commit_id=prev_commit_id, f_path=f_path,
672 673 _anchor='L{}'.format(line_anchor))
673 674
674 675 raise HTTPFound(prev_url)
675 676
676 677 @LoginRequired()
677 678 @HasRepoPermissionAnyDecorator(
678 679 'repository.read', 'repository.write', 'repository.admin')
679 680 @view_config(
680 681 route_name='repo_nodetree_full', request_method='GET',
681 682 renderer=None, xhr=True)
682 683 @view_config(
683 684 route_name='repo_nodetree_full:default_path', request_method='GET',
684 685 renderer=None, xhr=True)
685 686 def repo_nodetree_full(self):
686 687 """
687 688 Returns rendered html of file tree that contains commit date,
688 689 author, commit_id for the specified combination of
689 690 repo, commit_id and file path
690 691 """
691 692 c = self.load_default_context()
692 693
693 694 commit_id, f_path = self._get_commit_and_path()
694 695 commit = self._get_commit_or_redirect(commit_id)
695 696 try:
696 697 dir_node = commit.get_node(f_path)
697 698 except RepositoryError as e:
698 699 return Response('error: {}'.format(h.escape(safe_str(e))))
699 700
700 701 if dir_node.is_file():
701 702 return Response('')
702 703
703 704 c.file = dir_node
704 705 c.commit = commit
705 706
706 707 html = self._get_tree_at_commit(
707 708 c, commit.raw_id, dir_node.path, full_load=True)
708 709
709 710 return Response(html)
710 711
711 712 def _get_attachement_disposition(self, f_path):
712 713 return 'attachment; filename=%s' % \
713 714 safe_str(f_path.split(Repository.NAME_SEP)[-1])
714 715
715 716 @LoginRequired()
716 717 @HasRepoPermissionAnyDecorator(
717 718 'repository.read', 'repository.write', 'repository.admin')
718 719 @view_config(
719 720 route_name='repo_file_raw', request_method='GET',
720 721 renderer=None)
721 722 def repo_file_raw(self):
722 723 """
723 724 Action for show as raw, some mimetypes are "rendered",
724 725 those include images, icons.
725 726 """
726 727 c = self.load_default_context()
727 728
728 729 commit_id, f_path = self._get_commit_and_path()
729 730 commit = self._get_commit_or_redirect(commit_id)
730 731 file_node = self._get_filenode_or_redirect(commit, f_path)
731 732
732 733 raw_mimetype_mapping = {
733 734 # map original mimetype to a mimetype used for "show as raw"
734 735 # you can also provide a content-disposition to override the
735 736 # default "attachment" disposition.
736 737 # orig_type: (new_type, new_dispo)
737 738
738 739 # show images inline:
739 740 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
740 741 # for example render an SVG with javascript inside or even render
741 742 # HTML.
742 743 'image/x-icon': ('image/x-icon', 'inline'),
743 744 'image/png': ('image/png', 'inline'),
744 745 'image/gif': ('image/gif', 'inline'),
745 746 'image/jpeg': ('image/jpeg', 'inline'),
746 747 'application/pdf': ('application/pdf', 'inline'),
747 748 }
748 749
749 750 mimetype = file_node.mimetype
750 751 try:
751 752 mimetype, disposition = raw_mimetype_mapping[mimetype]
752 753 except KeyError:
753 754 # we don't know anything special about this, handle it safely
754 755 if file_node.is_binary:
755 756 # do same as download raw for binary files
756 757 mimetype, disposition = 'application/octet-stream', 'attachment'
757 758 else:
758 759 # do not just use the original mimetype, but force text/plain,
759 760 # otherwise it would serve text/html and that might be unsafe.
760 761 # Note: underlying vcs library fakes text/plain mimetype if the
761 762 # mimetype can not be determined and it thinks it is not
762 763 # binary.This might lead to erroneous text display in some
763 764 # cases, but helps in other cases, like with text files
764 765 # without extension.
765 766 mimetype, disposition = 'text/plain', 'inline'
766 767
767 768 if disposition == 'attachment':
768 769 disposition = self._get_attachement_disposition(f_path)
769 770
770 771 def stream_node():
771 772 yield file_node.raw_bytes
772 773
773 774 response = Response(app_iter=stream_node())
774 775 response.content_disposition = disposition
775 776 response.content_type = mimetype
776 777
777 778 charset = self._get_default_encoding(c)
778 779 if charset:
779 780 response.charset = charset
780 781
781 782 return response
782 783
783 784 @LoginRequired()
784 785 @HasRepoPermissionAnyDecorator(
785 786 'repository.read', 'repository.write', 'repository.admin')
786 787 @view_config(
787 788 route_name='repo_file_download', request_method='GET',
788 789 renderer=None)
789 790 @view_config(
790 791 route_name='repo_file_download:legacy', request_method='GET',
791 792 renderer=None)
792 793 def repo_file_download(self):
793 794 c = self.load_default_context()
794 795
795 796 commit_id, f_path = self._get_commit_and_path()
796 797 commit = self._get_commit_or_redirect(commit_id)
797 798 file_node = self._get_filenode_or_redirect(commit, f_path)
798 799
799 800 if self.request.GET.get('lf'):
800 801 # only if lf get flag is passed, we download this file
801 802 # as LFS/Largefile
802 803 lf_node = file_node.get_largefile_node()
803 804 if lf_node:
804 805 # overwrite our pointer with the REAL large-file
805 806 file_node = lf_node
806 807
807 808 disposition = self._get_attachement_disposition(f_path)
808 809
809 810 def stream_node():
810 811 yield file_node.raw_bytes
811 812
812 813 response = Response(app_iter=stream_node())
813 814 response.content_disposition = disposition
814 815 response.content_type = file_node.mimetype
815 816
816 817 charset = self._get_default_encoding(c)
817 818 if charset:
818 819 response.charset = charset
819 820
820 821 return response
821 822
822 823 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
823 824
824 825 cache_seconds = safe_int(
825 826 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
826 827 cache_on = cache_seconds > 0
827 828 log.debug(
828 829 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
829 830 'with caching: %s[TTL: %ss]' % (
830 831 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
831 832
832 833 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
833 834 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
834 835
835 836 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
836 837 condition=cache_on)
837 838 def compute_file_search(repo_id, commit_id, f_path):
838 839 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
839 840 repo_id, commit_id, f_path)
840 841 try:
841 842 _d, _f = ScmModel().get_nodes(
842 843 repo_name, commit_id, f_path, flat=False)
843 844 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
844 845 log.exception(safe_str(e))
845 846 h.flash(safe_str(h.escape(e)), category='error')
846 847 raise HTTPFound(h.route_path(
847 848 'repo_files', repo_name=self.db_repo_name,
848 849 commit_id='tip', f_path='/'))
849 850 return _d + _f
850 851
851 852 return compute_file_search(self.db_repo.repo_id, commit_id, f_path)
852 853
853 854 @LoginRequired()
854 855 @HasRepoPermissionAnyDecorator(
855 856 'repository.read', 'repository.write', 'repository.admin')
856 857 @view_config(
857 858 route_name='repo_files_nodelist', request_method='GET',
858 859 renderer='json_ext', xhr=True)
859 860 def repo_nodelist(self):
860 861 self.load_default_context()
861 862
862 863 commit_id, f_path = self._get_commit_and_path()
863 864 commit = self._get_commit_or_redirect(commit_id)
864 865
865 866 metadata = self._get_nodelist_at_commit(
866 867 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
867 868 return {'nodes': metadata}
868 869
869 870 def _create_references(
870 871 self, branches_or_tags, symbolic_reference, f_path):
871 872 items = []
872 873 for name, commit_id in branches_or_tags.items():
873 874 sym_ref = symbolic_reference(commit_id, name, f_path)
874 875 items.append((sym_ref, name))
875 876 return items
876 877
877 878 def _symbolic_reference(self, commit_id, name, f_path):
878 879 return commit_id
879 880
880 881 def _symbolic_reference_svn(self, commit_id, name, f_path):
881 882 new_f_path = vcspath.join(name, f_path)
882 883 return u'%s@%s' % (new_f_path, commit_id)
883 884
884 885 def _get_node_history(self, commit_obj, f_path, commits=None):
885 886 """
886 887 get commit history for given node
887 888
888 889 :param commit_obj: commit to calculate history
889 890 :param f_path: path for node to calculate history for
890 891 :param commits: if passed don't calculate history and take
891 892 commits defined in this list
892 893 """
893 894 _ = self.request.translate
894 895
895 896 # calculate history based on tip
896 897 tip = self.rhodecode_vcs_repo.get_commit()
897 898 if commits is None:
898 899 pre_load = ["author", "branch"]
899 900 try:
900 901 commits = tip.get_path_history(f_path, pre_load=pre_load)
901 902 except (NodeDoesNotExistError, CommitError):
902 903 # this node is not present at tip!
903 904 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
904 905
905 906 history = []
906 907 commits_group = ([], _("Changesets"))
907 908 for commit in commits:
908 909 branch = ' (%s)' % commit.branch if commit.branch else ''
909 910 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
910 911 commits_group[0].append((commit.raw_id, n_desc,))
911 912 history.append(commits_group)
912 913
913 914 symbolic_reference = self._symbolic_reference
914 915
915 916 if self.rhodecode_vcs_repo.alias == 'svn':
916 917 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
917 918 f_path, self.rhodecode_vcs_repo)
918 919 if adjusted_f_path != f_path:
919 920 log.debug(
920 921 'Recognized svn tag or branch in file "%s", using svn '
921 922 'specific symbolic references', f_path)
922 923 f_path = adjusted_f_path
923 924 symbolic_reference = self._symbolic_reference_svn
924 925
925 926 branches = self._create_references(
926 927 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path)
927 928 branches_group = (branches, _("Branches"))
928 929
929 930 tags = self._create_references(
930 931 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path)
931 932 tags_group = (tags, _("Tags"))
932 933
933 934 history.append(branches_group)
934 935 history.append(tags_group)
935 936
936 937 return history, commits
937 938
938 939 @LoginRequired()
939 940 @HasRepoPermissionAnyDecorator(
940 941 'repository.read', 'repository.write', 'repository.admin')
941 942 @view_config(
942 943 route_name='repo_file_history', request_method='GET',
943 944 renderer='json_ext')
944 945 def repo_file_history(self):
945 946 self.load_default_context()
946 947
947 948 commit_id, f_path = self._get_commit_and_path()
948 949 commit = self._get_commit_or_redirect(commit_id)
949 950 file_node = self._get_filenode_or_redirect(commit, f_path)
950 951
951 952 if file_node.is_file():
952 953 file_history, _hist = self._get_node_history(commit, f_path)
953 954
954 955 res = []
955 956 for obj in file_history:
956 957 res.append({
957 958 'text': obj[1],
958 959 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
959 960 })
960 961
961 962 data = {
962 963 'more': False,
963 964 'results': res
964 965 }
965 966 return data
966 967
967 968 log.warning('Cannot fetch history for directory')
968 969 raise HTTPBadRequest()
969 970
970 971 @LoginRequired()
971 972 @HasRepoPermissionAnyDecorator(
972 973 'repository.read', 'repository.write', 'repository.admin')
973 974 @view_config(
974 975 route_name='repo_file_authors', request_method='GET',
975 976 renderer='rhodecode:templates/files/file_authors_box.mako')
976 977 def repo_file_authors(self):
977 978 c = self.load_default_context()
978 979
979 980 commit_id, f_path = self._get_commit_and_path()
980 981 commit = self._get_commit_or_redirect(commit_id)
981 982 file_node = self._get_filenode_or_redirect(commit, f_path)
982 983
983 984 if not file_node.is_file():
984 985 raise HTTPBadRequest()
985 986
986 987 c.file_last_commit = file_node.last_commit
987 988 if self.request.GET.get('annotate') == '1':
988 989 # use _hist from annotation if annotation mode is on
989 990 commit_ids = set(x[1] for x in file_node.annotate)
990 991 _hist = (
991 992 self.rhodecode_vcs_repo.get_commit(commit_id)
992 993 for commit_id in commit_ids)
993 994 else:
994 995 _f_history, _hist = self._get_node_history(commit, f_path)
995 996 c.file_author = False
996 997
997 998 unique = collections.OrderedDict()
998 999 for commit in _hist:
999 1000 author = commit.author
1000 1001 if author not in unique:
1001 1002 unique[commit.author] = [
1002 1003 h.email(author),
1003 1004 h.person(author, 'username_or_name_or_email'),
1004 1005 1 # counter
1005 1006 ]
1006 1007
1007 1008 else:
1008 1009 # increase counter
1009 1010 unique[commit.author][2] += 1
1010 1011
1011 1012 c.authors = [val for val in unique.values()]
1012 1013
1013 1014 return self._get_template_context(c)
1014 1015
1015 1016 @LoginRequired()
1016 1017 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1017 1018 @view_config(
1018 1019 route_name='repo_files_remove_file', request_method='GET',
1019 1020 renderer='rhodecode:templates/files/files_delete.mako')
1020 1021 def repo_files_remove_file(self):
1021 1022 _ = self.request.translate
1022 1023 c = self.load_default_context()
1023 1024 commit_id, f_path = self._get_commit_and_path()
1024 1025
1025 1026 self._ensure_not_locked()
1026 1027 _branch_name, _sha_commit_id, is_head = \
1027 1028 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1028 1029
1029 1030 if not is_head:
1030 1031 h.flash(_('You can only delete files with commit '
1031 1032 'being a valid branch head.'), category='warning')
1032 1033 raise HTTPFound(
1033 1034 h.route_path('repo_files',
1034 1035 repo_name=self.db_repo_name, commit_id='tip',
1035 1036 f_path=f_path))
1036 1037
1037 1038 self.check_branch_permission(_branch_name)
1038 1039 c.commit = self._get_commit_or_redirect(commit_id)
1039 1040 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1040 1041
1041 1042 c.default_message = _(
1042 1043 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1043 1044 c.f_path = f_path
1044 1045
1045 1046 return self._get_template_context(c)
1046 1047
1047 1048 @LoginRequired()
1048 1049 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1049 1050 @CSRFRequired()
1050 1051 @view_config(
1051 1052 route_name='repo_files_delete_file', request_method='POST',
1052 1053 renderer=None)
1053 1054 def repo_files_delete_file(self):
1054 1055 _ = self.request.translate
1055 1056
1056 1057 c = self.load_default_context()
1057 1058 commit_id, f_path = self._get_commit_and_path()
1058 1059
1059 1060 self._ensure_not_locked()
1060 1061 _branch_name, _sha_commit_id, is_head = \
1061 1062 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1062 1063
1063 1064 if not is_head:
1064 1065 h.flash(_('You can only delete files with commit '
1065 1066 'being a valid branch head.'), category='warning')
1066 1067 raise HTTPFound(
1067 1068 h.route_path('repo_files',
1068 1069 repo_name=self.db_repo_name, commit_id='tip',
1069 1070 f_path=f_path))
1070 1071 self.check_branch_permission(_branch_name)
1071 1072
1072 1073 c.commit = self._get_commit_or_redirect(commit_id)
1073 1074 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1074 1075
1075 1076 c.default_message = _(
1076 1077 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1077 1078 c.f_path = f_path
1078 1079 node_path = f_path
1079 1080 author = self._rhodecode_db_user.full_contact
1080 1081 message = self.request.POST.get('message') or c.default_message
1081 1082 try:
1082 1083 nodes = {
1083 1084 node_path: {
1084 1085 'content': ''
1085 1086 }
1086 1087 }
1087 1088 ScmModel().delete_nodes(
1088 1089 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1089 1090 message=message,
1090 1091 nodes=nodes,
1091 1092 parent_commit=c.commit,
1092 1093 author=author,
1093 1094 )
1094 1095
1095 1096 h.flash(
1096 1097 _('Successfully deleted file `{}`').format(
1097 1098 h.escape(f_path)), category='success')
1098 1099 except Exception:
1099 1100 log.exception('Error during commit operation')
1100 1101 h.flash(_('Error occurred during commit'), category='error')
1101 1102 raise HTTPFound(
1102 1103 h.route_path('repo_commit', repo_name=self.db_repo_name,
1103 1104 commit_id='tip'))
1104 1105
1105 1106 @LoginRequired()
1106 1107 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1107 1108 @view_config(
1108 1109 route_name='repo_files_edit_file', request_method='GET',
1109 1110 renderer='rhodecode:templates/files/files_edit.mako')
1110 1111 def repo_files_edit_file(self):
1111 1112 _ = self.request.translate
1112 1113 c = self.load_default_context()
1113 1114 commit_id, f_path = self._get_commit_and_path()
1114 1115
1115 1116 self._ensure_not_locked()
1116 1117 _branch_name, _sha_commit_id, is_head = \
1117 1118 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1118 1119
1119 1120 if not is_head:
1120 1121 h.flash(_('You can only edit files with commit '
1121 1122 'being a valid branch head.'), category='warning')
1122 1123 raise HTTPFound(
1123 1124 h.route_path('repo_files',
1124 1125 repo_name=self.db_repo_name, commit_id='tip',
1125 1126 f_path=f_path))
1126 1127 self.check_branch_permission(_branch_name)
1127 1128
1128 1129 c.commit = self._get_commit_or_redirect(commit_id)
1129 1130 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1130 1131
1131 1132 if c.file.is_binary:
1132 1133 files_url = h.route_path(
1133 1134 'repo_files',
1134 1135 repo_name=self.db_repo_name,
1135 1136 commit_id=c.commit.raw_id, f_path=f_path)
1136 1137 raise HTTPFound(files_url)
1137 1138
1138 1139 c.default_message = _(
1139 1140 'Edited file {} via RhodeCode Enterprise').format(f_path)
1140 1141 c.f_path = f_path
1141 1142
1142 1143 return self._get_template_context(c)
1143 1144
1144 1145 @LoginRequired()
1145 1146 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1146 1147 @CSRFRequired()
1147 1148 @view_config(
1148 1149 route_name='repo_files_update_file', request_method='POST',
1149 1150 renderer=None)
1150 1151 def repo_files_update_file(self):
1151 1152 _ = self.request.translate
1152 1153 c = self.load_default_context()
1153 1154 commit_id, f_path = self._get_commit_and_path()
1154 1155
1155 1156 self._ensure_not_locked()
1156 1157 _branch_name, _sha_commit_id, is_head = \
1157 1158 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1158 1159
1159 1160 if not is_head:
1160 1161 h.flash(_('You can only edit files with commit '
1161 1162 'being a valid branch head.'), category='warning')
1162 1163 raise HTTPFound(
1163 1164 h.route_path('repo_files',
1164 1165 repo_name=self.db_repo_name, commit_id='tip',
1165 1166 f_path=f_path))
1166 1167
1167 1168 self.check_branch_permission(_branch_name)
1168 1169
1169 1170 c.commit = self._get_commit_or_redirect(commit_id)
1170 1171 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1171 1172
1172 1173 if c.file.is_binary:
1173 1174 raise HTTPFound(
1174 1175 h.route_path('repo_files',
1175 1176 repo_name=self.db_repo_name,
1176 1177 commit_id=c.commit.raw_id,
1177 1178 f_path=f_path))
1178 1179
1179 1180 c.default_message = _(
1180 1181 'Edited file {} via RhodeCode Enterprise').format(f_path)
1181 1182 c.f_path = f_path
1182 1183 old_content = c.file.content
1183 1184 sl = old_content.splitlines(1)
1184 1185 first_line = sl[0] if sl else ''
1185 1186
1186 1187 r_post = self.request.POST
1187 1188 # modes: 0 - Unix, 1 - Mac, 2 - DOS
1188 1189 mode = detect_mode(first_line, 0)
1189 1190 content = convert_line_endings(r_post.get('content', ''), mode)
1190 1191
1191 1192 message = r_post.get('message') or c.default_message
1192 1193 org_f_path = c.file.unicode_path
1193 1194 filename = r_post['filename']
1194 1195 org_filename = c.file.name
1195 1196
1196 1197 if content == old_content and filename == org_filename:
1197 1198 h.flash(_('No changes'), category='warning')
1198 1199 raise HTTPFound(
1199 1200 h.route_path('repo_commit', repo_name=self.db_repo_name,
1200 1201 commit_id='tip'))
1201 1202 try:
1202 1203 mapping = {
1203 1204 org_f_path: {
1204 1205 'org_filename': org_f_path,
1205 1206 'filename': os.path.join(c.file.dir_path, filename),
1206 1207 'content': content,
1207 1208 'lexer': '',
1208 1209 'op': 'mod',
1209 1210 }
1210 1211 }
1211 1212
1212 1213 ScmModel().update_nodes(
1213 1214 user=self._rhodecode_db_user.user_id,
1214 1215 repo=self.db_repo,
1215 1216 message=message,
1216 1217 nodes=mapping,
1217 1218 parent_commit=c.commit,
1218 1219 )
1219 1220
1220 1221 h.flash(
1221 1222 _('Successfully committed changes to file `{}`').format(
1222 1223 h.escape(f_path)), category='success')
1223 1224 except Exception:
1224 1225 log.exception('Error occurred during commit')
1225 1226 h.flash(_('Error occurred during commit'), category='error')
1226 1227 raise HTTPFound(
1227 1228 h.route_path('repo_commit', repo_name=self.db_repo_name,
1228 1229 commit_id='tip'))
1229 1230
1230 1231 @LoginRequired()
1231 1232 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1232 1233 @view_config(
1233 1234 route_name='repo_files_add_file', request_method='GET',
1234 1235 renderer='rhodecode:templates/files/files_add.mako')
1235 1236 def repo_files_add_file(self):
1236 1237 _ = self.request.translate
1237 1238 c = self.load_default_context()
1238 1239 commit_id, f_path = self._get_commit_and_path()
1239 1240
1240 1241 self._ensure_not_locked()
1241 1242
1242 1243 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1243 1244 if c.commit is None:
1244 1245 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1245 1246 c.default_message = (_('Added file via RhodeCode Enterprise'))
1246 1247 c.f_path = f_path.lstrip('/') # ensure not relative path
1247 1248
1248 1249 if self.rhodecode_vcs_repo.is_empty:
1249 1250 # for empty repository we cannot check for current branch, we rely on
1250 1251 # c.commit.branch instead
1251 1252 _branch_name = c.commit.branch
1252 1253 is_head = True
1253 1254 else:
1254 1255 _branch_name, _sha_commit_id, is_head = \
1255 1256 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1256 1257
1257 1258 if not is_head:
1258 1259 h.flash(_('You can only add files with commit '
1259 1260 'being a valid branch head.'), category='warning')
1260 1261 raise HTTPFound(
1261 1262 h.route_path('repo_files',
1262 1263 repo_name=self.db_repo_name, commit_id='tip',
1263 1264 f_path=f_path))
1264 1265
1265 1266 self.check_branch_permission(_branch_name)
1266 1267
1267 1268 return self._get_template_context(c)
1268 1269
1269 1270 @LoginRequired()
1270 1271 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1271 1272 @CSRFRequired()
1272 1273 @view_config(
1273 1274 route_name='repo_files_create_file', request_method='POST',
1274 1275 renderer=None)
1275 1276 def repo_files_create_file(self):
1276 1277 _ = self.request.translate
1277 1278 c = self.load_default_context()
1278 1279 commit_id, f_path = self._get_commit_and_path()
1279 1280
1280 1281 self._ensure_not_locked()
1281 1282
1282 1283 r_post = self.request.POST
1283 1284
1284 1285 c.commit = self._get_commit_or_redirect(
1285 1286 commit_id, redirect_after=False)
1286 1287 if c.commit is None:
1287 1288 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1288 1289
1289 1290 if self.rhodecode_vcs_repo.is_empty:
1290 1291 # for empty repository we cannot check for current branch, we rely on
1291 1292 # c.commit.branch instead
1292 1293 _branch_name = c.commit.branch
1293 1294 is_head = True
1294 1295 else:
1295 1296 _branch_name, _sha_commit_id, is_head = \
1296 1297 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1297 1298
1298 1299 if not is_head:
1299 1300 h.flash(_('You can only add files with commit '
1300 1301 'being a valid branch head.'), category='warning')
1301 1302 raise HTTPFound(
1302 1303 h.route_path('repo_files',
1303 1304 repo_name=self.db_repo_name, commit_id='tip',
1304 1305 f_path=f_path))
1305 1306
1306 1307 self.check_branch_permission(_branch_name)
1307 1308
1308 1309 c.default_message = (_('Added file via RhodeCode Enterprise'))
1309 1310 c.f_path = f_path
1310 1311 unix_mode = 0
1311 1312 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1312 1313
1313 1314 message = r_post.get('message') or c.default_message
1314 1315 filename = r_post.get('filename')
1315 1316 location = r_post.get('location', '') # dir location
1316 1317 file_obj = r_post.get('upload_file', None)
1317 1318
1318 1319 if file_obj is not None and hasattr(file_obj, 'filename'):
1319 1320 filename = r_post.get('filename_upload')
1320 1321 content = file_obj.file
1321 1322
1322 1323 if hasattr(content, 'file'):
1323 1324 # non posix systems store real file under file attr
1324 1325 content = content.file
1325 1326
1326 1327 if self.rhodecode_vcs_repo.is_empty:
1327 1328 default_redirect_url = h.route_path(
1328 1329 'repo_summary', repo_name=self.db_repo_name)
1329 1330 else:
1330 1331 default_redirect_url = h.route_path(
1331 1332 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1332 1333
1333 1334 # If there's no commit, redirect to repo summary
1334 1335 if type(c.commit) is EmptyCommit:
1335 1336 redirect_url = h.route_path(
1336 1337 'repo_summary', repo_name=self.db_repo_name)
1337 1338 else:
1338 1339 redirect_url = default_redirect_url
1339 1340
1340 1341 if not filename:
1341 1342 h.flash(_('No filename'), category='warning')
1342 1343 raise HTTPFound(redirect_url)
1343 1344
1344 1345 # extract the location from filename,
1345 1346 # allows using foo/bar.txt syntax to create subdirectories
1346 1347 subdir_loc = filename.rsplit('/', 1)
1347 1348 if len(subdir_loc) == 2:
1348 1349 location = os.path.join(location, subdir_loc[0])
1349 1350
1350 1351 # strip all crap out of file, just leave the basename
1351 1352 filename = os.path.basename(filename)
1352 1353 node_path = os.path.join(location, filename)
1353 1354 author = self._rhodecode_db_user.full_contact
1354 1355
1355 1356 try:
1356 1357 nodes = {
1357 1358 node_path: {
1358 1359 'content': content
1359 1360 }
1360 1361 }
1361 1362 ScmModel().create_nodes(
1362 1363 user=self._rhodecode_db_user.user_id,
1363 1364 repo=self.db_repo,
1364 1365 message=message,
1365 1366 nodes=nodes,
1366 1367 parent_commit=c.commit,
1367 1368 author=author,
1368 1369 )
1369 1370
1370 1371 h.flash(
1371 1372 _('Successfully committed new file `{}`').format(
1372 1373 h.escape(node_path)), category='success')
1373 1374 except NonRelativePathError:
1374 1375 log.exception('Non Relative path found')
1375 1376 h.flash(_(
1376 1377 'The location specified must be a relative path and must not '
1377 1378 'contain .. in the path'), category='warning')
1378 1379 raise HTTPFound(default_redirect_url)
1379 1380 except (NodeError, NodeAlreadyExistsError) as e:
1380 1381 h.flash(_(h.escape(e)), category='error')
1381 1382 except Exception:
1382 1383 log.exception('Error occurred during commit')
1383 1384 h.flash(_('Error occurred during commit'), category='error')
1384 1385
1385 1386 raise HTTPFound(default_redirect_url)
@@ -1,390 +1,390 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import string
23 23 import rhodecode
24 24
25 25 from pyramid.view import view_config
26 26
27 from rhodecode.controllers import utils
27 from rhodecode.lib.view_utils import get_format_ref_id
28 28 from rhodecode.apps._base import RepoAppView
29 29 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
30 30 from rhodecode.lib import helpers as h, rc_cache
31 31 from rhodecode.lib.utils2 import safe_str, safe_int
32 32 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
33 33 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
34 34 from rhodecode.lib.ext_json import json
35 35 from rhodecode.lib.vcs.backends.base import EmptyCommit
36 36 from rhodecode.lib.vcs.exceptions import (
37 37 CommitError, EmptyRepositoryError, CommitDoesNotExistError)
38 38 from rhodecode.model.db import Statistics, CacheKey, User
39 39 from rhodecode.model.meta import Session
40 40 from rhodecode.model.repo import ReadmeFinder
41 41 from rhodecode.model.scm import ScmModel
42 42
43 43 log = logging.getLogger(__name__)
44 44
45 45
46 46 class RepoSummaryView(RepoAppView):
47 47
48 48 def load_default_context(self):
49 49 c = self._get_local_tmpl_context(include_app_defaults=True)
50 50 c.rhodecode_repo = None
51 51 if not c.repository_requirements_missing:
52 52 c.rhodecode_repo = self.rhodecode_vcs_repo
53 53 return c
54 54
55 55 def _get_readme_data(self, db_repo, renderer_type):
56 56
57 57 log.debug('Looking for README file')
58 58
59 59 cache_namespace_uid = 'cache_repo_instance.{}_{}'.format(
60 60 db_repo.repo_id, CacheKey.CACHE_TYPE_README)
61 61 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
62 62 repo_id=self.db_repo.repo_id)
63 63 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
64 64
65 65 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
66 66 def generate_repo_readme(repo_id, _repo_name, _renderer_type):
67 67 readme_data = None
68 68 readme_node = None
69 69 readme_filename = None
70 70 commit = self._get_landing_commit_or_none(db_repo)
71 71 if commit:
72 72 log.debug("Searching for a README file.")
73 73 readme_node = ReadmeFinder(_renderer_type).search(commit)
74 74 if readme_node:
75 75 relative_urls = {
76 76 'raw': h.route_path(
77 77 'repo_file_raw', repo_name=_repo_name,
78 78 commit_id=commit.raw_id, f_path=readme_node.path),
79 79 'standard': h.route_path(
80 80 'repo_files', repo_name=_repo_name,
81 81 commit_id=commit.raw_id, f_path=readme_node.path),
82 82 }
83 83 readme_data = self._render_readme_or_none(
84 84 commit, readme_node, relative_urls)
85 85 readme_filename = readme_node.path
86 86 return readme_data, readme_filename
87 87
88 88 inv_context_manager = rc_cache.InvalidationContext(
89 89 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace)
90 90 with inv_context_manager as invalidation_context:
91 91 args = (db_repo.repo_id, db_repo.repo_name, renderer_type,)
92 92 # re-compute and store cache if we get invalidate signal
93 93 if invalidation_context.should_invalidate():
94 94 instance = generate_repo_readme.refresh(*args)
95 95 else:
96 96 instance = generate_repo_readme(*args)
97 97
98 98 log.debug(
99 99 'Repo readme generated and computed in %.3fs',
100 100 inv_context_manager.compute_time)
101 101 return instance
102 102
103 103 def _get_landing_commit_or_none(self, db_repo):
104 104 log.debug("Getting the landing commit.")
105 105 try:
106 106 commit = db_repo.get_landing_commit()
107 107 if not isinstance(commit, EmptyCommit):
108 108 return commit
109 109 else:
110 110 log.debug("Repository is empty, no README to render.")
111 111 except CommitError:
112 112 log.exception(
113 113 "Problem getting commit when trying to render the README.")
114 114
115 115 def _render_readme_or_none(self, commit, readme_node, relative_urls):
116 116 log.debug(
117 117 'Found README file `%s` rendering...', readme_node.path)
118 118 renderer = MarkupRenderer()
119 119 try:
120 120 html_source = renderer.render(
121 121 readme_node.content, filename=readme_node.path)
122 122 if relative_urls:
123 123 return relative_links(html_source, relative_urls)
124 124 return html_source
125 125 except Exception:
126 126 log.exception(
127 127 "Exception while trying to render the README")
128 128
129 129 def _load_commits_context(self, c):
130 130 p = safe_int(self.request.GET.get('page'), 1)
131 131 size = safe_int(self.request.GET.get('size'), 10)
132 132
133 133 def url_generator(**kw):
134 134 query_params = {
135 135 'size': size
136 136 }
137 137 query_params.update(kw)
138 138 return h.route_path(
139 139 'repo_summary_commits',
140 140 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
141 141
142 142 pre_load = ['author', 'branch', 'date', 'message']
143 143 try:
144 144 collection = self.rhodecode_vcs_repo.get_commits(pre_load=pre_load)
145 145 except EmptyRepositoryError:
146 146 collection = self.rhodecode_vcs_repo
147 147
148 148 c.repo_commits = h.RepoPage(
149 149 collection, page=p, items_per_page=size, url=url_generator)
150 150 page_ids = [x.raw_id for x in c.repo_commits]
151 151 c.comments = self.db_repo.get_comments(page_ids)
152 152 c.statuses = self.db_repo.statuses(page_ids)
153 153
154 154 @LoginRequired()
155 155 @HasRepoPermissionAnyDecorator(
156 156 'repository.read', 'repository.write', 'repository.admin')
157 157 @view_config(
158 158 route_name='repo_summary_commits', request_method='GET',
159 159 renderer='rhodecode:templates/summary/summary_commits.mako')
160 160 def summary_commits(self):
161 161 c = self.load_default_context()
162 162 self._load_commits_context(c)
163 163 return self._get_template_context(c)
164 164
165 165 @LoginRequired()
166 166 @HasRepoPermissionAnyDecorator(
167 167 'repository.read', 'repository.write', 'repository.admin')
168 168 @view_config(
169 169 route_name='repo_summary', request_method='GET',
170 170 renderer='rhodecode:templates/summary/summary.mako')
171 171 @view_config(
172 172 route_name='repo_summary_slash', request_method='GET',
173 173 renderer='rhodecode:templates/summary/summary.mako')
174 174 @view_config(
175 175 route_name='repo_summary_explicit', request_method='GET',
176 176 renderer='rhodecode:templates/summary/summary.mako')
177 177 def summary(self):
178 178 c = self.load_default_context()
179 179
180 180 # Prepare the clone URL
181 181 username = ''
182 182 if self._rhodecode_user.username != User.DEFAULT_USER:
183 183 username = safe_str(self._rhodecode_user.username)
184 184
185 185 _def_clone_uri = _def_clone_uri_id = c.clone_uri_tmpl
186 186 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
187 187
188 188 if '{repo}' in _def_clone_uri:
189 189 _def_clone_uri_id = _def_clone_uri.replace(
190 190 '{repo}', '_{repoid}')
191 191 elif '{repoid}' in _def_clone_uri:
192 192 _def_clone_uri_id = _def_clone_uri.replace(
193 193 '_{repoid}', '{repo}')
194 194
195 195 c.clone_repo_url = self.db_repo.clone_url(
196 196 user=username, uri_tmpl=_def_clone_uri)
197 197 c.clone_repo_url_id = self.db_repo.clone_url(
198 198 user=username, uri_tmpl=_def_clone_uri_id)
199 199 c.clone_repo_url_ssh = self.db_repo.clone_url(
200 200 uri_tmpl=_def_clone_uri_ssh, ssh=True)
201 201
202 202 # If enabled, get statistics data
203 203
204 204 c.show_stats = bool(self.db_repo.enable_statistics)
205 205
206 206 stats = Session().query(Statistics) \
207 207 .filter(Statistics.repository == self.db_repo) \
208 208 .scalar()
209 209
210 210 c.stats_percentage = 0
211 211
212 212 if stats and stats.languages:
213 213 c.no_data = False is self.db_repo.enable_statistics
214 214 lang_stats_d = json.loads(stats.languages)
215 215
216 216 # Sort first by decreasing count and second by the file extension,
217 217 # so we have a consistent output.
218 218 lang_stats_items = sorted(lang_stats_d.iteritems(),
219 219 key=lambda k: (-k[1], k[0]))[:10]
220 220 lang_stats = [(x, {"count": y,
221 221 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
222 222 for x, y in lang_stats_items]
223 223
224 224 c.trending_languages = json.dumps(lang_stats)
225 225 else:
226 226 c.no_data = True
227 227 c.trending_languages = json.dumps({})
228 228
229 229 scm_model = ScmModel()
230 230 c.enable_downloads = self.db_repo.enable_downloads
231 231 c.repository_followers = scm_model.get_followers(self.db_repo)
232 232 c.repository_forks = scm_model.get_forks(self.db_repo)
233 233 c.repository_is_user_following = scm_model.is_following_repo(
234 234 self.db_repo_name, self._rhodecode_user.user_id)
235 235
236 236 # first interaction with the VCS instance after here...
237 237 if c.repository_requirements_missing:
238 238 self.request.override_renderer = \
239 239 'rhodecode:templates/summary/missing_requirements.mako'
240 240 return self._get_template_context(c)
241 241
242 242 c.readme_data, c.readme_file = \
243 243 self._get_readme_data(self.db_repo, c.visual.default_renderer)
244 244
245 245 # loads the summary commits template context
246 246 self._load_commits_context(c)
247 247
248 248 return self._get_template_context(c)
249 249
250 250 def get_request_commit_id(self):
251 251 return self.request.matchdict['commit_id']
252 252
253 253 @LoginRequired()
254 254 @HasRepoPermissionAnyDecorator(
255 255 'repository.read', 'repository.write', 'repository.admin')
256 256 @view_config(
257 257 route_name='repo_stats', request_method='GET',
258 258 renderer='json_ext')
259 259 def repo_stats(self):
260 260 commit_id = self.get_request_commit_id()
261 261 show_stats = bool(self.db_repo.enable_statistics)
262 262 repo_id = self.db_repo.repo_id
263 263
264 264 cache_seconds = safe_int(
265 265 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
266 266 cache_on = cache_seconds > 0
267 267 log.debug(
268 268 'Computing REPO TREE for repo_id %s commit_id `%s` '
269 269 'with caching: %s[TTL: %ss]' % (
270 270 repo_id, commit_id, cache_on, cache_seconds or 0))
271 271
272 272 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
273 273 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
274 274
275 275 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
276 276 condition=cache_on)
277 277 def compute_stats(repo_id, commit_id, show_stats):
278 278 code_stats = {}
279 279 size = 0
280 280 try:
281 281 scm_instance = self.db_repo.scm_instance()
282 282 commit = scm_instance.get_commit(commit_id)
283 283
284 284 for node in commit.get_filenodes_generator():
285 285 size += node.size
286 286 if not show_stats:
287 287 continue
288 288 ext = string.lower(node.extension)
289 289 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
290 290 if ext_info:
291 291 if ext in code_stats:
292 292 code_stats[ext]['count'] += 1
293 293 else:
294 294 code_stats[ext] = {"count": 1, "desc": ext_info}
295 295 except (EmptyRepositoryError, CommitDoesNotExistError):
296 296 pass
297 297 return {'size': h.format_byte_size_binary(size),
298 298 'code_stats': code_stats}
299 299
300 300 stats = compute_stats(self.db_repo.repo_id, commit_id, show_stats)
301 301 return stats
302 302
303 303 @LoginRequired()
304 304 @HasRepoPermissionAnyDecorator(
305 305 'repository.read', 'repository.write', 'repository.admin')
306 306 @view_config(
307 307 route_name='repo_refs_data', request_method='GET',
308 308 renderer='json_ext')
309 309 def repo_refs_data(self):
310 310 _ = self.request.translate
311 311 self.load_default_context()
312 312
313 313 repo = self.rhodecode_vcs_repo
314 314 refs_to_create = [
315 315 (_("Branch"), repo.branches, 'branch'),
316 316 (_("Tag"), repo.tags, 'tag'),
317 317 (_("Bookmark"), repo.bookmarks, 'book'),
318 318 ]
319 319 res = self._create_reference_data(
320 320 repo, self.db_repo_name, refs_to_create)
321 321 data = {
322 322 'more': False,
323 323 'results': res
324 324 }
325 325 return data
326 326
327 327 @LoginRequired()
328 328 @HasRepoPermissionAnyDecorator(
329 329 'repository.read', 'repository.write', 'repository.admin')
330 330 @view_config(
331 331 route_name='repo_refs_changelog_data', request_method='GET',
332 332 renderer='json_ext')
333 333 def repo_refs_changelog_data(self):
334 334 _ = self.request.translate
335 335 self.load_default_context()
336 336
337 337 repo = self.rhodecode_vcs_repo
338 338
339 339 refs_to_create = [
340 340 (_("Branches"), repo.branches, 'branch'),
341 341 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
342 342 # TODO: enable when vcs can handle bookmarks filters
343 343 # (_("Bookmarks"), repo.bookmarks, "book"),
344 344 ]
345 345 res = self._create_reference_data(
346 346 repo, self.db_repo_name, refs_to_create)
347 347 data = {
348 348 'more': False,
349 349 'results': res
350 350 }
351 351 return data
352 352
353 353 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
354 format_ref_id = utils.get_format_ref_id(repo)
354 format_ref_id = get_format_ref_id(repo)
355 355
356 356 result = []
357 357 for title, refs, ref_type in refs_to_create:
358 358 if refs:
359 359 result.append({
360 360 'text': title,
361 361 'children': self._create_reference_items(
362 362 repo, full_repo_name, refs, ref_type,
363 363 format_ref_id),
364 364 })
365 365 return result
366 366
367 367 def _create_reference_items(self, repo, full_repo_name, refs, ref_type,
368 368 format_ref_id):
369 369 result = []
370 370 is_svn = h.is_svn(repo)
371 371 for ref_name, raw_id in refs.iteritems():
372 372 files_url = self._create_files_url(
373 373 repo, full_repo_name, ref_name, raw_id, is_svn)
374 374 result.append({
375 375 'text': ref_name,
376 376 'id': format_ref_id(ref_name, raw_id),
377 377 'raw_id': raw_id,
378 378 'type': ref_type,
379 379 'files_url': files_url,
380 380 })
381 381 return result
382 382
383 383 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
384 384 use_commit_id = '/' in ref_name or is_svn
385 385 return h.route_path(
386 386 'repo_files',
387 387 repo_name=full_repo_name,
388 388 f_path=ref_name if is_svn else '',
389 389 commit_id=raw_id if use_commit_id else ref_name,
390 390 _query=dict(at=ref_name))
1 NO CONTENT: file renamed from rhodecode/controllers/utils.py to rhodecode/lib/view_utils.py
@@ -1,93 +1,93 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 from rhodecode.controllers import utils
23 from rhodecode.lib import view_utils
24 24 from rhodecode.lib.vcs.exceptions import RepositoryError
25 25 import mock
26 26
27 27
28 28 @pytest.mark.parametrize('alias, expected', [
29 29 ('hg', [None, 'name']),
30 30 ('git', [None, 'name']),
31 31 ('svn', ['name', 'raw_id']),
32 32 ])
33 33 def test_parse_path_ref_understands_format_ref_id_result(alias, expected):
34 34 repo = mock.Mock(alias=alias)
35 35
36 36 # Formatting of reference ids as it is used by controllers
37 format_ref_id = utils.get_format_ref_id(repo)
37 format_ref_id = view_utils.get_format_ref_id(repo)
38 38 formatted_ref_id = format_ref_id(name='name', raw_id='raw_id')
39 39
40 40 # Parsing such a reference back as it is used by controllers
41 result = utils.parse_path_ref(formatted_ref_id)
41 result = view_utils.parse_path_ref(formatted_ref_id)
42 42
43 43 assert list(result) == expected
44 44
45 45
46 46 @pytest.mark.parametrize('ref, default_path, expected', [
47 47 ('a', None, (None, 'a')),
48 48 ('a', 'path', ('path', 'a')),
49 49 ('p@a', 'path', ('p', 'a')),
50 50 ('p@a', None, ('p', 'a')),
51 51 ])
52 52 def test_parse_path_ref(ref, default_path, expected):
53 result = utils.parse_path_ref(ref, default_path)
53 result = view_utils.parse_path_ref(ref, default_path)
54 54 assert list(result) == list(expected)
55 55
56 56
57 57 @pytest.mark.parametrize('alias, expected', [
58 58 ('hg', 'name'),
59 59 ('git', 'name'),
60 60 ('svn', 'name@raw_id'),
61 61 ])
62 62 def test_format_ref_id(alias, expected):
63 63 repo = mock.Mock(alias=alias)
64 format_ref_id = utils.get_format_ref_id(repo)
64 format_ref_id = view_utils.get_format_ref_id(repo)
65 65 result = format_ref_id(name='name', raw_id='raw_id')
66 66 assert result == expected
67 67
68 68
69 69 class TestGetCommit(object):
70 70 @pytest.mark.parametrize('ref_type', [None, 'book', 'tag', 'branch'])
71 71 def test_get_commit_from_ref_name_found(self, ref_type):
72 72 ref_name = 'a_None_id'
73 73 repo = mock.Mock()
74 74 scm_instance = repo.scm_instance()
75 75 scm_instance.branches = {ref_name: 'a_branch_id'}
76 76 scm_instance.tags = {ref_name: 'a_tag_id'}
77 77 scm_instance.bookmarks = {ref_name: 'a_book_id'}
78 78
79 79 scm_instance.get_commit.return_value = 'test'
80 commit = utils.get_commit_from_ref_name(repo, ref_name, ref_type)
80 commit = view_utils.get_commit_from_ref_name(repo, ref_name, ref_type)
81 81 scm_instance.get_commit.assert_called_once_with('a_%s_id' % ref_type)
82 82 assert commit == 'test'
83 83
84 84 @pytest.mark.parametrize('ref_type', ['book', 'tag', 'branch'])
85 85 def test_get_commit_from_ref_name_not_found(self, ref_type):
86 86 ref_name = 'invalid_ref'
87 87 repo = mock.Mock()
88 88 scm_instance = repo.scm_instance()
89 89 repo.scm_instance().branches = {}
90 90 repo.scm_instance().tags = {}
91 91 repo.scm_instance().bookmarks = {}
92 92 with pytest.raises(RepositoryError):
93 utils.get_commit_from_ref_name(repo, ref_name, ref_type)
93 view_utils.get_commit_from_ref_name(repo, ref_name, ref_type)
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now