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