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