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