##// END OF EJS Templates
downlaods: properly encode " in the filenames, and add RFC 5987 header for non-ascii files.
marcink -
r3343:15b9bc69 stable
parent child Browse files
Show More
@@ -1,1069 +1,1069 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22
23 23 import mock
24 24 import pytest
25 25
26 26 from rhodecode.apps.repository.views.repo_files import RepoFilesView
27 27 from rhodecode.lib import helpers as h
28 28 from rhodecode.lib.compat import OrderedDict
29 29 from rhodecode.lib.ext_json import json
30 30 from rhodecode.lib.vcs import nodes
31 31
32 32 from rhodecode.lib.vcs.conf import settings
33 33 from rhodecode.tests import assert_session_flash
34 34 from rhodecode.tests.fixture import Fixture
35 35 from rhodecode.model.db import Session
36 36
37 37 fixture = Fixture()
38 38
39 39
40 40 def get_node_history(backend_type):
41 41 return {
42 42 'hg': json.loads(fixture.load_resource('hg_node_history_response.json')),
43 43 'git': json.loads(fixture.load_resource('git_node_history_response.json')),
44 44 'svn': json.loads(fixture.load_resource('svn_node_history_response.json')),
45 45 }[backend_type]
46 46
47 47
48 48 def route_path(name, params=None, **kwargs):
49 49 import urllib
50 50
51 51 base_url = {
52 52 'repo_summary': '/{repo_name}',
53 53 'repo_archivefile': '/{repo_name}/archive/{fname}',
54 54 'repo_files_diff': '/{repo_name}/diff/{f_path}',
55 55 'repo_files_diff_2way_redirect': '/{repo_name}/diff-2way/{f_path}',
56 56 'repo_files': '/{repo_name}/files/{commit_id}/{f_path}',
57 57 'repo_files:default_path': '/{repo_name}/files/{commit_id}/',
58 58 'repo_files:default_commit': '/{repo_name}/files',
59 59 'repo_files:rendered': '/{repo_name}/render/{commit_id}/{f_path}',
60 60 'repo_files:annotated': '/{repo_name}/annotate/{commit_id}/{f_path}',
61 61 'repo_files:annotated_previous': '/{repo_name}/annotate-previous/{commit_id}/{f_path}',
62 62 'repo_files_nodelist': '/{repo_name}/nodelist/{commit_id}/{f_path}',
63 63 'repo_file_raw': '/{repo_name}/raw/{commit_id}/{f_path}',
64 64 'repo_file_download': '/{repo_name}/download/{commit_id}/{f_path}',
65 65 'repo_file_history': '/{repo_name}/history/{commit_id}/{f_path}',
66 66 'repo_file_authors': '/{repo_name}/authors/{commit_id}/{f_path}',
67 67 'repo_files_remove_file': '/{repo_name}/remove_file/{commit_id}/{f_path}',
68 68 'repo_files_delete_file': '/{repo_name}/delete_file/{commit_id}/{f_path}',
69 69 'repo_files_edit_file': '/{repo_name}/edit_file/{commit_id}/{f_path}',
70 70 'repo_files_update_file': '/{repo_name}/update_file/{commit_id}/{f_path}',
71 71 'repo_files_add_file': '/{repo_name}/add_file/{commit_id}/{f_path}',
72 72 'repo_files_create_file': '/{repo_name}/create_file/{commit_id}/{f_path}',
73 73 'repo_nodetree_full': '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
74 74 'repo_nodetree_full:default_path': '/{repo_name}/nodetree_full/{commit_id}/',
75 75 }[name].format(**kwargs)
76 76
77 77 if params:
78 78 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
79 79 return base_url
80 80
81 81
82 82 def assert_files_in_response(response, files, params):
83 83 template = (
84 84 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"')
85 85 _assert_items_in_response(response, files, template, params)
86 86
87 87
88 88 def assert_dirs_in_response(response, dirs, params):
89 89 template = (
90 90 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"')
91 91 _assert_items_in_response(response, dirs, template, params)
92 92
93 93
94 94 def _assert_items_in_response(response, items, template, params):
95 95 for item in items:
96 96 item_params = {'name': item}
97 97 item_params.update(params)
98 98 response.mustcontain(template % item_params)
99 99
100 100
101 101 def assert_timeago_in_response(response, items, params):
102 102 for item in items:
103 103 response.mustcontain(h.age_component(params['date']))
104 104
105 105
106 106 @pytest.mark.usefixtures("app")
107 107 class TestFilesViews(object):
108 108
109 109 def test_show_files(self, backend):
110 110 response = self.app.get(
111 111 route_path('repo_files',
112 112 repo_name=backend.repo_name,
113 113 commit_id='tip', f_path='/'))
114 114 commit = backend.repo.get_commit()
115 115
116 116 params = {
117 117 'repo_name': backend.repo_name,
118 118 'commit_id': commit.raw_id,
119 119 'date': commit.date
120 120 }
121 121 assert_dirs_in_response(response, ['docs', 'vcs'], params)
122 122 files = [
123 123 '.gitignore',
124 124 '.hgignore',
125 125 '.hgtags',
126 126 # TODO: missing in Git
127 127 # '.travis.yml',
128 128 'MANIFEST.in',
129 129 'README.rst',
130 130 # TODO: File is missing in svn repository
131 131 # 'run_test_and_report.sh',
132 132 'setup.cfg',
133 133 'setup.py',
134 134 'test_and_report.sh',
135 135 'tox.ini',
136 136 ]
137 137 assert_files_in_response(response, files, params)
138 138 assert_timeago_in_response(response, files, params)
139 139
140 140 def test_show_files_links_submodules_with_absolute_url(self, backend_hg):
141 141 repo = backend_hg['subrepos']
142 142 response = self.app.get(
143 143 route_path('repo_files',
144 144 repo_name=repo.repo_name,
145 145 commit_id='tip', f_path='/'))
146 146 assert_response = response.assert_response()
147 147 assert_response.contains_one_link(
148 148 'absolute-path @ 000000000000', 'http://example.com/absolute-path')
149 149
150 150 def test_show_files_links_submodules_with_absolute_url_subpaths(
151 151 self, backend_hg):
152 152 repo = backend_hg['subrepos']
153 153 response = self.app.get(
154 154 route_path('repo_files',
155 155 repo_name=repo.repo_name,
156 156 commit_id='tip', f_path='/'))
157 157 assert_response = response.assert_response()
158 158 assert_response.contains_one_link(
159 159 'subpaths-path @ 000000000000',
160 160 'http://sub-base.example.com/subpaths-path')
161 161
162 162 @pytest.mark.xfail_backends("svn", reason="Depends on branch support")
163 163 def test_files_menu(self, backend):
164 164 new_branch = "temp_branch_name"
165 165 commits = [
166 166 {'message': 'a'},
167 167 {'message': 'b', 'branch': new_branch}
168 168 ]
169 169 backend.create_repo(commits)
170 170 backend.repo.landing_rev = "branch:%s" % new_branch
171 171 Session().commit()
172 172
173 173 # get response based on tip and not new commit
174 174 response = self.app.get(
175 175 route_path('repo_files',
176 176 repo_name=backend.repo_name,
177 177 commit_id='tip', f_path='/'))
178 178
179 179 # make sure Files menu url is not tip but new commit
180 180 landing_rev = backend.repo.landing_rev[1]
181 181 files_url = route_path('repo_files:default_path',
182 182 repo_name=backend.repo_name,
183 183 commit_id=landing_rev)
184 184
185 185 assert landing_rev != 'tip'
186 186 response.mustcontain(
187 187 '<li class="active"><a class="menulink" href="%s">' % files_url)
188 188
189 189 def test_show_files_commit(self, backend):
190 190 commit = backend.repo.get_commit(commit_idx=32)
191 191
192 192 response = self.app.get(
193 193 route_path('repo_files',
194 194 repo_name=backend.repo_name,
195 195 commit_id=commit.raw_id, f_path='/'))
196 196
197 197 dirs = ['docs', 'tests']
198 198 files = ['README.rst']
199 199 params = {
200 200 'repo_name': backend.repo_name,
201 201 'commit_id': commit.raw_id,
202 202 }
203 203 assert_dirs_in_response(response, dirs, params)
204 204 assert_files_in_response(response, files, params)
205 205
206 206 def test_show_files_different_branch(self, backend):
207 207 branches = dict(
208 208 hg=(150, ['git']),
209 209 # TODO: Git test repository does not contain other branches
210 210 git=(633, ['master']),
211 211 # TODO: Branch support in Subversion
212 212 svn=(150, [])
213 213 )
214 214 idx, branches = branches[backend.alias]
215 215 commit = backend.repo.get_commit(commit_idx=idx)
216 216 response = self.app.get(
217 217 route_path('repo_files',
218 218 repo_name=backend.repo_name,
219 219 commit_id=commit.raw_id, f_path='/'))
220 220
221 221 assert_response = response.assert_response()
222 222 for branch in branches:
223 223 assert_response.element_contains('.tags .branchtag', branch)
224 224
225 225 def test_show_files_paging(self, backend):
226 226 repo = backend.repo
227 227 indexes = [73, 92, 109, 1, 0]
228 228 idx_map = [(rev, repo.get_commit(commit_idx=rev).raw_id)
229 229 for rev in indexes]
230 230
231 231 for idx in idx_map:
232 232 response = self.app.get(
233 233 route_path('repo_files',
234 234 repo_name=backend.repo_name,
235 235 commit_id=idx[1], f_path='/'))
236 236
237 237 response.mustcontain("""r%s:%s""" % (idx[0], idx[1][:8]))
238 238
239 239 def test_file_source(self, backend):
240 240 commit = backend.repo.get_commit(commit_idx=167)
241 241 response = self.app.get(
242 242 route_path('repo_files',
243 243 repo_name=backend.repo_name,
244 244 commit_id=commit.raw_id, f_path='vcs/nodes.py'))
245 245
246 246 msgbox = """<div class="commit right-content">%s</div>"""
247 247 response.mustcontain(msgbox % (commit.message, ))
248 248
249 249 assert_response = response.assert_response()
250 250 if commit.branch:
251 251 assert_response.element_contains(
252 252 '.tags.tags-main .branchtag', commit.branch)
253 253 if commit.tags:
254 254 for tag in commit.tags:
255 255 assert_response.element_contains('.tags.tags-main .tagtag', tag)
256 256
257 257 def test_file_source_annotated(self, backend):
258 258 response = self.app.get(
259 259 route_path('repo_files:annotated',
260 260 repo_name=backend.repo_name,
261 261 commit_id='tip', f_path='vcs/nodes.py'))
262 262 expected_commits = {
263 263 'hg': 'r356',
264 264 'git': 'r345',
265 265 'svn': 'r208',
266 266 }
267 267 response.mustcontain(expected_commits[backend.alias])
268 268
269 269 def test_file_source_authors(self, backend):
270 270 response = self.app.get(
271 271 route_path('repo_file_authors',
272 272 repo_name=backend.repo_name,
273 273 commit_id='tip', f_path='vcs/nodes.py'))
274 274 expected_authors = {
275 275 'hg': ('Marcin Kuzminski', 'Lukasz Balcerzak'),
276 276 'git': ('Marcin Kuzminski', 'Lukasz Balcerzak'),
277 277 'svn': ('marcin', 'lukasz'),
278 278 }
279 279
280 280 for author in expected_authors[backend.alias]:
281 281 response.mustcontain(author)
282 282
283 283 def test_file_source_authors_with_annotation(self, backend):
284 284 response = self.app.get(
285 285 route_path('repo_file_authors',
286 286 repo_name=backend.repo_name,
287 287 commit_id='tip', f_path='vcs/nodes.py',
288 288 params=dict(annotate=1)))
289 289 expected_authors = {
290 290 'hg': ('Marcin Kuzminski', 'Lukasz Balcerzak'),
291 291 'git': ('Marcin Kuzminski', 'Lukasz Balcerzak'),
292 292 'svn': ('marcin', 'lukasz'),
293 293 }
294 294
295 295 for author in expected_authors[backend.alias]:
296 296 response.mustcontain(author)
297 297
298 298 def test_file_source_history(self, backend, xhr_header):
299 299 response = self.app.get(
300 300 route_path('repo_file_history',
301 301 repo_name=backend.repo_name,
302 302 commit_id='tip', f_path='vcs/nodes.py'),
303 303 extra_environ=xhr_header)
304 304 assert get_node_history(backend.alias) == json.loads(response.body)
305 305
306 306 def test_file_source_history_svn(self, backend_svn, xhr_header):
307 307 simple_repo = backend_svn['svn-simple-layout']
308 308 response = self.app.get(
309 309 route_path('repo_file_history',
310 310 repo_name=simple_repo.repo_name,
311 311 commit_id='tip', f_path='trunk/example.py'),
312 312 extra_environ=xhr_header)
313 313
314 314 expected_data = json.loads(
315 315 fixture.load_resource('svn_node_history_branches.json'))
316 316 assert expected_data == response.json
317 317
318 318 def test_file_source_history_with_annotation(self, backend, xhr_header):
319 319 response = self.app.get(
320 320 route_path('repo_file_history',
321 321 repo_name=backend.repo_name,
322 322 commit_id='tip', f_path='vcs/nodes.py',
323 323 params=dict(annotate=1)),
324 324
325 325 extra_environ=xhr_header)
326 326 assert get_node_history(backend.alias) == json.loads(response.body)
327 327
328 328 def test_tree_search_top_level(self, backend, xhr_header):
329 329 commit = backend.repo.get_commit(commit_idx=173)
330 330 response = self.app.get(
331 331 route_path('repo_files_nodelist',
332 332 repo_name=backend.repo_name,
333 333 commit_id=commit.raw_id, f_path='/'),
334 334 extra_environ=xhr_header)
335 335 assert 'nodes' in response.json
336 336 assert {'name': 'docs', 'type': 'dir'} in response.json['nodes']
337 337
338 338 def test_tree_search_missing_xhr(self, backend):
339 339 self.app.get(
340 340 route_path('repo_files_nodelist',
341 341 repo_name=backend.repo_name,
342 342 commit_id='tip', f_path='/'),
343 343 status=404)
344 344
345 345 def test_tree_search_at_path(self, backend, xhr_header):
346 346 commit = backend.repo.get_commit(commit_idx=173)
347 347 response = self.app.get(
348 348 route_path('repo_files_nodelist',
349 349 repo_name=backend.repo_name,
350 350 commit_id=commit.raw_id, f_path='/docs'),
351 351 extra_environ=xhr_header)
352 352 assert 'nodes' in response.json
353 353 nodes = response.json['nodes']
354 354 assert {'name': 'docs/api', 'type': 'dir'} in nodes
355 355 assert {'name': 'docs/index.rst', 'type': 'file'} in nodes
356 356
357 357 def test_tree_search_at_path_2nd_level(self, backend, xhr_header):
358 358 commit = backend.repo.get_commit(commit_idx=173)
359 359 response = self.app.get(
360 360 route_path('repo_files_nodelist',
361 361 repo_name=backend.repo_name,
362 362 commit_id=commit.raw_id, f_path='/docs/api'),
363 363 extra_environ=xhr_header)
364 364 assert 'nodes' in response.json
365 365 nodes = response.json['nodes']
366 366 assert {'name': 'docs/api/index.rst', 'type': 'file'} in nodes
367 367
368 368 def test_tree_search_at_path_missing_xhr(self, backend):
369 369 self.app.get(
370 370 route_path('repo_files_nodelist',
371 371 repo_name=backend.repo_name,
372 372 commit_id='tip', f_path='/docs'),
373 373 status=404)
374 374
375 375 def test_nodetree(self, backend, xhr_header):
376 376 commit = backend.repo.get_commit(commit_idx=173)
377 377 response = self.app.get(
378 378 route_path('repo_nodetree_full',
379 379 repo_name=backend.repo_name,
380 380 commit_id=commit.raw_id, f_path='/'),
381 381 extra_environ=xhr_header)
382 382
383 383 assert_response = response.assert_response()
384 384
385 385 for attr in ['data-commit-id', 'data-date', 'data-author']:
386 386 elements = assert_response.get_elements('[{}]'.format(attr))
387 387 assert len(elements) > 1
388 388
389 389 for element in elements:
390 390 assert element.get(attr)
391 391
392 392 def test_nodetree_if_file(self, backend, xhr_header):
393 393 commit = backend.repo.get_commit(commit_idx=173)
394 394 response = self.app.get(
395 395 route_path('repo_nodetree_full',
396 396 repo_name=backend.repo_name,
397 397 commit_id=commit.raw_id, f_path='README.rst'),
398 398 extra_environ=xhr_header)
399 399 assert response.body == ''
400 400
401 401 def test_nodetree_wrong_path(self, backend, xhr_header):
402 402 commit = backend.repo.get_commit(commit_idx=173)
403 403 response = self.app.get(
404 404 route_path('repo_nodetree_full',
405 405 repo_name=backend.repo_name,
406 406 commit_id=commit.raw_id, f_path='/dont-exist'),
407 407 extra_environ=xhr_header)
408 408
409 409 err = 'error: There is no file nor ' \
410 410 'directory at the given path'
411 411 assert err in response.body
412 412
413 413 def test_nodetree_missing_xhr(self, backend):
414 414 self.app.get(
415 415 route_path('repo_nodetree_full',
416 416 repo_name=backend.repo_name,
417 417 commit_id='tip', f_path='/'),
418 418 status=404)
419 419
420 420
421 421 @pytest.mark.usefixtures("app", "autologin_user")
422 422 class TestRawFileHandling(object):
423 423
424 424 def test_download_file(self, backend):
425 425 commit = backend.repo.get_commit(commit_idx=173)
426 426 response = self.app.get(
427 427 route_path('repo_file_download',
428 428 repo_name=backend.repo_name,
429 429 commit_id=commit.raw_id, f_path='vcs/nodes.py'),)
430 430
431 assert response.content_disposition == "attachment; filename=nodes.py"
431 assert response.content_disposition == 'attachment; filename="nodes.py"; filename*=UTF-8\'\'nodes.py'
432 432 assert response.content_type == "text/x-python"
433 433
434 434 def test_download_file_wrong_cs(self, backend):
435 435 raw_id = u'ERRORce30c96924232dffcd24178a07ffeb5dfc'
436 436
437 437 response = self.app.get(
438 438 route_path('repo_file_download',
439 439 repo_name=backend.repo_name,
440 440 commit_id=raw_id, f_path='vcs/nodes.svg'),
441 441 status=404)
442 442
443 443 msg = """No such commit exists for this repository"""
444 444 response.mustcontain(msg)
445 445
446 446 def test_download_file_wrong_f_path(self, backend):
447 447 commit = backend.repo.get_commit(commit_idx=173)
448 448 f_path = 'vcs/ERRORnodes.py'
449 449
450 450 response = self.app.get(
451 451 route_path('repo_file_download',
452 452 repo_name=backend.repo_name,
453 453 commit_id=commit.raw_id, f_path=f_path),
454 454 status=404)
455 455
456 456 msg = (
457 457 "There is no file nor directory at the given path: "
458 458 "`%s` at commit %s" % (f_path, commit.short_id))
459 459 response.mustcontain(msg)
460 460
461 461 def test_file_raw(self, backend):
462 462 commit = backend.repo.get_commit(commit_idx=173)
463 463 response = self.app.get(
464 464 route_path('repo_file_raw',
465 465 repo_name=backend.repo_name,
466 466 commit_id=commit.raw_id, f_path='vcs/nodes.py'),)
467 467
468 468 assert response.content_type == "text/plain"
469 469
470 470 def test_file_raw_binary(self, backend):
471 471 commit = backend.repo.get_commit()
472 472 response = self.app.get(
473 473 route_path('repo_file_raw',
474 474 repo_name=backend.repo_name,
475 475 commit_id=commit.raw_id,
476 476 f_path='docs/theme/ADC/static/breadcrumb_background.png'),)
477 477
478 478 assert response.content_disposition == 'inline'
479 479
480 480 def test_raw_file_wrong_cs(self, backend):
481 481 raw_id = u'ERRORcce30c96924232dffcd24178a07ffeb5dfc'
482 482
483 483 response = self.app.get(
484 484 route_path('repo_file_raw',
485 485 repo_name=backend.repo_name,
486 486 commit_id=raw_id, f_path='vcs/nodes.svg'),
487 487 status=404)
488 488
489 489 msg = """No such commit exists for this repository"""
490 490 response.mustcontain(msg)
491 491
492 492 def test_raw_wrong_f_path(self, backend):
493 493 commit = backend.repo.get_commit(commit_idx=173)
494 494 f_path = 'vcs/ERRORnodes.py'
495 495 response = self.app.get(
496 496 route_path('repo_file_raw',
497 497 repo_name=backend.repo_name,
498 498 commit_id=commit.raw_id, f_path=f_path),
499 499 status=404)
500 500
501 501 msg = (
502 502 "There is no file nor directory at the given path: "
503 503 "`%s` at commit %s" % (f_path, commit.short_id))
504 504 response.mustcontain(msg)
505 505
506 506 def test_raw_svg_should_not_be_rendered(self, backend):
507 507 backend.create_repo()
508 508 backend.ensure_file("xss.svg")
509 509 response = self.app.get(
510 510 route_path('repo_file_raw',
511 511 repo_name=backend.repo_name,
512 512 commit_id='tip', f_path='xss.svg'),)
513 513 # If the content type is image/svg+xml then it allows to render HTML
514 514 # and malicious SVG.
515 515 assert response.content_type == "text/plain"
516 516
517 517
518 518 @pytest.mark.usefixtures("app")
519 519 class TestRepositoryArchival(object):
520 520
521 521 def test_archival(self, backend):
522 522 backend.enable_downloads()
523 523 commit = backend.repo.get_commit(commit_idx=173)
524 524 for archive, info in settings.ARCHIVE_SPECS.items():
525 525 mime_type, arch_ext = info
526 526 short = commit.short_id + arch_ext
527 527 fname = commit.raw_id + arch_ext
528 528 filename = '%s-%s' % (backend.repo_name, short)
529 529 response = self.app.get(
530 530 route_path('repo_archivefile',
531 531 repo_name=backend.repo_name,
532 532 fname=fname))
533 533
534 534 assert response.status == '200 OK'
535 535 headers = [
536 536 ('Content-Disposition', 'attachment; filename=%s' % filename),
537 537 ('Content-Type', '%s' % mime_type),
538 538 ]
539 539
540 540 for header in headers:
541 541 assert header in response.headers.items()
542 542
543 543 @pytest.mark.parametrize('arch_ext',[
544 544 'tar', 'rar', 'x', '..ax', '.zipz', 'tar.gz.tar'])
545 545 def test_archival_wrong_ext(self, backend, arch_ext):
546 546 backend.enable_downloads()
547 547 commit = backend.repo.get_commit(commit_idx=173)
548 548
549 549 fname = commit.raw_id + '.' + arch_ext
550 550
551 551 response = self.app.get(
552 552 route_path('repo_archivefile',
553 553 repo_name=backend.repo_name,
554 554 fname=fname))
555 555 response.mustcontain(
556 556 'Unknown archive type for: `{}`'.format(fname))
557 557
558 558 @pytest.mark.parametrize('commit_id', [
559 559 '00x000000', 'tar', 'wrong', '@$@$42413232', '232dffcd'])
560 560 def test_archival_wrong_commit_id(self, backend, commit_id):
561 561 backend.enable_downloads()
562 562 fname = '%s.zip' % commit_id
563 563
564 564 response = self.app.get(
565 565 route_path('repo_archivefile',
566 566 repo_name=backend.repo_name,
567 567 fname=fname))
568 568 response.mustcontain('Unknown commit_id')
569 569
570 570
571 571 @pytest.mark.usefixtures("app")
572 572 class TestFilesDiff(object):
573 573
574 574 @pytest.mark.parametrize("diff", ['diff', 'download', 'raw'])
575 575 def test_file_full_diff(self, backend, diff):
576 576 commit1 = backend.repo.get_commit(commit_idx=-1)
577 577 commit2 = backend.repo.get_commit(commit_idx=-2)
578 578
579 579 response = self.app.get(
580 580 route_path('repo_files_diff',
581 581 repo_name=backend.repo_name,
582 582 f_path='README'),
583 583 params={
584 584 'diff1': commit2.raw_id,
585 585 'diff2': commit1.raw_id,
586 586 'fulldiff': '1',
587 587 'diff': diff,
588 588 })
589 589
590 590 if diff == 'diff':
591 591 # use redirect since this is OLD view redirecting to compare page
592 592 response = response.follow()
593 593
594 594 # It's a symlink to README.rst
595 595 response.mustcontain('README.rst')
596 596 response.mustcontain('No newline at end of file')
597 597
598 598 def test_file_binary_diff(self, backend):
599 599 commits = [
600 600 {'message': 'First commit'},
601 601 {'message': 'Commit with binary',
602 602 'added': [nodes.FileNode('file.bin', content='\0BINARY\0')]},
603 603 ]
604 604 repo = backend.create_repo(commits=commits)
605 605
606 606 response = self.app.get(
607 607 route_path('repo_files_diff',
608 608 repo_name=backend.repo_name,
609 609 f_path='file.bin'),
610 610 params={
611 611 'diff1': repo.get_commit(commit_idx=0).raw_id,
612 612 'diff2': repo.get_commit(commit_idx=1).raw_id,
613 613 'fulldiff': '1',
614 614 'diff': 'diff',
615 615 })
616 616 # use redirect since this is OLD view redirecting to compare page
617 617 response = response.follow()
618 618 response.mustcontain('Expand 1 commit')
619 619 response.mustcontain('1 file changed: 0 inserted, 0 deleted')
620 620
621 621 if backend.alias == 'svn':
622 622 response.mustcontain('new file 10644')
623 623 # TODO(marcink): SVN doesn't yet detect binary changes
624 624 else:
625 625 response.mustcontain('new file 100644')
626 626 response.mustcontain('binary diff hidden')
627 627
628 628 def test_diff_2way(self, backend):
629 629 commit1 = backend.repo.get_commit(commit_idx=-1)
630 630 commit2 = backend.repo.get_commit(commit_idx=-2)
631 631 response = self.app.get(
632 632 route_path('repo_files_diff_2way_redirect',
633 633 repo_name=backend.repo_name,
634 634 f_path='README'),
635 635 params={
636 636 'diff1': commit2.raw_id,
637 637 'diff2': commit1.raw_id,
638 638 })
639 639 # use redirect since this is OLD view redirecting to compare page
640 640 response = response.follow()
641 641
642 642 # It's a symlink to README.rst
643 643 response.mustcontain('README.rst')
644 644 response.mustcontain('No newline at end of file')
645 645
646 646 def test_requires_one_commit_id(self, backend, autologin_user):
647 647 response = self.app.get(
648 648 route_path('repo_files_diff',
649 649 repo_name=backend.repo_name,
650 650 f_path='README.rst'),
651 651 status=400)
652 652 response.mustcontain(
653 653 'Need query parameter', 'diff1', 'diff2', 'to generate a diff.')
654 654
655 655 def test_returns_no_files_if_file_does_not_exist(self, vcsbackend):
656 656 repo = vcsbackend.repo
657 657 response = self.app.get(
658 658 route_path('repo_files_diff',
659 659 repo_name=repo.name,
660 660 f_path='does-not-exist-in-any-commit'),
661 661 params={
662 662 'diff1': repo[0].raw_id,
663 663 'diff2': repo[1].raw_id
664 664 })
665 665
666 666 response = response.follow()
667 667 response.mustcontain('No files')
668 668
669 669 def test_returns_redirect_if_file_not_changed(self, backend):
670 670 commit = backend.repo.get_commit(commit_idx=-1)
671 671 response = self.app.get(
672 672 route_path('repo_files_diff_2way_redirect',
673 673 repo_name=backend.repo_name,
674 674 f_path='README'),
675 675 params={
676 676 'diff1': commit.raw_id,
677 677 'diff2': commit.raw_id,
678 678 })
679 679
680 680 response = response.follow()
681 681 response.mustcontain('No files')
682 682 response.mustcontain('No commits in this compare')
683 683
684 684 def test_supports_diff_to_different_path_svn(self, backend_svn):
685 685 #TODO: check this case
686 686 return
687 687
688 688 repo = backend_svn['svn-simple-layout'].scm_instance()
689 689 commit_id_1 = '24'
690 690 commit_id_2 = '26'
691 691
692 692 response = self.app.get(
693 693 route_path('repo_files_diff',
694 694 repo_name=backend_svn.repo_name,
695 695 f_path='trunk/example.py'),
696 696 params={
697 697 'diff1': 'tags/v0.2/example.py@' + commit_id_1,
698 698 'diff2': commit_id_2,
699 699 })
700 700
701 701 response = response.follow()
702 702 response.mustcontain(
703 703 # diff contains this
704 704 "Will print out a useful message on invocation.")
705 705
706 706 # Note: Expecting that we indicate the user what's being compared
707 707 response.mustcontain("trunk/example.py")
708 708 response.mustcontain("tags/v0.2/example.py")
709 709
710 710 def test_show_rev_redirects_to_svn_path(self, backend_svn):
711 711 #TODO: check this case
712 712 return
713 713
714 714 repo = backend_svn['svn-simple-layout'].scm_instance()
715 715 commit_id = repo[-1].raw_id
716 716
717 717 response = self.app.get(
718 718 route_path('repo_files_diff',
719 719 repo_name=backend_svn.repo_name,
720 720 f_path='trunk/example.py'),
721 721 params={
722 722 'diff1': 'branches/argparse/example.py@' + commit_id,
723 723 'diff2': commit_id,
724 724 },
725 725 status=302)
726 726 response = response.follow()
727 727 assert response.headers['Location'].endswith(
728 728 'svn-svn-simple-layout/files/26/branches/argparse/example.py')
729 729
730 730 def test_show_rev_and_annotate_redirects_to_svn_path(self, backend_svn):
731 731 #TODO: check this case
732 732 return
733 733
734 734 repo = backend_svn['svn-simple-layout'].scm_instance()
735 735 commit_id = repo[-1].raw_id
736 736 response = self.app.get(
737 737 route_path('repo_files_diff',
738 738 repo_name=backend_svn.repo_name,
739 739 f_path='trunk/example.py'),
740 740 params={
741 741 'diff1': 'branches/argparse/example.py@' + commit_id,
742 742 'diff2': commit_id,
743 743 'show_rev': 'Show at Revision',
744 744 'annotate': 'true',
745 745 },
746 746 status=302)
747 747 response = response.follow()
748 748 assert response.headers['Location'].endswith(
749 749 'svn-svn-simple-layout/annotate/26/branches/argparse/example.py')
750 750
751 751
752 752 @pytest.mark.usefixtures("app", "autologin_user")
753 753 class TestModifyFilesWithWebInterface(object):
754 754
755 755 def test_add_file_view(self, backend):
756 756 self.app.get(
757 757 route_path('repo_files_add_file',
758 758 repo_name=backend.repo_name,
759 759 commit_id='tip', f_path='/')
760 760 )
761 761
762 762 @pytest.mark.xfail_backends("svn", reason="Depends on online editing")
763 763 def test_add_file_into_repo_missing_content(self, backend, csrf_token):
764 764 repo = backend.create_repo()
765 765 filename = 'init.py'
766 766 response = self.app.post(
767 767 route_path('repo_files_create_file',
768 768 repo_name=backend.repo_name,
769 769 commit_id='tip', f_path='/'),
770 770 params={
771 771 'content': "",
772 772 'filename': filename,
773 773 'location': "",
774 774 'csrf_token': csrf_token,
775 775 },
776 776 status=302)
777 777 assert_session_flash(response,
778 778 'Successfully committed new file `{}`'.format(
779 779 os.path.join(filename)))
780 780
781 781 def test_add_file_into_repo_missing_filename(self, backend, csrf_token):
782 782 response = self.app.post(
783 783 route_path('repo_files_create_file',
784 784 repo_name=backend.repo_name,
785 785 commit_id='tip', f_path='/'),
786 786 params={
787 787 'content': "foo",
788 788 'csrf_token': csrf_token,
789 789 },
790 790 status=302)
791 791
792 792 assert_session_flash(response, 'No filename')
793 793
794 794 def test_add_file_into_repo_errors_and_no_commits(
795 795 self, backend, csrf_token):
796 796 repo = backend.create_repo()
797 797 # Create a file with no filename, it will display an error but
798 798 # the repo has no commits yet
799 799 response = self.app.post(
800 800 route_path('repo_files_create_file',
801 801 repo_name=repo.repo_name,
802 802 commit_id='tip', f_path='/'),
803 803 params={
804 804 'content': "foo",
805 805 'csrf_token': csrf_token,
806 806 },
807 807 status=302)
808 808
809 809 assert_session_flash(response, 'No filename')
810 810
811 811 # Not allowed, redirect to the summary
812 812 redirected = response.follow()
813 813 summary_url = h.route_path('repo_summary', repo_name=repo.repo_name)
814 814
815 815 # As there are no commits, displays the summary page with the error of
816 816 # creating a file with no filename
817 817
818 818 assert redirected.request.path == summary_url
819 819
820 820 @pytest.mark.parametrize("location, filename", [
821 821 ('/abs', 'foo'),
822 822 ('../rel', 'foo'),
823 823 ('file/../foo', 'foo'),
824 824 ])
825 825 def test_add_file_into_repo_bad_filenames(
826 826 self, location, filename, backend, csrf_token):
827 827 response = self.app.post(
828 828 route_path('repo_files_create_file',
829 829 repo_name=backend.repo_name,
830 830 commit_id='tip', f_path='/'),
831 831 params={
832 832 'content': "foo",
833 833 'filename': filename,
834 834 'location': location,
835 835 'csrf_token': csrf_token,
836 836 },
837 837 status=302)
838 838
839 839 assert_session_flash(
840 840 response,
841 841 'The location specified must be a relative path and must not '
842 842 'contain .. in the path')
843 843
844 844 @pytest.mark.parametrize("cnt, location, filename", [
845 845 (1, '', 'foo.txt'),
846 846 (2, 'dir', 'foo.rst'),
847 847 (3, 'rel/dir', 'foo.bar'),
848 848 ])
849 849 def test_add_file_into_repo(self, cnt, location, filename, backend,
850 850 csrf_token):
851 851 repo = backend.create_repo()
852 852 response = self.app.post(
853 853 route_path('repo_files_create_file',
854 854 repo_name=repo.repo_name,
855 855 commit_id='tip', f_path='/'),
856 856 params={
857 857 'content': "foo",
858 858 'filename': filename,
859 859 'location': location,
860 860 'csrf_token': csrf_token,
861 861 },
862 862 status=302)
863 863 assert_session_flash(response,
864 864 'Successfully committed new file `{}`'.format(
865 865 os.path.join(location, filename)))
866 866
867 867 def test_edit_file_view(self, backend):
868 868 response = self.app.get(
869 869 route_path('repo_files_edit_file',
870 870 repo_name=backend.repo_name,
871 871 commit_id=backend.default_head_id,
872 872 f_path='vcs/nodes.py'),
873 873 status=200)
874 874 response.mustcontain("Module holding everything related to vcs nodes.")
875 875
876 876 def test_edit_file_view_not_on_branch(self, backend):
877 877 repo = backend.create_repo()
878 878 backend.ensure_file("vcs/nodes.py")
879 879
880 880 response = self.app.get(
881 881 route_path('repo_files_edit_file',
882 882 repo_name=repo.repo_name,
883 883 commit_id='tip',
884 884 f_path='vcs/nodes.py'),
885 885 status=302)
886 886 assert_session_flash(
887 887 response,
888 888 'You can only edit files with commit being a valid branch')
889 889
890 890 def test_edit_file_view_commit_changes(self, backend, csrf_token):
891 891 repo = backend.create_repo()
892 892 backend.ensure_file("vcs/nodes.py", content="print 'hello'")
893 893
894 894 response = self.app.post(
895 895 route_path('repo_files_update_file',
896 896 repo_name=repo.repo_name,
897 897 commit_id=backend.default_head_id,
898 898 f_path='vcs/nodes.py'),
899 899 params={
900 900 'content': "print 'hello world'",
901 901 'message': 'I committed',
902 902 'filename': "vcs/nodes.py",
903 903 'csrf_token': csrf_token,
904 904 },
905 905 status=302)
906 906 assert_session_flash(
907 907 response, 'Successfully committed changes to file `vcs/nodes.py`')
908 908 tip = repo.get_commit(commit_idx=-1)
909 909 assert tip.message == 'I committed'
910 910
911 911 def test_edit_file_view_commit_changes_default_message(self, backend,
912 912 csrf_token):
913 913 repo = backend.create_repo()
914 914 backend.ensure_file("vcs/nodes.py", content="print 'hello'")
915 915
916 916 commit_id = (
917 917 backend.default_branch_name or
918 918 backend.repo.scm_instance().commit_ids[-1])
919 919
920 920 response = self.app.post(
921 921 route_path('repo_files_update_file',
922 922 repo_name=repo.repo_name,
923 923 commit_id=commit_id,
924 924 f_path='vcs/nodes.py'),
925 925 params={
926 926 'content': "print 'hello world'",
927 927 'message': '',
928 928 'filename': "vcs/nodes.py",
929 929 'csrf_token': csrf_token,
930 930 },
931 931 status=302)
932 932 assert_session_flash(
933 933 response, 'Successfully committed changes to file `vcs/nodes.py`')
934 934 tip = repo.get_commit(commit_idx=-1)
935 935 assert tip.message == 'Edited file vcs/nodes.py via RhodeCode Enterprise'
936 936
937 937 def test_delete_file_view(self, backend):
938 938 self.app.get(
939 939 route_path('repo_files_remove_file',
940 940 repo_name=backend.repo_name,
941 941 commit_id=backend.default_head_id,
942 942 f_path='vcs/nodes.py'),
943 943 status=200)
944 944
945 945 def test_delete_file_view_not_on_branch(self, backend):
946 946 repo = backend.create_repo()
947 947 backend.ensure_file('vcs/nodes.py')
948 948
949 949 response = self.app.get(
950 950 route_path('repo_files_remove_file',
951 951 repo_name=repo.repo_name,
952 952 commit_id='tip',
953 953 f_path='vcs/nodes.py'),
954 954 status=302)
955 955 assert_session_flash(
956 956 response,
957 957 'You can only delete files with commit being a valid branch')
958 958
959 959 def test_delete_file_view_commit_changes(self, backend, csrf_token):
960 960 repo = backend.create_repo()
961 961 backend.ensure_file("vcs/nodes.py")
962 962
963 963 response = self.app.post(
964 964 route_path('repo_files_delete_file',
965 965 repo_name=repo.repo_name,
966 966 commit_id=backend.default_head_id,
967 967 f_path='vcs/nodes.py'),
968 968 params={
969 969 'message': 'i commited',
970 970 'csrf_token': csrf_token,
971 971 },
972 972 status=302)
973 973 assert_session_flash(
974 974 response, 'Successfully deleted file `vcs/nodes.py`')
975 975
976 976
977 977 @pytest.mark.usefixtures("app")
978 978 class TestFilesViewOtherCases(object):
979 979
980 980 def test_access_empty_repo_redirect_to_summary_with_alert_write_perms(
981 981 self, backend_stub, autologin_regular_user, user_regular,
982 982 user_util):
983 983
984 984 repo = backend_stub.create_repo()
985 985 user_util.grant_user_permission_to_repo(
986 986 repo, user_regular, 'repository.write')
987 987 response = self.app.get(
988 988 route_path('repo_files',
989 989 repo_name=repo.repo_name,
990 990 commit_id='tip', f_path='/'))
991 991
992 992 repo_file_add_url = route_path(
993 993 'repo_files_add_file',
994 994 repo_name=repo.repo_name,
995 995 commit_id=0, f_path='') + '#edit'
996 996
997 997 assert_session_flash(
998 998 response,
999 999 'There are no files yet. <a class="alert-link" '
1000 1000 'href="{}">Click here to add a new file.</a>'
1001 1001 .format(repo_file_add_url))
1002 1002
1003 1003 def test_access_empty_repo_redirect_to_summary_with_alert_no_write_perms(
1004 1004 self, backend_stub, autologin_regular_user):
1005 1005 repo = backend_stub.create_repo()
1006 1006 # init session for anon user
1007 1007 route_path('repo_summary', repo_name=repo.repo_name)
1008 1008
1009 1009 repo_file_add_url = route_path(
1010 1010 'repo_files_add_file',
1011 1011 repo_name=repo.repo_name,
1012 1012 commit_id=0, f_path='') + '#edit'
1013 1013
1014 1014 response = self.app.get(
1015 1015 route_path('repo_files',
1016 1016 repo_name=repo.repo_name,
1017 1017 commit_id='tip', f_path='/'))
1018 1018
1019 1019 assert_session_flash(response, no_=repo_file_add_url)
1020 1020
1021 1021 @pytest.mark.parametrize('file_node', [
1022 1022 'archive/file.zip',
1023 1023 'diff/my-file.txt',
1024 1024 'render.py',
1025 1025 'render',
1026 1026 'remove_file',
1027 1027 'remove_file/to-delete.txt',
1028 1028 ])
1029 1029 def test_file_names_equal_to_routes_parts(self, backend, file_node):
1030 1030 backend.create_repo()
1031 1031 backend.ensure_file(file_node)
1032 1032
1033 1033 self.app.get(
1034 1034 route_path('repo_files',
1035 1035 repo_name=backend.repo_name,
1036 1036 commit_id='tip', f_path=file_node),
1037 1037 status=200)
1038 1038
1039 1039
1040 1040 class TestAdjustFilePathForSvn(object):
1041 1041 """
1042 1042 SVN specific adjustments of node history in RepoFilesView.
1043 1043 """
1044 1044
1045 1045 def test_returns_path_relative_to_matched_reference(self):
1046 1046 repo = self._repo(branches=['trunk'])
1047 1047 self.assert_file_adjustment('trunk/file', 'file', repo)
1048 1048
1049 1049 def test_does_not_modify_file_if_no_reference_matches(self):
1050 1050 repo = self._repo(branches=['trunk'])
1051 1051 self.assert_file_adjustment('notes/file', 'notes/file', repo)
1052 1052
1053 1053 def test_does_not_adjust_partial_directory_names(self):
1054 1054 repo = self._repo(branches=['trun'])
1055 1055 self.assert_file_adjustment('trunk/file', 'trunk/file', repo)
1056 1056
1057 1057 def test_is_robust_to_patterns_which_prefix_other_patterns(self):
1058 1058 repo = self._repo(branches=['trunk', 'trunk/new', 'trunk/old'])
1059 1059 self.assert_file_adjustment('trunk/new/file', 'file', repo)
1060 1060
1061 1061 def assert_file_adjustment(self, f_path, expected, repo):
1062 1062 result = RepoFilesView.adjust_file_path_for_svn(f_path, repo)
1063 1063 assert result == expected
1064 1064
1065 1065 def _repo(self, branches=None):
1066 1066 repo = mock.Mock()
1067 1067 repo.branches = OrderedDict((name, '0') for name in branches or [])
1068 1068 repo.tags = {}
1069 1069 return repo
@@ -1,1385 +1,1391 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import itertools
22 22 import logging
23 23 import os
24 24 import shutil
25 25 import tempfile
26 26 import collections
27 import urllib
27 28
28 29 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
29 30 from pyramid.view import view_config
30 31 from pyramid.renderers import render
31 32 from pyramid.response import Response
32 33
33 34 import rhodecode
34 35 from rhodecode.apps._base import RepoAppView
35 36
36 37 from rhodecode.controllers.utils import parse_path_ref
37 38 from rhodecode.lib import diffs, helpers as h, rc_cache
38 39 from rhodecode.lib import audit_logger
39 40 from rhodecode.lib.exceptions import NonRelativePathError
40 41 from rhodecode.lib.codeblocks import (
41 42 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
42 43 from rhodecode.lib.utils2 import (
43 44 convert_line_endings, detect_mode, safe_str, str2bool, safe_int)
44 45 from rhodecode.lib.auth import (
45 46 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
46 47 from rhodecode.lib.vcs import path as vcspath
47 48 from rhodecode.lib.vcs.backends.base import EmptyCommit
48 49 from rhodecode.lib.vcs.conf import settings
49 50 from rhodecode.lib.vcs.nodes import FileNode
50 51 from rhodecode.lib.vcs.exceptions import (
51 52 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
52 53 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
53 54 NodeDoesNotExistError, CommitError, NodeError)
54 55
55 56 from rhodecode.model.scm import ScmModel
56 57 from rhodecode.model.db import Repository
57 58
58 59 log = logging.getLogger(__name__)
59 60
60 61
61 62 class RepoFilesView(RepoAppView):
62 63
63 64 @staticmethod
64 65 def adjust_file_path_for_svn(f_path, repo):
65 66 """
66 67 Computes the relative path of `f_path`.
67 68
68 69 This is mainly based on prefix matching of the recognized tags and
69 70 branches in the underlying repository.
70 71 """
71 72 tags_and_branches = itertools.chain(
72 73 repo.branches.iterkeys(),
73 74 repo.tags.iterkeys())
74 75 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
75 76
76 77 for name in tags_and_branches:
77 78 if f_path.startswith('{}/'.format(name)):
78 79 f_path = vcspath.relpath(f_path, name)
79 80 break
80 81 return f_path
81 82
82 83 def load_default_context(self):
83 84 c = self._get_local_tmpl_context(include_app_defaults=True)
84 85 c.rhodecode_repo = self.rhodecode_vcs_repo
85 86 return c
86 87
87 88 def _ensure_not_locked(self):
88 89 _ = self.request.translate
89 90
90 91 repo = self.db_repo
91 92 if repo.enable_locking and repo.locked[0]:
92 93 h.flash(_('This repository has been locked by %s on %s')
93 94 % (h.person_by_id(repo.locked[0]),
94 95 h.format_date(h.time_to_datetime(repo.locked[1]))),
95 96 'warning')
96 97 files_url = h.route_path(
97 98 'repo_files:default_path',
98 99 repo_name=self.db_repo_name, commit_id='tip')
99 100 raise HTTPFound(files_url)
100 101
101 102 def check_branch_permission(self, branch_name):
102 103 _ = self.request.translate
103 104
104 105 rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
105 106 self.db_repo_name, branch_name)
106 107 if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
107 108 h.flash(
108 109 _('Branch `{}` changes forbidden by rule {}.').format(branch_name, rule),
109 110 'warning')
110 111 files_url = h.route_path(
111 112 'repo_files:default_path',
112 113 repo_name=self.db_repo_name, commit_id='tip')
113 114 raise HTTPFound(files_url)
114 115
115 116 def _get_commit_and_path(self):
116 117 default_commit_id = self.db_repo.landing_rev[1]
117 118 default_f_path = '/'
118 119
119 120 commit_id = self.request.matchdict.get(
120 121 'commit_id', default_commit_id)
121 122 f_path = self._get_f_path(self.request.matchdict, default_f_path)
122 123 return commit_id, f_path
123 124
124 125 def _get_default_encoding(self, c):
125 126 enc_list = getattr(c, 'default_encodings', [])
126 127 return enc_list[0] if enc_list else 'UTF-8'
127 128
128 129 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
129 130 """
130 131 This is a safe way to get commit. If an error occurs it redirects to
131 132 tip with proper message
132 133
133 134 :param commit_id: id of commit to fetch
134 135 :param redirect_after: toggle redirection
135 136 """
136 137 _ = self.request.translate
137 138
138 139 try:
139 140 return self.rhodecode_vcs_repo.get_commit(commit_id)
140 141 except EmptyRepositoryError:
141 142 if not redirect_after:
142 143 return None
143 144
144 145 _url = h.route_path(
145 146 'repo_files_add_file',
146 147 repo_name=self.db_repo_name, commit_id=0, f_path='',
147 148 _anchor='edit')
148 149
149 150 if h.HasRepoPermissionAny(
150 151 'repository.write', 'repository.admin')(self.db_repo_name):
151 152 add_new = h.link_to(
152 153 _('Click here to add a new file.'), _url, class_="alert-link")
153 154 else:
154 155 add_new = ""
155 156
156 157 h.flash(h.literal(
157 158 _('There are no files yet. %s') % add_new), category='warning')
158 159 raise HTTPFound(
159 160 h.route_path('repo_summary', repo_name=self.db_repo_name))
160 161
161 162 except (CommitDoesNotExistError, LookupError):
162 163 msg = _('No such commit exists for this repository')
163 164 h.flash(msg, category='error')
164 165 raise HTTPNotFound()
165 166 except RepositoryError as e:
166 167 h.flash(safe_str(h.escape(e)), category='error')
167 168 raise HTTPNotFound()
168 169
169 170 def _get_filenode_or_redirect(self, commit_obj, path):
170 171 """
171 172 Returns file_node, if error occurs or given path is directory,
172 173 it'll redirect to top level path
173 174 """
174 175 _ = self.request.translate
175 176
176 177 try:
177 178 file_node = commit_obj.get_node(path)
178 179 if file_node.is_dir():
179 180 raise RepositoryError('The given path is a directory')
180 181 except CommitDoesNotExistError:
181 182 log.exception('No such commit exists for this repository')
182 183 h.flash(_('No such commit exists for this repository'), category='error')
183 184 raise HTTPNotFound()
184 185 except RepositoryError as e:
185 186 log.warning('Repository error while fetching '
186 187 'filenode `%s`. Err:%s', path, e)
187 188 h.flash(safe_str(h.escape(e)), category='error')
188 189 raise HTTPNotFound()
189 190
190 191 return file_node
191 192
192 193 def _is_valid_head(self, commit_id, repo):
193 194 branch_name = sha_commit_id = ''
194 195 is_head = False
195 196
196 197 if h.is_svn(repo) and not repo.is_empty():
197 198 # Note: Subversion only has one head.
198 199 if commit_id == repo.get_commit(commit_idx=-1).raw_id:
199 200 is_head = True
200 201 return branch_name, sha_commit_id, is_head
201 202
202 203 for _branch_name, branch_commit_id in repo.branches.items():
203 204 # simple case we pass in branch name, it's a HEAD
204 205 if commit_id == _branch_name:
205 206 is_head = True
206 207 branch_name = _branch_name
207 208 sha_commit_id = branch_commit_id
208 209 break
209 210 # case when we pass in full sha commit_id, which is a head
210 211 elif commit_id == branch_commit_id:
211 212 is_head = True
212 213 branch_name = _branch_name
213 214 sha_commit_id = branch_commit_id
214 215 break
215 216
216 217 # checked branches, means we only need to try to get the branch/commit_sha
217 218 if not repo.is_empty:
218 219 commit = repo.get_commit(commit_id=commit_id)
219 220 if commit:
220 221 branch_name = commit.branch
221 222 sha_commit_id = commit.raw_id
222 223
223 224 return branch_name, sha_commit_id, is_head
224 225
225 226 def _get_tree_at_commit(
226 227 self, c, commit_id, f_path, full_load=False):
227 228
228 229 repo_id = self.db_repo.repo_id
229 230
230 231 cache_seconds = safe_int(
231 232 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
232 233 cache_on = cache_seconds > 0
233 234 log.debug(
234 235 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
235 236 'with caching: %s[TTL: %ss]' % (
236 237 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
237 238
238 239 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
239 240 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
240 241
241 242 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
242 243 condition=cache_on)
243 244 def compute_file_tree(repo_id, commit_id, f_path, full_load):
244 245 log.debug('Generating cached file tree for repo_id: %s, %s, %s',
245 246 repo_id, commit_id, f_path)
246 247
247 248 c.full_load = full_load
248 249 return render(
249 250 'rhodecode:templates/files/files_browser_tree.mako',
250 251 self._get_template_context(c), self.request)
251 252
252 253 return compute_file_tree(self.db_repo.repo_id, commit_id, f_path, full_load)
253 254
254 255 def _get_archive_spec(self, fname):
255 256 log.debug('Detecting archive spec for: `%s`', fname)
256 257
257 258 fileformat = None
258 259 ext = None
259 260 content_type = None
260 261 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
261 262 content_type, extension = ext_data
262 263
263 264 if fname.endswith(extension):
264 265 fileformat = a_type
265 266 log.debug('archive is of type: %s', fileformat)
266 267 ext = extension
267 268 break
268 269
269 270 if not fileformat:
270 271 raise ValueError()
271 272
272 273 # left over part of whole fname is the commit
273 274 commit_id = fname[:-len(ext)]
274 275
275 276 return commit_id, ext, fileformat, content_type
276 277
277 278 @LoginRequired()
278 279 @HasRepoPermissionAnyDecorator(
279 280 'repository.read', 'repository.write', 'repository.admin')
280 281 @view_config(
281 282 route_name='repo_archivefile', request_method='GET',
282 283 renderer=None)
283 284 def repo_archivefile(self):
284 285 # archive cache config
285 286 from rhodecode import CONFIG
286 287 _ = self.request.translate
287 288 self.load_default_context()
288 289
289 290 fname = self.request.matchdict['fname']
290 291 subrepos = self.request.GET.get('subrepos') == 'true'
291 292
292 293 if not self.db_repo.enable_downloads:
293 294 return Response(_('Downloads disabled'))
294 295
295 296 try:
296 297 commit_id, ext, fileformat, content_type = \
297 298 self._get_archive_spec(fname)
298 299 except ValueError:
299 300 return Response(_('Unknown archive type for: `{}`').format(
300 301 h.escape(fname)))
301 302
302 303 try:
303 304 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
304 305 except CommitDoesNotExistError:
305 306 return Response(_('Unknown commit_id {}').format(
306 307 h.escape(commit_id)))
307 308 except EmptyRepositoryError:
308 309 return Response(_('Empty repository'))
309 310
310 311 archive_name = '%s-%s%s%s' % (
311 312 safe_str(self.db_repo_name.replace('/', '_')),
312 313 '-sub' if subrepos else '',
313 314 safe_str(commit.short_id), ext)
314 315
315 316 use_cached_archive = False
316 317 archive_cache_enabled = CONFIG.get(
317 318 'archive_cache_dir') and not self.request.GET.get('no_cache')
318 319 cached_archive_path = None
319 320
320 321 if archive_cache_enabled:
321 322 # check if we it's ok to write
322 323 if not os.path.isdir(CONFIG['archive_cache_dir']):
323 324 os.makedirs(CONFIG['archive_cache_dir'])
324 325 cached_archive_path = os.path.join(
325 326 CONFIG['archive_cache_dir'], archive_name)
326 327 if os.path.isfile(cached_archive_path):
327 328 log.debug('Found cached archive in %s', cached_archive_path)
328 329 fd, archive = None, cached_archive_path
329 330 use_cached_archive = True
330 331 else:
331 332 log.debug('Archive %s is not yet cached', archive_name)
332 333
333 334 if not use_cached_archive:
334 335 # generate new archive
335 336 fd, archive = tempfile.mkstemp()
336 337 log.debug('Creating new temp archive in %s', archive)
337 338 try:
338 339 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
339 340 except ImproperArchiveTypeError:
340 341 return _('Unknown archive type')
341 342 if archive_cache_enabled:
342 343 # if we generated the archive and we have cache enabled
343 344 # let's use this for future
344 345 log.debug('Storing new archive in %s', cached_archive_path)
345 346 shutil.move(archive, cached_archive_path)
346 347 archive = cached_archive_path
347 348
348 349 # store download action
349 350 audit_logger.store_web(
350 351 'repo.archive.download', action_data={
351 352 'user_agent': self.request.user_agent,
352 353 'archive_name': archive_name,
353 354 'archive_spec': fname,
354 355 'archive_cached': use_cached_archive},
355 356 user=self._rhodecode_user,
356 357 repo=self.db_repo,
357 358 commit=True
358 359 )
359 360
360 361 def get_chunked_archive(archive_path):
361 362 with open(archive_path, 'rb') as stream:
362 363 while True:
363 364 data = stream.read(16 * 1024)
364 365 if not data:
365 366 if fd: # fd means we used temporary file
366 367 os.close(fd)
367 368 if not archive_cache_enabled:
368 369 log.debug('Destroying temp archive %s', archive_path)
369 370 os.remove(archive_path)
370 371 break
371 372 yield data
372 373
373 374 response = Response(app_iter=get_chunked_archive(archive))
374 375 response.content_disposition = str(
375 376 'attachment; filename=%s' % archive_name)
376 377 response.content_type = str(content_type)
377 378
378 379 return response
379 380
380 381 def _get_file_node(self, commit_id, f_path):
381 382 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
382 383 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
383 384 try:
384 385 node = commit.get_node(f_path)
385 386 if node.is_dir():
386 387 raise NodeError('%s path is a %s not a file'
387 388 % (node, type(node)))
388 389 except NodeDoesNotExistError:
389 390 commit = EmptyCommit(
390 391 commit_id=commit_id,
391 392 idx=commit.idx,
392 393 repo=commit.repository,
393 394 alias=commit.repository.alias,
394 395 message=commit.message,
395 396 author=commit.author,
396 397 date=commit.date)
397 398 node = FileNode(f_path, '', commit=commit)
398 399 else:
399 400 commit = EmptyCommit(
400 401 repo=self.rhodecode_vcs_repo,
401 402 alias=self.rhodecode_vcs_repo.alias)
402 403 node = FileNode(f_path, '', commit=commit)
403 404 return node
404 405
405 406 @LoginRequired()
406 407 @HasRepoPermissionAnyDecorator(
407 408 'repository.read', 'repository.write', 'repository.admin')
408 409 @view_config(
409 410 route_name='repo_files_diff', request_method='GET',
410 411 renderer=None)
411 412 def repo_files_diff(self):
412 413 c = self.load_default_context()
413 414 f_path = self._get_f_path(self.request.matchdict)
414 415 diff1 = self.request.GET.get('diff1', '')
415 416 diff2 = self.request.GET.get('diff2', '')
416 417
417 418 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
418 419
419 420 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
420 421 line_context = self.request.GET.get('context', 3)
421 422
422 423 if not any((diff1, diff2)):
423 424 h.flash(
424 425 'Need query parameter "diff1" or "diff2" to generate a diff.',
425 426 category='error')
426 427 raise HTTPBadRequest()
427 428
428 429 c.action = self.request.GET.get('diff')
429 430 if c.action not in ['download', 'raw']:
430 431 compare_url = h.route_path(
431 432 'repo_compare',
432 433 repo_name=self.db_repo_name,
433 434 source_ref_type='rev',
434 435 source_ref=diff1,
435 436 target_repo=self.db_repo_name,
436 437 target_ref_type='rev',
437 438 target_ref=diff2,
438 439 _query=dict(f_path=f_path))
439 440 # redirect to new view if we render diff
440 441 raise HTTPFound(compare_url)
441 442
442 443 try:
443 444 node1 = self._get_file_node(diff1, path1)
444 445 node2 = self._get_file_node(diff2, f_path)
445 446 except (RepositoryError, NodeError):
446 447 log.exception("Exception while trying to get node from repository")
447 448 raise HTTPFound(
448 449 h.route_path('repo_files', repo_name=self.db_repo_name,
449 450 commit_id='tip', f_path=f_path))
450 451
451 452 if all(isinstance(node.commit, EmptyCommit)
452 453 for node in (node1, node2)):
453 454 raise HTTPNotFound()
454 455
455 456 c.commit_1 = node1.commit
456 457 c.commit_2 = node2.commit
457 458
458 459 if c.action == 'download':
459 460 _diff = diffs.get_gitdiff(node1, node2,
460 461 ignore_whitespace=ignore_whitespace,
461 462 context=line_context)
462 463 diff = diffs.DiffProcessor(_diff, format='gitdiff')
463 464
464 465 response = Response(self.path_filter.get_raw_patch(diff))
465 466 response.content_type = 'text/plain'
466 467 response.content_disposition = (
467 468 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
468 469 )
469 470 charset = self._get_default_encoding(c)
470 471 if charset:
471 472 response.charset = charset
472 473 return response
473 474
474 475 elif c.action == 'raw':
475 476 _diff = diffs.get_gitdiff(node1, node2,
476 477 ignore_whitespace=ignore_whitespace,
477 478 context=line_context)
478 479 diff = diffs.DiffProcessor(_diff, format='gitdiff')
479 480
480 481 response = Response(self.path_filter.get_raw_patch(diff))
481 482 response.content_type = 'text/plain'
482 483 charset = self._get_default_encoding(c)
483 484 if charset:
484 485 response.charset = charset
485 486 return response
486 487
487 488 # in case we ever end up here
488 489 raise HTTPNotFound()
489 490
490 491 @LoginRequired()
491 492 @HasRepoPermissionAnyDecorator(
492 493 'repository.read', 'repository.write', 'repository.admin')
493 494 @view_config(
494 495 route_name='repo_files_diff_2way_redirect', request_method='GET',
495 496 renderer=None)
496 497 def repo_files_diff_2way_redirect(self):
497 498 """
498 499 Kept only to make OLD links work
499 500 """
500 501 f_path = self._get_f_path_unchecked(self.request.matchdict)
501 502 diff1 = self.request.GET.get('diff1', '')
502 503 diff2 = self.request.GET.get('diff2', '')
503 504
504 505 if not any((diff1, diff2)):
505 506 h.flash(
506 507 'Need query parameter "diff1" or "diff2" to generate a diff.',
507 508 category='error')
508 509 raise HTTPBadRequest()
509 510
510 511 compare_url = h.route_path(
511 512 'repo_compare',
512 513 repo_name=self.db_repo_name,
513 514 source_ref_type='rev',
514 515 source_ref=diff1,
515 516 target_ref_type='rev',
516 517 target_ref=diff2,
517 518 _query=dict(f_path=f_path, diffmode='sideside',
518 519 target_repo=self.db_repo_name,))
519 520 raise HTTPFound(compare_url)
520 521
521 522 @LoginRequired()
522 523 @HasRepoPermissionAnyDecorator(
523 524 'repository.read', 'repository.write', 'repository.admin')
524 525 @view_config(
525 526 route_name='repo_files', request_method='GET',
526 527 renderer=None)
527 528 @view_config(
528 529 route_name='repo_files:default_path', request_method='GET',
529 530 renderer=None)
530 531 @view_config(
531 532 route_name='repo_files:default_commit', request_method='GET',
532 533 renderer=None)
533 534 @view_config(
534 535 route_name='repo_files:rendered', request_method='GET',
535 536 renderer=None)
536 537 @view_config(
537 538 route_name='repo_files:annotated', request_method='GET',
538 539 renderer=None)
539 540 def repo_files(self):
540 541 c = self.load_default_context()
541 542
542 543 view_name = getattr(self.request.matched_route, 'name', None)
543 544
544 545 c.annotate = view_name == 'repo_files:annotated'
545 546 # default is false, but .rst/.md files later are auto rendered, we can
546 547 # overwrite auto rendering by setting this GET flag
547 548 c.renderer = view_name == 'repo_files:rendered' or \
548 549 not self.request.GET.get('no-render', False)
549 550
550 551 # redirect to given commit_id from form if given
551 552 get_commit_id = self.request.GET.get('at_rev', None)
552 553 if get_commit_id:
553 554 self._get_commit_or_redirect(get_commit_id)
554 555
555 556 commit_id, f_path = self._get_commit_and_path()
556 557 c.commit = self._get_commit_or_redirect(commit_id)
557 558 c.branch = self.request.GET.get('branch', None)
558 559 c.f_path = f_path
559 560
560 561 # prev link
561 562 try:
562 563 prev_commit = c.commit.prev(c.branch)
563 564 c.prev_commit = prev_commit
564 565 c.url_prev = h.route_path(
565 566 'repo_files', repo_name=self.db_repo_name,
566 567 commit_id=prev_commit.raw_id, f_path=f_path)
567 568 if c.branch:
568 569 c.url_prev += '?branch=%s' % c.branch
569 570 except (CommitDoesNotExistError, VCSError):
570 571 c.url_prev = '#'
571 572 c.prev_commit = EmptyCommit()
572 573
573 574 # next link
574 575 try:
575 576 next_commit = c.commit.next(c.branch)
576 577 c.next_commit = next_commit
577 578 c.url_next = h.route_path(
578 579 'repo_files', repo_name=self.db_repo_name,
579 580 commit_id=next_commit.raw_id, f_path=f_path)
580 581 if c.branch:
581 582 c.url_next += '?branch=%s' % c.branch
582 583 except (CommitDoesNotExistError, VCSError):
583 584 c.url_next = '#'
584 585 c.next_commit = EmptyCommit()
585 586
586 587 # files or dirs
587 588 try:
588 589 c.file = c.commit.get_node(f_path)
589 590 c.file_author = True
590 591 c.file_tree = ''
591 592
592 593 # load file content
593 594 if c.file.is_file():
594 595 c.lf_node = c.file.get_largefile_node()
595 596
596 597 c.file_source_page = 'true'
597 598 c.file_last_commit = c.file.last_commit
598 599 if c.file.size < c.visual.cut_off_limit_diff:
599 600 if c.annotate: # annotation has precedence over renderer
600 601 c.annotated_lines = filenode_as_annotated_lines_tokens(
601 602 c.file
602 603 )
603 604 else:
604 605 c.renderer = (
605 606 c.renderer and h.renderer_from_filename(c.file.path)
606 607 )
607 608 if not c.renderer:
608 609 c.lines = filenode_as_lines_tokens(c.file)
609 610
610 611 _branch_name, _sha_commit_id, is_head = self._is_valid_head(
611 612 commit_id, self.rhodecode_vcs_repo)
612 613 c.on_branch_head = is_head
613 614
614 615 branch = c.commit.branch if (
615 616 c.commit.branch and '/' not in c.commit.branch) else None
616 617 c.branch_or_raw_id = branch or c.commit.raw_id
617 618 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
618 619
619 620 author = c.file_last_commit.author
620 621 c.authors = [[
621 622 h.email(author),
622 623 h.person(author, 'username_or_name_or_email'),
623 624 1
624 625 ]]
625 626
626 627 else: # load tree content at path
627 628 c.file_source_page = 'false'
628 629 c.authors = []
629 630 # this loads a simple tree without metadata to speed things up
630 631 # later via ajax we call repo_nodetree_full and fetch whole
631 632 c.file_tree = self._get_tree_at_commit(
632 633 c, c.commit.raw_id, f_path)
633 634
634 635 except RepositoryError as e:
635 636 h.flash(safe_str(h.escape(e)), category='error')
636 637 raise HTTPNotFound()
637 638
638 639 if self.request.environ.get('HTTP_X_PJAX'):
639 640 html = render('rhodecode:templates/files/files_pjax.mako',
640 641 self._get_template_context(c), self.request)
641 642 else:
642 643 html = render('rhodecode:templates/files/files.mako',
643 644 self._get_template_context(c), self.request)
644 645 return Response(html)
645 646
646 647 @HasRepoPermissionAnyDecorator(
647 648 'repository.read', 'repository.write', 'repository.admin')
648 649 @view_config(
649 650 route_name='repo_files:annotated_previous', request_method='GET',
650 651 renderer=None)
651 652 def repo_files_annotated_previous(self):
652 653 self.load_default_context()
653 654
654 655 commit_id, f_path = self._get_commit_and_path()
655 656 commit = self._get_commit_or_redirect(commit_id)
656 657 prev_commit_id = commit.raw_id
657 658 line_anchor = self.request.GET.get('line_anchor')
658 659 is_file = False
659 660 try:
660 661 _file = commit.get_node(f_path)
661 662 is_file = _file.is_file()
662 663 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
663 664 pass
664 665
665 666 if is_file:
666 667 history = commit.get_path_history(f_path)
667 668 prev_commit_id = history[1].raw_id \
668 669 if len(history) > 1 else prev_commit_id
669 670 prev_url = h.route_path(
670 671 'repo_files:annotated', repo_name=self.db_repo_name,
671 672 commit_id=prev_commit_id, f_path=f_path,
672 673 _anchor='L{}'.format(line_anchor))
673 674
674 675 raise HTTPFound(prev_url)
675 676
676 677 @LoginRequired()
677 678 @HasRepoPermissionAnyDecorator(
678 679 'repository.read', 'repository.write', 'repository.admin')
679 680 @view_config(
680 681 route_name='repo_nodetree_full', request_method='GET',
681 682 renderer=None, xhr=True)
682 683 @view_config(
683 684 route_name='repo_nodetree_full:default_path', request_method='GET',
684 685 renderer=None, xhr=True)
685 686 def repo_nodetree_full(self):
686 687 """
687 688 Returns rendered html of file tree that contains commit date,
688 689 author, commit_id for the specified combination of
689 690 repo, commit_id and file path
690 691 """
691 692 c = self.load_default_context()
692 693
693 694 commit_id, f_path = self._get_commit_and_path()
694 695 commit = self._get_commit_or_redirect(commit_id)
695 696 try:
696 697 dir_node = commit.get_node(f_path)
697 698 except RepositoryError as e:
698 699 return Response('error: {}'.format(h.escape(safe_str(e))))
699 700
700 701 if dir_node.is_file():
701 702 return Response('')
702 703
703 704 c.file = dir_node
704 705 c.commit = commit
705 706
706 707 html = self._get_tree_at_commit(
707 708 c, commit.raw_id, dir_node.path, full_load=True)
708 709
709 710 return Response(html)
710 711
711 def _get_attachement_disposition(self, f_path):
712 return 'attachment; filename=%s' % \
713 safe_str(f_path.split(Repository.NAME_SEP)[-1])
712 def _get_attachement_headers(self, f_path):
713 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
714 safe_path = f_name.replace('"', '\\"')
715 encoded_path = urllib.quote(f_name)
716
717 return "attachment; " \
718 "filename=\"{}\"; " \
719 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
714 720
715 721 @LoginRequired()
716 722 @HasRepoPermissionAnyDecorator(
717 723 'repository.read', 'repository.write', 'repository.admin')
718 724 @view_config(
719 725 route_name='repo_file_raw', request_method='GET',
720 726 renderer=None)
721 727 def repo_file_raw(self):
722 728 """
723 729 Action for show as raw, some mimetypes are "rendered",
724 730 those include images, icons.
725 731 """
726 732 c = self.load_default_context()
727 733
728 734 commit_id, f_path = self._get_commit_and_path()
729 735 commit = self._get_commit_or_redirect(commit_id)
730 736 file_node = self._get_filenode_or_redirect(commit, f_path)
731 737
732 738 raw_mimetype_mapping = {
733 739 # map original mimetype to a mimetype used for "show as raw"
734 740 # you can also provide a content-disposition to override the
735 741 # default "attachment" disposition.
736 742 # orig_type: (new_type, new_dispo)
737 743
738 744 # show images inline:
739 745 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
740 746 # for example render an SVG with javascript inside or even render
741 747 # HTML.
742 748 'image/x-icon': ('image/x-icon', 'inline'),
743 749 'image/png': ('image/png', 'inline'),
744 750 'image/gif': ('image/gif', 'inline'),
745 751 'image/jpeg': ('image/jpeg', 'inline'),
746 752 'application/pdf': ('application/pdf', 'inline'),
747 753 }
748 754
749 755 mimetype = file_node.mimetype
750 756 try:
751 757 mimetype, disposition = raw_mimetype_mapping[mimetype]
752 758 except KeyError:
753 759 # we don't know anything special about this, handle it safely
754 760 if file_node.is_binary:
755 761 # do same as download raw for binary files
756 762 mimetype, disposition = 'application/octet-stream', 'attachment'
757 763 else:
758 764 # do not just use the original mimetype, but force text/plain,
759 765 # otherwise it would serve text/html and that might be unsafe.
760 766 # Note: underlying vcs library fakes text/plain mimetype if the
761 767 # mimetype can not be determined and it thinks it is not
762 768 # binary.This might lead to erroneous text display in some
763 769 # cases, but helps in other cases, like with text files
764 770 # without extension.
765 771 mimetype, disposition = 'text/plain', 'inline'
766 772
767 773 if disposition == 'attachment':
768 disposition = self._get_attachement_disposition(f_path)
774 disposition = self._get_attachement_headers(f_path)
769 775
770 776 def stream_node():
771 777 yield file_node.raw_bytes
772 778
773 779 response = Response(app_iter=stream_node())
774 780 response.content_disposition = disposition
775 781 response.content_type = mimetype
776 782
777 783 charset = self._get_default_encoding(c)
778 784 if charset:
779 785 response.charset = charset
780 786
781 787 return response
782 788
783 789 @LoginRequired()
784 790 @HasRepoPermissionAnyDecorator(
785 791 'repository.read', 'repository.write', 'repository.admin')
786 792 @view_config(
787 793 route_name='repo_file_download', request_method='GET',
788 794 renderer=None)
789 795 @view_config(
790 796 route_name='repo_file_download:legacy', request_method='GET',
791 797 renderer=None)
792 798 def repo_file_download(self):
793 799 c = self.load_default_context()
794 800
795 801 commit_id, f_path = self._get_commit_and_path()
796 802 commit = self._get_commit_or_redirect(commit_id)
797 803 file_node = self._get_filenode_or_redirect(commit, f_path)
798 804
799 805 if self.request.GET.get('lf'):
800 806 # only if lf get flag is passed, we download this file
801 807 # as LFS/Largefile
802 808 lf_node = file_node.get_largefile_node()
803 809 if lf_node:
804 810 # overwrite our pointer with the REAL large-file
805 811 file_node = lf_node
806 812
807 disposition = self._get_attachement_disposition(f_path)
813 disposition = self._get_attachement_headers(f_path)
808 814
809 815 def stream_node():
810 816 yield file_node.raw_bytes
811 817
812 818 response = Response(app_iter=stream_node())
813 819 response.content_disposition = disposition
814 820 response.content_type = file_node.mimetype
815 821
816 822 charset = self._get_default_encoding(c)
817 823 if charset:
818 824 response.charset = charset
819 825
820 826 return response
821 827
822 828 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
823 829
824 830 cache_seconds = safe_int(
825 831 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
826 832 cache_on = cache_seconds > 0
827 833 log.debug(
828 834 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
829 835 'with caching: %s[TTL: %ss]' % (
830 836 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
831 837
832 838 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
833 839 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
834 840
835 841 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
836 842 condition=cache_on)
837 843 def compute_file_search(repo_id, commit_id, f_path):
838 844 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
839 845 repo_id, commit_id, f_path)
840 846 try:
841 847 _d, _f = ScmModel().get_nodes(
842 848 repo_name, commit_id, f_path, flat=False)
843 849 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
844 850 log.exception(safe_str(e))
845 851 h.flash(safe_str(h.escape(e)), category='error')
846 852 raise HTTPFound(h.route_path(
847 853 'repo_files', repo_name=self.db_repo_name,
848 854 commit_id='tip', f_path='/'))
849 855 return _d + _f
850 856
851 857 return compute_file_search(self.db_repo.repo_id, commit_id, f_path)
852 858
853 859 @LoginRequired()
854 860 @HasRepoPermissionAnyDecorator(
855 861 'repository.read', 'repository.write', 'repository.admin')
856 862 @view_config(
857 863 route_name='repo_files_nodelist', request_method='GET',
858 864 renderer='json_ext', xhr=True)
859 865 def repo_nodelist(self):
860 866 self.load_default_context()
861 867
862 868 commit_id, f_path = self._get_commit_and_path()
863 869 commit = self._get_commit_or_redirect(commit_id)
864 870
865 871 metadata = self._get_nodelist_at_commit(
866 872 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
867 873 return {'nodes': metadata}
868 874
869 875 def _create_references(
870 876 self, branches_or_tags, symbolic_reference, f_path):
871 877 items = []
872 878 for name, commit_id in branches_or_tags.items():
873 879 sym_ref = symbolic_reference(commit_id, name, f_path)
874 880 items.append((sym_ref, name))
875 881 return items
876 882
877 883 def _symbolic_reference(self, commit_id, name, f_path):
878 884 return commit_id
879 885
880 886 def _symbolic_reference_svn(self, commit_id, name, f_path):
881 887 new_f_path = vcspath.join(name, f_path)
882 888 return u'%s@%s' % (new_f_path, commit_id)
883 889
884 890 def _get_node_history(self, commit_obj, f_path, commits=None):
885 891 """
886 892 get commit history for given node
887 893
888 894 :param commit_obj: commit to calculate history
889 895 :param f_path: path for node to calculate history for
890 896 :param commits: if passed don't calculate history and take
891 897 commits defined in this list
892 898 """
893 899 _ = self.request.translate
894 900
895 901 # calculate history based on tip
896 902 tip = self.rhodecode_vcs_repo.get_commit()
897 903 if commits is None:
898 904 pre_load = ["author", "branch"]
899 905 try:
900 906 commits = tip.get_path_history(f_path, pre_load=pre_load)
901 907 except (NodeDoesNotExistError, CommitError):
902 908 # this node is not present at tip!
903 909 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
904 910
905 911 history = []
906 912 commits_group = ([], _("Changesets"))
907 913 for commit in commits:
908 914 branch = ' (%s)' % commit.branch if commit.branch else ''
909 915 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
910 916 commits_group[0].append((commit.raw_id, n_desc,))
911 917 history.append(commits_group)
912 918
913 919 symbolic_reference = self._symbolic_reference
914 920
915 921 if self.rhodecode_vcs_repo.alias == 'svn':
916 922 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
917 923 f_path, self.rhodecode_vcs_repo)
918 924 if adjusted_f_path != f_path:
919 925 log.debug(
920 926 'Recognized svn tag or branch in file "%s", using svn '
921 927 'specific symbolic references', f_path)
922 928 f_path = adjusted_f_path
923 929 symbolic_reference = self._symbolic_reference_svn
924 930
925 931 branches = self._create_references(
926 932 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path)
927 933 branches_group = (branches, _("Branches"))
928 934
929 935 tags = self._create_references(
930 936 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path)
931 937 tags_group = (tags, _("Tags"))
932 938
933 939 history.append(branches_group)
934 940 history.append(tags_group)
935 941
936 942 return history, commits
937 943
938 944 @LoginRequired()
939 945 @HasRepoPermissionAnyDecorator(
940 946 'repository.read', 'repository.write', 'repository.admin')
941 947 @view_config(
942 948 route_name='repo_file_history', request_method='GET',
943 949 renderer='json_ext')
944 950 def repo_file_history(self):
945 951 self.load_default_context()
946 952
947 953 commit_id, f_path = self._get_commit_and_path()
948 954 commit = self._get_commit_or_redirect(commit_id)
949 955 file_node = self._get_filenode_or_redirect(commit, f_path)
950 956
951 957 if file_node.is_file():
952 958 file_history, _hist = self._get_node_history(commit, f_path)
953 959
954 960 res = []
955 961 for obj in file_history:
956 962 res.append({
957 963 'text': obj[1],
958 964 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
959 965 })
960 966
961 967 data = {
962 968 'more': False,
963 969 'results': res
964 970 }
965 971 return data
966 972
967 973 log.warning('Cannot fetch history for directory')
968 974 raise HTTPBadRequest()
969 975
970 976 @LoginRequired()
971 977 @HasRepoPermissionAnyDecorator(
972 978 'repository.read', 'repository.write', 'repository.admin')
973 979 @view_config(
974 980 route_name='repo_file_authors', request_method='GET',
975 981 renderer='rhodecode:templates/files/file_authors_box.mako')
976 982 def repo_file_authors(self):
977 983 c = self.load_default_context()
978 984
979 985 commit_id, f_path = self._get_commit_and_path()
980 986 commit = self._get_commit_or_redirect(commit_id)
981 987 file_node = self._get_filenode_or_redirect(commit, f_path)
982 988
983 989 if not file_node.is_file():
984 990 raise HTTPBadRequest()
985 991
986 992 c.file_last_commit = file_node.last_commit
987 993 if self.request.GET.get('annotate') == '1':
988 994 # use _hist from annotation if annotation mode is on
989 995 commit_ids = set(x[1] for x in file_node.annotate)
990 996 _hist = (
991 997 self.rhodecode_vcs_repo.get_commit(commit_id)
992 998 for commit_id in commit_ids)
993 999 else:
994 1000 _f_history, _hist = self._get_node_history(commit, f_path)
995 1001 c.file_author = False
996 1002
997 1003 unique = collections.OrderedDict()
998 1004 for commit in _hist:
999 1005 author = commit.author
1000 1006 if author not in unique:
1001 1007 unique[commit.author] = [
1002 1008 h.email(author),
1003 1009 h.person(author, 'username_or_name_or_email'),
1004 1010 1 # counter
1005 1011 ]
1006 1012
1007 1013 else:
1008 1014 # increase counter
1009 1015 unique[commit.author][2] += 1
1010 1016
1011 1017 c.authors = [val for val in unique.values()]
1012 1018
1013 1019 return self._get_template_context(c)
1014 1020
1015 1021 @LoginRequired()
1016 1022 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1017 1023 @view_config(
1018 1024 route_name='repo_files_remove_file', request_method='GET',
1019 1025 renderer='rhodecode:templates/files/files_delete.mako')
1020 1026 def repo_files_remove_file(self):
1021 1027 _ = self.request.translate
1022 1028 c = self.load_default_context()
1023 1029 commit_id, f_path = self._get_commit_and_path()
1024 1030
1025 1031 self._ensure_not_locked()
1026 1032 _branch_name, _sha_commit_id, is_head = \
1027 1033 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1028 1034
1029 1035 if not is_head:
1030 1036 h.flash(_('You can only delete files with commit '
1031 1037 'being a valid branch head.'), category='warning')
1032 1038 raise HTTPFound(
1033 1039 h.route_path('repo_files',
1034 1040 repo_name=self.db_repo_name, commit_id='tip',
1035 1041 f_path=f_path))
1036 1042
1037 1043 self.check_branch_permission(_branch_name)
1038 1044 c.commit = self._get_commit_or_redirect(commit_id)
1039 1045 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1040 1046
1041 1047 c.default_message = _(
1042 1048 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1043 1049 c.f_path = f_path
1044 1050
1045 1051 return self._get_template_context(c)
1046 1052
1047 1053 @LoginRequired()
1048 1054 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1049 1055 @CSRFRequired()
1050 1056 @view_config(
1051 1057 route_name='repo_files_delete_file', request_method='POST',
1052 1058 renderer=None)
1053 1059 def repo_files_delete_file(self):
1054 1060 _ = self.request.translate
1055 1061
1056 1062 c = self.load_default_context()
1057 1063 commit_id, f_path = self._get_commit_and_path()
1058 1064
1059 1065 self._ensure_not_locked()
1060 1066 _branch_name, _sha_commit_id, is_head = \
1061 1067 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1062 1068
1063 1069 if not is_head:
1064 1070 h.flash(_('You can only delete files with commit '
1065 1071 'being a valid branch head.'), category='warning')
1066 1072 raise HTTPFound(
1067 1073 h.route_path('repo_files',
1068 1074 repo_name=self.db_repo_name, commit_id='tip',
1069 1075 f_path=f_path))
1070 1076 self.check_branch_permission(_branch_name)
1071 1077
1072 1078 c.commit = self._get_commit_or_redirect(commit_id)
1073 1079 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1074 1080
1075 1081 c.default_message = _(
1076 1082 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1077 1083 c.f_path = f_path
1078 1084 node_path = f_path
1079 1085 author = self._rhodecode_db_user.full_contact
1080 1086 message = self.request.POST.get('message') or c.default_message
1081 1087 try:
1082 1088 nodes = {
1083 1089 node_path: {
1084 1090 'content': ''
1085 1091 }
1086 1092 }
1087 1093 ScmModel().delete_nodes(
1088 1094 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1089 1095 message=message,
1090 1096 nodes=nodes,
1091 1097 parent_commit=c.commit,
1092 1098 author=author,
1093 1099 )
1094 1100
1095 1101 h.flash(
1096 1102 _('Successfully deleted file `{}`').format(
1097 1103 h.escape(f_path)), category='success')
1098 1104 except Exception:
1099 1105 log.exception('Error during commit operation')
1100 1106 h.flash(_('Error occurred during commit'), category='error')
1101 1107 raise HTTPFound(
1102 1108 h.route_path('repo_commit', repo_name=self.db_repo_name,
1103 1109 commit_id='tip'))
1104 1110
1105 1111 @LoginRequired()
1106 1112 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1107 1113 @view_config(
1108 1114 route_name='repo_files_edit_file', request_method='GET',
1109 1115 renderer='rhodecode:templates/files/files_edit.mako')
1110 1116 def repo_files_edit_file(self):
1111 1117 _ = self.request.translate
1112 1118 c = self.load_default_context()
1113 1119 commit_id, f_path = self._get_commit_and_path()
1114 1120
1115 1121 self._ensure_not_locked()
1116 1122 _branch_name, _sha_commit_id, is_head = \
1117 1123 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1118 1124
1119 1125 if not is_head:
1120 1126 h.flash(_('You can only edit files with commit '
1121 1127 'being a valid branch head.'), category='warning')
1122 1128 raise HTTPFound(
1123 1129 h.route_path('repo_files',
1124 1130 repo_name=self.db_repo_name, commit_id='tip',
1125 1131 f_path=f_path))
1126 1132 self.check_branch_permission(_branch_name)
1127 1133
1128 1134 c.commit = self._get_commit_or_redirect(commit_id)
1129 1135 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1130 1136
1131 1137 if c.file.is_binary:
1132 1138 files_url = h.route_path(
1133 1139 'repo_files',
1134 1140 repo_name=self.db_repo_name,
1135 1141 commit_id=c.commit.raw_id, f_path=f_path)
1136 1142 raise HTTPFound(files_url)
1137 1143
1138 1144 c.default_message = _(
1139 1145 'Edited file {} via RhodeCode Enterprise').format(f_path)
1140 1146 c.f_path = f_path
1141 1147
1142 1148 return self._get_template_context(c)
1143 1149
1144 1150 @LoginRequired()
1145 1151 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1146 1152 @CSRFRequired()
1147 1153 @view_config(
1148 1154 route_name='repo_files_update_file', request_method='POST',
1149 1155 renderer=None)
1150 1156 def repo_files_update_file(self):
1151 1157 _ = self.request.translate
1152 1158 c = self.load_default_context()
1153 1159 commit_id, f_path = self._get_commit_and_path()
1154 1160
1155 1161 self._ensure_not_locked()
1156 1162 _branch_name, _sha_commit_id, is_head = \
1157 1163 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1158 1164
1159 1165 if not is_head:
1160 1166 h.flash(_('You can only edit files with commit '
1161 1167 'being a valid branch head.'), category='warning')
1162 1168 raise HTTPFound(
1163 1169 h.route_path('repo_files',
1164 1170 repo_name=self.db_repo_name, commit_id='tip',
1165 1171 f_path=f_path))
1166 1172
1167 1173 self.check_branch_permission(_branch_name)
1168 1174
1169 1175 c.commit = self._get_commit_or_redirect(commit_id)
1170 1176 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1171 1177
1172 1178 if c.file.is_binary:
1173 1179 raise HTTPFound(
1174 1180 h.route_path('repo_files',
1175 1181 repo_name=self.db_repo_name,
1176 1182 commit_id=c.commit.raw_id,
1177 1183 f_path=f_path))
1178 1184
1179 1185 c.default_message = _(
1180 1186 'Edited file {} via RhodeCode Enterprise').format(f_path)
1181 1187 c.f_path = f_path
1182 1188 old_content = c.file.content
1183 1189 sl = old_content.splitlines(1)
1184 1190 first_line = sl[0] if sl else ''
1185 1191
1186 1192 r_post = self.request.POST
1187 1193 # modes: 0 - Unix, 1 - Mac, 2 - DOS
1188 1194 mode = detect_mode(first_line, 0)
1189 1195 content = convert_line_endings(r_post.get('content', ''), mode)
1190 1196
1191 1197 message = r_post.get('message') or c.default_message
1192 1198 org_f_path = c.file.unicode_path
1193 1199 filename = r_post['filename']
1194 1200 org_filename = c.file.name
1195 1201
1196 1202 if content == old_content and filename == org_filename:
1197 1203 h.flash(_('No changes'), category='warning')
1198 1204 raise HTTPFound(
1199 1205 h.route_path('repo_commit', repo_name=self.db_repo_name,
1200 1206 commit_id='tip'))
1201 1207 try:
1202 1208 mapping = {
1203 1209 org_f_path: {
1204 1210 'org_filename': org_f_path,
1205 1211 'filename': os.path.join(c.file.dir_path, filename),
1206 1212 'content': content,
1207 1213 'lexer': '',
1208 1214 'op': 'mod',
1209 1215 }
1210 1216 }
1211 1217
1212 1218 ScmModel().update_nodes(
1213 1219 user=self._rhodecode_db_user.user_id,
1214 1220 repo=self.db_repo,
1215 1221 message=message,
1216 1222 nodes=mapping,
1217 1223 parent_commit=c.commit,
1218 1224 )
1219 1225
1220 1226 h.flash(
1221 1227 _('Successfully committed changes to file `{}`').format(
1222 1228 h.escape(f_path)), category='success')
1223 1229 except Exception:
1224 1230 log.exception('Error occurred during commit')
1225 1231 h.flash(_('Error occurred during commit'), category='error')
1226 1232 raise HTTPFound(
1227 1233 h.route_path('repo_commit', repo_name=self.db_repo_name,
1228 1234 commit_id='tip'))
1229 1235
1230 1236 @LoginRequired()
1231 1237 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1232 1238 @view_config(
1233 1239 route_name='repo_files_add_file', request_method='GET',
1234 1240 renderer='rhodecode:templates/files/files_add.mako')
1235 1241 def repo_files_add_file(self):
1236 1242 _ = self.request.translate
1237 1243 c = self.load_default_context()
1238 1244 commit_id, f_path = self._get_commit_and_path()
1239 1245
1240 1246 self._ensure_not_locked()
1241 1247
1242 1248 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1243 1249 if c.commit is None:
1244 1250 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1245 1251 c.default_message = (_('Added file via RhodeCode Enterprise'))
1246 1252 c.f_path = f_path.lstrip('/') # ensure not relative path
1247 1253
1248 1254 if self.rhodecode_vcs_repo.is_empty:
1249 1255 # for empty repository we cannot check for current branch, we rely on
1250 1256 # c.commit.branch instead
1251 1257 _branch_name = c.commit.branch
1252 1258 is_head = True
1253 1259 else:
1254 1260 _branch_name, _sha_commit_id, is_head = \
1255 1261 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1256 1262
1257 1263 if not is_head:
1258 1264 h.flash(_('You can only add files with commit '
1259 1265 'being a valid branch head.'), category='warning')
1260 1266 raise HTTPFound(
1261 1267 h.route_path('repo_files',
1262 1268 repo_name=self.db_repo_name, commit_id='tip',
1263 1269 f_path=f_path))
1264 1270
1265 1271 self.check_branch_permission(_branch_name)
1266 1272
1267 1273 return self._get_template_context(c)
1268 1274
1269 1275 @LoginRequired()
1270 1276 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1271 1277 @CSRFRequired()
1272 1278 @view_config(
1273 1279 route_name='repo_files_create_file', request_method='POST',
1274 1280 renderer=None)
1275 1281 def repo_files_create_file(self):
1276 1282 _ = self.request.translate
1277 1283 c = self.load_default_context()
1278 1284 commit_id, f_path = self._get_commit_and_path()
1279 1285
1280 1286 self._ensure_not_locked()
1281 1287
1282 1288 r_post = self.request.POST
1283 1289
1284 1290 c.commit = self._get_commit_or_redirect(
1285 1291 commit_id, redirect_after=False)
1286 1292 if c.commit is None:
1287 1293 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1288 1294
1289 1295 if self.rhodecode_vcs_repo.is_empty:
1290 1296 # for empty repository we cannot check for current branch, we rely on
1291 1297 # c.commit.branch instead
1292 1298 _branch_name = c.commit.branch
1293 1299 is_head = True
1294 1300 else:
1295 1301 _branch_name, _sha_commit_id, is_head = \
1296 1302 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1297 1303
1298 1304 if not is_head:
1299 1305 h.flash(_('You can only add files with commit '
1300 1306 'being a valid branch head.'), category='warning')
1301 1307 raise HTTPFound(
1302 1308 h.route_path('repo_files',
1303 1309 repo_name=self.db_repo_name, commit_id='tip',
1304 1310 f_path=f_path))
1305 1311
1306 1312 self.check_branch_permission(_branch_name)
1307 1313
1308 1314 c.default_message = (_('Added file via RhodeCode Enterprise'))
1309 1315 c.f_path = f_path
1310 1316 unix_mode = 0
1311 1317 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1312 1318
1313 1319 message = r_post.get('message') or c.default_message
1314 1320 filename = r_post.get('filename')
1315 1321 location = r_post.get('location', '') # dir location
1316 1322 file_obj = r_post.get('upload_file', None)
1317 1323
1318 1324 if file_obj is not None and hasattr(file_obj, 'filename'):
1319 1325 filename = r_post.get('filename_upload')
1320 1326 content = file_obj.file
1321 1327
1322 1328 if hasattr(content, 'file'):
1323 1329 # non posix systems store real file under file attr
1324 1330 content = content.file
1325 1331
1326 1332 if self.rhodecode_vcs_repo.is_empty:
1327 1333 default_redirect_url = h.route_path(
1328 1334 'repo_summary', repo_name=self.db_repo_name)
1329 1335 else:
1330 1336 default_redirect_url = h.route_path(
1331 1337 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1332 1338
1333 1339 # If there's no commit, redirect to repo summary
1334 1340 if type(c.commit) is EmptyCommit:
1335 1341 redirect_url = h.route_path(
1336 1342 'repo_summary', repo_name=self.db_repo_name)
1337 1343 else:
1338 1344 redirect_url = default_redirect_url
1339 1345
1340 1346 if not filename:
1341 1347 h.flash(_('No filename'), category='warning')
1342 1348 raise HTTPFound(redirect_url)
1343 1349
1344 1350 # extract the location from filename,
1345 1351 # allows using foo/bar.txt syntax to create subdirectories
1346 1352 subdir_loc = filename.rsplit('/', 1)
1347 1353 if len(subdir_loc) == 2:
1348 1354 location = os.path.join(location, subdir_loc[0])
1349 1355
1350 1356 # strip all crap out of file, just leave the basename
1351 1357 filename = os.path.basename(filename)
1352 1358 node_path = os.path.join(location, filename)
1353 1359 author = self._rhodecode_db_user.full_contact
1354 1360
1355 1361 try:
1356 1362 nodes = {
1357 1363 node_path: {
1358 1364 'content': content
1359 1365 }
1360 1366 }
1361 1367 ScmModel().create_nodes(
1362 1368 user=self._rhodecode_db_user.user_id,
1363 1369 repo=self.db_repo,
1364 1370 message=message,
1365 1371 nodes=nodes,
1366 1372 parent_commit=c.commit,
1367 1373 author=author,
1368 1374 )
1369 1375
1370 1376 h.flash(
1371 1377 _('Successfully committed new file `{}`').format(
1372 1378 h.escape(node_path)), category='success')
1373 1379 except NonRelativePathError:
1374 1380 log.exception('Non Relative path found')
1375 1381 h.flash(_(
1376 1382 'The location specified must be a relative path and must not '
1377 1383 'contain .. in the path'), category='warning')
1378 1384 raise HTTPFound(default_redirect_url)
1379 1385 except (NodeError, NodeAlreadyExistsError) as e:
1380 1386 h.flash(_(h.escape(e)), category='error')
1381 1387 except Exception:
1382 1388 log.exception('Error occurred during commit')
1383 1389 h.flash(_('Error occurred during commit'), category='error')
1384 1390
1385 1391 raise HTTPFound(default_redirect_url)
General Comments 0
You need to be logged in to leave comments. Login now