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